Brings the backend from a scaffold to a working end-to-end MVP — real auth, persistent actor registry, Anthropic tool-use loop in the Grow Agent, and per-user Gitea+OpenCode provisioning. Also adds the client-facing architecture diagram under docs/architecture.html.
84 lines
2.2 KiB
TypeScript
84 lines
2.2 KiB
TypeScript
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;
|
|
},
|
|
},
|
|
});
|