import { actor } from "rivetkit"; import { db } from "../db/client.js"; import { actors as actorsTable, events as eventsTable } from "../db/schema.js"; import { and, eq, desc } from "drizzle-orm"; type LogEntry = { ts: number; level: "info" | "warn" | "error"; msg: string; }; type SubAgentState = { parentUserId: string; type: string; status: "idle" | "running" | "done" | "error"; channelId: string; logs: LogEntry[]; }; const initialState: SubAgentState = { parentUserId: "", type: "generic", status: "idle", channelId: "", logs: [], }; // Sub-agent actor mainly exposes status + logs for the UI. The actual task // execution lives in sub-agent-runner.ts, invoked by the Grow Agent's tool // dispatch path (PRD ยง3.3). export const subAgent = actor({ state: initialState, actions: { init: async ( c, input: { parentUserId: string; type: string; channelId: string }, ) => { c.state.parentUserId = input.parentUserId; c.state.type = input.type; c.state.channelId = input.channelId; c.state.status = "idle"; }, appendLog: async (c, entry: LogEntry) => { c.state.logs.push(entry); c.broadcast("log", entry); }, setStatus: async (c, status: SubAgentState["status"]) => { c.state.status = status; c.broadcast("status", { status }); }, getLogs: async (c) => c.state.logs, getStatus: async (c) => c.state.status, // Pulls historical events from the DB so a returning user sees prior runs. getHistory: async (c, input: { subAgentId: string }) => { const rows = await db .select() .from(eventsTable) .where( and( eq(eventsTable.userId, c.state.parentUserId), eq(eventsTable.actorId, input.subAgentId), ), ) .orderBy(desc(eventsTable.createdAt)) .limit(50); return rows; }, getActorRow: async (c, input: { subAgentId: string }) => { const row = await db.query.actors.findFirst({ where: and( eq(actorsTable.userId, c.state.parentUserId), eq(actorsTable.actorId, input.subAgentId), ), }); return row; }, }, });