consortium
Version:
Remote control and session sharing CLI for AI coding agents
136 lines (134 loc) • 3.6 kB
JavaScript
import * as net from 'net';
import { randomUUID } from 'crypto';
const MAX_RETRIES = 5;
const RECONNECT_BACKOFF_MS = 1e3;
const REQUEST_TIMEOUT_MS = 5 * 60 * 1e3;
let socket = null;
let connecting = null;
let retries = 0;
const pending = /* @__PURE__ */ new Map();
let rxBuffer = "";
function envSocketPath() {
const p = process.env.PI_CONSORTIUM_PERMISSION_SOCKET;
return p && p.length > 0 ? p : void 0;
}
function failPending(reason) {
for (const [, p] of pending) {
clearTimeout(p.timer);
p.resolve({ decision: "deny", reason });
}
pending.clear();
}
function attachSocketHandlers(sock) {
sock.setEncoding("utf8");
sock.on("data", (chunk) => {
rxBuffer += chunk;
let idx = rxBuffer.indexOf("\n");
while (idx !== -1) {
const line = rxBuffer.slice(0, idx).trim();
rxBuffer = rxBuffer.slice(idx + 1);
idx = rxBuffer.indexOf("\n");
if (!line) continue;
try {
const frame = JSON.parse(line);
if (frame.kind !== "permission_response" || !frame.id) continue;
const p = pending.get(frame.id);
if (!p) continue;
pending.delete(frame.id);
clearTimeout(p.timer);
p.resolve({
decision: frame.decision === "allow" ? "allow" : "deny",
reason: frame.reason
});
} catch {
}
}
});
sock.on("error", () => {
});
sock.on("close", () => {
if (socket === sock) {
socket = null;
rxBuffer = "";
}
failPending("Permission bridge disconnected");
});
}
async function connect() {
if (socket && !socket.destroyed) return socket;
if (connecting) return connecting;
const target = envSocketPath();
if (!target) return null;
connecting = (async () => {
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
const sock = await new Promise((resolve, reject) => {
const s = net.createConnection(target);
s.once("connect", () => {
s.removeAllListeners("error");
resolve(s);
});
s.once("error", reject);
});
attachSocketHandlers(sock);
socket = sock;
retries = 0;
return sock;
} catch {
retries = attempt + 1;
if (attempt < MAX_RETRIES - 1) {
await new Promise((r) => setTimeout(r, RECONNECT_BACKOFF_MS));
}
}
}
return null;
})();
try {
return await connecting;
} finally {
connecting = null;
}
}
async function onToolRequest(req) {
if (!envSocketPath()) {
return { decision: "allow" };
}
const sock = await connect();
if (!sock) {
return { decision: "deny", reason: "Permission bridge unreachable" };
}
const id = randomUUID();
const frame = {
kind: "permission_request",
id,
sessionId: req.sessionId ?? "",
tool: req.tool,
arguments: req.arguments ?? {},
callId: req.callId ?? id
};
return new Promise((resolve) => {
const timer = setTimeout(() => {
if (pending.delete(id)) {
resolve({ decision: "deny", reason: "Permission request timed out" });
}
}, REQUEST_TIMEOUT_MS);
pending.set(id, { resolve, timer });
try {
sock.write(JSON.stringify(frame) + "\n");
} catch (err) {
pending.delete(id);
clearTimeout(timer);
resolve({
decision: "deny",
reason: `Permission bridge write failed: ${err.message}`
});
}
});
}
module.exports = {
onToolRequest,
// Surfaced for diagnostics; not part of Pi's contract.
_internals: { connect, get retries() {
return retries;
} }
};