consortium
Version:
Remote control and session sharing CLI for AI coding agents
435 lines (427 loc) • 17.9 kB
JavaScript
;
var axios = require('axios');
var node_crypto = require('node:crypto');
var index = require('./index-BMIckAk5.cjs');
var persistence = require('./types-B_i6lpTn.cjs');
var createSessionMetadata = require('./createSessionMetadata-CVgp25Mn.cjs');
var setupOfflineReconnection = require('./setupOfflineReconnection-CY4q78_S.cjs');
var node_module = require('node:module');
var fs = require('node:fs');
var node_child_process = require('node:child_process');
var path = require('node:path');
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
const SUPPORTED_PLATFORMS = /* @__PURE__ */ new Set(["darwin-arm64", "darwin-x64", "linux-x64", "linux-arm64"]);
const DEFAULT_INSTALLED_PKG = "consortium";
function detectInstalledPackageName(binaryPath) {
try {
const parts = binaryPath.split(path.sep);
for (let i = parts.length - 1; i >= 2; i--) {
if (parts[i] === "node_modules" && parts[i - 2] === "node_modules") {
const candidate = parts[i - 1];
if (candidate && !candidate.startsWith("@")) {
return candidate;
}
}
}
let dir = path.dirname(binaryPath);
for (let depth = 0; depth < 8; depth++) {
const pj = path.join(dir, "..", "..", "package.json");
if (fs.existsSync(pj)) {
const parsed = JSON.parse(fs.readFileSync(pj, "utf8"));
if (parsed.name) return parsed.name;
}
const next = path.dirname(dir);
if (next === dir) break;
dir = next;
}
} catch {
}
return DEFAULT_INSTALLED_PKG;
}
function allChannelsReinstallHint(pkgName) {
return `Reinstall the channel you originally installed:
npm install -g consortium@latest # stable
npm install -g canary-dolphin-swimsuit@latest # canary
npm install -g dev-hummingbird-sneakers@latest # dev
# or force the missing platform package directly:
npm install -g ${pkgName}`;
}
let cachedBinary = null;
const probeCache = /* @__PURE__ */ new Map();
function currentPlatform() {
return `${process.platform}-${process.arch}`;
}
function resolveConsortiumCodeBinarySync() {
if (cachedBinary) return cachedBinary;
const platform = currentPlatform();
if (!SUPPORTED_PLATFORMS.has(platform)) {
throw new Error(
`Consortium Code is not yet built for ${platform}.
Supported: ${[...SUPPORTED_PLATFORMS].join(", ")}.
Ping #platform-support if you need this one added.`
);
}
const pkgName = `consortium-code-${platform}`;
const specifier = `${pkgName}/bin/consortium-code`;
const req = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cliRouting-BIEQ5EVs.cjs', document.baseURI).href)));
let binaryPath;
try {
binaryPath = req.resolve(specifier);
} catch (err) {
const fromSource = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cliRouting-BIEQ5EVs.cjs', document.baseURI).href)).includes("/packages/consortium-cli/src/");
const hint = fromSource ? `Running from source tree \u2014 run \`yarn install\` at the monorepo root to pull the platform binary.` : `Optional dependency install likely failed during \`npm install -g\`.
` + allChannelsReinstallHint(pkgName);
throw new Error(
`Consortium Code binary (${pkgName}) is not installed.
${hint}
Underlying resolver error: ${err instanceof Error ? err.message : String(err)}`
);
}
if (!fs.existsSync(binaryPath)) {
const installedPkg = detectInstalledPackageName(binaryPath);
throw new Error(
`Resolved ${pkgName} but ${binaryPath} does not exist on disk.
Reinstall: npm install -g ${installedPkg}@latest`
);
}
try {
fs.chmodSync(binaryPath, 493);
} catch {
}
try {
const pkgDir = path.dirname(path.dirname(binaryPath));
chmodEveryBinFileUnder(pkgDir);
} catch (err) {
persistence.logger.debug(`[binaryResolution] bin/ chmod sweep failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
}
persistence.logger.debug(`[binaryResolution] Resolved ${pkgName} -> ${binaryPath}`);
cachedBinary = binaryPath;
return binaryPath;
}
function chmodEveryBinFileUnder(root, depth = 0) {
if (depth > 6) return;
let entries;
try {
entries = fs.readdirSync(root);
} catch {
return;
}
for (const entry of entries) {
const full = path.join(root, entry);
let entryStat;
try {
entryStat = fs.statSync(full);
} catch {
continue;
}
if (entryStat.isDirectory()) {
if (entry === "bin") {
chmodAllFilesIn(full);
}
chmodEveryBinFileUnder(full, depth + 1);
}
}
}
function chmodAllFilesIn(binDir) {
let entries;
try {
entries = fs.readdirSync(binDir);
} catch {
return;
}
for (const entry of entries) {
const full = path.join(binDir, entry);
try {
const st = fs.statSync(full);
if (st.isFile()) {
fs.chmodSync(full, 493);
} else if (st.isDirectory()) {
chmodAllFilesIn(full);
}
} catch {
}
}
}
async function resolveConsortiumCodeBinary() {
const binaryPath = resolveConsortiumCodeBinarySync();
if (process.env.CONSORTIUM_SKIP_BINARY_PROBE === "1") {
persistence.logger.debug("[binaryResolution] Probe skipped via CONSORTIUM_SKIP_BINARY_PROBE=1");
return binaryPath;
}
const result = await probeBinaryHealthy(binaryPath);
if (result.ok) return binaryPath;
if (result.classification === "slow" || result.classification === "empty") {
persistence.logger.warn(
`[binaryResolution] --version probe was inconclusive (${result.classification}); proceeding to launch anyway. If launch hangs or fails, run \`consortium doctor\`. Diagnostics:
${result.diagnostics}`
);
return binaryPath;
}
const pkgName = `consortium-code-${currentPlatform()}`;
const installedPkg = detectInstalledPackageName(binaryPath);
const remediation = [
"",
"What to try, in order:",
` 1. Run \`consortium doctor\` for a guided health check.`,
` 2. Reinstall the CLI for your channel:`,
` npm install -g consortium@latest # stable`,
` npm install -g canary-dolphin-swimsuit@latest # canary`,
` npm install -g dev-hummingbird-sneakers@latest # dev`,
` (you appear to be on: ${installedPkg})`,
` 3. Force-install the platform package directly:`,
` npm install -g ${pkgName}`,
` 4. On macOS, clear the quarantine bit: \`xattr -dr com.apple.quarantine "$(dirname "$(which consortium)")/.."\``,
` 5. Fall back to the Docker image: \`docker run --rm -it ghcr.io/consortium/cli:dev\`.`,
""
].join("\n");
throw new Error(
`Consortium Code binary failed its pre-launch probe (${result.classification}):
` + result.diagnostics + remediation
);
}
function getProbeTimeoutMs() {
const override = parseInt(process.env.CONSORTIUM_PROBE_TIMEOUT_MS_OVERRIDE || "", 10);
if (Number.isFinite(override) && override > 0) return override;
return 3e4;
}
const CAPTURE_LIMIT_BYTES = 2 * 1024;
function truncate(s, max) {
if (!s) return "";
if (s.length <= max) return s;
return s.slice(0, max) + `
\u2026[truncated ${s.length - max} bytes]`;
}
function safeExec(cmd, args, timeoutMs = 1e3) {
try {
const out = node_child_process.execFileSync(cmd, args, {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
timeout: timeoutMs
});
return (out || "").toString().trim();
} catch (err) {
const partial = ((err?.stdout || "") + (err?.stderr || "")).toString().trim();
if (partial) return partial;
return `<unavailable: ${err?.code || err?.message || "error"}>`;
}
}
function probeGlibcVersion() {
if (process.platform !== "linux") return "<n/a (not linux)>";
const getconf = safeExec("getconf", ["GNU_LIBC_VERSION"]);
if (getconf && !getconf.startsWith("<unavailable")) return getconf;
const ldd = safeExec("ldd", ["--version"]);
if (ldd && !ldd.startsWith("<unavailable")) return ldd.split("\n")[0] || ldd;
return "<unavailable>";
}
function probeBinaryRequiredGlibc(binaryPath) {
if (process.platform !== "linux") return "<n/a (not linux)>";
const out = safeExec("readelf", ["-V", binaryPath], 2e3);
if (!out || out.startsWith("<unavailable")) return "<unavailable>";
const matches = out.match(/GLIBC_(\d+)\.(\d+)(?:\.(\d+))?/g) || [];
if (matches.length === 0) return "<no GLIBC references found>";
let best = { major: 0, minor: 0, patch: 0, raw: "" };
for (const m of matches) {
const parts = m.replace("GLIBC_", "").split(".").map((n) => parseInt(n, 10));
const major = parts[0] || 0;
const minor = parts[1] || 0;
const patch = parts[2] || 0;
if (major > best.major || major === best.major && minor > best.minor || major === best.major && minor === best.minor && patch > best.patch) {
best = { major, minor, patch, raw: m };
}
}
return best.raw || "<unknown>";
}
function runBinaryVersion(binaryPath) {
return new Promise((resolve) => {
let stdout = "";
let stderr = "";
let timedOut = false;
let spawnErr = null;
const timeoutMs = getProbeTimeoutMs();
const child = node_child_process.execFile(binaryPath, ["--version"], {
timeout: timeoutMs,
maxBuffer: 64 * 1024
}, (err, out, errOut) => {
stdout = typeof out === "string" ? out : String(out ?? "");
stderr = typeof errOut === "string" ? errOut : String(errOut ?? "");
if (err) {
const errAny = err;
if (errAny.killed && errAny.signal === "SIGTERM") timedOut = true;
if (errAny.code === "ENOENT") {
spawnErr = errAny;
}
const code = typeof err.code === "number" ? err.code : null;
resolve({ code, stdout, stderr, timedOut, spawnErr });
return;
}
resolve({ code: 0, stdout, stderr, timedOut: false, spawnErr: null });
});
child.on("error", (err) => {
if (err.code === "ENOENT") spawnErr = err;
});
});
}
async function probeBinaryHealthy(binaryPath) {
const cached = probeCache.get(binaryPath);
if (cached) return cached;
const res = await runBinaryVersion(binaryPath);
const stdoutTrim = (res.stdout || "").trim();
const isEnoent = res.spawnErr?.code === "ENOENT";
const healthy = !isEnoent && !res.timedOut && res.code === 0 && stdoutTrim.length > 0;
if (healthy) {
const ok = { ok: true };
probeCache.set(binaryPath, ok);
return ok;
}
const classification = isEnoent ? "enoent" : res.code != null && res.code !== 0 ? "nonzero-exit" : res.timedOut ? "slow" : "empty";
const reason = classification === "enoent" ? "ENOENT (binary not found at path)" : classification === "slow" ? `timed out after ${getProbeTimeoutMs()}ms with no output (likely slow launch, not incompatibility)` : classification === "nonzero-exit" ? `non-zero exit code ${res.code}` : "exited 0 with empty stdout";
const uname = process.platform === "linux" || process.platform === "darwin" ? safeExec("uname", ["-m"]) : "<n/a>";
const glibcHost = probeGlibcVersion();
const glibcRequired = probeBinaryRequiredGlibc(binaryPath);
const alpine = fs.existsSync("/etc/alpine-release");
let statInfo = "<unavailable>";
try {
const st = fs.statSync(binaryPath);
statInfo = `size=${st.size} mode=0${(st.mode & 511).toString(8)} uid=${st.uid} gid=${st.gid}`;
} catch (err) {
statInfo = `<stat failed: ${err instanceof Error ? err.message : String(err)}>`;
}
const lines = [];
lines.push(` binary: ${binaryPath}`);
lines.push(` stat: ${statInfo}`);
lines.push(` failure reason: ${reason}`);
lines.push(` process.platform: ${process.platform}`);
lines.push(` process.arch: ${process.arch}`);
lines.push(` uname -m: ${uname}`);
lines.push(` host glibc: ${glibcHost}`);
lines.push(` binary needs glibc: ${glibcRequired}`);
lines.push(` /etc/alpine-release present (musl signal): ${alpine ? "yes" : "no"}`);
lines.push(" --- captured stderr ---");
lines.push(truncate(res.stderr || "<empty>", CAPTURE_LIMIT_BYTES));
lines.push(" --- captured stdout ---");
lines.push(truncate(res.stdout || "<empty>", CAPTURE_LIMIT_BYTES));
const diagnostics = lines.join("\n");
const out = { ok: false, diagnostics, classification };
probeCache.set(binaryPath, out);
return out;
}
async function resolveSessionTagById(credentials, sessionId) {
try {
const response = await axios.get(
`${persistence.configuration.serverUrl}/v1/sessions`,
{
headers: { Authorization: `Bearer ${credentials.token}` },
timeout: 1e4
}
);
const row = (response.data?.sessions ?? []).find((s) => s.id === sessionId);
if (row && typeof row.tag === "string" && row.tag.length > 0) return row.tag;
return null;
} catch (err) {
persistence.logger.debug("[cliRouting] resolveSessionTagById failed", err);
return null;
}
}
async function handleConsortiumCodeCommand(args, startedBy) {
let startingMode = "local";
let parsedStartedBy = startedBy;
let resumeSessionId = null;
for (let i = 0; i < args.length; i++) {
if (args[i] === "--consortium-starting-mode" && args[i + 1]) {
startingMode = args[++i];
} else if (args[i] === "--started-by" && args[i + 1]) {
parsedStartedBy = args[++i];
} else if (args[i] === "--resume" && args[i + 1]) {
resumeSessionId = args[++i];
}
}
persistence.logger.debug(`[consortium-code] mode=${startingMode}, startedBy=${parsedStartedBy}, resume=${resumeSessionId ?? "(none)"}`);
const { credentials, machineId } = await index.authAndSetupMachineIfNeeded();
let resumeTag = null;
if (resumeSessionId) {
resumeTag = await resolveSessionTagById(credentials, resumeSessionId);
if (!resumeTag) {
persistence.logger.debug(`[consortium-code] --resume ${resumeSessionId}: session not found; starting fresh`);
}
}
if (startingMode === "remote") {
persistence.logger.debug("[consortium-code] Starting in remote mode (ACP backend)");
const { runConsortiumCode } = await Promise.resolve().then(function () { return require('./runConsortiumCode-DXwy0vho.cjs'); });
await runConsortiumCode({ credentials, startedBy: parsedStartedBy, resumeTag });
return;
}
const { isInteractiveTerminal, looksLikeWindowsSshNoPty } = await Promise.resolve().then(function () { return require('./index-BMIckAk5.cjs'); }).then(function (n) { return n.ttyDetect; });
if (!isInteractiveTerminal() && parsedStartedBy !== "daemon" && process.env.CONSORTIUM_ALLOW_NO_TTY !== "1") {
const winSshHint = looksLikeWindowsSshNoPty() ? "\nDetected Windows OpenSSH without a PTY. Reconnect with: `ssh -t user@host consortium code`\n" : "";
const msg = "Error: Consortium Code requires an interactive terminal (TTY).\n" + winSshHint + "Options:\n - Reconnect over SSH with `-t` to force PTY allocation.\n - Run `consortium daemon start` then drive sessions from the mobile/web client.\n - Pass `CONSORTIUM_ALLOW_NO_TTY=1` to bypass this check (advanced).\n";
process.stdout.write(msg);
process.stderr.write(msg);
process.exit(1);
}
const api = await persistence.ApiClient.create(credentials);
const activeOrgId = await index.ensureActiveOrg(credentials);
const sessionTag = resumeTag ?? node_crypto.randomUUID();
const { state, metadata } = createSessionMetadata.createSessionMetadata({
flavor: "consortium-code",
machineId,
startedBy: parsedStartedBy
});
const [, sessionResponse] = await Promise.all([
api.getOrCreateMachine({ machineId, metadata: index.initialMachineMetadata, organizationId: activeOrgId ?? void 0 }),
api.getOrCreateSession({ tag: sessionTag, metadata, state, organizationId: activeOrgId ?? void 0 })
]);
const reconnectionResult = setupOfflineReconnection.setupOfflineReconnection({
api,
sessionTag,
metadata,
state,
response: sessionResponse,
onSessionSwap: (newSession) => {
session = newSession;
}
});
let session = reconnectionResult.session;
session.keepAlive(false, "local");
index.registerKillSessionHandler(session.rpcHandlerManager, async () => {
session.sendSessionDeath();
await session.flush();
await session.close();
process.exit(0);
});
const messageQueue = new index.MessageQueue2((mode) => index.hashObject({
permissionMode: mode.permissionMode,
model: mode.model
}));
session.onUserMessage((message) => {
const text = message.content.text;
if (!text) return;
const permissionMode = message.meta?.permissionMode ?? "default";
const model = message.meta?.model;
messageQueue.push(text, { permissionMode, model });
});
const binary = await resolveConsortiumCodeBinary();
persistence.logger.debug(`[consortium-code] Entering mode-switching loop (binary: ${binary})`);
const { consortiumCodeLoop } = await Promise.resolve().then(function () { return require('./consortiumCodeLoop-ZCx91UYS.cjs'); });
const exitCode = await consortiumCodeLoop({
credentials,
machineId,
cwd: process.cwd(),
binary,
session,
startingMode: "local",
startedBy: parsedStartedBy,
messageQueue
});
session.sendSessionDeath();
await session.flush().catch(() => {
});
await session.close().catch(() => {
});
process.exit(exitCode);
}
var cliRouting = /*#__PURE__*/Object.freeze({
__proto__: null,
handleConsortiumCodeCommand: handleConsortiumCodeCommand
});
exports.cliRouting = cliRouting;
exports.resolveConsortiumCodeBinarySync = resolveConsortiumCodeBinarySync;