UNPKG

@phren/cli

Version:

Knowledge layer for AI agents - CLI, MCP server, and data layer

107 lines (106 loc) • 5.41 kB
import { createHash } from "node:crypto"; import { readFile } from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; import { atomicInPrivateDir, BridgeError, bridgeRoot } from "./protocol.js"; import { terminalProvider } from "./terminal.js"; import { briefArrival, briefId, assertUnusedLaunchId } from "./launch-brief.js"; import { moveToSchema, projectName } from "./dispatch.js"; export const continuationLaunchSchema = z.object({ workerDispatch: briefId.optional(), worker: z.boolean().default(false), computerId: z.string().uuid(), project: projectName, to: moveToSchema.refine(to => to.computer === undefined, "A receiving launch is local only."), label: z.string().max(200), brief: z.object({ id: z.string().uuid(), text: z.string().min(1).max(32768) }).strict(), }).strict(); const queues = new Map(); const creating = new Map(); const fileFor = (id) => path.join(bridgeRoot(), "move-launches", `${z.string().uuid().parse(id)}.json`); /** A durable intent precedes creation. An interrupted intent is reconciled by * its brief arrival, never by creating another pane. */ export async function continuationPlacement(id, includeCreating = true) { let saved; try { saved = JSON.parse(await readFile(fileFor(id), "utf8")); } catch (error) { if (error.code === "ENOENT") return includeCreating ? creating.get(fileFor(id)) : undefined; throw error; } if (!saved.result) { const arrived = (await briefArrival(id))?.accepted?.target; if (arrived) { saved = { ...saved, state: "placed", result: { target: arrived, briefLaunched: true } }; await atomicInPrivateDir(fileFor(id), JSON.stringify(saved)); } } if (!saved.result && !queues.has(fileFor(id)) && saved.createdAt && Date.now() - saved.createdAt >= 10 * 60_000 && saved.server) { // A unique creation label covers a crash between create() and onPlaced(). // Failure to inspect the terminal keeps the placement uncertain. const panes = await terminalProvider().listPanes(saved.server); if (!panes.some(pane => pane.label === `move-${id}` || saved.paneTarget && pane.pane === saved.paneTarget.pane)) { saved = { ...saved, state: "not-placed" }; await atomicInPrivateDir(fileFor(id), JSON.stringify(saved)); } } return saved; } export async function launchContinuation(input, launch, authorization) { const data = continuationLaunchSchema.parse(input); const file = fileFor(data.brief.id); const hash = createHash("sha256").update(JSON.stringify(data)).digest("hex"); const pending = queues.get(file); if (pending) { await pending.catch(() => undefined); return launchContinuation(data, launch, authorization); } const metadata = { hash, worker: data.worker, ...(data.workerDispatch ? { workerDispatch: data.workerDispatch } : {}), createdAt: Date.now(), ...authorization }; creating.set(file, { ...metadata, state: "uncertain" }); const work = (async () => { const saved = await continuationPlacement(data.brief.id, false); if (saved) { if (JSON.stringify(saved.caller) !== JSON.stringify(authorization?.caller)) throw new BridgeError(403, "This peer is not the launch's recorded counterpart."); if (saved.hash !== hash) throw new BridgeError(409, "This move id already names a different launch."); if (saved.result) return saved.result; throw new BridgeError(409, "This move's launch is uncertain; inspect its existing pane or wait for its arrival. It will not be launched twice."); } await assertUnusedLaunchId(data.brief.id); await atomicInPrivateDir(file, JSON.stringify({ ...metadata, state: "uncertain" })); let paneTarget; const result = await launch(data, async (pane) => { paneTarget = pane; await atomicInPrivateDir(file, JSON.stringify({ ...metadata, state: "uncertain", paneTarget })); }); await atomicInPrivateDir(file, JSON.stringify({ ...metadata, state: "placed", paneTarget, result })); return result; })(); queues.set(file, work); try { return await work; } finally { queues.delete(file); creating.delete(file); } } /** An owner-admitted source move publishes one canonical intent privately. * The receiving Hook pulls it back over its own verified reverse enrollment. */ const intentFile = (id) => path.join(bridgeRoot(), "move-intents", `${z.string().uuid().parse(id)}.json`); export async function saveContinuationIntent(input) { const id = z.string().uuid().parse(input.brief.id); const previous = await readFile(intentFile(id), "utf8").catch(error => { if (error.code !== "ENOENT") throw error; return undefined; }); const encoded = JSON.stringify(input); if (previous && previous !== encoded) throw new BridgeError(409, "This move id already has a different launch intent."); await atomicInPrivateDir(intentFile(id), encoded); } export async function continuationIntent(id) { try { return JSON.parse(await readFile(intentFile(id), "utf8")); } catch { throw new BridgeError(404, "No owner-admitted continuation intent is recorded."); } }