UNPKG

@phren/cli

Version:

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

34 lines (33 loc) • 2.69 kB
import { continuationPlacement } from "./continuation-launch.js"; import { hookRequest } from "./client.js"; import { dispatchStatus } from "./dispatch.js"; import { optionalHookPeers, peerRequest } from "./peers.js"; import { BridgeError, object } from "./protocol.js"; /** Carry the original receipt id so its dispatcher can prove compatibility * even before its next poll observes the new session. Aliases have no authority. */ export async function preflightWorkerRelocation(dispatch, target) { const original = (await continuationPlacement(dispatch))?.workerDispatch ?? dispatch; const query = `/v1/dispatch/relocation?id=${encodeURIComponent(original)}&session=${encodeURIComponent(target.session)}`; const { peers } = await optionalHookPeers(); const probes = [ async () => ({ health: await hookRequest("/v1/health", undefined, undefined, 5_000), known: await hookRequest(query, undefined, undefined, 5_000) }), ...peers.map(peer => async () => ({ health: await peerRequest(peer, "/v1/health", undefined, 5_000), known: await peerRequest(peer, query, undefined, 5_000) })), ]; const answers = await Promise.all(probes.map(probe => probe().catch(() => undefined))); const ownsReceipt = (answer) => answer.known.known === true || answer.known.knownDispatch === true; const dispatchers = answers.filter(answer => answer && ownsReceipt(answer) && object(answer.health.capabilities).workerRelocation === 1); const incompatibleDispatcher = answers.some(answer => answer && ownsReceipt(answer) && object(answer.health.capabilities).workerRelocation !== 1); if (dispatchers.length !== 1 || incompatibleDispatcher) throw new BridgeError(409, "Update the dispatching computer's Phren Hook first"); return original; } export async function relocationSupport(id, session) { const receipts = await dispatchStatus(); const matched = receipts.filter(receipt => receipt.id === id || receipt.moves?.some(move => move.id === id)); const known = matched.some(receipt => receipt.target && "session" in receipt.target && receipt.target.session === session); // A source move is durable before a worker's receiving Hook may use its id. // This also repairs moves launched before workerDispatch was serialized. const moved = matched.length ? undefined : await (await import("./session-move.js")).readMove(id).catch(() => undefined); const knownDispatch = matched.length > 0 || !!moved?.dispatch && receipts.some(receipt => receipt.id === moved.dispatch) && moved.target && "session" in moved.target && moved.target.session === session; return { known, knownDispatch: !!knownDispatch, workerRelocation: 1 }; }