Expand data model with companies, approvals, costs, and heartbeats
Add new DB schemas: companies, agent_api_keys, approvals, cost_events, heartbeat_runs, issue_comments. Add corresponding shared types and validators. Update existing schemas (agents, goals, issues, projects) with new fields for company association, budgets, and richer metadata. Generate initial Drizzle migration. Update seed data. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
208
packages/db/src/migrations/0000_mature_masked_marvel.sql
Normal file
208
packages/db/src/migrations/0000_mature_masked_marvel.sql
Normal file
@@ -0,0 +1,208 @@
|
||||
CREATE TABLE "activity_log" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"actor_type" text DEFAULT 'system' NOT NULL,
|
||||
"actor_id" text NOT NULL,
|
||||
"action" text NOT NULL,
|
||||
"entity_type" text NOT NULL,
|
||||
"entity_id" text NOT NULL,
|
||||
"agent_id" uuid,
|
||||
"details" jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "agent_api_keys" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"agent_id" uuid NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"key_hash" text NOT NULL,
|
||||
"last_used_at" timestamp with time zone,
|
||||
"revoked_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "agents" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"role" text DEFAULT 'general' NOT NULL,
|
||||
"title" text,
|
||||
"status" text DEFAULT 'idle' NOT NULL,
|
||||
"reports_to" uuid,
|
||||
"capabilities" text,
|
||||
"adapter_type" text DEFAULT 'process' NOT NULL,
|
||||
"adapter_config" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"context_mode" text DEFAULT 'thin' NOT NULL,
|
||||
"budget_monthly_cents" integer DEFAULT 0 NOT NULL,
|
||||
"spent_monthly_cents" integer DEFAULT 0 NOT NULL,
|
||||
"last_heartbeat_at" timestamp with time zone,
|
||||
"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 "approvals" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"type" text NOT NULL,
|
||||
"requested_by_agent_id" uuid,
|
||||
"requested_by_user_id" text,
|
||||
"status" text DEFAULT 'pending' NOT NULL,
|
||||
"payload" jsonb NOT NULL,
|
||||
"decision_note" text,
|
||||
"decided_by_user_id" text,
|
||||
"decided_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "companies" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"status" text DEFAULT 'active' NOT NULL,
|
||||
"budget_monthly_cents" integer DEFAULT 0 NOT NULL,
|
||||
"spent_monthly_cents" integer DEFAULT 0 NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "cost_events" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"agent_id" uuid NOT NULL,
|
||||
"issue_id" uuid,
|
||||
"project_id" uuid,
|
||||
"goal_id" uuid,
|
||||
"billing_code" text,
|
||||
"provider" text NOT NULL,
|
||||
"model" text NOT NULL,
|
||||
"input_tokens" integer DEFAULT 0 NOT NULL,
|
||||
"output_tokens" integer DEFAULT 0 NOT NULL,
|
||||
"cost_cents" integer NOT NULL,
|
||||
"occurred_at" timestamp with time zone NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "goals" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"title" text NOT NULL,
|
||||
"description" text,
|
||||
"level" text DEFAULT 'task' NOT NULL,
|
||||
"status" text DEFAULT 'planned' NOT NULL,
|
||||
"parent_id" uuid,
|
||||
"owner_agent_id" uuid,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "heartbeat_runs" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"agent_id" uuid NOT NULL,
|
||||
"invocation_source" text DEFAULT 'manual' NOT NULL,
|
||||
"status" text DEFAULT 'queued' NOT NULL,
|
||||
"started_at" timestamp with time zone,
|
||||
"finished_at" timestamp with time zone,
|
||||
"error" text,
|
||||
"external_run_id" text,
|
||||
"context_snapshot" 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_comments" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"issue_id" uuid NOT NULL,
|
||||
"author_agent_id" uuid,
|
||||
"author_user_id" text,
|
||||
"body" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "issues" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"project_id" uuid,
|
||||
"goal_id" uuid,
|
||||
"parent_id" uuid,
|
||||
"title" text NOT NULL,
|
||||
"description" text,
|
||||
"status" text DEFAULT 'backlog' NOT NULL,
|
||||
"priority" text DEFAULT 'medium' NOT NULL,
|
||||
"assignee_agent_id" uuid,
|
||||
"created_by_agent_id" uuid,
|
||||
"created_by_user_id" text,
|
||||
"request_depth" integer DEFAULT 0 NOT NULL,
|
||||
"billing_code" text,
|
||||
"started_at" timestamp with time zone,
|
||||
"completed_at" timestamp with time zone,
|
||||
"cancelled_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "projects" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"goal_id" uuid,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"status" text DEFAULT 'backlog' NOT NULL,
|
||||
"lead_agent_id" uuid,
|
||||
"target_date" date,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "activity_log" ADD CONSTRAINT "activity_log_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 "activity_log" ADD CONSTRAINT "activity_log_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "agent_api_keys" ADD CONSTRAINT "agent_api_keys_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "agent_api_keys" ADD CONSTRAINT "agent_api_keys_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 "agents" ADD CONSTRAINT "agents_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 "agents" ADD CONSTRAINT "agents_reports_to_agents_id_fk" FOREIGN KEY ("reports_to") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "approvals" ADD CONSTRAINT "approvals_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 "approvals" ADD CONSTRAINT "approvals_requested_by_agent_id_agents_id_fk" FOREIGN KEY ("requested_by_agent_id") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "cost_events" ADD CONSTRAINT "cost_events_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 "cost_events" ADD CONSTRAINT "cost_events_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "cost_events" ADD CONSTRAINT "cost_events_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "cost_events" ADD CONSTRAINT "cost_events_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "cost_events" ADD CONSTRAINT "cost_events_goal_id_goals_id_fk" FOREIGN KEY ("goal_id") REFERENCES "public"."goals"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "goals" ADD CONSTRAINT "goals_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 "goals" ADD CONSTRAINT "goals_parent_id_goals_id_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."goals"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "goals" ADD CONSTRAINT "goals_owner_agent_id_agents_id_fk" FOREIGN KEY ("owner_agent_id") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "heartbeat_runs" ADD CONSTRAINT "heartbeat_runs_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 "heartbeat_runs" ADD CONSTRAINT "heartbeat_runs_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "issue_comments" ADD CONSTRAINT "issue_comments_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_comments" ADD CONSTRAINT "issue_comments_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "issue_comments" ADD CONSTRAINT "issue_comments_author_agent_id_agents_id_fk" FOREIGN KEY ("author_agent_id") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "issues" ADD CONSTRAINT "issues_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 "issues" ADD CONSTRAINT "issues_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "issues" ADD CONSTRAINT "issues_goal_id_goals_id_fk" FOREIGN KEY ("goal_id") REFERENCES "public"."goals"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "issues" ADD CONSTRAINT "issues_parent_id_issues_id_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."issues"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "issues" ADD CONSTRAINT "issues_assignee_agent_id_agents_id_fk" FOREIGN KEY ("assignee_agent_id") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "issues" ADD CONSTRAINT "issues_created_by_agent_id_agents_id_fk" FOREIGN KEY ("created_by_agent_id") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "projects" ADD CONSTRAINT "projects_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 "projects" ADD CONSTRAINT "projects_goal_id_goals_id_fk" FOREIGN KEY ("goal_id") REFERENCES "public"."goals"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "projects" ADD CONSTRAINT "projects_lead_agent_id_agents_id_fk" FOREIGN KEY ("lead_agent_id") REFERENCES "public"."agents"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "activity_log_company_created_idx" ON "activity_log" USING btree ("company_id","created_at");--> statement-breakpoint
|
||||
CREATE INDEX "agent_api_keys_key_hash_idx" ON "agent_api_keys" USING btree ("key_hash");--> statement-breakpoint
|
||||
CREATE INDEX "agent_api_keys_company_agent_idx" ON "agent_api_keys" USING btree ("company_id","agent_id");--> statement-breakpoint
|
||||
CREATE INDEX "agents_company_status_idx" ON "agents" USING btree ("company_id","status");--> statement-breakpoint
|
||||
CREATE INDEX "agents_company_reports_to_idx" ON "agents" USING btree ("company_id","reports_to");--> statement-breakpoint
|
||||
CREATE INDEX "approvals_company_status_type_idx" ON "approvals" USING btree ("company_id","status","type");--> statement-breakpoint
|
||||
CREATE INDEX "cost_events_company_occurred_idx" ON "cost_events" USING btree ("company_id","occurred_at");--> statement-breakpoint
|
||||
CREATE INDEX "cost_events_company_agent_occurred_idx" ON "cost_events" USING btree ("company_id","agent_id","occurred_at");--> statement-breakpoint
|
||||
CREATE INDEX "goals_company_idx" ON "goals" USING btree ("company_id");--> statement-breakpoint
|
||||
CREATE INDEX "heartbeat_runs_company_agent_started_idx" ON "heartbeat_runs" USING btree ("company_id","agent_id","started_at");--> statement-breakpoint
|
||||
CREATE INDEX "issue_comments_issue_idx" ON "issue_comments" USING btree ("issue_id");--> statement-breakpoint
|
||||
CREATE INDEX "issue_comments_company_idx" ON "issue_comments" USING btree ("company_id");--> statement-breakpoint
|
||||
CREATE INDEX "issues_company_status_idx" ON "issues" USING btree ("company_id","status");--> statement-breakpoint
|
||||
CREATE INDEX "issues_company_assignee_status_idx" ON "issues" USING btree ("company_id","assignee_agent_id","status");--> statement-breakpoint
|
||||
CREATE INDEX "issues_company_parent_idx" ON "issues" USING btree ("company_id","parent_id");--> statement-breakpoint
|
||||
CREATE INDEX "issues_company_project_idx" ON "issues" USING btree ("company_id","project_id");--> statement-breakpoint
|
||||
CREATE INDEX "projects_company_idx" ON "projects" USING btree ("company_id");
|
||||
1743
packages/db/src/migrations/meta/0000_snapshot.json
Normal file
1743
packages/db/src/migrations/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1 +1,13 @@
|
||||
{"version":"7","dialect":"postgresql","entries":[]}
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1771300567463,
|
||||
"tag": "0000_mature_masked_marvel",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,12 +1,22 @@
|
||||
import { pgTable, uuid, text, timestamp, jsonb } from "drizzle-orm/pg-core";
|
||||
import { pgTable, uuid, text, timestamp, jsonb, index } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
import { agents } from "./agents.js";
|
||||
|
||||
export const activityLog = pgTable("activity_log", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
action: text("action").notNull(),
|
||||
entityType: text("entity_type").notNull(),
|
||||
entityId: uuid("entity_id").notNull(),
|
||||
agentId: uuid("agent_id").references(() => agents.id),
|
||||
details: jsonb("details"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
export const activityLog = pgTable(
|
||||
"activity_log",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
actorType: text("actor_type").notNull().default("system"),
|
||||
actorId: text("actor_id").notNull(),
|
||||
action: text("action").notNull(),
|
||||
entityType: text("entity_type").notNull(),
|
||||
entityId: text("entity_id").notNull(),
|
||||
agentId: uuid("agent_id").references(() => agents.id),
|
||||
details: jsonb("details").$type<Record<string, unknown>>(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
companyCreatedIdx: index("activity_log_company_created_idx").on(table.companyId, table.createdAt),
|
||||
}),
|
||||
);
|
||||
|
||||
21
packages/db/src/schema/agent_api_keys.ts
Normal file
21
packages/db/src/schema/agent_api_keys.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core";
|
||||
import { agents } from "./agents.js";
|
||||
import { companies } from "./companies.js";
|
||||
|
||||
export const agentApiKeys = pgTable(
|
||||
"agent_api_keys",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
agentId: uuid("agent_id").notNull().references(() => agents.id),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
name: text("name").notNull(),
|
||||
keyHash: text("key_hash").notNull(),
|
||||
lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
|
||||
revokedAt: timestamp("revoked_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
keyHashIdx: index("agent_api_keys_key_hash_idx").on(table.keyHash),
|
||||
companyAgentIdx: index("agent_api_keys_company_agent_idx").on(table.companyId, table.agentId),
|
||||
}),
|
||||
);
|
||||
@@ -1,15 +1,38 @@
|
||||
import { type AnyPgColumn, pgTable, uuid, text, integer, timestamp, jsonb } from "drizzle-orm/pg-core";
|
||||
import {
|
||||
type AnyPgColumn,
|
||||
pgTable,
|
||||
uuid,
|
||||
text,
|
||||
integer,
|
||||
timestamp,
|
||||
jsonb,
|
||||
index,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
|
||||
export const agents = pgTable("agents", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
name: text("name").notNull(),
|
||||
role: text("role").notNull().default("general"),
|
||||
status: text("status").notNull().default("idle"),
|
||||
budgetCents: integer("budget_cents").notNull().default(0),
|
||||
spentCents: integer("spent_cents").notNull().default(0),
|
||||
lastHeartbeat: timestamp("last_heartbeat", { withTimezone: true }),
|
||||
reportsTo: uuid("reports_to").references((): AnyPgColumn => agents.id),
|
||||
metadata: jsonb("metadata"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
export const agents = pgTable(
|
||||
"agents",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
name: text("name").notNull(),
|
||||
role: text("role").notNull().default("general"),
|
||||
title: text("title"),
|
||||
status: text("status").notNull().default("idle"),
|
||||
reportsTo: uuid("reports_to").references((): AnyPgColumn => agents.id),
|
||||
capabilities: text("capabilities"),
|
||||
adapterType: text("adapter_type").notNull().default("process"),
|
||||
adapterConfig: jsonb("adapter_config").$type<Record<string, unknown>>().notNull().default({}),
|
||||
contextMode: text("context_mode").notNull().default("thin"),
|
||||
budgetMonthlyCents: integer("budget_monthly_cents").notNull().default(0),
|
||||
spentMonthlyCents: integer("spent_monthly_cents").notNull().default(0),
|
||||
lastHeartbeatAt: timestamp("last_heartbeat_at", { withTimezone: true }),
|
||||
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
companyStatusIdx: index("agents_company_status_idx").on(table.companyId, table.status),
|
||||
companyReportsToIdx: index("agents_company_reports_to_idx").on(table.companyId, table.reportsTo),
|
||||
}),
|
||||
);
|
||||
|
||||
28
packages/db/src/schema/approvals.ts
Normal file
28
packages/db/src/schema/approvals.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { pgTable, uuid, text, timestamp, jsonb, index } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
import { agents } from "./agents.js";
|
||||
|
||||
export const approvals = pgTable(
|
||||
"approvals",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
type: text("type").notNull(),
|
||||
requestedByAgentId: uuid("requested_by_agent_id").references(() => agents.id),
|
||||
requestedByUserId: text("requested_by_user_id"),
|
||||
status: text("status").notNull().default("pending"),
|
||||
payload: jsonb("payload").$type<Record<string, unknown>>().notNull(),
|
||||
decisionNote: text("decision_note"),
|
||||
decidedByUserId: text("decided_by_user_id"),
|
||||
decidedAt: timestamp("decided_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
companyStatusTypeIdx: index("approvals_company_status_type_idx").on(
|
||||
table.companyId,
|
||||
table.status,
|
||||
table.type,
|
||||
),
|
||||
}),
|
||||
);
|
||||
12
packages/db/src/schema/companies.ts
Normal file
12
packages/db/src/schema/companies.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { pgTable, uuid, text, integer, timestamp } from "drizzle-orm/pg-core";
|
||||
|
||||
export const companies = pgTable("companies", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
name: text("name").notNull(),
|
||||
description: text("description"),
|
||||
status: text("status").notNull().default("active"),
|
||||
budgetMonthlyCents: integer("budget_monthly_cents").notNull().default(0),
|
||||
spentMonthlyCents: integer("spent_monthly_cents").notNull().default(0),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
34
packages/db/src/schema/cost_events.ts
Normal file
34
packages/db/src/schema/cost_events.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { pgTable, uuid, text, timestamp, integer, index } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
import { agents } from "./agents.js";
|
||||
import { issues } from "./issues.js";
|
||||
import { projects } from "./projects.js";
|
||||
import { goals } from "./goals.js";
|
||||
|
||||
export const costEvents = pgTable(
|
||||
"cost_events",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
agentId: uuid("agent_id").notNull().references(() => agents.id),
|
||||
issueId: uuid("issue_id").references(() => issues.id),
|
||||
projectId: uuid("project_id").references(() => projects.id),
|
||||
goalId: uuid("goal_id").references(() => goals.id),
|
||||
billingCode: text("billing_code"),
|
||||
provider: text("provider").notNull(),
|
||||
model: text("model").notNull(),
|
||||
inputTokens: integer("input_tokens").notNull().default(0),
|
||||
outputTokens: integer("output_tokens").notNull().default(0),
|
||||
costCents: integer("cost_cents").notNull(),
|
||||
occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
companyOccurredIdx: index("cost_events_company_occurred_idx").on(table.companyId, table.occurredAt),
|
||||
companyAgentOccurredIdx: index("cost_events_company_agent_occurred_idx").on(
|
||||
table.companyId,
|
||||
table.agentId,
|
||||
table.occurredAt,
|
||||
),
|
||||
}),
|
||||
);
|
||||
@@ -1,13 +1,29 @@
|
||||
import { type AnyPgColumn, pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core";
|
||||
import {
|
||||
type AnyPgColumn,
|
||||
pgTable,
|
||||
uuid,
|
||||
text,
|
||||
timestamp,
|
||||
index,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { agents } from "./agents.js";
|
||||
import { companies } from "./companies.js";
|
||||
|
||||
export const goals = pgTable("goals", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
title: text("title").notNull(),
|
||||
description: text("description"),
|
||||
level: text("level").notNull().default("task"),
|
||||
parentId: uuid("parent_id").references((): AnyPgColumn => goals.id),
|
||||
ownerId: uuid("owner_id").references(() => agents.id),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
export const goals = pgTable(
|
||||
"goals",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
title: text("title").notNull(),
|
||||
description: text("description"),
|
||||
level: text("level").notNull().default("task"),
|
||||
status: text("status").notNull().default("planned"),
|
||||
parentId: uuid("parent_id").references((): AnyPgColumn => goals.id),
|
||||
ownerAgentId: uuid("owner_agent_id").references(() => agents.id),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
companyIdx: index("goals_company_idx").on(table.companyId),
|
||||
}),
|
||||
);
|
||||
|
||||
28
packages/db/src/schema/heartbeat_runs.ts
Normal file
28
packages/db/src/schema/heartbeat_runs.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { pgTable, uuid, text, timestamp, jsonb, index } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
import { agents } from "./agents.js";
|
||||
|
||||
export const heartbeatRuns = pgTable(
|
||||
"heartbeat_runs",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
agentId: uuid("agent_id").notNull().references(() => agents.id),
|
||||
invocationSource: text("invocation_source").notNull().default("manual"),
|
||||
status: text("status").notNull().default("queued"),
|
||||
startedAt: timestamp("started_at", { withTimezone: true }),
|
||||
finishedAt: timestamp("finished_at", { withTimezone: true }),
|
||||
error: text("error"),
|
||||
externalRunId: text("external_run_id"),
|
||||
contextSnapshot: jsonb("context_snapshot").$type<Record<string, unknown>>(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
companyAgentStartedIdx: index("heartbeat_runs_company_agent_started_idx").on(
|
||||
table.companyId,
|
||||
table.agentId,
|
||||
table.startedAt,
|
||||
),
|
||||
}),
|
||||
);
|
||||
@@ -1,5 +1,11 @@
|
||||
export { companies } from "./companies.js";
|
||||
export { agents } from "./agents.js";
|
||||
export { agentApiKeys } from "./agent_api_keys.js";
|
||||
export { projects } from "./projects.js";
|
||||
export { goals } from "./goals.js";
|
||||
export { issues } from "./issues.js";
|
||||
export { issueComments } from "./issue_comments.js";
|
||||
export { heartbeatRuns } from "./heartbeat_runs.js";
|
||||
export { costEvents } from "./cost_events.js";
|
||||
export { approvals } from "./approvals.js";
|
||||
export { activityLog } from "./activity_log.js";
|
||||
|
||||
22
packages/db/src/schema/issue_comments.ts
Normal file
22
packages/db/src/schema/issue_comments.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
import { issues } from "./issues.js";
|
||||
import { agents } from "./agents.js";
|
||||
|
||||
export const issueComments = pgTable(
|
||||
"issue_comments",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
issueId: uuid("issue_id").notNull().references(() => issues.id),
|
||||
authorAgentId: uuid("author_agent_id").references(() => agents.id),
|
||||
authorUserId: text("author_user_id"),
|
||||
body: text("body").notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
issueIdx: index("issue_comments_issue_idx").on(table.issueId),
|
||||
companyIdx: index("issue_comments_company_idx").on(table.companyId),
|
||||
}),
|
||||
);
|
||||
@@ -1,17 +1,48 @@
|
||||
import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core";
|
||||
import {
|
||||
type AnyPgColumn,
|
||||
pgTable,
|
||||
uuid,
|
||||
text,
|
||||
timestamp,
|
||||
integer,
|
||||
index,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { agents } from "./agents.js";
|
||||
import { projects } from "./projects.js";
|
||||
import { goals } from "./goals.js";
|
||||
import { companies } from "./companies.js";
|
||||
|
||||
export const issues = pgTable("issues", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
title: text("title").notNull(),
|
||||
description: text("description"),
|
||||
status: text("status").notNull().default("backlog"),
|
||||
priority: text("priority").notNull().default("medium"),
|
||||
projectId: uuid("project_id").references(() => projects.id),
|
||||
assigneeId: uuid("assignee_id").references(() => agents.id),
|
||||
goalId: uuid("goal_id").references(() => goals.id),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
export const issues = pgTable(
|
||||
"issues",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
projectId: uuid("project_id").references(() => projects.id),
|
||||
goalId: uuid("goal_id").references(() => goals.id),
|
||||
parentId: uuid("parent_id").references((): AnyPgColumn => issues.id),
|
||||
title: text("title").notNull(),
|
||||
description: text("description"),
|
||||
status: text("status").notNull().default("backlog"),
|
||||
priority: text("priority").notNull().default("medium"),
|
||||
assigneeAgentId: uuid("assignee_agent_id").references(() => agents.id),
|
||||
createdByAgentId: uuid("created_by_agent_id").references(() => agents.id),
|
||||
createdByUserId: text("created_by_user_id"),
|
||||
requestDepth: integer("request_depth").notNull().default(0),
|
||||
billingCode: text("billing_code"),
|
||||
startedAt: timestamp("started_at", { withTimezone: true }),
|
||||
completedAt: timestamp("completed_at", { withTimezone: true }),
|
||||
cancelledAt: timestamp("cancelled_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
companyStatusIdx: index("issues_company_status_idx").on(table.companyId, table.status),
|
||||
assigneeStatusIdx: index("issues_company_assignee_status_idx").on(
|
||||
table.companyId,
|
||||
table.assigneeAgentId,
|
||||
table.status,
|
||||
),
|
||||
parentIdx: index("issues_company_parent_idx").on(table.companyId, table.parentId),
|
||||
projectIdx: index("issues_company_project_idx").on(table.companyId, table.projectId),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core";
|
||||
import { pgTable, uuid, text, timestamp, date, index } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
import { goals } from "./goals.js";
|
||||
import { agents } from "./agents.js";
|
||||
|
||||
export const projects = pgTable("projects", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
name: text("name").notNull(),
|
||||
description: text("description"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
export const projects = pgTable(
|
||||
"projects",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
goalId: uuid("goal_id").references(() => goals.id),
|
||||
name: text("name").notNull(),
|
||||
description: text("description"),
|
||||
status: text("status").notNull().default("backlog"),
|
||||
leadAgentId: uuid("lead_agent_id").references(() => agents.id),
|
||||
targetDate: date("target_date"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
companyIdx: index("projects_company_idx").on(table.companyId),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { eq, inArray } from "drizzle-orm";
|
||||
import { createDb } from "./client.js";
|
||||
import { agents, projects, issues, goals } from "./schema/index.js";
|
||||
import { companies, agents, goals, projects, issues } from "./schema/index.js";
|
||||
|
||||
const url = process.env.DATABASE_URL;
|
||||
if (!url) throw new Error("DATABASE_URL is required");
|
||||
@@ -9,56 +8,90 @@ const db = createDb(url);
|
||||
|
||||
console.log("Seeding database...");
|
||||
|
||||
const [ceo, engineer, researcher] = await db
|
||||
.insert(agents)
|
||||
.values([
|
||||
{ name: "CEO Agent", role: "ceo", status: "active" },
|
||||
{ name: "Engineer Agent", role: "engineer", status: "idle" },
|
||||
{ name: "Researcher Agent", role: "researcher", status: "idle" },
|
||||
])
|
||||
const [company] = await db
|
||||
.insert(companies)
|
||||
.values({
|
||||
name: "Paperclip Demo Co",
|
||||
description: "A demo autonomous company",
|
||||
status: "active",
|
||||
budgetMonthlyCents: 50000,
|
||||
})
|
||||
.returning();
|
||||
|
||||
// Wire up reporting hierarchy: engineer and researcher report to CEO
|
||||
await db
|
||||
.update(agents)
|
||||
.set({ reportsTo: ceo!.id })
|
||||
.where(inArray(agents.id, [engineer!.id, researcher!.id]));
|
||||
const [ceo] = await db
|
||||
.insert(agents)
|
||||
.values({
|
||||
companyId: company!.id,
|
||||
name: "CEO Agent",
|
||||
role: "ceo",
|
||||
title: "Chief Executive Officer",
|
||||
status: "idle",
|
||||
adapterType: "process",
|
||||
adapterConfig: { command: "echo", args: ["hello from ceo"] },
|
||||
budgetMonthlyCents: 15000,
|
||||
})
|
||||
.returning();
|
||||
|
||||
const [project] = await db
|
||||
.insert(projects)
|
||||
.values([{ name: "Paperclip MVP", description: "Build the initial paperclip management platform" }])
|
||||
const [engineer] = await db
|
||||
.insert(agents)
|
||||
.values({
|
||||
companyId: company!.id,
|
||||
name: "Engineer Agent",
|
||||
role: "engineer",
|
||||
title: "Software Engineer",
|
||||
status: "idle",
|
||||
reportsTo: ceo!.id,
|
||||
adapterType: "process",
|
||||
adapterConfig: { command: "echo", args: ["hello from engineer"] },
|
||||
budgetMonthlyCents: 10000,
|
||||
})
|
||||
.returning();
|
||||
|
||||
const [goal] = await db
|
||||
.insert(goals)
|
||||
.values([
|
||||
{
|
||||
title: "Launch MVP",
|
||||
description: "Ship the minimum viable product",
|
||||
level: "milestone",
|
||||
ownerId: ceo!.id,
|
||||
},
|
||||
])
|
||||
.values({
|
||||
companyId: company!.id,
|
||||
title: "Ship V1",
|
||||
description: "Deliver first control plane release",
|
||||
level: "company",
|
||||
status: "active",
|
||||
ownerAgentId: ceo!.id,
|
||||
})
|
||||
.returning();
|
||||
|
||||
const [project] = await db
|
||||
.insert(projects)
|
||||
.values({
|
||||
companyId: company!.id,
|
||||
goalId: goal!.id,
|
||||
name: "Control Plane MVP",
|
||||
description: "Implement core board + agent loop",
|
||||
status: "in_progress",
|
||||
leadAgentId: ceo!.id,
|
||||
})
|
||||
.returning();
|
||||
|
||||
await db.insert(issues).values([
|
||||
{
|
||||
title: "Set up database schema",
|
||||
description: "Create initial Drizzle schema with all core tables",
|
||||
status: "done",
|
||||
priority: "high",
|
||||
companyId: company!.id,
|
||||
projectId: project!.id,
|
||||
assigneeId: engineer!.id,
|
||||
goalId: goal!.id,
|
||||
title: "Implement atomic task checkout",
|
||||
description: "Ensure in_progress claiming is conflict-safe",
|
||||
status: "todo",
|
||||
priority: "high",
|
||||
assigneeAgentId: engineer!.id,
|
||||
createdByAgentId: ceo!.id,
|
||||
},
|
||||
{
|
||||
title: "Implement agent heartbeat",
|
||||
description: "Add periodic heartbeat mechanism for agent health monitoring",
|
||||
status: "in_progress",
|
||||
priority: "medium",
|
||||
companyId: company!.id,
|
||||
projectId: project!.id,
|
||||
assigneeId: engineer!.id,
|
||||
goalId: goal!.id,
|
||||
title: "Add budget auto-pause",
|
||||
description: "Pause agent at hard budget ceiling",
|
||||
status: "backlog",
|
||||
priority: "medium",
|
||||
createdByAgentId: ceo!.id,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user