Merge public-gh/master into paperclip-company-import-export

This commit is contained in:
Dotta
2026-03-17 10:45:14 -05:00
88 changed files with 29002 additions and 888 deletions
@@ -94,7 +94,7 @@ export async function prepareWorktreeCodexHome(
}
await onLog(
"stderr",
"stdout",
`[paperclip] Using worktree-isolated Codex home "${targetHome}" (seeded from "${sourceHome}").\n`,
);
return targetHome;
@@ -123,7 +123,7 @@ export async function ensureCodexSkillsInjected(
);
for (const skillName of removedSkills) {
await onLog(
"stderr",
"stdout",
`[paperclip] Removed maintainer-only Codex skill "${skillName}" from ${skillsHome}\n`,
);
}
@@ -150,8 +150,8 @@ export async function ensureCodexSkillsInjected(
await fs.symlink(entry.source, target);
}
await onLog(
"stderr",
`[paperclip] Repaired Codex skill "${entry.key}" into ${skillsHome}\n`,
"stdout",
`[paperclip] Repaired Codex skill "${entry.runtimeName}" into ${skillsHome}\n`,
);
continue;
}
@@ -161,8 +161,8 @@ export async function ensureCodexSkillsInjected(
if (result === "skipped") continue;
await onLog(
"stderr",
`[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Codex skill "${entry.key}" into ${skillsHome}\n`,
"stdout",
`[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Codex skill "${entry.runtimeName}" into ${skillsHome}\n`,
);
} catch (err) {
await onLog(
@@ -379,7 +379,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
`Resolve any relative file references from ${instructionsDir}.\n\n`;
instructionsChars = instructionsPrefix.length;
await onLog(
"stderr",
"stdout",
`[paperclip] Loaded agent instructions file: ${instructionsFilePath}\n`,
);
} catch (err) {
+1 -1
View File
@@ -33,9 +33,9 @@
"seed": "tsx src/seed.ts"
},
"dependencies": {
"embedded-postgres": "^18.1.0-beta.16",
"@paperclipai/shared": "workspace:*",
"drizzle-orm": "^0.38.4",
"embedded-postgres": "^18.1.0-beta.16",
"postgres": "^3.4.5"
},
"devDependencies": {
+157
View File
@@ -0,0 +1,157 @@
import { createHash } from "node:crypto";
import fs from "node:fs";
import net from "node:net";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import postgres from "postgres";
import {
applyPendingMigrations,
ensurePostgresDatabase,
inspectMigrations,
} from "./client.js";
type EmbeddedPostgresInstance = {
initialise(): Promise<void>;
start(): Promise<void>;
stop(): Promise<void>;
};
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;
const tempPaths: string[] = [];
const runningInstances: EmbeddedPostgresInstance[] = [];
async function getEmbeddedPostgresCtor(): Promise<EmbeddedPostgresCtor> {
const mod = await import("embedded-postgres");
return mod.default as EmbeddedPostgresCtor;
}
async function getAvailablePort(): Promise<number> {
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 createTempDatabase(): Promise<string> {
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-db-client-"));
tempPaths.push(dataDir);
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();
runningInstances.push(instance);
const adminUrl = `postgres://paperclip:paperclip@127.0.0.1:${port}/postgres`;
await ensurePostgresDatabase(adminUrl, "paperclip");
return `postgres://paperclip:paperclip@127.0.0.1:${port}/paperclip`;
}
async function migrationHash(migrationFile: string): Promise<string> {
const content = await fs.promises.readFile(
new URL(`./migrations/${migrationFile}`, import.meta.url),
"utf8",
);
return createHash("sha256").update(content).digest("hex");
}
afterEach(async () => {
while (runningInstances.length > 0) {
const instance = runningInstances.pop();
if (!instance) continue;
await instance.stop();
}
while (tempPaths.length > 0) {
const tempPath = tempPaths.pop();
if (!tempPath) continue;
fs.rmSync(tempPath, { recursive: true, force: true });
}
});
describe("applyPendingMigrations", () => {
it(
"applies an inserted earlier migration without replaying later legacy migrations",
async () => {
const connectionString = await createTempDatabase();
await applyPendingMigrations(connectionString);
const sql = postgres(connectionString, { max: 1, onnotice: () => {} });
try {
const richMagnetoHash = await migrationHash("0030_rich_magneto.sql");
await sql.unsafe(
`DELETE FROM "drizzle"."__drizzle_migrations" WHERE hash = '${richMagnetoHash}'`,
);
await sql.unsafe(`DROP TABLE "company_logos"`);
} finally {
await sql.end();
}
const pendingState = await inspectMigrations(connectionString);
expect(pendingState).toMatchObject({
status: "needsMigrations",
pendingMigrations: ["0030_rich_magneto.sql"],
reason: "pending-migrations",
});
await applyPendingMigrations(connectionString);
const finalState = await inspectMigrations(connectionString);
expect(finalState.status).toBe("upToDate");
const verifySql = postgres(connectionString, { max: 1, onnotice: () => {} });
try {
const rows = await verifySql.unsafe<{ table_name: string }[]>(
`
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN ('company_logos', 'execution_workspaces')
ORDER BY table_name
`,
);
expect(rows.map((row) => row.table_name)).toEqual([
"company_logos",
"execution_workspaces",
]);
} finally {
await verifySql.end();
}
},
20_000,
);
});
+35 -7
View File
@@ -50,6 +50,21 @@ export function createDb(url: string) {
return drizzlePg(sql, { schema });
}
export async function getPostgresDataDirectory(url: string): Promise<string | null> {
const sql = createUtilitySql(url);
try {
const rows = await sql<{ data_directory: string | null }[]>`
SELECT current_setting('data_directory', true) AS data_directory
`;
const actual = rows[0]?.data_directory;
return typeof actual === "string" && actual.length > 0 ? actual : null;
} catch {
return null;
} finally {
await sql.end();
}
}
async function listMigrationFiles(): Promise<string[]> {
const entries = await readdir(MIGRATIONS_FOLDER, { withFileTypes: true });
return entries
@@ -646,13 +661,26 @@ export async function applyPendingMigrations(url: string): Promise<void> {
const initialState = await inspectMigrations(url);
if (initialState.status === "upToDate") return;
const sql = createUtilitySql(url);
if (initialState.reason === "no-migration-journal-empty-db") {
const sql = createUtilitySql(url);
try {
const db = drizzlePg(sql);
await migratePg(db, { migrationsFolder: MIGRATIONS_FOLDER });
} finally {
await sql.end();
}
try {
const db = drizzlePg(sql);
await migratePg(db, { migrationsFolder: MIGRATIONS_FOLDER });
} finally {
await sql.end();
const bootstrappedState = await inspectMigrations(url);
if (bootstrappedState.status === "upToDate") return;
throw new Error(
`Failed to bootstrap migrations: ${bootstrappedState.pendingMigrations.join(", ")}`,
);
}
if (initialState.reason === "no-migration-journal-non-empty-db") {
throw new Error(
"Database has tables but no migration journal; automatic migration is unsafe. Initialize migration history manually.",
);
}
let state = await inspectMigrations(url);
@@ -665,7 +693,7 @@ export async function applyPendingMigrations(url: string): Promise<void> {
}
if (state.status !== "needsMigrations" || state.reason !== "pending-migrations") {
throw new Error("Migrations are still pending after attempted apply; run inspectMigrations for details.");
throw new Error("Migrations are still pending after migration-history reconciliation; run inspectMigrations for details.");
}
await applyPendingMigrationsManually(url, state.pendingMigrations);
+1
View File
@@ -1,5 +1,6 @@
export {
createDb,
getPostgresDataDirectory,
ensurePostgresDatabase,
inspectMigrations,
applyPendingMigrations,
+27 -13
View File
@@ -1,9 +1,7 @@
import { existsSync, readFileSync, rmSync } from "node:fs";
import { createRequire } from "node:module";
import { createServer } from "node:net";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { ensurePostgresDatabase } from "./client.js";
import { ensurePostgresDatabase, getPostgresDataDirectory } from "./client.js";
import { resolveDatabaseTarget } from "./runtime-config.js";
type EmbeddedPostgresInstance = {
@@ -90,17 +88,8 @@ async function findAvailablePort(startPort: number): Promise<number> {
}
async function loadEmbeddedPostgresCtor(): Promise<EmbeddedPostgresCtor> {
const require = createRequire(import.meta.url);
const resolveCandidates = [
path.resolve(fileURLToPath(new URL("../..", import.meta.url))),
path.resolve(fileURLToPath(new URL("../../server", import.meta.url))),
path.resolve(fileURLToPath(new URL("../../cli", import.meta.url))),
process.cwd(),
];
try {
const resolvedModulePath = require.resolve("embedded-postgres", { paths: resolveCandidates });
const mod = await import(pathToFileURL(resolvedModulePath).href);
const mod = await import("embedded-postgres");
return mod.default as EmbeddedPostgresCtor;
} catch {
throw new Error(
@@ -116,8 +105,33 @@ async function ensureEmbeddedPostgresConnection(
const EmbeddedPostgres = await loadEmbeddedPostgresCtor();
const selectedPort = await findAvailablePort(preferredPort);
const postmasterPidFile = path.resolve(dataDir, "postmaster.pid");
const pgVersionFile = path.resolve(dataDir, "PG_VERSION");
const runningPid = readRunningPostmasterPid(postmasterPidFile);
const runningPort = readPidFilePort(postmasterPidFile);
const preferredAdminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${preferredPort}/postgres`;
if (!runningPid && existsSync(pgVersionFile)) {
try {
const actualDataDir = await getPostgresDataDirectory(preferredAdminConnectionString);
const matchesDataDir =
typeof actualDataDir === "string" &&
path.resolve(actualDataDir) === path.resolve(dataDir);
if (!matchesDataDir) {
throw new Error("reachable postgres does not use the expected embedded data directory");
}
await ensurePostgresDatabase(preferredAdminConnectionString, "paperclip");
process.emitWarning(
`Adopting an existing PostgreSQL instance on port ${preferredPort} for embedded data dir ${dataDir} because postmaster.pid is missing.`,
);
return {
connectionString: `postgres://paperclip:paperclip@127.0.0.1:${preferredPort}/paperclip`,
source: `embedded-postgres@${preferredPort}`,
stop: async () => {},
};
} catch {
// Fall through and attempt to start the configured embedded cluster.
}
}
if (runningPid) {
const port = runningPort ?? preferredPort;
@@ -0,0 +1,91 @@
CREATE TABLE "execution_workspaces" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"company_id" uuid NOT NULL,
"project_id" uuid NOT NULL,
"project_workspace_id" uuid,
"source_issue_id" uuid,
"mode" text NOT NULL,
"strategy_type" text NOT NULL,
"name" text NOT NULL,
"status" text DEFAULT 'active' NOT NULL,
"cwd" text,
"repo_url" text,
"base_ref" text,
"branch_name" text,
"provider_type" text DEFAULT 'local_fs' NOT NULL,
"provider_ref" text,
"derived_from_execution_workspace_id" uuid,
"last_used_at" timestamp with time zone DEFAULT now() NOT NULL,
"opened_at" timestamp with time zone DEFAULT now() NOT NULL,
"closed_at" timestamp with time zone,
"cleanup_eligible_at" timestamp with time zone,
"cleanup_reason" text,
"metadata" jsonb,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "issue_work_products" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"company_id" uuid NOT NULL,
"project_id" uuid,
"issue_id" uuid NOT NULL,
"execution_workspace_id" uuid,
"runtime_service_id" uuid,
"type" text NOT NULL,
"provider" text NOT NULL,
"external_id" text,
"title" text NOT NULL,
"url" text,
"status" text NOT NULL,
"review_state" text DEFAULT 'none' NOT NULL,
"is_primary" boolean DEFAULT false NOT NULL,
"health_status" text DEFAULT 'unknown' NOT NULL,
"summary" text,
"metadata" jsonb,
"created_by_run_id" uuid,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "issues" ADD COLUMN "project_workspace_id" uuid;--> statement-breakpoint
ALTER TABLE "issues" ADD COLUMN "execution_workspace_id" uuid;--> statement-breakpoint
ALTER TABLE "issues" ADD COLUMN "execution_workspace_preference" text;--> statement-breakpoint
ALTER TABLE "project_workspaces" ADD COLUMN "source_type" text DEFAULT 'local_path' NOT NULL;--> statement-breakpoint
ALTER TABLE "project_workspaces" ADD COLUMN "default_ref" text;--> statement-breakpoint
ALTER TABLE "project_workspaces" ADD COLUMN "visibility" text DEFAULT 'default' NOT NULL;--> statement-breakpoint
ALTER TABLE "project_workspaces" ADD COLUMN "setup_command" text;--> statement-breakpoint
ALTER TABLE "project_workspaces" ADD COLUMN "cleanup_command" text;--> statement-breakpoint
ALTER TABLE "project_workspaces" ADD COLUMN "remote_provider" text;--> statement-breakpoint
ALTER TABLE "project_workspaces" ADD COLUMN "remote_workspace_ref" text;--> statement-breakpoint
ALTER TABLE "project_workspaces" ADD COLUMN "shared_workspace_key" text;--> statement-breakpoint
ALTER TABLE "workspace_runtime_services" ADD COLUMN "execution_workspace_id" uuid;--> statement-breakpoint
ALTER TABLE "execution_workspaces" ADD CONSTRAINT "execution_workspaces_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "execution_workspaces" ADD CONSTRAINT "execution_workspaces_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "execution_workspaces" ADD CONSTRAINT "execution_workspaces_project_workspace_id_project_workspaces_id_fk" FOREIGN KEY ("project_workspace_id") REFERENCES "public"."project_workspaces"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "execution_workspaces" ADD CONSTRAINT "execution_workspaces_source_issue_id_issues_id_fk" FOREIGN KEY ("source_issue_id") REFERENCES "public"."issues"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "execution_workspaces" ADD CONSTRAINT "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk" FOREIGN KEY ("derived_from_execution_workspace_id") REFERENCES "public"."execution_workspaces"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "issue_work_products" ADD CONSTRAINT "issue_work_products_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "issue_work_products" ADD CONSTRAINT "issue_work_products_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "issue_work_products" ADD CONSTRAINT "issue_work_products_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "issue_work_products" ADD CONSTRAINT "issue_work_products_execution_workspace_id_execution_workspaces_id_fk" FOREIGN KEY ("execution_workspace_id") REFERENCES "public"."execution_workspaces"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "issue_work_products" ADD CONSTRAINT "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk" FOREIGN KEY ("runtime_service_id") REFERENCES "public"."workspace_runtime_services"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "issue_work_products" ADD CONSTRAINT "issue_work_products_created_by_run_id_heartbeat_runs_id_fk" FOREIGN KEY ("created_by_run_id") REFERENCES "public"."heartbeat_runs"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "execution_workspaces_company_project_status_idx" ON "execution_workspaces" USING btree ("company_id","project_id","status");--> statement-breakpoint
CREATE INDEX "execution_workspaces_company_project_workspace_status_idx" ON "execution_workspaces" USING btree ("company_id","project_workspace_id","status");--> statement-breakpoint
CREATE INDEX "execution_workspaces_company_source_issue_idx" ON "execution_workspaces" USING btree ("company_id","source_issue_id");--> statement-breakpoint
CREATE INDEX "execution_workspaces_company_last_used_idx" ON "execution_workspaces" USING btree ("company_id","last_used_at");--> statement-breakpoint
CREATE INDEX "execution_workspaces_company_branch_idx" ON "execution_workspaces" USING btree ("company_id","branch_name");--> statement-breakpoint
CREATE INDEX "issue_work_products_company_issue_type_idx" ON "issue_work_products" USING btree ("company_id","issue_id","type");--> statement-breakpoint
CREATE INDEX "issue_work_products_company_execution_workspace_type_idx" ON "issue_work_products" USING btree ("company_id","execution_workspace_id","type");--> statement-breakpoint
CREATE INDEX "issue_work_products_company_provider_external_id_idx" ON "issue_work_products" USING btree ("company_id","provider","external_id");--> statement-breakpoint
CREATE INDEX "issue_work_products_company_updated_idx" ON "issue_work_products" USING btree ("company_id","updated_at");--> statement-breakpoint
ALTER TABLE "issues" ADD CONSTRAINT "issues_project_workspace_id_project_workspaces_id_fk" FOREIGN KEY ("project_workspace_id") REFERENCES "public"."project_workspaces"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "issues" ADD CONSTRAINT "issues_execution_workspace_id_execution_workspaces_id_fk" FOREIGN KEY ("execution_workspace_id") REFERENCES "public"."execution_workspaces"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "workspace_runtime_services" ADD CONSTRAINT "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk" FOREIGN KEY ("execution_workspace_id") REFERENCES "public"."execution_workspaces"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "issues_company_project_workspace_idx" ON "issues" USING btree ("company_id","project_workspace_id");--> statement-breakpoint
CREATE INDEX "issues_company_execution_workspace_idx" ON "issues" USING btree ("company_id","execution_workspace_id");--> statement-breakpoint
CREATE INDEX "project_workspaces_project_source_type_idx" ON "project_workspaces" USING btree ("project_id","source_type");--> statement-breakpoint
CREATE INDEX "project_workspaces_company_shared_key_idx" ON "project_workspaces" USING btree ("company_id","shared_workspace_key");--> statement-breakpoint
CREATE UNIQUE INDEX "project_workspaces_project_remote_ref_idx" ON "project_workspaces" USING btree ("project_id","remote_provider","remote_workspace_ref");--> statement-breakpoint
CREATE INDEX "workspace_runtime_services_company_execution_workspace_status_idx" ON "workspace_runtime_services" USING btree ("company_id","execution_workspace_id","status");
@@ -0,0 +1,9 @@
CREATE TABLE "instance_settings" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"singleton_key" text DEFAULT 'default' NOT NULL,
"experimental" jsonb DEFAULT '{}'::jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX "instance_settings_singleton_key_idx" ON "instance_settings" USING btree ("singleton_key");
@@ -0,0 +1,29 @@
CREATE TABLE "workspace_operations" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"company_id" uuid NOT NULL,
"execution_workspace_id" uuid,
"heartbeat_run_id" uuid,
"phase" text NOT NULL,
"command" text,
"cwd" text,
"status" text DEFAULT 'running' NOT NULL,
"exit_code" integer,
"log_store" text,
"log_ref" text,
"log_bytes" bigint,
"log_sha256" text,
"log_compressed" boolean DEFAULT false NOT NULL,
"stdout_excerpt" text,
"stderr_excerpt" text,
"metadata" jsonb,
"started_at" timestamp with time zone DEFAULT now() NOT NULL,
"finished_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "workspace_operations" ADD CONSTRAINT "workspace_operations_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "workspace_operations" ADD CONSTRAINT "workspace_operations_execution_workspace_id_execution_workspaces_id_fk" FOREIGN KEY ("execution_workspace_id") REFERENCES "public"."execution_workspaces"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "workspace_operations" ADD CONSTRAINT "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk" FOREIGN KEY ("heartbeat_run_id") REFERENCES "public"."heartbeat_runs"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "workspace_operations_company_run_started_idx" ON "workspace_operations" USING btree ("company_id","heartbeat_run_id","started_at");--> statement-breakpoint
CREATE INDEX "workspace_operations_company_workspace_started_idx" ON "workspace_operations" USING btree ("company_id","execution_workspace_id","started_at");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+16 -2
View File
@@ -250,8 +250,22 @@
{
"idx": 35,
"version": "7",
"when": 1773703213570,
"tag": "0035_colorful_rhino",
"when": 1773698696169,
"tag": "0035_marvelous_satana",
"breakpoints": true
},
{
"idx": 36,
"version": "7",
"when": 1773756213455,
"tag": "0036_cheerful_nitro",
"breakpoints": true
},
{
"idx": 37,
"version": "7",
"when": 1773756922363,
"tag": "0037_friendly_eddie_brock",
"breakpoints": true
}
]
@@ -0,0 +1,68 @@
import {
type AnyPgColumn,
index,
jsonb,
pgTable,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import { companies } from "./companies.js";
import { issues } from "./issues.js";
import { projectWorkspaces } from "./project_workspaces.js";
import { projects } from "./projects.js";
export const executionWorkspaces = pgTable(
"execution_workspaces",
{
id: uuid("id").primaryKey().defaultRandom(),
companyId: uuid("company_id").notNull().references(() => companies.id),
projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
projectWorkspaceId: uuid("project_workspace_id").references(() => projectWorkspaces.id, { onDelete: "set null" }),
sourceIssueId: uuid("source_issue_id").references((): AnyPgColumn => issues.id, { onDelete: "set null" }),
mode: text("mode").notNull(),
strategyType: text("strategy_type").notNull(),
name: text("name").notNull(),
status: text("status").notNull().default("active"),
cwd: text("cwd"),
repoUrl: text("repo_url"),
baseRef: text("base_ref"),
branchName: text("branch_name"),
providerType: text("provider_type").notNull().default("local_fs"),
providerRef: text("provider_ref"),
derivedFromExecutionWorkspaceId: uuid("derived_from_execution_workspace_id")
.references((): AnyPgColumn => executionWorkspaces.id, { onDelete: "set null" }),
lastUsedAt: timestamp("last_used_at", { withTimezone: true }).notNull().defaultNow(),
openedAt: timestamp("opened_at", { withTimezone: true }).notNull().defaultNow(),
closedAt: timestamp("closed_at", { withTimezone: true }),
cleanupEligibleAt: timestamp("cleanup_eligible_at", { withTimezone: true }),
cleanupReason: text("cleanup_reason"),
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
companyProjectStatusIdx: index("execution_workspaces_company_project_status_idx").on(
table.companyId,
table.projectId,
table.status,
),
companyProjectWorkspaceStatusIdx: index("execution_workspaces_company_project_workspace_status_idx").on(
table.companyId,
table.projectWorkspaceId,
table.status,
),
companySourceIssueIdx: index("execution_workspaces_company_source_issue_idx").on(
table.companyId,
table.sourceIssueId,
),
companyLastUsedIdx: index("execution_workspaces_company_last_used_idx").on(
table.companyId,
table.lastUsedAt,
),
companyBranchIdx: index("execution_workspaces_company_branch_idx").on(
table.companyId,
table.branchName,
),
}),
);
+4
View File
@@ -1,6 +1,7 @@
export { companies } from "./companies.js";
export { companyLogos } from "./company_logos.js";
export { authUsers, authSessions, authAccounts, authVerifications } from "./auth.js";
export { instanceSettings } from "./instance_settings.js";
export { instanceUserRoles } from "./instance_user_roles.js";
export { agents } from "./agents.js";
export { companyMemberships } from "./company_memberships.js";
@@ -16,10 +17,13 @@ export { agentTaskSessions } from "./agent_task_sessions.js";
export { agentWakeupRequests } from "./agent_wakeup_requests.js";
export { projects } from "./projects.js";
export { projectWorkspaces } from "./project_workspaces.js";
export { executionWorkspaces } from "./execution_workspaces.js";
export { workspaceOperations } from "./workspace_operations.js";
export { workspaceRuntimeServices } from "./workspace_runtime_services.js";
export { projectGoals } from "./project_goals.js";
export { goals } from "./goals.js";
export { issues } from "./issues.js";
export { issueWorkProducts } from "./issue_work_products.js";
export { labels } from "./labels.js";
export { issueLabels } from "./issue_labels.js";
export { issueApprovals } from "./issue_approvals.js";
@@ -0,0 +1,15 @@
import { pgTable, uuid, text, timestamp, jsonb, uniqueIndex } from "drizzle-orm/pg-core";
export const instanceSettings = pgTable(
"instance_settings",
{
id: uuid("id").primaryKey().defaultRandom(),
singletonKey: text("singleton_key").notNull().default("default"),
experimental: jsonb("experimental").$type<Record<string, unknown>>().notNull().default({}),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
singletonKeyIdx: uniqueIndex("instance_settings_singleton_key_idx").on(table.singletonKey),
}),
);
@@ -0,0 +1,64 @@
import {
boolean,
index,
jsonb,
pgTable,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import { companies } from "./companies.js";
import { executionWorkspaces } from "./execution_workspaces.js";
import { heartbeatRuns } from "./heartbeat_runs.js";
import { issues } from "./issues.js";
import { projects } from "./projects.js";
import { workspaceRuntimeServices } from "./workspace_runtime_services.js";
export const issueWorkProducts = pgTable(
"issue_work_products",
{
id: uuid("id").primaryKey().defaultRandom(),
companyId: uuid("company_id").notNull().references(() => companies.id),
projectId: uuid("project_id").references(() => projects.id, { onDelete: "set null" }),
issueId: uuid("issue_id").notNull().references(() => issues.id, { onDelete: "cascade" }),
executionWorkspaceId: uuid("execution_workspace_id")
.references(() => executionWorkspaces.id, { onDelete: "set null" }),
runtimeServiceId: uuid("runtime_service_id")
.references(() => workspaceRuntimeServices.id, { onDelete: "set null" }),
type: text("type").notNull(),
provider: text("provider").notNull(),
externalId: text("external_id"),
title: text("title").notNull(),
url: text("url"),
status: text("status").notNull(),
reviewState: text("review_state").notNull().default("none"),
isPrimary: boolean("is_primary").notNull().default(false),
healthStatus: text("health_status").notNull().default("unknown"),
summary: text("summary"),
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
createdByRunId: uuid("created_by_run_id").references(() => heartbeatRuns.id, { onDelete: "set null" }),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
companyIssueTypeIdx: index("issue_work_products_company_issue_type_idx").on(
table.companyId,
table.issueId,
table.type,
),
companyExecutionWorkspaceTypeIdx: index("issue_work_products_company_execution_workspace_type_idx").on(
table.companyId,
table.executionWorkspaceId,
table.type,
),
companyProviderExternalIdIdx: index("issue_work_products_company_provider_external_id_idx").on(
table.companyId,
table.provider,
table.externalId,
),
companyUpdatedIdx: index("issue_work_products_company_updated_idx").on(
table.companyId,
table.updatedAt,
),
}),
);
+8
View File
@@ -14,6 +14,8 @@ import { projects } from "./projects.js";
import { goals } from "./goals.js";
import { companies } from "./companies.js";
import { heartbeatRuns } from "./heartbeat_runs.js";
import { projectWorkspaces } from "./project_workspaces.js";
import { executionWorkspaces } from "./execution_workspaces.js";
export const issues = pgTable(
"issues",
@@ -21,6 +23,7 @@ export const issues = pgTable(
id: uuid("id").primaryKey().defaultRandom(),
companyId: uuid("company_id").notNull().references(() => companies.id),
projectId: uuid("project_id").references(() => projects.id),
projectWorkspaceId: uuid("project_workspace_id").references(() => projectWorkspaces.id, { onDelete: "set null" }),
goalId: uuid("goal_id").references(() => goals.id),
parentId: uuid("parent_id").references((): AnyPgColumn => issues.id),
title: text("title").notNull(),
@@ -40,6 +43,9 @@ export const issues = pgTable(
requestDepth: integer("request_depth").notNull().default(0),
billingCode: text("billing_code"),
assigneeAdapterOverrides: jsonb("assignee_adapter_overrides").$type<Record<string, unknown>>(),
executionWorkspaceId: uuid("execution_workspace_id")
.references((): AnyPgColumn => executionWorkspaces.id, { onDelete: "set null" }),
executionWorkspacePreference: text("execution_workspace_preference"),
executionWorkspaceSettings: jsonb("execution_workspace_settings").$type<Record<string, unknown>>(),
startedAt: timestamp("started_at", { withTimezone: true }),
completedAt: timestamp("completed_at", { withTimezone: true }),
@@ -62,6 +68,8 @@ export const issues = pgTable(
),
parentIdx: index("issues_company_parent_idx").on(table.companyId, table.parentId),
projectIdx: index("issues_company_project_idx").on(table.companyId, table.projectId),
projectWorkspaceIdx: index("issues_company_project_workspace_idx").on(table.companyId, table.projectWorkspaceId),
executionWorkspaceIdx: index("issues_company_execution_workspace_idx").on(table.companyId, table.executionWorkspaceId),
identifierIdx: uniqueIndex("issues_identifier_idx").on(table.identifier),
}),
);
@@ -5,6 +5,7 @@ import {
pgTable,
text,
timestamp,
uniqueIndex,
uuid,
} from "drizzle-orm/pg-core";
import { companies } from "./companies.js";
@@ -17,9 +18,17 @@ export const projectWorkspaces = pgTable(
companyId: uuid("company_id").notNull().references(() => companies.id),
projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
name: text("name").notNull(),
sourceType: text("source_type").notNull().default("local_path"),
cwd: text("cwd"),
repoUrl: text("repo_url"),
repoRef: text("repo_ref"),
defaultRef: text("default_ref"),
visibility: text("visibility").notNull().default("default"),
setupCommand: text("setup_command"),
cleanupCommand: text("cleanup_command"),
remoteProvider: text("remote_provider"),
remoteWorkspaceRef: text("remote_workspace_ref"),
sharedWorkspaceKey: text("shared_workspace_key"),
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
isPrimary: boolean("is_primary").notNull().default(false),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
@@ -28,5 +37,9 @@ export const projectWorkspaces = pgTable(
(table) => ({
companyProjectIdx: index("project_workspaces_company_project_idx").on(table.companyId, table.projectId),
projectPrimaryIdx: index("project_workspaces_project_primary_idx").on(table.projectId, table.isPrimary),
projectSourceTypeIdx: index("project_workspaces_project_source_type_idx").on(table.projectId, table.sourceType),
companySharedKeyIdx: index("project_workspaces_company_shared_key_idx").on(table.companyId, table.sharedWorkspaceKey),
projectRemoteRefIdx: uniqueIndex("project_workspaces_project_remote_ref_idx")
.on(table.projectId, table.remoteProvider, table.remoteWorkspaceRef),
}),
);
@@ -0,0 +1,57 @@
import {
bigint,
boolean,
index,
integer,
jsonb,
pgTable,
text,
timestamp,
uuid,
} from "drizzle-orm/pg-core";
import { companies } from "./companies.js";
import { executionWorkspaces } from "./execution_workspaces.js";
import { heartbeatRuns } from "./heartbeat_runs.js";
export const workspaceOperations = pgTable(
"workspace_operations",
{
id: uuid("id").primaryKey().defaultRandom(),
companyId: uuid("company_id").notNull().references(() => companies.id),
executionWorkspaceId: uuid("execution_workspace_id").references(() => executionWorkspaces.id, {
onDelete: "set null",
}),
heartbeatRunId: uuid("heartbeat_run_id").references(() => heartbeatRuns.id, {
onDelete: "set null",
}),
phase: text("phase").notNull(),
command: text("command"),
cwd: text("cwd"),
status: text("status").notNull().default("running"),
exitCode: integer("exit_code"),
logStore: text("log_store"),
logRef: text("log_ref"),
logBytes: bigint("log_bytes", { mode: "number" }),
logSha256: text("log_sha256"),
logCompressed: boolean("log_compressed").notNull().default(false),
stdoutExcerpt: text("stdout_excerpt"),
stderrExcerpt: text("stderr_excerpt"),
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
startedAt: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
finishedAt: timestamp("finished_at", { withTimezone: true }),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
companyRunStartedIdx: index("workspace_operations_company_run_started_idx").on(
table.companyId,
table.heartbeatRunId,
table.startedAt,
),
companyWorkspaceStartedIdx: index("workspace_operations_company_workspace_started_idx").on(
table.companyId,
table.executionWorkspaceId,
table.startedAt,
),
}),
);
@@ -10,6 +10,7 @@ import {
import { companies } from "./companies.js";
import { projects } from "./projects.js";
import { projectWorkspaces } from "./project_workspaces.js";
import { executionWorkspaces } from "./execution_workspaces.js";
import { issues } from "./issues.js";
import { agents } from "./agents.js";
import { heartbeatRuns } from "./heartbeat_runs.js";
@@ -21,6 +22,7 @@ export const workspaceRuntimeServices = pgTable(
companyId: uuid("company_id").notNull().references(() => companies.id),
projectId: uuid("project_id").references(() => projects.id, { onDelete: "set null" }),
projectWorkspaceId: uuid("project_workspace_id").references(() => projectWorkspaces.id, { onDelete: "set null" }),
executionWorkspaceId: uuid("execution_workspace_id").references(() => executionWorkspaces.id, { onDelete: "set null" }),
issueId: uuid("issue_id").references(() => issues.id, { onDelete: "set null" }),
scopeType: text("scope_type").notNull(),
scopeId: text("scope_id"),
@@ -50,6 +52,11 @@ export const workspaceRuntimeServices = pgTable(
table.projectWorkspaceId,
table.status,
),
companyExecutionWorkspaceStatusIdx: index("workspace_runtime_services_company_execution_workspace_status_idx").on(
table.companyId,
table.executionWorkspaceId,
table.status,
),
companyProjectStatusIdx: index("workspace_runtime_services_company_project_status_idx").on(
table.companyId,
table.projectId,
+3
View File
@@ -353,6 +353,7 @@ export function createTestHarness(options: TestHarnessOptions): TestHarness {
id: randomUUID(),
companyId: input.companyId,
projectId: input.projectId ?? null,
projectWorkspaceId: null,
goalId: input.goalId ?? null,
parentId: input.parentId ?? null,
title: input.title,
@@ -372,6 +373,8 @@ export function createTestHarness(options: TestHarnessOptions): TestHarness {
requestDepth: 0,
billingCode: null,
assigneeAdapterOverrides: null,
executionWorkspaceId: null,
executionWorkspacePreference: null,
executionWorkspaceSettings: null,
startedAt: null,
completedAt: null,
+32
View File
@@ -144,6 +144,8 @@ export type {
AgentSkillEntry,
AgentSkillSnapshot,
AgentSkillSyncRequest,
InstanceExperimentalSettings,
InstanceSettings,
Agent,
AgentPermissions,
AgentKeyCreated,
@@ -154,14 +156,28 @@ export type {
AdapterEnvironmentTestResult,
AssetImage,
Project,
ProjectCodebase,
ProjectCodebaseOrigin,
ProjectGoalRef,
ProjectWorkspace,
ExecutionWorkspace,
WorkspaceRuntimeService,
WorkspaceOperation,
WorkspaceOperationPhase,
WorkspaceOperationStatus,
ExecutionWorkspaceStrategyType,
ExecutionWorkspaceMode,
ExecutionWorkspaceProviderType,
ExecutionWorkspaceStatus,
ExecutionWorkspaceStrategy,
ProjectExecutionWorkspacePolicy,
ProjectExecutionWorkspaceDefaultMode,
IssueExecutionWorkspaceSettings,
IssueWorkProduct,
IssueWorkProductType,
IssueWorkProductProvider,
IssueWorkProductStatus,
IssueWorkProductReviewState,
Issue,
IssueAssigneeAdapterOverrides,
IssueComment,
@@ -258,6 +274,12 @@ export type {
ProviderQuotaResult,
} from "./types/index.js";
export {
instanceExperimentalSettingsSchema,
patchInstanceExperimentalSettingsSchema,
type PatchInstanceExperimentalSettings,
} from "./validators/index.js";
export {
createCompanySchema,
updateCompanySchema,
@@ -305,6 +327,13 @@ export {
addIssueCommentSchema,
linkIssueApprovalSchema,
createIssueAttachmentMetadataSchema,
createIssueWorkProductSchema,
updateIssueWorkProductSchema,
issueWorkProductTypeSchema,
issueWorkProductStatusSchema,
issueWorkProductReviewStateSchema,
updateExecutionWorkspaceSchema,
executionWorkspaceStatusSchema,
issueDocumentFormatSchema,
issueDocumentKeySchema,
upsertIssueDocumentSchema,
@@ -315,6 +344,9 @@ export {
type AddIssueComment,
type LinkIssueApproval,
type CreateIssueAttachmentMetadata,
type CreateIssueWorkProduct,
type UpdateIssueWorkProduct,
type UpdateExecutionWorkspace,
type IssueDocumentFormat,
type UpsertIssueDocument,
createGoalSchema,
+18 -1
View File
@@ -1,4 +1,5 @@
export type { Company } from "./company.js";
export type { InstanceExperimentalSettings, InstanceSettings } from "./instance.js";
export type {
CompanySkillSourceType,
CompanySkillTrustLevel,
@@ -38,15 +39,31 @@ export type {
AdapterEnvironmentTestResult,
} from "./agent.js";
export type { AssetImage } from "./asset.js";
export type { Project, ProjectGoalRef, ProjectWorkspace } from "./project.js";
export type { Project, ProjectCodebase, ProjectCodebaseOrigin, ProjectGoalRef, ProjectWorkspace } from "./project.js";
export type {
ExecutionWorkspace,
WorkspaceRuntimeService,
ExecutionWorkspaceStrategyType,
ExecutionWorkspaceMode,
ExecutionWorkspaceProviderType,
ExecutionWorkspaceStatus,
ExecutionWorkspaceStrategy,
ProjectExecutionWorkspacePolicy,
ProjectExecutionWorkspaceDefaultMode,
IssueExecutionWorkspaceSettings,
} from "./workspace-runtime.js";
export type {
WorkspaceOperation,
WorkspaceOperationPhase,
WorkspaceOperationStatus,
} from "./workspace-operation.js";
export type {
IssueWorkProduct,
IssueWorkProductType,
IssueWorkProductProvider,
IssueWorkProductStatus,
IssueWorkProductReviewState,
} from "./work-product.js";
export type {
Issue,
IssueAssigneeAdapterOverrides,
+10
View File
@@ -0,0 +1,10 @@
export interface InstanceExperimentalSettings {
enableIsolatedWorkspaces: boolean;
}
export interface InstanceSettings {
id: string;
experimental: InstanceExperimentalSettings;
createdAt: Date;
updatedAt: Date;
}
+7 -1
View File
@@ -1,7 +1,8 @@
import type { IssuePriority, IssueStatus } from "../constants.js";
import type { Goal } from "./goal.js";
import type { Project, ProjectWorkspace } from "./project.js";
import type { IssueExecutionWorkspaceSettings } from "./workspace-runtime.js";
import type { ExecutionWorkspace, IssueExecutionWorkspaceSettings } from "./workspace-runtime.js";
import type { IssueWorkProduct } from "./work-product.js";
export interface IssueAncestorProject {
id: string;
@@ -97,6 +98,7 @@ export interface Issue {
id: string;
companyId: string;
projectId: string | null;
projectWorkspaceId: string | null;
goalId: string | null;
parentId: string | null;
ancestors?: IssueAncestor[];
@@ -117,6 +119,8 @@ export interface Issue {
requestDepth: number;
billingCode: string | null;
assigneeAdapterOverrides: IssueAssigneeAdapterOverrides | null;
executionWorkspaceId: string | null;
executionWorkspacePreference: string | null;
executionWorkspaceSettings: IssueExecutionWorkspaceSettings | null;
startedAt: Date | null;
completedAt: Date | null;
@@ -129,6 +133,8 @@ export interface Issue {
legacyPlanDocument?: LegacyPlanDocument | null;
project?: Project | null;
goal?: Goal | null;
currentExecutionWorkspace?: ExecutionWorkspace | null;
workProducts?: IssueWorkProduct[];
mentionedProjects?: Project[];
myLastTouchAt?: Date | null;
lastExternalCommentAt?: Date | null;
+26
View File
@@ -1,6 +1,9 @@
import type { PauseReason, ProjectStatus } from "../constants.js";
import type { ProjectExecutionWorkspacePolicy, WorkspaceRuntimeService } from "./workspace-runtime.js";
export type ProjectWorkspaceSourceType = "local_path" | "git_repo" | "remote_managed" | "non_git_path";
export type ProjectWorkspaceVisibility = "default" | "advanced";
export interface ProjectGoalRef {
id: string;
title: string;
@@ -11,9 +14,17 @@ export interface ProjectWorkspace {
companyId: string;
projectId: string;
name: string;
sourceType: ProjectWorkspaceSourceType;
cwd: string | null;
repoUrl: string | null;
repoRef: string | null;
defaultRef: string | null;
visibility: ProjectWorkspaceVisibility;
setupCommand: string | null;
cleanupCommand: string | null;
remoteProvider: string | null;
remoteWorkspaceRef: string | null;
sharedWorkspaceKey: string | null;
metadata: Record<string, unknown> | null;
isPrimary: boolean;
runtimeServices?: WorkspaceRuntimeService[];
@@ -21,6 +32,20 @@ export interface ProjectWorkspace {
updatedAt: Date;
}
export type ProjectCodebaseOrigin = "local_folder" | "managed_checkout";
export interface ProjectCodebase {
workspaceId: string | null;
repoUrl: string | null;
repoRef: string | null;
defaultRef: string | null;
repoName: string | null;
localFolder: string | null;
managedFolder: string;
effectiveLocalFolder: string;
origin: ProjectCodebaseOrigin;
}
export interface Project {
id: string;
companyId: string;
@@ -38,6 +63,7 @@ export interface Project {
pauseReason: PauseReason | null;
pausedAt: Date | null;
executionWorkspacePolicy: ProjectExecutionWorkspacePolicy | null;
codebase: ProjectCodebase;
workspaces: ProjectWorkspace[];
primaryWorkspace: ProjectWorkspace | null;
archivedAt: Date | null;
+55
View File
@@ -0,0 +1,55 @@
export type IssueWorkProductType =
| "preview_url"
| "runtime_service"
| "pull_request"
| "branch"
| "commit"
| "artifact"
| "document";
export type IssueWorkProductProvider =
| "paperclip"
| "github"
| "vercel"
| "s3"
| "custom";
export type IssueWorkProductStatus =
| "active"
| "ready_for_review"
| "approved"
| "changes_requested"
| "merged"
| "closed"
| "failed"
| "archived"
| "draft";
export type IssueWorkProductReviewState =
| "none"
| "needs_board_review"
| "approved"
| "changes_requested";
export interface IssueWorkProduct {
id: string;
companyId: string;
projectId: string | null;
issueId: string;
executionWorkspaceId: string | null;
runtimeServiceId: string | null;
type: IssueWorkProductType;
provider: IssueWorkProductProvider | string;
externalId: string | null;
title: string;
url: string | null;
status: IssueWorkProductStatus | string;
reviewState: IssueWorkProductReviewState;
isPrimary: boolean;
healthStatus: "unknown" | "healthy" | "unhealthy";
summary: string | null;
metadata: Record<string, unknown> | null;
createdByRunId: string | null;
createdAt: Date;
updatedAt: Date;
}
@@ -0,0 +1,31 @@
export type WorkspaceOperationPhase =
| "worktree_prepare"
| "workspace_provision"
| "workspace_teardown"
| "worktree_cleanup";
export type WorkspaceOperationStatus = "running" | "succeeded" | "failed" | "skipped";
export interface WorkspaceOperation {
id: string;
companyId: string;
executionWorkspaceId: string | null;
heartbeatRunId: string | null;
phase: WorkspaceOperationPhase;
command: string | null;
cwd: string | null;
status: WorkspaceOperationStatus;
exitCode: number | null;
logStore: string | null;
logRef: string | null;
logBytes: number | null;
logSha256: string | null;
logCompressed: boolean;
stdoutExcerpt: string | null;
stderrExcerpt: string | null;
metadata: Record<string, unknown> | null;
startedAt: Date;
finishedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}
+62 -3
View File
@@ -1,6 +1,35 @@
export type ExecutionWorkspaceStrategyType = "project_primary" | "git_worktree";
export type ExecutionWorkspaceStrategyType =
| "project_primary"
| "git_worktree"
| "adapter_managed"
| "cloud_sandbox";
export type ExecutionWorkspaceMode = "inherit" | "project_primary" | "isolated" | "agent_default";
export type ProjectExecutionWorkspaceDefaultMode =
| "shared_workspace"
| "isolated_workspace"
| "operator_branch"
| "adapter_default";
export type ExecutionWorkspaceMode =
| "inherit"
| "shared_workspace"
| "isolated_workspace"
| "operator_branch"
| "reuse_existing"
| "agent_default";
export type ExecutionWorkspaceProviderType =
| "local_fs"
| "git_worktree"
| "adapter_managed"
| "cloud_sandbox";
export type ExecutionWorkspaceStatus =
| "active"
| "idle"
| "in_review"
| "archived"
| "cleanup_failed";
export interface ExecutionWorkspaceStrategy {
type: ExecutionWorkspaceStrategyType;
@@ -13,12 +42,14 @@ export interface ExecutionWorkspaceStrategy {
export interface ProjectExecutionWorkspacePolicy {
enabled: boolean;
defaultMode?: "project_primary" | "isolated";
defaultMode?: ProjectExecutionWorkspaceDefaultMode;
allowIssueOverride?: boolean;
defaultProjectWorkspaceId?: string | null;
workspaceStrategy?: ExecutionWorkspaceStrategy | null;
workspaceRuntime?: Record<string, unknown> | null;
branchPolicy?: Record<string, unknown> | null;
pullRequestPolicy?: Record<string, unknown> | null;
runtimePolicy?: Record<string, unknown> | null;
cleanupPolicy?: Record<string, unknown> | null;
}
@@ -28,11 +59,39 @@ export interface IssueExecutionWorkspaceSettings {
workspaceRuntime?: Record<string, unknown> | null;
}
export interface ExecutionWorkspace {
id: string;
companyId: string;
projectId: string;
projectWorkspaceId: string | null;
sourceIssueId: string | null;
mode: Exclude<ExecutionWorkspaceMode, "inherit" | "reuse_existing" | "agent_default"> | "adapter_managed" | "cloud_sandbox";
strategyType: ExecutionWorkspaceStrategyType;
name: string;
status: ExecutionWorkspaceStatus;
cwd: string | null;
repoUrl: string | null;
baseRef: string | null;
branchName: string | null;
providerType: ExecutionWorkspaceProviderType;
providerRef: string | null;
derivedFromExecutionWorkspaceId: string | null;
lastUsedAt: Date;
openedAt: Date;
closedAt: Date | null;
cleanupEligibleAt: Date | null;
cleanupReason: string | null;
metadata: Record<string, unknown> | null;
createdAt: Date;
updatedAt: Date;
}
export interface WorkspaceRuntimeService {
id: string;
companyId: string;
projectId: string | null;
projectWorkspaceId: string | null;
executionWorkspaceId: string | null;
issueId: string | null;
scopeType: "project_workspace" | "execution_workspace" | "run" | "agent";
scopeId: string | null;
@@ -0,0 +1,18 @@
import { z } from "zod";
export const executionWorkspaceStatusSchema = z.enum([
"active",
"idle",
"in_review",
"archived",
"cleanup_failed",
]);
export const updateExecutionWorkspaceSchema = z.object({
status: executionWorkspaceStatusSchema.optional(),
cleanupEligibleAt: z.string().datetime().optional().nullable(),
cleanupReason: z.string().optional().nullable(),
metadata: z.record(z.unknown()).optional().nullable(),
}).strict();
export type UpdateExecutionWorkspace = z.infer<typeof updateExecutionWorkspaceSchema>;
+23
View File
@@ -1,3 +1,10 @@
export {
instanceExperimentalSettingsSchema,
patchInstanceExperimentalSettingsSchema,
type InstanceExperimentalSettings,
type PatchInstanceExperimentalSettings,
} from "./instance.js";
export {
upsertBudgetPolicySchema,
resolveBudgetIncidentSchema,
@@ -121,6 +128,22 @@ export {
type UpsertIssueDocument,
} from "./issue.js";
export {
createIssueWorkProductSchema,
updateIssueWorkProductSchema,
issueWorkProductTypeSchema,
issueWorkProductStatusSchema,
issueWorkProductReviewStateSchema,
type CreateIssueWorkProduct,
type UpdateIssueWorkProduct,
} from "./work-product.js";
export {
updateExecutionWorkspaceSchema,
executionWorkspaceStatusSchema,
type UpdateExecutionWorkspace,
} from "./execution-workspace.js";
export {
createGoalSchema,
updateGoalSchema,
@@ -0,0 +1,10 @@
import { z } from "zod";
export const instanceExperimentalSettingsSchema = z.object({
enableIsolatedWorkspaces: z.boolean().default(false),
}).strict();
export const patchInstanceExperimentalSettingsSchema = instanceExperimentalSettingsSchema.partial();
export type InstanceExperimentalSettings = z.infer<typeof instanceExperimentalSettingsSchema>;
export type PatchInstanceExperimentalSettings = z.infer<typeof patchInstanceExperimentalSettingsSchema>;
+12 -2
View File
@@ -3,7 +3,7 @@ import { ISSUE_PRIORITIES, ISSUE_STATUSES } from "../constants.js";
const executionWorkspaceStrategySchema = z
.object({
type: z.enum(["project_primary", "git_worktree"]).optional(),
type: z.enum(["project_primary", "git_worktree", "adapter_managed", "cloud_sandbox"]).optional(),
baseRef: z.string().optional().nullable(),
branchTemplate: z.string().optional().nullable(),
worktreeParentDir: z.string().optional().nullable(),
@@ -14,7 +14,7 @@ const executionWorkspaceStrategySchema = z
export const issueExecutionWorkspaceSettingsSchema = z
.object({
mode: z.enum(["inherit", "project_primary", "isolated", "agent_default"]).optional(),
mode: z.enum(["inherit", "shared_workspace", "isolated_workspace", "operator_branch", "reuse_existing", "agent_default"]).optional(),
workspaceStrategy: executionWorkspaceStrategySchema.optional().nullable(),
workspaceRuntime: z.record(z.unknown()).optional().nullable(),
})
@@ -29,6 +29,7 @@ export const issueAssigneeAdapterOverridesSchema = z
export const createIssueSchema = z.object({
projectId: z.string().uuid().optional().nullable(),
projectWorkspaceId: z.string().uuid().optional().nullable(),
goalId: z.string().uuid().optional().nullable(),
parentId: z.string().uuid().optional().nullable(),
title: z.string().min(1),
@@ -40,6 +41,15 @@ export const createIssueSchema = z.object({
requestDepth: z.number().int().nonnegative().optional().default(0),
billingCode: z.string().optional().nullable(),
assigneeAdapterOverrides: issueAssigneeAdapterOverridesSchema.optional().nullable(),
executionWorkspaceId: z.string().uuid().optional().nullable(),
executionWorkspacePreference: z.enum([
"inherit",
"shared_workspace",
"isolated_workspace",
"operator_branch",
"reuse_existing",
"agent_default",
]).optional().nullable(),
executionWorkspaceSettings: issueExecutionWorkspaceSettingsSchema.optional().nullable(),
labelIds: z.array(z.string().uuid()).optional(),
});
+36 -7
View File
@@ -3,7 +3,7 @@ import { PROJECT_STATUSES } from "../constants.js";
const executionWorkspaceStrategySchema = z
.object({
type: z.enum(["project_primary", "git_worktree"]).optional(),
type: z.enum(["project_primary", "git_worktree", "adapter_managed", "cloud_sandbox"]).optional(),
baseRef: z.string().optional().nullable(),
branchTemplate: z.string().optional().nullable(),
worktreeParentDir: z.string().optional().nullable(),
@@ -15,30 +15,54 @@ const executionWorkspaceStrategySchema = z
export const projectExecutionWorkspacePolicySchema = z
.object({
enabled: z.boolean(),
defaultMode: z.enum(["project_primary", "isolated"]).optional(),
defaultMode: z.enum(["shared_workspace", "isolated_workspace", "operator_branch", "adapter_default"]).optional(),
allowIssueOverride: z.boolean().optional(),
defaultProjectWorkspaceId: z.string().uuid().optional().nullable(),
workspaceStrategy: executionWorkspaceStrategySchema.optional().nullable(),
workspaceRuntime: z.record(z.unknown()).optional().nullable(),
branchPolicy: z.record(z.unknown()).optional().nullable(),
pullRequestPolicy: z.record(z.unknown()).optional().nullable(),
runtimePolicy: z.record(z.unknown()).optional().nullable(),
cleanupPolicy: z.record(z.unknown()).optional().nullable(),
})
.strict();
const projectWorkspaceSourceTypeSchema = z.enum(["local_path", "git_repo", "remote_managed", "non_git_path"]);
const projectWorkspaceVisibilitySchema = z.enum(["default", "advanced"]);
const projectWorkspaceFields = {
name: z.string().min(1).optional(),
sourceType: projectWorkspaceSourceTypeSchema.optional(),
cwd: z.string().min(1).optional().nullable(),
repoUrl: z.string().url().optional().nullable(),
repoRef: z.string().optional().nullable(),
defaultRef: z.string().optional().nullable(),
visibility: projectWorkspaceVisibilitySchema.optional(),
setupCommand: z.string().optional().nullable(),
cleanupCommand: z.string().optional().nullable(),
remoteProvider: z.string().optional().nullable(),
remoteWorkspaceRef: z.string().optional().nullable(),
sharedWorkspaceKey: z.string().optional().nullable(),
metadata: z.record(z.unknown()).optional().nullable(),
};
export const createProjectWorkspaceSchema = z.object({
...projectWorkspaceFields,
isPrimary: z.boolean().optional().default(false),
}).superRefine((value, ctx) => {
function validateProjectWorkspace(value: Record<string, unknown>, ctx: z.RefinementCtx) {
const sourceType = value.sourceType ?? "local_path";
const hasCwd = typeof value.cwd === "string" && value.cwd.trim().length > 0;
const hasRepo = typeof value.repoUrl === "string" && value.repoUrl.trim().length > 0;
const hasRemoteRef = typeof value.remoteWorkspaceRef === "string" && value.remoteWorkspaceRef.trim().length > 0;
if (sourceType === "remote_managed") {
if (!hasRemoteRef && !hasRepo) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Remote-managed workspace requires remoteWorkspaceRef or repoUrl.",
path: ["remoteWorkspaceRef"],
});
}
return;
}
if (!hasCwd && !hasRepo) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
@@ -46,7 +70,12 @@ export const createProjectWorkspaceSchema = z.object({
path: ["cwd"],
});
}
});
}
export const createProjectWorkspaceSchema = z.object({
...projectWorkspaceFields,
isPrimary: z.boolean().optional().default(false),
}).superRefine(validateProjectWorkspace);
export type CreateProjectWorkspace = z.infer<typeof createProjectWorkspaceSchema>;
@@ -0,0 +1,54 @@
import { z } from "zod";
export const issueWorkProductTypeSchema = z.enum([
"preview_url",
"runtime_service",
"pull_request",
"branch",
"commit",
"artifact",
"document",
]);
export const issueWorkProductStatusSchema = z.enum([
"active",
"ready_for_review",
"approved",
"changes_requested",
"merged",
"closed",
"failed",
"archived",
"draft",
]);
export const issueWorkProductReviewStateSchema = z.enum([
"none",
"needs_board_review",
"approved",
"changes_requested",
]);
export const createIssueWorkProductSchema = z.object({
projectId: z.string().uuid().optional().nullable(),
executionWorkspaceId: z.string().uuid().optional().nullable(),
runtimeServiceId: z.string().uuid().optional().nullable(),
type: issueWorkProductTypeSchema,
provider: z.string().min(1),
externalId: z.string().optional().nullable(),
title: z.string().min(1),
url: z.string().url().optional().nullable(),
status: issueWorkProductStatusSchema.default("active"),
reviewState: issueWorkProductReviewStateSchema.optional().default("none"),
isPrimary: z.boolean().optional().default(false),
healthStatus: z.enum(["unknown", "healthy", "unhealthy"]).optional().default("unknown"),
summary: z.string().optional().nullable(),
metadata: z.record(z.unknown()).optional().nullable(),
createdByRunId: z.string().uuid().optional().nullable(),
});
export type CreateIssueWorkProduct = z.infer<typeof createIssueWorkProductSchema>;
export const updateIssueWorkProductSchema = createIssueWorkProductSchema.partial();
export type UpdateIssueWorkProduct = z.infer<typeof updateIssueWorkProductSchema>;