Merge PR #11: Register curator actor
Register curatorActor in the Rivet registry and route Curator APIs through the actor handle.
This commit is contained in:
@@ -7,6 +7,7 @@ import { memoryActor } from "./memory/index.js";
|
||||
import { growActor } from "./grow/index.js";
|
||||
import { userEventActor } from "./events/index.js";
|
||||
import { analyticsActor } from "./analytics/index.js";
|
||||
import { curatorActor } from "../v1/curator/curator-actor.js";
|
||||
import {
|
||||
careerTransitionMissionActor,
|
||||
interviewToOfferMissionActor,
|
||||
@@ -20,6 +21,7 @@ export const registry = setup({
|
||||
growActor,
|
||||
userEventActor,
|
||||
analyticsActor,
|
||||
curatorActor,
|
||||
conversationActor,
|
||||
memoryActor,
|
||||
interviewToOfferMissionActor,
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { Registry } from "../../actors/registry.js";
|
||||
import { getConversationModel } from "../../actors/conversation/agent.js";
|
||||
import { db } from "../../db/client.js";
|
||||
import { growConversationMessages, growEvents } from "../../db/schema.js";
|
||||
import { curatorActor } from "../curator/curator-actor.js";
|
||||
import { curatorService } from "../curator/curator-actor.js";
|
||||
import { curatorImprovementSignalSchema } from "../curator/curator-types.js";
|
||||
|
||||
let _client: Client<Registry> | null = null;
|
||||
@@ -57,7 +57,7 @@ export const analyticsTools = {
|
||||
apply_improvement_to_curator: tool({
|
||||
description: "Apply generated improvement signals to the curator.",
|
||||
inputSchema: z.object({ userId: z.string(), date: z.string(), signals: z.array(curatorImprovementSignalSchema) }),
|
||||
execute: async ({ userId, date, signals }) => curatorActor.applyImprovementSignals({ userId, date, signals }),
|
||||
execute: async ({ userId, date, signals }) => curatorService.applyImprovementSignals({ userId, date, signals }),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -103,7 +103,7 @@ export const v1AnalyticsActor = {
|
||||
},
|
||||
|
||||
async applyImprovementSignals(input: { userId: string; date: string; signals: z.infer<typeof curatorImprovementSignalSchema>[] }) {
|
||||
return curatorActor.applyImprovementSignals(input);
|
||||
return curatorService.applyImprovementSignals(input);
|
||||
},
|
||||
|
||||
async runNightly(input: { date: string; userId?: string }) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { actor } from "rivetkit";
|
||||
import { buildCuratorPlan, buildCuratorSprint, buildCuratorStreak, buildCuratorTasks, buildServiceCurationPreview, todayIsoDate } from "./curator-store.js";
|
||||
import { curatorPlanSchema, curatorSprintResponseSchema, type CuratorImprovementSignal } from "./curator-types.js";
|
||||
import { emitCuratorEvent } from "./curator-events.js";
|
||||
@@ -6,7 +7,22 @@ import { prepareHandoffForTask } from "./curator-tools.js";
|
||||
import type { CuratorIcpId } from "./curator-icp-playbooks.js";
|
||||
import { runCuratorOnboardingLoop } from "./curator-onboarding-loop.js";
|
||||
|
||||
export const curatorActor = {
|
||||
type CuratorActorState = {
|
||||
userId: string;
|
||||
planGenerations: number;
|
||||
sprintReads: number;
|
||||
taskCompletions: number;
|
||||
lastActionAt?: string;
|
||||
lastEventId?: string;
|
||||
};
|
||||
|
||||
function touch(c: { state: CuratorActorState }, input: { userId: string }) {
|
||||
if (c.state.userId && c.state.userId !== input.userId) throw new Error("curatorActor initialized for a different user");
|
||||
c.state.userId = input.userId;
|
||||
c.state.lastActionAt = new Date().toISOString();
|
||||
}
|
||||
|
||||
export const curatorService = {
|
||||
async generatePlanRange(input: { userId: string; startDate?: string; endDate?: string; goals?: string[]; forceRegenerate?: boolean }) {
|
||||
const startDate = input.startDate ?? todayIsoDate();
|
||||
const endDate = input.endDate ?? startDate;
|
||||
@@ -129,3 +145,77 @@ export const curatorActor = {
|
||||
return { tasks: sprint.todayTasks, streak: sprint.streak, sprint };
|
||||
},
|
||||
};
|
||||
|
||||
export const curatorActor = actor({
|
||||
options: { name: "Curator Actor", icon: "sparkles", noSleep: true, actionTimeout: 300_000 },
|
||||
state: {
|
||||
userId: "",
|
||||
planGenerations: 0,
|
||||
sprintReads: 0,
|
||||
taskCompletions: 0,
|
||||
} as CuratorActorState,
|
||||
actions: {
|
||||
generatePlanRange: async (c, input: Parameters<typeof curatorService.generatePlanRange>[0]) => {
|
||||
touch(c, input);
|
||||
c.state.planGenerations += 1;
|
||||
return curatorService.generatePlanRange(input);
|
||||
},
|
||||
getPlan: async (c, input: Parameters<typeof curatorService.getPlan>[0]) => {
|
||||
touch(c, input);
|
||||
return curatorService.getPlan(input);
|
||||
},
|
||||
previewCuration: async (c, input: Parameters<typeof curatorService.previewCuration>[0]) => {
|
||||
touch(c, input);
|
||||
return curatorService.previewCuration(input);
|
||||
},
|
||||
runOnboardingLoop: async (c, input: Parameters<typeof curatorService.runOnboardingLoop>[0]) => {
|
||||
touch(c, input);
|
||||
return curatorService.runOnboardingLoop(input);
|
||||
},
|
||||
getToday: async (c, input: Parameters<typeof curatorService.getToday>[0]) => {
|
||||
touch(c, input);
|
||||
c.state.sprintReads += 1;
|
||||
return curatorService.getToday(input);
|
||||
},
|
||||
getSprint: async (c, input: Parameters<typeof curatorService.getSprint>[0]) => {
|
||||
touch(c, input);
|
||||
c.state.sprintReads += 1;
|
||||
return curatorService.getSprint(input);
|
||||
},
|
||||
chat: async (c, input: Parameters<typeof curatorService.chat>[0]) => {
|
||||
touch(c, input);
|
||||
return curatorService.chat(input);
|
||||
},
|
||||
startTask: async (c, input: Parameters<typeof curatorService.startTask>[0]) => {
|
||||
touch(c, input);
|
||||
const result = await curatorService.startTask(input);
|
||||
c.state.lastEventId = result.eventId;
|
||||
return result;
|
||||
},
|
||||
prepareTaskHandoff: async (c, input: Parameters<typeof curatorService.prepareTaskHandoff>[0]) => {
|
||||
touch(c, input);
|
||||
return curatorService.prepareTaskHandoff(input);
|
||||
},
|
||||
completeTask: async (c, input: Parameters<typeof curatorService.completeTask>[0]) => {
|
||||
touch(c, input);
|
||||
const result = await curatorService.completeTask(input);
|
||||
c.state.taskCompletions += 1;
|
||||
c.state.lastEventId = result.eventId;
|
||||
return result;
|
||||
},
|
||||
recordServiceImpact: async (c, input: Parameters<typeof curatorService.recordServiceImpact>[0]) => {
|
||||
touch(c, input);
|
||||
c.state.lastEventId = input.eventId;
|
||||
return curatorService.recordServiceImpact(input);
|
||||
},
|
||||
applyImprovementSignals: async (c, input: Parameters<typeof curatorService.applyImprovementSignals>[0]) => {
|
||||
touch(c, input);
|
||||
return curatorService.applyImprovementSignals(input);
|
||||
},
|
||||
getState: async (c, input: Parameters<typeof curatorService.getState>[0]) => {
|
||||
touch(c, input);
|
||||
const state = await curatorService.getState(input);
|
||||
return { ...state, actorState: c.state };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
import { Hono } from "hono";
|
||||
import { z } from "zod";
|
||||
import { createClient, type Client } from "rivetkit/client";
|
||||
import { requireUser, type AuthContext } from "../../auth/clerk.js";
|
||||
import { curatorActor } from "./curator-actor.js";
|
||||
import { config } from "../../config.js";
|
||||
import type { Registry } from "../../actors/registry.js";
|
||||
|
||||
let _client: Client<Registry> | null = null;
|
||||
function getClient(): Client<Registry> {
|
||||
return (_client ??= createClient<Registry>(config.rivetClientEndpoint));
|
||||
}
|
||||
|
||||
function getCuratorActor(userId: string) {
|
||||
return getClient().curatorActor.getOrCreate(["user", userId]);
|
||||
}
|
||||
|
||||
const chatSchema = z.object({
|
||||
conversationId: z.string().optional(),
|
||||
@@ -31,12 +42,12 @@ export function v1CuratorRoutes() {
|
||||
goals: z.array(z.string()).optional(),
|
||||
forceRegenerate: z.boolean().optional(),
|
||||
}).parse(await c.req.json().catch(() => ({})));
|
||||
return c.json(await curatorActor.generatePlanRange({ userId, ...body }));
|
||||
return c.json(await getCuratorActor(userId).generatePlanRange({ userId, ...body }));
|
||||
});
|
||||
|
||||
app.get("/plan", async (c) => {
|
||||
const userId = c.get("userId");
|
||||
return c.json(await curatorActor.getPlan({
|
||||
return c.json(await getCuratorActor(userId).getPlan({
|
||||
userId,
|
||||
startDate: c.req.query("startDate"),
|
||||
endDate: c.req.query("endDate"),
|
||||
@@ -45,18 +56,18 @@ export function v1CuratorRoutes() {
|
||||
|
||||
app.get("/today", async (c) => {
|
||||
const userId = c.get("userId");
|
||||
return c.json(await curatorActor.getToday({ userId, date: c.req.query("date") }));
|
||||
return c.json(await getCuratorActor(userId).getToday({ userId, date: c.req.query("date") }));
|
||||
});
|
||||
|
||||
app.get("/sprint", async (c) => {
|
||||
const userId = c.get("userId");
|
||||
return c.json(await curatorActor.getSprint({ userId, date: c.req.query("date") }));
|
||||
return c.json(await getCuratorActor(userId).getSprint({ userId, date: c.req.query("date") }));
|
||||
});
|
||||
|
||||
app.post("/curation/preview", async (c) => {
|
||||
const userId = c.get("userId");
|
||||
const body = curationPreviewSchema.parse(await c.req.json().catch(() => ({})));
|
||||
return c.json(await curatorActor.previewCuration({ userId, ...body }));
|
||||
return c.json(await getCuratorActor(userId).previewCuration({ userId, ...body }));
|
||||
});
|
||||
|
||||
app.post("/onboarding/run", async (c) => {
|
||||
@@ -64,40 +75,40 @@ export function v1CuratorRoutes() {
|
||||
const body = z.object({
|
||||
completedAt: z.string().optional(),
|
||||
}).parse(await c.req.json().catch(() => ({})));
|
||||
return c.json(await curatorActor.runOnboardingLoop({ userId, ...body }));
|
||||
return c.json(await getCuratorActor(userId).runOnboardingLoop({ userId, ...body }));
|
||||
});
|
||||
|
||||
app.post("/chat", async (c) => {
|
||||
const userId = c.get("userId");
|
||||
const body = chatSchema.parse(await c.req.json());
|
||||
return c.json(await curatorActor.chat({ userId, ...body }));
|
||||
return c.json(await getCuratorActor(userId).chat({ userId, ...body }));
|
||||
});
|
||||
|
||||
app.post("/tasks/:taskId/start", async (c) => {
|
||||
const userId = c.get("userId");
|
||||
return c.json(await curatorActor.startTask({ userId, taskId: c.req.param("taskId"), date: c.req.query("date") }));
|
||||
return c.json(await getCuratorActor(userId).startTask({ userId, taskId: c.req.param("taskId"), date: c.req.query("date") }));
|
||||
});
|
||||
|
||||
app.post("/tasks/:taskId/handoff", async (c) => {
|
||||
const userId = c.get("userId");
|
||||
return c.json(await curatorActor.prepareTaskHandoff({ userId, taskId: c.req.param("taskId"), date: c.req.query("date") }));
|
||||
return c.json(await getCuratorActor(userId).prepareTaskHandoff({ userId, taskId: c.req.param("taskId"), date: c.req.query("date") }));
|
||||
});
|
||||
|
||||
app.post("/tasks/:taskId/complete", async (c) => {
|
||||
const userId = c.get("userId");
|
||||
const body = z.object({ reason: z.string().optional() }).parse(await c.req.json().catch(() => ({})));
|
||||
return c.json(await curatorActor.completeTask({ userId, taskId: c.req.param("taskId"), date: c.req.query("date"), reason: body.reason }));
|
||||
return c.json(await getCuratorActor(userId).completeTask({ userId, taskId: c.req.param("taskId"), date: c.req.query("date"), reason: body.reason }));
|
||||
});
|
||||
|
||||
app.post("/events/service-impact", async (c) => {
|
||||
const userId = c.get("userId");
|
||||
const body = z.object({ eventId: z.string() }).parse(await c.req.json());
|
||||
return c.json(await curatorActor.recordServiceImpact({ userId, eventId: body.eventId }));
|
||||
return c.json(await getCuratorActor(userId).recordServiceImpact({ userId, eventId: body.eventId }));
|
||||
});
|
||||
|
||||
app.get("/state", async (c) => {
|
||||
const userId = c.get("userId");
|
||||
return c.json(await curatorActor.getState({ userId }));
|
||||
return c.json(await getCuratorActor(userId).getState({ userId }));
|
||||
});
|
||||
|
||||
return app;
|
||||
|
||||
Reference in New Issue
Block a user