UNPKG

consortium

Version:

Remote control and session sharing CLI for AI coding agents

157 lines (152 loc) 4.17 kB
'use strict'; var net = require('net'); var crypto = require('crypto'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var net__namespace = /*#__PURE__*/_interopNamespaceDefault(net); 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__namespace.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 = crypto.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; } } };