ligbox-ops-platform/projects/cloudflare-agents/onboard/src/onboard-maestro.ts
Ligbox Spec Hub c1881f58e6 chore: sync Console SSO, DNS viewer, specs e infra docs pendentes
Inclui console handoff Desk↔Console (Spec 019), melhorias DNS Viewer (037),
OpenPanel/Nextcloud/VM116 deploy notes, contracts stack e sidebar actualizado.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 20:10:17 +00:00

131 lines
4.8 KiB
TypeScript

/**
* 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<Response> {
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<OnboardState>("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<Response> {
return Response.json({ agent: "proj-mail", status: "delegate-to-maestro" });
}
}
export class CfZoneAgent extends DurableObject {
async fetch(request: Request): Promise<Response> {
return Response.json({ agent: "cf-zone", status: "delegate-to-maestro" });
}
}
export class CfHandoffAgent extends DurableObject {
async fetch(request: Request): Promise<Response> {
return Response.json({ agent: "cf-handoff", status: "delegate-to-maestro" });
}
}