UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

295 lines (294 loc) • 14.1 kB
import { lstat } from "node:fs/promises"; import { isAbsolute, dirname, resolve as resolvePath } from "node:path"; import { formatPeerNameRef, mintPeerRef, normalizePeerName, parsePeerNameRef, PEER_REF_RE } from "./cross-session-ref.js"; import { inlineUntrusted } from "../core/untrusted-text.js"; import { posixModeBitsMeaningful } from "../core/posix-mode-face.js"; export const PEER_SESSION_RECORD_SCHEMA_VERSION = 1; export const PEER_SESSION_RECORD_MAX_BYTES = 262_144; export const PEER_PRESENCE_MAX_CHARS = 120; export function peerPresenceLabelValid(v) { return v === undefined || (typeof v === "string" && v.length <= PEER_PRESENCE_MAX_CHARS); } export function assertPeerPresenceValue(v) { const bad = v === undefined ? undefined : v === null || typeof v !== "object" || Array.isArray(v) ? "peerPresence" : ["agent", "intent"].find((k) => !peerPresenceLabelValid(v[k])); if (bad === undefined) return; const e = new Error(`TaskSpec.${bad === "peerPresence" ? bad : `peerPresence.${bad}`} must be ${bad === "peerPresence" ? "a plain { agent?, intent? } object" : `a string of at most ${PEER_PRESENCE_MAX_CHARS} characters`} — the peer directory refuses such a row whole, so the run would advertise a presence no listing can show`); e.code = "config.peer_presence_invalid"; throw e; } export const SESSION_BOX_PREFIX = "session."; const SESSION_ID_RE = /^[A-Za-z0-9_-]{1,80}$/; export const PEER_SESSION_ID_GRAMMAR = "^[A-Za-z0-9_-]{1,80}$"; export function isPeerSessionId(sessionId) { return SESSION_ID_RE.test(sessionId); } const SESSION_ADDRESS_RE = /^session\.([A-Za-z0-9_-]{1,80})$/i; export function peerSessionBoxHandle(sessionId) { if (!SESSION_ID_RE.test(sessionId)) throw new Error(`peerSessionBoxHandle: session id ${JSON.stringify(sessionId)} is outside the address grammar ^[A-Za-z0-9_-]{1,80}$`); return `${SESSION_BOX_PREFIX}${sessionId.toLowerCase()}`; } export function parsePeerSessionAddress(text) { const m = SESSION_ADDRESS_RE.exec(text.trim()); return m === null ? undefined : m[1].toLowerCase(); } function optNumber(v) { return v === undefined || (typeof v === "number" && Number.isFinite(v)); } function optString(v) { return v === undefined || typeof v === "string"; } function optStringArray(v) { return v === undefined || (Array.isArray(v) && v.every((x) => typeof x === "string")); } export function readPeerSessionRecord(raw) { if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return { ok: false, reason: "not_an_object" }; const r = raw; if (typeof r["schemaVersion"] !== "number" || !Number.isInteger(r["schemaVersion"]) || r["schemaVersion"] < 1) return { ok: false, reason: "schema_missing" }; if (r["schemaVersion"] > PEER_SESSION_RECORD_SCHEMA_VERSION) return { ok: false, reason: "schema_newer" }; if (typeof r["sessionId"] !== "string" || !SESSION_ID_RE.test(r["sessionId"])) return { ok: false, reason: "session_id_invalid" }; if (typeof r["scope"] !== "string" || r["scope"] === "") return { ok: false, reason: "scope_invalid" }; if (typeof r["name"] !== "string" || normalizePeerName(r["name"]) === "") return { ok: false, reason: "name_invalid" }; const liveness = r["liveness"]; if (liveness !== "live" && liveness !== "dead" && liveness !== "deleted") return { ok: false, reason: "liveness_invalid" }; const bad = (field) => ({ ok: false, reason: "field_invalid", field }); if (!optNumber(r["startedAt"]) || r["startedAt"] === undefined) return bad("startedAt"); if (!optNumber(r["updatedAt"]) || r["updatedAt"] === undefined) return bad("updatedAt"); if (!optNumber(r["pid"])) return bad("pid"); if (!optNumber(r["procStartMs"])) return bad("procStartMs"); if (!optNumber(r["diedAt"])) return bad("diedAt"); if (!optNumber(r["peerProtocol"])) return bad("peerProtocol"); if (!optString(r["cwd"])) return bad("cwd"); if (!peerPresenceLabelValid(r["agent"])) return bad("agent"); if (!peerPresenceLabelValid(r["intent"])) return bad("intent"); const agent = typeof r["agent"] === "string" && r["agent"].trim() !== "" ? r["agent"] : undefined; const intent = typeof r["intent"] === "string" && r["intent"].trim() !== "" ? r["intent"] : undefined; if (!optString(r["entrypoint"])) return bad("entrypoint"); if (!optString(r["sockPath"])) return bad("sockPath"); if (!optStringArray(r["formerNames"])) return bad("formerNames"); if (!optStringArray(r["peerFeatures"])) return bad("peerFeatures"); const nameSource = r["nameSource"]; if (nameSource !== undefined && nameSource !== "auto" && nameSource !== "flag" && nameSource !== "rename") return bad("nameSource"); const tempo = r["tempo"]; if (tempo !== undefined && tempo !== "active" && tempo !== "idle" && tempo !== "blocked") return bad("tempo"); const inboundPosture = r["inboundPosture"]; if (inboundPosture !== undefined && inboundPosture !== "available" && inboundPosture !== "unavailable") return bad("inboundPosture"); const record = { schemaVersion: r["schemaVersion"], sessionId: r["sessionId"], scope: r["scope"], name: r["name"], ...(nameSource !== undefined ? { nameSource } : {}), ...(r["formerNames"] !== undefined ? { formerNames: [...r["formerNames"]] } : {}), ...(r["pid"] !== undefined ? { pid: r["pid"] } : {}), ...(r["procStartMs"] !== undefined ? { procStartMs: r["procStartMs"] } : {}), ...(r["cwd"] !== undefined ? { cwd: r["cwd"] } : {}), ...(agent !== undefined ? { agent } : {}), ...(intent !== undefined ? { intent } : {}), startedAt: r["startedAt"], updatedAt: r["updatedAt"], liveness, ...(r["diedAt"] !== undefined ? { diedAt: r["diedAt"] } : {}), ...(tempo !== undefined ? { tempo } : {}), ...(r["entrypoint"] !== undefined ? { entrypoint: r["entrypoint"] } : {}), ...(r["peerProtocol"] !== undefined ? { peerProtocol: r["peerProtocol"] } : {}), ...(r["peerFeatures"] !== undefined ? { peerFeatures: [...r["peerFeatures"]] } : {}), ...(r["sockPath"] !== undefined ? { sockPath: r["sockPath"] } : {}), ...(inboundPosture !== undefined ? { inboundPosture } : {}), }; return { ok: true, record }; } export const defaultPeerLivenessProbe = { processExists(pid) { try { process.kill(pid, 0); return true; } catch (e) { return e.code === "EPERM"; } }, }; export function isPeerSessionProcessAlive(record, probe = defaultPeerLivenessProbe) { if (record.liveness !== "live") return false; if (record.pid === undefined) return true; if (!probe.processExists(record.pid)) return false; if (record.procStartMs !== undefined && probe.processStartMs !== undefined) { const started = probe.processStartMs(record.pid); if (started !== undefined && Math.abs(started - record.procStartMs) > 2_000) return false; } return true; } export function mintPeerSessionCandidates(records, self) { const entries = records.map((record) => ({ kind: "session", id: record.sessionId.toLowerCase(), record })); const selfEntry = self !== undefined && !records.some((r) => r.sessionId.toLowerCase() === self.sessionId.toLowerCase()) ? { kind: "session", id: self.sessionId.toLowerCase() } : undefined; return mintPeerRef(entries, selfEntry).map((e) => ({ record: e.record, ref: e.ref })); } function describeCandidate(c) { const state = c.record.liveness === "live" ? "" : c.record.liveness === "dead" ? " (offline)" : " (deleted)"; return `${formatPeerNameRef(inlineUntrusted(c.record.name, 64), c.ref)}${state} → ${peerSessionBoxHandle(c.record.sessionId)}`; } export function resolvePeerSessions(records, to, self) { const candidates = mintPeerSessionCandidates(records, self); const selfId = self?.sessionId.toLowerCase(); const settle = (c, rung) => { if (selfId !== undefined && c.record.sessionId.toLowerCase() === selfId) return { status: "self" }; if (c.record.liveness === "deleted") return { status: "deleted", candidate: c }; return { status: "found", candidate: c, rung }; }; const address = parsePeerSessionAddress(to); if (address !== undefined) { if (selfId !== undefined && address === selfId) return { status: "self" }; const hit = candidates.find((c) => c.record.sessionId.toLowerCase() === address); return hit === undefined ? { status: "not_found" } : settle(hit, "address"); } const nameRef = parsePeerNameRef(to); const rawName = nameRef?.name ?? to; const key = normalizePeerName(rawName); if (key === "") return { status: "not_found" }; const byName = candidates.filter((c) => normalizePeerName(c.record.name) === key); if (nameRef !== undefined) { const hit = byName.find((c) => c.ref === nameRef.ref) ?? (PEER_REF_RE.test(nameRef.ref) ? candidates.find((c) => c.ref === nameRef.ref && normalizePeerName(c.record.name) === key) : undefined); if (hit === undefined) return { status: "stale_ref", ref: nameRef.ref }; return settle(hit, "ref"); } if (byName.length === 0) return { status: "not_found" }; if (byName.length === 1) return settle(byName[0], "name"); const sorted = [...byName].sort((a, b) => (a.record.liveness === b.record.liveness ? b.record.updatedAt - a.record.updatedAt : a.record.liveness === "live" ? -1 : 1)); return { status: "ambiguous", candidates: sorted, message: `"${inlineUntrusted(rawName, 64)}" matches ${sorted.length} sessions — re-send with the name and ref of the one you mean: ${sorted.map(describeCandidate).join("; ")}`, }; } export function createInMemoryPeerDirectory() { const rows = new Map(); const key = (scope, sessionId) => JSON.stringify([scope, sessionId.toLowerCase()]); return { listPeerSessions(access) { return [...rows.values()].filter((r) => r.scope === access.scope).map((r) => ({ ...r })); }, upsert(record) { const read = readPeerSessionRecord(record); if (!read.ok) throw new Error(`InMemoryPeerDirectory.upsert: refused row (${read.reason}${read.field !== undefined ? `: ${read.field}` : ""})`); rows.set(key(record.scope, record.sessionId), read.record); }, markDead(scope, sessionId, diedAt = Date.now()) { const r = rows.get(key(scope, sessionId)); if (r === undefined || r.liveness !== "live") return false; rows.set(key(scope, sessionId), { ...r, liveness: "dead", diedAt, updatedAt: diedAt }); return true; }, markDeleted(scope, sessionId, at = Date.now()) { const r = rows.get(key(scope, sessionId)); if (r === undefined) return false; rows.set(key(scope, sessionId), { ...r, liveness: "deleted", diedAt: r.diedAt ?? at, updatedAt: at }); return true; }, restorePeerSession(record) { const k = key(record.scope, record.sessionId); if (rows.has(k)) return false; const read = readPeerSessionRecord(record); if (!read.ok) return false; rows.set(k, read.record); return true; }, async sweep(scope, boxEmpty) { const swept = []; for (const [k, r] of [...rows.entries()]) { if (r.scope !== scope || r.liveness === "live") continue; if (await boxEmpty(r.sessionId)) { if (rows.get(k) !== r) continue; rows.delete(k); swept.push(r.sessionId); } } return swept; }, }; } export async function vetPeerRegistryDirectory(dir, opts) { if (!isAbsolute(dir)) return { ok: false, code: "not_absolute", path: dir }; const uid = opts?.uid ?? (typeof process.getuid === "function" ? process.getuid() : undefined); const judgeModeBits = posixModeBitsMeaningful(opts?.platform); const stopAt = opts?.stopAt !== undefined ? resolvePath(opts.stopAt) : undefined; let cursor = resolvePath(dir); for (;;) { let st; try { st = await lstat(cursor); } catch { return { ok: false, code: "missing", path: cursor }; } if (st.isSymbolicLink()) return { ok: false, code: "symlink", path: cursor }; if (!st.isDirectory()) return { ok: false, code: "not_directory", path: cursor }; const parent = dirname(cursor); const isRoot = parent === cursor || (stopAt !== undefined && cursor === stopAt); if (!isRoot) { if (uid !== undefined && st.uid !== uid) return { ok: false, code: "foreign_owner", path: cursor }; if (judgeModeBits && (st.mode & 0o022) !== 0) return { ok: false, code: "group_or_world_writable", path: cursor }; } if (isRoot) return { ok: true }; cursor = parent; } } export function judgePeerRecordFile(stat, selfUid, platform) { if (!stat.isFile) return { ok: false, code: "not_regular" }; if (selfUid !== undefined && stat.uid !== undefined && stat.uid !== selfUid) return { ok: false, code: "foreign_owner" }; if (posixModeBitsMeaningful(platform) && (stat.mode & 0o022) !== 0) return { ok: false, code: "group_or_world_writable" }; if (stat.size > PEER_SESSION_RECORD_MAX_BYTES) return { ok: false, code: "too_large" }; return { ok: true }; }