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/packages/db/src/migrations/0028_unusual_the_hunter.sql b/packages/db/src/migrations/0028_unusual_the_hunter.sql new file mode 100644 index 00000000..e19de785 --- /dev/null +++ b/packages/db/src/migrations/0028_unusual_the_hunter.sql @@ -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"); \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0028_snapshot.json b/packages/db/src/migrations/meta/0028_snapshot.json new file mode 100644 index 00000000..ef9b8494 --- /dev/null +++ b/packages/db/src/migrations/meta/0028_snapshot.json @@ -0,0 +1,7125 @@ +{ + "id": "d25020c0-808b-45bb-9e28-bfc354d6289b", + "prevId": "8186209d-f7ec-4048-bd4f-c96530f45304", + "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 + }, + "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.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'" + }, + "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_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 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "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": {} + } + }, + "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" + } + }, + "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.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_idx": { + "name": "heartbeat_run_events_run_seq_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_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.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 + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "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 80a1dfbd..56e8c493 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -197,6 +197,13 @@ "when": 1773150731736, "tag": "0027_tranquil_tenebrous", "breakpoints": true + }, + { + "idx": 28, + "version": "7", + "when": 1773439626334, + "tag": "0028_unusual_the_hunter", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema/execution_workspaces.ts b/packages/db/src/schema/execution_workspaces.ts new file mode 100644 index 00000000..72e63d5b --- /dev/null +++ b/packages/db/src/schema/execution_workspaces.ts @@ -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>(), + 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, + ), + }), +); diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts index 3416ea9a..7a7a110d 100644 --- a/packages/db/src/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -13,10 +13,12 @@ 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 { 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"; diff --git a/packages/db/src/schema/issue_work_products.ts b/packages/db/src/schema/issue_work_products.ts new file mode 100644 index 00000000..788317d2 --- /dev/null +++ b/packages/db/src/schema/issue_work_products.ts @@ -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>(), + 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, + ), + }), +); diff --git a/packages/db/src/schema/issues.ts b/packages/db/src/schema/issues.ts index 80093e67..cd63cfe8 100644 --- a/packages/db/src/schema/issues.ts +++ b/packages/db/src/schema/issues.ts @@ -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>(), + executionWorkspaceId: uuid("execution_workspace_id") + .references((): AnyPgColumn => executionWorkspaces.id, { onDelete: "set null" }), + executionWorkspacePreference: text("execution_workspace_preference"), executionWorkspaceSettings: jsonb("execution_workspace_settings").$type>(), 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), }), ); diff --git a/packages/db/src/schema/project_workspaces.ts b/packages/db/src/schema/project_workspaces.ts index 8ff52739..7f247ffe 100644 --- a/packages/db/src/schema/project_workspaces.ts +++ b/packages/db/src/schema/project_workspaces.ts @@ -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>(), 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), }), ); diff --git a/packages/db/src/schema/workspace_runtime_services.ts b/packages/db/src/schema/workspace_runtime_services.ts index 0837855f..150c332d 100644 --- a/packages/db/src/schema/workspace_runtime_services.ts +++ b/packages/db/src/schema/workspace_runtime_services.ts @@ -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, diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 1a222f27..7ba01e40 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -77,12 +77,21 @@ export type { Project, ProjectGoalRef, ProjectWorkspace, + ExecutionWorkspace, WorkspaceRuntimeService, ExecutionWorkspaceStrategyType, ExecutionWorkspaceMode, + ExecutionWorkspaceProviderType, + ExecutionWorkspaceStatus, ExecutionWorkspaceStrategy, ProjectExecutionWorkspacePolicy, + ProjectExecutionWorkspaceDefaultMode, IssueExecutionWorkspaceSettings, + IssueWorkProduct, + IssueWorkProductType, + IssueWorkProductProvider, + IssueWorkProductStatus, + IssueWorkProductReviewState, Issue, IssueAssigneeAdapterOverrides, IssueComment, @@ -172,6 +181,13 @@ export { addIssueCommentSchema, linkIssueApprovalSchema, createIssueAttachmentMetadataSchema, + createIssueWorkProductSchema, + updateIssueWorkProductSchema, + issueWorkProductTypeSchema, + issueWorkProductStatusSchema, + issueWorkProductReviewStateSchema, + updateExecutionWorkspaceSchema, + executionWorkspaceStatusSchema, type CreateIssue, type CreateIssueLabel, type UpdateIssue, @@ -179,6 +195,9 @@ export { type AddIssueComment, type LinkIssueApproval, type CreateIssueAttachmentMetadata, + type CreateIssueWorkProduct, + type UpdateIssueWorkProduct, + type UpdateExecutionWorkspace, createGoalSchema, updateGoalSchema, type CreateGoal, diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index 07862c58..fa95125f 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -12,13 +12,24 @@ export type { export type { AssetImage } from "./asset.js"; export type { Project, ProjectGoalRef, ProjectWorkspace } from "./project.js"; export type { + ExecutionWorkspace, WorkspaceRuntimeService, ExecutionWorkspaceStrategyType, ExecutionWorkspaceMode, + ExecutionWorkspaceProviderType, + ExecutionWorkspaceStatus, ExecutionWorkspaceStrategy, ProjectExecutionWorkspacePolicy, + ProjectExecutionWorkspaceDefaultMode, IssueExecutionWorkspaceSettings, } from "./workspace-runtime.js"; +export type { + IssueWorkProduct, + IssueWorkProductType, + IssueWorkProductProvider, + IssueWorkProductStatus, + IssueWorkProductReviewState, +} from "./work-product.js"; export type { Issue, IssueAssigneeAdapterOverrides, diff --git a/packages/shared/src/types/issue.ts b/packages/shared/src/types/issue.ts index 550e8d24..a8bc63d5 100644 --- a/packages/shared/src/types/issue.ts +++ b/packages/shared/src/types/issue.ts @@ -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; @@ -54,6 +55,7 @@ export interface Issue { id: string; companyId: string; projectId: string | null; + projectWorkspaceId: string | null; goalId: string | null; parentId: string | null; ancestors?: IssueAncestor[]; @@ -74,6 +76,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; @@ -83,6 +87,8 @@ export interface Issue { labels?: IssueLabel[]; project?: Project | null; goal?: Goal | null; + currentExecutionWorkspace?: ExecutionWorkspace | null; + workProducts?: IssueWorkProduct[]; mentionedProjects?: Project[]; myLastTouchAt?: Date | null; lastExternalCommentAt?: Date | null; diff --git a/packages/shared/src/types/project.ts b/packages/shared/src/types/project.ts index e9981ff7..60d0d25f 100644 --- a/packages/shared/src/types/project.ts +++ b/packages/shared/src/types/project.ts @@ -1,6 +1,9 @@ import type { 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 | null; isPrimary: boolean; runtimeServices?: WorkspaceRuntimeService[]; diff --git a/packages/shared/src/types/work-product.ts b/packages/shared/src/types/work-product.ts new file mode 100644 index 00000000..297a2463 --- /dev/null +++ b/packages/shared/src/types/work-product.ts @@ -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 | null; + createdByRunId: string | null; + createdAt: Date; + updatedAt: Date; +} diff --git a/packages/shared/src/types/workspace-runtime.ts b/packages/shared/src/types/workspace-runtime.ts index f2aa023c..47ed9494 100644 --- a/packages/shared/src/types/workspace-runtime.ts +++ b/packages/shared/src/types/workspace-runtime.ts @@ -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 | null; branchPolicy?: Record | null; pullRequestPolicy?: Record | null; + runtimePolicy?: Record | null; cleanupPolicy?: Record | null; } @@ -28,11 +59,39 @@ export interface IssueExecutionWorkspaceSettings { workspaceRuntime?: Record | null; } +export interface ExecutionWorkspace { + id: string; + companyId: string; + projectId: string; + projectWorkspaceId: string | null; + sourceIssueId: string | null; + mode: Exclude | "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 | 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; diff --git a/packages/shared/src/validators/execution-workspace.ts b/packages/shared/src/validators/execution-workspace.ts new file mode 100644 index 00000000..53a74036 --- /dev/null +++ b/packages/shared/src/validators/execution-workspace.ts @@ -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; diff --git a/packages/shared/src/validators/index.ts b/packages/shared/src/validators/index.ts index ad74a1e8..44779c99 100644 --- a/packages/shared/src/validators/index.ts +++ b/packages/shared/src/validators/index.ts @@ -76,6 +76,22 @@ export { type CreateIssueAttachmentMetadata, } 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, diff --git a/packages/shared/src/validators/issue.ts b/packages/shared/src/validators/issue.ts index 3e269e04..e2c60c9d 100644 --- a/packages/shared/src/validators/issue.ts +++ b/packages/shared/src/validators/issue.ts @@ -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(), }); diff --git a/packages/shared/src/validators/project.ts b/packages/shared/src/validators/project.ts index da375495..cf5aba8a 100644 --- a/packages/shared/src/validators/project.ts +++ b/packages/shared/src/validators/project.ts @@ -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, 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; diff --git a/packages/shared/src/validators/work-product.ts b/packages/shared/src/validators/work-product.ts new file mode 100644 index 00000000..839cc15a --- /dev/null +++ b/packages/shared/src/validators/work-product.ts @@ -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; + +export const updateIssueWorkProductSchema = createIssueWorkProductSchema.partial(); + +export type UpdateIssueWorkProduct = z.infer; diff --git a/server/src/__tests__/execution-workspace-policy.test.ts b/server/src/__tests__/execution-workspace-policy.test.ts index a4afe287..72f2837e 100644 --- a/server/src/__tests__/execution-workspace-policy.test.ts +++ b/server/src/__tests__/execution-workspace-policy.test.ts @@ -12,36 +12,36 @@ describe("execution workspace policy helpers", () => { expect( defaultIssueExecutionWorkspaceSettingsForProject({ enabled: true, - defaultMode: "isolated", + defaultMode: "isolated_workspace", }), - ).toEqual({ mode: "isolated" }); + ).toEqual({ mode: "isolated_workspace" }); expect( defaultIssueExecutionWorkspaceSettingsForProject({ enabled: true, - defaultMode: "project_primary", + defaultMode: "shared_workspace", }), - ).toEqual({ mode: "project_primary" }); + ).toEqual({ mode: "shared_workspace" }); expect(defaultIssueExecutionWorkspaceSettingsForProject(null)).toBeNull(); }); it("prefers explicit issue mode over project policy and legacy overrides", () => { expect( resolveExecutionWorkspaceMode({ - projectPolicy: { enabled: true, defaultMode: "project_primary" }, - issueSettings: { mode: "isolated" }, + projectPolicy: { enabled: true, defaultMode: "shared_workspace" }, + issueSettings: { mode: "isolated_workspace" }, legacyUseProjectWorkspace: false, }), - ).toBe("isolated"); + ).toBe("isolated_workspace"); }); it("falls back to project policy before legacy project-workspace compatibility flag", () => { expect( resolveExecutionWorkspaceMode({ - projectPolicy: { enabled: true, defaultMode: "isolated" }, + projectPolicy: { enabled: true, defaultMode: "isolated_workspace" }, issueSettings: null, legacyUseProjectWorkspace: false, }), - ).toBe("isolated"); + ).toBe("isolated_workspace"); expect( resolveExecutionWorkspaceMode({ projectPolicy: null, @@ -58,7 +58,7 @@ describe("execution workspace policy helpers", () => { }, projectPolicy: { enabled: true, - defaultMode: "isolated", + defaultMode: "isolated_workspace", workspaceStrategy: { type: "git_worktree", baseRef: "origin/main", @@ -69,7 +69,7 @@ describe("execution workspace policy helpers", () => { }, }, issueSettings: null, - mode: "isolated", + mode: "isolated_workspace", legacyUseProjectWorkspace: null, }); @@ -92,9 +92,9 @@ describe("execution workspace policy helpers", () => { expect( buildExecutionWorkspaceAdapterConfig({ agentConfig: baseConfig, - projectPolicy: { enabled: true, defaultMode: "isolated" }, - issueSettings: { mode: "project_primary" }, - mode: "project_primary", + projectPolicy: { enabled: true, defaultMode: "isolated_workspace" }, + issueSettings: { mode: "shared_workspace" }, + mode: "shared_workspace", legacyUseProjectWorkspace: null, }).workspaceStrategy, ).toBeUndefined(); @@ -124,7 +124,7 @@ describe("execution workspace policy helpers", () => { }), ).toEqual({ enabled: true, - defaultMode: "isolated", + defaultMode: "isolated_workspace", workspaceStrategy: { type: "git_worktree", worktreeParentDir: ".paperclip/worktrees", @@ -137,7 +137,7 @@ describe("execution workspace policy helpers", () => { mode: "project_primary", }), ).toEqual({ - mode: "project_primary", + mode: "shared_workspace", }); }); }); diff --git a/server/src/app.ts b/server/src/app.ts index 6871552a..7133a3a3 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -14,6 +14,7 @@ import { companyRoutes } from "./routes/companies.js"; import { agentRoutes } from "./routes/agents.js"; import { projectRoutes } from "./routes/projects.js"; import { issueRoutes } from "./routes/issues.js"; +import { executionWorkspaceRoutes } from "./routes/execution-workspaces.js"; import { goalRoutes } from "./routes/goals.js"; import { approvalRoutes } from "./routes/approvals.js"; import { secretRoutes } from "./routes/secrets.js"; @@ -107,6 +108,7 @@ export async function createApp( api.use(assetRoutes(db, opts.storageService)); api.use(projectRoutes(db)); api.use(issueRoutes(db, opts.storageService)); + api.use(executionWorkspaceRoutes(db)); api.use(goalRoutes(db)); api.use(approvalRoutes(db)); api.use(secretRoutes(db)); diff --git a/server/src/routes/execution-workspaces.ts b/server/src/routes/execution-workspaces.ts new file mode 100644 index 00000000..5c5b6bbe --- /dev/null +++ b/server/src/routes/execution-workspaces.ts @@ -0,0 +1,68 @@ +import { Router } from "express"; +import type { Db } from "@paperclipai/db"; +import { updateExecutionWorkspaceSchema } from "@paperclipai/shared"; +import { validate } from "../middleware/validate.js"; +import { executionWorkspaceService, logActivity } from "../services/index.js"; +import { assertCompanyAccess, getActorInfo } from "./authz.js"; + +export function executionWorkspaceRoutes(db: Db) { + const router = Router(); + const svc = executionWorkspaceService(db); + + router.get("/companies/:companyId/execution-workspaces", async (req, res) => { + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + const workspaces = await svc.list(companyId, { + projectId: req.query.projectId as string | undefined, + projectWorkspaceId: req.query.projectWorkspaceId as string | undefined, + issueId: req.query.issueId as string | undefined, + status: req.query.status as string | undefined, + reuseEligible: req.query.reuseEligible === "true", + }); + res.json(workspaces); + }); + + router.get("/execution-workspaces/:id", async (req, res) => { + const id = req.params.id as string; + const workspace = await svc.getById(id); + if (!workspace) { + res.status(404).json({ error: "Execution workspace not found" }); + return; + } + assertCompanyAccess(req, workspace.companyId); + res.json(workspace); + }); + + router.patch("/execution-workspaces/:id", validate(updateExecutionWorkspaceSchema), async (req, res) => { + const id = req.params.id as string; + const existing = await svc.getById(id); + if (!existing) { + res.status(404).json({ error: "Execution workspace not found" }); + return; + } + assertCompanyAccess(req, existing.companyId); + const workspace = await svc.update(id, { + ...req.body, + ...(req.body.cleanupEligibleAt ? { cleanupEligibleAt: new Date(req.body.cleanupEligibleAt) } : {}), + }); + if (!workspace) { + res.status(404).json({ error: "Execution workspace not found" }); + return; + } + const actor = getActorInfo(req); + await logActivity(db, { + companyId: existing.companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "execution_workspace.updated", + entityType: "execution_workspace", + entityId: workspace.id, + details: { changedKeys: Object.keys(req.body).sort() }, + }); + res.json(workspace); + }); + + return router; +} diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index f02067a6..3aaa03fa 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -4,10 +4,12 @@ import type { Db } from "@paperclipai/db"; import { addIssueCommentSchema, createIssueAttachmentMetadataSchema, + createIssueWorkProductSchema, createIssueLabelSchema, checkoutIssueSchema, createIssueSchema, linkIssueApprovalSchema, + updateIssueWorkProductSchema, updateIssueSchema, } from "@paperclipai/shared"; import type { StorageService } from "../storage/types.js"; @@ -15,12 +17,14 @@ import { validate } from "../middleware/validate.js"; import { accessService, agentService, + executionWorkspaceService, goalService, heartbeatService, issueApprovalService, issueService, logActivity, projectService, + workProductService, } from "../services/index.js"; import { logger } from "../middleware/logger.js"; import { forbidden, HttpError, unauthorized } from "../errors.js"; @@ -37,6 +41,8 @@ export function issueRoutes(db: Db, storage: StorageService) { const projectsSvc = projectService(db); const goalsSvc = goalService(db); const issueApprovalsSvc = issueApprovalService(db); + const executionWorkspacesSvc = executionWorkspaceService(db); + const workProductsSvc = workProductService(db); const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: MAX_ATTACHMENT_BYTES, files: 1 }, @@ -304,6 +310,10 @@ export function issueRoutes(db: Db, storage: StorageService) { const mentionedProjects = mentionedProjectIds.length > 0 ? await projectsSvc.listByIds(issue.companyId, mentionedProjectIds) : []; + const currentExecutionWorkspace = issue.executionWorkspaceId + ? await executionWorkspacesSvc.getById(issue.executionWorkspaceId) + : null; + const workProducts = await workProductsSvc.listForIssue(issue.id); res.json({ ...issue, goalId: goal?.id ?? issue.goalId, @@ -311,9 +321,110 @@ export function issueRoutes(db: Db, storage: StorageService) { project: project ?? null, goal: goal ?? null, mentionedProjects, + currentExecutionWorkspace, + workProducts, }); }); + router.get("/issues/:id/work-products", async (req, res) => { + const id = req.params.id as string; + const issue = await svc.getById(id); + if (!issue) { + res.status(404).json({ error: "Issue not found" }); + return; + } + assertCompanyAccess(req, issue.companyId); + const workProducts = await workProductsSvc.listForIssue(issue.id); + res.json(workProducts); + }); + + router.post("/issues/:id/work-products", validate(createIssueWorkProductSchema), async (req, res) => { + const id = req.params.id as string; + const issue = await svc.getById(id); + if (!issue) { + res.status(404).json({ error: "Issue not found" }); + return; + } + assertCompanyAccess(req, issue.companyId); + const product = await workProductsSvc.createForIssue(issue.id, issue.companyId, { + ...req.body, + projectId: req.body.projectId ?? issue.projectId ?? null, + }); + if (!product) { + res.status(422).json({ error: "Invalid work product payload" }); + return; + } + const actor = getActorInfo(req); + await logActivity(db, { + companyId: issue.companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "issue.work_product_created", + entityType: "issue", + entityId: issue.id, + details: { workProductId: product.id, type: product.type, provider: product.provider }, + }); + res.status(201).json(product); + }); + + router.patch("/work-products/:id", validate(updateIssueWorkProductSchema), async (req, res) => { + const id = req.params.id as string; + const existing = await workProductsSvc.getById(id); + if (!existing) { + res.status(404).json({ error: "Work product not found" }); + return; + } + assertCompanyAccess(req, existing.companyId); + const product = await workProductsSvc.update(id, req.body); + if (!product) { + res.status(404).json({ error: "Work product not found" }); + return; + } + const actor = getActorInfo(req); + await logActivity(db, { + companyId: existing.companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "issue.work_product_updated", + entityType: "issue", + entityId: existing.issueId, + details: { workProductId: product.id, changedKeys: Object.keys(req.body).sort() }, + }); + res.json(product); + }); + + router.delete("/work-products/:id", async (req, res) => { + const id = req.params.id as string; + const existing = await workProductsSvc.getById(id); + if (!existing) { + res.status(404).json({ error: "Work product not found" }); + return; + } + assertCompanyAccess(req, existing.companyId); + const removed = await workProductsSvc.remove(id); + if (!removed) { + res.status(404).json({ error: "Work product not found" }); + return; + } + const actor = getActorInfo(req); + await logActivity(db, { + companyId: existing.companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "issue.work_product_deleted", + entityType: "issue", + entityId: existing.issueId, + details: { workProductId: removed.id, type: removed.type }, + }); + res.json(removed); + }); + router.post("/issues/:id/read", async (req, res) => { const id = req.params.id as string; const issue = await svc.getById(id); diff --git a/server/src/services/execution-workspace-policy.ts b/server/src/services/execution-workspace-policy.ts index f4552af3..584f44e9 100644 --- a/server/src/services/execution-workspace-policy.ts +++ b/server/src/services/execution-workspace-policy.ts @@ -2,11 +2,12 @@ import type { ExecutionWorkspaceMode, ExecutionWorkspaceStrategy, IssueExecutionWorkspaceSettings, + ProjectExecutionWorkspaceDefaultMode, ProjectExecutionWorkspacePolicy, } from "@paperclipai/shared"; import { asString, parseObject } from "../adapters/utils.js"; -type ParsedExecutionWorkspaceMode = Exclude; +type ParsedExecutionWorkspaceMode = Exclude; function cloneRecord(value: Record | null | undefined): Record | null { if (!value) return null; @@ -16,7 +17,7 @@ function cloneRecord(value: Record | null | undefined): Record< function parseExecutionWorkspaceStrategy(raw: unknown): ExecutionWorkspaceStrategy | null { const parsed = parseObject(raw); const type = asString(parsed.type, ""); - if (type !== "project_primary" && type !== "git_worktree") { + if (type !== "project_primary" && type !== "git_worktree" && type !== "adapter_managed" && type !== "cloud_sandbox") { return null; } return { @@ -34,12 +35,28 @@ export function parseProjectExecutionWorkspacePolicy(raw: unknown): ProjectExecu if (Object.keys(parsed).length === 0) return null; const enabled = typeof parsed.enabled === "boolean" ? parsed.enabled : false; const defaultMode = asString(parsed.defaultMode, ""); + const defaultProjectWorkspaceId = + typeof parsed.defaultProjectWorkspaceId === "string" ? parsed.defaultProjectWorkspaceId : undefined; const allowIssueOverride = typeof parsed.allowIssueOverride === "boolean" ? parsed.allowIssueOverride : undefined; + const normalizedDefaultMode = (() => { + if ( + defaultMode === "shared_workspace" || + defaultMode === "isolated_workspace" || + defaultMode === "operator_branch" || + defaultMode === "adapter_default" + ) { + return defaultMode as ProjectExecutionWorkspaceDefaultMode; + } + if (defaultMode === "project_primary") return "shared_workspace"; + if (defaultMode === "isolated") return "isolated_workspace"; + return undefined; + })(); return { enabled, - ...(defaultMode === "project_primary" || defaultMode === "isolated" ? { defaultMode } : {}), + ...(normalizedDefaultMode ? { defaultMode: normalizedDefaultMode } : {}), ...(allowIssueOverride !== undefined ? { allowIssueOverride } : {}), + ...(defaultProjectWorkspaceId ? { defaultProjectWorkspaceId } : {}), ...(parseExecutionWorkspaceStrategy(parsed.workspaceStrategy) ? { workspaceStrategy: parseExecutionWorkspaceStrategy(parsed.workspaceStrategy) } : {}), @@ -52,6 +69,9 @@ export function parseProjectExecutionWorkspacePolicy(raw: unknown): ProjectExecu ...(parsed.pullRequestPolicy && typeof parsed.pullRequestPolicy === "object" && !Array.isArray(parsed.pullRequestPolicy) ? { pullRequestPolicy: { ...(parsed.pullRequestPolicy as Record) } } : {}), + ...(parsed.runtimePolicy && typeof parsed.runtimePolicy === "object" && !Array.isArray(parsed.runtimePolicy) + ? { runtimePolicy: { ...(parsed.runtimePolicy as Record) } } + : {}), ...(parsed.cleanupPolicy && typeof parsed.cleanupPolicy === "object" && !Array.isArray(parsed.cleanupPolicy) ? { cleanupPolicy: { ...(parsed.cleanupPolicy as Record) } } : {}), @@ -62,9 +82,24 @@ export function parseIssueExecutionWorkspaceSettings(raw: unknown): IssueExecuti const parsed = parseObject(raw); if (Object.keys(parsed).length === 0) return null; const mode = asString(parsed.mode, ""); + const normalizedMode = (() => { + if ( + mode === "inherit" || + mode === "shared_workspace" || + mode === "isolated_workspace" || + mode === "operator_branch" || + mode === "reuse_existing" || + mode === "agent_default" + ) { + return mode; + } + if (mode === "project_primary") return "shared_workspace"; + if (mode === "isolated") return "isolated_workspace"; + return ""; + })(); return { - ...(mode === "inherit" || mode === "project_primary" || mode === "isolated" || mode === "agent_default" - ? { mode } + ...(normalizedMode + ? { mode: normalizedMode as IssueExecutionWorkspaceSettings["mode"] } : {}), ...(parseExecutionWorkspaceStrategy(parsed.workspaceStrategy) ? { workspaceStrategy: parseExecutionWorkspaceStrategy(parsed.workspaceStrategy) } @@ -80,7 +115,14 @@ export function defaultIssueExecutionWorkspaceSettingsForProject( ): IssueExecutionWorkspaceSettings | null { if (!projectPolicy?.enabled) return null; return { - mode: projectPolicy.defaultMode === "isolated" ? "isolated" : "project_primary", + mode: + projectPolicy.defaultMode === "isolated_workspace" + ? "isolated_workspace" + : projectPolicy.defaultMode === "operator_branch" + ? "operator_branch" + : projectPolicy.defaultMode === "adapter_default" + ? "agent_default" + : "shared_workspace", }; } @@ -90,16 +132,19 @@ export function resolveExecutionWorkspaceMode(input: { legacyUseProjectWorkspace: boolean | null; }): ParsedExecutionWorkspaceMode { const issueMode = input.issueSettings?.mode; - if (issueMode && issueMode !== "inherit") { + if (issueMode && issueMode !== "inherit" && issueMode !== "reuse_existing") { return issueMode; } if (input.projectPolicy?.enabled) { - return input.projectPolicy.defaultMode === "isolated" ? "isolated" : "project_primary"; + if (input.projectPolicy.defaultMode === "isolated_workspace") return "isolated_workspace"; + if (input.projectPolicy.defaultMode === "operator_branch") return "operator_branch"; + if (input.projectPolicy.defaultMode === "adapter_default") return "agent_default"; + return "shared_workspace"; } if (input.legacyUseProjectWorkspace === false) { return "agent_default"; } - return "project_primary"; + return "shared_workspace"; } export function buildExecutionWorkspaceAdapterConfig(input: { @@ -119,7 +164,7 @@ export function buildExecutionWorkspaceAdapterConfig(input: { const hasWorkspaceControl = projectHasPolicy || issueHasWorkspaceOverrides || input.legacyUseProjectWorkspace === false; if (hasWorkspaceControl) { - if (input.mode === "isolated") { + if (input.mode === "isolated_workspace") { const strategy = input.issueSettings?.workspaceStrategy ?? input.projectPolicy?.workspaceStrategy ?? diff --git a/server/src/services/execution-workspaces.ts b/server/src/services/execution-workspaces.ts new file mode 100644 index 00000000..ea4dd163 --- /dev/null +++ b/server/src/services/execution-workspaces.ts @@ -0,0 +1,99 @@ +import { and, desc, eq, inArray } from "drizzle-orm"; +import type { Db } from "@paperclipai/db"; +import { executionWorkspaces } from "@paperclipai/db"; +import type { ExecutionWorkspace } from "@paperclipai/shared"; + +type ExecutionWorkspaceRow = typeof executionWorkspaces.$inferSelect; + +function toExecutionWorkspace(row: ExecutionWorkspaceRow): ExecutionWorkspace { + return { + id: row.id, + companyId: row.companyId, + projectId: row.projectId, + projectWorkspaceId: row.projectWorkspaceId ?? null, + sourceIssueId: row.sourceIssueId ?? null, + mode: row.mode as ExecutionWorkspace["mode"], + strategyType: row.strategyType as ExecutionWorkspace["strategyType"], + name: row.name, + status: row.status as ExecutionWorkspace["status"], + cwd: row.cwd ?? null, + repoUrl: row.repoUrl ?? null, + baseRef: row.baseRef ?? null, + branchName: row.branchName ?? null, + providerType: row.providerType as ExecutionWorkspace["providerType"], + providerRef: row.providerRef ?? null, + derivedFromExecutionWorkspaceId: row.derivedFromExecutionWorkspaceId ?? null, + lastUsedAt: row.lastUsedAt, + openedAt: row.openedAt, + closedAt: row.closedAt ?? null, + cleanupEligibleAt: row.cleanupEligibleAt ?? null, + cleanupReason: row.cleanupReason ?? null, + metadata: (row.metadata as Record | null) ?? null, + createdAt: row.createdAt, + updatedAt: row.updatedAt, + }; +} + +export function executionWorkspaceService(db: Db) { + return { + list: async (companyId: string, filters?: { + projectId?: string; + projectWorkspaceId?: string; + issueId?: string; + status?: string; + reuseEligible?: boolean; + }) => { + const conditions = [eq(executionWorkspaces.companyId, companyId)]; + if (filters?.projectId) conditions.push(eq(executionWorkspaces.projectId, filters.projectId)); + if (filters?.projectWorkspaceId) { + conditions.push(eq(executionWorkspaces.projectWorkspaceId, filters.projectWorkspaceId)); + } + if (filters?.issueId) conditions.push(eq(executionWorkspaces.sourceIssueId, filters.issueId)); + if (filters?.status) { + const statuses = filters.status.split(",").map((value) => value.trim()).filter(Boolean); + if (statuses.length === 1) conditions.push(eq(executionWorkspaces.status, statuses[0]!)); + else if (statuses.length > 1) conditions.push(inArray(executionWorkspaces.status, statuses)); + } + if (filters?.reuseEligible) { + conditions.push(inArray(executionWorkspaces.status, ["active", "idle", "in_review"])); + } + + const rows = await db + .select() + .from(executionWorkspaces) + .where(and(...conditions)) + .orderBy(desc(executionWorkspaces.lastUsedAt), desc(executionWorkspaces.createdAt)); + return rows.map(toExecutionWorkspace); + }, + + getById: async (id: string) => { + const row = await db + .select() + .from(executionWorkspaces) + .where(eq(executionWorkspaces.id, id)) + .then((rows) => rows[0] ?? null); + return row ? toExecutionWorkspace(row) : null; + }, + + create: async (data: typeof executionWorkspaces.$inferInsert) => { + const row = await db + .insert(executionWorkspaces) + .values(data) + .returning() + .then((rows) => rows[0] ?? null); + return row ? toExecutionWorkspace(row) : null; + }, + + update: async (id: string, patch: Partial) => { + const row = await db + .update(executionWorkspaces) + .set({ ...patch, updatedAt: new Date() }) + .where(eq(executionWorkspaces.id, id)) + .returning() + .then((rows) => rows[0] ?? null); + return row ? toExecutionWorkspace(row) : null; + }, + }; +} + +export { toExecutionWorkspace }; diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index f0665c9a..7ca4949d 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -33,6 +33,7 @@ import { releaseRuntimeServicesForRun, } from "./workspace-runtime.js"; import { issueService } from "./issues.js"; +import { executionWorkspaceService } from "./execution-workspaces.js"; import { buildExecutionWorkspaceAdapterConfig, parseIssueExecutionWorkspaceSettings, @@ -455,6 +456,7 @@ export function heartbeatService(db: Db) { const runLogStore = getRunLogStore(); const secretsSvc = secretService(db); const issuesSvc = issueService(db); + const executionWorkspacesSvc = executionWorkspaceService(db); const activeRunExecutions = new Set(); async function getAgent(agentId: string) { @@ -1130,6 +1132,9 @@ export function heartbeatService(db: Db) { ? await db .select({ projectId: issues.projectId, + projectWorkspaceId: issues.projectWorkspaceId, + executionWorkspaceId: issues.executionWorkspaceId, + executionWorkspacePreference: issues.executionWorkspacePreference, assigneeAgentId: issues.assigneeAgentId, assigneeAdapterOverrides: issues.assigneeAdapterOverrides, executionWorkspaceSettings: issues.executionWorkspaceSettings, @@ -1197,6 +1202,10 @@ export function heartbeatService(db: Db) { id: issues.id, identifier: issues.identifier, title: issues.title, + projectId: issues.projectId, + projectWorkspaceId: issues.projectWorkspaceId, + executionWorkspaceId: issues.executionWorkspaceId, + executionWorkspacePreference: issues.executionWorkspacePreference, }) .from(issues) .where(and(eq(issues.id, issueId), eq(issues.companyId, agent.companyId))) @@ -1219,6 +1228,67 @@ export function heartbeatService(db: Db) { companyId: agent.companyId, }, }); + const existingExecutionWorkspace = + issueRef?.executionWorkspaceId ? await executionWorkspacesSvc.getById(issueRef.executionWorkspaceId) : null; + const resolvedProjectId = executionWorkspace.projectId ?? issueRef?.projectId ?? executionProjectId ?? null; + const resolvedProjectWorkspaceId = issueRef?.projectWorkspaceId ?? resolvedWorkspace.workspaceId ?? null; + const shouldReuseExisting = + issueRef?.executionWorkspacePreference === "reuse_existing" && + existingExecutionWorkspace && + existingExecutionWorkspace.status !== "archived"; + const persistedExecutionWorkspace = shouldReuseExisting && existingExecutionWorkspace + ? await executionWorkspacesSvc.update(existingExecutionWorkspace.id, { + cwd: executionWorkspace.cwd, + repoUrl: executionWorkspace.repoUrl, + baseRef: executionWorkspace.repoRef, + branchName: executionWorkspace.branchName, + providerType: executionWorkspace.strategy === "git_worktree" ? "git_worktree" : "local_fs", + providerRef: executionWorkspace.worktreePath, + status: "active", + lastUsedAt: new Date(), + metadata: { + ...(existingExecutionWorkspace.metadata ?? {}), + source: executionWorkspace.source, + createdByRuntime: executionWorkspace.created, + }, + }) + : resolvedProjectId + ? await executionWorkspacesSvc.create({ + companyId: agent.companyId, + projectId: resolvedProjectId, + projectWorkspaceId: resolvedProjectWorkspaceId, + sourceIssueId: issueRef?.id ?? null, + mode: + executionWorkspaceMode === "isolated_workspace" + ? "isolated_workspace" + : executionWorkspaceMode === "operator_branch" + ? "operator_branch" + : executionWorkspaceMode === "agent_default" + ? "adapter_managed" + : "shared_workspace", + strategyType: executionWorkspace.strategy === "git_worktree" ? "git_worktree" : "project_primary", + name: executionWorkspace.branchName ?? issueRef?.identifier ?? `workspace-${agent.id.slice(0, 8)}`, + status: "active", + cwd: executionWorkspace.cwd, + repoUrl: executionWorkspace.repoUrl, + baseRef: executionWorkspace.repoRef, + branchName: executionWorkspace.branchName, + providerType: executionWorkspace.strategy === "git_worktree" ? "git_worktree" : "local_fs", + providerRef: executionWorkspace.worktreePath, + lastUsedAt: new Date(), + openedAt: new Date(), + metadata: { + source: executionWorkspace.source, + createdByRuntime: executionWorkspace.created, + }, + }) + : null; + if (issueId && persistedExecutionWorkspace && issueRef?.executionWorkspaceId !== persistedExecutionWorkspace.id) { + await issuesSvc.update(issueId, { + executionWorkspaceId: persistedExecutionWorkspace.id, + ...(resolvedProjectWorkspaceId ? { projectWorkspaceId: resolvedProjectWorkspaceId } : {}), + }); + } const runtimeSessionResolution = resolveRuntimeSessionParamsForWorkspace({ agentId: agent.id, previousSessionParams, diff --git a/server/src/services/index.ts b/server/src/services/index.ts index 99a950c5..f68290e7 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -14,6 +14,8 @@ export { dashboardService } from "./dashboard.js"; export { sidebarBadgeService } from "./sidebar-badges.js"; export { accessService } from "./access.js"; export { companyPortabilityService } from "./company-portability.js"; +export { executionWorkspaceService } from "./execution-workspaces.js"; +export { workProductService } from "./work-products.js"; export { logActivity, type LogActivityInput } from "./activity-log.js"; export { notifyHireApproved, type NotifyHireApprovedInput } from "./hire-hook.js"; export { publishLiveEvent, subscribeCompanyLiveEvents } from "./live-events.js"; diff --git a/server/src/services/issues.ts b/server/src/services/issues.ts index 807a97eb..a76258c7 100644 --- a/server/src/services/issues.ts +++ b/server/src/services/issues.ts @@ -7,6 +7,7 @@ import { companyMemberships, goals, heartbeatRuns, + executionWorkspaces, issueAttachments, issueLabels, issueComments, @@ -353,6 +354,40 @@ export function issueService(db: Db) { } } + async function assertValidProjectWorkspace(companyId: string, projectId: string | null | undefined, projectWorkspaceId: string) { + const workspace = await db + .select({ + id: projectWorkspaces.id, + companyId: projectWorkspaces.companyId, + projectId: projectWorkspaces.projectId, + }) + .from(projectWorkspaces) + .where(eq(projectWorkspaces.id, projectWorkspaceId)) + .then((rows) => rows[0] ?? null); + if (!workspace) throw notFound("Project workspace not found"); + if (workspace.companyId !== companyId) throw unprocessable("Project workspace must belong to same company"); + if (projectId && workspace.projectId !== projectId) { + throw unprocessable("Project workspace must belong to the selected project"); + } + } + + async function assertValidExecutionWorkspace(companyId: string, projectId: string | null | undefined, executionWorkspaceId: string) { + const workspace = await db + .select({ + id: executionWorkspaces.id, + companyId: executionWorkspaces.companyId, + projectId: executionWorkspaces.projectId, + }) + .from(executionWorkspaces) + .where(eq(executionWorkspaces.id, executionWorkspaceId)) + .then((rows) => rows[0] ?? null); + if (!workspace) throw notFound("Execution workspace not found"); + if (workspace.companyId !== companyId) throw unprocessable("Execution workspace must belong to same company"); + if (projectId && workspace.projectId !== projectId) { + throw unprocessable("Execution workspace must belong to the selected project"); + } + } + async function assertValidLabelIds(companyId: string, labelIds: string[], dbOrTx: any = db) { if (labelIds.length === 0) return; const existing = await dbOrTx @@ -647,6 +682,12 @@ export function issueService(db: Db) { if (data.assigneeUserId) { await assertAssignableUser(companyId, data.assigneeUserId); } + if (data.projectWorkspaceId) { + await assertValidProjectWorkspace(companyId, data.projectId, data.projectWorkspaceId); + } + if (data.executionWorkspaceId) { + await assertValidExecutionWorkspace(companyId, data.projectId, data.executionWorkspaceId); + } if (data.status === "in_progress" && !data.assigneeAgentId && !data.assigneeUserId) { throw unprocessable("in_progress issues require an assignee"); } @@ -665,6 +706,26 @@ export function issueService(db: Db) { parseProjectExecutionWorkspacePolicy(project?.executionWorkspacePolicy), ) as Record | null; } + let projectWorkspaceId = issueData.projectWorkspaceId ?? null; + if (!projectWorkspaceId && issueData.projectId) { + const project = await tx + .select({ + executionWorkspacePolicy: projects.executionWorkspacePolicy, + }) + .from(projects) + .where(and(eq(projects.id, issueData.projectId), eq(projects.companyId, companyId))) + .then((rows) => rows[0] ?? null); + const projectPolicy = parseProjectExecutionWorkspacePolicy(project?.executionWorkspacePolicy); + projectWorkspaceId = projectPolicy?.defaultProjectWorkspaceId ?? null; + if (!projectWorkspaceId) { + projectWorkspaceId = await tx + .select({ id: projectWorkspaces.id }) + .from(projectWorkspaces) + .where(and(eq(projectWorkspaces.projectId, issueData.projectId), eq(projectWorkspaces.companyId, companyId))) + .orderBy(desc(projectWorkspaces.isPrimary), asc(projectWorkspaces.createdAt), asc(projectWorkspaces.id)) + .then((rows) => rows[0]?.id ?? null); + } + } const [company] = await tx .update(companies) .set({ issueCounter: sql`${companies.issueCounter} + 1` }) @@ -681,6 +742,7 @@ export function issueService(db: Db) { goalId: issueData.goalId, defaultGoalId: defaultCompanyGoal?.id ?? null, }), + ...(projectWorkspaceId ? { projectWorkspaceId } : {}), ...(executionWorkspaceSettings ? { executionWorkspaceSettings } : {}), companyId, issueNumber, @@ -741,6 +803,17 @@ export function issueService(db: Db) { if (issueData.assigneeUserId) { await assertAssignableUser(existing.companyId, issueData.assigneeUserId); } + const nextProjectId = issueData.projectId !== undefined ? issueData.projectId : existing.projectId; + const nextProjectWorkspaceId = + issueData.projectWorkspaceId !== undefined ? issueData.projectWorkspaceId : existing.projectWorkspaceId; + const nextExecutionWorkspaceId = + issueData.executionWorkspaceId !== undefined ? issueData.executionWorkspaceId : existing.executionWorkspaceId; + if (nextProjectWorkspaceId) { + await assertValidProjectWorkspace(existing.companyId, nextProjectId, nextProjectWorkspaceId); + } + if (nextExecutionWorkspaceId) { + await assertValidExecutionWorkspace(existing.companyId, nextProjectId, nextExecutionWorkspaceId); + } applyStatusSideEffects(issueData.status, patch); if (issueData.status && issueData.status !== "done") { diff --git a/server/src/services/projects.ts b/server/src/services/projects.ts index bb8e180e..62be240b 100644 --- a/server/src/services/projects.ts +++ b/server/src/services/projects.ts @@ -20,9 +20,17 @@ type WorkspaceRuntimeServiceRow = typeof workspaceRuntimeServices.$inferSelect; const REPO_ONLY_CWD_SENTINEL = "/__paperclip_repo_only__"; type CreateWorkspaceInput = { name?: string | null; + sourceType?: string | null; cwd?: string | null; repoUrl?: string | null; repoRef?: string | null; + defaultRef?: string | null; + visibility?: string | null; + setupCommand?: string | null; + cleanupCommand?: string | null; + remoteProvider?: string | null; + remoteWorkspaceRef?: string | null; + sharedWorkspaceKey?: string | null; metadata?: Record | null; isPrimary?: boolean; }; @@ -91,6 +99,7 @@ function toRuntimeService(row: WorkspaceRuntimeServiceRow): WorkspaceRuntimeServ companyId: row.companyId, projectId: row.projectId ?? null, projectWorkspaceId: row.projectWorkspaceId ?? null, + executionWorkspaceId: row.executionWorkspaceId ?? null, issueId: row.issueId ?? null, scopeType: row.scopeType as WorkspaceRuntimeService["scopeType"], scopeId: row.scopeId ?? null, @@ -125,9 +134,17 @@ function toWorkspace( companyId: row.companyId, projectId: row.projectId, name: row.name, + sourceType: row.sourceType as ProjectWorkspace["sourceType"], cwd: row.cwd, repoUrl: row.repoUrl ?? null, repoRef: row.repoRef ?? null, + defaultRef: row.defaultRef ?? row.repoRef ?? null, + visibility: row.visibility as ProjectWorkspace["visibility"], + setupCommand: row.setupCommand ?? null, + cleanupCommand: row.cleanupCommand ?? null, + remoteProvider: row.remoteProvider ?? null, + remoteWorkspaceRef: row.remoteWorkspaceRef ?? null, + sharedWorkspaceKey: row.sharedWorkspaceKey ?? null, metadata: (row.metadata as Record | null) ?? null, isPrimary: row.isPrimary, runtimeServices, @@ -491,7 +508,13 @@ export function projectService(db: Db) { const cwd = normalizeWorkspaceCwd(data.cwd); const repoUrl = readNonEmptyString(data.repoUrl); - if (!cwd && !repoUrl) return null; + const sourceType = readNonEmptyString(data.sourceType) ?? (repoUrl ? "git_repo" : cwd ? "local_path" : "remote_managed"); + const remoteWorkspaceRef = readNonEmptyString(data.remoteWorkspaceRef); + if (sourceType === "remote_managed") { + if (!remoteWorkspaceRef && !repoUrl) return null; + } else if (!cwd && !repoUrl) { + return null; + } const name = deriveWorkspaceName({ name: data.name, cwd, @@ -525,9 +548,17 @@ export function projectService(db: Db) { companyId: project.companyId, projectId, name, + sourceType, cwd: cwd ?? null, repoUrl: repoUrl ?? null, repoRef: readNonEmptyString(data.repoRef), + defaultRef: readNonEmptyString(data.defaultRef) ?? readNonEmptyString(data.repoRef), + visibility: readNonEmptyString(data.visibility) ?? "default", + setupCommand: readNonEmptyString(data.setupCommand), + cleanupCommand: readNonEmptyString(data.cleanupCommand), + remoteProvider: readNonEmptyString(data.remoteProvider), + remoteWorkspaceRef, + sharedWorkspaceKey: readNonEmptyString(data.sharedWorkspaceKey), metadata: (data.metadata as Record | null | undefined) ?? null, isPrimary: shouldBePrimary, }) @@ -564,7 +595,19 @@ export function projectService(db: Db) { data.repoUrl !== undefined ? readNonEmptyString(data.repoUrl) : readNonEmptyString(existing.repoUrl); - if (!nextCwd && !nextRepoUrl) return null; + const nextSourceType = + data.sourceType !== undefined + ? readNonEmptyString(data.sourceType) + : readNonEmptyString(existing.sourceType); + const nextRemoteWorkspaceRef = + data.remoteWorkspaceRef !== undefined + ? readNonEmptyString(data.remoteWorkspaceRef) + : readNonEmptyString(existing.remoteWorkspaceRef); + if (nextSourceType === "remote_managed") { + if (!nextRemoteWorkspaceRef && !nextRepoUrl) return null; + } else if (!nextCwd && !nextRepoUrl) { + return null; + } const patch: Partial = { updatedAt: new Date(), @@ -576,6 +619,16 @@ export function projectService(db: Db) { if (data.cwd !== undefined) patch.cwd = nextCwd ?? null; if (data.repoUrl !== undefined) patch.repoUrl = nextRepoUrl ?? null; if (data.repoRef !== undefined) patch.repoRef = readNonEmptyString(data.repoRef); + if (data.sourceType !== undefined && nextSourceType) patch.sourceType = nextSourceType; + if (data.defaultRef !== undefined) patch.defaultRef = readNonEmptyString(data.defaultRef); + if (data.visibility !== undefined && readNonEmptyString(data.visibility)) { + patch.visibility = readNonEmptyString(data.visibility)!; + } + if (data.setupCommand !== undefined) patch.setupCommand = readNonEmptyString(data.setupCommand); + if (data.cleanupCommand !== undefined) patch.cleanupCommand = readNonEmptyString(data.cleanupCommand); + if (data.remoteProvider !== undefined) patch.remoteProvider = readNonEmptyString(data.remoteProvider); + if (data.remoteWorkspaceRef !== undefined) patch.remoteWorkspaceRef = nextRemoteWorkspaceRef; + if (data.sharedWorkspaceKey !== undefined) patch.sharedWorkspaceKey = readNonEmptyString(data.sharedWorkspaceKey); if (data.metadata !== undefined) patch.metadata = data.metadata; const updated = await db.transaction(async (tx) => { diff --git a/server/src/services/work-products.ts b/server/src/services/work-products.ts new file mode 100644 index 00000000..e5e4cc63 --- /dev/null +++ b/server/src/services/work-products.ts @@ -0,0 +1,113 @@ +import { and, desc, eq } from "drizzle-orm"; +import type { Db } from "@paperclipai/db"; +import { issueWorkProducts } from "@paperclipai/db"; +import type { IssueWorkProduct } from "@paperclipai/shared"; + +type IssueWorkProductRow = typeof issueWorkProducts.$inferSelect; + +function toIssueWorkProduct(row: IssueWorkProductRow): IssueWorkProduct { + return { + id: row.id, + companyId: row.companyId, + projectId: row.projectId ?? null, + issueId: row.issueId, + executionWorkspaceId: row.executionWorkspaceId ?? null, + runtimeServiceId: row.runtimeServiceId ?? null, + type: row.type as IssueWorkProduct["type"], + provider: row.provider, + externalId: row.externalId ?? null, + title: row.title, + url: row.url ?? null, + status: row.status, + reviewState: row.reviewState as IssueWorkProduct["reviewState"], + isPrimary: row.isPrimary, + healthStatus: row.healthStatus as IssueWorkProduct["healthStatus"], + summary: row.summary ?? null, + metadata: (row.metadata as Record | null) ?? null, + createdByRunId: row.createdByRunId ?? null, + createdAt: row.createdAt, + updatedAt: row.updatedAt, + }; +} + +export function workProductService(db: Db) { + return { + listForIssue: async (issueId: string) => { + const rows = await db + .select() + .from(issueWorkProducts) + .where(eq(issueWorkProducts.issueId, issueId)) + .orderBy(desc(issueWorkProducts.isPrimary), desc(issueWorkProducts.updatedAt)); + return rows.map(toIssueWorkProduct); + }, + + getById: async (id: string) => { + const row = await db + .select() + .from(issueWorkProducts) + .where(eq(issueWorkProducts.id, id)) + .then((rows) => rows[0] ?? null); + return row ? toIssueWorkProduct(row) : null; + }, + + createForIssue: async (issueId: string, companyId: string, data: Omit) => { + if (data.isPrimary) { + await db + .update(issueWorkProducts) + .set({ isPrimary: false, updatedAt: new Date() }) + .where(and(eq(issueWorkProducts.companyId, companyId), eq(issueWorkProducts.issueId, issueId), eq(issueWorkProducts.type, data.type))); + } + const row = await db + .insert(issueWorkProducts) + .values({ + ...data, + companyId, + issueId, + }) + .returning() + .then((rows) => rows[0] ?? null); + return row ? toIssueWorkProduct(row) : null; + }, + + update: async (id: string, patch: Partial) => { + const existing = await db + .select() + .from(issueWorkProducts) + .where(eq(issueWorkProducts.id, id)) + .then((rows) => rows[0] ?? null); + if (!existing) return null; + + if (patch.isPrimary === true) { + await db + .update(issueWorkProducts) + .set({ isPrimary: false, updatedAt: new Date() }) + .where( + and( + eq(issueWorkProducts.companyId, existing.companyId), + eq(issueWorkProducts.issueId, existing.issueId), + eq(issueWorkProducts.type, existing.type), + ), + ); + } + + const row = await db + .update(issueWorkProducts) + .set({ ...patch, updatedAt: new Date() }) + .where(eq(issueWorkProducts.id, id)) + .returning() + .then((rows) => rows[0] ?? null); + return row ? toIssueWorkProduct(row) : null; + }, + + remove: async (id: string) => { + const row = await db + .delete(issueWorkProducts) + .where(eq(issueWorkProducts.id, id)) + .returning() + .then((rows) => rows[0] ?? null); + return row ? toIssueWorkProduct(row) : null; + }, + }; +} + +export { toIssueWorkProduct }; diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 1cfdd9df..da3c9833 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -14,6 +14,7 @@ import { Projects } from "./pages/Projects"; import { ProjectDetail } from "./pages/ProjectDetail"; import { Issues } from "./pages/Issues"; import { IssueDetail } from "./pages/IssueDetail"; +import { ExecutionWorkspaceDetail } from "./pages/ExecutionWorkspaceDetail"; import { Goals } from "./pages/Goals"; import { GoalDetail } from "./pages/GoalDetail"; import { Approvals } from "./pages/Approvals"; @@ -136,6 +137,7 @@ function boardRoutes() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/ui/src/api/execution-workspaces.ts b/ui/src/api/execution-workspaces.ts new file mode 100644 index 00000000..bf83999c --- /dev/null +++ b/ui/src/api/execution-workspaces.ts @@ -0,0 +1,26 @@ +import type { ExecutionWorkspace } from "@paperclipai/shared"; +import { api } from "./client"; + +export const executionWorkspacesApi = { + list: ( + companyId: string, + filters?: { + projectId?: string; + projectWorkspaceId?: string; + issueId?: string; + status?: string; + reuseEligible?: boolean; + }, + ) => { + const params = new URLSearchParams(); + if (filters?.projectId) params.set("projectId", filters.projectId); + if (filters?.projectWorkspaceId) params.set("projectWorkspaceId", filters.projectWorkspaceId); + if (filters?.issueId) params.set("issueId", filters.issueId); + if (filters?.status) params.set("status", filters.status); + if (filters?.reuseEligible) params.set("reuseEligible", "true"); + const qs = params.toString(); + return api.get(`/companies/${companyId}/execution-workspaces${qs ? `?${qs}` : ""}`); + }, + get: (id: string) => api.get(`/execution-workspaces/${id}`), + update: (id: string, data: Record) => api.patch(`/execution-workspaces/${id}`, data), +}; diff --git a/ui/src/api/issues.ts b/ui/src/api/issues.ts index 941294e6..1d99d9fc 100644 --- a/ui/src/api/issues.ts +++ b/ui/src/api/issues.ts @@ -1,4 +1,4 @@ -import type { Approval, Issue, IssueAttachment, IssueComment, IssueLabel } from "@paperclipai/shared"; +import type { Approval, Issue, IssueAttachment, IssueComment, IssueLabel, IssueWorkProduct } from "@paperclipai/shared"; import { api } from "./client"; export const issuesApi = { @@ -73,4 +73,10 @@ export const issuesApi = { api.post(`/issues/${id}/approvals`, { approvalId }), unlinkApproval: (id: string, approvalId: string) => api.delete<{ ok: true }>(`/issues/${id}/approvals/${approvalId}`), + listWorkProducts: (id: string) => api.get(`/issues/${id}/work-products`), + createWorkProduct: (id: string, data: Record) => + api.post(`/issues/${id}/work-products`, data), + updateWorkProduct: (id: string, data: Record) => + api.patch(`/work-products/${id}`, data), + deleteWorkProduct: (id: string) => api.delete(`/work-products/${id}`), }; diff --git a/ui/src/components/IssueProperties.tsx b/ui/src/components/IssueProperties.tsx index ca8e1bd4..60cce40d 100644 --- a/ui/src/components/IssueProperties.tsx +++ b/ui/src/components/IssueProperties.tsx @@ -4,6 +4,7 @@ import type { Issue } from "@paperclipai/shared"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { agentsApi } from "../api/agents"; import { authApi } from "../api/auth"; +import { executionWorkspacesApi } from "../api/execution-workspaces"; import { issuesApi } from "../api/issues"; import { projectsApi } from "../api/projects"; import { useCompany } from "../context/CompanyContext"; @@ -15,13 +16,43 @@ import { PriorityIcon } from "./PriorityIcon"; import { Identity } from "./Identity"; import { formatDate, cn, projectUrl } from "../lib/utils"; import { timeAgo } from "../lib/timeAgo"; +import { useExperimentalWorkspacesEnabled } from "../lib/experimentalSettings"; import { Separator } from "@/components/ui/separator"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { User, Hexagon, ArrowUpRight, Tag, Plus, Trash2 } from "lucide-react"; import { AgentIcon } from "./AgentIconPicker"; -// TODO(issue-worktree-support): re-enable this UI once the workflow is ready to ship. -const SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI = false; +const EXECUTION_WORKSPACE_OPTIONS = [ + { value: "shared_workspace", label: "Project default" }, + { value: "isolated_workspace", label: "New isolated workspace" }, + { value: "reuse_existing", label: "Reuse existing workspace" }, + { value: "operator_branch", label: "Operator branch" }, + { value: "agent_default", label: "Agent default" }, +] as const; + +function defaultProjectWorkspaceIdForProject(project: { + workspaces?: Array<{ id: string; isPrimary: boolean }>; + executionWorkspacePolicy?: { defaultProjectWorkspaceId?: string | null } | null; +} | null | undefined) { + if (!project) return null; + return project.executionWorkspacePolicy?.defaultProjectWorkspaceId + ?? project.workspaces?.find((workspace) => workspace.isPrimary)?.id + ?? project.workspaces?.[0]?.id + ?? null; +} + +function defaultExecutionWorkspaceModeForProject(project: { executionWorkspacePolicy?: { enabled?: boolean; defaultMode?: string | null } | null } | null | undefined) { + const defaultMode = project?.executionWorkspacePolicy?.enabled ? project.executionWorkspacePolicy.defaultMode : null; + if (defaultMode === "isolated_workspace" || defaultMode === "operator_branch") return defaultMode; + if (defaultMode === "adapter_default") return "agent_default"; + return "shared_workspace"; +} + +function issueModeForExistingWorkspace(mode: string | null | undefined) { + if (mode === "isolated_workspace" || mode === "operator_branch" || mode === "shared_workspace") return mode; + if (mode === "adapter_managed" || mode === "cloud_sandbox") return "agent_default"; + return "shared_workspace"; +} interface IssuePropertiesProps { issue: Issue; @@ -102,6 +133,7 @@ function PropertyPicker({ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProps) { const { selectedCompanyId } = useCompany(); + const { enabled: showExperimentalWorkspaceUi } = useExperimentalWorkspacesEnabled(); const queryClient = useQueryClient(); const companyId = issue.companyId ?? selectedCompanyId; const [assigneeOpen, setAssigneeOpen] = useState(false); @@ -182,15 +214,32 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp const currentProject = issue.projectId ? orderedProjects.find((project) => project.id === issue.projectId) ?? null : null; - const currentProjectExecutionWorkspacePolicy = SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI + const currentProjectExecutionWorkspacePolicy = showExperimentalWorkspaceUi ? currentProject?.executionWorkspacePolicy ?? null : null; const currentProjectSupportsExecutionWorkspace = Boolean(currentProjectExecutionWorkspacePolicy?.enabled); - const usesIsolatedExecutionWorkspace = issue.executionWorkspaceSettings?.mode === "isolated" - ? true - : issue.executionWorkspaceSettings?.mode === "project_primary" - ? false - : currentProjectExecutionWorkspacePolicy?.defaultMode === "isolated"; + const currentProjectWorkspaces = currentProject?.workspaces ?? []; + const currentExecutionWorkspaceSelection = + issue.executionWorkspacePreference + ?? issue.executionWorkspaceSettings?.mode + ?? defaultExecutionWorkspaceModeForProject(currentProject); + const { data: reusableExecutionWorkspaces } = useQuery({ + queryKey: queryKeys.executionWorkspaces.list(companyId!, { + projectId: issue.projectId ?? undefined, + projectWorkspaceId: issue.projectWorkspaceId ?? undefined, + reuseEligible: true, + }), + queryFn: () => + executionWorkspacesApi.list(companyId!, { + projectId: issue.projectId ?? undefined, + projectWorkspaceId: issue.projectWorkspaceId ?? undefined, + reuseEligible: true, + }), + enabled: Boolean(companyId) && showExperimentalWorkspaceUi && Boolean(issue.projectId), + }); + const selectedReusableExecutionWorkspace = (reusableExecutionWorkspaces ?? []).find( + (workspace) => workspace.id === issue.executionWorkspaceId, + ); const projectLink = (id: string | null) => { if (!id) return null; const project = projects?.find((p) => p.id === id) ?? null; @@ -418,7 +467,13 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp !issue.projectId && "bg-accent" )} onClick={() => { - onUpdate({ projectId: null, executionWorkspaceSettings: null }); + onUpdate({ + projectId: null, + projectWorkspaceId: null, + executionWorkspaceId: null, + executionWorkspacePreference: null, + executionWorkspaceSettings: null, + }); setProjectOpen(false); }} > @@ -438,10 +493,14 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp p.id === issue.projectId && "bg-accent" )} onClick={() => { + const defaultMode = defaultExecutionWorkspaceModeForProject(p); onUpdate({ projectId: p.id, - executionWorkspaceSettings: SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI && p.executionWorkspacePolicy?.enabled - ? { mode: p.executionWorkspacePolicy.defaultMode === "isolated" ? "isolated" : "project_primary" } + projectWorkspaceId: showExperimentalWorkspaceUi ? defaultProjectWorkspaceIdForProject(p) : null, + executionWorkspaceId: null, + executionWorkspacePreference: showExperimentalWorkspaceUi ? defaultMode : null, + executionWorkspaceSettings: showExperimentalWorkspaceUi && p.executionWorkspacePolicy?.enabled + ? { mode: defaultMode } : null, }); setProjectOpen(false); @@ -530,38 +589,94 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp {projectContent} - {currentProjectSupportsExecutionWorkspace && ( + {showExperimentalWorkspaceUi && currentProjectWorkspaces.length > 0 && ( + + + + )} + + {showExperimentalWorkspaceUi && currentProjectSupportsExecutionWorkspace && ( -
-
-
- {usesIsolatedExecutionWorkspace ? "Isolated issue checkout" : "Project primary checkout"} -
-
- Toggle whether this issue runs in its own execution workspace. -
-
-
- {currentProjectSupportsExecutionWorkspace && ( -
-
-
-
Use isolated issue checkout
+ {showExperimentalWorkspaceUi && currentProject && ( +
+ {currentProjectWorkspaces.length > 0 && ( +
+
Codebase
- Create an issue-specific execution workspace instead of using the project's primary checkout. + Choose which project workspace this issue should use.
+
- -
+ {executionWorkspaceMode === "reuse_existing" && selectedReusableExecutionWorkspace && ( +
+ Reusing {selectedReusableExecutionWorkspace.name} from {selectedReusableExecutionWorkspace.branchName ?? selectedReusableExecutionWorkspace.cwd ?? "existing execution workspace"}. +
+ )} +
+ )}
)} diff --git a/ui/src/components/ProjectProperties.tsx b/ui/src/components/ProjectProperties.tsx index 9237f5e3..6a809f33 100644 --- a/ui/src/components/ProjectProperties.tsx +++ b/ui/src/components/ProjectProperties.tsx @@ -17,6 +17,7 @@ import { AlertCircle, Check, ExternalLink, Github, Loader2, Plus, Trash2, X } fr import { ChoosePathButton } from "./PathInstructionsModal"; import { DraftInput } from "./agent-config-primitives"; import { InlineEditor } from "./InlineEditor"; +import { useExperimentalWorkspacesEnabled } from "../lib/experimentalSettings"; const PROJECT_STATUSES = [ { value: "backlog", label: "Backlog" }, @@ -26,9 +27,6 @@ const PROJECT_STATUSES = [ { value: "cancelled", label: "Cancelled" }, ]; -// TODO(issue-worktree-support): re-enable this UI once the workflow is ready to ship. -const SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI = false; - interface ProjectPropertiesProps { project: Project; onUpdate?: (data: Record) => void; @@ -154,6 +152,7 @@ function ProjectStatusPicker({ status, onChange }: { status: string; onChange: ( export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSaveState }: ProjectPropertiesProps) { const { selectedCompanyId } = useCompany(); + const { enabled: showExperimentalWorkspaceUi } = useExperimentalWorkspacesEnabled(); const queryClient = useQueryClient(); const [goalOpen, setGoalOpen] = useState(false); const [executionWorkspaceAdvancedOpen, setExecutionWorkspaceAdvancedOpen] = useState(false); @@ -195,7 +194,7 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa const executionWorkspacePolicy = project.executionWorkspacePolicy ?? null; const executionWorkspacesEnabled = executionWorkspacePolicy?.enabled === true; const executionWorkspaceDefaultMode = - executionWorkspacePolicy?.defaultMode === "isolated" ? "isolated" : "project_primary"; + executionWorkspacePolicy?.defaultMode === "isolated_workspace" ? "isolated_workspace" : "shared_workspace"; const executionWorkspaceStrategy = executionWorkspacePolicy?.workspaceStrategy ?? { type: "git_worktree", baseRef: "", @@ -710,7 +709,7 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa )}
- {SHOW_EXPERIMENTAL_ISSUE_WORKTREE_UI && ( + {showExperimentalWorkspaceUi && ( <> @@ -785,21 +784,21 @@ export function ProjectProperties({ project, onUpdate, onFieldUpdate, getFieldSa diff --git a/ui/src/lib/experimentalSettings.ts b/ui/src/lib/experimentalSettings.ts new file mode 100644 index 00000000..a48d7c06 --- /dev/null +++ b/ui/src/lib/experimentalSettings.ts @@ -0,0 +1,39 @@ +import { useEffect, useState } from "react"; + +const WORKSPACES_KEY = "paperclip:experimental:workspaces"; + +export function loadExperimentalWorkspacesEnabled(): boolean { + if (typeof window === "undefined") return false; + return window.localStorage.getItem(WORKSPACES_KEY) === "true"; +} + +export function saveExperimentalWorkspacesEnabled(enabled: boolean) { + if (typeof window === "undefined") return; + window.localStorage.setItem(WORKSPACES_KEY, enabled ? "true" : "false"); + window.dispatchEvent(new CustomEvent("paperclip:experimental:workspaces", { detail: enabled })); +} + +export function useExperimentalWorkspacesEnabled() { + const [enabled, setEnabled] = useState(loadExperimentalWorkspacesEnabled); + + useEffect(() => { + const handleStorage = (event: StorageEvent) => { + if (event.key && event.key !== WORKSPACES_KEY) return; + setEnabled(loadExperimentalWorkspacesEnabled()); + }; + const handleCustom = () => setEnabled(loadExperimentalWorkspacesEnabled()); + window.addEventListener("storage", handleStorage); + window.addEventListener("paperclip:experimental:workspaces", handleCustom as EventListener); + return () => { + window.removeEventListener("storage", handleStorage); + window.removeEventListener("paperclip:experimental:workspaces", handleCustom as EventListener); + }; + }, []); + + const update = (next: boolean) => { + saveExperimentalWorkspacesEnabled(next); + setEnabled(next); + }; + + return { enabled, setEnabled: update }; +} diff --git a/ui/src/lib/inbox.test.ts b/ui/src/lib/inbox.test.ts index a8480828..9e14a76e 100644 --- a/ui/src/lib/inbox.test.ts +++ b/ui/src/lib/inbox.test.ts @@ -110,6 +110,7 @@ function makeIssue(id: string, isUnreadForMe: boolean): Issue { id, companyId: "company-1", projectId: null, + projectWorkspaceId: null, goalId: null, parentId: null, title: `Issue ${id}`, @@ -125,6 +126,8 @@ function makeIssue(id: string, isUnreadForMe: boolean): Issue { requestDepth: 0, billingCode: null, assigneeAdapterOverrides: null, + executionWorkspaceId: null, + executionWorkspacePreference: null, executionWorkspaceSettings: null, checkoutRunId: null, executionRunId: null, diff --git a/ui/src/lib/queryKeys.ts b/ui/src/lib/queryKeys.ts index c500afdc..a057fd7e 100644 --- a/ui/src/lib/queryKeys.ts +++ b/ui/src/lib/queryKeys.ts @@ -32,6 +32,12 @@ export const queryKeys = { approvals: (issueId: string) => ["issues", "approvals", issueId] as const, liveRuns: (issueId: string) => ["issues", "live-runs", issueId] as const, activeRun: (issueId: string) => ["issues", "active-run", issueId] as const, + workProducts: (issueId: string) => ["issues", "work-products", issueId] as const, + }, + executionWorkspaces: { + list: (companyId: string, filters?: Record) => + ["execution-workspaces", companyId, filters ?? {}] as const, + detail: (id: string) => ["execution-workspaces", "detail", id] as const, }, projects: { list: (companyId: string) => ["projects", companyId] as const, diff --git a/ui/src/pages/ExecutionWorkspaceDetail.tsx b/ui/src/pages/ExecutionWorkspaceDetail.tsx new file mode 100644 index 00000000..03f2715d --- /dev/null +++ b/ui/src/pages/ExecutionWorkspaceDetail.tsx @@ -0,0 +1,70 @@ +import { Link, useParams } from "@/lib/router"; +import { useQuery } from "@tanstack/react-query"; +import { ExternalLink } from "lucide-react"; +import { executionWorkspacesApi } from "../api/execution-workspaces"; +import { queryKeys } from "../lib/queryKeys"; + +function DetailRow({ label, children }: { label: string; children: React.ReactNode }) { + return ( +
+
{label}
+
{children}
+
+ ); +} + +export function ExecutionWorkspaceDetail() { + const { workspaceId } = useParams<{ workspaceId: string }>(); + + const { data: workspace, isLoading, error } = useQuery({ + queryKey: queryKeys.executionWorkspaces.detail(workspaceId!), + queryFn: () => executionWorkspacesApi.get(workspaceId!), + enabled: Boolean(workspaceId), + }); + + if (isLoading) return

Loading...

; + if (error) return

{error instanceof Error ? error.message : "Failed to load workspace"}

; + if (!workspace) return null; + + return ( +
+
+
Execution workspace
+

{workspace.name}

+
+ {workspace.status} · {workspace.mode} · {workspace.providerType} +
+
+ +
+ + {workspace.projectId ? {workspace.projectId} : "None"} + + + {workspace.sourceIssueId ? {workspace.sourceIssueId} : "None"} + + {workspace.branchName ?? "None"} + {workspace.baseRef ?? "None"} + + {workspace.cwd ?? "None"} + + + {workspace.providerRef ?? "None"} + + + {workspace.repoUrl ? ( + + {workspace.repoUrl} + + + ) : "None"} + + {new Date(workspace.openedAt).toLocaleString()} + {new Date(workspace.lastUsedAt).toLocaleString()} + + {workspace.cleanupEligibleAt ? `${new Date(workspace.cleanupEligibleAt).toLocaleString()}${workspace.cleanupReason ? ` · ${workspace.cleanupReason}` : ""}` : "Not scheduled"} + +
+
+ ); +} diff --git a/ui/src/pages/InstanceSettings.tsx b/ui/src/pages/InstanceSettings.tsx index a4781e1f..ab77a177 100644 --- a/ui/src/pages/InstanceSettings.tsx +++ b/ui/src/pages/InstanceSettings.tsx @@ -12,6 +12,7 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { queryKeys } from "../lib/queryKeys"; import { formatDateTime, relativeTime } from "../lib/utils"; +import { useExperimentalWorkspacesEnabled } from "../lib/experimentalSettings"; function asRecord(value: unknown): Record | null { if (typeof value !== "object" || value === null || Array.isArray(value)) return null; @@ -30,6 +31,7 @@ export function InstanceSettings() { const { setBreadcrumbs } = useBreadcrumbs(); const queryClient = useQueryClient(); const [actionError, setActionError] = useState(null); + const { enabled: workspacesEnabled, setEnabled: setWorkspacesEnabled } = useExperimentalWorkspacesEnabled(); useEffect(() => { setBreadcrumbs([ @@ -110,6 +112,34 @@ export function InstanceSettings() { return (
+
+
+
+ +

Experimental

+
+

+ UI-only feature flags for in-progress product surfaces. +

+
+
+
+
Workspaces
+
+ Show workspace, execution workspace, and work product controls in project and issue UI. +
+
+ +
+
+
diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index bb152e17..fb4d50fb 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 { useBreadcrumbs } from "../context/BreadcrumbContext"; import { queryKeys } from "../lib/queryKeys"; +import { useExperimentalWorkspacesEnabled } from "../lib/experimentalSettings"; import { readIssueDetailBreadcrumb } from "../lib/issueDetailBreadcrumb"; import { useProjectOrder } from "../hooks/useProjectOrder"; import { relativeTime, cn, formatTokens } from "../lib/utils"; @@ -36,15 +37,21 @@ import { ChevronDown, ChevronRight, EyeOff, + ExternalLink, + FileText, + GitBranch, + GitPullRequest, Hexagon, ListTree, MessageSquare, MoreHorizontal, + Package, Paperclip, + Rocket, SlidersHorizontal, Trash2, } from "lucide-react"; -import type { ActivityEvent } from "@paperclipai/shared"; +import type { ActivityEvent, IssueWorkProduct } from "@paperclipai/shared"; import type { Agent, IssueAttachment } from "@paperclipai/shared"; type CommentReassignment = { @@ -133,6 +140,24 @@ function formatAction(action: string, details?: Record | null): return ACTION_LABELS[action] ?? action.replace(/[._]/g, " "); } +function workProductIcon(product: IssueWorkProduct) { + switch (product.type) { + case "pull_request": + return ; + case "branch": + case "commit": + return ; + case "artifact": + return ; + case "document": + return ; + case "runtime_service": + return ; + default: + return ; + } +} + function ActorIdentity({ evt, agentMap }: { evt: ActivityEvent; agentMap: Map }) { const id = evt.actorId; if (evt.actorType === "agent") { @@ -147,6 +172,7 @@ function ActorIdentity({ evt, agentMap }: { evt: ActivityEvent; agentMap: Map(); const { selectedCompanyId } = useCompany(); + const { enabled: experimentalWorkspacesEnabled } = useExperimentalWorkspacesEnabled(); const { openPanel, closePanel, panelVisible, setPanelVisible } = usePanel(); const { setBreadcrumbs } = useBreadcrumbs(); const queryClient = useQueryClient(); @@ -160,6 +186,13 @@ export function IssueDetail() { cost: false, }); const [attachmentError, setAttachmentError] = useState(null); + const [newWorkProductType, setNewWorkProductType] = useState("preview_url"); + const [newWorkProductProvider, setNewWorkProductProvider] = useState("paperclip"); + const [newWorkProductTitle, setNewWorkProductTitle] = useState(""); + const [newWorkProductUrl, setNewWorkProductUrl] = useState(""); + const [newWorkProductStatus, setNewWorkProductStatus] = useState("active"); + const [newWorkProductReviewState, setNewWorkProductReviewState] = useState("none"); + const [newWorkProductSummary, setNewWorkProductSummary] = useState(""); const fileInputRef = useRef(null); const lastMarkedReadIssueIdRef = useRef(null); @@ -387,6 +420,7 @@ export function IssueDetail() { queryClient.invalidateQueries({ queryKey: queryKeys.issues.attachments(issueId!) }); queryClient.invalidateQueries({ queryKey: queryKeys.issues.liveRuns(issueId!) }); queryClient.invalidateQueries({ queryKey: queryKeys.issues.activeRun(issueId!) }); + queryClient.invalidateQueries({ queryKey: queryKeys.issues.workProducts(issueId!) }); if (selectedCompanyId) { queryClient.invalidateQueries({ queryKey: queryKeys.issues.list(selectedCompanyId) }); queryClient.invalidateQueries({ queryKey: queryKeys.issues.listTouchedByMe(selectedCompanyId) }); @@ -471,6 +505,42 @@ export function IssueDetail() { }, }); + const createWorkProduct = useMutation({ + mutationFn: () => + issuesApi.createWorkProduct(issueId!, { + type: newWorkProductType, + provider: newWorkProductProvider, + title: newWorkProductTitle.trim(), + url: newWorkProductUrl.trim() || null, + status: newWorkProductStatus, + reviewState: newWorkProductReviewState, + summary: newWorkProductSummary.trim() || null, + projectId: issue?.projectId ?? null, + executionWorkspaceId: issue?.currentExecutionWorkspace?.id ?? issue?.executionWorkspaceId ?? null, + }), + onSuccess: () => { + setNewWorkProductTitle(""); + setNewWorkProductUrl(""); + setNewWorkProductSummary(""); + setNewWorkProductType("preview_url"); + setNewWorkProductProvider("paperclip"); + setNewWorkProductStatus("active"); + setNewWorkProductReviewState("none"); + invalidateIssue(); + }, + }); + + const updateWorkProduct = useMutation({ + mutationFn: ({ id, data }: { id: string; data: Record }) => + issuesApi.updateWorkProduct(id, data), + onSuccess: () => invalidateIssue(), + }); + + const deleteWorkProduct = useMutation({ + mutationFn: (id: string) => issuesApi.deleteWorkProduct(id), + onSuccess: () => invalidateIssue(), + }); + useEffect(() => { const titleLabel = issue?.title ?? issueId ?? "Issue"; setBreadcrumbs([ @@ -508,6 +578,11 @@ export function IssueDetail() { // Ancestors are returned oldest-first from the server (root at end, immediate parent at start) const ancestors = issue.ancestors ?? []; + const workProducts = issue.workProducts ?? []; + const showOutputsTab = + experimentalWorkspacesEnabled || + Boolean(issue.currentExecutionWorkspace) || + workProducts.length > 0; const handleFilePicked = async (evt: ChangeEvent) => { const file = evt.target.files?.[0]; @@ -759,6 +834,12 @@ export function IssueDetail() { Comments + {showOutputsTab && ( + + + Outputs + + )} Sub-issues @@ -798,6 +879,199 @@ export function IssueDetail() { /> + {showOutputsTab && ( + + {issue.currentExecutionWorkspace && ( +
+
+
+
Execution workspace
+
+ {issue.currentExecutionWorkspace.status} · {issue.currentExecutionWorkspace.mode} +
+
+ + Open + + +
+
+ {issue.currentExecutionWorkspace.branchName ?? issue.currentExecutionWorkspace.cwd ?? "No workspace path recorded."} +
+
+ )} + +
+
Work product
+
+ + setNewWorkProductProvider(e.target.value)} + placeholder="Provider" + /> + setNewWorkProductTitle(e.target.value)} + placeholder="Title" + /> + setNewWorkProductUrl(e.target.value)} + placeholder="URL" + /> + + +