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:
Forgotten
2026-02-17 09:07:22 -06:00
parent fade29fc3e
commit 8c830eae70
41 changed files with 4464 additions and 121 deletions

View File

@@ -1,11 +1,21 @@
import { z } from "zod";
import { AGENT_ROLES, AGENT_STATUSES } from "../constants.js";
import {
AGENT_ADAPTER_TYPES,
AGENT_CONTEXT_MODES,
AGENT_ROLES,
AGENT_STATUSES,
} from "../constants.js";
export const createAgentSchema = z.object({
name: z.string().min(1),
role: z.enum(AGENT_ROLES),
budgetCents: z.number().int().nonnegative().optional().default(0),
role: z.enum(AGENT_ROLES).optional().default("general"),
title: z.string().optional().nullable(),
reportsTo: z.string().uuid().optional().nullable(),
capabilities: z.string().optional().nullable(),
adapterType: z.enum(AGENT_ADAPTER_TYPES).optional().default("process"),
adapterConfig: z.record(z.unknown()).optional().default({}),
contextMode: z.enum(AGENT_CONTEXT_MODES).optional().default("thin"),
budgetMonthlyCents: z.number().int().nonnegative().optional().default(0),
metadata: z.record(z.unknown()).optional().nullable(),
});
@@ -15,6 +25,13 @@ export const updateAgentSchema = createAgentSchema
.partial()
.extend({
status: z.enum(AGENT_STATUSES).optional(),
spentMonthlyCents: z.number().int().nonnegative().optional(),
});
export type UpdateAgent = z.infer<typeof updateAgentSchema>;
export const createAgentKeySchema = z.object({
name: z.string().min(1).default("default"),
});
export type CreateAgentKey = z.infer<typeof createAgentKeySchema>;

View File

@@ -0,0 +1,17 @@
import { z } from "zod";
import { APPROVAL_TYPES } from "../constants.js";
export const createApprovalSchema = z.object({
type: z.enum(APPROVAL_TYPES),
requestedByAgentId: z.string().uuid().optional().nullable(),
payload: z.record(z.unknown()),
});
export type CreateApproval = z.infer<typeof createApprovalSchema>;
export const resolveApprovalSchema = z.object({
decisionNote: z.string().optional().nullable(),
decidedByUserId: z.string().optional().default("board"),
});
export type ResolveApproval = z.infer<typeof resolveApprovalSchema>;

View File

@@ -0,0 +1,19 @@
import { z } from "zod";
import { COMPANY_STATUSES } from "../constants.js";
export const createCompanySchema = z.object({
name: z.string().min(1),
description: z.string().optional().nullable(),
budgetMonthlyCents: z.number().int().nonnegative().optional().default(0),
});
export type CreateCompany = z.infer<typeof createCompanySchema>;
export const updateCompanySchema = createCompanySchema
.partial()
.extend({
status: z.enum(COMPANY_STATUSES).optional(),
spentMonthlyCents: z.number().int().nonnegative().optional(),
});
export type UpdateCompany = z.infer<typeof updateCompanySchema>;

View File

@@ -0,0 +1,23 @@
import { z } from "zod";
export const createCostEventSchema = z.object({
agentId: z.string().uuid(),
issueId: z.string().uuid().optional().nullable(),
projectId: z.string().uuid().optional().nullable(),
goalId: z.string().uuid().optional().nullable(),
billingCode: z.string().optional().nullable(),
provider: z.string().min(1),
model: z.string().min(1),
inputTokens: z.number().int().nonnegative().optional().default(0),
outputTokens: z.number().int().nonnegative().optional().default(0),
costCents: z.number().int().nonnegative(),
occurredAt: z.string().datetime(),
});
export type CreateCostEvent = z.infer<typeof createCostEventSchema>;
export const updateBudgetSchema = z.object({
budgetMonthlyCents: z.number().int().nonnegative(),
});
export type UpdateBudget = z.infer<typeof updateBudgetSchema>;

View File

@@ -1,12 +1,13 @@
import { z } from "zod";
import { GOAL_LEVELS } from "../constants.js";
import { GOAL_LEVELS, GOAL_STATUSES } from "../constants.js";
export const createGoalSchema = z.object({
title: z.string().min(1),
description: z.string().optional().nullable(),
level: z.enum(GOAL_LEVELS),
level: z.enum(GOAL_LEVELS).optional().default("task"),
status: z.enum(GOAL_STATUSES).optional().default("planned"),
parentId: z.string().uuid().optional().nullable(),
ownerId: z.string().uuid().optional().nullable(),
ownerAgentId: z.string().uuid().optional().nullable(),
});
export type CreateGoal = z.infer<typeof createGoalSchema>;

View File

@@ -1,8 +1,17 @@
export {
createCompanySchema,
updateCompanySchema,
type CreateCompany,
type UpdateCompany,
} from "./company.js";
export {
createAgentSchema,
updateAgentSchema,
createAgentKeySchema,
type CreateAgent,
type UpdateAgent,
type CreateAgentKey,
} from "./agent.js";
export {
@@ -15,8 +24,12 @@ export {
export {
createIssueSchema,
updateIssueSchema,
checkoutIssueSchema,
addIssueCommentSchema,
type CreateIssue,
type UpdateIssue,
type CheckoutIssue,
type AddIssueComment,
} from "./issue.js";
export {
@@ -25,3 +38,17 @@ export {
type CreateGoal,
type UpdateGoal,
} from "./goal.js";
export {
createApprovalSchema,
resolveApprovalSchema,
type CreateApproval,
type ResolveApproval,
} from "./approval.js";
export {
createCostEventSchema,
updateBudgetSchema,
type CreateCostEvent,
type UpdateBudget,
} from "./cost.js";

View File

@@ -2,13 +2,16 @@ import { z } from "zod";
import { ISSUE_PRIORITIES, ISSUE_STATUSES } from "../constants.js";
export const createIssueSchema = z.object({
projectId: z.string().uuid().optional().nullable(),
goalId: z.string().uuid().optional().nullable(),
parentId: z.string().uuid().optional().nullable(),
title: z.string().min(1),
description: z.string().optional().nullable(),
status: z.enum(ISSUE_STATUSES).optional().default("backlog"),
priority: z.enum(ISSUE_PRIORITIES).optional().default("medium"),
projectId: z.string().uuid().optional().nullable(),
assigneeId: z.string().uuid().optional().nullable(),
goalId: z.string().uuid().optional().nullable(),
assigneeAgentId: z.string().uuid().optional().nullable(),
requestDepth: z.number().int().nonnegative().optional().default(0),
billingCode: z.string().optional().nullable(),
});
export type CreateIssue = z.infer<typeof createIssueSchema>;
@@ -16,3 +19,16 @@ export type CreateIssue = z.infer<typeof createIssueSchema>;
export const updateIssueSchema = createIssueSchema.partial();
export type UpdateIssue = z.infer<typeof updateIssueSchema>;
export const checkoutIssueSchema = z.object({
agentId: z.string().uuid(),
expectedStatuses: z.array(z.enum(ISSUE_STATUSES)).nonempty(),
});
export type CheckoutIssue = z.infer<typeof checkoutIssueSchema>;
export const addIssueCommentSchema = z.object({
body: z.string().min(1),
});
export type AddIssueComment = z.infer<typeof addIssueCommentSchema>;

View File

@@ -1,8 +1,13 @@
import { z } from "zod";
import { PROJECT_STATUSES } from "../constants.js";
export const createProjectSchema = z.object({
goalId: z.string().uuid().optional().nullable(),
name: z.string().min(1),
description: z.string().optional().nullable(),
status: z.enum(PROJECT_STATUSES).optional().default("backlog"),
leadAgentId: z.string().uuid().optional().nullable(),
targetDate: z.string().optional().nullable(),
});
export type CreateProject = z.infer<typeof createProjectSchema>;