/** * Spec 037 — OnboardMaestro (Fase B) * Durable Object: orquestra passos; delega agentes especializados (Fase C). */ import { DurableObject } from "cloudflare:workers"; import { vm112Get, vm112Post, type Vm112Config } from "./vm112-client"; export type OnboardState = { sessionId: string; domain: string; step: string; ligboxProjectEmail?: string; projectId?: string; dnsMode?: string; awaitingHuman?: string | null; history: string[]; }; export class OnboardMaestro extends DurableObject { private state: OnboardState | null = null; private cfg(): Vm112Config { const env = this.env as { VM112_API_BASE: string }; if (!this.state?.sessionId) throw new Error("session not initialized"); return { baseUrl: env.VM112_API_BASE, sessionId: this.state.sessionId }; } private log(msg: string) { if (!this.state) return; this.state.history.push(`${new Date().toISOString()} ${msg}`); if (this.state.history.length > 100) this.state.history.shift(); } async fetch(request: Request): Promise { const url = new URL(request.url); const path = url.pathname; if (path === "/init" && request.method === "POST") { const body = (await request.json()) as { sessionId: string; domain: string }; this.state = { sessionId: body.sessionId, domain: body.domain.toLowerCase(), step: "init", history: [], awaitingHuman: null, }; await this.ctx.storage.put("state", this.state); this.log("initialized"); return Response.json({ ok: true, state: this.state }); } const stored = (await this.ctx.storage.get("state")) || this.state; if (!stored) return Response.json({ error: "not initialized" }, { status: 400 }); this.state = stored; if (path === "/state") { return Response.json({ state: this.state }); } if (path === "/run/proj-mail" && request.method === "POST") { const data = await vm112Post(this.cfg(), "/onboarding/project/provision-email", { domain: this.state.domain, }); this.state.ligboxProjectEmail = data.ligbox_project_email as string; this.state.projectId = data.project_id as string; this.state.step = "proj_mail_done"; this.state.awaitingHuman = "reveal_password"; await this.ctx.storage.put("state", this.state); this.log(`proj-mail ${this.state.ligboxProjectEmail}`); return Response.json({ ok: true, data, state: this.state }); } if (path === "/run/cf-zone" && request.method === "POST") { const prov = await vm112Post(this.cfg(), "/onboarding/dns/cloudflare/provision-zone", { domain: this.state.domain, }); const apply = await vm112Post(this.cfg(), "/onboarding/dns/cloudflare/apply", { domain: this.state.domain, zone_id: prov.zone_id, }); const verify = await vm112Get(this.cfg(), `/onboarding/dns/verify/${this.state.domain}`); this.state.dnsMode = (apply.dns_mode as string) || "ligbox_cf"; this.state.step = "cf_zone_done"; await this.ctx.storage.put("state", this.state); this.log("cf-zone apply+verify"); return Response.json({ ok: true, prov, apply, verify, state: this.state }); } if (path === "/human/ack" && request.method === "POST") { const body = (await request.json()) as { action: string }; if (this.state.awaitingHuman === body.action) { this.state.awaitingHuman = null; await this.ctx.storage.put("state", this.state); } return Response.json({ ok: true, state: this.state }); } if (path === "/run/handoff" && request.method === "POST") { const body = (await request.json()) as { manager_email: string }; const data = await vm112Post(this.cfg(), "/onboarding/dns/cloudflare/handoff-manager", { domain: this.state.domain, manager_email: body.manager_email, }); this.state.step = "handoff_done"; await this.ctx.storage.put("state", this.state); this.log(`handoff ${body.manager_email}`); return Response.json({ ok: true, data, state: this.state }); } return Response.json({ error: "not found" }, { status: 404 }); } } /** Fase C — agentes especializados (stubs; lógica no Maestro até split) */ export class ProjMailAgent extends DurableObject { async fetch(request: Request): Promise { return Response.json({ agent: "proj-mail", status: "delegate-to-maestro" }); } } export class CfZoneAgent extends DurableObject { async fetch(request: Request): Promise { return Response.json({ agent: "cf-zone", status: "delegate-to-maestro" }); } } export class CfHandoffAgent extends DurableObject { async fetch(request: Request): Promise { return Response.json({ agent: "cf-handoff", status: "delegate-to-maestro" }); } }