@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
199 lines (198 loc) • 10.7 kB
JavaScript
import { createHash } from "node:crypto";
import { z } from "zod";
import { computerName } from "./computers.js";
import { BridgeError, object, provider, sessionId, targetSchema } from "./protocol.js";
const computerID = z.string().uuid();
const dispatchID = z.string().uuid();
const childID = z.string().regex(/^[a-f0-9]{32}$/);
export const dispatchParentSchema = z.object({
provider,
session: sessionId,
computer: computerID,
}).strict();
/** Parent metadata is optional as a pair. When present it must name this Hook
* and the exact live conversation target the caller supplied. */
export async function validateDispatchParent(input, localComputer, validateTarget) {
const hasParent = input.parent !== undefined, hasTarget = input.parentTarget !== undefined;
if (hasParent !== hasTarget)
throw new BridgeError(400, "Supply both parent and parentTarget, or omit both.");
if (!hasParent)
return {};
const parent = dispatchParentSchema.parse(input.parent);
const parentTarget = targetSchema.parse(input.parentTarget);
if (parent.computer !== computerID.parse(localComputer))
throw new BridgeError(403, "The dispatch parent belongs to a different computer.");
if (parent.provider !== parentTarget.source || parent.session !== parentTarget.session) {
throw new BridgeError(400, "The dispatch parent does not match parentTarget.");
}
await validateTarget(parentTarget);
return { parent, parentTarget };
}
const sameTarget = (left, right) => left.server === right.server && left.workspace === right.workspace && left.tab === right.tab
&& left.pane === right.pane && left.source === right.source && left.session === right.session;
function publicID(parent, computer, dispatch, descendant) {
return createHash("sha256").update(`${parent.provider}\0${parent.session}\0${computer}\0${dispatch}\0${descendant}`)
.digest("hex").slice(0, 32);
}
function publicLabel(value) {
if (typeof value !== "string" || value.length === 0 || value.length > 512)
return;
return /^(?:[/\\]|[A-Za-z]:[/\\]|~[/\\])/.test(value) ? "Agent" : value;
}
function sameParent(value, parent) {
const parsed = dispatchParentSchema.safeParse(value);
return parsed.success && parsed.data.provider === parent.provider && parsed.data.session === parent.session
&& parsed.data.computer === parent.computer;
}
function parentTargetMatches(value, parent) {
const parsed = targetSchema.safeParse(value);
return parsed.success && parsed.data.source === parent.provider && parsed.data.session === parent.session;
}
function directSnapshot(source) {
if (!source || typeof source !== "object" || Array.isArray(source) || source instanceof Map)
return;
const value = source;
return value.available !== undefined || value.state !== undefined || value.target !== undefined
|| value.computer !== undefined || value.agents !== undefined || value.children !== undefined ? value : undefined;
}
async function snapshotFor(source, receipt) {
try {
if (source === undefined)
return;
if (typeof source === "function")
return await source(receipt);
if (source instanceof Map)
return source.get(receipt.id);
if (Array.isArray(source))
return source.find(item => item.dispatchId === receipt.id);
const direct = directSnapshot(source);
if (direct)
return direct;
return Object.prototype.hasOwnProperty.call(source, receipt.id)
? source[receipt.id] : undefined;
}
catch {
return;
}
}
function snapshotMatches(snapshot, target, computer) {
if (snapshot.target !== undefined) {
const parsed = targetSchema.safeParse(snapshot.target);
if (!parsed.success || !sameTarget(parsed.data, target))
return false;
}
if (snapshot.computer !== undefined) {
const parsed = z.object({ id: computerID, name: computerName }).strict().safeParse(snapshot.computer);
if (!parsed.success || parsed.data.id !== computer.id)
return false;
}
return true;
}
function destination(raw, fallbackComputer, fallbackTarget) {
const suppliedComputer = raw.computer !== undefined, suppliedRemote = raw.remote !== undefined;
if (!suppliedComputer && !suppliedRemote) {
const id = childID.safeParse(raw.id);
return id.success ? { computer: fallbackComputer, remote: { target: fallbackTarget, child: id.data } } : undefined;
}
if (!suppliedComputer || !suppliedRemote)
return;
const computer = z.object({ id: computerID, name: computerName }).strict().safeParse(raw.computer);
const remoteRaw = object(raw.remote), target = targetSchema.safeParse(remoteRaw.target);
const child = remoteRaw.child === undefined ? undefined : childID.safeParse(remoteRaw.child);
if (!computer.success || !target.success || (child !== undefined && !child.success))
return;
return { computer: computer.data, remote: { target: target.data, ...(child?.success ? { child: child.data } : {}) } };
}
function projectAgents(values, parent, receipt, fallbackComputer, fallbackTarget, depth, state, lineage = "") {
if (depth > 4 || state.count >= 128)
return [];
const projected = [];
for (const value of values.slice(0, 128)) {
if (state.count >= 128)
break;
const raw = object(value), id = childID.safeParse(raw.id), source = provider.safeParse(raw.provider);
const path = publicLabel(raw.path);
const callId = typeof raw.callId === "string" && raw.callId.length > 0 && raw.callId.length <= 200 ? raw.callId : undefined;
const routed = destination(raw, fallbackComputer, fallbackTarget);
if (!id.success || !source.success || !path || !callId || !routed)
continue;
const descendant = lineage ? `${lineage}/${id.data}` : id.data;
const nestedSession = sessionId.safeParse(raw.session);
const cycleIdentity = nestedSession.success ? nestedSession.data
: routed.remote.child ?? routed.remote.target.session;
const cycleKey = `${routed.computer.id}\0${source.data}\0${cycleIdentity}`;
if (state.seen.has(cycleKey))
continue;
state.seen.add(cycleKey);
state.count++;
const childState = ["running", "completed", "failed", "unavailable"].includes(String(raw.state))
? raw.state : "unavailable";
const children = Array.isArray(raw.children)
? projectAgents(raw.children, parent, receipt, fallbackComputer, fallbackTarget, depth + 1, state, descendant) : [];
projected.push({
id: publicID(parent, routed.computer.id, receipt.id, descendant),
provider: source.data,
path,
callId,
state: raw.failed === true ? "failed" : childState,
...(typeof raw.finishedAt === "string" && Number.isFinite(Date.parse(raw.finishedAt)) ? { finishedAt: raw.finishedAt } : {}),
...(typeof raw.reason === "string" && raw.reason.length > 0 && raw.reason.length <= 500 ? { reason: raw.reason } : {}),
...(typeof raw.model === "string" && raw.model.length > 0 && raw.model.length <= 200 ? { model: raw.model } : {}),
...(typeof raw.worktreeName === "string" && raw.worktreeName.length > 0 && raw.worktreeName.length <= 200
&& !/[/\\]/.test(raw.worktreeName) ? { worktreeName: raw.worktreeName } : {}),
...(typeof raw.branch === "string" && raw.branch.length > 0 && raw.branch.length <= 200 ? { branch: raw.branch } : {}),
...(typeof object(raw.fanout).resumable === "boolean" ? { fanout: { resumable: object(raw.fanout).resumable } } : {}),
computer: routed.computer,
remote: routed.remote,
children,
});
}
return projected;
}
/** Project this conductor's durable outbound receipts into remote work-tree
* rows. Snapshot readers are GET-only adapters; missing or rejected snapshots
* keep the lead visible as unavailable. */
export async function remoteChildren(parentValue, receipts, remoteSnapshot) {
const parent = dispatchParentSchema.parse(parentValue);
const state = {
count: 0,
seen: new Set([`${parent.computer}\0${parent.provider}\0${parent.session}`]),
};
const children = [];
for (const receipt of receipts) {
if (state.count >= 128 || receipt.state === "failed" || !dispatchID.safeParse(receipt.id).success
|| !sameParent(receipt.parent, parent) || !parentTargetMatches(receipt.parentTarget, parent))
continue;
const storedComputer = z.object({ id: computerID, name: computerName }).strict().safeParse(receipt.remoteComputer);
const target = targetSchema.safeParse(receipt.target);
const id = computerID.safeParse(receipt.computerId ?? (storedComputer.success ? storedComputer.data.id : undefined));
const name = computerName.safeParse(storedComputer.success ? storedComputer.data.name : receipt.computer);
if (!target.success || !id.success || !name.success)
continue;
const cycleKey = `${id.data}\0${target.data.source}\0${target.data.session}`;
if (state.seen.has(cycleKey))
continue;
state.seen.add(cycleKey);
state.count++;
const computer = { id: id.data, name: name.data };
const snapshot = await snapshotFor(remoteSnapshot, receipt);
const available = snapshot !== undefined && snapshot.available !== false && snapshotMatches(snapshot, target.data, computer);
const completed = receipt.state === "completed" || receipt.status === "completed" || receipt.completedAt !== undefined
|| snapshot?.state === "completed";
const snapshotAgents = snapshot?.agents ?? snapshot?.children;
const projected = available && Array.isArray(snapshotAgents)
? projectAgents(snapshotAgents, parent, receipt, computer, target.data, 2, state) : [];
children.push({
id: publicID(parent, computer.id, receipt.id, "lead"),
provider: target.data.source,
path: receipt.label.slice(0, 200),
callId: `dispatch:${receipt.id}`,
state: completed ? "completed" : available ? "running" : "unavailable",
...(receipt.model !== undefined ? { model: receipt.model.slice(0, 200) } : {}),
computer,
remote: { target: target.data },
children: projected,
});
}
return children;
}