UNPKG

@phren/cli

Version:

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

63 lines (62 loc) • 2.92 kB
import { requestPeer } from "./request-origin.js"; /** Sockets one identity may hold per client kind, and in all. */ export const SOCKETS_PER_KIND = 16; export const SOCKETS_PER_IDENTITY = 32; /** Every identity together; the backstop if identities themselves multiply. */ export const SOCKETS_TOTAL = 128; /** * Who a WebSocket belongs to, from what the Hook itself trusts rather than a * client hint: a peer key's gateway rebuilds its request with `x-phren-origin: * peer` and the peer's fingerprint, so that is its own identity; everything * else arrives on the owner's local socket or the owner's SSH pipe and is the * owner. `x-phren-client` only picks a sub-pool inside the identity. */ export function socketIdentity(request) { const peer = requestPeer(request); return peer ? `peer:${peer.fingerprint}` : "owner"; } export const socketKind = (request) => request.headers["x-phren-client"] === "desktop" ? "desktop" : "default"; /** Admits sockets oldest-first under the per-kind, per-identity and total caps. */ export class SocketBudget { limits; entries = new Set(); constructor(limits = { kind: SOCKETS_PER_KIND, identity: SOCKETS_PER_IDENTITY, total: SOCKETS_TOTAL }) { this.limits = limits; } /** Register a socket; returns the sockets to close to stay inside the caps. */ add(socket, identity, kind) { const entry = { socket, identity, kind }; this.entries.add(entry); const evicted = []; const drop = (match) => { const oldest = [...this.entries].find(match); if (!oldest) return; this.entries.delete(oldest); evicted.push(oldest.socket); }; while ([...this.entries].filter(e => e.identity === identity && e.kind === kind).length > this.limits.kind) drop(e => e.identity === identity && e.kind === kind); while ([...this.entries].filter(e => e.identity === identity).length > this.limits.identity) drop(e => e.identity === identity); while (this.entries.size > this.limits.total) { const counts = new Map(); for (const e of this.entries) counts.set(e.identity, (counts.get(e.identity) ?? 0) + 1); // Ties go against a peer identity, so forged peer budgets never push out the owner's own devices first. const [heaviest] = [...counts].sort((a, b) => b[1] - a[1] || Number(a[0] === "owner") - Number(b[0] === "owner"))[0]; drop(e => e.identity === heaviest); } return evicted; } remove(socket) { for (const e of this.entries) if (e.socket === socket) { this.entries.delete(e); return; } } count(identity) { return identity === undefined ? this.entries.size : [...this.entries].filter(e => e.identity === identity).length; } }