From ff022208904750ba2e07908b54453dd151f8b118 Mon Sep 17 00:00:00 2001 From: Alaa Alghazouli Date: Thu, 12 Mar 2026 23:03:44 +0100 Subject: [PATCH 01/26] fix: add initdbFlags to embedded postgres ctor types --- cli/src/commands/worktree.ts | 1 + packages/db/src/migration-runtime.ts | 1 + server/src/index.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/cli/src/commands/worktree.ts b/cli/src/commands/worktree.ts index 7311793b..e807dafb 100644 --- a/cli/src/commands/worktree.ts +++ b/cli/src/commands/worktree.ts @@ -83,6 +83,7 @@ type EmbeddedPostgresCtor = new (opts: { password: string; port: number; persistent: boolean; + initdbFlags?: string[]; onLog?: (message: unknown) => void; onError?: (message: unknown) => void; }) => EmbeddedPostgresInstance; diff --git a/packages/db/src/migration-runtime.ts b/packages/db/src/migration-runtime.ts index 10b7b9b1..e07bdf04 100644 --- a/packages/db/src/migration-runtime.ts +++ b/packages/db/src/migration-runtime.ts @@ -17,6 +17,7 @@ type EmbeddedPostgresCtor = new (opts: { password: string; port: number; persistent: boolean; + initdbFlags?: string[]; onLog?: (message: unknown) => void; onError?: (message: unknown) => void; }) => EmbeddedPostgresInstance; diff --git a/server/src/index.ts b/server/src/index.ts index 50c6a7b2..0479f878 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -53,6 +53,7 @@ type EmbeddedPostgresCtor = new (opts: { password: string; port: number; persistent: boolean; + initdbFlags?: string[]; onLog?: (message: unknown) => void; onError?: (message: unknown) => void; }) => EmbeddedPostgresInstance; From 3d2abbde7223801edea65c15799c6f060a04012b Mon Sep 17 00:00:00 2001 From: Sigmabrogz Date: Fri, 13 Mar 2026 00:42:28 +0000 Subject: [PATCH 02/26] fix(openclaw-gateway): catch challengePromise rejection to prevent unhandled rejection process crash Resolves #727 Signed-off-by: Sigmabrogz --- packages/adapters/openclaw-gateway/src/server/execute.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/adapters/openclaw-gateway/src/server/execute.ts b/packages/adapters/openclaw-gateway/src/server/execute.ts index eaacbd33..f1c85c11 100644 --- a/packages/adapters/openclaw-gateway/src/server/execute.ts +++ b/packages/adapters/openclaw-gateway/src/server/execute.ts @@ -605,6 +605,7 @@ class GatewayWsClient { this.resolveChallenge = resolve; this.rejectChallenge = reject; }); + this.challengePromise.catch(() => {}); } async connect( From 71e1bc260d18a8ec00a18f3f6d2bab1b33134114 Mon Sep 17 00:00:00 2001 From: Andrew Orobator Date: Tue, 17 Mar 2026 09:35:57 -0400 Subject: [PATCH 03/26] Clarify linked ticket references in Paperclip skill Co-Authored-By: Paperclip --- skills/paperclip/SKILL.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index 0e6044e6..eb098c49 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -71,6 +71,8 @@ Read enough ancestor/comment context to understand _why_ the task exists and wha **Step 8 — Update status and communicate.** Always include the run ID header. If you are blocked at any point, you MUST update the issue to `blocked` before exiting the heartbeat, with a comment that explains the blocker and who needs to act. +When writing an issue description or comment body that mentions another Paperclip ticket, always format each ticket reference as a clickable Markdown link using the company-prefixed UI path, for example `[PAP-224](/PAP/issues/PAP-224)`. + ```json PATCH /api/issues/{issueId} Headers: X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID @@ -144,12 +146,19 @@ Access control: ## Comment Style (Required) -When posting issue comments, use concise markdown with: +When posting issue comments or writing issue descriptions, use concise markdown with: - a short status line - bullets for what changed / what is blocked - links to related entities when available +**Ticket references are links (required):** If you mention another issue identifier such as `PAP-224`, `ZED-24`, or any `{PREFIX}-{NUMBER}` ticket id inside a comment body or issue description, wrap it in a Markdown link: + +- `[PAP-224](/PAP/issues/PAP-224)` +- `[ZED-24](/ZED/issues/ZED-24)` + +Never leave bare ticket ids in issue descriptions or comments when a clickable internal link can be provided. + **Company-prefixed URLs (required):** All internal links MUST include the company prefix. Derive the prefix from any issue identifier you have (e.g., `PAP-315` → prefix is `PAP`). Use this prefix in all UI links: - Issues: `//issues/` (e.g., `/PAP/issues/PAP-224`) @@ -172,6 +181,7 @@ Submitted CTO hire request and linked it for board review. - Approval: [ca6ba09d](/PAP/approvals/ca6ba09d-b558-4a53-a552-e7ef87e54a1b) - Pending agent: [CTO draft](/PAP/agents/cto) - Source issue: [PC-142](/PAP/issues/PC-142) +- Depends on: [PAP-224](/PAP/issues/PAP-224) ``` ## Planning (Required when planning requested) From 7a08fbd37001702b448fa3225b5308d412ab74ac Mon Sep 17 00:00:00 2001 From: Andrew Orobator Date: Tue, 17 Mar 2026 09:43:47 -0400 Subject: [PATCH 04/26] Reduce duplicate ticket-link guidance Co-Authored-By: Paperclip --- skills/paperclip/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index eb098c49..1b895f16 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -71,7 +71,7 @@ Read enough ancestor/comment context to understand _why_ the task exists and wha **Step 8 — Update status and communicate.** Always include the run ID header. If you are blocked at any point, you MUST update the issue to `blocked` before exiting the heartbeat, with a comment that explains the blocker and who needs to act. -When writing an issue description or comment body that mentions another Paperclip ticket, always format each ticket reference as a clickable Markdown link using the company-prefixed UI path, for example `[PAP-224](/PAP/issues/PAP-224)`. +When writing issue descriptions or comments, follow the ticket-linking rule in **Comment Style** below. ```json PATCH /api/issues/{issueId} From c539fcde8b73fe7abb52ac48556dfdcf665373a1 Mon Sep 17 00:00:00 2001 From: Andrew Orobator Date: Tue, 17 Mar 2026 09:52:32 -0400 Subject: [PATCH 05/26] Fix stale Paperclip issue link example Co-Authored-By: Paperclip --- skills/paperclip/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index 1b895f16..054c6a87 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -180,7 +180,7 @@ Submitted CTO hire request and linked it for board review. - Approval: [ca6ba09d](/PAP/approvals/ca6ba09d-b558-4a53-a552-e7ef87e54a1b) - Pending agent: [CTO draft](/PAP/agents/cto) -- Source issue: [PC-142](/PAP/issues/PC-142) +- Source issue: [PAP-142](/PAP/issues/PAP-142) - Depends on: [PAP-224](/PAP/issues/PAP-224) ``` From f37e0aa7b3ec3c2da0afb6d8ec30ab96e961991b Mon Sep 17 00:00:00 2001 From: Richard Anaya Date: Wed, 18 Mar 2026 20:34:08 -0700 Subject: [PATCH 06/26] fix(pi-local): pass --skill flag for paperclip skills and enable pi_local in adapter dropdown - Add --skill ~/.pi/agent/skills to pi CLI invocation so it loads the paperclip skill - Enable pi_local in the AgentConfigForm adapter type dropdown (was showing as coming soon) --- packages/adapters/pi-local/src/server/execute.ts | 4 ++++ ui/src/components/AgentConfigForm.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/adapters/pi-local/src/server/execute.ts b/packages/adapters/pi-local/src/server/execute.ts index 27dce547..e6c4df97 100644 --- a/packages/adapters/pi-local/src/server/execute.ts +++ b/packages/adapters/pi-local/src/server/execute.ts @@ -336,6 +336,10 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0) args.push(...extraArgs); return args; diff --git a/ui/src/components/AgentConfigForm.tsx b/ui/src/components/AgentConfigForm.tsx index 3b3d53c0..69c31919 100644 --- a/ui/src/components/AgentConfigForm.tsx +++ b/ui/src/components/AgentConfigForm.tsx @@ -939,7 +939,7 @@ function AdapterEnvironmentResult({ result }: { result: AdapterEnvironmentTestRe /* ---- Internal sub-components ---- */ -const ENABLED_ADAPTER_TYPES = new Set(["claude_local", "codex_local", "gemini_local", "opencode_local", "cursor"]); +const ENABLED_ADAPTER_TYPES = new Set(["claude_local", "codex_local", "gemini_local", "opencode_local", "pi_local", "cursor"]); /** Display list includes all real adapter types plus UI-only coming-soon entries. */ const ADAPTER_DISPLAY_LIST: { value: string; label: string; comingSoon: boolean }[] = [ From 5536e6b91e9c6d872612c41e98c5e56af6297a92 Mon Sep 17 00:00:00 2001 From: Richard Anaya Date: Wed, 18 Mar 2026 20:40:55 -0700 Subject: [PATCH 07/26] fix(pi-local): ensure skills directory exists before passing --skill flag - Hoist PI_AGENT_SKILLS_DIR to module-level constant to avoid duplicate path computation - Always create ~/.pi/agent/skills directory before early return in ensurePiSkillsInjected, so the path is valid when --skill flag is passed --- .../adapters/pi-local/src/server/execute.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/adapters/pi-local/src/server/execute.ts b/packages/adapters/pi-local/src/server/execute.ts index e6c4df97..697387cd 100644 --- a/packages/adapters/pi-local/src/server/execute.ts +++ b/packages/adapters/pi-local/src/server/execute.ts @@ -26,6 +26,7 @@ import { ensurePiModelConfiguredAndAvailable } from "./models.js"; const __moduleDir = path.dirname(fileURLToPath(import.meta.url)); const PAPERCLIP_SESSIONS_DIR = path.join(os.homedir(), ".pi", "paperclips"); +const PI_AGENT_SKILLS_DIR = path.join(os.homedir(), ".pi", "agent", "skills"); function firstNonEmptyLine(text: string): string { return ( @@ -56,35 +57,35 @@ function resolvePiBiller(env: Record, provider: string | null): async function ensurePiSkillsInjected(onLog: AdapterExecutionContext["onLog"]) { const skillsEntries = await listPaperclipSkillEntries(__moduleDir); + + await fs.mkdir(PI_AGENT_SKILLS_DIR, { recursive: true }); if (skillsEntries.length === 0) return; - const piSkillsHome = path.join(os.homedir(), ".pi", "agent", "skills"); - await fs.mkdir(piSkillsHome, { recursive: true }); const removedSkills = await removeMaintainerOnlySkillSymlinks( - piSkillsHome, + PI_AGENT_SKILLS_DIR, skillsEntries.map((entry) => entry.name), ); for (const skillName of removedSkills) { await onLog( "stderr", - `[paperclip] Removed maintainer-only Pi skill "${skillName}" from ${piSkillsHome}\n`, + `[paperclip] Removed maintainer-only Pi skill "${skillName}" from ${PI_AGENT_SKILLS_DIR}\n`, ); } for (const entry of skillsEntries) { - const target = path.join(piSkillsHome, entry.name); + const target = path.join(PI_AGENT_SKILLS_DIR, entry.name); try { const result = await ensurePaperclipSkillSymlink(entry.source, target); if (result === "skipped") continue; await onLog( "stderr", - `[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Pi skill "${entry.name}" into ${piSkillsHome}\n`, + `[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Pi skill "${entry.name}" into ${PI_AGENT_SKILLS_DIR}\n`, ); } catch (err) { await onLog( "stderr", - `[paperclip] Failed to inject Pi skill "${entry.name}" into ${piSkillsHome}: ${err instanceof Error ? err.message : String(err)}\n`, + `[paperclip] Failed to inject Pi skill "${entry.name}" into ${PI_AGENT_SKILLS_DIR}: ${err instanceof Error ? err.message : String(err)}\n`, ); } } @@ -337,8 +338,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise 0) args.push(...extraArgs); From aa854e7efed5f1b85d4019ec837940eeb94eaeca Mon Sep 17 00:00:00 2001 From: Richard Anaya Date: Wed, 18 Mar 2026 20:51:59 -0700 Subject: [PATCH 08/26] fix: include toolName in tool_result transcript entries for Pi adapter When tool_result entries arrive without a matching tool_call, the transcript was showing generic 'tool' as the name. Now pl-local parses toolName from tool_execution_end events and passes it through, so the UI can display the actual tool name (e.g., 'bash', 'Read', 'Ls') instead of 'tool'. --- packages/adapter-utils/src/types.ts | 2 +- packages/adapters/pi-local/src/ui/parse-stdout.ts | 2 ++ ui/src/components/transcript/RunTranscriptView.tsx | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/adapter-utils/src/types.ts b/packages/adapter-utils/src/types.ts index f907d4b4..e7639593 100644 --- a/packages/adapter-utils/src/types.ts +++ b/packages/adapter-utils/src/types.ts @@ -246,7 +246,7 @@ export type TranscriptEntry = | { kind: "thinking"; ts: string; text: string; delta?: boolean } | { kind: "user"; ts: string; text: string } | { kind: "tool_call"; ts: string; name: string; input: unknown; toolUseId?: string } - | { kind: "tool_result"; ts: string; toolUseId: string; content: string; isError: boolean } + | { kind: "tool_result"; ts: string; toolUseId: string; toolName?: string; content: string; isError: boolean } | { kind: "init"; ts: string; model: string; sessionId: string } | { kind: "result"; ts: string; text: string; inputTokens: number; outputTokens: number; cachedTokens: number; costUsd: number; subtype: string; isError: boolean; errors: string[] } | { kind: "stderr"; ts: string; text: string } diff --git a/packages/adapters/pi-local/src/ui/parse-stdout.ts b/packages/adapters/pi-local/src/ui/parse-stdout.ts index b80fe5f1..7ded5633 100644 --- a/packages/adapters/pi-local/src/ui/parse-stdout.ts +++ b/packages/adapters/pi-local/src/ui/parse-stdout.ts @@ -130,6 +130,7 @@ export function parsePiStdoutLine(line: string, ts: string): TranscriptEntry[] { if (type === "tool_execution_end") { const toolCallId = asString(parsed.toolCallId); + const toolName = asString(parsed.toolName); const result = parsed.result; const isError = parsed.isError === true; const contentStr = typeof result === "string" ? result : JSON.stringify(result); @@ -138,6 +139,7 @@ export function parsePiStdoutLine(line: string, ts: string): TranscriptEntry[] { kind: "tool_result", ts, toolUseId: toolCallId || "unknown", + toolName, content: contentStr, isError, }]; diff --git a/ui/src/components/transcript/RunTranscriptView.tsx b/ui/src/components/transcript/RunTranscriptView.tsx index 5f42ec0e..cd52dbc1 100644 --- a/ui/src/components/transcript/RunTranscriptView.tsx +++ b/ui/src/components/transcript/RunTranscriptView.tsx @@ -400,7 +400,7 @@ export function normalizeTranscript(entries: TranscriptEntry[], streaming: boole type: "tool", ts: entry.ts, endTs: entry.ts, - name: "tool", + name: entry.toolName ?? "tool", toolUseId: entry.toolUseId, input: null, result: entry.content, From 47a6d86174e7be38b1154207a27971448273099f Mon Sep 17 00:00:00 2001 From: Richard Anaya Date: Wed, 18 Mar 2026 20:57:32 -0700 Subject: [PATCH 09/26] fix: include toolName in tool_result entries from turn_end toolResults The turn_end event includes toolResults array with toolName. Previously the parsing only included toolCallId, now we also extract toolName so the UI can display the correct tool name even when tool_result entries arrive without a preceding tool_call. --- packages/adapters/pi-local/src/ui/parse-stdout.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/adapters/pi-local/src/ui/parse-stdout.ts b/packages/adapters/pi-local/src/ui/parse-stdout.ts index 7ded5633..f7b5b7f9 100644 --- a/packages/adapters/pi-local/src/ui/parse-stdout.ts +++ b/packages/adapters/pi-local/src/ui/parse-stdout.ts @@ -77,6 +77,7 @@ export function parsePiStdoutLine(line: string, ts: string): TranscriptEntry[] { kind: "tool_result", ts, toolUseId: asString(tr.toolCallId, "unknown"), + toolName: asString(tr.toolName), content: contentStr, isError, }); From 6eb9545a72be62987ce86e7a25c41ca407ddb00a Mon Sep 17 00:00:00 2001 From: Richard Anaya Date: Wed, 18 Mar 2026 21:02:53 -0700 Subject: [PATCH 10/26] fix: extract text content from Pi's tool result content arrays Pi returns tool results in format: {"content": [{"type": "text", "text": "..."}]} Previously we were JSON.stringify-ing the whole object, showing as: {"content":[{"type":"text","text":"..."}]} Now we extract the actual text content for cleaner display. --- .../adapters/pi-local/src/ui/parse-stdout.ts | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/adapters/pi-local/src/ui/parse-stdout.ts b/packages/adapters/pi-local/src/ui/parse-stdout.ts index f7b5b7f9..57a78725 100644 --- a/packages/adapters/pi-local/src/ui/parse-stdout.ts +++ b/packages/adapters/pi-local/src/ui/parse-stdout.ts @@ -72,7 +72,17 @@ export function parsePiStdoutLine(line: string, ts: string): TranscriptEntry[] { for (const tr of toolResults) { const content = tr.content; const isError = tr.isError === true; - const contentStr = typeof content === "string" ? content : JSON.stringify(content); + + // Extract text from Pi's content array format + let contentStr: string; + if (typeof content === "string") { + contentStr = content; + } else if (Array.isArray(content)) { + contentStr = extractTextContent(content as Array<{ type: string; text?: string }>); + } else { + contentStr = JSON.stringify(content); + } + entries.push({ kind: "tool_result", ts, @@ -134,7 +144,21 @@ export function parsePiStdoutLine(line: string, ts: string): TranscriptEntry[] { const toolName = asString(parsed.toolName); const result = parsed.result; const isError = parsed.isError === true; - const contentStr = typeof result === "string" ? result : JSON.stringify(result); + + // Extract text from Pi's content array format: {"content": [{"type": "text", "text": "..."}]} + let contentStr: string; + if (typeof result === "string") { + contentStr = result; + } else if (result && typeof result === "object") { + const resultObj = result as Record; + if (Array.isArray(resultObj.content)) { + contentStr = extractTextContent(resultObj.content as Array<{ type: string; text?: string }>); + } else { + contentStr = JSON.stringify(result); + } + } else { + contentStr = JSON.stringify(result); + } return [{ kind: "tool_result", From 1cd61601f399150646c3655ed266a0d92c6bb88f Mon Sep 17 00:00:00 2001 From: Richard Anaya Date: Wed, 18 Mar 2026 21:11:03 -0700 Subject: [PATCH 11/26] fix: handle direct array format for Pi tool results Pi sometimes sends tool results as a direct array [{"type":"text","text":"..."}] rather than wrapped in {"content": [...]}. Now handles both formats to properly extract text content instead of showing raw JSON. --- packages/adapters/pi-local/src/ui/parse-stdout.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/adapters/pi-local/src/ui/parse-stdout.ts b/packages/adapters/pi-local/src/ui/parse-stdout.ts index 57a78725..64a553e9 100644 --- a/packages/adapters/pi-local/src/ui/parse-stdout.ts +++ b/packages/adapters/pi-local/src/ui/parse-stdout.ts @@ -145,13 +145,18 @@ export function parsePiStdoutLine(line: string, ts: string): TranscriptEntry[] { const result = parsed.result; const isError = parsed.isError === true; - // Extract text from Pi's content array format: {"content": [{"type": "text", "text": "..."}]} + // Extract text from Pi's content array format + // Can be: {"content": [{"type": "text", "text": "..."}]} or [{"type": "text", "text": "..."}] let contentStr: string; if (typeof result === "string") { contentStr = result; + } else if (Array.isArray(result)) { + // Direct array format: result is [{"type": "text", "text": "..."}] + contentStr = extractTextContent(result as Array<{ type: string; text?: string }>); } else if (result && typeof result === "object") { const resultObj = result as Record; if (Array.isArray(resultObj.content)) { + // Wrapped format: result is {"content": [{"type": "text", "text": "..."}]} contentStr = extractTextContent(resultObj.content as Array<{ type: string; text?: string }>); } else { contentStr = JSON.stringify(result); From 9c5a31ed45a87ad10f8b8a654433df09e037844b Mon Sep 17 00:00:00 2001 From: dotta Date: Wed, 18 Mar 2026 16:28:40 -0500 Subject: [PATCH 12/26] Allow CEO agents to update company branding (name, description, logo, color) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add updateCompanyBrandingSchema restricting agent-updatable fields to name, description, brandColor, and logoAssetId - Update PATCH /api/companies/:companyId to allow CEO agents with branding-only fields while keeping admin fields (status, budget, etc.) board-only - Allow agents to GET /api/companies/:companyId for reading company info - issuePrefix (company slug) remains protected — not in any update schema - Document branding APIs in SKILL.md quick reference and api-reference.md Co-Authored-By: Paperclip --- packages/shared/src/index.ts | 2 + packages/shared/src/validators/company.ts | 10 +++++ packages/shared/src/validators/index.ts | 2 + server/src/routes/companies.ts | 40 ++++++++++++++++---- skills/paperclip/SKILL.md | 28 ++++++++++++++ skills/paperclip/references/api-reference.md | 20 ++++++++++ 6 files changed, 95 insertions(+), 7 deletions(-) diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 2b87a556..665b47c4 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -253,8 +253,10 @@ export { export { createCompanySchema, updateCompanySchema, + updateCompanyBrandingSchema, type CreateCompany, type UpdateCompany, + type UpdateCompanyBranding, createAgentSchema, createAgentHireSchema, updateAgentSchema, diff --git a/packages/shared/src/validators/company.ts b/packages/shared/src/validators/company.ts index bb4851f4..d3e77af3 100644 --- a/packages/shared/src/validators/company.ts +++ b/packages/shared/src/validators/company.ts @@ -22,3 +22,13 @@ export const updateCompanySchema = createCompanySchema }); export type UpdateCompany = z.infer; + +/** Branding-only subset that CEO agents may update. */ +export const updateCompanyBrandingSchema = z.object({ + name: z.string().min(1).optional(), + description: z.string().nullable().optional(), + brandColor: z.string().regex(/^#[0-9a-fA-F]{6}$/).nullable().optional(), + logoAssetId: logoAssetIdSchema, +}); + +export type UpdateCompanyBranding = z.infer; diff --git a/packages/shared/src/validators/index.ts b/packages/shared/src/validators/index.ts index ed9dcdd0..6979e467 100644 --- a/packages/shared/src/validators/index.ts +++ b/packages/shared/src/validators/index.ts @@ -15,8 +15,10 @@ export { export { createCompanySchema, updateCompanySchema, + updateCompanyBrandingSchema, type CreateCompany, type UpdateCompany, + type UpdateCompanyBranding, } from "./company.js"; export { portabilityIncludeSchema, diff --git a/server/src/routes/companies.ts b/server/src/routes/companies.ts index bb6585a2..f11e7cae 100644 --- a/server/src/routes/companies.ts +++ b/server/src/routes/companies.ts @@ -6,11 +6,13 @@ import { companyPortabilityPreviewSchema, createCompanySchema, updateCompanySchema, + updateCompanyBrandingSchema, } from "@paperclipai/shared"; import { forbidden } from "../errors.js"; import { validate } from "../middleware/validate.js"; import { accessService, + agentService, budgetService, companyPortabilityService, companyService, @@ -58,9 +60,12 @@ export function companyRoutes(db: Db) { }); router.get("/:companyId", async (req, res) => { - assertBoard(req); const companyId = req.params.companyId as string; assertCompanyAccess(req, companyId); + // Allow agents (CEO) to read their own company; board always allowed + if (req.actor.type !== "agent") { + assertBoard(req); + } const company = await svc.getById(companyId); if (!company) { res.status(404).json({ error: "Company not found" }); @@ -144,23 +149,44 @@ export function companyRoutes(db: Db) { res.status(201).json(company); }); - router.patch("/:companyId", validate(updateCompanySchema), async (req, res) => { - assertBoard(req); + router.patch("/:companyId", async (req, res) => { const companyId = req.params.companyId as string; assertCompanyAccess(req, companyId); - const company = await svc.update(companyId, req.body); + + const actor = getActorInfo(req); + let body: Record; + + if (req.actor.type === "agent") { + // Only CEO agents may update company branding fields + const agentSvc = agentService(db); + const actorAgent = req.actor.agentId ? await agentSvc.getById(req.actor.agentId) : null; + if (!actorAgent || actorAgent.role !== "ceo") { + throw forbidden("Only CEO agents or board users may update company settings"); + } + if (actorAgent.companyId !== companyId) { + throw forbidden("Agent key cannot access another company"); + } + body = updateCompanyBrandingSchema.parse(req.body); + } else { + assertBoard(req); + body = updateCompanySchema.parse(req.body); + } + + const company = await svc.update(companyId, body); if (!company) { res.status(404).json({ error: "Company not found" }); return; } await logActivity(db, { companyId, - actorType: "user", - actorId: req.actor.userId ?? "board", + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, action: "company.updated", entityType: "company", entityId: companyId, - details: req.body, + details: body, }); res.json(company); }); diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index 054c6a87..5335dcbe 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -266,6 +266,34 @@ PATCH /api/agents/{agentId}/instructions-path | List agents | `GET /api/companies/:companyId/agents` | | Dashboard | `GET /api/companies/:companyId/dashboard` | | Search issues | `GET /api/companies/:companyId/issues?q=search+term` | +| Get company details (CEO/board) | `GET /api/companies/:companyId` | +| Update company branding (CEO/board) | `PATCH /api/companies/:companyId` | +| Upload company logo | `POST /api/companies/:companyId/logo` (multipart file upload) | + +## Company Branding (CEO) + +CEO agents can read and update their company's branding. Board users have full access to all company fields. + +**Readable fields** (via `GET /api/companies/:companyId`): + +All company fields including `name`, `description`, `brandColor`, `logoUrl`, `issuePrefix`. + +**Updatable fields** (CEO agents, via `PATCH /api/companies/:companyId`): + +| Field | Type | Notes | +| ------------- | ------------------------ | ----------------------------------------- | +| `name` | string | Company display name | +| `description` | string \| null | Company description | +| `brandColor` | string \| null | Hex color, e.g. `#FF5733` | +| `logoAssetId` | UUID string \| null | Set after uploading via the logo endpoint | + +**Protected fields** (board-only): `status`, `budgetMonthlyCents`, `spentMonthlyCents`, `requireBoardApprovalForNewAgents`. The `issuePrefix` (company slug) cannot be changed via API. + +**Logo upload flow:** + +1. Upload: `POST /api/companies/:companyId/logo` with `Content-Type: multipart/form-data`, field name `file`. Accepts PNG, JPEG, WebP, GIF, SVG (max 10 MB). +2. Set: `PATCH /api/companies/:companyId` with `{ "logoAssetId": "" }`. +3. Clear: `PATCH /api/companies/:companyId` with `{ "logoAssetId": null }`. ## Searching Issues diff --git a/skills/paperclip/references/api-reference.md b/skills/paperclip/references/api-reference.md index cbf5ef05..6e4ae4cb 100644 --- a/skills/paperclip/references/api-reference.md +++ b/skills/paperclip/references/api-reference.md @@ -280,6 +280,26 @@ GET /api/companies/{companyId}/dashboard — health summary: agent/task counts, Use the dashboard for situational awareness, especially if you're a manager or CEO. +## Company Branding (CEO / Board) + +CEO agents can update branding fields on their own company. Board users can update all fields. + +``` +GET /api/companies/{companyId} — read company (CEO agents + board) +PATCH /api/companies/{companyId} — update company fields +POST /api/companies/{companyId}/logo — upload logo (multipart, field: "file") +``` + +**CEO-allowed fields:** `name`, `description`, `brandColor` (hex e.g. `#FF5733` or null), `logoAssetId` (UUID or null). + +**Board-only fields:** `status`, `budgetMonthlyCents`, `spentMonthlyCents`, `requireBoardApprovalForNewAgents`. + +**Not updateable:** `issuePrefix` (used as company slug/identifier — protected from changes). + +**Logo workflow:** +1. `POST /api/companies/{companyId}/logo` with file upload → returns `{ assetId }`. +2. `PATCH /api/companies/{companyId}` with `{ "logoAssetId": "" }`. + ## OpenClaw Invite Prompt (CEO) Use this endpoint to generate a short-lived OpenClaw onboarding invite prompt: From 915a3ff3cede45ee706e4c403bb371c522bfbc85 Mon Sep 17 00:00:00 2001 From: dotta Date: Wed, 18 Mar 2026 16:30:59 -0500 Subject: [PATCH 13/26] fix: default comment reassign to last commenter who isn't me When commenting on an issue, the reassign dropdown now defaults to the last commenter who is not the current user, preventing accidental self-reassignment. Falls back to the current issue assignee if no other commenters exist. Co-Authored-By: Paperclip Co-Authored-By: Claude Opus 4.6 --- ui/src/pages/IssueDetail.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index a6ac9c76..7f98db5e 100644 --- a/ui/src/pages/IssueDetail.tsx +++ b/ui/src/pages/IssueDetail.tsx @@ -376,10 +376,20 @@ export function IssueDetail() { }, [agents, currentUserId]); const currentAssigneeValue = useMemo(() => { + // Default to the last commenter who is not "me" so the user doesn't + // accidentally reassign to themselves when commenting on their own issue. + if (comments && comments.length > 0 && currentUserId) { + for (let i = comments.length - 1; i >= 0; i--) { + const c = comments[i]; + if (c.authorAgentId) return `agent:${c.authorAgentId}`; + if (c.authorUserId && c.authorUserId !== currentUserId) + return `user:${c.authorUserId}`; + } + } if (issue?.assigneeAgentId) return `agent:${issue.assigneeAgentId}`; if (issue?.assigneeUserId) return `user:${issue.assigneeUserId}`; return ""; - }, [issue?.assigneeAgentId, issue?.assigneeUserId]); + }, [issue?.assigneeAgentId, issue?.assigneeUserId, comments, currentUserId]); const commentsWithRunMeta = useMemo(() => { const runMetaByCommentId = new Map(); From 58a3cbd654161e634adadebcf6d3609558969e84 Mon Sep 17 00:00:00 2001 From: dotta Date: Wed, 18 Mar 2026 21:16:37 -0500 Subject: [PATCH 14/26] Route non-fatal adapter notices to stdout Co-Authored-By: Paperclip --- packages/adapters/claude-local/src/server/execute.ts | 4 ++-- packages/adapters/codex-local/src/server/execute.ts | 6 +++--- packages/adapters/cursor-local/src/server/execute.ts | 8 ++++---- packages/adapters/gemini-local/src/server/execute.ts | 8 ++++---- packages/adapters/opencode-local/src/server/execute.ts | 8 ++++---- packages/adapters/pi-local/src/server/execute.ts | 8 ++++---- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/adapters/claude-local/src/server/execute.ts b/packages/adapters/claude-local/src/server/execute.ts index cd1f0f15..415ea7bd 100644 --- a/packages/adapters/claude-local/src/server/execute.ts +++ b/packages/adapters/claude-local/src/server/execute.ts @@ -369,7 +369,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise Date: Wed, 18 Mar 2026 21:26:36 -0500 Subject: [PATCH 15/26] Show agent name in inbox approval labels (e.g. "Hire Agent: Designer") Instead of the generic "Hire Agent" label, display the agent's name from the approval payload for hire_agent approvals across inbox, approval card, and approval detail views. Co-Authored-By: Paperclip Co-Authored-By: Claude Opus 4.6 --- ui/src/components/ApprovalCard.tsx | 4 ++-- ui/src/components/ApprovalPayload.tsx | 9 +++++++++ ui/src/pages/ApprovalDetail.tsx | 4 ++-- ui/src/pages/Inbox.tsx | 4 ++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/ui/src/components/ApprovalCard.tsx b/ui/src/components/ApprovalCard.tsx index ee0a4163..2123fc57 100644 --- a/ui/src/components/ApprovalCard.tsx +++ b/ui/src/components/ApprovalCard.tsx @@ -2,7 +2,7 @@ import { CheckCircle2, XCircle, Clock } from "lucide-react"; import { Link } from "@/lib/router"; import { Button } from "@/components/ui/button"; import { Identity } from "./Identity"; -import { typeLabel, typeIcon, defaultTypeIcon, ApprovalPayloadRenderer } from "./ApprovalPayload"; +import { approvalLabel, typeIcon, defaultTypeIcon, ApprovalPayloadRenderer } from "./ApprovalPayload"; import { timeAgo } from "../lib/timeAgo"; import type { Approval, Agent } from "@paperclipai/shared"; @@ -32,7 +32,7 @@ export function ApprovalCard({ isPending: boolean; }) { const Icon = typeIcon[approval.type] ?? defaultTypeIcon; - const label = typeLabel[approval.type] ?? approval.type; + const label = approvalLabel(approval.type, approval.payload as Record | null); const showResolutionButtons = approval.type !== "budget_override_required" && (approval.status === "pending" || approval.status === "revision_requested"); diff --git a/ui/src/components/ApprovalPayload.tsx b/ui/src/components/ApprovalPayload.tsx index 3eb793d6..97cb1ad5 100644 --- a/ui/src/components/ApprovalPayload.tsx +++ b/ui/src/components/ApprovalPayload.tsx @@ -7,6 +7,15 @@ export const typeLabel: Record = { budget_override_required: "Budget Override", }; +/** Build a contextual label for an approval, e.g. "Hire Agent: Designer" */ +export function approvalLabel(type: string, payload?: Record | null): string { + const base = typeLabel[type] ?? type; + if (type === "hire_agent" && payload?.name) { + return `${base}: ${String(payload.name)}`; + } + return base; +} + export const typeIcon: Record = { hire_agent: UserPlus, approve_ceo_strategy: Lightbulb, diff --git a/ui/src/pages/ApprovalDetail.tsx b/ui/src/pages/ApprovalDetail.tsx index 741f9657..c3f27de6 100644 --- a/ui/src/pages/ApprovalDetail.tsx +++ b/ui/src/pages/ApprovalDetail.tsx @@ -8,7 +8,7 @@ import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { queryKeys } from "../lib/queryKeys"; import { StatusBadge } from "../components/StatusBadge"; import { Identity } from "../components/Identity"; -import { typeLabel, typeIcon, defaultTypeIcon, ApprovalPayloadRenderer } from "../components/ApprovalPayload"; +import { approvalLabel, typeIcon, defaultTypeIcon, ApprovalPayloadRenderer } from "../components/ApprovalPayload"; import { PageSkeleton } from "../components/PageSkeleton"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; @@ -203,7 +203,7 @@ export function ApprovalDetail() {
-

{typeLabel[approval.type] ?? approval.type.replace(/_/g, " ")}

+

{approvalLabel(approval.type, approval.payload as Record | null)}

{approval.id}

diff --git a/ui/src/pages/Inbox.tsx b/ui/src/pages/Inbox.tsx index 2ddd4ed4..74a6144a 100644 --- a/ui/src/pages/Inbox.tsx +++ b/ui/src/pages/Inbox.tsx @@ -18,7 +18,7 @@ import { IssueRow } from "../components/IssueRow"; import { PriorityIcon } from "../components/PriorityIcon"; import { StatusIcon } from "../components/StatusIcon"; import { StatusBadge } from "../components/StatusBadge"; -import { defaultTypeIcon, typeIcon, typeLabel } from "../components/ApprovalPayload"; +import { approvalLabel, defaultTypeIcon, typeIcon } from "../components/ApprovalPayload"; import { timeAgo } from "../lib/timeAgo"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; @@ -253,7 +253,7 @@ function ApprovalInboxRow({ isPending: boolean; }) { const Icon = typeIcon[approval.type] ?? defaultTypeIcon; - const label = typeLabel[approval.type] ?? approval.type; + const label = approvalLabel(approval.type, approval.payload as Record | null); const showResolutionButtons = approval.type !== "budget_override_required" && ACTIONABLE_APPROVAL_STATUSES.has(approval.status); From 72a0e256a89aa29cae8287ddfa17bca9e8e5c20e Mon Sep 17 00:00:00 2001 From: dotta Date: Thu, 19 Mar 2026 07:17:49 -0500 Subject: [PATCH 16/26] Simplify new project dialog: always show repo and local folder fields Remove the workspace setup toggle menu ("Where will work be done on this project?") and instead always display both repo URL and local folder inputs directly. Both fields are marked as optional with help tooltips explaining their purpose. Repo is shown first, local folder second. Co-Authored-By: Paperclip --- ui/src/components/NewProjectDialog.tsx | 162 +++++++++---------------- 1 file changed, 56 insertions(+), 106 deletions(-) diff --git a/ui/src/components/NewProjectDialog.tsx b/ui/src/components/NewProjectDialog.tsx index 6378dfda..6a449b52 100644 --- a/ui/src/components/NewProjectDialog.tsx +++ b/ui/src/components/NewProjectDialog.tsx @@ -23,10 +23,13 @@ import { Calendar, Plus, X, - FolderOpen, - Github, - GitBranch, + HelpCircle, } from "lucide-react"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { PROJECT_COLORS } from "@paperclipai/shared"; import { cn } from "../lib/utils"; import { MarkdownEditor, type MarkdownEditorRef } from "./MarkdownEditor"; @@ -41,8 +44,6 @@ const projectStatuses = [ { value: "cancelled", label: "Cancelled" }, ]; -type WorkspaceSetup = "none" | "local" | "repo" | "both"; - export function NewProjectDialog() { const { newProjectOpen, closeNewProject } = useDialog(); const { selectedCompanyId, selectedCompany } = useCompany(); @@ -53,7 +54,6 @@ export function NewProjectDialog() { const [goalIds, setGoalIds] = useState([]); const [targetDate, setTargetDate] = useState(""); const [expanded, setExpanded] = useState(false); - const [workspaceSetup, setWorkspaceSetup] = useState("none"); const [workspaceLocalPath, setWorkspaceLocalPath] = useState(""); const [workspaceRepoUrl, setWorkspaceRepoUrl] = useState(""); const [workspaceError, setWorkspaceError] = useState(null); @@ -87,7 +87,6 @@ export function NewProjectDialog() { setGoalIds([]); setTargetDate(""); setExpanded(false); - setWorkspaceSetup("none"); setWorkspaceLocalPath(""); setWorkspaceRepoUrl(""); setWorkspaceError(null); @@ -124,23 +123,16 @@ export function NewProjectDialog() { } }; - const toggleWorkspaceSetup = (next: WorkspaceSetup) => { - setWorkspaceSetup((prev) => (prev === next ? "none" : next)); - setWorkspaceError(null); - }; - async function handleSubmit() { if (!selectedCompanyId || !name.trim()) return; - const localRequired = workspaceSetup === "local" || workspaceSetup === "both"; - const repoRequired = workspaceSetup === "repo" || workspaceSetup === "both"; const localPath = workspaceLocalPath.trim(); const repoUrl = workspaceRepoUrl.trim(); - if (localRequired && !isAbsolutePath(localPath)) { + if (localPath && !isAbsolutePath(localPath)) { setWorkspaceError("Local folder must be a full absolute path."); return; } - if (repoRequired && !isGitHubRepoUrl(repoUrl)) { + if (repoUrl && !isGitHubRepoUrl(repoUrl)) { setWorkspaceError("Repo must use a valid GitHub repo URL."); return; } @@ -157,28 +149,15 @@ export function NewProjectDialog() { ...(targetDate ? { targetDate } : {}), }); - const workspacePayloads: Array> = []; - if (localRequired && repoRequired) { - workspacePayloads.push({ - name: deriveWorkspaceNameFromPath(localPath), - cwd: localPath, - repoUrl, - }); - } else if (localRequired) { - workspacePayloads.push({ - name: deriveWorkspaceNameFromPath(localPath), - cwd: localPath, - }); - } else if (repoRequired) { - workspacePayloads.push({ - name: deriveWorkspaceNameFromRepo(repoUrl), - repoUrl, - }); - } - for (const workspacePayload of workspacePayloads) { - await projectsApi.createWorkspace(created.id, { - ...workspacePayload, - }); + if (localPath || repoUrl) { + const workspacePayload: Record = { + name: localPath + ? deriveWorkspaceNameFromPath(localPath) + : deriveWorkspaceNameFromRepo(repoUrl), + ...(localPath ? { cwd: localPath } : {}), + ...(repoUrl ? { repoUrl } : {}), + }; + await projectsApi.createWorkspace(created.id, workspacePayload); } queryClient.invalidateQueries({ queryKey: queryKeys.projects.list(selectedCompanyId) }); @@ -280,80 +259,51 @@ export function NewProjectDialog() {
-
-

Where will work be done on this project?

-

Add a repo and/or local folder for this project.

-
-
- - - +
+
+ + optional + + + + + + Link a GitHub repository so agents can clone, read, and push code for this project. + + +
+ { setWorkspaceRepoUrl(e.target.value); setWorkspaceError(null); }} + placeholder="https://github.com/org/repo" + />
- {(workspaceSetup === "local" || workspaceSetup === "both") && ( -
- -
- setWorkspaceLocalPath(e.target.value)} - placeholder="/absolute/path/to/workspace" - /> - -
+
+
+ + optional + + + + + + Set an absolute path on this machine where local agents will read and write files for this project. + +
- )} - {(workspaceSetup === "repo" || workspaceSetup === "both") && ( -
- +
setWorkspaceRepoUrl(e.target.value)} - placeholder="https://github.com/org/repo" + className="w-full rounded border border-border bg-transparent px-2 py-1 text-xs font-mono outline-none" + value={workspaceLocalPath} + onChange={(e) => { setWorkspaceLocalPath(e.target.value); setWorkspaceError(null); }} + placeholder="/absolute/path/to/workspace" /> +
- )} +
+ {workspaceError && (

{workspaceError}

)} From 25af0a153262c7f389e1ca2aef5e43048aa165a5 Mon Sep 17 00:00:00 2001 From: dotta Date: Thu, 19 Mar 2026 07:24:52 -0500 Subject: [PATCH 17/26] Interleave failed runs with issues and approvals in inbox Failed runs are no longer shown in a separate section. They are now mixed into the main work items feed sorted by timestamp, matching how approvals are already interleaved with issues. Replaced the large FailedRunCard with a compact FailedRunInboxRow that matches the ApprovalInboxRow visual style. Co-Authored-By: Paperclip --- ui/src/lib/inbox.test.ts | 6 +- ui/src/lib/inbox.ts | 12 ++ ui/src/pages/Inbox.tsx | 275 +++++++++++++++++---------------------- 3 files changed, 140 insertions(+), 153 deletions(-) diff --git a/ui/src/lib/inbox.test.ts b/ui/src/lib/inbox.test.ts index 4821b6cf..13e0ada0 100644 --- a/ui/src/lib/inbox.test.ts +++ b/ui/src/lib/inbox.test.ts @@ -289,7 +289,11 @@ describe("inbox helpers", () => { getInboxWorkItems({ issues: [olderIssue, newerIssue], approvals: [approval], - }).map((item) => item.kind === "issue" ? `issue:${item.issue.id}` : `approval:${item.approval.id}`), + }).map((item) => { + if (item.kind === "issue") return `issue:${item.issue.id}`; + if (item.kind === "approval") return `approval:${item.approval.id}`; + return `run:${item.run.id}`; + }), ).toEqual([ "issue:1", "approval:approval-between", diff --git a/ui/src/lib/inbox.ts b/ui/src/lib/inbox.ts index b9a74f72..98de7055 100644 --- a/ui/src/lib/inbox.ts +++ b/ui/src/lib/inbox.ts @@ -23,6 +23,11 @@ export type InboxWorkItem = kind: "approval"; timestamp: number; approval: Approval; + } + | { + kind: "failed_run"; + timestamp: number; + run: HeartbeatRun; }; export interface InboxBadgeData { @@ -146,9 +151,11 @@ export function approvalActivityTimestamp(approval: Approval): number { export function getInboxWorkItems({ issues, approvals, + failedRuns = [], }: { issues: Issue[]; approvals: Approval[]; + failedRuns?: HeartbeatRun[]; }): InboxWorkItem[] { return [ ...issues.map((issue) => ({ @@ -161,6 +168,11 @@ export function getInboxWorkItems({ timestamp: approvalActivityTimestamp(approval), approval, })), + ...failedRuns.map((run) => ({ + kind: "failed_run" as const, + timestamp: normalizeTimestamp(run.createdAt), + run, + })), ].sort((a, b) => { const timestampDiff = b.timestamp - a.timestamp; if (timestampDiff !== 0) return timestampDiff; diff --git a/ui/src/pages/Inbox.tsx b/ui/src/pages/Inbox.tsx index 74a6144a..c75fdeab 100644 --- a/ui/src/pages/Inbox.tsx +++ b/ui/src/pages/Inbox.tsx @@ -33,12 +33,10 @@ import { import { Inbox as InboxIcon, AlertTriangle, - ArrowUpRight, XCircle, X, RotateCcw, } from "lucide-react"; -import { Identity } from "../components/Identity"; import { PageTabBar } from "../components/PageTabBar"; import type { Approval, HeartbeatRun, Issue, JoinRequest } from "@paperclipai/shared"; import { @@ -64,16 +62,8 @@ type InboxCategoryFilter = type SectionKey = | "work_items" | "join_requests" - | "failed_runs" | "alerts"; -const RUN_SOURCE_LABELS: Record = { - timer: "Scheduled", - assignment: "Assignment", - on_demand: "Manual", - automation: "Automation", -}; - function firstNonEmptyLine(value: string | null | undefined): string | null { if (!value) return null; const line = value.split("\n").map((chunk) => chunk.trim()).find(Boolean); @@ -101,139 +91,102 @@ function readIssueIdFromRun(run: HeartbeatRun): string | null { return null; } -function FailedRunCard({ +function FailedRunInboxRow({ run, issueById, agentName: linkedAgentName, issueLinkState, onDismiss, + onRetry, + isRetrying, }: { run: HeartbeatRun; issueById: Map; agentName: string | null; issueLinkState: unknown; onDismiss: () => void; + onRetry: () => void; + isRetrying: boolean; }) { - const queryClient = useQueryClient(); - const navigate = useNavigate(); const issueId = readIssueIdFromRun(run); const issue = issueId ? issueById.get(issueId) ?? null : null; - const sourceLabel = RUN_SOURCE_LABELS[run.invocationSource] ?? "Manual"; const displayError = runFailureMessage(run); - const retryRun = useMutation({ - mutationFn: async () => { - const payload: Record = {}; - const context = run.contextSnapshot as Record | null; - if (context) { - if (typeof context.issueId === "string" && context.issueId) payload.issueId = context.issueId; - if (typeof context.taskId === "string" && context.taskId) payload.taskId = context.taskId; - if (typeof context.taskKey === "string" && context.taskKey) payload.taskKey = context.taskKey; - } - const result = await agentsApi.wakeup(run.agentId, { - source: "on_demand", - triggerDetail: "manual", - reason: "retry_failed_run", - payload, - }); - if (!("id" in result)) { - throw new Error("Retry was skipped because the agent is not currently invokable."); - } - return result; - }, - onSuccess: (newRun) => { - queryClient.invalidateQueries({ queryKey: queryKeys.heartbeats(run.companyId) }); - queryClient.invalidateQueries({ queryKey: queryKeys.heartbeats(run.companyId, run.agentId) }); - navigate(`/agents/${run.agentId}/runs/${newRun.id}`); - }, - }); - return ( -
-
- -
- {issue ? ( - - - {issue.identifier ?? issue.id.slice(0, 8)} - - {issue.title} - - ) : ( - - {run.errorCode ? `Error code: ${run.errorCode}` : "No linked issue"} +
+
+ +
); @@ -473,13 +426,19 @@ export function Inbox() { const showFailedRunsCategory = allCategoryFilter === "everything" || allCategoryFilter === "failed_runs"; const showAlertsCategory = allCategoryFilter === "everything" || allCategoryFilter === "alerts"; + const failedRunsForTab = useMemo(() => { + if (tab === "all" && !showFailedRunsCategory) return []; + return failedRuns; + }, [failedRuns, tab, showFailedRunsCategory]); + const workItemsToRender = useMemo( () => getInboxWorkItems({ issues: tab === "all" && !showTouchedCategory ? [] : issuesToRender, approvals: tab === "all" && !showApprovalsCategory ? [] : approvalsToRender, + failedRuns: failedRunsForTab, }), - [approvalsToRender, issuesToRender, showApprovalsCategory, showTouchedCategory, tab], + [approvalsToRender, issuesToRender, showApprovalsCategory, showTouchedCategory, tab, failedRunsForTab], ); const agentName = (id: string | null) => { @@ -538,6 +497,33 @@ export function Inbox() { }, }); + const retryRunMutation = useMutation({ + mutationFn: async (run: HeartbeatRun) => { + const payload: Record = {}; + const context = run.contextSnapshot as Record | null; + if (context) { + if (typeof context.issueId === "string" && context.issueId) payload.issueId = context.issueId; + if (typeof context.taskId === "string" && context.taskId) payload.taskId = context.taskId; + if (typeof context.taskKey === "string" && context.taskKey) payload.taskKey = context.taskKey; + } + const result = await agentsApi.wakeup(run.agentId, { + source: "on_demand", + triggerDetail: "manual", + reason: "retry_failed_run", + payload, + }); + if (!("id" in result)) { + throw new Error("Retry was skipped because the agent is not currently invokable."); + } + return { newRun: result, originalRun: run }; + }, + onSuccess: ({ newRun, originalRun }) => { + queryClient.invalidateQueries({ queryKey: queryKeys.heartbeats(originalRun.companyId) }); + queryClient.invalidateQueries({ queryKey: queryKeys.heartbeats(originalRun.companyId, originalRun.agentId) }); + navigate(`/agents/${originalRun.agentId}/runs/${newRun.id}`); + }, + }); + const [fadingOutIssues, setFadingOutIssues] = useState>(new Set()); const invalidateInboxIssueQueries = () => { @@ -607,13 +593,6 @@ export function Inbox() { const showWorkItemsSection = workItemsToRender.length > 0; const showJoinRequestsSection = tab === "all" ? showJoinRequestsCategory && hasJoinRequests : tab === "unread" && hasJoinRequests; - const showFailedRunsSection = shouldShowInboxSection({ - tab, - hasItems: hasRunFailures, - showOnRecent: hasRunFailures, - showOnUnread: hasRunFailures, - showOnAll: showFailedRunsCategory && hasRunFailures, - }); const showAlertsSection = shouldShowInboxSection({ tab, hasItems: hasAlerts, @@ -623,7 +602,6 @@ export function Inbox() { }); const visibleSections = [ - showFailedRunsSection ? "failed_runs" : null, showAlertsSection ? "alerts" : null, showJoinRequestsSection ? "join_requests" : null, showWorkItemsSection ? "work_items" : null, @@ -751,6 +729,21 @@ export function Inbox() { ); } + if (item.kind === "failed_run") { + return ( + dismiss(`run:${item.run.id}`)} + onRetry={() => retryRunMutation.mutate(item.run)} + isRetrying={retryRunMutation.isPending} + /> + ); + } + const issue = item.issue; const isUnread = issue.isUnreadForMe && !fadingOutIssues.has(issue.id); const isFading = fadingOutIssues.has(issue.id); @@ -857,28 +850,6 @@ export function Inbox() { )} - {showFailedRunsSection && ( - <> - {showSeparatorBefore("failed_runs") && } -
-

- Failed Runs -

-
- {failedRuns.map((run) => ( - dismiss(`run:${run.id}`)} - /> - ))} -
-
- - )} {showAlertsSection && ( <> From bcc1d9f3d688342ca1d82d7fe03311215da866aa Mon Sep 17 00:00:00 2001 From: dotta Date: Thu, 19 Mar 2026 07:26:28 -0500 Subject: [PATCH 18/26] Remove border containers from workspace fields in new project dialog Strip the rounded-border card wrappers from repo URL and local folder fields so they sit directly in the section. Add pt-3 to the section so the first field doesn't touch the border-t above it. Co-Authored-By: Paperclip --- ui/src/components/NewProjectDialog.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/components/NewProjectDialog.tsx b/ui/src/components/NewProjectDialog.tsx index 6a449b52..4561ac93 100644 --- a/ui/src/components/NewProjectDialog.tsx +++ b/ui/src/components/NewProjectDialog.tsx @@ -258,8 +258,8 @@ export function NewProjectDialog() { />
-
-
+
+
optional @@ -280,7 +280,7 @@ export function NewProjectDialog() { />
-
+
optional From f9d685344d898b7a7bf3b1644ae86b320cb6ce98 Mon Sep 17 00:00:00 2001 From: dotta Date: Thu, 19 Mar 2026 08:14:29 -0500 Subject: [PATCH 19/26] Expose agent task assignment permissions Co-Authored-By: Paperclip --- packages/shared/src/index.ts | 3 + packages/shared/src/types/agent.ts | 23 +++++ packages/shared/src/types/index.ts | 3 + packages/shared/src/validators/agent.ts | 1 + server/src/routes/access.ts | 8 ++ server/src/routes/agents.ts | 114 +++++++++++++++++++-- server/src/services/access.ts | 82 +++++++++++++++ server/src/services/company-portability.ts | 9 ++ ui/src/api/agents.ts | 14 ++- ui/src/pages/AgentDetail.tsx | 80 ++++++++++++--- 10 files changed, 310 insertions(+), 27 deletions(-) diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 665b47c4..98850f9e 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -123,6 +123,9 @@ export type { InstanceExperimentalSettings, InstanceSettings, Agent, + AgentAccessState, + AgentChainOfCommandEntry, + AgentDetail, AgentPermissions, AgentKeyCreated, AgentConfigRevision, diff --git a/packages/shared/src/types/agent.ts b/packages/shared/src/types/agent.ts index dd1ae45f..550e34aa 100644 --- a/packages/shared/src/types/agent.ts +++ b/packages/shared/src/types/agent.ts @@ -4,11 +4,29 @@ import type { AgentRole, AgentStatus, } from "../constants.js"; +import type { + CompanyMembership, + PrincipalPermissionGrant, +} from "./access.js"; export interface AgentPermissions { canCreateAgents: boolean; } +export interface AgentAccessState { + canAssignTasks: boolean; + taskAssignSource: "explicit_grant" | "agent_creator" | "ceo_role" | "none"; + membership: CompanyMembership | null; + grants: PrincipalPermissionGrant[]; +} + +export interface AgentChainOfCommandEntry { + id: string; + name: string; + role: AgentRole; + title: string | null; +} + export interface Agent { id: string; companyId: string; @@ -34,6 +52,11 @@ export interface Agent { updatedAt: Date; } +export interface AgentDetail extends Agent { + chainOfCommand: AgentChainOfCommandEntry[]; + access: AgentAccessState; +} + export interface AgentKeyCreated { id: string; name: string; diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index 01b20b74..f573db4d 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -2,6 +2,9 @@ export type { Company } from "./company.js"; export type { InstanceExperimentalSettings, InstanceSettings } from "./instance.js"; export type { Agent, + AgentAccessState, + AgentChainOfCommandEntry, + AgentDetail, AgentPermissions, AgentKeyCreated, AgentConfigRevision, diff --git a/packages/shared/src/validators/agent.ts b/packages/shared/src/validators/agent.ts index f703f036..107551be 100644 --- a/packages/shared/src/validators/agent.ts +++ b/packages/shared/src/validators/agent.ts @@ -100,6 +100,7 @@ export type TestAdapterEnvironment = z.infer; diff --git a/server/src/routes/access.ts b/server/src/routes/access.ts index a966d12b..3e29bb47 100644 --- a/server/src/routes/access.ts +++ b/server/src/routes/access.ts @@ -2450,6 +2450,14 @@ export function accessRoutes( "member", "active" ); + await access.setPrincipalPermission( + companyId, + "agent", + created.id, + "tasks:assign", + true, + req.actor.userId ?? null + ); const grants = grantsFromDefaults( invite.defaultsPayload as Record | null, "agent" diff --git a/server/src/routes/agents.ts b/server/src/routes/agents.ts index db91ad8c..2c6b12b4 100644 --- a/server/src/routes/agents.ts +++ b/server/src/routes/agents.ts @@ -71,6 +71,80 @@ export function agentRoutes(db: Db) { return Boolean((agent.permissions as Record).canCreateAgents); } + async function buildAgentAccessState(agent: NonNullable>>) { + const membership = await access.getMembership(agent.companyId, "agent", agent.id); + const grants = membership + ? await access.listPrincipalGrants(agent.companyId, "agent", agent.id) + : []; + const hasExplicitTaskAssignGrant = grants.some((grant) => grant.permissionKey === "tasks:assign"); + + if (agent.role === "ceo") { + return { + canAssignTasks: true, + taskAssignSource: "ceo_role" as const, + membership, + grants, + }; + } + + if (canCreateAgents(agent)) { + return { + canAssignTasks: true, + taskAssignSource: "agent_creator" as const, + membership, + grants, + }; + } + + if (hasExplicitTaskAssignGrant) { + return { + canAssignTasks: true, + taskAssignSource: "explicit_grant" as const, + membership, + grants, + }; + } + + return { + canAssignTasks: false, + taskAssignSource: "none" as const, + membership, + grants, + }; + } + + async function buildAgentDetail( + agent: NonNullable>>, + options?: { restricted?: boolean }, + ) { + const [chainOfCommand, accessState] = await Promise.all([ + svc.getChainOfCommand(agent.id), + buildAgentAccessState(agent), + ]); + + return { + ...(options?.restricted ? redactForRestrictedAgentView(agent) : agent), + chainOfCommand, + access: accessState, + }; + } + + async function applyDefaultAgentTaskAssignGrant( + companyId: string, + agentId: string, + grantedByUserId: string | null, + ) { + await access.ensureMembership(companyId, "agent", agentId, "member", "active"); + await access.setPrincipalPermission( + companyId, + "agent", + agentId, + "tasks:assign", + true, + grantedByUserId, + ); + } + async function assertCanCreateAgentsForCompany(req: Request, companyId: string) { assertCompanyAccess(req, companyId); if (req.actor.type === "board") { @@ -575,8 +649,7 @@ export function agentRoutes(db: Db) { res.status(404).json({ error: "Agent not found" }); return; } - const chainOfCommand = await svc.getChainOfCommand(agent.id); - res.json({ ...agent, chainOfCommand }); + res.json(await buildAgentDetail(agent)); }); router.get("/agents/me/inbox-lite", async (req, res) => { @@ -618,13 +691,11 @@ export function agentRoutes(db: Db) { if (req.actor.type === "agent" && req.actor.agentId !== id) { const canRead = await actorCanReadConfigurationsForCompany(req, agent.companyId); if (!canRead) { - const chainOfCommand = await svc.getChainOfCommand(agent.id); - res.json({ ...redactForRestrictedAgentView(agent), chainOfCommand }); + res.json(await buildAgentDetail(agent, { restricted: true })); return; } } - const chainOfCommand = await svc.getChainOfCommand(agent.id); - res.json({ ...agent, chainOfCommand }); + res.json(await buildAgentDetail(agent)); }); router.get("/agents/:id/configuration", async (req, res) => { @@ -884,6 +955,12 @@ export function agentRoutes(db: Db) { }, }); + await applyDefaultAgentTaskAssignGrant( + companyId, + agent.id, + actor.actorType === "user" ? actor.actorId : null, + ); + if (approval) { await logActivity(db, { companyId, @@ -945,6 +1022,12 @@ export function agentRoutes(db: Db) { details: { name: agent.name, role: agent.role }, }); + await applyDefaultAgentTaskAssignGrant( + companyId, + agent.id, + req.actor.type === "board" ? (req.actor.userId ?? null) : null, + ); + if (agent.budgetMonthlyCents > 0) { await budgets.upsertPolicy( companyId, @@ -988,6 +1071,18 @@ export function agentRoutes(db: Db) { return; } + const effectiveCanAssignTasks = + agent.role === "ceo" || Boolean(agent.permissions?.canCreateAgents) || req.body.canAssignTasks; + await access.ensureMembership(agent.companyId, "agent", agent.id, "member", "active"); + await access.setPrincipalPermission( + agent.companyId, + "agent", + agent.id, + "tasks:assign", + effectiveCanAssignTasks, + req.actor.type === "board" ? (req.actor.userId ?? null) : null, + ); + const actor = getActorInfo(req); await logActivity(db, { companyId: agent.companyId, @@ -998,10 +1093,13 @@ export function agentRoutes(db: Db) { action: "agent.permissions_updated", entityType: "agent", entityId: agent.id, - details: req.body, + details: { + canCreateAgents: agent.permissions?.canCreateAgents ?? false, + canAssignTasks: effectiveCanAssignTasks, + }, }); - res.json(agent); + res.json(await buildAgentDetail(agent)); }); router.patch("/agents/:id/instructions-path", validate(updateAgentInstructionsPathSchema), async (req, res) => { diff --git a/server/src/services/access.ts b/server/src/services/access.ts index 9ec0387d..e02b36d7 100644 --- a/server/src/services/access.ts +++ b/server/src/services/access.ts @@ -251,6 +251,86 @@ export function accessService(db: Db) { }); } + async function listPrincipalGrants( + companyId: string, + principalType: PrincipalType, + principalId: string, + ) { + return db + .select() + .from(principalPermissionGrants) + .where( + and( + eq(principalPermissionGrants.companyId, companyId), + eq(principalPermissionGrants.principalType, principalType), + eq(principalPermissionGrants.principalId, principalId), + ), + ) + .orderBy(principalPermissionGrants.permissionKey); + } + + async function setPrincipalPermission( + companyId: string, + principalType: PrincipalType, + principalId: string, + permissionKey: PermissionKey, + enabled: boolean, + grantedByUserId: string | null, + scope: Record | null = null, + ) { + if (!enabled) { + await db + .delete(principalPermissionGrants) + .where( + and( + eq(principalPermissionGrants.companyId, companyId), + eq(principalPermissionGrants.principalType, principalType), + eq(principalPermissionGrants.principalId, principalId), + eq(principalPermissionGrants.permissionKey, permissionKey), + ), + ); + return; + } + + await ensureMembership(companyId, principalType, principalId, "member", "active"); + + const existing = await db + .select() + .from(principalPermissionGrants) + .where( + and( + eq(principalPermissionGrants.companyId, companyId), + eq(principalPermissionGrants.principalType, principalType), + eq(principalPermissionGrants.principalId, principalId), + eq(principalPermissionGrants.permissionKey, permissionKey), + ), + ) + .then((rows) => rows[0] ?? null); + + if (existing) { + await db + .update(principalPermissionGrants) + .set({ + scope, + grantedByUserId, + updatedAt: new Date(), + }) + .where(eq(principalPermissionGrants.id, existing.id)); + return; + } + + await db.insert(principalPermissionGrants).values({ + companyId, + principalType, + principalId, + permissionKey, + scope, + grantedByUserId, + createdAt: new Date(), + updatedAt: new Date(), + }); + } + return { isInstanceAdmin, canUser, @@ -264,5 +344,7 @@ export function accessService(db: Db) { listUserCompanyAccess, setUserCompanyAccess, setPrincipalGrants, + listPrincipalGrants, + setPrincipalPermission, }; } diff --git a/server/src/services/company-portability.ts b/server/src/services/company-portability.ts index f067e957..7afdb381 100644 --- a/server/src/services/company-portability.ts +++ b/server/src/services/company-portability.ts @@ -955,6 +955,15 @@ export function companyPortabilityService(db: Db) { } const created = await agents.create(targetCompany.id, patch); + await access.ensureMembership(targetCompany.id, "agent", created.id, "member", "active"); + await access.setPrincipalPermission( + targetCompany.id, + "agent", + created.id, + "tasks:assign", + true, + actorUserId ?? null, + ); importedSlugToAgentId.set(planAgent.slug, created.id); existingSlugToAgentId.set(normalizeAgentUrlKey(created.name) ?? created.id, created.id); resultAgents.push({ diff --git a/ui/src/api/agents.ts b/ui/src/api/agents.ts index 9008fbca..1f4a642e 100644 --- a/ui/src/api/agents.ts +++ b/ui/src/api/agents.ts @@ -1,5 +1,6 @@ import type { Agent, + AgentDetail, AdapterEnvironmentTestResult, AgentKeyCreated, AgentRuntimeState, @@ -45,6 +46,11 @@ export interface AgentHireResponse { approval: Approval | null; } +export interface AgentPermissionUpdate { + canCreateAgents: boolean; + canAssignTasks: boolean; +} + function withCompanyScope(path: string, companyId?: string) { if (!companyId) return path; const separator = path.includes("?") ? "&" : "?"; @@ -62,7 +68,7 @@ export const agentsApi = { api.get[]>(`/companies/${companyId}/agent-configurations`), get: async (id: string, companyId?: string) => { try { - return await api.get(agentPath(id, companyId)); + return await api.get(agentPath(id, companyId)); } catch (error) { // Backward-compat fallback: if backend shortname lookup reports ambiguity, // resolve using company agent list while ignoring terminated agents. @@ -83,7 +89,7 @@ export const agentsApi = { (agent) => agent.status !== "terminated" && normalizeAgentUrlKey(agent.urlKey) === urlKey, ); if (matches.length !== 1) throw error; - return api.get(agentPath(matches[0]!.id, companyId)); + return api.get(agentPath(matches[0]!.id, companyId)); } }, getConfiguration: (id: string, companyId?: string) => @@ -100,8 +106,8 @@ export const agentsApi = { api.post(`/companies/${companyId}/agent-hires`, data), update: (id: string, data: Record, companyId?: string) => api.patch(agentPath(id, companyId), data), - updatePermissions: (id: string, data: { canCreateAgents: boolean }, companyId?: string) => - api.patch(agentPath(id, companyId, "/permissions"), data), + updatePermissions: (id: string, data: AgentPermissionUpdate, companyId?: string) => + api.patch(agentPath(id, companyId, "/permissions"), data), pause: (id: string, companyId?: string) => api.post(agentPath(id, companyId, "/pause"), {}), resume: (id: string, companyId?: string) => api.post(agentPath(id, companyId, "/resume"), {}), terminate: (id: string, companyId?: string) => api.post(agentPath(id, companyId, "/terminate"), {}), diff --git a/ui/src/pages/AgentDetail.tsx b/ui/src/pages/AgentDetail.tsx index f11be75e..9c2a66a3 100644 --- a/ui/src/pages/AgentDetail.tsx +++ b/ui/src/pages/AgentDetail.tsx @@ -1,7 +1,13 @@ import { useCallback, useEffect, useMemo, useState, useRef } from "react"; import { useParams, useNavigate, Link, Navigate, useBeforeUnload } from "@/lib/router"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { agentsApi, type AgentKey, type ClaudeLoginResult, type AvailableSkill } from "../api/agents"; +import { + agentsApi, + type AgentKey, + type ClaudeLoginResult, + type AvailableSkill, + type AgentPermissionUpdate, +} from "../api/agents"; import { budgetsApi } from "../api/budgets"; import { heartbeatsApi } from "../api/heartbeats"; import { ApiError } from "../api/client"; @@ -64,6 +70,7 @@ import { RunTranscriptView, type TranscriptMode } from "../components/transcript import { isUuidLike, type Agent, + type AgentDetail as AgentDetailRecord, type BudgetPolicySummary, type HeartbeatRun, type HeartbeatRunEvent, @@ -486,7 +493,7 @@ export function AgentDetail() { const setSaveConfigAction = useCallback((fn: (() => void) | null) => { saveConfigActionRef.current = fn; }, []); const setCancelConfigAction = useCallback((fn: (() => void) | null) => { cancelConfigActionRef.current = fn; }, []); - const { data: agent, isLoading, error } = useQuery({ + const { data: agent, isLoading, error } = useQuery({ queryKey: [...queryKeys.agents.detail(routeAgentRef), lookupCompanyId ?? null], queryFn: () => agentsApi.get(routeAgentRef, lookupCompanyId), enabled: canFetchAgent, @@ -672,8 +679,8 @@ export function AgentDetail() { }); const updatePermissions = useMutation({ - mutationFn: (canCreateAgents: boolean) => - agentsApi.updatePermissions(agentLookupRef, { canCreateAgents }, resolvedCompanyId ?? undefined), + mutationFn: (permissions: AgentPermissionUpdate) => + agentsApi.updatePermissions(agentLookupRef, permissions, resolvedCompanyId ?? undefined), onSuccess: () => { setActionError(null); queryClient.invalidateQueries({ queryKey: queryKeys.agents.detail(routeAgentRef) }); @@ -1076,7 +1083,7 @@ function AgentOverview({ agentId, agentRouteId, }: { - agent: Agent; + agent: AgentDetailRecord; runs: HeartbeatRun[]; assignedIssues: { id: string; title: string; status: string; priority: string; identifier?: string | null; createdAt: Date }[]; runtimeState?: AgentRuntimeState; @@ -1233,14 +1240,14 @@ function AgentConfigurePage({ onSavingChange, updatePermissions, }: { - agent: Agent; + agent: AgentDetailRecord; agentId: string; companyId?: string; onDirtyChange: (dirty: boolean) => void; onSaveActionChange: (save: (() => void) | null) => void; onCancelActionChange: (cancel: (() => void) | null) => void; onSavingChange: (saving: boolean) => void; - updatePermissions: { mutate: (canCreate: boolean) => void; isPending: boolean }; + updatePermissions: { mutate: (permissions: AgentPermissionUpdate) => void; isPending: boolean }; }) { const queryClient = useQueryClient(); const [revisionsOpen, setRevisionsOpen] = useState(false); @@ -1340,13 +1347,13 @@ function ConfigurationTab({ onSavingChange, updatePermissions, }: { - agent: Agent; + agent: AgentDetailRecord; companyId?: string; onDirtyChange: (dirty: boolean) => void; onSaveActionChange: (save: (() => void) | null) => void; onCancelActionChange: (cancel: (() => void) | null) => void; onSavingChange: (saving: boolean) => void; - updatePermissions: { mutate: (canCreate: boolean) => void; isPending: boolean }; + updatePermissions: { mutate: (permissions: AgentPermissionUpdate) => void; isPending: boolean }; }) { const queryClient = useQueryClient(); const [awaitingRefreshAfterSave, setAwaitingRefreshAfterSave] = useState(false); @@ -1389,6 +1396,19 @@ function ConfigurationTab({ onSavingChange(isConfigSaving); }, [onSavingChange, isConfigSaving]); + const canCreateAgents = Boolean(agent.permissions?.canCreateAgents); + const canAssignTasks = Boolean(agent.access?.canAssignTasks); + const taskAssignSource = agent.access?.taskAssignSource ?? "none"; + const taskAssignLocked = agent.role === "ceo" || canCreateAgents; + const taskAssignHint = + taskAssignSource === "ceo_role" + ? "Enabled automatically for CEO agents." + : taskAssignSource === "agent_creator" + ? "Enabled automatically while this agent can create new agents." + : taskAssignSource === "explicit_grant" + ? "Enabled via explicit company permission grant." + : "Disabled unless explicitly granted."; + return (

Permissions

-
-
- Can create new agents +
+
+
+
Can create new agents
+

+ Lets this agent create or hire agents and implicitly assign tasks. +

+
+
+
+
+
Can assign tasks
+

+ {taskAssignHint} +

+
+
From d6c6aa5c490c1dedb7ffc376b383ac964cf8dcae Mon Sep 17 00:00:00 2001 From: dotta Date: Thu, 19 Mar 2026 08:20:12 -0500 Subject: [PATCH 20/26] test: cover agent permission cleanup routes Co-Authored-By: Paperclip --- .../agent-permissions-routes.test.ts | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 server/src/__tests__/agent-permissions-routes.test.ts diff --git a/server/src/__tests__/agent-permissions-routes.test.ts b/server/src/__tests__/agent-permissions-routes.test.ts new file mode 100644 index 00000000..3b0f9e78 --- /dev/null +++ b/server/src/__tests__/agent-permissions-routes.test.ts @@ -0,0 +1,246 @@ +import express from "express"; +import request from "supertest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { agentRoutes } from "../routes/agents.js"; +import { errorHandler } from "../middleware/index.js"; + +const agentId = "11111111-1111-4111-8111-111111111111"; +const companyId = "22222222-2222-4222-8222-222222222222"; + +const baseAgent = { + id: agentId, + companyId, + name: "Builder", + urlKey: "builder", + role: "engineer", + title: "Builder", + icon: null, + status: "idle", + reportsTo: null, + capabilities: null, + adapterType: "process", + adapterConfig: {}, + runtimeConfig: {}, + budgetMonthlyCents: 0, + spentMonthlyCents: 0, + pauseReason: null, + pausedAt: null, + permissions: { canCreateAgents: false }, + lastHeartbeatAt: null, + metadata: null, + createdAt: new Date("2026-03-19T00:00:00.000Z"), + updatedAt: new Date("2026-03-19T00:00:00.000Z"), +}; + +const mockAgentService = vi.hoisted(() => ({ + getById: vi.fn(), + create: vi.fn(), + updatePermissions: vi.fn(), + getChainOfCommand: vi.fn(), + resolveByReference: vi.fn(), +})); + +const mockAccessService = vi.hoisted(() => ({ + canUser: vi.fn(), + hasPermission: vi.fn(), + getMembership: vi.fn(), + ensureMembership: vi.fn(), + listPrincipalGrants: vi.fn(), + setPrincipalPermission: vi.fn(), +})); + +const mockApprovalService = vi.hoisted(() => ({ + create: vi.fn(), + getById: vi.fn(), +})); + +const mockBudgetService = vi.hoisted(() => ({ + upsertPolicy: vi.fn(), +})); + +const mockHeartbeatService = vi.hoisted(() => ({ + listTaskSessions: vi.fn(), + resetRuntimeSession: vi.fn(), +})); + +const mockIssueApprovalService = vi.hoisted(() => ({ + linkManyForApproval: vi.fn(), +})); + +const mockIssueService = vi.hoisted(() => ({ + list: vi.fn(), +})); + +const mockSecretService = vi.hoisted(() => ({ + normalizeAdapterConfigForPersistence: vi.fn(), + resolveAdapterConfigForRuntime: vi.fn(), +})); + +const mockWorkspaceOperationService = vi.hoisted(() => ({})); +const mockLogActivity = vi.hoisted(() => vi.fn()); + +vi.mock("../services/index.js", () => ({ + agentService: () => mockAgentService, + accessService: () => mockAccessService, + approvalService: () => mockApprovalService, + budgetService: () => mockBudgetService, + heartbeatService: () => mockHeartbeatService, + issueApprovalService: () => mockIssueApprovalService, + issueService: () => mockIssueService, + logActivity: mockLogActivity, + secretService: () => mockSecretService, + workspaceOperationService: () => mockWorkspaceOperationService, +})); + +function createDbStub() { + return { + select: vi.fn().mockReturnValue({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + then: vi.fn().mockResolvedValue([{ + id: companyId, + name: "Paperclip", + requireBoardApprovalForNewAgents: false, + }]), + }), + }), + }), + }; +} + +function createApp(actor: Record) { + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + (req as any).actor = actor; + next(); + }); + app.use("/api", agentRoutes(createDbStub() as any)); + app.use(errorHandler); + return app; +} + +describe("agent permission routes", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockAgentService.getById.mockResolvedValue(baseAgent); + mockAgentService.getChainOfCommand.mockResolvedValue([]); + mockAgentService.resolveByReference.mockResolvedValue({ ambiguous: false, agent: baseAgent }); + mockAgentService.create.mockResolvedValue(baseAgent); + mockAgentService.updatePermissions.mockResolvedValue(baseAgent); + mockAccessService.getMembership.mockResolvedValue({ + id: "membership-1", + companyId, + principalType: "agent", + principalId: agentId, + status: "active", + membershipRole: "member", + createdAt: new Date("2026-03-19T00:00:00.000Z"), + updatedAt: new Date("2026-03-19T00:00:00.000Z"), + }); + mockAccessService.listPrincipalGrants.mockResolvedValue([]); + mockAccessService.ensureMembership.mockResolvedValue(undefined); + mockAccessService.setPrincipalPermission.mockResolvedValue(undefined); + mockBudgetService.upsertPolicy.mockResolvedValue(undefined); + mockSecretService.normalizeAdapterConfigForPersistence.mockImplementation(async (_companyId, config) => config); + mockSecretService.resolveAdapterConfigForRuntime.mockImplementation(async (_companyId, config) => ({ config })); + mockLogActivity.mockResolvedValue(undefined); + }); + + it("grants tasks:assign by default when board creates a new agent", async () => { + const app = createApp({ + type: "board", + userId: "board-user", + source: "local_implicit", + isInstanceAdmin: true, + companyIds: [companyId], + }); + + const res = await request(app) + .post(`/api/companies/${companyId}/agents`) + .send({ + name: "Builder", + role: "engineer", + adapterType: "process", + adapterConfig: {}, + }); + + expect(res.status).toBe(201); + expect(mockAccessService.ensureMembership).toHaveBeenCalledWith( + companyId, + "agent", + agentId, + "member", + "active", + ); + expect(mockAccessService.setPrincipalPermission).toHaveBeenCalledWith( + companyId, + "agent", + agentId, + "tasks:assign", + true, + "board-user", + ); + }); + + it("exposes explicit task assignment access on agent detail", async () => { + mockAccessService.listPrincipalGrants.mockResolvedValue([ + { + id: "grant-1", + companyId, + principalType: "agent", + principalId: agentId, + permissionKey: "tasks:assign", + scope: null, + grantedByUserId: "board-user", + createdAt: new Date("2026-03-19T00:00:00.000Z"), + updatedAt: new Date("2026-03-19T00:00:00.000Z"), + }, + ]); + + const app = createApp({ + type: "board", + userId: "board-user", + source: "local_implicit", + isInstanceAdmin: true, + companyIds: [companyId], + }); + + const res = await request(app).get(`/api/agents/${agentId}`); + + expect(res.status).toBe(200); + expect(res.body.access.canAssignTasks).toBe(true); + expect(res.body.access.taskAssignSource).toBe("explicit_grant"); + }); + + it("keeps task assignment enabled when agent creation privilege is enabled", async () => { + mockAgentService.updatePermissions.mockResolvedValue({ + ...baseAgent, + permissions: { canCreateAgents: true }, + }); + + const app = createApp({ + type: "board", + userId: "board-user", + source: "local_implicit", + isInstanceAdmin: true, + companyIds: [companyId], + }); + + const res = await request(app) + .patch(`/api/agents/${agentId}/permissions`) + .send({ canCreateAgents: true, canAssignTasks: false }); + + expect(res.status).toBe(200); + expect(mockAccessService.setPrincipalPermission).toHaveBeenCalledWith( + companyId, + "agent", + agentId, + "tasks:assign", + true, + "board-user", + ); + expect(res.body.access.canAssignTasks).toBe(true); + expect(res.body.access.taskAssignSource).toBe("agent_creator"); + }); +}); From 7f3fad64b814caa13224fa5363cb02f5c804b66a Mon Sep 17 00:00:00 2001 From: dotta Date: Thu, 19 Mar 2026 08:53:28 -0500 Subject: [PATCH 21/26] Move cost summary from standalone collapsible to top of Activity tab Moves the cost summary out of a collapsible section below the tabs and into the Activity tab content, displayed as a static card at the top. Removes the now-unused `cost` state from `secondaryOpen`. Closes PAP-559 Co-Authored-By: Paperclip Co-Authored-By: Claude Opus 4.6 --- ui/src/pages/IssueDetail.tsx | 62 ++++++++++++++---------------------- 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index 7f98db5e..e90965f6 100644 --- a/ui/src/pages/IssueDetail.tsx +++ b/ui/src/pages/IssueDetail.tsx @@ -206,7 +206,6 @@ export function IssueDetail() { const [detailTab, setDetailTab] = useState("comments"); const [secondaryOpen, setSecondaryOpen] = useState({ approvals: false, - cost: false, }); const [attachmentError, setAttachmentError] = useState(null); const [attachmentDragActive, setAttachmentDragActive] = useState(false); @@ -1065,6 +1064,30 @@ export function IssueDetail() { + {linkedRuns && linkedRuns.length > 0 && ( +
+
Cost Summary
+ {!issueCostSummary.hasCost && !issueCostSummary.hasTokens ? ( +
No cost data yet.
+ ) : ( +
+ {issueCostSummary.hasCost && ( + + ${issueCostSummary.cost.toFixed(4)} + + )} + {issueCostSummary.hasTokens && ( + + Tokens {formatTokens(issueCostSummary.totalTokens)} + {issueCostSummary.cached > 0 + ? ` (in ${formatTokens(issueCostSummary.input)}, out ${formatTokens(issueCostSummary.output)}, cached ${formatTokens(issueCostSummary.cached)})` + : ` (in ${formatTokens(issueCostSummary.input)}, out ${formatTokens(issueCostSummary.output)})`} + + )} +
+ )} +
+ )} {!activity || activity.length === 0 ? (

No activity yet.

) : ( @@ -1133,43 +1156,6 @@ export function IssueDetail() { )} - {linkedRuns && linkedRuns.length > 0 && ( - setSecondaryOpen((prev) => ({ ...prev, cost: open }))} - className="rounded-lg border border-border" - > - - Cost Summary - - - -
- {!issueCostSummary.hasCost && !issueCostSummary.hasTokens ? ( -
No cost data yet.
- ) : ( -
- {issueCostSummary.hasCost && ( - - ${issueCostSummary.cost.toFixed(4)} - - )} - {issueCostSummary.hasTokens && ( - - Tokens {formatTokens(issueCostSummary.totalTokens)} - {issueCostSummary.cached > 0 - ? ` (in ${formatTokens(issueCostSummary.input)}, out ${formatTokens(issueCostSummary.output)}, cached ${formatTokens(issueCostSummary.cached)})` - : ` (in ${formatTokens(issueCostSummary.input)}, out ${formatTokens(issueCostSummary.output)})`} - - )} -
- )} -
-
-
- )} {/* Mobile properties drawer */} From c844ca1a40cc6045314a5c506c82db1b81662e71 Mon Sep 17 00:00:00 2001 From: dotta Date: Thu, 19 Mar 2026 11:20:36 -0500 Subject: [PATCH 22/26] Improve orphaned local heartbeat recovery Persist child-process metadata for local adapter runs, keep detached runs alive when their pid still exists, queue a single automatic retry when the pid is confirmed dead, and clear detached warnings when the original run reports activity again. Co-Authored-By: Paperclip --- packages/adapter-utils/src/server-utils.ts | 12 + packages/adapter-utils/src/types.ts | 1 + .../claude-local/src/server/execute.ts | 3 +- .../codex-local/src/server/execute.ts | 3 +- .../cursor-local/src/server/execute.ts | 3 +- .../gemini-local/src/server/execute.ts | 3 +- .../opencode-local/src/server/execute.ts | 3 +- .../adapters/pi-local/src/server/execute.ts | 3 +- .../migrations/0038_careless_iron_monger.sql | 5 + .../db/src/migrations/meta/0038_snapshot.json | 10301 ++++++++++++++++ packages/db/src/migrations/meta/_journal.json | 7 + packages/db/src/schema/heartbeat_runs.ts | 8 +- packages/shared/src/types/heartbeat.ts | 4 + .../heartbeat-process-recovery.test.ts | 321 + server/src/routes/issues.ts | 12 +- server/src/services/heartbeat.ts | 253 +- ui/src/lib/inbox.test.ts | 4 + 17 files changed, 10924 insertions(+), 22 deletions(-) create mode 100644 packages/db/src/migrations/0038_careless_iron_monger.sql create mode 100644 packages/db/src/migrations/meta/0038_snapshot.json create mode 100644 server/src/__tests__/heartbeat-process-recovery.test.ts diff --git a/packages/adapter-utils/src/server-utils.ts b/packages/adapter-utils/src/server-utils.ts index 52e52b4c..2bbe911d 100644 --- a/packages/adapter-utils/src/server-utils.ts +++ b/packages/adapter-utils/src/server-utils.ts @@ -8,6 +8,8 @@ export interface RunProcessResult { timedOut: boolean; stdout: string; stderr: string; + pid: number | null; + startedAt: string | null; } interface RunningProcess { @@ -423,6 +425,7 @@ export async function runChildProcess( graceSec: number; onLog: (stream: "stdout" | "stderr", chunk: string) => Promise; onLogError?: (err: unknown, runId: string, message: string) => void; + onSpawn?: (meta: { pid: number; startedAt: string }) => Promise; stdin?: string; }, ): Promise { @@ -455,12 +458,19 @@ export async function runChildProcess( shell: false, stdio: [opts.stdin != null ? "pipe" : "ignore", "pipe", "pipe"], }) as ChildProcessWithEvents; + const startedAt = new Date().toISOString(); if (opts.stdin != null && child.stdin) { child.stdin.write(opts.stdin); child.stdin.end(); } + if (typeof child.pid === "number" && child.pid > 0 && opts.onSpawn) { + void opts.onSpawn({ pid: child.pid, startedAt }).catch((err) => { + onLogError(err, runId, "failed to record child process metadata"); + }); + } + runningProcesses.set(runId, { child, graceSec: opts.graceSec }); let timedOut = false; @@ -519,6 +529,8 @@ export async function runChildProcess( timedOut, stdout, stderr, + pid: child.pid ?? null, + startedAt, }); }); }); diff --git a/packages/adapter-utils/src/types.ts b/packages/adapter-utils/src/types.ts index e7639593..d8f2ea3c 100644 --- a/packages/adapter-utils/src/types.ts +++ b/packages/adapter-utils/src/types.ts @@ -120,6 +120,7 @@ export interface AdapterExecutionContext { context: Record; onLog: (stream: "stdout" | "stderr", chunk: string) => Promise; onMeta?: (meta: AdapterInvocationMeta) => Promise; + onSpawn?: (meta: { pid: number; startedAt: string }) => Promise; authToken?: string; } diff --git a/packages/adapters/claude-local/src/server/execute.ts b/packages/adapters/claude-local/src/server/execute.ts index 415ea7bd..6bc0da64 100644 --- a/packages/adapters/claude-local/src/server/execute.ts +++ b/packages/adapters/claude-local/src/server/execute.ts @@ -303,7 +303,7 @@ export async function runClaudeLogin(input: { } export async function execute(ctx: AdapterExecutionContext): Promise { - const { runId, agent, runtime, config, context, onLog, onMeta, authToken } = ctx; + const { runId, agent, runtime, config, context, onLog, onMeta, onSpawn, authToken } = ctx; const promptTemplate = asString( config.promptTemplate, @@ -455,6 +455,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise { - const { runId, agent, runtime, config, context, onLog, onMeta, authToken } = ctx; + const { runId, agent, runtime, config, context, onLog, onMeta, onSpawn, authToken } = ctx; const promptTemplate = asString( config.promptTemplate, @@ -454,6 +454,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise { if (stream !== "stderr") { await onLog(stream, chunk); diff --git a/packages/adapters/cursor-local/src/server/execute.ts b/packages/adapters/cursor-local/src/server/execute.ts index 46614e1a..59d8e6cb 100644 --- a/packages/adapters/cursor-local/src/server/execute.ts +++ b/packages/adapters/cursor-local/src/server/execute.ts @@ -152,7 +152,7 @@ export async function ensureCursorSkillsInjected( } export async function execute(ctx: AdapterExecutionContext): Promise { - const { runId, agent, runtime, config, context, onLog, onMeta, authToken } = ctx; + const { runId, agent, runtime, config, context, onLog, onMeta, onSpawn, authToken } = ctx; const promptTemplate = asString( config.promptTemplate, @@ -419,6 +419,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise { if (stream !== "stdout") { await onLog(stream, chunk); diff --git a/packages/adapters/gemini-local/src/server/execute.ts b/packages/adapters/gemini-local/src/server/execute.ts index 475314d8..0c146041 100644 --- a/packages/adapters/gemini-local/src/server/execute.ts +++ b/packages/adapters/gemini-local/src/server/execute.ts @@ -129,7 +129,7 @@ async function ensureGeminiSkillsInjected( } export async function execute(ctx: AdapterExecutionContext): Promise { - const { runId, agent, runtime, config, context, onLog, onMeta, authToken } = ctx; + const { runId, agent, runtime, config, context, onLog, onMeta, onSpawn, authToken } = ctx; const promptTemplate = asString( config.promptTemplate, @@ -349,6 +349,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise { - const { runId, agent, runtime, config, context, onLog, onMeta, authToken } = ctx; + const { runId, agent, runtime, config, context, onLog, onMeta, onSpawn, authToken } = ctx; const promptTemplate = asString( config.promptTemplate, @@ -301,6 +301,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise { - const { runId, agent, runtime, config, context, onLog, onMeta, authToken } = ctx; + const { runId, agent, runtime, config, context, onLog, onMeta, onSpawn, authToken } = ctx; const promptTemplate = asString( config.promptTemplate, @@ -398,6 +398,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise statement-breakpoint +ALTER TABLE "heartbeat_runs" ADD COLUMN "process_started_at" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "heartbeat_runs" ADD COLUMN "retry_of_run_id" uuid;--> statement-breakpoint +ALTER TABLE "heartbeat_runs" ADD COLUMN "process_loss_retry_count" integer DEFAULT 0 NOT NULL;--> statement-breakpoint +ALTER TABLE "heartbeat_runs" ADD CONSTRAINT "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk" FOREIGN KEY ("retry_of_run_id") REFERENCES "public"."heartbeat_runs"("id") ON DELETE set null ON UPDATE no action; \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0038_snapshot.json b/packages/db/src/migrations/meta/0038_snapshot.json new file mode 100644 index 00000000..ad39617c --- /dev/null +++ b/packages/db/src/migrations/meta/0038_snapshot.json @@ -0,0 +1,10301 @@ +{ + "id": "cb7f5c2d-8be7-4bd7-8adc-6d942a4f2589", + "prevId": "8ff38d89-6a83-4736-a198-8960c880739c", + "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.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'" + }, + "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/_journal.json b/packages/db/src/migrations/meta/_journal.json index 890469eb..7e282cb3 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -267,6 +267,13 @@ "when": 1773756922363, "tag": "0037_friendly_eddie_brock", "breakpoints": true + }, + { + "idx": 38, + "version": "7", + "when": 1773931592563, + "tag": "0038_careless_iron_monger", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema/heartbeat_runs.ts b/packages/db/src/schema/heartbeat_runs.ts index 1557da3d..58a1dcdb 100644 --- a/packages/db/src/schema/heartbeat_runs.ts +++ b/packages/db/src/schema/heartbeat_runs.ts @@ -1,4 +1,4 @@ -import { pgTable, uuid, text, timestamp, jsonb, index, integer, bigint, boolean } from "drizzle-orm/pg-core"; +import { type AnyPgColumn, pgTable, uuid, text, timestamp, jsonb, index, integer, bigint, boolean } from "drizzle-orm/pg-core"; import { companies } from "./companies.js"; import { agents } from "./agents.js"; import { agentWakeupRequests } from "./agent_wakeup_requests.js"; @@ -31,6 +31,12 @@ export const heartbeatRuns = pgTable( stderrExcerpt: text("stderr_excerpt"), errorCode: text("error_code"), externalRunId: text("external_run_id"), + processPid: integer("process_pid"), + processStartedAt: timestamp("process_started_at", { withTimezone: true }), + retryOfRunId: uuid("retry_of_run_id").references((): AnyPgColumn => heartbeatRuns.id, { + onDelete: "set null", + }), + processLossRetryCount: integer("process_loss_retry_count").notNull().default(0), contextSnapshot: jsonb("context_snapshot").$type>(), createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), diff --git a/packages/shared/src/types/heartbeat.ts b/packages/shared/src/types/heartbeat.ts index 2e5a2006..c399b6da 100644 --- a/packages/shared/src/types/heartbeat.ts +++ b/packages/shared/src/types/heartbeat.ts @@ -33,6 +33,10 @@ export interface HeartbeatRun { stderrExcerpt: string | null; errorCode: string | null; externalRunId: string | null; + processPid: number | null; + processStartedAt: Date | null; + retryOfRunId: string | null; + processLossRetryCount: number; contextSnapshot: Record | null; createdAt: Date; updatedAt: Date; diff --git a/server/src/__tests__/heartbeat-process-recovery.test.ts b/server/src/__tests__/heartbeat-process-recovery.test.ts new file mode 100644 index 00000000..a5742f42 --- /dev/null +++ b/server/src/__tests__/heartbeat-process-recovery.test.ts @@ -0,0 +1,321 @@ +import { randomUUID } from "node:crypto"; +import fs from "node:fs"; +import net from "node:net"; +import os from "node:os"; +import path from "node:path"; +import { spawn, type ChildProcess } from "node:child_process"; +import { eq } from "drizzle-orm"; +import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; +import { + applyPendingMigrations, + createDb, + ensurePostgresDatabase, + agents, + agentWakeupRequests, + companies, + heartbeatRunEvents, + heartbeatRuns, + issues, +} from "@paperclipai/db"; +import { runningProcesses } from "../adapters/index.ts"; +import { heartbeatService } from "../services/heartbeat.ts"; + +type EmbeddedPostgresInstance = { + initialise(): Promise; + start(): Promise; + stop(): Promise; +}; + +type EmbeddedPostgresCtor = new (opts: { + databaseDir: string; + user: string; + password: string; + port: number; + persistent: boolean; + initdbFlags?: string[]; + onLog?: (message: unknown) => void; + onError?: (message: unknown) => void; +}) => EmbeddedPostgresInstance; + +async function getEmbeddedPostgresCtor(): Promise { + const mod = await import("embedded-postgres"); + return mod.default as EmbeddedPostgresCtor; +} + +async function getAvailablePort(): Promise { + return await new Promise((resolve, reject) => { + const server = net.createServer(); + server.unref(); + server.on("error", reject); + server.listen(0, "127.0.0.1", () => { + const address = server.address(); + if (!address || typeof address === "string") { + server.close(() => reject(new Error("Failed to allocate test port"))); + return; + } + const { port } = address; + server.close((error) => { + if (error) reject(error); + else resolve(port); + }); + }); + }); +} + +async function startTempDatabase() { + const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-heartbeat-recovery-")); + const port = await getAvailablePort(); + const EmbeddedPostgres = await getEmbeddedPostgresCtor(); + const instance = new EmbeddedPostgres({ + databaseDir: dataDir, + user: "paperclip", + password: "paperclip", + port, + persistent: true, + initdbFlags: ["--encoding=UTF8", "--locale=C"], + onLog: () => {}, + onError: () => {}, + }); + await instance.initialise(); + await instance.start(); + + const adminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/postgres`; + await ensurePostgresDatabase(adminConnectionString, "paperclip"); + const connectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/paperclip`; + await applyPendingMigrations(connectionString); + return { connectionString, instance, dataDir }; +} + +function spawnAliveProcess() { + return spawn(process.execPath, ["-e", "setInterval(() => {}, 1000)"], { + stdio: "ignore", + }); +} + +describe("heartbeat orphaned process recovery", () => { + let db!: ReturnType; + let instance: EmbeddedPostgresInstance | null = null; + let dataDir = ""; + const childProcesses = new Set(); + + beforeAll(async () => { + const started = await startTempDatabase(); + db = createDb(started.connectionString); + instance = started.instance; + dataDir = started.dataDir; + }, 20_000); + + afterEach(async () => { + runningProcesses.clear(); + for (const child of childProcesses) { + child.kill("SIGKILL"); + } + childProcesses.clear(); + await db.delete(issues); + await db.delete(heartbeatRunEvents); + await db.delete(heartbeatRuns); + await db.delete(agentWakeupRequests); + await db.delete(agents); + await db.delete(companies); + }); + + afterAll(async () => { + for (const child of childProcesses) { + child.kill("SIGKILL"); + } + childProcesses.clear(); + runningProcesses.clear(); + await instance?.stop(); + if (dataDir) { + fs.rmSync(dataDir, { recursive: true, force: true }); + } + }); + + async function seedRunFixture(input?: { + adapterType?: string; + runStatus?: "running" | "queued" | "failed"; + processPid?: number | null; + processLossRetryCount?: number; + includeIssue?: boolean; + runErrorCode?: string | null; + runError?: string | null; + }) { + const companyId = randomUUID(); + const agentId = randomUUID(); + const runId = randomUUID(); + const wakeupRequestId = randomUUID(); + const issueId = randomUUID(); + const now = new Date("2026-03-19T00:00:00.000Z"); + const issuePrefix = `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`; + + await db.insert(companies).values({ + id: companyId, + name: "Paperclip", + issuePrefix, + requireBoardApprovalForNewAgents: false, + }); + + await db.insert(agents).values({ + id: agentId, + companyId, + name: "CodexCoder", + role: "engineer", + status: "paused", + adapterType: input?.adapterType ?? "codex_local", + adapterConfig: {}, + runtimeConfig: {}, + permissions: {}, + }); + + await db.insert(agentWakeupRequests).values({ + id: wakeupRequestId, + companyId, + agentId, + source: "assignment", + triggerDetail: "system", + reason: "issue_assigned", + payload: input?.includeIssue === false ? {} : { issueId }, + status: "claimed", + runId, + claimedAt: now, + }); + + await db.insert(heartbeatRuns).values({ + id: runId, + companyId, + agentId, + invocationSource: "assignment", + triggerDetail: "system", + status: input?.runStatus ?? "running", + wakeupRequestId, + contextSnapshot: input?.includeIssue === false ? {} : { issueId }, + processPid: input?.processPid ?? null, + processLossRetryCount: input?.processLossRetryCount ?? 0, + errorCode: input?.runErrorCode ?? null, + error: input?.runError ?? null, + startedAt: now, + updatedAt: new Date("2026-03-19T00:00:00.000Z"), + }); + + if (input?.includeIssue !== false) { + await db.insert(issues).values({ + id: issueId, + companyId, + title: "Recover local adapter after lost process", + status: "in_progress", + priority: "medium", + assigneeAgentId: agentId, + checkoutRunId: runId, + executionRunId: runId, + issueNumber: 1, + identifier: `${issuePrefix}-1`, + }); + } + + return { companyId, agentId, runId, wakeupRequestId, issueId }; + } + + it("keeps a local run active when the recorded pid is still alive", async () => { + const child = spawnAliveProcess(); + childProcesses.add(child); + expect(child.pid).toBeTypeOf("number"); + + const { runId, wakeupRequestId } = await seedRunFixture({ + processPid: child.pid ?? null, + includeIssue: false, + }); + const heartbeat = heartbeatService(db); + + const result = await heartbeat.reapOrphanedRuns(); + expect(result.reaped).toBe(0); + + const run = await heartbeat.getRun(runId); + expect(run?.status).toBe("running"); + expect(run?.errorCode).toBe("process_detached"); + expect(run?.error).toContain(String(child.pid)); + + const wakeup = await db + .select() + .from(agentWakeupRequests) + .where(eq(agentWakeupRequests.id, wakeupRequestId)) + .then((rows) => rows[0] ?? null); + expect(wakeup?.status).toBe("claimed"); + }); + + it("queues exactly one retry when the recorded local pid is dead", async () => { + const { agentId, runId, issueId } = await seedRunFixture({ + processPid: 999_999_999, + }); + const heartbeat = heartbeatService(db); + + const result = await heartbeat.reapOrphanedRuns(); + expect(result.reaped).toBe(1); + expect(result.runIds).toEqual([runId]); + + const runs = await db + .select() + .from(heartbeatRuns) + .where(eq(heartbeatRuns.agentId, agentId)); + expect(runs).toHaveLength(2); + + const failedRun = runs.find((row) => row.id === runId); + const retryRun = runs.find((row) => row.id !== runId); + expect(failedRun?.status).toBe("failed"); + expect(failedRun?.errorCode).toBe("process_lost"); + expect(retryRun?.status).toBe("queued"); + expect(retryRun?.retryOfRunId).toBe(runId); + expect(retryRun?.processLossRetryCount).toBe(1); + + const issue = await db + .select() + .from(issues) + .where(eq(issues.id, issueId)) + .then((rows) => rows[0] ?? null); + expect(issue?.executionRunId).toBe(retryRun?.id ?? null); + expect(issue?.checkoutRunId).toBe(runId); + }); + + it("does not queue a second retry after the first process-loss retry was already used", async () => { + const { agentId, runId, issueId } = await seedRunFixture({ + processPid: 999_999_999, + processLossRetryCount: 1, + }); + const heartbeat = heartbeatService(db); + + const result = await heartbeat.reapOrphanedRuns(); + expect(result.reaped).toBe(1); + expect(result.runIds).toEqual([runId]); + + const runs = await db + .select() + .from(heartbeatRuns) + .where(eq(heartbeatRuns.agentId, agentId)); + expect(runs).toHaveLength(1); + expect(runs[0]?.status).toBe("failed"); + + const issue = await db + .select() + .from(issues) + .where(eq(issues.id, issueId)) + .then((rows) => rows[0] ?? null); + expect(issue?.executionRunId).toBeNull(); + expect(issue?.checkoutRunId).toBe(runId); + }); + + it("clears the detached warning when the run reports activity again", async () => { + const { runId } = await seedRunFixture({ + includeIssue: false, + runErrorCode: "process_detached", + runError: "Lost in-memory process handle, but child pid 123 is still alive", + }); + const heartbeat = heartbeatService(db); + + const updated = await heartbeat.reportRunActivity(runId); + expect(updated?.errorCode).toBeNull(); + expect(updated?.error).toBeNull(); + + const run = await heartbeat.getRun(runId); + expect(run?.errorCode).toBeNull(); + expect(run?.error).toBeNull(); + }); +}); diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index cf8d170a..215c532f 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -820,6 +820,7 @@ export function issueRoutes(db: Db, storage: StorageService) { } if (!(await assertAgentRunCheckoutOwnership(req, res, existing))) return; + const actor = getActorInfo(req); const { comment: commentBody, hiddenAt: hiddenAtRaw, ...updateFields } = req.body; if (hiddenAtRaw !== undefined) { updateFields.hiddenAt = hiddenAtRaw ? new Date(hiddenAtRaw) : null; @@ -856,6 +857,11 @@ export function issueRoutes(db: Db, storage: StorageService) { return; } + if (actor.runId) { + await heartbeat.reportRunActivity(actor.runId).catch((err) => + logger.warn({ err, runId: actor.runId }, "failed to clear detached run warning after issue activity")); + } + // Build activity details with previous values for changed fields const previous: Record = {}; for (const key of Object.keys(updateFields)) { @@ -864,7 +870,6 @@ export function issueRoutes(db: Db, storage: StorageService) { } } - const actor = getActorInfo(req); const hasFieldChanges = Object.keys(previous).length > 0; await logActivity(db, { companyId: issue.companyId, @@ -1278,6 +1283,11 @@ export function issueRoutes(db: Db, storage: StorageService) { userId: actor.actorType === "user" ? actor.actorId : undefined, }); + if (actor.runId) { + await heartbeat.reportRunActivity(actor.runId).catch((err) => + logger.warn({ err, runId: actor.runId }, "failed to clear detached run warning after issue comment")); + } + await logActivity(db, { companyId: currentIssue.companyId, actorType: actor.actorType, diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 8e338b90..18eb3ebe 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -60,6 +60,7 @@ const MAX_LIVE_LOG_CHUNK_BYTES = 8 * 1024; const HEARTBEAT_MAX_CONCURRENT_RUNS_DEFAULT = 1; const HEARTBEAT_MAX_CONCURRENT_RUNS_MAX = 10; const DEFERRED_WAKE_CONTEXT_KEY = "_paperclipWakeContext"; +const DETACHED_PROCESS_ERROR_CODE = "process_detached"; const startLocksByAgent = new Map>(); const REPO_ONLY_CWD_SENTINEL = "/__paperclip_repo_only__"; const MANAGED_WORKSPACE_GIT_CLONE_TIMEOUT_MS = 10 * 60 * 1000; @@ -163,6 +164,10 @@ const heartbeatRunListColumns = { stderrExcerpt: sql`NULL`.as("stderrExcerpt"), errorCode: heartbeatRuns.errorCode, externalRunId: heartbeatRuns.externalRunId, + processPid: heartbeatRuns.processPid, + processStartedAt: heartbeatRuns.processStartedAt, + retryOfRunId: heartbeatRuns.retryOfRunId, + processLossRetryCount: heartbeatRuns.processLossRetryCount, contextSnapshot: heartbeatRuns.contextSnapshot, createdAt: heartbeatRuns.createdAt, updatedAt: heartbeatRuns.updatedAt, @@ -598,6 +603,23 @@ function isSameTaskScope(left: string | null, right: string | null) { return (left ?? null) === (right ?? null); } +function isTrackedLocalChildProcessAdapter(adapterType: string) { + return SESSIONED_LOCAL_ADAPTERS.has(adapterType); +} + +function isProcessAlive(pid: number | null | undefined) { + if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return false; + try { + process.kill(pid, 0); + return true; + } catch (error) { + const code = (error as NodeJS.ErrnoException | undefined)?.code; + if (code === "EPERM") return true; + if (code === "ESRCH") return false; + return false; + } +} + function truncateDisplayId(value: string | null | undefined, max = 128) { if (!value) return null; return value.length > max ? value.slice(0, max) : value; @@ -1326,6 +1348,156 @@ export function heartbeatService(db: Db) { }); } + async function nextRunEventSeq(runId: string) { + const [row] = await db + .select({ maxSeq: sql`max(${heartbeatRunEvents.seq})` }) + .from(heartbeatRunEvents) + .where(eq(heartbeatRunEvents.runId, runId)); + return Number(row?.maxSeq ?? 0) + 1; + } + + async function persistRunProcessMetadata( + runId: string, + meta: { pid: number; startedAt: string }, + ) { + const startedAt = new Date(meta.startedAt); + return db + .update(heartbeatRuns) + .set({ + processPid: meta.pid, + processStartedAt: Number.isNaN(startedAt.getTime()) ? new Date() : startedAt, + updatedAt: new Date(), + }) + .where(eq(heartbeatRuns.id, runId)) + .returning() + .then((rows) => rows[0] ?? null); + } + + async function clearDetachedRunWarning(runId: string) { + const updated = await db + .update(heartbeatRuns) + .set({ + error: null, + errorCode: null, + updatedAt: new Date(), + }) + .where(and(eq(heartbeatRuns.id, runId), eq(heartbeatRuns.status, "running"), eq(heartbeatRuns.errorCode, DETACHED_PROCESS_ERROR_CODE))) + .returning() + .then((rows) => rows[0] ?? null); + if (!updated) return null; + + await appendRunEvent(updated, await nextRunEventSeq(updated.id), { + eventType: "lifecycle", + stream: "system", + level: "info", + message: "Detached child process reported activity; cleared detached warning", + }); + return updated; + } + + async function enqueueProcessLossRetry( + run: typeof heartbeatRuns.$inferSelect, + agent: typeof agents.$inferSelect, + now: Date, + ) { + const contextSnapshot = parseObject(run.contextSnapshot); + const issueId = readNonEmptyString(contextSnapshot.issueId); + const taskKey = deriveTaskKey(contextSnapshot, null); + const sessionBefore = await resolveSessionBeforeForWakeup(agent, taskKey); + const retryContextSnapshot = { + ...contextSnapshot, + retryOfRunId: run.id, + wakeReason: "process_lost_retry", + retryReason: "process_lost", + }; + + const queued = await db.transaction(async (tx) => { + const wakeupRequest = await tx + .insert(agentWakeupRequests) + .values({ + companyId: run.companyId, + agentId: run.agentId, + source: "automation", + triggerDetail: "system", + reason: "process_lost_retry", + payload: { + ...(issueId ? { issueId } : {}), + retryOfRunId: run.id, + }, + status: "queued", + requestedByActorType: "system", + requestedByActorId: null, + updatedAt: now, + }) + .returning() + .then((rows) => rows[0]); + + const retryRun = await tx + .insert(heartbeatRuns) + .values({ + companyId: run.companyId, + agentId: run.agentId, + invocationSource: "automation", + triggerDetail: "system", + status: "queued", + wakeupRequestId: wakeupRequest.id, + contextSnapshot: retryContextSnapshot, + sessionIdBefore: sessionBefore, + retryOfRunId: run.id, + processLossRetryCount: (run.processLossRetryCount ?? 0) + 1, + updatedAt: now, + }) + .returning() + .then((rows) => rows[0]); + + await tx + .update(agentWakeupRequests) + .set({ + runId: retryRun.id, + updatedAt: now, + }) + .where(eq(agentWakeupRequests.id, wakeupRequest.id)); + + if (issueId) { + await tx + .update(issues) + .set({ + executionRunId: retryRun.id, + executionAgentNameKey: normalizeAgentNameKey(agent.name), + executionLockedAt: now, + updatedAt: now, + }) + .where(and(eq(issues.id, issueId), eq(issues.companyId, run.companyId), eq(issues.executionRunId, run.id))); + } + + return retryRun; + }); + + publishLiveEvent({ + companyId: queued.companyId, + type: "heartbeat.run.queued", + payload: { + runId: queued.id, + agentId: queued.agentId, + invocationSource: queued.invocationSource, + triggerDetail: queued.triggerDetail, + wakeupRequestId: queued.wakeupRequestId, + }, + }); + + await appendRunEvent(queued, 1, { + eventType: "lifecycle", + stream: "system", + level: "warn", + message: "Queued automatic retry after orphaned child process was confirmed dead", + payload: { + retryOfRunId: run.id, + }, + }); + + return queued; + } + function parseHeartbeatPolicy(agent: typeof agents.$inferSelect) { const runtimeConfig = parseObject(agent.runtimeConfig); const heartbeat = parseObject(runtimeConfig.heartbeat); @@ -1453,13 +1625,17 @@ export function heartbeatService(db: Db) { // Find all runs stuck in "running" state (queued runs are legitimately waiting; resumeQueuedRuns handles them) const activeRuns = await db - .select() + .select({ + run: heartbeatRuns, + adapterType: agents.adapterType, + }) .from(heartbeatRuns) + .innerJoin(agents, eq(heartbeatRuns.agentId, agents.id)) .where(eq(heartbeatRuns.status, "running")); const reaped: string[] = []; - for (const run of activeRuns) { + for (const { run, adapterType } of activeRuns) { if (runningProcesses.has(run.id) || activeRunExecutions.has(run.id)) continue; // Apply staleness threshold to avoid false positives @@ -1468,25 +1644,69 @@ export function heartbeatService(db: Db) { if (now.getTime() - refTime < staleThresholdMs) continue; } - await setRunStatus(run.id, "failed", { - error: "Process lost -- server may have restarted", + const tracksLocalChild = isTrackedLocalChildProcessAdapter(adapterType); + if (tracksLocalChild && run.processPid && isProcessAlive(run.processPid)) { + if (run.errorCode !== DETACHED_PROCESS_ERROR_CODE) { + const detachedMessage = `Lost in-memory process handle, but child pid ${run.processPid} is still alive`; + const detachedRun = await setRunStatus(run.id, "running", { + error: detachedMessage, + errorCode: DETACHED_PROCESS_ERROR_CODE, + }); + if (detachedRun) { + await appendRunEvent(detachedRun, await nextRunEventSeq(detachedRun.id), { + eventType: "lifecycle", + stream: "system", + level: "warn", + message: detachedMessage, + payload: { + processPid: run.processPid, + }, + }); + } + } + continue; + } + + const shouldRetry = tracksLocalChild && !!run.processPid && (run.processLossRetryCount ?? 0) < 1; + const baseMessage = run.processPid + ? `Process lost -- child pid ${run.processPid} is no longer running` + : "Process lost -- server may have restarted"; + + let finalizedRun = await setRunStatus(run.id, "failed", { + error: shouldRetry ? `${baseMessage}; retrying once` : baseMessage, errorCode: "process_lost", finishedAt: now, }); await setWakeupStatus(run.wakeupRequestId, "failed", { finishedAt: now, - error: "Process lost -- server may have restarted", + error: shouldRetry ? `${baseMessage}; retrying once` : baseMessage, }); - const updatedRun = await getRun(run.id); - if (updatedRun) { - await appendRunEvent(updatedRun, 1, { - eventType: "lifecycle", - stream: "system", - level: "error", - message: "Process lost -- server may have restarted", - }); - await releaseIssueExecutionAndPromote(updatedRun); + if (!finalizedRun) finalizedRun = await getRun(run.id); + if (!finalizedRun) continue; + + let retriedRun: typeof heartbeatRuns.$inferSelect | null = null; + if (shouldRetry) { + const agent = await getAgent(run.agentId); + if (agent) { + retriedRun = await enqueueProcessLossRetry(finalizedRun, agent, now); + } + } else { + await releaseIssueExecutionAndPromote(finalizedRun); } + + await appendRunEvent(finalizedRun, await nextRunEventSeq(finalizedRun.id), { + eventType: "lifecycle", + stream: "system", + level: "error", + message: shouldRetry + ? `${baseMessage}; queued retry ${retriedRun?.id ?? ""}`.trim() + : baseMessage, + payload: { + ...(run.processPid ? { processPid: run.processPid } : {}), + ...(retriedRun ? { retryRunId: retriedRun.id } : {}), + }, + }); + await finalizeAgentStatus(run.agentId, "failed"); await startNextQueuedRunForAgent(run.agentId); runningProcesses.delete(run.id); @@ -2152,6 +2372,9 @@ export function heartbeatService(db: Db) { context, onLog, onMeta: onAdapterMeta, + onSpawn: async (meta) => { + await persistRunProcessMetadata(run.id, meta); + }, authToken: authToken ?? undefined, }); const adapterManagedRuntimeServices = adapterResult.runtimeServices @@ -3403,6 +3626,8 @@ export function heartbeatService(db: Db) { wakeup: enqueueWakeup, + reportRunActivity: clearDetachedRunWarning, + reapOrphanedRuns, resumeQueuedRuns, diff --git a/ui/src/lib/inbox.test.ts b/ui/src/lib/inbox.test.ts index 13e0ada0..2a9f629e 100644 --- a/ui/src/lib/inbox.test.ts +++ b/ui/src/lib/inbox.test.ts @@ -111,6 +111,10 @@ function makeRun(id: string, status: HeartbeatRun["status"], createdAt: string, logCompressed: false, errorCode: null, externalRunId: null, + processPid: null, + processStartedAt: null, + retryOfRunId: null, + processLossRetryCount: 0, stdoutExcerpt: null, stderrExcerpt: null, contextSnapshot: null, From ee850285340b74034b98c17a2a0526fa46c082d6 Mon Sep 17 00:00:00 2001 From: dotta Date: Fri, 20 Mar 2026 06:04:43 -0500 Subject: [PATCH 23/26] docs: expand paperclip company skills guidance --- skills/paperclip/SKILL.md | 116 ++++++----- skills/paperclip/references/company-skills.md | 187 ++++++++++++++++++ 2 files changed, 254 insertions(+), 49 deletions(-) create mode 100644 skills/paperclip/references/company-skills.md diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index 5335dcbe..ee5ac2ae 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -126,6 +126,17 @@ Access control: 4. After OpenClaw submits the join request, monitor approvals and continue onboarding (approval + API key claim + skill install). +## Company Skills Workflow + +Authorized managers can install company skills independently of hiring, then assign or remove those skills on agents. + +- Install and inspect company skills with the company skills API. +- Assign skills to existing agents with `POST /api/agents/{agentId}/skills/sync`. +- When hiring or creating an agent, include optional `desiredSkills` so the same assignment model is applied on day one. + +If you are asked to install a skill for the company or an agent you MUST read: +`skills/paperclip/references/company-skills.md` + ## Critical Rules - **Always checkout** before working. Never PATCH to `in_progress` manually. @@ -240,60 +251,67 @@ PATCH /api/agents/{agentId}/instructions-path ## Key Endpoints (Quick Reference) -| Action | Endpoint | -| ------------------------------------- | ------------------------------------------------------------------------------------------ | -| My identity | `GET /api/agents/me` | -| My compact inbox | `GET /api/agents/me/inbox-lite` | -| My assignments | `GET /api/companies/:companyId/issues?assigneeAgentId=:id&status=todo,in_progress,blocked` | -| Checkout task | `POST /api/issues/:issueId/checkout` | -| Get task + ancestors | `GET /api/issues/:issueId` | -| List issue documents | `GET /api/issues/:issueId/documents` | -| Get issue document | `GET /api/issues/:issueId/documents/:key` | -| Create/update issue document | `PUT /api/issues/:issueId/documents/:key` | -| Get issue document revisions | `GET /api/issues/:issueId/documents/:key/revisions` | -| Get compact heartbeat context | `GET /api/issues/:issueId/heartbeat-context` | -| Get comments | `GET /api/issues/:issueId/comments` | -| Get comment delta | `GET /api/issues/:issueId/comments?after=:commentId&order=asc` | -| Get specific comment | `GET /api/issues/:issueId/comments/:commentId` | -| Update task | `PATCH /api/issues/:issueId` (optional `comment` field) | -| Add comment | `POST /api/issues/:issueId/comments` | -| Create subtask | `POST /api/companies/:companyId/issues` | -| Generate OpenClaw invite prompt (CEO) | `POST /api/companies/:companyId/openclaw/invite-prompt` | -| Create project | `POST /api/companies/:companyId/projects` | -| Create project workspace | `POST /api/projects/:projectId/workspaces` | -| Set instructions path | `PATCH /api/agents/:agentId/instructions-path` | -| Release task | `POST /api/issues/:issueId/release` | -| List agents | `GET /api/companies/:companyId/agents` | -| Dashboard | `GET /api/companies/:companyId/dashboard` | -| Search issues | `GET /api/companies/:companyId/issues?q=search+term` | -| Get company details (CEO/board) | `GET /api/companies/:companyId` | -| Update company branding (CEO/board) | `PATCH /api/companies/:companyId` | -| Upload company logo | `POST /api/companies/:companyId/logo` (multipart file upload) | +| Action | Endpoint | +| ----------------------------------------- | ------------------------------------------------------------------------------------------ | +| My identity | `GET /api/agents/me` | +| My compact inbox | `GET /api/agents/me/inbox-lite` | +| My assignments | `GET /api/companies/:companyId/issues?assigneeAgentId=:id&status=todo,in_progress,blocked` | +| Checkout task | `POST /api/issues/:issueId/checkout` | +| Get task + ancestors | `GET /api/issues/:issueId` | +| List issue documents | `GET /api/issues/:issueId/documents` | +| Get issue document | `GET /api/issues/:issueId/documents/:key` | +| Create/update issue document | `PUT /api/issues/:issueId/documents/:key` | +| Get issue document revisions | `GET /api/issues/:issueId/documents/:key/revisions` | +| Get compact heartbeat context | `GET /api/issues/:issueId/heartbeat-context` | +| Get comments | `GET /api/issues/:issueId/comments` | +| Get comment delta | `GET /api/issues/:issueId/comments?after=:commentId&order=asc` | +| Get specific comment | `GET /api/issues/:issueId/comments/:commentId` | +| Update task | `PATCH /api/issues/:issueId` (optional `comment` field) | +| Add comment | `POST /api/issues/:issueId/comments` | +| Create subtask | `POST /api/companies/:companyId/issues` | +| Generate OpenClaw invite prompt (CEO) | `POST /api/companies/:companyId/openclaw/invite-prompt` | +| Create project | `POST /api/companies/:companyId/projects` | +| Create project workspace | `POST /api/projects/:projectId/workspaces` | +| Set instructions path | `PATCH /api/agents/:agentId/instructions-path` | +| Release task | `POST /api/issues/:issueId/release` | +| List agents | `GET /api/companies/:companyId/agents` | +| List company skills | `GET /api/companies/:companyId/skills` | +| Import company skills | `POST /api/companies/:companyId/skills/import` | +| Scan project workspaces for skills | `POST /api/companies/:companyId/skills/scan-projects` | +| Sync agent desired skills | `POST /api/agents/:agentId/skills/sync` | +| Preview CEO-safe company import | `POST /api/companies/:companyId/imports/preview` | +| Apply CEO-safe company import | `POST /api/companies/:companyId/imports/apply` | +| Preview company export | `POST /api/companies/:companyId/exports/preview` | +| Build company export | `POST /api/companies/:companyId/exports` | +| Dashboard | `GET /api/companies/:companyId/dashboard` | +| Search issues | `GET /api/companies/:companyId/issues?q=search+term` | +| Upload attachment (multipart, field=file) | `POST /api/companies/:companyId/issues/:issueId/attachments` | +| List issue attachments | `GET /api/issues/:issueId/attachments` | +| Get attachment content | `GET /api/attachments/:attachmentId/content` | +| Delete attachment | `DELETE /api/attachments/:attachmentId` | -## Company Branding (CEO) +## Company Import / Export -CEO agents can read and update their company's branding. Board users have full access to all company fields. +Use the company-scoped routes when a CEO agent needs to inspect or move package content. -**Readable fields** (via `GET /api/companies/:companyId`): +- CEO-safe imports: + - `POST /api/companies/{companyId}/imports/preview` + - `POST /api/companies/{companyId}/imports/apply` +- Allowed callers: board users and the CEO agent of that same company. +- Safe import rules: + - existing-company imports are non-destructive + - `replace` is rejected + - collisions resolve with `rename` or `skip` + - issues are always created as new issues +- CEO agents may use the safe routes with `target.mode = "new_company"` to create a new company directly. Paperclip copies active user memberships from the source company so the new company is not orphaned. -All company fields including `name`, `description`, `brandColor`, `logoUrl`, `issuePrefix`. +For export, preview first and keep tasks explicit: -**Updatable fields** (CEO agents, via `PATCH /api/companies/:companyId`): - -| Field | Type | Notes | -| ------------- | ------------------------ | ----------------------------------------- | -| `name` | string | Company display name | -| `description` | string \| null | Company description | -| `brandColor` | string \| null | Hex color, e.g. `#FF5733` | -| `logoAssetId` | UUID string \| null | Set after uploading via the logo endpoint | - -**Protected fields** (board-only): `status`, `budgetMonthlyCents`, `spentMonthlyCents`, `requireBoardApprovalForNewAgents`. The `issuePrefix` (company slug) cannot be changed via API. - -**Logo upload flow:** - -1. Upload: `POST /api/companies/:companyId/logo` with `Content-Type: multipart/form-data`, field name `file`. Accepts PNG, JPEG, WebP, GIF, SVG (max 10 MB). -2. Set: `PATCH /api/companies/:companyId` with `{ "logoAssetId": "" }`. -3. Clear: `PATCH /api/companies/:companyId` with `{ "logoAssetId": null }`. +- `POST /api/companies/{companyId}/exports/preview` +- `POST /api/companies/{companyId}/exports` +- Export preview defaults to `issues: false` +- Add `issues` or `projectIssues` only when you intentionally need task files +- Use `selectedFiles` to narrow the final package to specific agents, skills, projects, or tasks after you inspect the preview inventory ## Searching Issues diff --git a/skills/paperclip/references/company-skills.md b/skills/paperclip/references/company-skills.md new file mode 100644 index 00000000..a0ddd064 --- /dev/null +++ b/skills/paperclip/references/company-skills.md @@ -0,0 +1,187 @@ +# Company Skills Workflow + +Use this reference when a board user, CEO, or manager asks you to find a skill, install it into the company library, or assign it to an agent. + +## What Exists + +- Company skill library: install, inspect, update, and read imported skills for the whole company. +- Agent skill assignment: add or remove company skills on an existing agent. +- Hire/create composition: pass `desiredSkills` when creating or hiring an agent so the same assignment model applies immediately. + +The canonical model is: + +1. install the skill into the company +2. assign the company skill to the agent +3. optionally do step 2 during hire/create with `desiredSkills` + +## Permission Model + +- Company skill reads: any same-company actor +- Company skill mutations: board, CEO, or an agent with the effective `agents:create` capability +- Agent skill assignment: same permission model as updating that agent + +## Core Endpoints + +- `GET /api/companies/:companyId/skills` +- `GET /api/companies/:companyId/skills/:skillId` +- `POST /api/companies/:companyId/skills/import` +- `POST /api/companies/:companyId/skills/scan-projects` +- `POST /api/companies/:companyId/skills/:skillId/install-update` +- `GET /api/agents/:agentId/skills` +- `POST /api/agents/:agentId/skills/sync` +- `POST /api/companies/:companyId/agent-hires` +- `POST /api/companies/:companyId/agents` + +## Install A Skill Into The Company + +Import using a **skills.sh URL**, a key-style source string, a GitHub URL, or a local path. + +### Source types (in order of preference) + +| Source format | Example | When to use | +|---|---|---| +| **skills.sh URL** | `https://skills.sh/google-labs-code/stitch-skills/design-md` | When a user gives you a `skills.sh` link. This is the managed skill registry — **always prefer it when available**. | +| **Key-style string** | `google-labs-code/stitch-skills/design-md` | Shorthand for the same skill — `org/repo/skill-name` format. Equivalent to the skills.sh URL. | +| **GitHub URL** | `https://github.com/vercel-labs/agent-browser` | When the skill is in a GitHub repo but not on skills.sh. | +| **Local path** | `/abs/path/to/skill-dir` | When the skill is on disk (dev/testing only). | + +**Critical:** If a user gives you a `https://skills.sh/...` URL, use that URL or its key-style equivalent (`org/repo/skill-name`) as the `source`. Do **not** convert it to a GitHub URL — skills.sh is the managed registry and the source of truth for versioning, discovery, and updates. + +### Example: skills.sh import (preferred) + +```sh +curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills/import" \ + -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "source": "https://skills.sh/google-labs-code/stitch-skills/design-md" + }' +``` + +Or equivalently using the key-style string: + +```sh +curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills/import" \ + -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "source": "google-labs-code/stitch-skills/design-md" + }' +``` + +### Example: GitHub import + +```sh +curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills/import" \ + -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "source": "https://github.com/vercel-labs/agent-browser" + }' +``` + +If the task is to discover skills from the company project workspaces first: + +```sh +curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills/scan-projects" \ + -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +## Inspect What Was Installed + +```sh +curl -sS "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills" \ + -H "Authorization: Bearer $PAPERCLIP_API_KEY" +``` + +Read the skill entry and its `SKILL.md`: + +```sh +curl -sS "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills/" \ + -H "Authorization: Bearer $PAPERCLIP_API_KEY" + +curl -sS "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/skills//files?path=SKILL.md" \ + -H "Authorization: Bearer $PAPERCLIP_API_KEY" +``` + +## Assign Skills To An Existing Agent + +`desiredSkills` accepts: + +- exact company skill key +- exact company skill id +- exact slug when it is unique in the company + +The server persists canonical company skill keys. + +```sh +curl -sS -X POST "$PAPERCLIP_API_URL/api/agents//skills/sync" \ + -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "desiredSkills": [ + "vercel-labs/agent-browser/agent-browser" + ] + }' +``` + +If you need the current state first: + +```sh +curl -sS "$PAPERCLIP_API_URL/api/agents//skills" \ + -H "Authorization: Bearer $PAPERCLIP_API_KEY" +``` + +## Include Skills During Hire Or Create + +Use the same company skill keys or references in `desiredSkills` when hiring or creating an agent: + +```sh +curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/agent-hires" \ + -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "QA Browser Agent", + "role": "qa", + "adapterType": "codex_local", + "adapterConfig": { + "cwd": "/abs/path/to/repo" + }, + "desiredSkills": [ + "agent-browser" + ] + }' +``` + +For direct create without approval: + +```sh +curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/agents" \ + -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "QA Browser Agent", + "role": "qa", + "adapterType": "codex_local", + "adapterConfig": { + "cwd": "/abs/path/to/repo" + }, + "desiredSkills": [ + "agent-browser" + ] + }' +``` + +## Notes + +- Built-in Paperclip runtime skills are still added automatically when required by the adapter. +- If a reference is missing or ambiguous, the API returns `422`. +- Prefer linking back to the relevant issue, approval, and agent when you comment about skill changes. +- Use company portability routes when you need whole-package import/export, not just a skill: + - `POST /api/companies/:companyId/imports/preview` + - `POST /api/companies/:companyId/imports/apply` + - `POST /api/companies/:companyId/exports/preview` + - `POST /api/companies/:companyId/exports` +- Use skill-only import when the task is specifically to add a skill to the company library without importing the surrounding company/team/package structure. From 4ffa2b15dcf85ad8bfd74601d282c1496f4a6c93 Mon Sep 17 00:00:00 2001 From: dotta Date: Fri, 20 Mar 2026 06:05:05 -0500 Subject: [PATCH 24/26] fix: suggest comment reassignment from recent commenter --- ui/src/components/CommentThread.tsx | 11 +++++++---- ui/src/lib/assignees.test.ts | 24 ++++++++++++++++++++++++ ui/src/lib/assignees.ts | 28 ++++++++++++++++++++++++++++ ui/src/pages/IssueDetail.tsx | 28 ++++++++++++---------------- 4 files changed, 71 insertions(+), 20 deletions(-) diff --git a/ui/src/components/CommentThread.tsx b/ui/src/components/CommentThread.tsx index 8e042acf..0e97f31a 100644 --- a/ui/src/components/CommentThread.tsx +++ b/ui/src/components/CommentThread.tsx @@ -46,6 +46,7 @@ interface CommentThreadProps { enableReassign?: boolean; reassignOptions?: InlineEntityOption[]; currentAssigneeValue?: string; + suggestedAssigneeValue?: string; mentions?: MentionOption[]; } @@ -269,13 +270,15 @@ export function CommentThread({ enableReassign = false, reassignOptions = [], currentAssigneeValue = "", + suggestedAssigneeValue, mentions: providedMentions, }: CommentThreadProps) { const [body, setBody] = useState(""); const [reopen, setReopen] = useState(true); const [submitting, setSubmitting] = useState(false); const [attaching, setAttaching] = useState(false); - const [reassignTarget, setReassignTarget] = useState(currentAssigneeValue); + const effectiveSuggestedAssigneeValue = suggestedAssigneeValue ?? currentAssigneeValue; + const [reassignTarget, setReassignTarget] = useState(effectiveSuggestedAssigneeValue); const [highlightCommentId, setHighlightCommentId] = useState(null); const editorRef = useRef(null); const attachInputRef = useRef(null); @@ -337,8 +340,8 @@ export function CommentThread({ }, []); useEffect(() => { - setReassignTarget(currentAssigneeValue); - }, [currentAssigneeValue]); + setReassignTarget(effectiveSuggestedAssigneeValue); + }, [effectiveSuggestedAssigneeValue]); // Scroll to comment when URL hash matches #comment-{id} useEffect(() => { @@ -370,7 +373,7 @@ export function CommentThread({ setBody(""); if (draftKey) clearDraft(draftKey); setReopen(false); - setReassignTarget(currentAssigneeValue); + setReassignTarget(effectiveSuggestedAssigneeValue); } finally { setSubmitting(false); } diff --git a/ui/src/lib/assignees.test.ts b/ui/src/lib/assignees.test.ts index 1ce22ef7..59822ef6 100644 --- a/ui/src/lib/assignees.test.ts +++ b/ui/src/lib/assignees.test.ts @@ -4,6 +4,7 @@ import { currentUserAssigneeOption, formatAssigneeUserLabel, parseAssigneeValue, + suggestedCommentAssigneeValue, } from "./assignees"; describe("assignee selection helpers", () => { @@ -50,4 +51,27 @@ describe("assignee selection helpers", () => { expect(formatAssigneeUserLabel("local-board", "someone-else")).toBe("Board"); expect(formatAssigneeUserLabel("user-abcdef", "someone-else")).toBe("user-"); }); + + it("suggests the last non-me commenter without changing the actual assignee encoding", () => { + expect( + suggestedCommentAssigneeValue( + { assigneeUserId: "board-user" }, + [ + { authorUserId: "board-user" }, + { authorAgentId: "agent-123" }, + ], + "board-user", + ), + ).toBe("agent:agent-123"); + }); + + it("falls back to the actual assignee when there is no better commenter hint", () => { + expect( + suggestedCommentAssigneeValue( + { assigneeUserId: "board-user" }, + [{ authorUserId: "board-user" }], + "board-user", + ), + ).toBe("user:board-user"); + }); }); diff --git a/ui/src/lib/assignees.ts b/ui/src/lib/assignees.ts index 274bcd40..c4df80a3 100644 --- a/ui/src/lib/assignees.ts +++ b/ui/src/lib/assignees.ts @@ -9,12 +9,40 @@ export interface AssigneeOption { searchText?: string; } +interface CommentAssigneeSuggestionInput { + assigneeAgentId?: string | null; + assigneeUserId?: string | null; +} + +interface CommentAssigneeSuggestionComment { + authorAgentId?: string | null; + authorUserId?: string | null; +} + export function assigneeValueFromSelection(selection: Partial): string { if (selection.assigneeAgentId) return `agent:${selection.assigneeAgentId}`; if (selection.assigneeUserId) return `user:${selection.assigneeUserId}`; return ""; } +export function suggestedCommentAssigneeValue( + issue: CommentAssigneeSuggestionInput, + comments: CommentAssigneeSuggestionComment[] | null | undefined, + currentUserId: string | null | undefined, +): string { + if (comments && comments.length > 0 && currentUserId) { + for (let i = comments.length - 1; i >= 0; i--) { + const comment = comments[i]; + if (comment.authorAgentId) return assigneeValueFromSelection({ assigneeAgentId: comment.authorAgentId }); + if (comment.authorUserId && comment.authorUserId !== currentUserId) { + return assigneeValueFromSelection({ assigneeUserId: comment.authorUserId }); + } + } + } + + return assigneeValueFromSelection(issue); +} + export function parseAssigneeValue(value: string): AssigneeSelection { if (!value) { return { assigneeAgentId: null, assigneeUserId: null }; diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index e90965f6..fbed1e9c 100644 --- a/ui/src/pages/IssueDetail.tsx +++ b/ui/src/pages/IssueDetail.tsx @@ -11,6 +11,7 @@ import { useCompany } from "../context/CompanyContext"; import { usePanel } from "../context/PanelContext"; import { useToast } from "../context/ToastContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; +import { assigneeValueFromSelection, suggestedCommentAssigneeValue } from "../lib/assignees"; import { queryKeys } from "../lib/queryKeys"; import { readIssueDetailBreadcrumb } from "../lib/issueDetailBreadcrumb"; import { useProjectOrder } from "../hooks/useProjectOrder"; @@ -374,21 +375,15 @@ export function IssueDetail() { return options; }, [agents, currentUserId]); - const currentAssigneeValue = useMemo(() => { - // Default to the last commenter who is not "me" so the user doesn't - // accidentally reassign to themselves when commenting on their own issue. - if (comments && comments.length > 0 && currentUserId) { - for (let i = comments.length - 1; i >= 0; i--) { - const c = comments[i]; - if (c.authorAgentId) return `agent:${c.authorAgentId}`; - if (c.authorUserId && c.authorUserId !== currentUserId) - return `user:${c.authorUserId}`; - } - } - if (issue?.assigneeAgentId) return `agent:${issue.assigneeAgentId}`; - if (issue?.assigneeUserId) return `user:${issue.assigneeUserId}`; - return ""; - }, [issue?.assigneeAgentId, issue?.assigneeUserId, comments, currentUserId]); + const actualAssigneeValue = useMemo( + () => assigneeValueFromSelection(issue ?? {}), + [issue], + ); + + const suggestedAssigneeValue = useMemo( + () => suggestedCommentAssigneeValue(issue ?? {}, comments, currentUserId), + [issue, comments, currentUserId], + ); const commentsWithRunMeta = useMemo(() => { const runMetaByCommentId = new Map(); @@ -1011,7 +1006,8 @@ export function IssueDetail() { draftKey={`paperclip:issue-comment-draft:${issue.id}`} enableReassign reassignOptions={commentReassignOptions} - currentAssigneeValue={currentAssigneeValue} + currentAssigneeValue={actualAssigneeValue} + suggestedAssigneeValue={suggestedAssigneeValue} mentions={mentionOptions} onAdd={async (body, reopen, reassignment) => { if (reassignment) { From 22b38b19564b857a8238cc5a31563c9935240471 Mon Sep 17 00:00:00 2001 From: dotta Date: Fri, 20 Mar 2026 06:09:04 -0500 Subject: [PATCH 25/26] Use toggle for task assignment permission control Co-Authored-By: Paperclip --- ui/src/pages/AgentDetail.tsx | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/ui/src/pages/AgentDetail.tsx b/ui/src/pages/AgentDetail.tsx index 9c2a66a3..3e238e5e 100644 --- a/ui/src/pages/AgentDetail.tsx +++ b/ui/src/pages/AgentDetail.tsx @@ -1456,10 +1456,16 @@ function ConfigurationTab({ {taskAssignHint}

- + +
From a0a28fce38331a4548933ee3be373a8aa1a0da1a Mon Sep 17 00:00:00 2001 From: dotta Date: Fri, 20 Mar 2026 06:18:00 -0500 Subject: [PATCH 26/26] fix: address greptile feedback --- server/src/services/heartbeat.ts | 3 +++ ui/src/lib/assignees.test.ts | 15 +++++++++++++++ ui/src/lib/assignees.ts | 7 +++++-- ui/src/pages/Inbox.tsx | 15 ++++++++++++++- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 18eb3ebe..c162e187 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -607,6 +607,9 @@ function isTrackedLocalChildProcessAdapter(adapterType: string) { return SESSIONED_LOCAL_ADAPTERS.has(adapterType); } +// A positive liveness check means some process currently owns the PID. +// On Linux, PIDs can be recycled, so this is a best-effort signal rather +// than proof that the original child is still alive. function isProcessAlive(pid: number | null | undefined) { if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return false; try { diff --git a/ui/src/lib/assignees.test.ts b/ui/src/lib/assignees.test.ts index 59822ef6..225f7133 100644 --- a/ui/src/lib/assignees.test.ts +++ b/ui/src/lib/assignees.test.ts @@ -74,4 +74,19 @@ describe("assignee selection helpers", () => { ), ).toBe("user:board-user"); }); + + it("skips the current agent when choosing a suggested commenter assignee", () => { + expect( + suggestedCommentAssigneeValue( + { assigneeUserId: "board-user" }, + [ + { authorUserId: "board-user" }, + { authorAgentId: "agent-self" }, + { authorAgentId: "agent-123" }, + ], + null, + "agent-self", + ), + ).toBe("agent:agent-123"); + }); }); diff --git a/ui/src/lib/assignees.ts b/ui/src/lib/assignees.ts index c4df80a3..0dc57e96 100644 --- a/ui/src/lib/assignees.ts +++ b/ui/src/lib/assignees.ts @@ -29,11 +29,14 @@ export function suggestedCommentAssigneeValue( issue: CommentAssigneeSuggestionInput, comments: CommentAssigneeSuggestionComment[] | null | undefined, currentUserId: string | null | undefined, + currentAgentId?: string | null | undefined, ): string { - if (comments && comments.length > 0 && currentUserId) { + if (comments && comments.length > 0 && (currentUserId || currentAgentId)) { for (let i = comments.length - 1; i >= 0; i--) { const comment = comments[i]; - if (comment.authorAgentId) return assigneeValueFromSelection({ assigneeAgentId: comment.authorAgentId }); + if (comment.authorAgentId && comment.authorAgentId !== currentAgentId) { + return assigneeValueFromSelection({ assigneeAgentId: comment.authorAgentId }); + } if (comment.authorUserId && comment.authorUserId !== currentUserId) { return assigneeValueFromSelection({ assigneeUserId: comment.authorUserId }); } diff --git a/ui/src/pages/Inbox.tsx b/ui/src/pages/Inbox.tsx index c75fdeab..d2a6ce20 100644 --- a/ui/src/pages/Inbox.tsx +++ b/ui/src/pages/Inbox.tsx @@ -497,6 +497,8 @@ export function Inbox() { }, }); + const [retryingRunIds, setRetryingRunIds] = useState>(new Set()); + const retryRunMutation = useMutation({ mutationFn: async (run: HeartbeatRun) => { const payload: Record = {}; @@ -517,11 +519,22 @@ export function Inbox() { } return { newRun: result, originalRun: run }; }, + onMutate: (run) => { + setRetryingRunIds((prev) => new Set(prev).add(run.id)); + }, onSuccess: ({ newRun, originalRun }) => { queryClient.invalidateQueries({ queryKey: queryKeys.heartbeats(originalRun.companyId) }); queryClient.invalidateQueries({ queryKey: queryKeys.heartbeats(originalRun.companyId, originalRun.agentId) }); navigate(`/agents/${originalRun.agentId}/runs/${newRun.id}`); }, + onSettled: (_data, _error, run) => { + if (!run) return; + setRetryingRunIds((prev) => { + const next = new Set(prev); + next.delete(run.id); + return next; + }); + }, }); const [fadingOutIssues, setFadingOutIssues] = useState>(new Set()); @@ -739,7 +752,7 @@ export function Inbox() { issueLinkState={issueLinkState} onDismiss={() => dismiss(`run:${item.run.id}`)} onRetry={() => retryRunMutation.mutate(item.run)} - isRetrying={retryRunMutation.isPending} + isRetrying={retryingRunIds.has(item.run.id)} /> ); }