import { int, mysqlEnum, mysqlTable, text, timestamp, varchar, decimal, json, boolean, index, } from "drizzle-orm/mysql-core"; /** * Core user table backing auth flow. * Extend this file with additional tables as your product grows. * Columns use camelCase to match both database fields and generated types. */ export const users = mysqlTable("users", { /** * Surrogate primary key. Auto-incremented numeric value managed by the database. * Use this for relations between tables. */ id: int("id").autoincrement().primaryKey(), /** Manus OAuth identifier (openId) returned from the OAuth callback. Unique per user. */ openId: varchar("openId", { length: 64 }).notNull().unique(), name: text("name"), email: varchar("email", { length: 320 }), loginMethod: varchar("loginMethod", { length: 64 }), role: mysqlEnum("role", ["user", "admin"]).default("user").notNull(), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), lastSignedIn: timestamp("lastSignedIn").defaultNow().notNull(), }); export type User = typeof users.$inferSelect; export type InsertUser = typeof users.$inferInsert; /** * Agents — конфигурация и управление AI-агентами */ export const agents = mysqlTable( "agents", { id: int("id").autoincrement().primaryKey(), userId: int("userId").notNull(), // Владелец агента name: varchar("name", { length: 255 }).notNull(), description: text("description"), role: varchar("role", { length: 100 }).notNull(), // "developer", "researcher", "executor" // Модель LLM model: varchar("model", { length: 100 }).notNull(), provider: varchar("provider", { length: 50 }).notNull(), // Параметры LLM temperature: decimal("temperature", { precision: 3, scale: 2 }).default( "0.7" ), maxTokens: int("maxTokens").default(2048), topP: decimal("topP", { precision: 3, scale: 2 }).default("1.0"), frequencyPenalty: decimal("frequencyPenalty", { precision: 3, scale: 2, }).default("0.0"), presencePenalty: decimal("presencePenalty", { precision: 3, scale: 2, }).default("0.0"), // System Prompt systemPrompt: text("systemPrompt"), // Доступы и разрешения allowedTools: json("allowedTools").$type().default([]), allowedDomains: json("allowedDomains").$type().default([]), maxRequestsPerHour: int("maxRequestsPerHour").default(100), // Статус isActive: boolean("isActive").default(true), isPublic: boolean("isPublic").default(false), isSystem: boolean("isSystem").default(false), // Системный агент (нельзя удалить) isOrchestrator: boolean("isOrchestrator").default(false), // Главный оркестратор чата // Docker Swarm / Container fields (Phase A) serviceName: varchar("serviceName", { length: 100 }), // Docker Swarm service name: goclaw-agent-{id} servicePort: int("servicePort"), // HTTP API port inside overlay network (8001-8999) containerImage: varchar("containerImage", { length: 255 }).default( "goclaw-agent-worker:latest" ), // Docker image to run containerStatus: mysqlEnum("containerStatus", [ "stopped", "deploying", "running", "error", ]).default("stopped"), // Container lifecycle state // Метаданные tags: json("tags").$type().default([]), metadata: json("metadata").$type>().default({}), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }, table => ({ userIdIdx: index("agents_userId_idx").on(table.userId), modelIdx: index("agents_model_idx").on(table.model), }) ); export type Agent = typeof agents.$inferSelect; export type InsertAgent = typeof agents.$inferInsert; /** * Agent Metrics — метрики производительности агентов */ export const agentMetrics = mysqlTable( "agentMetrics", { id: int("id").autoincrement().primaryKey(), agentId: int("agentId").notNull(), // Информация о запросе requestId: varchar("requestId", { length: 64 }).notNull().unique(), userMessage: text("userMessage"), agentResponse: text("agentResponse"), // Токены inputTokens: int("inputTokens").default(0), outputTokens: int("outputTokens").default(0), totalTokens: int("totalTokens").default(0), // Время обработки processingTimeMs: int("processingTimeMs").notNull(), // Статус status: mysqlEnum("status", [ "success", "error", "timeout", "rate_limited", ]).notNull(), errorMessage: text("errorMessage"), // Инструменты toolsCalled: json("toolsCalled").$type().default([]), // Модель model: varchar("model", { length: 100 }), temperature: decimal("temperature", { precision: 3, scale: 2 }), createdAt: timestamp("createdAt").defaultNow().notNull(), }, table => ({ agentIdIdx: index("agentMetrics_agentId_idx").on(table.agentId), createdAtIdx: index("agentMetrics_createdAt_idx").on(table.createdAt), }) ); export type AgentMetric = typeof agentMetrics.$inferSelect; export type InsertAgentMetric = typeof agentMetrics.$inferInsert; /** * Agent History — полная история запросов */ export const agentHistory = mysqlTable( "agentHistory", { id: int("id").autoincrement().primaryKey(), agentId: int("agentId").notNull(), userMessage: text("userMessage").notNull(), agentResponse: text("agentResponse"), conversationId: varchar("conversationId", { length: 64 }), messageIndex: int("messageIndex"), status: mysqlEnum("status", ["pending", "success", "error"]).default( "pending" ), createdAt: timestamp("createdAt").defaultNow().notNull(), }, table => ({ agentIdIdx: index("agentHistory_agentId_idx").on(table.agentId), }) ); export type AgentHistory = typeof agentHistory.$inferSelect; export type InsertAgentHistory = typeof agentHistory.$inferInsert; /** * Agent Access Control — управление доступами */ export const agentAccessControl = mysqlTable( "agentAccessControl", { id: int("id").autoincrement().primaryKey(), agentId: int("agentId").notNull(), tool: varchar("tool", { length: 50 }).notNull(), isAllowed: boolean("isAllowed").default(true), maxExecutionsPerHour: int("maxExecutionsPerHour").default(100), timeoutSeconds: int("timeoutSeconds").default(30), allowedPatterns: json("allowedPatterns").$type().default([]), blockedPatterns: json("blockedPatterns").$type().default([]), createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }, table => ({ agentIdToolIdx: index("agentAccessControl_agentId_tool_idx").on( table.agentId, table.tool ), }) ); export type AgentAccessControl = typeof agentAccessControl.$inferSelect; export type InsertAgentAccessControl = typeof agentAccessControl.$inferInsert; /** * Tool Definitions — пользовательские инструменты, созданные Tool Builder Agent */ export const toolDefinitions = mysqlTable("toolDefinitions", { id: int("id").autoincrement().primaryKey(), toolId: varchar("toolId", { length: 100 }).notNull().unique(), name: varchar("name", { length: 255 }).notNull(), description: text("description").notNull(), category: varchar("category", { length: 50 }).notNull().default("custom"), dangerous: boolean("dangerous").default(false), parameters: json("parameters").$type< Record >(), implementation: text("implementation").notNull(), // JS код функции isActive: boolean("isActive").default(true), createdBy: int("createdBy"), // agentId или null createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); export type ToolDefinition = typeof toolDefinitions.$inferSelect; export type InsertToolDefinition = typeof toolDefinitions.$inferInsert; /** * Browser Sessions — активные сессии браузера для Browser Agent */ export const browserSessions = mysqlTable("browserSessions", { id: int("id").autoincrement().primaryKey(), sessionId: varchar("sessionId", { length: 64 }).notNull().unique(), agentId: int("agentId").notNull(), currentUrl: text("currentUrl"), title: text("title"), status: mysqlEnum("status", ["active", "idle", "closed", "error"]).default( "idle" ), screenshotUrl: text("screenshotUrl"), // S3 URL последнего скриншота lastActionAt: timestamp("lastActionAt").defaultNow(), createdAt: timestamp("createdAt").defaultNow().notNull(), closedAt: timestamp("closedAt"), }); export type BrowserSession = typeof browserSessions.$inferSelect; export type InsertBrowserSession = typeof browserSessions.$inferInsert; /** * Node Metrics — исторические метрики Docker-контейнеров/нод (сохраняется каждые 30s) */ export const nodeMetrics = mysqlTable( "nodeMetrics", { id: int("id").autoincrement().primaryKey(), containerId: varchar("containerId", { length: 64 }).notNull(), containerName: varchar("containerName", { length: 255 }).notNull(), cpuPercent: decimal("cpuPercent", { precision: 6, scale: 2 }) .notNull() .default("0.00"), memUsedMb: decimal("memUsedMb", { precision: 10, scale: 2 }) .notNull() .default("0.00"), memLimitMb: decimal("memLimitMb", { precision: 10, scale: 2 }) .notNull() .default("0.00"), status: varchar("status", { length: 32 }).notNull().default("running"), recordedAt: timestamp("recordedAt").defaultNow().notNull(), }, table => ({ containerIdIdx: index("nodeMetrics_containerId_idx").on(table.containerId), recordedAtIdx: index("nodeMetrics_recordedAt_idx").on(table.recordedAt), }) ); export type NodeMetric = typeof nodeMetrics.$inferSelect; export type InsertNodeMetric = typeof nodeMetrics.$inferInsert; /** * Tasks — задачи, создаваемые агентами для отслеживания работы */ export const tasks = mysqlTable( "tasks", { id: int("id").autoincrement().primaryKey(), agentId: int("agentId").notNull(), conversationId: varchar("conversationId", { length: 64 }), title: varchar("title", { length: 255 }).notNull(), description: text("description"), status: mysqlEnum("status", [ "pending", "in_progress", "completed", "failed", "blocked", ]) .default("pending") .notNull(), priority: mysqlEnum("priority", ["low", "medium", "high", "critical"]) .default("medium") .notNull(), dependsOn: json("dependsOn").$type().default([]), result: text("result"), errorMessage: text("errorMessage"), createdAt: timestamp("createdAt").defaultNow().notNull(), startedAt: timestamp("startedAt"), completedAt: timestamp("completedAt"), metadata: json("metadata").$type>().default({}), }, table => ({ agentIdIdx: index("tasks_agentId_idx").on(table.agentId), statusIdx: index("tasks_status_idx").on(table.status), conversationIdIdx: index("tasks_conversationId_idx").on( table.conversationId ), }) ); export type Task = typeof tasks.$inferSelect; export type InsertTask = typeof tasks.$inferInsert;