UNPKG

consortium

Version:

Remote control and session sharing CLI for AI coding agents

13,366 lines • 481 kB
'use strict';

var axios = require('axios');
var chalk = require('chalk');
var fs$1 = require('fs');
var fs = require('node:fs');
var os = require('node:os');
var path = require('node:path');
var node_events = require('node:events');
var socket_ioClient = require('socket.io-client');
var z = require('zod');
var node_crypto = require('node:crypto');
var tweetnacl = require('tweetnacl');
var node_module = require('node:module');
var child_process = require('child_process');
var util = require('util');
var fs$2 = require('fs/promises');
var os$1 = require('os');
var crypto = require('crypto');
var path$1 = require('path');
var url = require('url');
var node_child_process = require('node:child_process');
var fs$3 = require('node:fs/promises');
var node_util = require('node:util');
var http = require('http');
var net = require('net');
var expoServerSdk = require('expo-server-sdk');

var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
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 z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);

var name = "consortium";
var version = "0.8.12";
var description = "Remote control and session sharing CLI for AI coding agents";
var author = "ConsortiumAI";
var license = "BUSL-1.1";
var type = "module";
var homepage = "https://github.com/ConsortiumAI/consortium-cli";
var bugs = "https://github.com/ConsortiumAI/consortium-cli/issues";
var repository = "ConsortiumAI/consortium-cli";
var bin = {
	consortium: "./bin/consortium.mjs",
	"consortium-local": "./bin/consortium-local.mjs",
	"consortium-mcp": "./bin/consortium-mcp.mjs"
};
var main = "./dist/index.cjs";
var module$1 = "./dist/index.mjs";
var types = "./dist/index.d.cts";
var exports$1 = {
	".": {
		require: {
			types: "./dist/index.d.cts",
			"default": "./dist/index.cjs"
		},
		"import": {
			types: "./dist/index.d.mts",
			"default": "./dist/index.mjs"
		}
	},
	"./lib": {
		require: {
			types: "./dist/lib.d.cts",
			"default": "./dist/lib.cjs"
		},
		"import": {
			types: "./dist/lib.d.mts",
			"default": "./dist/lib.mjs"
		}
	},
	"./codex/consortiumMcpStdioBridge": {
		require: {
			types: "./dist/codex/consortiumMcpStdioBridge.d.cts",
			"default": "./dist/codex/consortiumMcpStdioBridge.cjs"
		},
		"import": {
			types: "./dist/codex/consortiumMcpStdioBridge.d.mts",
			"default": "./dist/codex/consortiumMcpStdioBridge.mjs"
		}
	},
	"./pi-ext": {
		require: {
			types: "./dist/pi/ext/consortium-permission/index.d.cts",
			"default": "./dist/pi/ext/consortium-permission/index.cjs"
		},
		"import": {
			types: "./dist/pi/ext/consortium-permission/index.d.mts",
			"default": "./dist/pi/ext/consortium-permission/index.mjs"
		}
	}
};
var files = [
	"dist",
	"bin",
	"scripts",
	"tools/archives",
	"tools/licenses",
	"package.json"
];
var scripts = {
	"why do we need to build before running tests / dev?": "We need the binary to be built so we run daemon commands which directly run the binary - we don't want them to go out of sync or have custom spawn logic depending how we started consortium",
	typecheck: "tsc --noEmit",
	build: "npx shx rm -rf dist && node -e \"process.argv=process.argv.concat(['--noEmit']);require('typescript/lib/tsc.js')\" && node scripts/build-stamp.cjs && node scripts/copy-pi-ext-pkg.cjs",
	"build:tests": "node -e \"process.argv=process.argv.concat(['--noEmit']);require('typescript/lib/tsc.js')\" && node scripts/build-stamp.cjs && node scripts/copy-pi-ext-pkg.cjs",
	"check:no-placeholder-key": "node -e \"const fs=require('fs');const s=fs.readFileSync('src/harness/verifySignature.ts','utf8');const m=s.match(/-----BEGIN PUBLIC KEY-----[\\s\\S]*?-----END PUBLIC KEY-----/);if(!m||m[0].includes('PLACEHOLDER')){console.error('placeholder pubkey in verifySignature.ts');process.exit(1)}\"",
	"check:harness-key": "node scripts/check-harness-key.cjs",
	test: "$npm_execpath run check:no-placeholder-key && $npm_execpath run build && vitest run",
	start: "$npm_execpath run build && node ./bin/consortium.mjs",
	dev: "tsx src/index.ts",
	"dev:local-server": "$npm_execpath run build && tsx --env-file .env.dev-local-server src/index.ts",
	"dev:cluster": "$npm_execpath run build && tsx --env-file .env.dev-cluster src/index.ts",
	"dev:integration-test-env": "$npm_execpath run build && tsx --env-file .env.integration-test src/index.ts",
	prepublishOnly: "$npm_execpath run build && $npm_execpath test && node scripts/check-dist-version.cjs",
	release: "$npm_execpath install && release-it",
	postinstall: "node scripts/unpack-tools.cjs && node scripts/fix-node-pty-perms.cjs && node scripts/fix-libsodium-esm.cjs && node scripts/verify-platform.cjs",
	"// ==== Dev/Stable Variant Management ====": "",
	stable: "node scripts/env-wrapper.cjs stable",
	"dev:variant": "node scripts/env-wrapper.cjs dev",
	"// ==== Stable Version Quick Commands ====": "",
	"stable:daemon:start": "node scripts/env-wrapper.cjs stable daemon start",
	"stable:daemon:stop": "node scripts/env-wrapper.cjs stable daemon stop",
	"stable:daemon:status": "node scripts/env-wrapper.cjs stable daemon status",
	"stable:auth": "node scripts/env-wrapper.cjs stable auth",
	"// ==== Development Version Quick Commands ====": "",
	"dev:daemon:start": "node scripts/env-wrapper.cjs dev daemon start",
	"dev:daemon:stop": "node scripts/env-wrapper.cjs dev daemon stop",
	"dev:daemon:status": "node scripts/env-wrapper.cjs dev daemon status",
	"dev:auth": "node scripts/env-wrapper.cjs dev auth",
	"// ==== Setup ====": "",
	"setup:dev": "node scripts/setup-dev.cjs",
	doctor: "node scripts/env-wrapper.cjs stable doctor",
	"// ==== Development Linking ====": "",
	"link:dev": "node scripts/link-dev.cjs",
	"unlink:dev": "node scripts/link-dev.cjs unlink"
};
var dependencies = {
	"@agentclientprotocol/sdk": "^0.8.0",
	"@modelcontextprotocol/sdk": "^1.25.3",
	"@sentry/node": "^8.46.0",
	"@stablelib/base64": "^2.0.1",
	"@stablelib/hex": "^2.0.1",
	ai: "^5.0.107",
	axios: "^1.13.2",
	"better-sqlite3": "^12.9.0",
	chalk: "^5.6.2",
	"cross-spawn": "^7.0.6",
	"expo-server-sdk": "^3.15.0",
	fastify: "^5.6.2",
	"fastify-type-provider-zod": "4.0.2",
	"http-proxy": "^1.18.1",
	"http-proxy-middleware": "^3.0.5",
	imapflow: "^1.7.8",
	ink: "^6.5.1",
	"libsodium-wrappers": "^0.7.13",
	mailparser: "^3.9.20",
	nodemailer: "^9.1.1",
	open: "^10.2.0",
	"playwright-core": "1.58.2",
	"ps-list": "^8.1.1",
	"qrcode-terminal": "^0.12.0",
	react: "^19.2.0",
	"socket.io-client": "^4.8.1",
	tar: "^7.5.2",
	tmp: "^0.2.5",
	tweetnacl: "^1.0.3",
	ws: "^8.18.0",
	zod: "3.25.76",
	yaml: "^2.8.2",
	pixelmatch: "^6.0.0",
	pngjs: "^7.0.0"
};
var devDependencies = {
	"@eslint/compat": "^1",
	"@types/better-sqlite3": "^7.6.13",
	"@types/cross-spawn": "^6.0.6",
	"@types/http-proxy": "^1.17.17",
	"@types/libsodium-wrappers": "^0.7.14",
	"@types/mailparser": "^3.4.6",
	"@types/node": ">=20",
	"@types/ps-list": "^6.2.1",
	"@types/qrcode-terminal": "^0.12.2",
	"@types/react": "^19.2.7",
	"@types/tmp": "^0.2.6",
	"@types/ws": "^8.5.12",
	"cross-env": "^10.1.0",
	dotenv: "^16.6.1",
	eslint: "^9",
	"eslint-config-prettier": "^10",
	"ink-testing-library": "^4.0.0",
	pkgroll: "^2.14.2",
	"release-it": "^19.0.6",
	shx: "^0.3.3",
	"ts-node": "^10",
	tsx: "^4.20.6",
	typescript: "5.9.3",
	vitest: "^3.2.4",
	"@consortium/crypto": "*",
	"@consortium/atlas-core": "*",
	"@consortium/browser-core": "*",
	"@consortium/browser-protocol": "*",
	"@consortium/device-protocol": "*",
	"@yume-chan/adb": "^2.1.0",
	"@yume-chan/adb-scrcpy": "^2.1.0",
	"@yume-chan/scrcpy": "^2.1.0",
	"@yume-chan/adb-server-node-tcp": "^2.1.0",
	"@yume-chan/stream-extra": "^2.1.0",
	"@consortium/extension-sdk": "*",
	"@consortium/mail-core": "*",
	"consortium-bench": "*"
};
var resolutions = {
	"whatwg-url": "14.2.0",
	"parse-path": "7.0.3",
	"@types/parse-path": "7.0.3"
};
var optionalDependencies = {
	"node-pty": "^1.0.0",
	"consortium-code-darwin-arm64": "0.2.0-canary.20260417210700",
	"consortium-code-darwin-x64": "0.2.0-canary.20260417210700",
	"consortium-code-linux-arm64": "0.2.0-canary.20260417210700",
	"consortium-code-linux-x64": "0.2.0-canary.20260417210700"
};
var publishConfig = {
	registry: "https://registry.npmjs.org"
};
var packageManager = "yarn@1.22.22";
var engines = {
	node: ">=20"
};
var packageJson = {
	name: name,
	version: version,
	description: description,
	author: author,
	license: license,
	type: type,
	homepage: homepage,
	bugs: bugs,
	repository: repository,
	bin: bin,
	main: main,
	module: module$1,
	types: types,
	exports: exports$1,
	files: files,
	scripts: scripts,
	dependencies: dependencies,
	devDependencies: devDependencies,
	resolutions: resolutions,
	optionalDependencies: optionalDependencies,
	publishConfig: publishConfig,
	packageManager: packageManager,
	engines: engines
};

function isLocalhostUrl(url) {
  try {
    const host = new URL(url).hostname;
    return host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host === "::1";
  } catch {
    return false;
  }
}
class Configuration {
  serverUrl;
  webappUrl;
  isDaemonProcess;
  // Directories and paths (from persistence)
  consortiumHomeDir;
  logsDir;
  settingsFile;
  privateKeyFile;
  daemonStateFile;
  daemonLockFile;
  currentCliVersion;
  isExperimentalEnabled;
  disableCaffeinate;
  authToken = process.env.CONSORTIUM_TOKEN;
  constructor() {
    if (process.env.CONSORTIUM_HOME_DIR) {
      const expandedPath = process.env.CONSORTIUM_HOME_DIR.replace(/^~/, os.homedir());
      this.consortiumHomeDir = expandedPath;
    } else {
      this.consortiumHomeDir = path.join(os.homedir(), ".consortium");
    }
    this.settingsFile = path.join(this.consortiumHomeDir, "settings.json");
    let persistedServerUrl;
    try {
      const rawSettings = fs.readFileSync(this.settingsFile, "utf-8");
      const parsed = JSON.parse(rawSettings);
      if (typeof parsed.serverUrl === "string" && /^https?:\/\//.test(parsed.serverUrl)) {
        persistedServerUrl = parsed.serverUrl;
      }
    } catch {
    }
    this.serverUrl = process.env.CONSORTIUM_SERVER_URL || persistedServerUrl || "https://api.consortium.dev";
    this.webappUrl = process.env.CONSORTIUM_WEBAPP_URL || (isLocalhostUrl(this.serverUrl) ? `http://localhost:${process.env.CONSORTIUM_WEBAPP_PORT || "8081"}` : "https://consortium.dev");
    const args = process.argv.slice(2);
    this.isDaemonProcess = args.length >= 2 && args[0] === "daemon" && args[1] === "start-sync";
    this.logsDir = path.join(this.consortiumHomeDir, "logs");
    this.privateKeyFile = path.join(this.consortiumHomeDir, "access.key");
    this.daemonStateFile = path.join(this.consortiumHomeDir, "daemon.state.json");
    this.daemonLockFile = path.join(this.consortiumHomeDir, "daemon.state.json.lock");
    this.isExperimentalEnabled = ["true", "1", "yes"].includes(process.env.CONSORTIUM_EXPERIMENTAL?.toLowerCase() || "");
    this.disableCaffeinate = ["true", "1", "yes"].includes(process.env.CONSORTIUM_DISABLE_CAFFEINATE?.toLowerCase() || "");
    this.currentCliVersion = packageJson.version;
    const variant = process.env.CONSORTIUM_VARIANT || "stable";
    if (variant === "dev" && !this.consortiumHomeDir.includes("dev")) {
      console.warn('\u26A0\uFE0F  WARNING: CONSORTIUM_VARIANT=dev but CONSORTIUM_HOME_DIR does not contain "dev"');
      console.warn(`   Current: ${this.consortiumHomeDir}`);
      console.warn(`   Expected: Should contain "dev" (e.g., ~/.consortium-dev)`);
    }
    if (!this.isDaemonProcess && variant === "dev") {
      console.log("\x1B[33m\u{1F527} DEV MODE\x1B[0m - Data: " + this.consortiumHomeDir);
    }
    if (!fs.existsSync(this.consortiumHomeDir)) {
      fs.mkdirSync(this.consortiumHomeDir, { recursive: true, mode: 448 });
    }
    if (!fs.existsSync(this.logsDir)) {
      fs.mkdirSync(this.logsDir, { recursive: true, mode: 448 });
    }
  }
}
const configuration = new Configuration();

const MAX_CAUSE_DEPTH = 4;
function serializeError(err, depth = 0) {
  if (err instanceof Error) {
    const out = {
      name: err.name || "Error",
      message: err.message || ""
    };
    if (err.stack) {
      out.stack = err.stack;
    }
    const code = err.code;
    if (typeof code === "string") {
      out.code = code;
    }
    const cause = err.cause;
    if (cause !== void 0 && depth < MAX_CAUSE_DEPTH) {
      out.cause = serializeError(cause, depth + 1);
    }
    return out;
  }
  if (typeof err === "object" && err !== null) {
    try {
      return { name: "NonError", message: JSON.stringify(err) };
    } catch {
      return { name: "NonError", message: String(err) };
    }
  }
  return { name: "NonError", message: String(err) };
}

const RECENT_LINES_MAX = 100;
function createTimestampForFilename(date = /* @__PURE__ */ new Date()) {
  return date.toLocaleString("sv-SE", {
    timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit"
  }).replace(/[: ]/g, "-").replace(/,/g, "") + "-pid-" + process.pid;
}
function createTimestampForLogEntry(date = /* @__PURE__ */ new Date()) {
  return date.toLocaleTimeString("en-US", {
    timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    hour12: false,
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
    fractionalSecondDigits: 3
  });
}
function getSessionLogPath() {
  const timestamp = createTimestampForFilename();
  const filename = configuration.isDaemonProcess ? `${timestamp}-daemon.log` : `${timestamp}.log`;
  return path.join(configuration.logsDir, filename);
}
class Logger {
  constructor(logFilePath = getSessionLogPath()) {
    this.logFilePath = logFilePath;
    this.structuredLogFilePath = logFilePath.replace(/\.log$/, ".jsonl");
    if (process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING && process.env.CONSORTIUM_SERVER_URL) {
      this.dangerouslyUnencryptedServerLoggingUrl = process.env.CONSORTIUM_SERVER_URL;
      console.log(chalk.yellow("[REMOTE LOGGING] Sending logs to server for AI debugging"));
    }
  }
  dangerouslyUnencryptedServerLoggingUrl;
  /** Sibling structured log: same path with .jsonl extension */
  structuredLogFilePath;
  recentLines = [];
  subscribers = [];
  /** Correlation ID inherited from the spawning daemon, if any */
  inheritedReqId = process.env.CONSORTIUM_REQUEST_ID || void 0;
  /**
   * Subscribe to every structured entry (in-process). Used by the daemon log
   * watchdog. Subscribers must never throw — exceptions are swallowed so a
   * broken subscriber can't take down logging.
   */
  subscribe(fn) {
    this.subscribers.push(fn);
    return () => {
      const idx = this.subscribers.indexOf(fn);
      if (idx >= 0) this.subscribers.splice(idx, 1);
    };
  }
  /** Last N plaintext log lines, oldest first. Feeds last-exit.json forensics. */
  getRecentLines() {
    return [...this.recentLines];
  }
  /**
   * Structured logging entry point: writes both the plaintext line (for
   * humans) and the JSONL line (for tooling), with an explicit level and
   * optional error / context / correlation ID.
   */
  event(level, message, opts) {
    this.write(level, message, [], opts);
  }
  /**
   * Unified sink: one call produces exactly one plaintext line and one JSONL
   * line, updates the ring buffer, and notifies subscribers. All public log
   * methods route through here.
   */
  write(level, message, args, extra) {
    const entry = {
      ts: (/* @__PURE__ */ new Date()).toISOString(),
      level,
      msg: message
    };
    const errSource = extra?.err !== void 0 ? extra.err : args.find((a) => a instanceof Error);
    if (errSource !== void 0) entry.err = serializeError(errSource);
    if (extra?.ctx) entry.ctx = extra.ctx;
    const reqId = extra?.reqId ?? this.inheritedReqId;
    if (reqId) entry.reqId = reqId;
    const plaintextParts = [...args];
    if (extra?.err !== void 0) plaintextParts.push(serializeError(extra.err));
    if (extra?.ctx) plaintextParts.push(extra.ctx);
    const levelPrefix = level === "debug" ? "" : `[${level.toUpperCase()}] `;
    this.logToFile(`[${this.localTimezoneTimestamp()}]`, `${levelPrefix}${message}`, ...plaintextParts);
    try {
      fs$1.appendFileSync(this.structuredLogFilePath, JSON.stringify(entry) + "\n");
    } catch {
    }
    for (const fn of this.subscribers) {
      try {
        fn(entry);
      } catch {
      }
    }
  }
  // Use local timezone for simplicity of locating the logs,
  // in practice you will not need absolute timestamps
  localTimezoneTimestamp() {
    return createTimestampForLogEntry();
  }
  debug(message, ...args) {
    this.write("debug", message, args);
  }
  debugLargeJson(message, object, maxStringLength = 100, maxArrayLength = 10) {
    if (!process.env.DEBUG) {
      this.debug(`In production, skipping message inspection`);
    }
    const truncateStrings = (obj) => {
      if (typeof obj === "string") {
        return obj.length > maxStringLength ? obj.substring(0, maxStringLength) + "... [truncated for logs]" : obj;
      }
      if (Array.isArray(obj)) {
        const truncatedArray = obj.map((item) => truncateStrings(item)).slice(0, maxArrayLength);
        if (obj.length > maxArrayLength) {
          truncatedArray.push(`... [truncated array for logs up to ${maxArrayLength} items]`);
        }
        return truncatedArray;
      }
      if (obj && typeof obj === "object") {
        const result = {};
        for (const [key, value] of Object.entries(obj)) {
          if (key === "usage") {
            continue;
          }
          result[key] = truncateStrings(value);
        }
        return result;
      }
      return obj;
    };
    const truncatedObject = truncateStrings(object);
    const json = JSON.stringify(truncatedObject, null, 2);
    this.logToFile(`[${this.localTimezoneTimestamp()}]`, message, "\n", json);
  }
  info(message, ...args) {
    this.logToConsole("info", "", message, ...args);
    this.write("info", message, args);
  }
  infoDeveloper(message, ...args) {
    this.debug(message, ...args);
    if (process.env.DEBUG) {
      this.logToConsole("info", "[DEV]", message, ...args);
    }
  }
  warn(message, ...args) {
    this.logToConsole("warn", "", message, ...args);
    this.write("warn", message, args);
  }
  error(message, ...args) {
    this.logToConsole("error", "", message, ...args);
    this.write("error", message, args);
  }
  getLogPath() {
    return this.logFilePath;
  }
  logToConsole(level, prefix, message, ...args) {
    switch (level) {
      case "debug": {
        console.log(chalk.gray(prefix), message, ...args);
        break;
      }
      case "error": {
        console.error(chalk.red(prefix), message, ...args);
        break;
      }
      case "info": {
        console.log(chalk.blue(prefix), message, ...args);
        break;
      }
      case "warn": {
        console.log(chalk.yellow(prefix), message, ...args);
        break;
      }
      default: {
        this.debug("Unknown log level:", level);
        console.log(chalk.blue(prefix), message, ...args);
        break;
      }
    }
  }
  async sendToRemoteServer(level, message, ...args) {
    if (!this.dangerouslyUnencryptedServerLoggingUrl) return;
    try {
      await fetch(this.dangerouslyUnencryptedServerLoggingUrl + "/logs-combined-from-cli-and-mobile-for-simple-ai-debugging", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          timestamp: (/* @__PURE__ */ new Date()).toISOString(),
          level,
          message: `${message} ${args.map(
            (a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)
          ).join(" ")}`,
          source: "cli",
          platform: process.platform
        })
      });
    } catch (error) {
    }
  }
  logToFile(prefix, message, ...args) {
    const errorAwareReplacer = (_key, value) => value instanceof Error ? serializeError(value) : value;
    const logLine = `${prefix} ${message} ${args.map(
      (arg) => typeof arg === "string" ? arg : arg instanceof Error ? JSON.stringify(serializeError(arg)) : JSON.stringify(arg, errorAwareReplacer)
    ).join(" ")}
`;
    this.recentLines.push(logLine.trimEnd());
    if (this.recentLines.length > RECENT_LINES_MAX) {
      this.recentLines.splice(0, this.recentLines.length - RECENT_LINES_MAX);
    }
    if (this.dangerouslyUnencryptedServerLoggingUrl) {
      let level = "info";
      if (prefix.includes(this.localTimezoneTimestamp())) {
        level = "debug";
      }
      this.sendToRemoteServer(level, message, ...args).catch(() => {
      });
    }
    try {
      fs$1.appendFileSync(this.logFilePath, logLine);
    } catch (appendError) {
      if (process.env.DEBUG) {
        console.error("[DEV MODE ONLY THROWING] Failed to append to log file:", appendError);
        throw appendError;
      }
    }
  }
}
let logger = new Logger();
async function listDaemonLogFiles(limit = 50) {
  try {
    const logsDir = configuration.logsDir;
    if (!fs.existsSync(logsDir)) {
      return [];
    }
    const logs = fs.readdirSync(logsDir).filter((file) => file.endsWith("-daemon.log")).map((file) => {
      const fullPath = path.join(logsDir, file);
      const stats = fs.statSync(fullPath);
      return { file, path: fullPath, modified: stats.mtime };
    }).sort((a, b) => b.modified.getTime() - a.modified.getTime());
    try {
      const { readDaemonState } = await Promise.resolve().then(function () { return persistence; });
      const state = await readDaemonState();
      if (!state) {
        return logs;
      }
      if (state.daemonLogPath && fs.existsSync(state.daemonLogPath)) {
        const stats = fs.statSync(state.daemonLogPath);
        const persisted = {
          file: path.basename(state.daemonLogPath),
          path: state.daemonLogPath,
          modified: stats.mtime
        };
        const idx = logs.findIndex((l) => l.path === persisted.path);
        if (idx >= 0) {
          const [found] = logs.splice(idx, 1);
          logs.unshift(found);
        } else {
          logs.unshift(persisted);
        }
      }
    } catch {
    }
    return logs.slice(0, Math.max(0, limit));
  } catch {
    return [];
  }
}
async function getLatestDaemonLog() {
  const [latest] = await listDaemonLogFiles(1);
  return latest || null;
}

const SessionMessageContentSchema = z.z.object({
  c: z.z.string(),
  // Base64 encoded encrypted content
  t: z.z.literal("encrypted")
});
const UpdateBodySchema = z.z.object({
  message: z.z.object({
    id: z.z.string(),
    seq: z.z.number(),
    content: SessionMessageContentSchema
  }),
  sid: z.z.string(),
  // Session ID
  t: z.z.literal("new-message")
});
const UpdateSessionBodySchema = z.z.object({
  t: z.z.literal("update-session"),
  sid: z.z.string(),
  metadata: z.z.object({
    version: z.z.number(),
    value: z.z.string()
  }).nullish(),
  agentState: z.z.object({
    version: z.z.number(),
    value: z.z.string()
  }).nullish()
});
const UpdateMachineBodySchema = z.z.object({
  t: z.z.literal("update-machine"),
  machineId: z.z.string(),
  metadata: z.z.object({
    version: z.z.number(),
    value: z.z.string()
  }).nullish(),
  daemonState: z.z.object({
    version: z.z.number(),
    value: z.z.string()
  }).nullish()
});
z.z.object({
  id: z.z.string(),
  seq: z.z.number(),
  body: z.z.union([
    UpdateBodySchema,
    UpdateSessionBodySchema,
    UpdateMachineBodySchema
  ]),
  createdAt: z.z.number()
});
const HardwareProfileSchema = z.z.object({
  schemaVersion: z.z.literal(1),
  platform: z.z.string(),
  arch: z.z.string(),
  cpuModel: z.z.string(),
  cpuCoresPhysical: z.z.number(),
  cpuThreads: z.z.number(),
  ramTotalBytes: z.z.number(),
  gpus: z.z.array(z.z.object({
    vendor: z.z.enum(["apple", "nvidia", "amd", "intel", "unknown"]),
    name: z.z.string(),
    vramBytes: z.z.number().optional(),
    driver: z.z.string().optional(),
    computeCapability: z.z.string().optional()
  })),
  unifiedMemory: z.z.boolean(),
  usableModelBytes: z.z.number(),
  memBandwidthGBs: z.z.number().optional(),
  bandwidthSource: z.z.enum(["measured", "chip-table", "unknown"]),
  diskFreeBytes: z.z.number(),
  detectedAt: z.z.number(),
  warnings: z.z.array(z.z.string())
});
z.z.object({
  host: z.z.string(),
  platform: z.z.string(),
  consortiumCliVersion: z.z.string(),
  homeDir: z.z.string(),
  consortiumHomeDir: z.z.string(),
  consortiumLibDir: z.z.string(),
  // Hardware specs — collected once at daemon startup from os module
  // (no syscall, libuv-cached). Optional so older daemons stay valid.
  cpuCount: z.z.number().optional(),
  memoryTotalMb: z.z.number().optional(),
  // Full hardware profile (GPU, VRAM, unified-memory budget, bandwidth) used
  // by the local-model fit engine. Populated asynchronously after daemon boot,
  // so it is absent on a freshly-started daemon and on older CLI versions.
  hardware: HardwareProfileSchema.optional(),
  // OpenClaw detection fields (populated by daemon heartbeat)
  openclawInstalled: z.z.boolean().optional(),
  openclawVersion: z.z.string().nullable().optional(),
  openclawWorkspaceExists: z.z.boolean().optional(),
  openclawGatewayRunning: z.z.boolean().optional(),
  openclawGatewayPort: z.z.number().optional(),
  openclawChannels: z.z.array(z.z.string()).optional(),
  openclawAgentCount: z.z.number().optional(),
  openclawAgents: z.z.array(z.z.object({
    name: z.z.string(),
    workspace: z.z.string(),
    agentDir: z.z.string(),
    model: z.z.string(),
    routingRules: z.z.number(),
    isDefault: z.z.boolean()
  })).optional(),
  // Generic per-harness runtime detection, keyed by harnessType. Populated by
  // the daemon heartbeat for every registered harness (openclaw, hermes, …).
  harnessInstances: z.z.record(z.z.string(), z.z.object({
    type: z.z.string(),
    family: z.z.string().optional(),
    installed: z.z.boolean(),
    version: z.z.string().nullable().optional(),
    workspaceExists: z.z.boolean().optional(),
    gatewayRunning: z.z.boolean().optional(),
    gatewayPort: z.z.number().optional(),
    channels: z.z.array(z.z.string()).optional(),
    agentCount: z.z.number().optional(),
    agents: z.z.array(z.z.object({
      // Canonical agent id (deriveAgentId for managed agents). The detection
      // layer has always written it; the schema used to strip it, so the app
      // could only match agents by display name — which drifts on rename.
      id: z.z.string().optional(),
      name: z.z.string(),
      // Runtime liveness as the harness reports it ('active' | 'stopped' | …).
      status: z.z.string().optional(),
      port: z.z.number().optional(),
      workspace: z.z.string().optional(),
      agentDir: z.z.string().optional(),
      model: z.z.string().optional(),
      routingRules: z.z.number().optional(),
      isDefault: z.z.boolean().optional()
    })).optional()
  })).optional(),
  // Terminal multiplexer capabilities — populated at daemon startup.
  // Tells the mobile app whether "New persistent terminal" should be enabled.
  terminalCapabilities: z.z.object({
    tmux: z.z.boolean(),
    zellij: z.z.boolean()
  }).optional()
});
z.z.object({
  status: z.z.union([
    z.z.enum(["running", "shutting-down"]),
    z.z.string()
    // Forward compatibility
  ]),
  pid: z.z.number().optional(),
  httpPort: z.z.number().optional(),
  startedAt: z.z.number().optional(),
  shutdownRequestedAt: z.z.number().optional(),
  shutdownSource: z.z.union([
    z.z.enum(["mobile-app", "cli", "os-signal", "unknown"]),
    z.z.string()
    // Forward compatibility
  ]).optional()
});
z.z.object({
  content: SessionMessageContentSchema,
  createdAt: z.z.number(),
  id: z.z.string(),
  seq: z.z.number(),
  updatedAt: z.z.number()
});
const MessageMetaSchema = z.z.object({
  sentFrom: z.z.string().optional(),
  // Source identifier
  permissionMode: z.z.enum(["default", "acceptEdits", "bypassPermissions", "plan", "read-only", "safe-yolo", "yolo"]).optional(),
  // Permission mode for this message
  model: z.z.string().nullable().optional(),
  // Model name for this message (null = reset)
  fallbackModel: z.z.string().nullable().optional(),
  // Fallback model for this message (null = reset)
  customSystemPrompt: z.z.string().nullable().optional(),
  // Custom system prompt for this message (null = reset)
  appendSystemPrompt: z.z.string().nullable().optional(),
  // Append to system prompt for this message (null = reset)
  allowedTools: z.z.array(z.z.string()).nullable().optional(),
  // Allowed tools for this message (null = reset)
  disallowedTools: z.z.array(z.z.string()).nullable().optional()
  // Disallowed tools for this message (null = reset)
});
z.z.object({
  session: z.z.object({
    id: z.z.string(),
    tag: z.z.string(),
    seq: z.z.number(),
    createdAt: z.z.number(),
    updatedAt: z.z.number(),
    metadata: z.z.string(),
    metadataVersion: z.z.number(),
    agentState: z.z.string().nullable(),
    agentStateVersion: z.z.number()
  })
});
const UserMessageSchema = z.z.object({
  role: z.z.literal("user"),
  content: z.z.object({
    type: z.z.literal("text"),
    text: z.z.string(),
    // Session-attachments (PR 5): Drive node ids and the client-generated
    // local id ride alongside `text` inside the encrypted body. Optional /
    // additive — older messages without these fields must continue to
    // parse. PR 6 will consume them when forwarding to ACP.
    attachmentIds: z.z.array(z.z.string()).optional(),
    localId: z.z.string().optional(),
    // PR 5 inline-envelope path: when the message carries its own
    // driveId + per-message DEK, the resolver can hit Drive directly
    // without consulting the server's binding rows.
    driveId: z.z.string().optional(),
    driveNodeIds: z.z.array(z.z.string()).optional(),
    driveDek: z.z.string().optional(),
    // Per-attachment records (successor wire shape): each attachment
    // carries its OWN driveId + per-file key + name/mime, so the sender
    // no longer ships a whole-drive DEK and mixed-drive sends work.
    attachments: z.z.array(z.z.object({
      nodeId: z.z.string(),
      driveId: z.z.string(),
      kind: z.z.string(),
      key: z.z.string().optional(),
      name: z.z.string().optional(),
      mime: z.z.string().optional(),
      size: z.z.number().optional(),
      linkUri: z.z.string().optional()
    })).optional()
  }),
  localKey: z.z.string().optional(),
  // Mobile messages include this
  meta: MessageMetaSchema.optional(),
  // Convenience copy — apiSession also surfaces attachmentIds at the top
  // level (mirroring `content.attachmentIds`) so consumers don't have to
  // reach through `content`. PR 6 owns the forwarding path that reads
  // this; PR 5 just makes sure the field is preserved end-to-end.
  attachmentIds: z.z.array(z.z.string()).optional(),
  localId: z.z.string().optional(),
  driveId: z.z.string().optional(),
  driveDek: z.z.string().optional()
});
const AgentMessageSchema = z.z.object({
  role: z.z.literal("agent"),
  content: z.z.object({
    type: z.z.literal("output"),
    data: z.z.any()
  }),
  meta: MessageMetaSchema.optional()
});
z.z.union([UserMessageSchema, AgentMessageSchema]);

function encryptBox(env, data, recipientPublicKey) {
  const { sodium, getRandomBytes } = env;
  const ephemeralKeyPair = sodium.crypto_box_keypair();
  const nonce = getRandomBytes(sodium.crypto_box_NONCEBYTES);
  const encrypted = sodium.crypto_box_easy(data, nonce, recipientPublicKey, ephemeralKeyPair.privateKey);
  const result = new Uint8Array(ephemeralKeyPair.publicKey.length + nonce.length + encrypted.length);
  result.set(ephemeralKeyPair.publicKey, 0);
  result.set(nonce, ephemeralKeyPair.publicKey.length);
  result.set(encrypted, ephemeralKeyPair.publicKey.length + nonce.length);
  return result;
}
function decryptBox(env, encryptedBundle, recipientSecretKey) {
  const { sodium } = env;
  const ephemeralPublicKey = encryptedBundle.slice(0, sodium.crypto_box_PUBLICKEYBYTES);
  const nonce = encryptedBundle.slice(
    sodium.crypto_box_PUBLICKEYBYTES,
    sodium.crypto_box_PUBLICKEYBYTES + sodium.crypto_box_NONCEBYTES
  );
  const encrypted = encryptedBundle.slice(sodium.crypto_box_PUBLICKEYBYTES + sodium.crypto_box_NONCEBYTES);
  try {
    return sodium.crypto_box_open_easy(encrypted, nonce, ephemeralPublicKey, recipientSecretKey);
  } catch {
    return null;
  }
}
const SIGN_BYTES = 64;
const SIGN_PUBLICKEYBYTES = 32;
const DEK_WRAP_VERSION_LEGACY = 0;
const DEK_WRAP_VERSION_SIGNED = 1;
function concatBytes(...parts) {
  let total = 0;
  for (const p of parts) total += p.length;
  const out = new Uint8Array(total);
  let off = 0;
  for (const p of parts) {
    out.set(p, off);
    off += p.length;
  }
  return out;
}
function bytesEqual(a, b) {
  if (a.length !== b.length) return false;
  let diff = 0;
  for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
  return diff === 0;
}
function signDetached(env, message, signSecretKey) {
  const { sodium } = env;
  if (typeof sodium.crypto_sign_detached !== "function") {
    throw new Error(
      "signed DEK wrap requested but this libsodium binding lacks crypto_sign_detached (Ed25519 unavailable)"
    );
  }
  return sodium.crypto_sign_detached(message, signSecretKey);
}
function verifyDetached(env, signature, message, signPublicKey) {
  const { sodium } = env;
  if (typeof sodium.crypto_sign_verify_detached !== "function") return false;
  try {
    return sodium.crypto_sign_verify_detached(signature, message, signPublicKey) === true;
  } catch {
    return false;
  }
}
function deriveSignPublicKey(env, signSecretKey) {
  const { sodium } = env;
  if (typeof sodium.crypto_sign_ed25519_sk_to_pk === "function") {
    try {
      return sodium.crypto_sign_ed25519_sk_to_pk(signSecretKey);
    } catch {
    }
  }
  if (signSecretKey.length === 64) return signSecretKey.slice(32, 64);
  return null;
}

function decodeBase64$1(base64, encoding = "base64") {
  let normalizedBase64 = base64;
  if (encoding === "base64url") {
    normalizedBase64 = base64.replace(/-/g, "+").replace(/_/g, "/");
    const padding = normalizedBase64.length % 4;
    if (padding) {
      normalizedBase64 += "=".repeat(4 - padding);
    }
  }
  const binaryString = atob(normalizedBase64);
  const len = binaryString.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }
  return bytes;
}
function encodeBase64$1(buffer, encoding = "base64") {
  const CHUNK_SIZE = 32768;
  let binaryString = "";
  for (let i = 0; i < buffer.length; i += CHUNK_SIZE) {
    const chunk = buffer.subarray(i, i + CHUNK_SIZE);
    binaryString += String.fromCharCode.apply(null, Array.from(chunk));
  }
  const base64 = btoa(binaryString);
  if (encoding === "base64url") {
    return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
  }
  return base64;
}

const DRIVE_DEK_SIGN_DOMAIN = new TextEncoder().encode("consortium/dek-wrap/v1");
function generateDriveDek(env) {
  return env.getRandomBytes(32);
}
function encryptDriveDek(env, dek, publicKey, signSecretKey, senderSignPublicKey) {
  const sealed = encryptBox(env, dek, publicKey);
  {
    const result = new Uint8Array(sealed.length + 1);
    result[0] = DEK_WRAP_VERSION_LEGACY;
    result.set(sealed, 1);
    return result;
  }
}
function decryptDriveDek(env, encrypted, privateKey, expectedSenderSignPublicKey) {
  const version = encrypted[0];
  if (version === DEK_WRAP_VERSION_LEGACY) {
    return decryptBox(env, encrypted.slice(1), privateKey);
  }
  if (version === DEK_WRAP_VERSION_SIGNED) {
    const senderPub = encrypted.slice(1, 1 + SIGN_PUBLICKEYBYTES);
    const signature = encrypted.slice(1 + SIGN_PUBLICKEYBYTES, 1 + SIGN_PUBLICKEYBYTES + SIGN_BYTES);
    const sealed = encrypted.slice(1 + SIGN_PUBLICKEYBYTES + SIGN_BYTES);
    if (senderPub.length !== SIGN_PUBLICKEYBYTES || signature.length !== SIGN_BYTES) return null;
    if (expectedSenderSignPublicKey && !bytesEqual(senderPub, expectedSenderSignPublicKey)) return null;
    if (!verifyDetached(env, signature, concatBytes(DRIVE_DEK_SIGN_DOMAIN, sealed), senderPub)) return null;
    return decryptBox(env, sealed, privateKey);
  }
  return null;
}
function encryptDriveContent(env, plaintext, dek) {
  const { sodium, getRandomBytes } = env;
  const nonce = getRandomBytes(sodium.crypto_secretbox_NONCEBYTES);
  const ciphertext = sodium.crypto_secretbox_easy(plaintext, nonce, dek);
  const result = new Uint8Array(nonce.length + ciphertext.length);
  result.set(nonce);
  result.set(ciphertext, nonce.length);
  return result;
}
function decryptDriveContent(env, encrypted, dek) {
  const { sodium } = env;
  const nonce = encrypted.slice(0, sodium.crypto_secretbox_NONCEBYTES);
  const ciphertext = encrypted.slice(sodium.crypto_secretbox_NONCEBYTES);
  try {
    return sodium.crypto_secretbox_open_easy(ciphertext, nonce, dek);
  } catch {
    return null;
  }
}
function encryptDriveMetadata(env, metadata, dek) {
  const plaintext = new TextEncoder().encode(JSON.stringify(metadata));
  const encrypted = encryptDriveContent(env, plaintext, dek);
  return encodeBase64$1(encrypted, "base64");
}
function decryptDriveMetadata(env, encrypted, dek) {
  try {
    const data = decodeBase64$1(encrypted, "base64");
    const decrypted = decryptDriveContent(env, data, dek);
    if (!decrypted) return null;
    return JSON.parse(new TextDecoder().decode(decrypted));
  } catch {
    return null;
  }
}

const FIELD_VERSION = 0;
const GCM_IV_BYTES = 12;
const ARTIFACT_DEK_SIGN_DOMAIN = new TextEncoder().encode("consortium/artifact-dek-wrap/v1");
function subtle$1() {
  const c = globalThis.crypto;
  if (!c?.subtle) {
    throw new Error("artifact crypto requires Web Crypto (crypto.subtle) \u2014 unavailable in this runtime");
  }
  return c.subtle;
}
function toArrayBuffer$1(arr) {
  return arr.buffer.slice(arr.byteOffset, arr.byteOffset + arr.byteLength);
}
function generateArtifactDek(env) {
  return env.getRandomBytes(32);
}
function wrapArtifactDek(env, dek, userPublicKey, signSecretKey, senderSignPublicKey) {
  const sealed = encryptBox(env, dek, userPublicKey);
  if (!signSecretKey) {
    const out2 = new Uint8Array(sealed.length + 1);
    out2[0] = DEK_WRAP_VERSION_LEGACY;
    out2.set(sealed, 1);
    return encodeBase64$1(out2, "base64");
  }
  const senderPub = senderSignPublicKey ?? deriveSignPublicKey(env, signSecretKey);
  if (!senderPub) {
    throw new Error(
      "wrapArtifactDek: cannot derive Ed25519 sender public key; pass senderSignPublicKey explicitly"
    );
  }
  const signature = signDetached(env, concatBytes(ARTIFACT_DEK_SIGN_DOMAIN, sealed), signSecretKey);
  const out = new Uint8Array(1 + senderPub.length + signature.length + sealed.length);
  out[0] = DEK_WRAP_VERSION_SIGNED;
  out.set(senderPub, 1);
  out.set(signature, 1 + senderPub.length);
  out.set(sealed, 1 + senderPub.length + signature.length);
  return encodeBase64$1(out, "base64");
}
async function encryptArtifactField(value, dek) {
  const key = await subtle$1().importKey("raw", toArrayBuffer$1(dek), { name: "AES-GCM" }, false, ["encrypt"]);
  const iv = new Uint8Array(GCM_IV_BYTES);
  const c = globalThis.crypto;
  c.getRandomValues(iv);
  const plaintext = new TextEncoder().encode(JSON.stringify(value));
  const ct = new Uint8Array(await subtle$1().encrypt({ name: "AES-GCM", iv }, key, toArrayBuffer$1(plaintext)));
  const out = new Uint8Array(1 + iv.length + ct.length);
  out[0] = FIELD_VERSION;
  out.set(iv, 1);
  out.set(ct, 1 + iv.length);
  return encodeBase64$1(out, "base64");
}

const SHA512_BYTES = 64;
const SHA512_BLOCK_BYTES = 128;
const DERIVED_SEED_BYTES = 32;
async function sha512(env, data) {
  const s = env.sodium;
  if (typeof s.crypto_hash_sha512 === "function") return s.crypto_hash_sha512(data);
  if (typeof s.crypto_hash === "function") return s.crypto_hash(data);
  const subtle = globalThis.crypto?.subtle;
  if (!subtle) {
    throw new Error(
      "deriveKeyV2 requires SHA-512: binding lacks crypto_hash_sha512/crypto_hash and no Web Crypto subtle is available"
    );
  }
  const buf = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
  return new Uint8Array(await subtle.digest("SHA-512", buf));
}
async function hmacSha512(env, key, message) {
  let k = key;
  if (k.length > SHA512_BLOCK_BYTES) k = await sha512(env, k);
  const padded = new Uint8Array(SHA512_BLOCK_BYTES);
  padded.set(k);
  const inner = new Uint8Array(SHA512_BLOCK_BYTES + message.length);
  const outerPrefix = new Uint8Array(SHA512_BLOCK_BYTES);
  for (let i = 0; i < SHA512_BLOCK_BYTES; i++) {
    inner[i] = padded[i] ^ 54;
    outerPrefix[i] = padded[i] ^ 92;
  }
  inner.set(message, SHA512_BLOCK_BYTES);
  const innerHash = await sha512(env, inner);
  const outer = new Uint8Array(SHA512_BLOCK_BYTES + SHA512_BYTES);
  outer.set(outerPrefix);
  outer.set(innerHash, SHA512_BLOCK_BYTES);
  return sha512(env, outer);
}
async function deriveSeedV2(env, master, label) {
  const tag = await hmacSha512(env, master, new TextEncoder().encode(label));
  return tag.slice(0, DERIVED_SEED_BYTES);
}

const V2_CONTENT_DEK_WRAP_LABEL = "consortium/v2/dek-wrap";
const ENVELOPE_MAGIC = 226;
const FRAMED_HEADER_VERSION = 1;
const SCHEME_V4 = 4;
const FRAMED_HEADER_BYTES = 8;
const CONTENT_DEK_WRAP_VERSION = 220;
const GCM_NONCE_BYTES = 12;
const GCM_TAG_BYTES = 16;
const DEK_BYTES = 32;
const SHARED_CONTENT_DOMAIN = "consortium/content/v1";
const CONTENT_DOMAIN_AAD$1 = new TextEncoder().encode(SHARED_CONTENT_DOMAIN);
const SEALED_DEK_MARKER_HEADER = new Uint8Array([
  ENVELOPE_MAGIC,
  FRAMED_HEADER_VERSION,
  SCHEME_V4,
  0,
  0,
  0,
  0,
  0
]);
async function deriveContentDekWrapKeypair(env, master) {
  const seed = await deriveSeedV2(env, master, V2_CONTENT_DEK_WRAP_LABEL);
  if (seed.length !== DERIVED_SEED_BYTES) {
    throw new Error(`deriveContentDekWrapKeypair: unexpected seed length ${seed.length}`);
  }
  try {
    const kp = env.sodium.crypto_box_seed_keypair(seed);
    const secretKey = kp.secretKey ?? kp.privateKey;
    if (!secretKey) {
      throw new Error("deriveContentDekWrapKeypair: binding returned no box secret key");
    }
    return { publicKey: kp.publicKey, secretKey };
  } finally {
    seed.fill(0);
  }
}
function unwrapContentDek(env, frame, dekWrapSecretKey) {
  if (frame.length < 1 || frame[0] !== CONTENT_DEK_WRAP_VERSION) {
    return null;
  }
  const dek = decryptBox(env, frame.subarray(1), dekWrapSecretKey);
  if (!dek || dek.length !== DEK_BYTES) {
    return null;
  }
  return dek;
}
function parseSealedDekContent(payload) {
  if (payload.length < 2) {
    return null;
  }
  const wrappedLen = payload[0] << 8 | payload[1];
  let off = 2;
  if (payload.length < off + wrappedLen + GCM_NONCE_BYTES + GCM_TAG_BYTES) {
    return null;
  }
  const wrappedDek = payload.slice(off, off + wrappedLen);
  off += wrappedLen;
  const nonce = payload.slice(off, off + GCM_NONCE_BYTES);
  off += GCM_NONCE_BYTES;
  const ct = payload.slice(off);
  return { wrappedDek, nonce, ct };
}
function isSealedDekContent(blob) {
  if (blob.length < FRAMED_HEADER_BYTES + 2) {
    return false;
  }
  for (let i = 0; i < FRAMED_HEADER_BYTES; i++) {
    if (blob[i] !== SEALED_DEK_MARKER_HEADER[i]) {
      return false;
    }
  }
  return true;
}
function readSealedDekContent(blob) {
  if (!isSealedDekContent(blob)) {
    return null;
  }
  return parseSealedDekContent(blob.subarray(FRAMED_HEADER_BYTES));
}
function subtle() {
  const c = globalThis.crypto;
  if (!c?.subtle) {
    throw new Error("aesgcm-dek.v1 requires Web Crypto (crypto.subtle) \u2014 unavailable in this runtime");
  }
  return c.subtle;
}
function toArrayBuffer(arr) {
  return arr.buffer.slice(arr.byteOffset, arr.byteOffset + arr.byteLength);
}
async function aesGcmOpenBody(dek, nonce, body) {
  try {
    const key = await subtle().importKey("raw", toArrayBuffer(dek), { name: "AES-GCM" }, false, ["decrypt"]);
    const pt = await subtle().decrypt(
      { name: "AES-GCM", iv: toArrayBuffer(nonce), additionalData: toArrayBuffer(CONTENT_DOMAIN_AAD$1), tagLength: 128 },
      key,
      toArrayBuffer(body)
    );
    return new Uint8Array(pt);
  } catch {
    return null;
  }
}
async function openSealedDekBytesWithDek(blob, dek) {
  if (dek.length !== DEK_BYTES) {
    return null;
  }
  const parsed = readSealedDekContent(blob);
  if (!parsed) {
    return null;
  }
  return aesGcmOpenBody(dek, parsed.nonce, parsed.ct);
}

new TextEncoder().encode("consortium/identity-rotation/v1");

new TextEncoder();

class MessageDecodeError extends Error {
  constructor(message) {
    super(message);
    this.name = "MessageDecodeError";
  }
}
new TextEncoder();
const strictTextDecoder = new TextDecoder("utf-8", { fatal: true });
function isPlainObject(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
function coerceAttachmentIds(raw) {
  if (!Array.isArray(raw)) return [];
  const out = [];
  for (const entry of raw) {
    if (typeof entry !== "string") return [];
    out.push(entry);
  }
  return out;
}
function decodeMessageBody(plaintext) {
  let text;
  try {
    text = strictTextDecoder.decode(plaintext);
  } catch (e) {
    throw new MessageDecodeError(
      `Message body is not valid UTF-8: ${e.message}`
    );
  }
  const trimmed = text.trimStart();
  if (!trimmed.startsWith("{")) {
    return { v: 1, text };
  }
  let parsed;
  try {
    parsed = JSON.parse(text);
  } catch {
    return { v: 1, text };
  }
  if (!isPlainObject(parsed)) {
    return { v: 1, text };
  }
  if (typeof parsed.v !== "number" || parsed.v !== 2) {
    return { v: 1, text };
  }
  const innerText = typeof parsed.text === "string" ? parsed.text : "";
  const attachmentIds = coerceAttachmentIds(parsed.attachmentIds);
  const result = {
    v: 2,
    text: innerText,
    attachmentIds
  };
  if (typeof parsed.localId === "string") {
    result.localId = parsed.localId;
  }
  return result;
}

const require$2 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-BYXHizYk.cjs', document.baseURI).href)));
const sodium = require$2("libsodium-wrappers");

let ENV = null;
let READY = null;
async function getCryptoEnv() {
  if (ENV) return ENV;
  if (!READY) {
    READY = (async () => {
      await sodium.ready;
      const env = {
        // libsodium-wrappers exposes the required surface.
        // Cast: the .d.ts types are broader than SodiumLike but
        // structurally compatible.
        sodium,
        getRandomBytes: (size) => new Uint8Array(node_crypto.randomBytes(size))
      };
      ENV = env;
      return env;
    })();
  }
  return READY;
}

const CONTENT_DOMAIN_AAD = Buffer.from(SHARED_CONTENT_DOMAIN, "utf8");
let dekWrapKeypair = null;
let configuring = null;
let cachedEnv = null;
async function primeSealedDekEnv() {
  if (cachedEnv) return;
  try {
    cachedEnv = await getCryptoEnv();
  } catch {
    cachedEnv = null;
  }
}
async function configureSealedDekContent(credentials) {
  const enc = credentials?.encryption;
  if (!enc || enc.type !== "legacy" || enc.secret.length !== 32) {
    return;
  }
  if (dekWrapKeypair) {
    return;
  }
  if (!configuring) {
    configuring = (async () => {
      try {
        await primeSealedDekEnv();
        if (cachedEnv) {
          dekWrapKeypair = await deriveContentDekWrapKeypair(cachedEnv, enc.secret);
        }
      } catch {
        dekWrapKeypair = null;
      } finally {
        configuring = null;
      }
    })();
  }
  await configuring;
}
function isSealedDekReadable() {
  return dekWrapKeypair !== null;
}
function openSealedDekBytesSync(blob) {
  if (!dekWrapKeypair) {
    return null;
  }
  const parsed = readSealedDekContent(blob);
  if (!parsed) {
    return null;
  }
  const env = cachedEnv;
  if (!env) {
    return null;
  }
  const dek = unwrapContentDek(env, parsed.wrappedDek, dekWrapKeypair.secretKey);
  if (!dek) {
    return null;
  }
  try {
    const tag = parsed.ct.subarray(parsed.ct.length - 16);
    const ct = parsed.ct.subarray(0, parsed.ct.length - 16);
    const decipher = node_crypto.createDecipheriv("aes-256-gcm", dek, parsed.nonce);
    decipher.setAAD(CONTENT_DOMAIN_AAD);
    decipher.setAuthTag(tag);
    return new Uint8Array(Buffer.concat([decipher.update(ct), decipher.final()]));
  } catch {
    return null;
  } finally {
    dek.fill(0);
  }
}
function openSealedDekContentSync(blob) {
  const bytes = openSealedDekBytesSync(blob);
  if (!bytes) {
    return null;
  }
  try {
    return JSON.parse(new TextDecoder().decode(bytes));
  } catch {
    return null;
  }
}

function encodeBase64(buffer, variant = "base64") {
  if (variant === "base64url") {
    return encodeBase64Url(buffer);
  }
  return Buffer.from(buffer).toString("base64");
}
function encodeBase64Url(buffer) {
  return Buffer.from(buffer).toString("base64").replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
}
function decodeBase64(base64, variant = "base64") {
  if (variant === "base64url") {
    const base64Standard = base64.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - base64.length % 4) % 4);
    return new Uint8Array(Buffer.from(base64Standard, "base64"));
  }
  return new Uint8Array(Buffer.from(base64, "base64"));
}
function getRandomBytes(size) {
  return new Uint8Array(node_crypto.randomBytes(size));
}
function libsodiumEncryptForPublicKey(data, recipientPublicKey) {
  const ephemeralKeyPair = tweetnacl.box.keyPair();
  const nonce = getRandomBytes(tweetnacl.box.nonceLength);
  const encrypted = tweetnacl.box(data, nonce, recipientPublicKey, ephemeralKeyPair.secretKey);
  const result = new Uint8Array(ephemeralKeyPair.publicKey.length + nonce.length + encrypted.length);
  result.set(ephemeralKeyPair.publicKey, 0);
  result.set(nonce, ephemeralKeyPair.publicKey.length);
  result.set(encrypted, ephemeralKeyPair.publicKey.length + nonce.length);
  return result;
}
function encryptLegacy(data, secret) {
  const nonce = getRandomBytes(tweetnacl.secretbox.nonceLength);
  const encrypted = tweetnacl.secretbox(new TextEncoder().encode(JSON.stringify(data)), nonce, secret);
  const result = new Uint8Array(nonce.length + encrypted.length);
  result.set(nonce);
  result.set(encrypted, nonce.length);
  return result;
}
function decryptLegacyWithReason(data, secret) {
  const minLength = tweetnacl.secretbox.nonceLength + tweetnacl.secretbox.overheadLength;
  if (data.length < minLength) {
    return { value: null, reason: `payload too short: ${data.length} bytes (need >=${minLength})` };
  }
  const nonce = data.slice(0, tweetnacl.secretbox.nonceLength);
  const encrypted = data.slice(tweetnacl.secretbox.nonceLength);
  const decrypted = tweetnacl.secretbox.open(encrypted, nonce, secret);
  if (!decrypted) {
    return { value: null, reason: "secretbox authentication failed (wrong key or corrupted ciphertext)" };
  }
  try {
    return { value: JSON.parse(new TextDecoder().decode(decrypted)) };
  } catch (err) {
    return { value: null, reason: `decrypted plaintext is not valid JSON: ${err instanceof Error ? err.message : String(err)}` };
  }
}
function encryptWithDataKey(data, dataKey) {
  const nonce = getRandomBytes(12);
  const cipher = node_crypto.createCipheriv("aes-256-gcm", dataKey, nonce);
  const plaintext = new TextEncoder().encode(JSON.stringify(data));
  const encrypted = Buffer.concat([
    cipher.update(plaintext),
    cipher.final()
  ]);
  const authTag = cipher.getAuthTag();
  const bundle = new Uint8Array(12 + encrypted.length + 16 + 1);
  bundle.set([0], 0);
  bundle.set(nonce, 1);
  bundle.set(new Uint8Array(encrypted), 13);
  bundle.set(new Uint8Array(authTag), 13 + encrypted.length);
  return bundle;
}
function decryptWithDataKeyWithReason(bundle, dataKey) {
  if (bundle.length < 1) {
    return { value: null, reason: "empty bundle" };
  }
  if (bundle[0] !== 0) {
    return { value: null, reason: `unsupported bundle version ${bundle[0]} (expected 0) \u2014 CLI/app version skew?` };
  }
  if (bundle.length < 12 + 16 + 1) {
    return { value: null, reason: `bundle too short: ${bundle.length} bytes (need >=${12 + 16 + 1})` };
  }
  const nonce = bundle.slice(1, 13);
  const authTag = bundle.slice(bundle.length - 16);
  const ciphertext = bundle.slice(13, bundle.length - 16);
  let decrypted;
  try {
    const decipher = node_crypto.createDecipheriv("aes-256-gcm", dataKey, nonce);
    decipher.setAuthTag(authTag);
    decrypted = Buffer.concat([
      decipher.update(ciphertext),
      decipher.final()
    ]);
  } catch (error) {
    return { value: null, reason: `AES-GCM authentication failed (wrong key or corrupted ciphertext): ${error instanceof Error ? error.message : String(error)}` };
  }
  try {
    return { value: JSON.parse(new TextDecoder().decode(decrypted)) };
  } catch (err) {
    return { value: null, reason: `decrypted plaintext is not valid JSON: ${err instanceof Error ? err.message : String(err)}` };
  }
}
function decryptWithDataKey(bundle, dataKey) {
  return decryptWithDataKeyWithReason(bundle, dataKey).value;
}
function encrypt(key, variant, data) {
  if (variant === "legacy") {
    return encryptLegacy(data, key);
  } else {
    return encryptWithDataKey(data, key);
  }
}
function decrypt(key, variant, data) {
  return decryptWithReason(key, variant, data).value;
}
function decryptWithReason(key, variant, data) {
  const primary = variant === "legacy" ? decryptLegacyWithReason(data, key) : decryptWithDataKeyWithReason(data, key);
  if (primary.reason === void 0) {
    return primary;
  }
  if (!isSealedDekContent(data)) {
    return primary;
  }
  if (!isSealedDekReadable()) {
    return {
      value: null,
      reason: `aesgcm-dek.v1 content requires the account seed to derive the content-DEK-wrap key; these credentials carry none (dataKey tier) \u2014 fail closed`
    };
  }
  const opened = openSealedDekContentSync(data);
  if (opened === null) {
    return { value: null, reason: `aesgcm-dek.v1 content did not open (wrong account, tampered frame, or truncated payload)` };
  }
  return { value: opened };
}
function signChallenge(secret, challenge) {
  const keypair = tweetnacl.sign.keyPair.fromSeed(secret);
  const signature = tweetnacl.sign.detached(challenge, keypair.secretKey);
  return {
    challenge,
    publicKey: keypair.publicKey,
    signature
  };
}
function authChallenge(secret) {
  return signChallenge(secret, getRandomBytes(32));
}

async function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
function exponentialBackoffDelay(currentFailureCount, minDelay, maxDelay, maxFailureCount) {
  let maxDelayRet = minDelay + (maxDelay - minDelay) / maxFailureCount * Math.min(currentFailureCount, maxFailureCount);
  return Math.round(Math.random() * maxDelayRet);
}
function createBackoff(opts) {
  return async (callback) => {
    let currentFailureCount = 0;
    const minDelay = 250;
    const maxDelay = 1e3;
    const maxFailureCount = 50;
    while (true) {
      try {
        return await callback();
      } catch (e) {
        if (currentFailureCount < maxFailureCount) {
          currentFailureCount++;
        }
        let waitForRequest = exponentialBackoffDelay(currentFailureCount, minDelay, maxDelay, maxFailureCount);
        await delay(waitForRequest);
      }
    }
  };
}
let backoff = createBackoff();

class AsyncLock {
  permits = 1;
  promiseResolverQueue = [];
  async inLock(func) {
    try {
      await this.lock();
      return await func();
    } finally {
      this.unlock();
    }
  }
  async lock() {
    if (this.permits > 0) {
      this.permits = this.permits - 1;
      return;
    }
    await new Promise((resolve) => this.promiseResolverQueue.push(resolve));
  }
  unlock() {
    this.permits += 1;
    if (this.permits > 1 && this.promiseResolverQueue.length > 0) {
      throw new Error("this.permits should never be > 0 when there is someone waiting.");
    } else if (this.permits === 1 && this.promiseResolverQueue.length > 0) {
      this.permits -= 1;
      const nextResolver = this.promiseResolverQueue.shift();
      if (nextResolver) {
        setTimeout(() => {
          nextResolver(true);
        }, 0);
      }
    }
  }
}

const ERROR_SIGNATURES = [
  {
    key: "crypto-auth-failure",
    pattern: /AES-GCM authentication failed|secretbox authentication failed|unable to authenticate data/i,
    cause: "E2EE key mismatch between client and daemon, or corrupted ciphertext",
    hint: "The app and this machine likely hold different encryption keys. Re-pair the machine (or re-run `consortium auth login`) and restart the daemon."
  },
  {
    key: "crypto-version-skew",
    pattern: /unsupported bundle version/i,
    cause: "Encrypted payload format is newer/older than this CLI understands",
    hint: "CLI and app versions have drifted. Update the older side (`npm upgrade consortium` or update the app)."
  },
  {
    key: "crypto-payload-truncated",
    pattern: /payload too short|bundle too short|empty bundle/i,
    cause: "Encrypted payload arrived truncated or empty",
    hint: "Usually transient network corruption; if it recurs, check the relay/server logs for body-size limits."
  },
  {
    key: "binary-missing",
    pattern: /spawn \S+ ENOENT/,
    cause: "A required binary is not installed or not on PATH",
    hint: "Install the missing binary or remove the integration that requires it. The daemon inherits PATH from its launcher, not your shell profile."
  },
  {
    key: "connection-refused",
    pattern: /ECONNREFUSED/,
    cause: "Target service is not listening on the expected address/port",
    hint: "Check CONSORTIUM_SERVER_URL and that the server is running (`consortium daemon status`)."
  },
  {
    key: "port-in-use",
    pattern: /EADDRINUSE/,
    cause: "Another process already occupies the port",
    hint: "Find the conflicting process (`lsof -i :<port>`) or let the daemon pick a fresh random port by restarting it."
  },
  {
    key: "tls-verification",
    pattern: /CERT_|SELF_SIGNED|unable to verify the first certificate|certificate has expired/i,
    cause: "TLS certificate verification failed against the server",
    hint: "Check the server certificate (self-hosted instances often need the CA added to the trust store)."
  },
  {
    key: "missing-required-param",
    pattern: /is required/,
    cause: "RPC params missing a required field \u2014 when params decrypted to null, the real failure is the decryption upstream",
    hint: 'Look for a "Param decryption failed" log entry just before this one; fix that root cause first.'
  }
];
function matchErrorSignature(text) {
  for (const sig of ERROR_SIGNATURES) {
    if (sig.pattern.test(text)) {
      return sig;
    }
  }
  return null;
}

class RpcHandlerManager {
  handlers = /* @__PURE__ */ new Map();
  scopePrefix;
  encryptionKey;
  encryptionVariant;
  logger;
  onError;
  socket = null;
  constructor(config) {
    this.scopePrefix = config.scopePrefix;
    this.encryptionKey = config.encryptionKey;
    this.encryptionVariant = config.encryptionVariant;
    this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
    this.onError = config.onError;
  }
  /**
   * Register an RPC handler for a specific method
   * @param method - The method name (without prefix)
   * @param handler - The handler function
   */
  registerHandler(method, handler) {
    const prefixedMethod = this.getPrefixedMethod(method);
    this.handlers.set(prefixedMethod, handler);
    if (this.socket) {
      this.socket.emitWithAck("rpc-register", { method: prefixedMethod }).catch((err) => {
        this.logger(`[RPC] Failed to register ${prefixedMethod}: ${err}`);
      });
    }
  }
  /**
   * Handle an incoming RPC request
   * @param request - The RPC request data
   * @param callback - The response callback
   */
  async handleRequest(request) {
    let reqId;
    try {
      const handler = this.handlers.get(request.method);
      if (!handler) {
        this.logger("[RPC] [ERROR] Method not found", { method: request.method });
        const errorResponse = { error: "Method not found", method: request.method };
        const encryptedError = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
        return encryptedError;
      }
      const payload = decodeBase64(request.params);
      const decryption = decryptWithReason(this.encryptionKey, this.encryptionVariant, payload);
      if (decryption.value === null && decryption.reason !== void 0) {
        const signature = matchErrorSignature(decryption.reason);
        this.logger("[RPC] [ERROR] Param decryption failed", {
          method: request.method,
          reason: decryption.reason,
          payloadBytes: payload.length,
          ...signature ? { hint: signature.hint } : {}
        });
        this.onError?.(new Error(`RPC param decryption failed: ${decryption.reason}`), {
          method: request.method,
          payloadBytes: payload.length
        });
        const errorResponse = {
          error: `Param decryption failed: ${decryption.reason}`,
          method: request.method,
          ...signature ? { hint: signature.hint } : {}
        };
        return encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
      }
      const decryptedParams = decryption.value;
      if (decryptedParams && typeof decryptedParams === "object" && typeof decryptedParams._reqId === "string") {
        reqId = decryptedParams._reqId;
      }
      this.logger("[RPC] Calling handler", { method: request.method, ...reqId ? { reqId } : {} });
      const result = await handler(decryptedParams);
      this.logger("[RPC] Handler returned", { method: request.method, hasResult: result !== void 0, ...reqId ? { reqId } : {} });
      const encryptedResponse = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, result));
      this.logger("[RPC] Sending encrypted response", { method: request.method, responseLength: encryptedResponse.length, ...reqId ? { reqId } : {} });
      return encryptedResponse;
    } catch (error) {
      const serialized = serializeError(error);
      const signature = matchErrorSignature(`${serialized.message} ${serialized.code ?? ""}`);
      this.logger("[RPC] [ERROR] Error handling request", {
        method: request.method,
        error: serialized,
        ...reqId ? { reqId } : {},
        ...signature ? { hint: signature.hint } : {}
      });
      this.onError?.(error, { method: request.method, ...reqId ? { reqId } : {} });
      const errorResponse = {
        error: serialized.message || "Unknown error",
        errorName: serialized.name,
        ...serialized.code ? { errorCode: serialized.code } : {},
        method: request.method,
        ...reqId ? { reqId } : {},
        ...signature ? { hint: signature.hint } : {}
      };
      return encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
    }
  }
  async onSocketConnect(socket) {
    this.socket = socket;
    const registrations = Array.from(this.handlers.keys()).map(
      (prefixedMethod) => socket.emitWithAck("rpc-register", { method: prefixedMethod }).catch((err) => {
        this.logger(`[RPC] Failed to register ${prefixedMethod}: ${err}`);
      })
    );
    await Promise.all(registrations);
  }
  onSocketDisconnect() {
    this.socket = null;
  }
  /**
   * Get the number of registered handlers
   */
  getHandlerCount() {
    return this.handlers.size;
  }
  /**
   * Check if a handler is registered
   * @param method - The method name (without prefix)
   */
  hasHandler(method) {
    const prefixedMethod = this.getPrefixedMethod(method);
    return this.handlers.has(prefixedMethod);
  }
  /**
   * Invoke a registered handler in-process with already-decrypted params.
   * Used by the generic harness dispatcher to fall through to a legacy
   * per-harness RPC (e.g. `openclaw-agent-add`) when the harness provider
   * doesn't expose the dispatcher-facing method directly.
   */
  async callLocal(method, params) {
    const prefixedMethod = this.getPrefixedMethod(method);
    const handler = this.handlers.get(prefixedMethod);
    if (!handler) return void 0;
    return await handler(params);
  }
  /**
   * Clear all handlers
   */
  clearHandlers() {
    this.handlers.clear();
    this.logger("Cleared all RPC handlers");
  }
  /**
   * Get the prefixed method name
   * @param method - The method name
   */
  getPrefixedMethod(method) {
    return `${this.scopePrefix}:${method}`;
  }
}

const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-BYXHizYk.cjs', document.baseURI).href))));
function projectPath() {
  const path = path$1.resolve(__dirname$1, "..");
  return path;
}

function run$1(args, options) {
  const RUNNER_PATH = path$1.resolve(path$1.join(projectPath(), "scripts", "ripgrep_launcher.cjs"));
  return new Promise((resolve2, reject) => {
    const child = child_process.spawn(process.execPath, [RUNNER_PATH, JSON.stringify(args)], {
      stdio: ["pipe", "pipe", "pipe"],
      cwd: options?.cwd
    });
    let stdout = "";
    let stderr = "";
    child.stdout.on("data", (data) => {
      stdout += data.toString();
    });
    child.stderr.on("data", (data) => {
      stderr += data.toString();
    });
    child.on("close", (code) => {
      resolve2({
        exitCode: code || 0,
        stdout,
        stderr
      });
    });
    child.on("error", (err) => {
      reject(err);
    });
  });
}

function getBinaryPath() {
  const platformName = os$1.platform();
  const binaryName = platformName === "win32" ? "difft.exe" : "difft";
  return path$1.resolve(path$1.join(projectPath(), "tools", "unpacked", binaryName));
}
function run(args, options) {
  const binaryPath = getBinaryPath();
  return new Promise((resolve2, reject) => {
    const child = child_process.spawn(binaryPath, args, {
      stdio: ["pipe", "pipe", "pipe"],
      cwd: options?.cwd,
      env: {
        ...process.env,
        // Force color output when needed
        FORCE_COLOR: "1"
      }
    });
    let stdout = "";
    let stderr = "";
    child.stdout.on("data", (data) => {
      stdout += data.toString();
    });
    child.stderr.on("data", (data) => {
      stderr += data.toString();
    });
    child.on("close", (code) => {
      resolve2({
        exitCode: code || 0,
        stdout,
        stderr
      });
    });
    child.on("error", (err) => {
      reject(err);
    });
  });
}

function validatePath(targetPath, workingDirectory) {
  const resolvedTarget = path$1.resolve(workingDirectory, targetPath);
  const resolvedWorkingDir = path$1.resolve(workingDirectory);
  if (!resolvedTarget.startsWith(resolvedWorkingDir + "/") && resolvedTarget !== resolvedWorkingDir) {
    return {
      valid: false,
      error: `Access denied: Path '${targetPath}' is outside the working directory`
    };
  }
  return { valid: true };
}

const execFileAsync$6 = util.promisify(child_process.execFile);
const SAFE_GIT_REF = /^[A-Za-z0-9._/-]+$/;
function assertSafeGitRef(value, label) {
  if (!value || !SAFE_GIT_REF.test(value) || value.includes("..")) {
    throw new Error(`Unsafe git ${label}: ${JSON.stringify(value)}`);
  }
}
async function runCommand(file, args, cwd, timeout = 3e4) {
  const options = { cwd, timeout };
  const result = await execFileAsync$6(file, args, options);
  return {
    stdout: result.stdout?.toString() || "",
    stderr: result.stderr?.toString() || ""
  };
}
async function gitExec(args, cwd, timeout = 3e4) {
  return runCommand("git", args, cwd, timeout);
}
async function ensureDevGitignore(repoPath) {
  const gitignorePath = path$1.join(repoPath, ".gitignore");
  try {
    const content = await fs$2.readFile(gitignorePath, "utf8");
    if (!content.includes(".dev/") && !content.includes(".dev\n")) {
      await fs$2.appendFile(gitignorePath, "\n# Worktree directory\n.dev/\n");
    }
  } catch {
    await fs$2.appendFile(gitignorePath, "# Worktree directory\n.dev/\n");
  }
}
function slugify(text) {
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").substring(0, 40);
}
async function createCardWorktree(repoPath, cardId, cardSlug, baseBranch = "main") {
  assertSafeGitRef(baseBranch, "baseBranch");
  const slug = slugify(cardSlug);
  const cardIdShort = cardId.substring(0, 7);
  const branchName = `card/${cardIdShort}/${slug}`;
  const worktreeDir = `.dev/worktrees/${slug}`;
  const worktreePath = path$1.join(repoPath, worktreeDir);
  assertSafeGitRef(branchName, "branchName");
  await ensureDevGitignore(repoPath);
  await fs$2.mkdir(path$1.join(repoPath, ".dev/worktrees"), { recursive: true });
  try {
    await gitExec(["fetch", "origin"], repoPath, 6e4);
  } catch (e) {
    logger.debug(`[Worktree] Fetch failed (may be offline): ${e}`);
  }
  const startPoint = `origin/${baseBranch}`;
  try {
    await gitExec(["worktree", "add", "-b", branchName, worktreeDir, startPoint], repoPath);
  } catch (error) {
    if (error.stderr?.includes("already exists")) {
      try {
        await gitExec(["worktree", "add", worktreeDir, branchName], repoPath);
      } catch (e2) {
        if (e2.stderr?.includes("already registered")) {
          return { worktreePath, branchName };
        }
        throw new Error(`Failed to create worktree: ${e2.stderr || e2.message}`);
      }
    } else {
      throw new Error(`Failed to create worktree: ${error.stderr || error.message}`);
    }
  }
  return { worktreePath, branchName };
}
async function listWorktrees(repoPath, baseBranch = "main") {
  assertSafeGitRef(baseBranch, "baseBranch");
  const { stdout } = await gitExec(["worktree", "list", "--porcelain"], repoPath);
  const worktrees = [];
  let current = {};
  for (const line of stdout.split("\n")) {
    if (line.startsWith("worktree ")) {
      if (current.path) {
        worktrees.push(current);
      }
      current = { path: line.substring(9), changedFiles: [] };
    } else if (line.startsWith("HEAD ")) {
      current.head = line.substring(5);
    } else if (line.startsWith("branch ")) {
      const branch = line.substring(7).replace("refs/heads/", "");
      current.branch = branch;
      const cardMatch = branch.match(/^card\/([^/]+)\//);
      if (cardMatch) {
        current.cardId = cardMatch[1];
      }
    }
  }
  if (current.path) {
    worktrees.push(current);
  }
  const cardWorktrees = worktrees.filter((w) => w.path.includes(".dev/worktrees"));
  for (const wt of cardWorktrees) {
    wt.changedFiles = await getWorktreeChangedFiles(wt.path, repoPath, baseBranch);
  }
  return cardWorktrees;
}
async function getWorktreeChangedFiles(worktreePath, repoPath, baseBranch = "main") {
  assertSafeGitRef(baseBranch, "baseBranch");
  try {
    const { stdout } = await gitExec(["diff", "--name-only", `origin/${baseBranch}...HEAD`], worktreePath);
    return stdout.trim().split("\n").filter(Boolean);
  } catch {
    try {
      const { stdout } = await gitExec(["diff", "--name-only", "HEAD"], worktreePath);
      return stdout.trim().split("\n").filter(Boolean);
    } catch {
      return [];
    }
  }
}
async function cleanupWorktree(repoPath, worktreePath, deleteBranch = false) {
  let branchName;
  if (deleteBranch) {
    try {
      const { stdout } = await gitExec(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath);
      branchName = stdout.trim();
    } catch {
    }
  }
  try {
    await gitExec(["worktree", "remove", worktreePath, "--force"], repoPath);
  } catch (error) {
    await gitExec(["worktree", "prune"], repoPath);
  }
  if (deleteBranch && branchName && branchName !== "main" && branchName !== "master") {
    try {
      assertSafeGitRef(branchName, "branchName");
      await gitExec(["branch", "-D", branchName], repoPath);
    } catch {
    }
  }
}
async function pushWorktreeBranch(worktreePath, title) {
  try {
    await gitExec(["add", "-A"], worktreePath);
    await gitExec(["commit", "-m", title, "--allow-empty"], worktreePath);
  } catch {
  }
  const { stdout: branchStdout } = await gitExec(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath);
  const branchName = branchStdout.trim();
  assertSafeGitRef(branchName, "branchName");
  await gitExec(["push", "-u", "origin", branchName], worktreePath, 6e4);
  return { branchName };
}

const ALWAYS_CONFLICT_FILES = [
  "package.json",
  "package-lock.json",
  "yarn.lock",
  "pnpm-lock.yaml",
  "Cargo.lock",
  "Gemfile.lock",
  "go.sum",
  "poetry.lock",
  "composer.lock"
];
function detectConflicts(activeWorktrees, proposedFiles) {
  const fileToWorktrees = /* @__PURE__ */ new Map();
  for (const wt of activeWorktrees) {
    for (const file of wt.changedFiles) {
      const existing = fileToWorktrees.get(file) || [];
      existing.push(wt.branch);
      fileToWorktrees.set(file, existing);
    }
  }
  const conflicts = [];
  for (const [file, worktrees] of fileToWorktrees) {
    if (worktrees.length > 1) {
      const basename = file.split("/").pop() || file;
      conflicts.push({
        file,
        worktrees,
        isLockFile: ALWAYS_CONFLICT_FILES.includes(basename)
      });
    }
  }
  return {
    hasConflicts: conflicts.length > 0,
    hasLockFileConflicts: conflicts.some((c) => c.isLockFile),
    conflicts
  };
}
function formatConflictReport(report) {
  if (!report.hasConflicts) {
    return "No file conflicts detected between active worktrees.";
  }
  const lines = ["## File Conflict Warning\n"];
  if (report.hasLockFileConflicts) {
    lines.push("**CRITICAL: Lock file conflicts detected.** These cards should NOT run in parallel.\n");
  }
  lines.push("Conflicting files:");
  for (const conflict of report.conflicts) {
    const marker = conflict.isLockFile ? " \u26A0\uFE0F LOCK FILE" : "";
    lines.push(`- \`${conflict.file}\`${marker} \u2192 modified by: ${conflict.worktrees.join(", ")}`);
  }
  return lines.join("\n");
}

const execFileAsync$5 = util.promisify(child_process.execFile);
const MAX_BUFFER = 32 * 1024 * 1024;
async function runGit(repoPath, args, timeout = 6e4) {
  try {
    const { stdout, stderr } = await execFileAsync$5("git", args, { cwd: repoPath, timeout, maxBuffer: MAX_BUFFER });
    return { stdout: stdout.toString(), stderr: stderr.toString(), code: 0 };
  } catch (err) {
    return {
      stdout: (err?.stdout ?? "").toString(),
      stderr: (err?.stderr ?? err?.message ?? "").toString(),
      code: typeof err?.code === "number" ? err.code : 1
    };
  }
}
function ensureOk(result, what) {
  if (result.code !== 0) {
    throw new Error(`${what} failed: ${(result.stderr || result.stdout).trim()}`);
  }
  return result;
}
const CONFLICT_CODES = /* @__PURE__ */ new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
async function gitStatus(repoPath) {
  const res = ensureOk(await runGit(repoPath, ["status", "--porcelain=v1", "--branch", "--untracked-files=all"]), "git status");
  const lines = res.stdout.split("\n").filter(Boolean);
  let branch = "HEAD";
  let upstream = null;
  let ahead = 0;
  let behind = 0;
  const staged = [];
  const unstaged = [];
  const untracked = [];
  const conflicts = [];
  for (const line of lines) {
    if (line.startsWith("##")) {
      const info = line.slice(3);
      const branchPart = info.split(" ")[0];
      const [local, up] = branchPart.split("...");
      branch = local.replace(/^No commits yet on /, "") || "HEAD";
      upstream = up ? up.split(" ")[0] : null;
      const aheadM = info.match(/ahead (\d+)/);
      const behindM = info.match(/behind (\d+)/);
      if (aheadM) ahead = parseInt(aheadM[1], 10);
      if (behindM) behind = parseInt(behindM[1], 10);
      continue;
    }
    const xy = line.slice(0, 2);
    const path = line.slice(3);
    if (xy === "??") {
      untracked.push(path);
      continue;
    }
    if (CONFLICT_CODES.has(xy)) {
      conflicts.push(path);
      continue;
    }
    const x = xy[0];
    const y = xy[1];
    if (x !== " " && x !== "?") staged.push(path);
    if (y !== " " && y !== "?") unstaged.push(path);
  }
  const clean = staged.length === 0 && unstaged.length === 0 && untracked.length === 0 && conflicts.length === 0;
  return { branch, upstream, ahead, behind, staged, unstaged, untracked, conflicts, clean };
}
async function gitStage(repoPath, paths) {
  ensureOk(await runGit(repoPath, ["add", "--", ...paths]), "git add");
}
async function gitStageAll(repoPath) {
  ensureOk(await runGit(repoPath, ["add", "-A"]), "git add -A");
}
async function gitUnstage(repoPath, paths) {
  ensureOk(await runGit(repoPath, ["restore", "--staged", "--", ...paths]), "git unstage");
}
async function gitCommit(repoPath, message) {
  ensureOk(await runGit(repoPath, ["commit", "-m", message]), "git commit");
  const sha = ensureOk(await runGit(repoPath, ["rev-parse", "HEAD"]), "git rev-parse").stdout.trim();
  return { sha };
}
async function gitFetch(repoPath) {
  ensureOk(await runGit(repoPath, ["fetch", "--all", "--prune"], 12e4), "git fetch");
}
async function gitPush(repoPath, opts = {}) {
  const branch = ensureOk(await runGit(repoPath, ["rev-parse", "--abbrev-ref", "HEAD"]), "git branch").stdout.trim();
  const args = ["push", "--set-upstream", "origin", branch];
  if (opts.force) args.splice(1, 0, "--force-with-lease");
  const res = await runGit(repoPath, args, 12e4);
  return res;
}
async function gitPull(repoPath) {
  return runGit(repoPath, ["pull", "--ff-only"], 12e4);
}
async function gitBranchList(repoPath) {
  const res = ensureOk(await runGit(repoPath, ["branch", "--format=%(refname:short)"]), "git branch");
  const branches = res.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
  const current = ensureOk(await runGit(repoPath, ["rev-parse", "--abbrev-ref", "HEAD"]), "git branch").stdout.trim();
  return { branches, current };
}
async function gitCreateBranch(repoPath, name, checkout = true) {
  ensureOk(await runGit(repoPath, checkout ? ["checkout", "-b", name] : ["branch", name]), "git create branch");
}
async function gitCheckout(repoPath, ref) {
  ensureOk(await runGit(repoPath, ["checkout", ref]), "git checkout");
}
async function gitDeleteBranch(repoPath, name, force = false) {
  ensureOk(await runGit(repoPath, ["branch", force ? "-D" : "-d", name]), "git delete branch");
}
async function asOpResult(repoPath, res, what) {
  if (res.code === 0) {
    return { ok: true, conflicted: false, conflicts: [], message: `${what} completed` };
  }
  const status = await gitStatus(repoPath);
  if (status.conflicts.length > 0) {
    return { ok: false, conflicted: true, conflicts: status.conflicts, message: `${what} hit conflicts` };
  }
  return { ok: false, conflicted: false, conflicts: [], message: (res.stderr || res.stdout).trim() || `${what} failed` };
}
async function gitMerge(repoPath, ref, noFf = false) {
  const args = ["merge"];
  if (noFf) args.push("--no-ff");
  args.push(ref);
  return asOpResult(repoPath, await runGit(repoPath, args), `merge ${ref}`);
}
async function gitRebase(repoPath, onto) {
  return asOpResult(repoPath, await runGit(repoPath, ["rebase", onto]), `rebase onto ${onto}`);
}
async function gitCherryPick(repoPath, sha) {
  return asOpResult(repoPath, await runGit(repoPath, ["cherry-pick", sha]), `cherry-pick ${sha}`);
}
async function gitRevert(repoPath, sha) {
  return asOpResult(repoPath, await runGit(repoPath, ["revert", "--no-edit", sha]), `revert ${sha}`);
}
async function gitOpContinue(repoPath, kind) {
  const cmd = kind === "cherry-pick" ? ["cherry-pick", "--continue"] : kind === "rebase" ? ["rebase", "--continue"] : kind === "revert" ? ["revert", "--continue"] : ["merge", "--continue"];
  return asOpResult(repoPath, await runGitNoEditor(repoPath, cmd), `${kind} --continue`);
}
async function gitOpAbort(repoPath, kind) {
  const cmd = kind === "cherry-pick" ? ["cherry-pick", "--abort"] : kind === "rebase" ? ["rebase", "--abort"] : kind === "revert" ? ["revert", "--abort"] : ["merge", "--abort"];
  ensureOk(await runGit(repoPath, cmd), `${kind} --abort`);
}
async function runGitNoEditor(repoPath, args) {
  try {
    const { stdout, stderr } = await execFileAsync$5("git", args, { cwd: repoPath, timeout: 6e4, maxBuffer: MAX_BUFFER, env: { ...process.env, GIT_EDITOR: "true" } });
    return { stdout: stdout.toString(), stderr: stderr.toString(), code: 0 };
  } catch (err) {
    return { stdout: (err?.stdout ?? "").toString(), stderr: (err?.stderr ?? err?.message ?? "").toString(), code: typeof err?.code === "number" ? err.code : 1 };
  }
}
function looksBinary(buf) {
  const n = Math.min(buf.length, 8192);
  for (let i = 0; i < n; i++) if (buf[i] === 0) return true;
  return false;
}
async function gitListConflicts(repoPath) {
  const status = await gitStatus(repoPath);
  const out = [];
  for (const path of status.conflicts) {
    let buf = null;
    try {
      buf = await fs$2.readFile(path$1.join(repoPath, path));
    } catch {
      buf = null;
    }
    const binary = buf ? looksBinary(buf) : true;
    out.push({ path, binary, merged: binary || !buf ? null : buf.toString("utf-8") });
  }
  return out;
}
async function gitResolveSide(repoPath, path, side) {
  ensureOk(await runGit(repoPath, ["checkout", `--${side}`, "--", path]), `resolve --${side}`);
  ensureOk(await runGit(repoPath, ["add", "--", path]), "git add");
}
async function gitResolveContent(repoPath, path, content) {
  await fs$2.writeFile(path$1.join(repoPath, path), content, "utf-8");
  ensureOk(await runGit(repoPath, ["add", "--", path]), "git add");
}
async function gitDiff(repoPath, opts = {}) {
  const args = ["diff"];
  if (opts.staged) args.push("--staged");
  if (opts.path) args.push("--", opts.path);
  return ensureOk(await runGit(repoPath, args), "git diff").stdout;
}
async function gitLog(repoPath, limit = 50) {
  const fmt = "%H%x1f%an%x1f%aI%x1f%s";
  const res = ensureOk(await runGit(repoPath, ["log", `-${limit}`, `--pretty=format:${fmt}`]), "git log");
  return res.stdout.split("\n").filter(Boolean).map((line) => {
    const [sha, author, date, subject] = line.split("");
    return { sha, author, date, subject };
  });
}
logger.debug?.("gitManager loaded");

const execAsync = util.promisify(child_process.exec);
const execFileAsync$4 = util.promisify(child_process.execFile);
function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
  rpcHandlerManager.registerHandler("bash", async (data) => {
    logger.debug("Shell command request:", data.command);
    if (data.cwd && data.cwd !== "/") {
      const validation = validatePath(data.cwd, workingDirectory);
      if (!validation.valid) {
        return { success: false, error: validation.error };
      }
    }
    try {
      const options = {
        cwd: data.cwd === "/" ? void 0 : data.cwd,
        timeout: data.timeout || 3e4
        // Default 30 seconds timeout
      };
      logger.debug("Shell command executing...", { cwd: options.cwd, timeout: options.timeout });
      const { stdout, stderr } = await execAsync(data.command, options);
      logger.debug("Shell command executed, processing result...");
      const result = {
        success: true,
        stdout: stdout ? stdout.toString() : "",
        stderr: stderr ? stderr.toString() : "",
        exitCode: 0
      };
      logger.debug("Shell command result:", {
        success: true,
        exitCode: 0,
        stdoutLen: result.stdout.length,
        stderrLen: result.stderr.length
      });
      return result;
    } catch (error) {
      const execError = error;
      if (execError.code === "ETIMEDOUT" || execError.killed) {
        const result2 = {
          success: false,
          stdout: execError.stdout || "",
          stderr: execError.stderr || "",
          exitCode: typeof execError.code === "number" ? execError.code : -1,
          error: "Command timed out"
        };
        logger.debug("Shell command timed out:", {
          success: false,
          exitCode: result2.exitCode,
          error: "Command timed out"
        });
        return result2;
      }
      const result = {
        success: false,
        stdout: execError.stdout ? execError.stdout.toString() : "",
        stderr: execError.stderr ? execError.stderr.toString() : execError.message || "Command failed",
        exitCode: typeof execError.code === "number" ? execError.code : 1,
        error: execError.message || "Command failed"
      };
      logger.debug("Shell command failed:", {
        success: false,
        exitCode: result.exitCode,
        error: result.error,
        stdoutLen: result.stdout.length,
        stderrLen: result.stderr.length
      });
      return result;
    }
  });
  rpcHandlerManager.registerHandler("readFile", async (data) => {
    logger.debug("Read file request:", data.path);
    const validation = validatePath(data.path, workingDirectory);
    if (!validation.valid) {
      return { success: false, error: validation.error };
    }
    try {
      const buffer = await fs$2.readFile(data.path);
      const content = buffer.toString("base64");
      return { success: true, content };
    } catch (error) {
      logger.debug("Failed to read file:", error);
      return { success: false, error: error instanceof Error ? error.message : "Failed to read file" };
    }
  });
  rpcHandlerManager.registerHandler("writeFile", async (data) => {
    logger.debug("Write file request:", data.path);
    const validation = validatePath(data.path, workingDirectory);
    if (!validation.valid) {
      return { success: false, error: validation.error };
    }
    try {
      if (data.expectedHash !== null && data.expectedHash !== void 0) {
        try {
          const existingBuffer = await fs$2.readFile(data.path);
          const existingHash = crypto.createHash("sha256").update(existingBuffer).digest("hex");
          if (existingHash !== data.expectedHash) {
            return {
              success: false,
              error: `File hash mismatch. Expected: ${data.expectedHash}, Actual: ${existingHash}`
            };
          }
        } catch (error) {
          const nodeError = error;
          if (nodeError.code !== "ENOENT") {
            throw error;
          }
          return {
            success: false,
            error: "File does not exist but hash was provided"
          };
        }
      } else {
        try {
          await fs$2.stat(data.path);
          return {
            success: false,
            error: "File already exists but was expected to be new"
          };
        } catch (error) {
          const nodeError = error;
          if (nodeError.code !== "ENOENT") {
            throw error;
          }
        }
      }
      const buffer = Buffer.from(data.content, "base64");
      await fs$2.writeFile(data.path, buffer);
      const hash = crypto.createHash("sha256").update(buffer).digest("hex");
      return { success: true, hash };
    } catch (error) {
      logger.debug("Failed to write file:", error);
      return { success: false, error: error instanceof Error ? error.message : "Failed to write file" };
    }
  });
  rpcHandlerManager.registerHandler("listDirectory", async (data) => {
    logger.debug("List directory request:", data.path);
    const validation = validatePath(data.path, workingDirectory);
    if (!validation.valid) {
      return { success: false, error: validation.error };
    }
    try {
      const entries = await fs$2.readdir(data.path, { withFileTypes: true });
      const directoryEntries = await Promise.all(
        entries.map(async (entry) => {
          const fullPath = path$1.join(data.path, entry.name);
          let type = "other";
          let size;
          let modified;
          if (entry.isDirectory()) {
            type = "directory";
          } else if (entry.isFile()) {
            type = "file";
          }
          try {
            const stats = await fs$2.stat(fullPath);
            size = stats.size;
            modified = stats.mtime.getTime();
          } catch (error) {
            logger.debug(`Failed to stat ${fullPath}:`, error);
          }
          return {
            name: entry.name,
            type,
            size,
            modified
          };
        })
      );
      directoryEntries.sort((a, b) => {
        if (a.type === "directory" && b.type !== "directory") return -1;
        if (a.type !== "directory" && b.type === "directory") return 1;
        return a.name.localeCompare(b.name);
      });
      return { success: true, entries: directoryEntries };
    } catch (error) {
      logger.debug("Failed to list directory:", error);
      return { success: false, error: error instanceof Error ? error.message : "Failed to list directory" };
    }
  });
  rpcHandlerManager.registerHandler("getDirectoryTree", async (data) => {
    logger.debug("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
    const validation = validatePath(data.path, workingDirectory);
    if (!validation.valid) {
      return { success: false, error: validation.error };
    }
    async function buildTree(path, name, currentDepth) {
      try {
        const stats = await fs$2.stat(path);
        const node = {
          name,
          path,
          type: stats.isDirectory() ? "directory" : "file",
          size: stats.size,
          modified: stats.mtime.getTime()
        };
        if (stats.isDirectory() && currentDepth < data.maxDepth) {
          const entries = await fs$2.readdir(path, { withFileTypes: true });
          const children = [];
          await Promise.all(
            entries.map(async (entry) => {
              if (entry.isSymbolicLink()) {
                logger.debug(`Skipping symlink: ${path$1.join(path, entry.name)}`);
                return;
              }
              const childPath = path$1.join(path, entry.name);
              const childNode = await buildTree(childPath, entry.name, currentDepth + 1);
              if (childNode) {
                children.push(childNode);
              }
            })
          );
          children.sort((a, b) => {
            if (a.type === "directory" && b.type !== "directory") return -1;
            if (a.type !== "directory" && b.type === "directory") return 1;
            return a.name.localeCompare(b.name);
          });
          node.children = children;
        }
        return node;
      } catch (error) {
        logger.debug(`Failed to process ${path}:`, error instanceof Error ? error.message : String(error));
        return null;
      }
    }
    try {
      if (data.maxDepth < 0) {
        return { success: false, error: "maxDepth must be non-negative" };
      }
      const baseName = data.path === "/" ? "/" : data.path.split("/").pop() || data.path;
      const tree = await buildTree(data.path, baseName, 0);
      if (!tree) {
        return { success: false, error: "Failed to access the specified path" };
      }
      return { success: true, tree };
    } catch (error) {
      logger.debug("Failed to get directory tree:", error);
      return { success: false, error: error instanceof Error ? error.message : "Failed to get directory tree" };
    }
  });
  rpcHandlerManager.registerHandler("ripgrep", async (data) => {
    logger.debug("Ripgrep request with args:", data.args, "cwd:", data.cwd);
    if (data.cwd) {
      const validation = validatePath(data.cwd, workingDirectory);
      if (!validation.valid) {
        return { success: false, error: validation.error };
      }
    }
    try {
      const result = await run$1(data.args, { cwd: data.cwd });
      return {
        success: true,
        exitCode: result.exitCode,
        stdout: result.stdout.toString(),
        stderr: result.stderr.toString()
      };
    } catch (error) {
      logger.debug("Failed to run ripgrep:", error);
      return {
        success: false,
        error: error instanceof Error ? error.message : "Failed to run ripgrep"
      };
    }
  });
  rpcHandlerManager.registerHandler("difftastic", async (data) => {
    logger.debug("Difftastic request with args:", data.args, "cwd:", data.cwd);
    if (data.cwd) {
      const validation = validatePath(data.cwd, workingDirectory);
      if (!validation.valid) {
        return { success: false, error: validation.error };
      }
    }
    try {
      const result = await run(data.args, { cwd: data.cwd });
      return {
        success: true,
        exitCode: result.exitCode,
        stdout: result.stdout.toString(),
        stderr: result.stderr.toString()
      };
    } catch (error) {
      logger.debug("Failed to run difftastic:", error);
      return {
        success: false,
        error: error instanceof Error ? error.message : "Failed to run difftastic"
      };
    }
  });
  rpcHandlerManager.registerHandler("create-worktree", async (data) => {
    logger.debug("Create worktree request:", data);
    try {
      const result = await createCardWorktree(
        data.repoPath,
        data.cardId,
        data.cardSlug,
        data.baseBranch
      );
      return {
        success: true,
        worktreePath: result.worktreePath,
        branchName: result.branchName
      };
    } catch (error) {
      logger.debug("Failed to create worktree:", error);
      return {
        success: false,
        error: error instanceof Error ? error.message : "Failed to create worktree"
      };
    }
  });
  rpcHandlerManager.registerHandler("list-worktrees", async (data) => {
    logger.debug("List worktrees request:", data);
    try {
      const worktrees = await listWorktrees(data.repoPath, data.baseBranch);
      const report = detectConflicts(worktrees);
      return {
        success: true,
        worktrees: worktrees.map((w) => ({
          path: w.path,
          branch: w.branch,
          head: w.head,
          cardId: w.cardId,
          changedFiles: w.changedFiles
        })),
        conflictReport: formatConflictReport(report)
      };
    } catch (error) {
      logger.debug("Failed to list worktrees:", error);
      return {
        success: false,
        error: error instanceof Error ? error.message : "Failed to list worktrees"
      };
    }
  });
  rpcHandlerManager.registerHandler("cleanup-worktree", async (data) => {
    logger.debug("Cleanup worktree request:", data);
    try {
      await cleanupWorktree(data.repoPath, data.worktreePath, data.deleteBranch);
      return { success: true };
    } catch (error) {
      logger.debug("Failed to cleanup worktree:", error);
      return {
        success: false,
        error: error instanceof Error ? error.message : "Failed to cleanup worktree"
      };
    }
  });
  rpcHandlerManager.registerHandler("clone-project-repo", async (data) => {
    logger.debug(`Clone project repo request for project ${data.projectId}`);
    try {
      if (!/^[A-Za-z0-9_-]{1,128}$/.test(data.projectId)) {
        return { success: false, error: "Invalid project id" };
      }
      if (!/^https:\/\//i.test(data.cloneUrl)) {
        return { success: false, error: "Only https clone URLs are supported" };
      }
      const projectsRoot = process.env.CONSORTIUM_PROJECTS_DIR || path$1.join(os$1.homedir(), "consortium", "projects");
      const target = path$1.join(projectsRoot, data.projectId, "repo");
      if (fs$1.existsSync(path$1.join(target, ".git"))) {
        return { success: true, path: target, alreadyPresent: true };
      }
      await fs$2.mkdir(path$1.join(projectsRoot, data.projectId), { recursive: true });
      const args = ["clone"];
      if (data.ref) args.push("-b", data.ref);
      args.push(data.cloneUrl, target);
      await execFileAsync$4("git", args, { cwd: projectsRoot, timeout: 3e5, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } });
      if (data.scrubUrl && data.scrubUrl !== data.cloneUrl) {
        await execFileAsync$4("git", ["remote", "set-url", "origin", data.scrubUrl], { cwd: target, timeout: 3e4 });
      }
      return { success: true, path: target, alreadyPresent: false };
    } catch (error) {
      const raw = error instanceof Error ? error.message : String(error);
      const safe = raw.replace(/https:\/\/[^@\s]*@/gi, "https://");
      logger.debug(`Failed to clone project repo: ${safe}`);
      return { success: false, error: safe };
    }
  });
  rpcHandlerManager.registerHandler("push-worktree-branch", async (data) => {
    logger.debug("Push worktree branch request:", data);
    try {
      const result = await pushWorktreeBranch(data.worktreePath, data.title);
      return { success: true, branchName: result.branchName };
    } catch (error) {
      logger.debug("Failed to push worktree branch:", error);
      return {
        success: false,
        error: error instanceof Error ? error.message : "Failed to push branch"
      };
    }
  });
  const wrapGit = (name, fn) => fn().then((r) => ({ success: true, ...r })).catch((error) => {
    logger.debug(`[git] ${name} failed:`, error);
    return { success: false, error: error instanceof Error ? error.message : `git ${name} failed` };
  });
  rpcHandlerManager.registerHandler("git-status", (d) => wrapGit("status", async () => ({ status: await gitStatus(d.repoPath) })));
  rpcHandlerManager.registerHandler("git-stage", (d) => wrapGit("stage", async () => {
    d.paths && d.paths.length ? await gitStage(d.repoPath, d.paths) : await gitStageAll(d.repoPath);
    return {};
  }));
  rpcHandlerManager.registerHandler("git-unstage", (d) => wrapGit("unstage", async () => {
    await gitUnstage(d.repoPath, d.paths);
    return {};
  }));
  rpcHandlerManager.registerHandler("git-commit", (d) => wrapGit("commit", () => gitCommit(d.repoPath, d.message)));
  rpcHandlerManager.registerHandler("git-fetch", (d) => wrapGit("fetch", async () => {
    await gitFetch(d.repoPath);
    return {};
  }));
  rpcHandlerManager.registerHandler("git-push", (d) => wrapGit("push", async () => {
    const r = await gitPush(d.repoPath, { force: d.force });
    return { pushed: r.code === 0, output: (r.stderr || r.stdout).trim() };
  }));
  rpcHandlerManager.registerHandler("git-pull", (d) => wrapGit("pull", async () => {
    const r = await gitPull(d.repoPath);
    return { ok: r.code === 0, output: (r.stderr || r.stdout).trim() };
  }));
  rpcHandlerManager.registerHandler("git-branch-list", (d) => wrapGit("branch-list", () => gitBranchList(d.repoPath)));
  rpcHandlerManager.registerHandler("git-create-branch", (d) => wrapGit("create-branch", async () => {
    await gitCreateBranch(d.repoPath, d.name, d.checkout !== false);
    return {};
  }));
  rpcHandlerManager.registerHandler("git-checkout", (d) => wrapGit("checkout", async () => {
    await gitCheckout(d.repoPath, d.ref);
    return {};
  }));
  rpcHandlerManager.registerHandler("git-delete-branch", (d) => wrapGit("delete-branch", async () => {
    await gitDeleteBranch(d.repoPath, d.name, !!d.force);
    return {};
  }));
  rpcHandlerManager.registerHandler("git-merge", (d) => wrapGit("merge", async () => ({ result: await gitMerge(d.repoPath, d.ref, !!d.noFf) })));
  rpcHandlerManager.registerHandler("git-rebase", (d) => wrapGit("rebase", async () => ({ result: await gitRebase(d.repoPath, d.onto) })));
  rpcHandlerManager.registerHandler("git-cherry-pick", (d) => wrapGit("cherry-pick", async () => ({ result: await gitCherryPick(d.repoPath, d.sha) })));
  rpcHandlerManager.registerHandler("git-revert", (d) => wrapGit("revert", async () => ({ result: await gitRevert(d.repoPath, d.sha) })));
  rpcHandlerManager.registerHandler("git-op-continue", (d) => wrapGit("op-continue", async () => ({ result: await gitOpContinue(d.repoPath, d.kind) })));
  rpcHandlerManager.registerHandler("git-op-abort", (d) => wrapGit("op-abort", async () => {
    await gitOpAbort(d.repoPath, d.kind);
    return {};
  }));
  rpcHandlerManager.registerHandler("git-list-conflicts", (d) => wrapGit("list-conflicts", async () => ({ conflicts: await gitListConflicts(d.repoPath) })));
  rpcHandlerManager.registerHandler("git-resolve-side", (d) => wrapGit("resolve-side", async () => {
    await gitResolveSide(d.repoPath, d.path, d.side);
    return {};
  }));
  rpcHandlerManager.registerHandler("git-resolve-content", (d) => wrapGit("resolve-content", async () => {
    await gitResolveContent(d.repoPath, d.path, d.content);
    return {};
  }));
  rpcHandlerManager.registerHandler("git-diff", (d) => wrapGit("diff", async () => ({ diff: await gitDiff(d.repoPath, { staged: d.staged, path: d.path }) })));
  rpcHandlerManager.registerHandler("git-log", (d) => wrapGit("log", async () => ({ log: await gitLog(d.repoPath, d.limit) })));
}

const PRICING = {
  // --- Claude 4 & Future Models ---
  "claude-4.5-opus": {
    input: 5,
    output: 25,
    cache_write: 6.25,
    cache_read: 0.5
  },
  "claude-4.1-opus": {
    input: 15,
    output: 75,
    cache_write: 18.75,
    cache_read: 1.5
  },
  "claude-4-opus": {
    input: 15,
    output: 75,
    cache_write: 18.75,
    cache_read: 1.5
  },
  "claude-4.5-sonnet": {
    input: 3,
    output: 15,
    cache_write: 3.75,
    cache_read: 0.3
  },
  "claude-4-sonnet": {
    input: 3,
    output: 15,
    cache_write: 3.75,
    cache_read: 0.3
  },
  "claude-4.5-haiku": {
    input: 1,
    output: 5,
    cache_write: 1.25,
    cache_read: 0.1
  },
  // --- Legacy / Claude 3 ---
  "claude-3-opus-20240229": {
    input: 15,
    output: 75,
    cache_write: 18.75,
    cache_read: 1.5
  },
  "claude-3-sonnet-20240229": {
    input: 3,
    output: 15,
    cache_write: 3.75,
    cache_read: 0.3
  },
  "claude-3-5-sonnet-20240620": {
    input: 3,
    output: 15,
    cache_write: 3.75,
    cache_read: 0.3
  },
  // New Sonnet 3.5 updated model
  "claude-3-5-sonnet-20241022": {
    input: 3,
    output: 15,
    cache_write: 3.75,
    cache_read: 0.3
  },
  "claude-3-haiku-20240307": {
    input: 0.25,
    output: 1.25,
    cache_write: 0.3125,
    cache_read: 0.025
  },
  "claude-3-5-haiku-20241022": {
    input: 0.8,
    output: 4,
    cache_write: 1,
    // Approx based on 1.25x rule usually or custom
    cache_read: 0.08
  }
};
const DEFAULT_MODEL = "claude-3-5-sonnet-20241022";
function calculateCost(usage, modelId) {
  let pricing = PRICING[modelId];
  if (!pricing) {
    if (modelId?.includes("opus")) {
      if (modelId.includes("4.5")) pricing = PRICING["claude-4.5-opus"];
      else if (modelId.includes("4.1")) pricing = PRICING["claude-4.1-opus"];
      else if (modelId.includes("4")) pricing = PRICING["claude-4-opus"];
      else pricing = PRICING["claude-3-opus-20240229"];
    } else if (modelId?.includes("sonnet")) {
      if (modelId.includes("4.5")) pricing = PRICING["claude-4.5-sonnet"];
      else if (modelId.includes("4")) pricing = PRICING["claude-4-sonnet"];
      else pricing = PRICING["claude-3-5-sonnet-20241022"];
    } else if (modelId?.includes("haiku")) {
      if (modelId.includes("4.5")) pricing = PRICING["claude-4.5-haiku"];
      else if (modelId.includes("3.5")) pricing = PRICING["claude-3-5-haiku-20241022"];
      else pricing = PRICING["claude-3-haiku-20240307"];
    } else pricing = PRICING[DEFAULT_MODEL];
  }
  const inputCost = usage.input_tokens / 1e6 * pricing.input;
  const outputCost = usage.output_tokens / 1e6 * pricing.output;
  const cacheWriteCost = (usage.cache_creation_input_tokens || 0) / 1e6 * pricing.cache_write;
  const cacheReadCost = (usage.cache_read_input_tokens || 0) / 1e6 * pricing.cache_read;
  const totalInputCost = inputCost + cacheWriteCost + cacheReadCost;
  return {
    total: totalInputCost + outputCost,
    input: totalInputCost,
    output: outputCost
  };
}

const DEFAULT_TIMEOUT_MS$1 = 3e4;
const MAX_TIMEOUT_MS = 5 * 6e4;
const MAX_BUFFER_BYTES = 1024 * 1024;
const SIGKILL_GRACE_MS = 2e3;
function clampTimeout(requested) {
  if (typeof requested !== "number" || !Number.isFinite(requested) || requested <= 0) {
    return DEFAULT_TIMEOUT_MS$1;
  }
  return Math.min(Math.floor(requested), MAX_TIMEOUT_MS);
}
function executeCommand(options) {
  const { command, args = [], cwd, timeoutMs } = options;
  const resolvedTimeoutMs = clampTimeout(timeoutMs);
  const startedAt = Date.now();
  return new Promise((resolve) => {
    let stdout = "";
    let stderr = "";
    let stdoutTruncated = false;
    let stderrTruncated = false;
    let timedOut = false;
    let settled = false;
    let killTimer = null;
    const finish = (exitCode) => {
      if (settled) return;
      settled = true;
      if (killTimer) {
        clearTimeout(killTimer);
        killTimer = null;
      }
      clearTimeout(timeoutHandle);
      const truncated = stdoutTruncated || stderrTruncated;
      const result = {
        stdout,
        stderr,
        exitCode,
        durationMs: Date.now() - startedAt
      };
      if (truncated) result.truncated = true;
      if (timedOut) result.timedOut = true;
      resolve(result);
    };
    let child;
    try {
      child = node_child_process.execFile(command, args, {
        cwd,
        shell: false,
        windowsHide: true,
        // We manage our own truncation; set high buffer so execFile doesn't kill us.
        maxBuffer: MAX_BUFFER_BYTES * 4
      });
    } catch (err) {
      stderr = err instanceof Error ? err.message : String(err);
      finish(null);
      return;
    }
    const timeoutHandle = setTimeout(() => {
      timedOut = true;
      if (!child.killed) {
        try {
          child.kill("SIGTERM");
        } catch {
        }
      }
      killTimer = setTimeout(() => {
        if (!child.killed && child.exitCode === null) {
          try {
            child.kill("SIGKILL");
          } catch {
          }
        }
      }, SIGKILL_GRACE_MS);
    }, resolvedTimeoutMs);
    child.stdout?.on("data", (chunk) => {
      if (stdoutTruncated) return;
      const piece = typeof chunk === "string" ? chunk : chunk.toString("utf8");
      const remaining = MAX_BUFFER_BYTES - Buffer.byteLength(stdout, "utf8");
      if (Buffer.byteLength(piece, "utf8") <= remaining) {
        stdout += piece;
      } else {
        stdout += Buffer.from(piece, "utf8").slice(0, Math.max(0, remaining)).toString("utf8");
        stdoutTruncated = true;
      }
    });
    child.stderr?.on("data", (chunk) => {
      if (stderrTruncated) return;
      const piece = typeof chunk === "string" ? chunk : chunk.toString("utf8");
      const remaining = MAX_BUFFER_BYTES - Buffer.byteLength(stderr, "utf8");
      if (Buffer.byteLength(piece, "utf8") <= remaining) {
        stderr += piece;
      } else {
        stderr += Buffer.from(piece, "utf8").slice(0, Math.max(0, remaining)).toString("utf8");
        stderrTruncated = true;
      }
    });
    child.on("error", (err) => {
      if (!stderr) stderr = err instanceof Error ? err.message : String(err);
      finish(null);
    });
    child.on("close", (code, _signal) => {
      finish(code ?? null);
    });
  });
}

function isUsableForFailover(entry, now) {
  if (entry.status === "invalid") return false;
  if (entry.status === "cooldown" && typeof entry.cooldownUntil === "number" && entry.cooldownUntil > now) return false;
  return true;
}
function daemonAccountCandidates(index, providerId, pinnedAccountKeyId, now) {
  const out = [];
  if (pinnedAccountKeyId) {
    const entry = index.accounts[pinnedAccountKeyId];
    if (entry && entry.providerId === providerId) {
      out.push({ accountKeyId: pinnedAccountKeyId, reason: "pin", allowInvalid: true });
    }
  }
  const policy = index.failover[providerId];
  if (policy?.enabled) {
    for (const id of policy.order) {
      const entry = index.accounts[id];
      if (!entry || entry.providerId !== providerId) continue;
      if (!isUsableForFailover(entry, now)) continue;
      out.push({ accountKeyId: id, reason: "failover", allowInvalid: false });
    }
  }
  const defaultId = index.providerDefaults[providerId];
  if (defaultId) {
    const entry = index.accounts[defaultId];
    if (entry && entry.providerId === providerId && entry.status !== "invalid") {
      out.push({ accountKeyId: defaultId, reason: "default", allowInvalid: false });
    }
  }
  const newest = Object.entries(index.accounts).filter(([, e]) => e.providerId === providerId && e.status !== "invalid").sort(([, a], [, b]) => b.updatedAt - a.updatedAt)[0];
  if (newest) {
    out.push({ accountKeyId: newest[0], reason: "most-recent", allowInvalid: false });
  }
  return out;
}

const SCRYPT_N = 1 << 15;
const SCRYPT_r = 8;
const SCRYPT_p = 1;
const SCRYPT_DKLEN = 32;
const SCRYPT_MAXMEM = 256 * 1024 * 1024;
const SALT_LEN = 16;
const IV_LEN = 12;
const LEGACY_ID_PREFIX = "legacy:";
const ACCOUNT_BUNDLE_VERSION = 2;
function getKeystorePath() {
  return path.join(configuration.consortiumHomeDir, "byok.json");
}
function readAccessKeyMaterial() {
  try {
    if (fs.existsSync(configuration.privateKeyFile)) {
      return fs.readFileSync(configuration.privateKeyFile);
    }
  } catch (err) {
    logger.debug("[byokKeystore] could not read access.key:", err);
  }
  throw new Error(
    `BYOK keystore unavailable: no access key. Authenticate the daemon so access.key exists at ${configuration.privateKeyFile} before storing or reading BYOK keys.`
  );
}
function deriveAesKey(salt) {
  const material = readAccessKeyMaterial();
  return node_crypto.scryptSync(material, salt, SCRYPT_DKLEN, {
    N: SCRYPT_N,
    r: SCRYPT_r,
    p: SCRYPT_p,
    maxmem: SCRYPT_MAXMEM
  });
}
function deriveLegacyAesKey() {
  const raw = readAccessKeyMaterial();
  return node_crypto.createHash("sha256").update(raw).update("consortium-byok-v1").digest();
}
function sealWithMaterial(plaintext, material) {
  const salt = node_crypto.randomBytes(SALT_LEN);
  const key = node_crypto.scryptSync(material, salt, SCRYPT_DKLEN, {
    N: SCRYPT_N,
    r: SCRYPT_r,
    p: SCRYPT_p,
    maxmem: SCRYPT_MAXMEM
  });
  const iv = node_crypto.randomBytes(IV_LEN);
  const cipher = node_crypto.createCipheriv("aes-256-gcm", key, iv);
  const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
  return {
    salt: salt.toString("base64"),
    iv: iv.toString("base64"),
    ciphertext: ct.toString("base64"),
    authTag: cipher.getAuthTag().toString("base64")
  };
}
function openWithMaterial(blob, material) {
  try {
    const key = node_crypto.scryptSync(material, Buffer.from(blob.salt, "base64"), SCRYPT_DKLEN, {
      N: SCRYPT_N,
      r: SCRYPT_r,
      p: SCRYPT_p,
      maxmem: SCRYPT_MAXMEM
    });
    const decipher = node_crypto.createDecipheriv("aes-256-gcm", key, Buffer.from(blob.iv, "base64"));
    decipher.setAuthTag(Buffer.from(blob.authTag, "base64"));
    const pt = Buffer.concat([
      decipher.update(Buffer.from(blob.ciphertext, "base64")),
      decipher.final()
    ]);
    return pt.toString("utf8");
  } catch (err) {
    logger.debug("[byokKeystore] openWithMaterial failed:", err);
    return null;
  }
}
function encryptRecord(plaintext, key) {
  const iv = node_crypto.randomBytes(IV_LEN);
  const cipher = node_crypto.createCipheriv("aes-256-gcm", key, iv);
  const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
  const tag = cipher.getAuthTag();
  return {
    iv: iv.toString("base64"),
    ciphertext: ct.toString("base64"),
    authTag: tag.toString("base64"),
    createdAt: Date.now()
  };
}
function decryptRecord(rec, key) {
  try {
    const decipher = node_crypto.createDecipheriv(
      "aes-256-gcm",
      key,
      Buffer.from(rec.iv, "base64")
    );
    decipher.setAuthTag(Buffer.from(rec.authTag, "base64"));
    const pt = Buffer.concat([
      decipher.update(Buffer.from(rec.ciphertext, "base64")),
      decipher.final()
    ]);
    return pt.toString("utf8");
  } catch (err) {
    logger.debug("[byokKeystore] decrypt failed (likely KDF mismatch after re-auth):", err);
    return null;
  }
}
function decryptLegacy(entry, key) {
  try {
    const decipher = node_crypto.createDecipheriv(
      "aes-256-gcm",
      key,
      Buffer.from(entry.iv, "base64")
    );
    decipher.setAuthTag(Buffer.from(entry.tag, "base64"));
    const pt = Buffer.concat([
      decipher.update(Buffer.from(entry.ct, "base64")),
      decipher.final()
    ]);
    return pt.toString("utf8");
  } catch (err) {
    logger.debug("[byokKeystore] legacy decrypt failed:", err);
    return null;
  }
}
function writeFileAtomic(data) {
  const path$1 = getKeystorePath();
  try {
    fs.mkdirSync(path.dirname(path$1), { recursive: true });
  } catch {
  }
  const tmp = `${path$1}.tmp.${process.pid}.${Date.now()}`;
  fs.writeFileSync(tmp, JSON.stringify(data, null, 2), { encoding: "utf8", mode: 384 });
  try {
    fs.chmodSync(tmp, 384);
  } catch {
  }
  fs.renameSync(tmp, path$1);
  try {
    fs.chmodSync(path$1, 384);
  } catch {
  }
}
function emptyV3() {
  return {
    version: 3,
    salt: node_crypto.randomBytes(SALT_LEN).toString("base64"),
    records: {},
    index: { accounts: {}, providerDefaults: {}, failover: {} }
  };
}
function legacyAccountId(providerId) {
  return `${LEGACY_ID_PREFIX}${providerId}`;
}
function parseBundle(plaintext) {
  try {
    const parsed = JSON.parse(plaintext);
    if (parsed && typeof parsed === "object" && (parsed.kind === "oauth" || parsed.kind === "api-key")) {
      return {
        ...parsed,
        bundleVersion: typeof parsed.bundleVersion === "number" ? parsed.bundleVersion : 1
      };
    }
  } catch {
  }
  return null;
}
function serializeBundle(bundle) {
  return JSON.stringify({ ...bundle, bundleVersion: ACCOUNT_BUNDLE_VERSION });
}
function wrapRawKey(file, aesKey, providerId, rawKey, createdAt) {
  const id = legacyAccountId(providerId);
  const bundle = { kind: "api-key", apiKey: rawKey, raw: rawKey };
  file.records[id] = { ...encryptRecord(serializeBundle(bundle), aesKey), createdAt };
  file.index.accounts[id] = {
    providerId,
    label: "Imported key",
    kind: "api-key",
    updatedAt: createdAt
  };
  if (!file.index.providerDefaults[providerId]) {
    file.index.providerDefaults[providerId] = id;
  }
}
function readFileMigrating() {
  const path = getKeystorePath();
  if (!fs.existsSync(path)) return emptyV3();
  let parsed;
  try {
    parsed = JSON.parse(fs.readFileSync(path, "utf8"));
  } catch (err) {
    logger.debug("[byokKeystore] failed to parse byok.json; treating as empty:", err);
    return emptyV3();
  }
  if (parsed && typeof parsed === "object" && parsed.version === 3 && typeof parsed.salt === "string" && parsed.records && parsed.index) {
    return parsed;
  }
  if (parsed && typeof parsed === "object" && parsed.version === 2 && typeof parsed.salt === "string" && parsed.records) {
    const v2 = parsed;
    logger.debug("[byokKeystore] migrating v2 byok.json \u2192 v3 (multi-account)");
    const oldKey = deriveAesKey(Buffer.from(v2.salt, "base64"));
    const fresh = emptyV3();
    const newKey = deriveAesKey(Buffer.from(fresh.salt, "base64"));
    let migrated = 0;
    let dropped = 0;
    for (const [providerId, rec] of Object.entries(v2.records)) {
      const pt = decryptRecord(rec, oldKey);
      if (pt === null) {
        dropped++;
        continue;
      }
      wrapRawKey(fresh, newKey, providerId, pt, rec.createdAt ?? Date.now());
      migrated++;
    }
    try {
      writeFileAtomic(fresh);
    } catch (err) {
      logger.debug("[byokKeystore] failed to persist migrated v3 file:", err);
    }
    logger.debug(`[byokKeystore] v2\u2192v3 migration complete: migrated=${migrated} dropped=${dropped}`);
    return fresh;
  }
  if (parsed && typeof parsed === "object" && parsed.entries) {
    const v1 = parsed;
    logger.debug("[byokKeystore] migrating v1 byok.json \u2192 v3 (scrypt KDF + multi-account)");
    const legacyKey = deriveLegacyAesKey();
    const fresh = emptyV3();
    const newKey = deriveAesKey(Buffer.from(fresh.salt, "base64"));
    let migrated = 0;
    let dropped = 0;
    for (const [providerId, entry] of Object.entries(v1.entries ?? {})) {
      const pt = decryptLegacy(entry, legacyKey);
      if (pt === null) {
        dropped++;
        continue;
      }
      wrapRawKey(fresh, newKey, providerId, pt, Date.now());
      migrated++;
    }
    try {
      writeFileAtomic(fresh);
    } catch (err) {
      logger.debug("[byokKeystore] failed to persist migrated v3 file:", err);
    }
    logger.debug(`[byokKeystore] v1\u2192v3 migration complete: migrated=${migrated} dropped=${dropped}`);
    return fresh;
  }
  logger.debug("[byokKeystore] unknown byok.json shape; treating as empty");
  return emptyV3();
}
function runStartupMigration() {
  try {
    readFileMigrating();
  } catch (err) {
    logger.debug("[byokKeystore] runStartupMigration error (non-fatal):", err);
  }
}
function registerAccount(input) {
  const file = readFileMigrating();
  const aesKey = deriveAesKey(Buffer.from(file.salt, "base64"));
  file.records[input.accountKeyId] = encryptRecord(serializeBundle(input.bundle), aesKey);
  file.index.accounts[input.accountKeyId] = {
    providerId: input.providerId,
    vendor: input.vendor,
    label: input.label ?? "",
    kind: input.kind,
    envVar: input.envVar,
    status: "ok",
    expiresAt: input.bundle.expiresAt,
    updatedAt: Date.now()
  };
  if (input.isDefault || !file.index.providerDefaults[input.providerId]) {
    file.index.providerDefaults[input.providerId] = input.accountKeyId;
  }
  writeFileAtomic(file);
}
function maskedTailFromBundle(bundle) {
  if (!bundle) return "";
  if (bundle.kind === "api-key") {
    return bundle.apiKey ? bundle.apiKey.slice(-4) : "";
  }
  if (bundle.raw) {
    try {
      const parsed = JSON.parse(bundle.raw);
      const email = parsed?.account?.email_address ?? parsed?.account?.email ?? parsed?.email;
      if (typeof email === "string" && email.length > 0) return email;
    } catch {
    }
  }
  return bundle.accessToken ? bundle.accessToken.slice(-4) : "";
}
function getAccountBundle(accountKeyId) {
  const file = readFileMigrating();
  const rec = file.records[accountKeyId];
  if (!rec) return null;
  const aesKey = deriveAesKey(Buffer.from(file.salt, "base64"));
  const pt = decryptRecord(rec, aesKey);
  return pt === null ? null : parseBundle(pt);
}
function updateAccountBundle(accountKeyId, bundle) {
  const file = readFileMigrating();
  if (!file.records[accountKeyId] || !file.index.accounts[accountKeyId]) return false;
  const aesKey = deriveAesKey(Buffer.from(file.salt, "base64"));
  file.records[accountKeyId] = encryptRecord(serializeBundle(bundle), aesKey);
  const entry = file.index.accounts[accountKeyId];
  file.index.accounts[accountKeyId] = {
    ...entry,
    expiresAt: bundle.expiresAt,
    status: "ok",
    updatedAt: Date.now()
  };
  writeFileAtomic(file);
  return true;
}
function removeAccount(accountKeyId) {
  const file = readFileMigrating();
  if (!file.records[accountKeyId] && !file.index.accounts[accountKeyId]) return;
  const providerId = file.index.accounts[accountKeyId]?.providerId;
  delete file.records[accountKeyId];
  delete file.index.accounts[accountKeyId];
  if (providerId && file.index.providerDefaults[providerId] === accountKeyId) {
    const remaining = Object.entries(file.index.accounts).filter(([, e]) => e.providerId === providerId).sort(([, a], [, b]) => b.updatedAt - a.updatedAt);
    if (remaining[0]) file.index.providerDefaults[providerId] = remaining[0][0];
    else delete file.index.providerDefaults[providerId];
  }
  for (const [pid, policy] of Object.entries(file.index.failover)) {
    file.index.failover[pid] = { ...policy, order: policy.order.filter((id) => id !== accountKeyId) };
  }
  if (Object.keys(file.records).length === 0 && Object.keys(file.index.accounts).length === 0) {
    try {
      fs.unlinkSync(getKeystorePath());
      return;
    } catch {
    }
  }
  writeFileAtomic(file);
}
function listAccounts() {
  const file = readFileMigrating();
  return Object.entries(file.index.accounts).map(([accountKeyId, entry]) => ({ accountKeyId, ...entry })).sort((a, b) => b.updatedAt - a.updatedAt);
}
function setProviderDefault(providerId, accountKeyId) {
  const file = readFileMigrating();
  if (accountKeyId === null) {
    delete file.index.providerDefaults[providerId];
    writeFileAtomic(file);
    return true;
  }
  const entry = file.index.accounts[accountKeyId];
  if (!entry || entry.providerId !== providerId) return false;
  file.index.providerDefaults[providerId] = accountKeyId;
  writeFileAtomic(file);
  return true;
}
function setProviderFailover(providerId, policy) {
  const file = readFileMigrating();
  file.index.failover[providerId] = {
    enabled: policy.enabled,
    order: policy.order.filter((id) => !!file.index.accounts[id])
  };
  writeFileAtomic(file);
}
function markAccountStatus(accountKeyId, status, cooldownUntil) {
  const file = readFileMigrating();
  const entry = file.index.accounts[accountKeyId];
  if (!entry) return;
  file.index.accounts[accountKeyId] = {
    ...entry,
    status,
    cooldownUntil: status === "cooldown" ? cooldownUntil : void 0
  };
  writeFileAtomic(file);
}
function resolveAccountForProvider(providerId, pinnedAccountKeyId) {
  const file = readFileMigrating();
  const candidates = daemonAccountCandidates(
    file.index,
    providerId,
    pinnedAccountKeyId,
    Date.now()
  );
  let aesKey = null;
  for (const candidate of candidates) {
    const rec = file.records[candidate.accountKeyId];
    if (!rec) continue;
    aesKey ??= deriveAesKey(Buffer.from(file.salt, "base64"));
    const pt = decryptRecord(rec, aesKey);
    const bundle = pt === null ? null : parseBundle(pt);
    if (!bundle) continue;
    return {
      accountKeyId: candidate.accountKeyId,
      entry: file.index.accounts[candidate.accountKeyId],
      bundle,
      reason: candidate.reason
    };
  }
  return null;
}
function registerProviderKey(providerId, key) {
  const file = readFileMigrating();
  const aesKey = deriveAesKey(Buffer.from(file.salt, "base64"));
  const id = legacyAccountId(providerId);
  const bundle = { kind: "api-key", apiKey: key, raw: key };
  file.records[id] = encryptRecord(serializeBundle(bundle), aesKey);
  file.index.accounts[id] = {
    providerId,
    label: "Imported key",
    kind: "api-key",
    updatedAt: Date.now()
  };
  file.index.providerDefaults[providerId] = id;
  writeFileAtomic(file);
  return { byokKeyRef: `byok:${providerId}` };
}
function getProviderKey(providerId) {
  const resolved = resolveAccountForProvider(providerId);
  if (!resolved) return null;
  return resolved.bundle.apiKey ?? resolved.bundle.accessToken ?? resolved.bundle.raw ?? null;
}
function removeProviderKey(providerId) {
  removeAccount(legacyAccountId(providerId));
}

const KEYCHAIN_SERVICE$1 = "consortium-cli";
const EXEC_TIMEOUT_MS = 5e3;
const KEYCHAIN_KEK_ENV = "CONSORTIUM_KEK_KEYCHAIN";
function isKeychainKekEnabled() {
  const v = (process.env[KEYCHAIN_KEK_ENV] ?? "").trim().toLowerCase();
  return v === "1" || v === "true";
}
function currentAccount() {
  try {
    return os.userInfo().username || "consortium";
  } catch {
    return "consortium";
  }
}
function decodeKek(raw) {
  const trimmed = raw.trim();
  if (!trimmed) return null;
  const buf = Buffer.from(trimmed, "base64");
  return buf.length >= 32 ? buf : null;
}
function readMac(account) {
  try {
    const out = node_child_process.execFileSync(
      "security",
      ["find-generic-password", "-a", account, "-s", KEYCHAIN_SERVICE$1, "-w"],
      { encoding: "utf8", timeout: EXEC_TIMEOUT_MS, stdio: ["ignore", "pipe", "ignore"] }
    );
    return decodeKek(out);
  } catch {
    return null;
  }
}
function storeMac(account, b64) {
  try {
    node_child_process.execFileSync(
      "security",
      ["add-generic-password", "-a", account, "-s", KEYCHAIN_SERVICE$1, "-U", "-w", b64],
      { timeout: EXEC_TIMEOUT_MS, stdio: "ignore" }
    );
    return true;
  } catch {
    return false;
  }
}
function deleteMac(account) {
  try {
    node_child_process.execFileSync(
      "security",
      ["delete-generic-password", "-a", account, "-s", KEYCHAIN_SERVICE$1],
      { timeout: EXEC_TIMEOUT_MS, stdio: "ignore" }
    );
  } catch {
  }
}
function readLinux(account) {
  try {
    const out = node_child_process.execFileSync(
      "secret-tool",
      ["lookup", "service", KEYCHAIN_SERVICE$1, "account", account],
      { encoding: "utf8", timeout: EXEC_TIMEOUT_MS, stdio: ["ignore", "pipe", "ignore"] }
    );
    return decodeKek(out);
  } catch {
    return null;
  }
}
function storeLinux(account, b64) {
  try {
    node_child_process.execFileSync(
      "secret-tool",
      ["store", "--label=consortium-cli KEK", "service", KEYCHAIN_SERVICE$1, "account", account],
      { input: b64, timeout: EXEC_TIMEOUT_MS, stdio: ["pipe", "ignore", "ignore"] }
    );
    return true;
  } catch {
    return false;
  }
}
function deleteLinux(account) {
  try {
    node_child_process.execFileSync(
      "secret-tool",
      ["clear", "service", KEYCHAIN_SERVICE$1, "account", account],
      { timeout: EXEC_TIMEOUT_MS, stdio: "ignore" }
    );
  } catch {
  }
}
function winTarget(account) {
  return `${KEYCHAIN_SERVICE$1}:${account}`;
}
const WIN_ADDTYPE = `
$ErrorActionPreference='Stop'
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class ConsortiumCredMan {
  [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
  public struct CREDENTIAL {
    public uint Flags; public uint Type; public string TargetName; public string Comment;
    public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
    public uint CredentialBlobSize; public IntPtr CredentialBlob; public uint Persist;
    public uint AttributeCount; public IntPtr Attributes; public string TargetAlias; public string UserName;
  }
  [DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
  public static extern bool CredWrite(ref CREDENTIAL c, uint flags);
  [DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
  public static extern bool CredRead(string target, uint type, uint flags, out IntPtr credential);
  [DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
  public static extern bool CredDelete(string target, uint type, uint flags);
  [DllImport("advapi32.dll")] public static extern void CredFree(IntPtr credential);
}
"@
`;
function runPowerShell(script, env) {
  try {
    return node_child_process.execFileSync(
      "powershell.exe",
      ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", WIN_ADDTYPE + script],
      {
        encoding: "utf8",
        timeout: EXEC_TIMEOUT_MS,
        stdio: ["ignore", "pipe", "ignore"],
        env: { ...process.env, ...env }
      }
    );
  } catch {
    return null;
  }
}
function readWindows(account) {
  const out = runPowerShell(
    `
$t=$env:CONSORTIUM_KEK_TARGET
$p=[IntPtr]::Zero
if(-not [ConsortiumCredMan]::CredRead($t,1,0,[ref]$p)){ exit 1 }
$c=[System.Runtime.InteropServices.Marshal]::PtrToStructure($p,[Type][ConsortiumCredMan+CREDENTIAL])
$bytes=New-Object byte[] $c.CredentialBlobSize
[System.Runtime.InteropServices.Marshal]::Copy($c.CredentialBlob,$bytes,0,$c.CredentialBlobSize)
[ConsortiumCredMan]::CredFree($p)
[System.Text.Encoding]::UTF8.GetString($bytes)
`,
    { CONSORTIUM_KEK_TARGET: winTarget(account) }
  );
  return out === null ? null : decodeKek(out);
}
function storeWindows(account, b64) {
  const out = runPowerShell(
    `
$t=$env:CONSORTIUM_KEK_TARGET
$v=$env:CONSORTIUM_KEK_VALUE
$bytes=[System.Text.Encoding]::UTF8.GetBytes($v)
$blob=[System.Runtime.InteropServices.Marshal]::AllocHGlobal($bytes.Length)
[System.Runtime.InteropServices.Marshal]::Copy($bytes,0,$blob,$bytes.Length)
$cred=New-Object ConsortiumCredMan+CREDENTIAL
$cred.Type=1; $cred.TargetName=$t; $cred.Persist=2
$cred.CredentialBlobSize=$bytes.Length; $cred.CredentialBlob=$blob; $cred.UserName=$t
$ok=[ConsortiumCredMan]::CredWrite([ref]$cred,0)
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($blob)
if($ok){ 'OK' } else { exit 1 }
`,
    { CONSORTIUM_KEK_TARGET: winTarget(account), CONSORTIUM_KEK_VALUE: b64 }
  );
  return out !== null && out.includes("OK");
}
function deleteWindows(account) {
  runPowerShell(
    `
$t=$env:CONSORTIUM_KEK_TARGET
[ConsortiumCredMan]::CredDelete($t,1,0) | Out-Null
`,
    { CONSORTIUM_KEK_TARGET: winTarget(account) }
  );
}
function readKeychainKek() {
  const account = currentAccount();
  switch (process.platform) {
    case "darwin":
      return readMac(account);
    case "linux":
      return readLinux(account);
    case "win32":
      return readWindows(account);
    default:
      return null;
  }
}
function writeKeychainKek(kek) {
  const account = currentAccount();
  const b64 = kek.toString("base64");
  let stored;
  switch (process.platform) {
    case "darwin":
      stored = storeMac(account, b64);
      break;
    case "linux":
      stored = storeLinux(account, b64);
      break;
    case "win32":
      stored = storeWindows(account, b64);
      break;
    default:
      return false;
  }
  if (!stored) return false;
  const readBack = readKeychainKek();
  return readBack !== null && readBack.equals(kek);
}
function deleteKeychainKek() {
  const account = currentAccount();
  switch (process.platform) {
    case "darwin":
      deleteMac(account);
      break;
    case "linux":
      deleteLinux(account);
      break;
    case "win32":
      deleteWindows(account);
      break;
  }
}
const systemKeychainBackend = {
  readKeychainKek,
  writeKeychainKek
};
function resolveCredKek(deps) {
  const { keychainEnabled, backend, file } = deps;
  if (!keychainEnabled) {
    const existing2 = file.read();
    if (existing2) return { kek: existing2, source: "file" };
    const kek2 = file.mint();
    file.write(kek2);
    return { kek: kek2, source: "file" };
  }
  const fromKeychain = backend.readKeychainKek();
  if (fromKeychain) return { kek: fromKeychain, source: "keychain-hit" };
  const existing = file.read();
  const kek = existing ?? file.mint();
  if (backend.writeKeychainKek(kek)) {
    file.remove();
    return { kek, source: "keychain-stored" };
  }
  if (!existing) file.write(kek);
  return { kek, source: "file-fallback" };
}

const AnthropicConfigSchema = z__namespace.object({
  baseUrl: z__namespace.string().url().optional(),
  authToken: z__namespace.string().optional(),
  model: z__namespace.string().optional()
});
const OpenAIConfigSchema = z__namespace.object({
  apiKey: z__namespace.string().optional(),
  baseUrl: z__namespace.string().url().optional(),
  model: z__namespace.string().optional()
});
const AzureOpenAIConfigSchema = z__namespace.object({
  apiKey: z__namespace.string().optional(),
  endpoint: z__namespace.string().url().optional(),
  apiVersion: z__namespace.string().optional(),
  deploymentName: z__namespace.string().optional()
});
const TogetherAIConfigSchema = z__namespace.object({
  apiKey: z__namespace.string().optional(),
  model: z__namespace.string().optional()
});
const TmuxConfigSchema = z__namespace.object({
  sessionName: z__namespace.string().optional(),
  tmpDir: z__namespace.string().optional(),
  updateEnvironment: z__namespace.boolean().optional()
});
const EnvironmentVariableSchema = z__namespace.object({
  name: z__namespace.string().regex(/^[A-Z_][A-Z0-9_]*$/, "Invalid environment variable name"),
  value: z__namespace.string()
});
const ProfileCompatibilitySchema = z__namespace.object({
  claude: z__namespace.boolean().default(true),
  codex: z__namespace.boolean().default(true),
  gemini: z__namespace.boolean().default(true),
  // Renamed from 'opencode' to 'consortium-code'. Old profiles with 'opencode'
  // field are handled by passthrough — the default(true) ensures new profiles
  // get the correct value.
  "consortium-code": z__namespace.boolean().default(true)
}).passthrough();
const AIBackendProfileSchema = z__namespace.object({
  id: z__namespace.string().uuid(),
  name: z__namespace.string().min(1).max(100),
  description: z__namespace.string().max(500).optional(),
  // Agent-specific configurations
  anthropicConfig: AnthropicConfigSchema.optional(),
  openaiConfig: OpenAIConfigSchema.optional(),
  azureOpenAIConfig: AzureOpenAIConfigSchema.optional(),
  togetherAIConfig: TogetherAIConfigSchema.optional(),
  // Tmux configuration
  tmuxConfig: TmuxConfigSchema.optional(),
  // Environment variables (validated)
  environmentVariables: z__namespace.array(EnvironmentVariableSchema).default([]),
  // Default session type for this profile
  defaultSessionType: z__namespace.enum(["simple", "worktree"]).optional(),
  // Default permission mode for this profile (supports both Claude and Codex modes)
  defaultPermissionMode: z__namespace.enum([
    "default",
    "acceptEdits",
    "bypassPermissions",
    "plan",
    // Claude modes
    "read-only",
    "safe-yolo",
    "yolo"
    // Codex modes
  ]).optional(),
  // Default model mode for this profile
  defaultModelMode: z__namespace.string().optional(),
  // Compatibility metadata
  compatibility: ProfileCompatibilitySchema.default({ claude: true, codex: true, gemini: true, "consortium-code": true }),
  // Built-in profile indicator
  isBuiltIn: z__namespace.boolean().default(false),
  // Metadata
  createdAt: z__namespace.number().default(() => Date.now()),
  updatedAt: z__namespace.number().default(() => Date.now()),
  version: z__namespace.string().default("1.0.0")
});
function validateProfileForAgent(profile, agent) {
  return profile.compatibility[agent];
}
function getProfileEnvironmentVariables(profile) {
  const envVars = {};
  profile.environmentVariables.forEach((envVar) => {
    envVars[envVar.name] = envVar.value;
  });
  if (profile.anthropicConfig) {
    if (profile.anthropicConfig.baseUrl) envVars.ANTHROPIC_BASE_URL = profile.anthropicConfig.baseUrl;
    if (profile.anthropicConfig.authToken) envVars.ANTHROPIC_AUTH_TOKEN = profile.anthropicConfig.authToken;
    if (profile.anthropicConfig.model) envVars.ANTHROPIC_MODEL = profile.anthropicConfig.model;
  }
  if (profile.openaiConfig) {
    if (profile.openaiConfig.apiKey) envVars.OPENAI_API_KEY = profile.openaiConfig.apiKey;
    if (profile.openaiConfig.baseUrl) envVars.OPENAI_BASE_URL = profile.openaiConfig.baseUrl;
    if (profile.openaiConfig.model) envVars.OPENAI_MODEL = profile.openaiConfig.model;
  }
  if (profile.azureOpenAIConfig) {
    if (profile.azureOpenAIConfig.apiKey) envVars.AZURE_OPENAI_API_KEY = profile.azureOpenAIConfig.apiKey;
    if (profile.azureOpenAIConfig.endpoint) envVars.AZURE_OPENAI_ENDPOINT = profile.azureOpenAIConfig.endpoint;
    if (profile.azureOpenAIConfig.apiVersion) envVars.AZURE_OPENAI_API_VERSION = profile.azureOpenAIConfig.apiVersion;
    if (profile.azureOpenAIConfig.deploymentName) envVars.AZURE_OPENAI_DEPLOYMENT_NAME = profile.azureOpenAIConfig.deploymentName;
  }
  if (profile.togetherAIConfig) {
    if (profile.togetherAIConfig.apiKey) envVars.TOGETHER_API_KEY = profile.togetherAIConfig.apiKey;
    if (profile.togetherAIConfig.model) envVars.TOGETHER_MODEL = profile.togetherAIConfig.model;
  }
  if (profile.tmuxConfig) {
    if (profile.tmuxConfig.sessionName !== void 0) envVars.TMUX_SESSION_NAME = profile.tmuxConfig.sessionName;
    if (profile.tmuxConfig.tmpDir) envVars.TMUX_TMPDIR = profile.tmuxConfig.tmpDir;
    if (profile.tmuxConfig.updateEnvironment !== void 0) {
      envVars.TMUX_UPDATE_ENVIRONMENT = profile.tmuxConfig.updateEnvironment.toString();
    }
  }
  return envVars;
}
const SUPPORTED_SCHEMA_VERSION = 3;
const DEFAULT_RELAY_TRUST_POLICY = {
  mode: "prompt-per-app",
  perApp: {}
};
const defaultSettings = {
  schemaVersion: SUPPORTED_SCHEMA_VERSION,
  onboardingCompleted: false,
  profiles: [],
  localEnvironmentVariables: {},
  relayTrustPolicy: { ...DEFAULT_RELAY_TRUST_POLICY, perApp: {} }
};
function migrateSettings(raw, fromVersion) {
  let migrated = { ...raw };
  if (fromVersion < 2) {
    if (!migrated.profiles) {
      migrated.profiles = [];
    }
    if (!migrated.localEnvironmentVariables) {
      migrated.localEnvironmentVariables = {};
    }
    migrated.schemaVersion = 2;
  }
  if (fromVersion < 3) {
    if (!migrated.relayTrustPolicy || typeof migrated.relayTrustPolicy !== "object") {
      migrated.relayTrustPolicy = { ...DEFAULT_RELAY_TRUST_POLICY, perApp: {} };
    } else {
      if (!migrated.relayTrustPolicy.mode) {
        migrated.relayTrustPolicy.mode = DEFAULT_RELAY_TRUST_POLICY.mode;
      }
      if (!migrated.relayTrustPolicy.perApp || typeof migrated.relayTrustPolicy.perApp !== "object") {
        migrated.relayTrustPolicy.perApp = {};
      }
    }
    migrated.schemaVersion = 3;
  }
  return migrated;
}
async function readSettings() {
  if (!fs.existsSync(configuration.settingsFile)) {
    return { ...defaultSettings };
  }
  try {
    const content = await fs$3.readFile(configuration.settingsFile, "utf8");
    const raw = JSON.parse(content);
    const schemaVersion = raw.schemaVersion ?? 1;
    if (schemaVersion > SUPPORTED_SCHEMA_VERSION) {
      logger.warn(
        `\u26A0\uFE0F Settings schema v${schemaVersion} > supported v${SUPPORTED_SCHEMA_VERSION}. Update consortium-cli for full functionality.`
      );
    }
    const migrated = migrateSettings(raw, schemaVersion);
    if (migrated.profiles && Array.isArray(migrated.profiles)) {
      const validProfiles = [];
      for (const profile of migrated.profiles) {
        try {
          const validated = AIBackendProfileSchema.parse(profile);
          validProfiles.push(validated);
        } catch (error) {
          logger.warn(
            `\u26A0\uFE0F Invalid profile "${profile?.name || profile?.id || "unknown"}" - skipping. Error: ${error.message}`
          );
        }
      }
      migrated.profiles = validProfiles;
    }
    const merged = { ...defaultSettings, ...migrated };
    if (!merged.relayTrustPolicy) {
      merged.relayTrustPolicy = { ...DEFAULT_RELAY_TRUST_POLICY, perApp: {} };
    }
    return merged;
  } catch (error) {
    logger.warn(`Failed to read settings: ${error.message}`);
    return { ...defaultSettings };
  }
}
async function updateSettings(updater) {
  const LOCK_RETRY_INTERVAL_MS = 100;
  const MAX_LOCK_ATTEMPTS = 50;
  const STALE_LOCK_TIMEOUT_MS = 1e4;
  const lockFile = configuration.settingsFile + ".lock";
  const tmpFile = configuration.settingsFile + ".tmp";
  let fileHandle;
  let attempts = 0;
  while (attempts < MAX_LOCK_ATTEMPTS) {
    try {
      fileHandle = await fs$3.open(lockFile, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
      break;
    } catch (err) {
      if (err.code === "EEXIST") {
        attempts++;
        await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS));
        try {
          const stats = await fs$3.stat(lockFile);
          if (Date.now() - stats.mtimeMs > STALE_LOCK_TIMEOUT_MS) {
            await fs$3.unlink(lockFile).catch(() => {
            });
          }
        } catch {
        }
      } else {
        throw err;
      }
    }
  }
  if (!fileHandle) {
    throw new Error(`Failed to acquire settings lock after ${MAX_LOCK_ATTEMPTS * LOCK_RETRY_INTERVAL_MS / 1e3} seconds`);
  }
  try {
    const current = await readSettings() || { ...defaultSettings };
    const updated = await updater(current);
    if (!fs.existsSync(configuration.consortiumHomeDir)) {
      await fs$3.mkdir(configuration.consortiumHomeDir, { recursive: true, mode: 448 });
    }
    await fs$3.writeFile(tmpFile, JSON.stringify(updated, null, 2), { mode: 384 });
    await fs$3.rename(tmpFile, configuration.settingsFile);
    return updated;
  } finally {
    await fileHandle.close();
    await fs$3.unlink(lockFile).catch(() => {
    });
  }
}
const credentialsSchema$1 = z__namespace.object({
  token: z__namespace.string(),
  secret: z__namespace.string().base64().nullish(),
  // Legacy
  encryption: z__namespace.object({
    publicKey: z__namespace.string().base64(),
    machineKey: z__namespace.string().base64()
  }).nullish()
}).passthrough();
const CRED_ENVELOPE_TYPE = "consortium-cred-v1";
function credKekPath() {
  return path.join(configuration.consortiumHomeDir, "access.kek");
}
function credKekFileOps() {
  const p = credKekPath();
  return {
    read: () => {
      try {
        if (fs.existsSync(p)) {
          const buf = fs.readFileSync(p);
          if (buf.length >= 32) return buf;
        }
      } catch {
      }
      return null;
    },
    write: (kek) => {
      if (!fs.existsSync(configuration.consortiumHomeDir)) {
        fs.mkdirSync(configuration.consortiumHomeDir, { recursive: true, mode: 448 });
      }
      fs.writeFileSync(p, kek, { mode: 384 });
      try {
        fs.chmodSync(p, 384);
      } catch {
      }
    },
    remove: () => {
      try {
        if (fs.existsSync(p)) fs.unlinkSync(p);
      } catch {
      }
    },
    mint: () => node_crypto.randomBytes(32)
  };
}
function readOrCreateCredKek() {
  return resolveCredKek({
    keychainEnabled: isKeychainKekEnabled(),
    backend: systemKeychainBackend,
    file: credKekFileOps()
  }).kek;
}
async function readCredentialPayload() {
  const raw = await fs$3.readFile(configuration.privateKeyFile, "utf8");
  const outer = JSON.parse(raw);
  if (outer && typeof outer === "object" && outer.__consortiumCred === CRED_ENVELOPE_TYPE) {
    const kek = readOrCreateCredKek();
    const pt = openWithMaterial(outer, kek);
    if (pt === null) throw new Error("access.key decryption failed");
    return { obj: JSON.parse(pt), wasEncrypted: true };
  }
  return { obj: outer, wasEncrypted: false };
}
async function persistCredentials(payload) {
  if (!fs.existsSync(configuration.consortiumHomeDir)) {
    await fs$3.mkdir(configuration.consortiumHomeDir, { recursive: true, mode: 448 });
  }
  const canonical = JSON.stringify(payload);
  if (fs.existsSync(configuration.privateKeyFile)) {
    try {
      const existing = await readCredentialPayload();
      if (existing.wasEncrypted && JSON.stringify(existing.obj) === canonical) {
        try {
          await fs$3.chmod(configuration.privateKeyFile, 384);
        } catch {
        }
        return;
      }
    } catch {
    }
  }
  const kek = readOrCreateCredKek();
  const sealed = sealWithMaterial(JSON.stringify(payload, null, 2), kek);
  const envelope = { __consortiumCred: CRED_ENVELOPE_TYPE, ...sealed };
  writeFileAtomicSync(configuration.privateKeyFile, JSON.stringify(envelope, null, 2));
}
async function readCredentials() {
  if (!fs.existsSync(configuration.privateKeyFile)) {
    return null;
  }
  try {
    const payload = await readCredentialPayload();
    const credentials = credentialsSchema$1.parse(payload.obj);
    if (!payload.wasEncrypted) {
      try {
        await persistCredentials(payload.obj);
      } catch (err) {
        logger.debug("[PERSISTENCE] access.key encrypt-at-rest migration deferred:", err);
      }
    }
    if (credentials.secret) {
      const resolved = {
        token: credentials.token,
        encryption: {
          type: "legacy",
          secret: new Uint8Array(Buffer.from(credentials.secret, "base64"))
        }
      };
      void configureSealedDekContent(resolved);
      return resolved;
    } else if (credentials.encryption) {
      return {
        token: credentials.token,
        encryption: {
          type: "dataKey",
          publicKey: new Uint8Array(Buffer.from(credentials.encryption.publicKey, "base64")),
          machineKey: new Uint8Array(Buffer.from(credentials.encryption.machineKey, "base64"))
        }
      };
    } else {
      return {
        token: credentials.token,
        encryption: null
      };
    }
  } catch {
    return null;
  }
  return null;
}
async function writeCredentialsLegacy(credentials) {
  await persistCredentials({
    secret: encodeBase64(credentials.secret),
    token: credentials.token
  });
}
async function writeCredentialsDataKey(credentials) {
  await persistCredentials({
    encryption: { publicKey: encodeBase64(credentials.publicKey), machineKey: encodeBase64(credentials.machineKey) },
    token: credentials.token
  });
}
async function writeCredentialsTokenOnly(token) {
  await persistCredentials({ token });
}
async function clearCredentials() {
  if (fs.existsSync(configuration.privateKeyFile)) {
    await fs$3.unlink(configuration.privateKeyFile);
  }
  try {
    if (fs.existsSync(credKekPath())) await fs$3.unlink(credKekPath());
  } catch {
  }
  if (isKeychainKekEnabled()) {
    try {
      deleteKeychainKek();
    } catch {
    }
  }
}
async function clearMachineId() {
  await updateSettings((settings) => ({
    ...settings,
    machineId: void 0
  }));
}
async function readDaemonState() {
  try {
    if (!fs.existsSync(configuration.daemonStateFile)) {
      return null;
    }
    const content = await fs$3.readFile(configuration.daemonStateFile, "utf-8");
    return JSON.parse(content);
  } catch (error) {
    console.error(`[PERSISTENCE] Daemon state file corrupted: ${configuration.daemonStateFile}`, error);
    return null;
  }
}
function readDaemonStateSync() {
  try {
    if (!fs.existsSync(configuration.daemonStateFile)) {
      return null;
    }
    const content = fs.readFileSync(configuration.daemonStateFile, "utf-8");
    return JSON.parse(content);
  } catch (error) {
    logger.debug(`[PERSISTENCE] Daemon state file corrupted (sync read): ${configuration.daemonStateFile}`, error);
    return null;
  }
}
function writeDaemonState(state) {
  writeFileAtomicSync(configuration.daemonStateFile, JSON.stringify(state, null, 2));
}
function writeFileAtomicSync(target, payload) {
  const tmp = `${target}.tmp.${process.pid}`;
  const fd = fs.openSync(tmp, fs.constants.O_CREAT | fs.constants.O_TRUNC | fs.constants.O_WRONLY, 384);
  try {
    fs.writeSync(fd, payload);
    fs.fsyncSync(fd);
  } finally {
    fs.closeSync(fd);
  }
  try {
    fs.renameSync(tmp, target);
  } catch (err) {
    try {
      fs.unlinkSync(tmp);
    } catch {
    }
    throw err;
  }
  try {
    fs.chmodSync(target, 384);
  } catch {
  }
}
async function clearDaemonState() {
  if (fs.existsSync(configuration.daemonStateFile)) {
    try {
      await fs$3.unlink(configuration.daemonStateFile);
    } catch (err) {
      if (err.code !== "ENOENT") throw err;
    }
  }
}
async function forceCleanupStaleLock() {
  if (fs.existsSync(configuration.daemonLockFile)) {
    try {
      await fs$3.unlink(configuration.daemonLockFile);
    } catch {
    }
  }
}
const STALE_LOCK_MTIME_MS = 15 * 60 * 1e3;
function touchDaemonLock() {
  try {
    const now = /* @__PURE__ */ new Date();
    fs.utimesSync(configuration.daemonLockFile, now, now);
  } catch {
  }
}
async function acquireDaemonLock(maxAttempts = 5, delayIncrementMs = 200) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      const fileHandle = await fs$3.open(
        configuration.daemonLockFile,
        fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY,
        384
      );
      await fileHandle.writeFile(String(process.pid));
      return fileHandle;
    } catch (error) {
      if (error.code === "EEXIST") {
        try {
          const lockPid = fs.readFileSync(configuration.daemonLockFile, "utf-8").trim();
          if (lockPid && !isNaN(Number(lockPid))) {
            const pid = Number(lockPid);
            let processAlive = false;
            try {
              process.kill(pid, 0);
              processAlive = true;
            } catch {
            }
            if (!processAlive) {
              const tmpLockFile = configuration.daemonLockFile + `.stale.${process.pid}`;
              try {
                fs.renameSync(configuration.daemonLockFile, tmpLockFile);
                const confirmedPid = fs.readFileSync(tmpLockFile, "utf-8").trim();
                if (confirmedPid === lockPid) {
                  fs.unlinkSync(tmpLockFile);
                } else {
                  try {
                    fs.renameSync(tmpLockFile, configuration.daemonLockFile);
                  } catch {
                    try {
                      fs.unlinkSync(tmpLockFile);
                    } catch {
                    }
                  }
                }
              } catch (renameErr) {
                if (renameErr.code === "ENOENT") {
                } else {
                  throw renameErr;
                }
              }
              continue;
            }
          }
        } catch (readErr) {
          if (readErr.code !== "ENOENT") {
            try {
              const ageMs = Date.now() - fs.statSync(configuration.daemonLockFile).mtimeMs;
              if (ageMs > STALE_LOCK_MTIME_MS) {
                fs.unlinkSync(configuration.daemonLockFile);
              }
            } catch {
            }
          }
        }
      }
      if (attempt === maxAttempts) {
        return null;
      }
      const delayMs = attempt * delayIncrementMs;
      await new Promise((resolve) => setTimeout(resolve, delayMs));
    }
  }
  return null;
}
async function releaseDaemonLock(lockHandle) {
  try {
    await lockHandle.close();
  } catch {
  }
  try {
    if (fs.existsSync(configuration.daemonLockFile)) {
      fs.unlinkSync(configuration.daemonLockFile);
    }
  } catch {
  }
}
function activeOrgKey(serverUrl, token) {
  const material = token ? `${serverUrl}\0${token}` : `${serverUrl}\0__nocreds__`;
  return node_crypto.createHash("sha256").update(material).digest("hex").slice(0, 16);
}
async function currentActiveOrgKey() {
  let token = null;
  try {
    const creds = await readCredentials();
    token = creds?.token ?? null;
  } catch {
  }
  return activeOrgKey(configuration.serverUrl, token);
}
async function getActiveOrgId() {
  const settings = await readSettings();
  const key = await currentActiveOrgKey();
  const keyed = settings.activeOrgByKey?.[key];
  if (keyed) return keyed;
  if (settings.activeOrgId) {
    await setActiveOrgId(settings.activeOrgId);
    return settings.activeOrgId;
  }
  return null;
}
async function setActiveOrgId(orgId) {
  const key = await currentActiveOrgKey();
  await updateSettings((settings) => {
    const byKey = { ...settings.activeOrgByKey ?? {} };
    if (orgId) {
      byKey[key] = orgId;
    } else {
      delete byKey[key];
    }
    return {
      ...settings,
      activeOrgByKey: byKey,
      // Drop the legacy global field — keyed storage is authoritative now.
      activeOrgId: void 0
    };
  });
}
async function clearActiveOrgId() {
  await setActiveOrgId(void 0);
}
async function currentBrainScopeKey() {
  let token = null;
  try {
    token = (await readCredentials())?.token ?? null;
  } catch {
  }
  return activeOrgKey(configuration.serverUrl, token);
}
async function getActiveBrainKey() {
  const settings = await readSettings();
  const scope = await currentBrainScopeKey();
  return settings.activeBrainByKey?.[scope] ?? null;
}
async function setActiveBrainKey(brainKey) {
  const scope = await currentBrainScopeKey();
  await updateSettings((settings) => {
    const byKey = { ...settings.activeBrainByKey ?? {} };
    if (brainKey) byKey[scope] = brainKey;
    else delete byKey[scope];
    return { ...settings, activeBrainByKey: byKey };
  });
}
async function listBrains() {
  const settings = await readSettings();
  return Object.values(settings.brains ?? {});
}
async function getBrain(brainKey) {
  const settings = await readSettings();
  return settings.brains?.[brainKey] ?? null;
}
async function getActiveBrain() {
  const key = await getActiveBrainKey();
  if (!key) return null;
  return getBrain(key);
}
async function upsertBrain(entry) {
  await updateSettings((settings) => ({
    ...settings,
    brains: { ...settings.brains ?? {}, [entry.key]: entry }
  }));
}
async function setBrainSeq(brainKey, lastSeq) {
  await updateSettings((settings) => {
    const existing = settings.brains?.[brainKey];
    if (!existing) return settings;
    return {
      ...settings,
      brains: { ...settings.brains ?? {}, [brainKey]: { ...existing, lastSeq } }
    };
  });
}
async function readRelayTrustPolicy() {
  const settings = await readSettings();
  const policy = settings.relayTrustPolicy;
  if (!policy) {
    return { ...DEFAULT_RELAY_TRUST_POLICY, perApp: {} };
  }
  return {
    mode: policy.mode ?? DEFAULT_RELAY_TRUST_POLICY.mode,
    perApp: policy.perApp ?? {}
  };
}
async function writeRelayTrustPolicy(updater) {
  const updated = await updateSettings((settings) => {
    const current = settings.relayTrustPolicy ?? { ...DEFAULT_RELAY_TRUST_POLICY, perApp: {} };
    const next = updater(current);
    return {
      ...settings,
      relayTrustPolicy: {
        mode: next.mode ?? DEFAULT_RELAY_TRUST_POLICY.mode,
        perApp: next.perApp ?? {}
      }
    };
  });
  return updated.relayTrustPolicy ?? { ...DEFAULT_RELAY_TRUST_POLICY, perApp: {} };
}

var persistence = /*#__PURE__*/Object.freeze({
  __proto__: null,
  AIBackendProfileSchema: AIBackendProfileSchema,
  STALE_LOCK_MTIME_MS: STALE_LOCK_MTIME_MS,
  SUPPORTED_SCHEMA_VERSION: SUPPORTED_SCHEMA_VERSION,
  acquireDaemonLock: acquireDaemonLock,
  clearActiveOrgId: clearActiveOrgId,
  clearCredentials: clearCredentials,
  clearDaemonState: clearDaemonState,
  clearMachineId: clearMachineId,
  forceCleanupStaleLock: forceCleanupStaleLock,
  getActiveBrain: getActiveBrain,
  getActiveBrainKey: getActiveBrainKey,
  getActiveOrgId: getActiveOrgId,
  getBrain: getBrain,
  getProfileEnvironmentVariables: getProfileEnvironmentVariables,
  listBrains: listBrains,
  readCredentials: readCredentials,
  readDaemonState: readDaemonState,
  readDaemonStateSync: readDaemonStateSync,
  readRelayTrustPolicy: readRelayTrustPolicy,
  readSettings: readSettings,
  releaseDaemonLock: releaseDaemonLock,
  setActiveBrainKey: setActiveBrainKey,
  setActiveOrgId: setActiveOrgId,
  setBrainSeq: setBrainSeq,
  touchDaemonLock: touchDaemonLock,
  updateSettings: updateSettings,
  upsertBrain: upsertBrain,
  validateProfileForAgent: validateProfileForAgent,
  writeCredentialsDataKey: writeCredentialsDataKey,
  writeCredentialsLegacy: writeCredentialsLegacy,
  writeCredentialsTokenOnly: writeCredentialsTokenOnly,
  writeDaemonState: writeDaemonState,
  writeRelayTrustPolicy: writeRelayTrustPolicy
});

const DEFAULT_TIMEOUT_MS = 1e4;
const REQUEST_EVENT = "external-relay-trust-request";
const RESPONSE_EVENT = "external-relay-trust-response";
const KNOWN_DECISIONS = /* @__PURE__ */ new Set([
  "allow",
  "allow-always",
  "deny",
  "deny-always"
]);
function isTrustResponsePayload(payload) {
  if (!payload || typeof payload !== "object") return false;
  const candidate = payload;
  return typeof candidate.promptId === "string" && typeof candidate.decision === "string" && KNOWN_DECISIONS.has(candidate.decision);
}
async function requestTrustFromMobile(args) {
  const { socket, appId, kind, sessionId, machineId } = args;
  const timeoutMs = args.timeoutMs ?? DEFAULT_TIMEOUT_MS;
  const promptId = node_crypto.randomUUID();
  return await new Promise((resolve) => {
    let settled = false;
    let timer = null;
    const cleanup = () => {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      try {
        socket.off(RESPONSE_EVENT, listener);
      } catch (err) {
        logger.debug("[relay/trust] Failed to detach trust-response listener", { err, promptId });
      }
    };
    const finish = (outcome) => {
      if (settled) return;
      settled = true;
      cleanup();
      resolve(outcome);
    };
    const listener = (payload) => {
      if (!isTrustResponsePayload(payload)) return;
      if (payload.promptId !== promptId) return;
      finish(payload.decision);
    };
    try {
      socket.on(RESPONSE_EVENT, listener);
    } catch (err) {
      logger.debug("[relay/trust] Failed to attach trust-response listener", { err, promptId });
      resolve("timeout");
      return;
    }
    timer = setTimeout(() => finish("timeout"), timeoutMs);
    const maybeUnref = timer.unref;
    if (typeof maybeUnref === "function") {
      try {
        maybeUnref.call(timer);
      } catch {
      }
    }
    try {
      socket.emit(REQUEST_EVENT, {
        promptId,
        appId,
        kind,
        sessionId,
        machineId,
        requestedAt: Date.now()
      });
    } catch (err) {
      logger.debug("[relay/trust] Failed to emit trust-request event", { err, promptId });
      finish("timeout");
    }
  });
}

const PAT_SYNTHETIC_APP_ID = "__pat__";
let cached = null;
async function loadPolicy() {
  if (cached) return cached;
  cached = await readRelayTrustPolicy();
  return cached;
}
function invalidateCache() {
  cached = null;
}
async function readPerAppPolicy(appId) {
  const policy = await loadPolicy();
  const entry = policy.perApp[appId];
  return entry ?? null;
}
async function writePerAppPolicy(appId, policy) {
  await writeRelayTrustPolicy((current) => ({
    mode: current.mode,
    perApp: { ...current.perApp, [appId]: policy }
  }));
  invalidateCache();
}
async function persistIfAlways(appId, outcome) {
  if (outcome === "allow-always") {
    await writePerAppPolicy(appId, "allow");
  } else if (outcome === "deny-always") {
    await writePerAppPolicy(appId, "deny");
  }
}
function outcomeToDecision(outcome) {
  if (outcome === "allow" || outcome === "allow-always") return "allow";
  return "deny";
}
async function promptAndPersist(appId, kind, socket, sessionId, machineId, promptTimeoutMs) {
  if (!socket) {
    logger.debug("[relay/trust] No socket available for mobile prompt \u2014 denying", { appId, kind });
    return "deny";
  }
  const outcome = await requestTrustFromMobile({
    socket,
    appId,
    kind,
    sessionId,
    machineId,
    timeoutMs: promptTimeoutMs
  });
  await persistIfAlways(appId, outcome);
  return outcomeToDecision(outcome);
}
async function evaluateTrust(input) {
  const { appId, kind, sessionId, machineId, socket, promptTimeoutMs } = input;
  if (!appId) {
    if (kind !== "machine.invoke") {
      return "allow";
    }
    const existing2 = await readPerAppPolicy(PAT_SYNTHETIC_APP_ID);
    if (existing2) return existing2;
    return await promptAndPersist(PAT_SYNTHETIC_APP_ID, kind, socket, sessionId, machineId, promptTimeoutMs);
  }
  const policy = await loadPolicy();
  const existing = policy.perApp[appId];
  if (existing === "allow" || existing === "deny") {
    return existing;
  }
  if (policy.mode === "auto-accept" && kind !== "machine.invoke") {
    return "allow";
  }
  return await promptAndPersist(appId, kind, socket, sessionId, machineId, promptTimeoutMs);
}

function isRelayRequest(event) {
  if (!event || typeof event !== "object") return false;
  const candidate = event;
  return candidate.type === "external-relay-request" && typeof candidate.requestId === "string" && typeof candidate.kind === "string" && typeof candidate.payload === "object" && candidate.payload !== null;
}
function isRelayEnabled() {
  return process.env.CONSORTIUM_RELAY_ENABLED === "1";
}
function createRelayHandler(options) {
  const { socket, session } = options;
  const reply = (response) => {
    try {
      ;
      socket.emit("external-relay-response", response);
    } catch (err) {
      logger.debug("[relay] Failed to emit relay response", { err, requestId: response.requestId });
    }
  };
  const handleSessionSendMessage = (event) => {
    const payload = event.payload;
    if (typeof payload.content !== "string") {
      reply({
        requestId: event.requestId,
        status: "error",
        errorCode: "invalid_payload",
        errorMessage: "session.send-message requires payload.content: string"
      });
      return;
    }
    try {
      session.sendClaudeSessionMessage({
        type: "user",
        uuid: node_crypto.randomUUID(),
        message: { content: payload.content }
      });
    } catch (err) {
      logger.debug("[relay] sendClaudeSessionMessage threw", { err, requestId: event.requestId });
      reply({
        requestId: event.requestId,
        status: "error",
        errorCode: "internal",
        errorMessage: err instanceof Error ? err.message : String(err)
      });
      return;
    }
    reply({
      requestId: event.requestId,
      status: "ok",
      result: { enqueued: true, timestamp: Date.now() }
    });
  };
  const handleMachineInvoke = async (event) => {
    const payload = event.payload;
    if (typeof payload.command !== "string" || payload.command.length === 0) {
      reply({
        requestId: event.requestId,
        status: "error",
        errorCode: "invalid_payload",
        errorMessage: "machine.invoke requires payload.command: string"
      });
      return;
    }
    const args = Array.isArray(payload.args) ? payload.args.filter((a) => typeof a === "string") : void 0;
    const cwd = typeof payload.cwd === "string" ? payload.cwd : void 0;
    const timeoutMs = typeof payload.timeoutMs === "number" ? payload.timeoutMs : void 0;
    const result = await executeCommand({
      command: payload.command,
      args,
      cwd,
      timeoutMs
    });
    reply({
      requestId: event.requestId,
      status: "ok",
      result
    });
  };
  const dispatch = (event) => {
    try {
      switch (event.kind) {
        case "session.send-message":
          handleSessionSendMessage(event);
          return;
        case "machine.invoke":
          handleMachineInvoke(event).catch((err) => {
            logger.debug("[relay] machine.invoke handler rejected", { err, requestId: event.requestId });
            reply({
              requestId: event.requestId,
              status: "error",
              errorCode: "internal",
              errorMessage: err instanceof Error ? err.message : String(err)
            });
          });
          return;
        default:
          reply({
            requestId: event.requestId,
            status: "error",
            errorCode: "unknown_kind"
          });
          return;
      }
    } catch (err) {
      reply({
        requestId: event.requestId,
        status: "error",
        errorCode: "internal",
        errorMessage: err instanceof Error ? err.message : String(err)
      });
    }
  };
  const onEphemeral = (event) => {
    if (!isRelayRequest(event)) {
      return;
    }
    if (!isRelayEnabled()) {
      reply({
        requestId: event.requestId,
        status: "rejected",
        errorCode: "disabled"
      });
      return;
    }
    if (event.kind !== "session.send-message" && event.kind !== "machine.invoke") {
      reply({
        requestId: event.requestId,
        status: "error",
        errorCode: "unknown_kind"
      });
      return;
    }
    if (!event.appId && event.kind === "session.send-message") {
      dispatch(event);
      return;
    }
    evaluateTrust({
      appId: event.appId,
      kind: event.kind,
      sessionId: event.sessionId,
      machineId: event.machineId,
      socket
    }).then((decision) => {
      if (decision === "deny") {
        reply({
          requestId: event.requestId,
          status: "rejected",
          errorCode: "user_declined"
        });
        return;
      }
      dispatch(event);
    }).catch((err) => {
      logger.debug("[relay] Trust evaluation threw \u2014 failing closed", { err, requestId: event.requestId });
      reply({
        requestId: event.requestId,
        status: "rejected",
        errorCode: "user_declined"
      });
    });
  };
  return { onEphemeral };
}

const WRAPPED_KEY_NONCE_BYTES = tweetnacl.box.nonceLength;
tweetnacl.box.publicKeyLength;
function wrapDataEncryptionKey(dataEncryptionKey, appBoxPublicKey) {
  const ephemeral = tweetnacl.box.keyPair();
  const nonce = getRandomBytes(WRAPPED_KEY_NONCE_BYTES);
  const ciphertext = tweetnacl.box(
    dataEncryptionKey,
    nonce,
    appBoxPublicKey,
    ephemeral.secretKey
  );
  const bundle = new Uint8Array(
    ephemeral.publicKey.length + nonce.length + ciphertext.length
  );
  bundle.set(ephemeral.publicKey, 0);
  bundle.set(nonce, ephemeral.publicKey.length);
  bundle.set(ciphertext, ephemeral.publicKey.length + nonce.length);
  return bundle;
}

const SESSION_DEK_BYTES = tweetnacl.secretbox.keyLength;
async function rotateSessionKey(input) {
  const {
    serverBaseUrl,
    deviceToken,
    sessionId,
    currentKeyVersion,
    authorizedGrants
  } = input;
  if (currentKeyVersion < 1 || !Number.isInteger(currentKeyVersion)) {
    throw new Error(
      `sessionKeyRotate: currentKeyVersion must be a positive integer, got ${currentKeyVersion}`
    );
  }
  const seen = /* @__PURE__ */ new Set();
  for (const g of authorizedGrants) {
    if (g.appBoxPublicKey.length !== tweetnacl.box.publicKeyLength) {
      throw new Error(
        `sessionKeyRotate: appBoxPublicKey for ${g.appId} must be ${tweetnacl.box.publicKeyLength} bytes, got ${g.appBoxPublicKey.length}`
      );
    }
    if (seen.has(g.appId)) {
      throw new Error(`sessionKeyRotate: duplicate appId in authorizedGrants: ${g.appId}`);
    }
    seen.add(g.appId);
  }
  const newDek = getRandomBytes(SESSION_DEK_BYTES);
  const wrappedKeys = authorizedGrants.map((g) => ({
    appId: g.appId,
    wrappedKey: encodeBase64(wrapDataEncryptionKey(newDek, g.appBoxPublicKey))
  }));
  const newKeyVersion = currentKeyVersion + 1;
  const url = `${serverBaseUrl.replace(/\/+$/, "")}/v1/sessions/${encodeURIComponent(sessionId)}/rotate-key`;
  try {
    const response = await axios.post(
      url,
      { newKeyVersion, wrappedKeys },
      {
        headers: {
          "Authorization": `Bearer ${deviceToken}`,
          "Content-Type": "application/json"
        },
        timeout: 15e3,
        validateStatus: () => true
      }
    );
    if (response.status === 409) {
      throw new SessionKeyRotateVersionConflictError(
        extractServerErrorMessage(response.status, response.data)
      );
    }
    if (response.status < 200 || response.status >= 300) {
      throw new Error(extractServerErrorMessage(response.status, response.data));
    }
    const body = response.data;
    if (!body || typeof body.keyVersion !== "number" || typeof body.rotatedAt !== "number" || typeof body.appliedGrants !== "number" || typeof body.skippedRevokedGrants !== "number") {
      throw new Error(
        `sessionKeyRotate: malformed success response from ${url} \u2014 expected { keyVersion, rotatedAt, appliedGrants, skippedRevokedGrants }`
      );
    }
    return {
      newDek,
      newKeyVersion: body.keyVersion,
      rotatedAt: body.rotatedAt,
      appliedGrants: body.appliedGrants,
      skippedRevokedGrants: body.skippedRevokedGrants
    };
  } catch (error) {
    if (error instanceof SessionKeyRotateVersionConflictError) {
      throw error;
    }
    if (error instanceof Error && error.message.startsWith("sessionKeyRotate:")) {
      throw error;
    }
    if (axios.isAxiosError(error)) {
      const code = error.code ? ` (${error.code})` : "";
      throw new Error(`sessionKeyRotate: request to ${url} failed${code}: ${error.message}`);
    }
    const message = error instanceof Error ? error.message : String(error);
    throw new Error(`sessionKeyRotate: ${message}`);
  }
}
class SessionKeyRotateVersionConflictError extends Error {
  constructor(message) {
    super(message);
    this.name = "SessionKeyRotateVersionConflictError";
  }
}
function extractServerErrorMessage(status, body) {
  if (body && typeof body === "object") {
    const record = body;
    const error = typeof record.error === "string" ? record.error : void 0;
    const message = typeof record.message === "string" ? record.message : void 0;
    if (error && message) return `sessionKeyRotate: ${error}: ${message}`;
    if (error) return `sessionKeyRotate: ${error}`;
    if (message) return `sessionKeyRotate: ${message}`;
  }
  if (typeof body === "string" && body.length > 0) {
    return `sessionKeyRotate: HTTP ${status}: ${body}`;
  }
  return `sessionKeyRotate: HTTP ${status}`;
}

function isRotateRequest(event) {
  if (!event || typeof event !== "object") return false;
  const candidate = event;
  return candidate.type === "session-rotate-requested" && typeof candidate.sessionId === "string" && typeof candidate.reason === "string";
}
function createRotateHandler(options) {
  const onEphemeral = (event) => {
    if (!isRotateRequest(event)) return;
    (async () => {
      const sessionId = event.sessionId;
      try {
        const state = await options.lookupSession(sessionId);
        if (!state) {
          logger.debug("[rotate] Unknown session \u2014 dropping rotate request", { sessionId });
          return;
        }
        const result = await rotateSessionKey({
          serverBaseUrl: options.serverBaseUrl,
          deviceToken: options.deviceToken,
          sessionId,
          currentKeyVersion: state.currentKeyVersion,
          authorizedGrants: state.authorizedGrants
        });
        await options.onRotated(sessionId, result);
        logger.debug("[rotate] Session DEK rotated", {
          sessionId,
          newKeyVersion: result.newKeyVersion,
          appliedGrants: result.appliedGrants
        });
      } catch (err) {
        logger.debug("[rotate] Rotation failed", {
          sessionId,
          err: err instanceof Error ? err.message : String(err)
        });
      }
    })();
  };
  return { onEphemeral };
}

const MAX_ARTIFACT_FILE_BYTES = 512 * 1024;
class ApiSessionClient extends node_events.EventEmitter {
  token;
  sessionId;
  metadata;
  metadataVersion;
  agentState;
  agentStateVersion;
  socket;
  pendingMessages = [];
  pendingMessageCallback = null;
  // EPHEMERAL-1: optional subscriber for server-broadcast model-change
  // events (Path A set/clear). Runners register this to reconcile the
  // active model + repaint their terminal UI without waiting for the next
  // request. Null when no runner is interested (e.g. headless tooling).
  modelChangeCallback = null;
  rpcHandlerManager;
  agentStateLock = new AsyncLock();
  metadataLock = new AsyncLock();
  encryptionKey;
  encryptionVariant;
  // User content public key for wrapping library-artifact DEKs (dataKey accounts only).
  userContentPublicKey;
  // In-memory state for library artifacts created in this session (id -> state).
  artifactStates = /* @__PURE__ */ new Map();
  // Latest model id observed on this session's stream. Claude Code's SDK
  // result messages carry the canonical token totals (including cache_read
  // and cache_creation) but no model id — the model lives on assistant
  // and system-init messages. We cache it here so the result-message
  // usage report can attribute tokens to the right model.
  lastClaudeModel;
  // Buffer for session messages when the socket is disconnected. Drained on
  // the next `connect` event (which socket.io fires on both initial connect
  // and every reconnect). Without this, a brief network blip during an
  // agent's reply was silently dropping messages on the floor, which is why
  // sessions appeared to "only update when the user typed" — the typing
  // triggered a reconnect that caused the client to refetch missed state.
  pendingEmits = [];
  static MAX_PENDING_EMITS = 1e3;
  // Manual reconnect state. Socket.IO v4 does not auto-retry middleware-
  // level handshake rejections (the path the server takes for "Invalid
  // authentication token"), so we schedule reconnects ourselves with
  // exponential backoff.
  reconnectTimer = null;
  manualReconnectAttempts = 0;
  destroyed = false;
  // Watchdog armed when we buffer a payload while disconnected. If the
  // socket has not reconnected within FLUSH_WATCHDOG_MS we fall back to
  // scheduleManualReconnect() instead of waiting for Socket.IO's idle
  // backoff — otherwise scheduled-wake assistant output sits in
  // pendingEmits until the next user-typed message nudges the
  // connection alive, producing an n+1 visual lag.
  flushWatchdog = null;
  static FLUSH_WATCHDOG_MS = 2e3;
  /** Get encryption details for passing to child processes. */
  getEncryptionDetails() {
    return { key: this.encryptionKey, variant: this.encryptionVariant };
  }
  /**
   * Upload an agent-produced media blob to the server as a SessionArtifact.
   *
   * Phase 1.5 (E2EE): when the session content key is available (it always
   * is for a live ApiSessionClient — both 'legacy' and 'dataKey' variants
   * carry a 32-byte symmetric key that the app derives for the same
   * session), the bytes are encrypted client-side with libsodium secretbox
   * as `nonce || ciphertext` — the exact Drive-content primitive, so the
   * app decrypts with the code path it already ships — and the POST carries
   * `encVersion: 1`. If the key is missing/malformed (defensive: should not
   * happen), we fall back to the Phase-1 plaintext upload with no
   * `encVersion`, which the private-prefix + presigned-URL model still
   * gates. Server-side size caps apply to the CIPHERTEXT (secretbox adds
   * only 24-byte nonce + 16-byte MAC).
   *
   * Returns the artifact id and metadata so the caller can immediately
   * reference it in an outgoing agent message
   * (`{ type: 'artifact', artifactId, mime }`).
   */
  async uploadArtifact(args) {
    let payload = args.bytes;
    let encVersion;
    if (this.encryptionKey && this.encryptionKey.length === 32) {
      const env = await getCryptoEnv();
      payload = encryptDriveContent(env, args.bytes, this.encryptionKey);
      encVersion = 1;
    }
    const dataBase64 = encodeBase64(payload);
    const axiosMod = await import('axios');
    const axios = axiosMod.default ?? axiosMod;
    const url = `${configuration.serverUrl}/v1/sessions/${this.sessionId}/artifacts`;
    let result;
    try {
      const { data } = await axios.post(
        url,
        { mime: args.mime, dataBase64, ...encVersion !== void 0 ? { encVersion } : {} },
        { headers: { Authorization: `Bearer ${this.token}` } }
      );
      result = data;
    } catch (err) {
      const status = err?.response?.status;
      const serverBody = err?.response?.data;
      const serverMsg = typeof serverBody === "string" ? serverBody : serverBody?.error ?? serverBody?.message ?? JSON.stringify(serverBody ?? {});
      const msg = `uploadArtifact failed: HTTP ${status ?? "?"} ${serverMsg} (mime=${args.mime}, bytes=${args.bytes.length}, base64=${dataBase64.length})`;
      throw new Error(msg);
    }
    if (encVersion === 1 && result.encVersion !== 1) {
      throw new Error(
        `uploadArtifact aborted: server did not acknowledge encVersion=1 (got ${result.encVersion ?? "none"}). The server is older than this CLI; encrypted artifact bytes would be stored as legacy plaintext and permanently corrupt the artifact. Upgrade the server before uploading encrypted artifacts.`
      );
    }
    return result;
  }
  /** True when this session can create interactive library artifacts. */
  canCreateLibraryArtifacts() {
    return !!this.userContentPublicKey;
  }
  /**
   * Create a versioned interactive artifact (the /v1/artifacts entity),
   * linked to this session. Encrypts header+body with a fresh per-artifact
   * DEK wrapped to the user's content public key — byte-compatible with the
   * mobile app's ArtifactEncryption, so the artifact opens in the app's
   * slide-in panel and library. Returns the new artifact id.
   */
  async createLibraryArtifact(input) {
    if (!this.userContentPublicKey) {
      throw new Error("This account uses legacy encryption and cannot create interactive artifacts.");
    }
    const env = await getCryptoEnv();
    const dek = generateArtifactDek(env);
    const wrappedDek = wrapArtifactDek(env, dek, this.userContentPublicKey);
    const header = await encryptArtifactField(
      { title: input.title, sessions: [this.sessionId], kind: input.kind, language: input.language, mime: input.mime },
      dek
    );
    const body = await encryptArtifactField({ body: input.content, versions: [] }, dek);
    const id = node_crypto.randomUUID();
    const axiosMod = await import('axios');
    const axios = axiosMod.default ?? axiosMod;
    const url = `${configuration.serverUrl}/v1/artifacts`;
    await axios.post(
      url,
      { id, header, body, dataEncryptionKey: wrappedDek },
      { headers: { Authorization: `Bearer ${this.token}` } }
    );
    this.artifactStates.set(id, {
      dek,
      title: input.title,
      kind: input.kind,
      language: input.language,
      mime: input.mime,
      content: input.content,
      bodyVersion: 1,
      versions: []
    });
    this.autoArtifactSeen.add(this.artifactDedupeKey(input.kind, input.content));
    return { id };
  }
  /**
   * Update an artifact previously created in this session, producing a new
   * body version and appending the prior body to the inline version history
   * (capped). Requires the artifact to exist in this session's in-memory
   * state (i.e. created in the same run) — otherwise the DEK is unavailable
   * and the caller should create a new artifact instead.
   */
  async updateLibraryArtifact(input) {
    const state = this.artifactStates.get(input.id);
    if (!state) {
      throw new Error("Artifact not found in this session \u2014 create a new one instead of updating.");
    }
    await getCryptoEnv();
    const nextVersions = [
      ...state.versions,
      { n: state.bodyVersion, body: state.content, createdAt: Date.now(), summary: input.summary }
    ].slice(-10);
    const newTitle = input.title ?? state.title;
    const updateRequest = {};
    const encryptedBody = await encryptArtifactField({ body: input.content, versions: nextVersions }, state.dek);
    updateRequest.body = encryptedBody;
    updateRequest.expectedBodyVersion = state.bodyVersion;
    if (input.title !== void 0 && input.title !== state.title) {
      updateRequest.header = await encryptArtifactField(
        { title: newTitle, sessions: [this.sessionId], kind: state.kind, language: state.language, mime: state.mime },
        state.dek
      );
      updateRequest.expectedHeaderVersion = state.bodyVersion;
    }
    const axiosMod = await import('axios');
    const axios = axiosMod.default ?? axiosMod;
    const url = `${configuration.serverUrl}/v1/artifacts/${input.id}`;
    const { data } = await axios.post(url, updateRequest, {
      headers: { Authorization: `Bearer ${this.token}` }
    });
    if (data && data.success === false && data.error === "version-mismatch") {
      const retryBodyVersion = typeof data.currentBodyVersion === "number" ? data.currentBodyVersion : state.bodyVersion + 1;
      await axios.post(
        url,
        { body: encryptedBody, expectedBodyVersion: retryBodyVersion },
        { headers: { Authorization: `Bearer ${this.token}` } }
      );
      state.bodyVersion = retryBodyVersion + 1;
    } else {
      state.bodyVersion = typeof data?.bodyVersion === "number" ? data.bodyVersion : state.bodyVersion + 1;
    }
    state.content = input.content;
    state.title = newTitle;
    state.versions = nextVersions;
    return { bodyVersion: state.bodyVersion, title: state.title, kind: state.kind };
  }
  constructor(token, session) {
    super();
    this.token = token;
    this.sessionId = session.id;
    this.metadata = session.metadata;
    this.metadataVersion = session.metadataVersion;
    this.agentState = session.agentState;
    this.agentStateVersion = session.agentStateVersion;
    this.encryptionKey = session.encryptionKey;
    this.encryptionVariant = session.encryptionVariant;
    this.userContentPublicKey = session.userContentPublicKey;
    this.rpcHandlerManager = new RpcHandlerManager({
      scopePrefix: this.sessionId,
      encryptionKey: this.encryptionKey,
      encryptionVariant: this.encryptionVariant,
      logger: (msg, data) => logger.debug(msg, data)
    });
    registerCommonHandlers(this.rpcHandlerManager, this.metadata.path);
    this.socket = socket_ioClient.io(configuration.serverUrl, {
      auth: {
        token: this.token,
        clientType: "session-scoped",
        sessionId: this.sessionId
      },
      path: "/v1/updates",
      reconnection: true,
      reconnectionAttempts: Infinity,
      reconnectionDelay: 1e3,
      reconnectionDelayMax: 5e3,
      transports: ["websocket"],
      withCredentials: true,
      autoConnect: false,
      // Bump RPC acknowledgement timeout from the 60s default. The
      // bare "operation has timed out" string users were seeing on
      // intermittent Grok session-create was Socket.IO's ack
      // timeout firing during a slow WS handshake or first RPC.
      // 120s tolerates a brief network blip without the agent
      // appearing to "just fail to start".
      ackTimeout: 12e4,
      timeout: 3e4
      // initial connect timeout
    });
    this.socket.on("connect", () => {
      logger.debug("Socket connected successfully");
      this.manualReconnectAttempts = 0;
      if (this.reconnectTimer) {
        clearTimeout(this.reconnectTimer);
        this.reconnectTimer = null;
      }
      if (this.flushWatchdog) {
        clearTimeout(this.flushWatchdog);
        this.flushWatchdog = null;
      }
      this.rpcHandlerManager.onSocketConnect(this.socket);
      this.flushPendingEmits();
    });
    this.socket.on("rpc-request", async (data, callback) => {
      callback(await this.rpcHandlerManager.handleRequest(data));
    });
    this.socket.on("disconnect", (reason) => {
      logger.debug("[API] Socket disconnected:", reason);
      this.rpcHandlerManager.onSocketDisconnect();
    });
    this.socket.on("server-shutting-down", (data) => {
      logger.debug(`[API] Server shutting down (reason: ${data.reason}), forcing transport close to trigger reconnect`);
      this.socket.io.engine?.close();
    });
    this.socket.on("connect_error", (error) => {
      logger.debug("[API] Socket connection error:", error);
      this.rpcHandlerManager.onSocketDisconnect();
      this.scheduleManualReconnect();
    });
    this.socket.on("update", (data) => {
      try {
        logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", data);
        if (!data.body) {
          logger.debug("[SOCKET] [UPDATE] [ERROR] No body in update!");
          return;
        }
        if (data.body.t === "new-message" && data.body.message.content.t === "encrypted") {
          const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.message.content.c));
          logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", body);
          const userResult = UserMessageSchema.safeParse(body);
          if (userResult.success) {
            const transportLocalId = data.body.message.localId;
            if (userResult.data.content.localId === void 0 && typeof transportLocalId === "string") {
              userResult.data.content.localId = transportLocalId;
            }
            try {
              const decoded = decodeMessageBody(
                new TextEncoder().encode(userResult.data.content.text)
              );
              if (decoded.v === 2) {
                userResult.data.content.text = decoded.text;
                if (userResult.data.content.attachmentIds === void 0) {
                  userResult.data.content.attachmentIds = decoded.attachmentIds;
                }
                if (userResult.data.content.localId === void 0 && decoded.localId) {
                  userResult.data.content.localId = decoded.localId;
                }
              }
            } catch (e) {
              if (!(e instanceof MessageDecodeError)) throw e;
            }
            if (userResult.data.content.attachmentIds !== void 0) {
              userResult.data.attachmentIds = userResult.data.content.attachmentIds;
            }
            if (userResult.data.content.driveId !== void 0) {
              userResult.data.driveId = userResult.data.content.driveId;
            }
            if (this.pendingMessageCallback) {
              this.pendingMessageCallback(userResult.data);
            } else {
              this.pendingMessages.push(userResult.data);
            }
          } else {
            this.emit("message", body);
          }
        } else if (data.body.t === "update-session") {
          if (data.body.metadata && data.body.metadata.version > this.metadataVersion) {
            this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.metadata.value));
            this.metadataVersion = data.body.metadata.version;
          }
          if (data.body.agentState && data.body.agentState.version > this.agentStateVersion) {
            this.agentState = data.body.agentState.value ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.agentState.value)) : null;
            this.agentStateVersion = data.body.agentState.version;
          }
        } else if (data.body.t === "update-machine") {
          logger.debug(`[SOCKET] WARNING: Session client received unexpected machine update - ignoring`);
        } else {
          this.emit("message", data.body);
        }
      } catch (error) {
        logger.debug("[SOCKET] [UPDATE] [ERROR] Error handling update", { error });
      }
    });
    const relayHandler = createRelayHandler({ socket: this.socket, session: this });
    let rotationNoOpWarned = false;
    const rotateHandler = createRotateHandler({
      serverBaseUrl: configuration.serverUrl,
      deviceToken: this.token,
      lookupSession: async (sessionId) => {
        if (!rotationNoOpWarned) {
          rotationNoOpWarned = true;
          logger.info(
            "[rotate] secure_rotate requested but forward-secure DEK rotation is not yet implemented on this device; the server version bump and delivery supersession still apply, but the local DEK is NOT rotated",
            { sessionId }
          );
        } else {
          logger.debug("[rotate] secure_rotate no-op (rotation unimplemented on this device)", { sessionId });
        }
        return null;
      },
      onRotated: () => {
      }
    });
    this.socket.on("ephemeral", (event) => {
      relayHandler.onEphemeral(event);
      rotateHandler.onEphemeral(event);
      this.onModelChangeEphemeral(event);
    });
    this.socket.on("error", (error) => {
      logger.debug("[API] Socket error:", error);
    });
    this.socket.connect();
  }
  onUserMessage(callback) {
    this.pendingMessageCallback = callback;
    while (this.pendingMessages.length > 0) {
      callback(this.pendingMessages.shift());
    }
  }
  /**
   * EPHEMERAL-1: subscribe to server-broadcast model-change events
   * (emitted on Path A set AND clear — CONTRACT A). The server fans these
   * out so every live client/session reconciles the active model
   * immediately instead of only learning on the next request. Runners use
   * this to update their in-memory model + repaint the model pill.
   *
   * `modelId === null` means the override was cleared (reset to default).
   * `sessionId` is present for session-scoped changes and absent for
   * account-level (default-for-new-sessions) changes.
   */
  onModelChange(callback) {
    this.modelChangeCallback = callback;
  }
  /**
   * Parse + dispatch a model-change ephemeral. Defensive: tolerates the
   * broadcast arriving as either `model-change` or `model-changed` and
   * silently ignores anything that isn't a well-formed model-change event
   * (the same socket carries relay + rotate ephemerals).
   */
  onModelChangeEphemeral(event) {
    if (!event || typeof event !== "object") return;
    const e = event;
    if (e.type !== "model-change" && e.type !== "model-changed") return;
    const providerId = typeof e.providerId === "string" ? e.providerId : void 0;
    if (!providerId) return;
    const modelId = e.modelId === null ? null : typeof e.modelId === "string" ? e.modelId : void 0;
    if (modelId === void 0) return;
    const sessionId = typeof e.sessionId === "string" ? e.sessionId : void 0;
    if (sessionId && sessionId !== this.sessionId) {
      logger.debug(`[API] Ignoring model-change for other session ${sessionId} (ours=${this.sessionId})`);
      return;
    }
    logger.debug(`[API] model-change ephemeral: provider=${providerId} model=${modelId ?? "(cleared)"} sessionId=${sessionId ?? "(account)"}`);
    if (this.modelChangeCallback) {
      try {
        this.modelChangeCallback({ providerId, modelId, sessionId });
      } catch (err) {
        logger.debug("[API] model-change callback threw", { err });
      }
    }
  }
  /**
   * Emit a 'message' payload, buffering it if the socket is currently
   * disconnected. Drained on the next 'connect' event.
   */
  reliableEmitMessage(payload, context) {
    if (this.socket.connected) {
      this.socket.emit("message", payload);
      return;
    }
    if (this.pendingEmits.length >= ApiSessionClient.MAX_PENDING_EMITS) {
      this.pendingEmits.shift();
      logger.debug("[API] Reconnect buffer at cap, dropping oldest", { context });
    }
    this.pendingEmits.push(payload);
    logger.debug("[API] Socket disconnected, queued for reconnect", { context, queueSize: this.pendingEmits.length });
    if (!this.destroyed && !this.reconnectTimer) {
      try {
        this.socket.connect();
      } catch (err) {
        logger.debug("[API] socket.connect() while buffering threw", { err });
      }
    }
    if (!this.destroyed && !this.flushWatchdog) {
      this.flushWatchdog = setTimeout(() => {
        this.flushWatchdog = null;
        if (this.destroyed) return;
        if (this.socket.connected) return;
        if (this.pendingEmits.length === 0) return;
        logger.debug("[API] Flush watchdog firing manual reconnect");
        this.scheduleManualReconnect();
      }, ApiSessionClient.FLUSH_WATCHDOG_MS);
    }
  }
  flushPendingEmits() {
    if (this.pendingEmits.length === 0) return;
    const toSend = this.pendingEmits;
    this.pendingEmits = [];
    logger.debug(`[API] Flushing ${toSend.length} pending messages after reconnect`);
    for (const payload of toSend) {
      this.socket.emit("message", payload);
    }
  }
  /**
   * Send message to session
   * @param body - Message body (can be MessageContent or raw content for agent messages)
   */
  sendClaudeSessionMessage(body) {
    let content;
    if (body.type === "user" && typeof body.message.content === "string" && body.isSidechain !== true && body.isMeta !== true) {
      content = {
        role: "user",
        content: {
          type: "text",
          text: body.message.content
        },
        meta: {
          sentFrom: "cli"
        }
      };
    } else {
      content = {
        role: "agent",
        content: {
          type: "output",
          data: body
          // This wraps the entire Claude message
        },
        meta: {
          sentFrom: "cli"
        }
      };
    }
    logger.debugLargeJson("[SOCKET] Sending message through socket:", content);
    const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
    this.reliableEmitMessage({
      sid: this.sessionId,
      message: encrypted,
      localId: body.type === "summary" ? `summary:${body.leafUuid}` : body.uuid
    }, `claude-session:${body.type}`);
    if (body.type === "summary" && "summary" in body && "leafUuid" in body) {
      this.updateMetadata((metadata) => ({
        ...metadata,
        summary: {
          text: body.summary,
          updatedAt: Date.now()
        }
      }));
    }
  }
  sendCodexMessage(body) {
    let content = {
      role: "agent",
      content: {
        type: "codex",
        data: body
      },
      meta: {
        sentFrom: "cli"
      }
    };
    const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
    this.reliableEmitMessage({
      sid: this.sessionId,
      message: encrypted,
      localId: node_crypto.randomUUID()
    }, `codex:${body?.type ?? "unknown"}`);
  }
  /**
   * Send a generic agent message to the session using ACP (Agent Communication Protocol) format.
   * Works for any agent type (Gemini, Codex, Claude, etc.) - CLI normalizes to unified ACP format.
   * 
   * @param provider - The agent provider sending the message (e.g., 'gemini', 'codex', 'claude')
   * @param body - The message payload (type: 'message' | 'reasoning' | 'tool-call' | 'tool-result')
   */
  /**
   * Send a user message to the session, rendered as a chat bubble on the web client.
   * Used to mirror terminal TUI input to the web session.
   */
  sendUserChatMessage(text) {
    const content = {
      role: "user",
      content: { type: "text", text },
      meta: { sentFrom: "cli" }
    };
    const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
    this.reliableEmitMessage({
      sid: this.sessionId,
      message: encrypted,
      localId: node_crypto.randomUUID()
    }, "user-chat");
  }
  sendAgentMessage(provider, body) {
    let content = {
      role: "agent",
      content: {
        type: "acp",
        provider,
        data: body
      },
      meta: {
        sentFrom: "cli"
      }
    };
    logger.debug(`[SOCKET] Sending ACP message from ${provider}:`, { type: body.type, hasMessage: "message" in body });
    const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
    this.reliableEmitMessage({
      sid: this.sessionId,
      message: encrypted,
      localId: node_crypto.randomUUID()
    }, `acp:${provider}:${body.type}`);
    if (provider !== "consortium-code" && body.type === "message" && typeof body.message === "string") {
      void this.autoDetectArtifactsFromText(provider, body.message);
    }
    if (body.type === "tool-call") {
      const path = this.extractWritePath(body);
      if (path) this.pendingWriteCalls.set(body.callId, path);
    } else if (body.type === "tool-result") {
      const callId = body.callId;
      const path = this.pendingWriteCalls.get(callId);
      if (path) {
        this.pendingWriteCalls.delete(callId);
        if (!body.isError) {
          void this.promoteFileWriteArtifact(provider, path);
        }
      }
    }
  }
  // callId -> written file path, captured on a write tool-call and consumed
  // on the matching tool-result (when the file is on disk).
  pendingWriteCalls = /* @__PURE__ */ new Map();
  // Written file path -> artifact id, so re-writing the same file produces a
  // new VERSION of one artifact instead of a fresh card each time.
  fileArtifactIds = /* @__PURE__ */ new Map();
  // Content hashes of blocks already promoted to artifacts this session, so a
  // re-sent / streamed-then-finalized message doesn't create duplicates.
  autoArtifactSeen = /* @__PURE__ */ new Set();
  // Dedupe key for a promoted block: kind + sha256 of the trailing-trimmed
  // content. Hashing the FULL normalized content (not length + first 64 chars)
  // avoids both false-positive collisions between distinct same-length blocks
  // and false-negatives between the explicit tool (raw arg) and the fallback
  // (scanForArtifacts already strips trailing whitespace).
  artifactDedupeKey(kind, content) {
    const normalized = content.replace(/\s+$/, "");
    return `${kind}:${node_crypto.createHash("sha256").update(normalized).digest("hex")}`;
  }
  /**
   * Scan agent text for substantial fenced blocks and promote them to
   * interactive artifacts — the safety net for agents that print a code
   * block instead of calling create_artifact. Works for ALL agents: full-
   * message agents (gemini/grok/pi/claude) are scanned per outgoing message
   * from sendAgentMessage; streaming agents (consortium-code) call this once
   * with the whole turn's text. Best-effort and fire-and-forget.
   *
   * Default ON; set CONSORTIUM_ARTIFACTS_AUTODETECT=0 to disable.
   */
  async autoDetectArtifactsFromText(provider, text) {
    if (process.env.CONSORTIUM_ARTIFACTS_AUTODETECT === "0") return;
    if (!this.userContentPublicKey) return;
    try {
      const { scanForArtifacts } = await Promise.resolve().then(function () { return require('./scanForArtifacts-DBtmjd28.cjs'); });
      const candidates = scanForArtifacts(text);
      for (const c of candidates) {
        const key = this.artifactDedupeKey(c.kind, c.content);
        if (this.autoArtifactSeen.has(key)) continue;
        this.autoArtifactSeen.add(key);
        const { id } = await this.createLibraryArtifact({
          title: c.title,
          kind: c.kind,
          content: c.content,
          language: c.language
        });
        this.sendAgentMessage(provider, { type: "artifact-ref", artifactId: id, title: c.title, artifactKind: c.kind, versionHint: 1, id: node_crypto.randomUUID() });
      }
    } catch (err) {
      logger.debug("[apiSession] auto artifact detection failed (non-fatal):", err);
    }
  }
  // Extract a written file path from a write-style tool-call, or null if this
  // isn't a file write. Gated on both a write-y tool name and a path arg so
  // non-file tools (bash, search, etc.) are ignored.
  extractWritePath(body) {
    const name = (body.name ?? "").toLowerCase();
    if (!/write|edit|create|patch|save|replace|notebook/.test(name)) return null;
    const input = body.input;
    if (!input || typeof input !== "object") return null;
    const p = input.filePath ?? input.file_path ?? input.path ?? input.target_file ?? input.absolute_path;
    return typeof p === "string" && p.length > 0 ? p : null;
  }
  // Extension -> artifact kind. Only PREVIEWABLE / deliverable kinds promote
  // on file write (html/svg/mermaid/markdown/react) — promoting every .ts/.js
  // a coding agent writes would spam cards during ordinary development.
  fileArtifactKind(path$1) {
    const ext = path.extname(path$1).slice(1).toLowerCase();
    switch (ext) {
      case "html":
      case "htm":
        return { kind: "html" };
      case "svg":
        return { kind: "svg" };
      case "jsx":
        return { kind: "react", language: "jsx" };
      case "tsx":
        return { kind: "react", language: "tsx" };
      case "md":
      case "markdown":
        return { kind: "markdown" };
      case "mmd":
      case "mermaid":
        return { kind: "mermaid" };
      default:
        return null;
    }
  }
  /**
   * Promote a written file to an interactive artifact: read it, infer the
   * kind from its extension, and create (or, for a re-write of the same path,
   * version) the artifact + drop a chat card. This is what makes coding
   * agents behave like Claude artifacts — they write files, not fences.
   *
   * `content` may be passed when the caller already has it in hand (e.g.
   * Claude's Write tool input, Codex add patches); otherwise the file is read
   * from disk (it exists by the time the write tool-result fires). Best-effort
   * and fire-and-forget. Honours CONSORTIUM_ARTIFACTS_AUTODETECT.
   */
  async promoteFileWriteArtifact(provider, filePath, content) {
    if (process.env.CONSORTIUM_ARTIFACTS_AUTODETECT === "0") return;
    if (!this.userContentPublicKey) return;
    try {
      const kindInfo = this.fileArtifactKind(filePath);
      if (!kindInfo) return;
      logger.debug(`[apiSession] promoting file-write artifact: ${filePath} (kind=${kindInfo.kind}, provider=${provider})`);
      let text = content;
      if (text === void 0) {
        const abs = path.isAbsolute(filePath) ? filePath : path.join(this.metadata?.path ?? "", filePath);
        const { readFile } = await import('node:fs/promises');
        text = await readFile(abs, "utf8");
      }
      if (!text || !text.trim()) return;
      if (text.length > MAX_ARTIFACT_FILE_BYTES) return;
      const title = path.basename(filePath);
      const key = this.artifactDedupeKey(kindInfo.kind, text);
      if (this.autoArtifactSeen.has(key)) return;
      const existingId = this.fileArtifactIds.get(filePath);
      if (existingId && this.artifactStates.has(existingId)) {
        const res = await this.updateLibraryArtifact({ id: existingId, content: text });
        this.autoArtifactSeen.add(key);
        this.sendAgentMessage(provider, { type: "artifact-ref", artifactId: existingId, title, artifactKind: kindInfo.kind, versionHint: res.bodyVersion, id: node_crypto.randomUUID() });
      } else {
        const { id } = await this.createLibraryArtifact({ title, kind: kindInfo.kind, content: text, language: kindInfo.language });
        this.fileArtifactIds.set(filePath, id);
        this.sendAgentMessage(provider, { type: "artifact-ref", artifactId: id, title, artifactKind: kindInfo.kind, versionHint: 1, id: node_crypto.randomUUID() });
      }
    } catch (err) {
      logger.debug("[apiSession] file-write artifact promotion failed (non-fatal):", err);
    }
  }
  sendSessionEvent(event, id) {
    let content = {
      role: "agent",
      content: {
        id: id ?? node_crypto.randomUUID(),
        type: "event",
        data: event
      }
    };
    const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
    this.socket.emit("message", {
      sid: this.sessionId,
      message: encrypted
    });
  }
  /**
   * Emit a live token-stream delta over the ephemeral `session-stream`
   * channel. Unlike `sendAgentMessage`, this is NOT persisted — the
   * server relays it to clients currently viewing the session and drops
   * it otherwise. The durable, authoritative message still flows through
   * `sendAgentMessage` at turn end; this only powers the live "typing"
   * feed. Deltas are best-effort (`volatile`): if the socket is
   * congested a dropped delta is harmless because the final message
   * reconciles the text.
   *
   * The `textDelta` is encrypted with the session key exactly like a
   * durable message body, so the relay server never sees plaintext.
   *
   * @param provider  agent id ('grok' | 'gemini' | 'codex' | 'claude' | 'pi' | …)
   * @param streamId  stable id for one assistant turn (answer+reasoning share it)
   * @param channel   'answer' for response text, 'reasoning' for thinking
   * @param seq       monotonically increasing index within the stream (ordering)
   * @param textDelta the incremental chunk (omit on the terminal frame)
   * @param done      true to signal the stream is finished (clears the buffer)
   */
  sendStreamDelta(provider, streamId, channel, seq, textDelta, done = false) {
    let data;
    if (textDelta) {
      data = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, { textDelta }));
    }
    this.socket.volatile.emit("session-stream", {
      sid: this.sessionId,
      provider,
      streamId,
      channel,
      seq,
      done,
      data
    });
  }
  /**
   * Optional provider for message-queue observability. When set, every
   * keepAlive heartbeat carries `queued`/`queuedIds` so clients can render
   * exact queue state (level-triggered: re-sent every ~2s on a volatile
   * emit, so a dropped frame self-heals on the next tick — no ack contract).
   * Old servers/clients simply ignore the extra fields.
   */
  queueStatsProvider = null;
  setQueueStatsProvider(provider) {
    this.queueStatsProvider = provider;
  }
  /**
   * Send a ping message to keep the connection alive
   */
  keepAlive(thinking, mode) {
    if (process.env.DEBUG) {
      logger.debug(`[API] Sending keep alive message: ${thinking}`);
    }
    let stats = null;
    try {
      stats = this.queueStatsProvider?.() ?? null;
    } catch {
    }
    this.socket.volatile.emit("session-alive", {
      sid: this.sessionId,
      time: Date.now(),
      thinking,
      mode,
      ...stats && { queued: stats.queued, queuedIds: stats.queuedIds.slice(0, 32) }
    });
  }
  /**
   * Send session death message
   */
  sendSessionDeath() {
    this.socket.emit("session-end", { sid: this.sessionId, time: Date.now() });
  }
  /**
   * Infers the LLM provider ID from a model name string.
   */
  inferProvider(model) {
    if (!model) return void 0;
    if (/^claude-/i.test(model)) return "anthropic";
    if (/^(gpt-|o[134]-|chatgpt-)/i.test(model)) return "openai";
    if (/^gemini-/i.test(model)) return "google";
    if (/^deepseek-/i.test(model)) return "deepseek";
    if (/^grok/i.test(model)) return "xai";
    return void 0;
  }
  /**
   * Detects the usage mode based on environment variables (B-8).
   *
   *  · 'managed' — the agent's traffic is rewritten through the Consortium
   *    proxy, so the SERVER already metered and billed this call and the
   *    report is supplementary analytics. This used to check only
   *    `ANTHROPIC_BASE_URL`, so a managed OpenAI or Google session was
   *    mislabeled 'wrapped' and got billed a SECOND time from the
   *    client-reported cost. All three vendor base-URL vars are checked now.
   *  · 'byok' — the daemon injected the user's OWN credential for this spawn
   *    (custom base URL and/or a vendor API-key env var it set). The user
   *    pays their vendor directly, so the server must not bill the wallet.
   *  · 'wrapped' — everything else; the server bills the client-reported cost.
   *
   * The BYOK signals are deliberately restricted to variables the DAEMON
   * sets (`daemon/run.ts` — `CONSORTIUM_CODE_BYOK_BASE_URL` /
   * `CONSORTIUM_CODE_BYOK_ENV_VAR` / `CONSORTIUM_CODE_BYOK_PROVIDER`). A bare
   * `ANTHROPIC_API_KEY` inherited from the user's shell is NOT treated as
   * BYOK: that would silently stop billing existing wrapped sessions, which
   * is a revenue change, not an accounting fix.
   */
  detectUsageMode() {
    const managedBaseUrls = [
      process.env.ANTHROPIC_BASE_URL,
      process.env.OPENAI_BASE_URL,
      process.env.GOOGLE_API_ENDPOINT
    ];
    if (managedBaseUrls.some((u) => (u || "").includes("/v1/proxy/"))) return "managed";
    if (process.env.CONSORTIUM_CODE_BYOK_BASE_URL) return "byok";
    const byokEnvVar = process.env.CONSORTIUM_CODE_BYOK_ENV_VAR;
    if (byokEnvVar && process.env[byokEnvVar]) return "byok";
    return "wrapped";
  }
  /**
   * Send usage data to the server
   *
   * `opts` (L4.2 — non-claude agents + sub-agent rollup):
   *  · `keyPrefix` — report-key namespace. Defaults to the legacy
   *    'claude-session' so existing claude rows keep upserting under their
   *    historical keys; codex/gemini/grok pass their agent name so the
   *    ledger can tell who reported.
   *  · `providerId` — explicit provider when the model id alone can't infer
   *    it (e.g. codex running its default model: the CLI never learns the
   *    model string, but the vendor is definitionally openai).
   *  · `zeroCost` — report tokens only, cost 0. REQUIRED for the non-claude
   *    agent self-reports: `calculateCost` falls back to Anthropic Sonnet
   *    pricing for unknown model ids, and the server debits the wallet for
   *    any wrapped-mode report with cost > 0 — a codex/gemini/grok session
   *    running on the user's OWN vendor account must never create a wallet
   *    charge. Vendor-true cost adoption is lane L4.4's scope, not ours.
   */
  sendUsageData(usage, model, turnKey, opts) {
    const totalTokens = usage.input_tokens + usage.output_tokens + (usage.cache_creation_input_tokens || 0) + (usage.cache_read_input_tokens || 0);
    const costs = opts?.zeroCost ? { total: 0, input: 0, output: 0 } : calculateCost(usage, model);
    const usageMode = this.detectUsageMode();
    const usageReport = {
      key: `${opts?.keyPrefix ?? "claude-session"}:${turnKey ?? node_crypto.randomUUID()}`,
      sessionId: this.sessionId,
      tokens: {
        total: totalTokens,
        input: usage.input_tokens,
        output: usage.output_tokens,
        // The server aggregator at /v1/sessions/:id/usage reads
        // these as camelCase (matching what proxyBilling writes for
        // managed-mode rows). Snake-case keys here would silently
        // sum to zero in the cumulative cache totals.
        cacheWrite: usage.cache_creation_input_tokens || 0,
        cacheRead: usage.cache_read_input_tokens || 0,
        // Snake-case duplicates kept for any older readers that
        // expect them. Cheap to ship; the server picks one.
        cache_creation: usage.cache_creation_input_tokens || 0,
        cache_read: usage.cache_read_input_tokens || 0
      },
      cost: {
        total: costs.total,
        input: costs.input,
        output: costs.output
      },
      providerId: this.inferProvider(model) ?? opts?.providerId,
      usageMode,
      modelId: model,
      // D0 provenance: these counts are the AGENT's own report (Claude
      // Code's `result.usage`), i.e. vendor-computed but relayed by the
      // agent — not measured by our proxy. The server stores this on
      // `UsageReport.data.source` so the UI can label it honestly
      // instead of presenting every number as proxy-measured truth.
      source: "agent-reported"
    };
    logger.debugLargeJson("[SOCKET] Sending usage data:", usageReport);
    this.socket.emit("usage-report", usageReport);
  }
  /**
   * Update session metadata
   * @param handler - Handler function that returns the updated metadata
   */
  updateMetadata(handler) {
    this.metadataLock.inLock(async () => {
      await backoff(async () => {
        let updated = handler(this.metadata);
        const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) });
        if (answer.result === "success") {
          this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
          this.metadataVersion = answer.version;
        } else if (answer.result === "version-mismatch") {
          if (answer.version > this.metadataVersion) {
            this.metadataVersion = answer.version;
            this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
          }
          throw new Error("Metadata version mismatch");
        } else if (answer.result === "error") ;
      });
    });
  }
  /**
   * Update session agent state
   * @param handler - Handler function that returns the updated agent state
   */
  updateAgentState(handler) {
    logger.debugLargeJson("Updating agent state", this.agentState);
    this.agentStateLock.inLock(async () => {
      await backoff(async () => {
        let updated = handler(this.agentState || {});
        const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) : null });
        if (answer.result === "success") {
          this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
          this.agentStateVersion = answer.version;
          logger.debug("Agent state updated", this.agentState);
        } else if (answer.result === "version-mismatch") {
          if (answer.version > this.agentStateVersion) {
            this.agentStateVersion = answer.version;
            this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
          }
          throw new Error("Agent state version mismatch");
        } else if (answer.result === "error") ;
      });
    });
  }
  /**
   * Wait for socket buffer to flush
   */
  async flush() {
    if (!this.socket.connected) {
      return;
    }
    return new Promise((resolve) => {
      this.socket.emit("ping", () => {
        resolve();
      });
      setTimeout(() => {
        resolve();
      }, 1e4);
    });
  }
  async close() {
    logger.debug("[API] socket.close() called");
    this.destroyed = true;
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    if (this.flushWatchdog) {
      clearTimeout(this.flushWatchdog);
      this.flushWatchdog = null;
    }
    this.socket.close();
  }
  /**
   * Schedule a manual reconnect after a connect_error. Mirrors
   * ApiMachineClient.scheduleManualReconnect; see comment there for the
   * Socket.IO middleware-rejection rationale. Session sockets don't
   * carry their own refresh path — the session token comes from the
   * parent process, so the only thing we can do here is retry with
   * the existing token until it works (or until close() is called).
   */
  scheduleManualReconnect() {
    if (this.destroyed) return;
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    this.manualReconnectAttempts++;
    const delay = Math.min(1e3 * Math.pow(2, this.manualReconnectAttempts - 1), 3e4) + Math.floor(Math.random() * 1e3);
    logger.debug(`[API] Scheduling manual reconnect attempt #${this.manualReconnectAttempts} in ${delay}ms`);
    this.reconnectTimer = setTimeout(() => {
      this.reconnectTimer = null;
      if (this.destroyed) return;
      if (this.socket.connected) return;
      logger.debug(`[API] Manual reconnect attempt #${this.manualReconnectAttempts} firing`);
      this.socket.connect();
    }, delay);
  }
}
function finiteNumber(v) {
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
}
function usageFromCodexLastTokenUsage(last) {
  if (!last || typeof last !== "object") return null;
  const o = last;
  const input = finiteNumber(o.input_tokens);
  const output = finiteNumber(o.output_tokens);
  if (input === void 0 || output === void 0) return null;
  const cached = finiteNumber(o.cached_input_tokens) ?? 0;
  return {
    input_tokens: Math.max(0, input - cached),
    output_tokens: output,
    cache_creation_input_tokens: 0,
    cache_read_input_tokens: Math.max(0, cached)
  };
}
function usageFromAgentTokenCount(raw) {
  if (!raw || typeof raw !== "object") return null;
  const o = raw;
  const info = o.info;
  if (info && typeof info === "object") {
    const fromCodex = usageFromCodexLastTokenUsage(info.last_token_usage);
    if (fromCodex) return fromCodex;
  }
  {
    const input = finiteNumber(o.input_tokens);
    const output = finiteNumber(o.output_tokens);
    if (input !== void 0 && output !== void 0) {
      const cacheRead = finiteNumber(o.cache_read_input_tokens);
      const cacheWrite = finiteNumber(o.cache_creation_input_tokens);
      if (cacheRead !== void 0 || cacheWrite !== void 0) {
        return {
          input_tokens: input,
          output_tokens: output,
          cache_creation_input_tokens: cacheWrite ?? 0,
          cache_read_input_tokens: cacheRead ?? 0
        };
      }
      const cached = finiteNumber(o.cached_input_tokens) ?? 0;
      return {
        input_tokens: Math.max(0, input - cached),
        output_tokens: output,
        cache_creation_input_tokens: 0,
        cache_read_input_tokens: Math.max(0, cached)
      };
    }
  }
  for (const candidate of [o, o.usage, o.usageMetadata]) {
    if (!candidate || typeof candidate !== "object") continue;
    const m = candidate;
    const prompt = finiteNumber(m.promptTokenCount);
    const output = finiteNumber(m.candidatesTokenCount);
    if (prompt === void 0 || output === void 0) continue;
    const cached = finiteNumber(m.cachedContentTokenCount) ?? 0;
    const thoughts = finiteNumber(m.thoughtsTokenCount) ?? 0;
    return {
      input_tokens: Math.max(0, prompt - cached),
      output_tokens: output + thoughts,
      cache_creation_input_tokens: 0,
      cache_read_input_tokens: Math.max(0, cached)
    };
  }
  return null;
}

const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-BYXHizYk.cjs', document.baseURI).href)));
function collectDiagnostics(binary, args) {
  const resolvedPath = fs.existsSync(binary) ? binary : null;
  return {
    binary,
    resolvedPath,
    args,
    platform: process.platform,
    arch: process.arch,
    path: process.env.PATH || "(unset)",
    execPath: process.execPath
  };
}
class SpawnDiagnosticsError extends Error {
  info;
  cause;
  constructor(original, info) {
    const origMsg = original instanceof Error ? original.message : String(original);
    super(
      `Failed to spawn process.
  binary:        ${info.binary}
  absolute path: ${info.resolvedPath ?? "<not found on disk>"}
  args:          ${JSON.stringify(info.args)}
  platform:      ${info.platform} (${info.arch})
  execPath:      ${info.execPath}
  PATH:          ${info.path}
  underlying error: ${origMsg}`
    );
    this.name = "SpawnDiagnosticsError";
    this.info = info;
    this.cause = original;
  }
}
function spawnPtyWithDiagnostics(binary, args, options) {
  const info = collectDiagnostics(binary, args);
  logger.debug(`[spawnPty] ${binary} ${args.join(" ")} (path=${info.resolvedPath ?? "NOT FOUND"})`);
  try {
    const nodePty = require$1("node-pty");
    return nodePty.spawn(binary, args, options);
  } catch (err) {
    throw new SpawnDiagnosticsError(err, info);
  }
}
function isPosixSpawnpError(err) {
  let cur = err;
  for (let i = 0; i < 5 && cur; i++) {
    if (cur instanceof Error && typeof cur.message === "string" && /posix_spawnp failed/i.test(cur.message)) {
      return true;
    }
    if (cur instanceof SpawnDiagnosticsError) return true;
    cur = cur?.cause;
  }
  return false;
}
function formatPosixSpawnpGuidance(err, packageName) {
  let info;
  if (err instanceof SpawnDiagnosticsError) info = err.info;
  const lines = [];
  lines.push("");
  lines.push("A subprocess spawn failed. This usually means a binary is missing,");
  lines.push("the architecture of an installed native module does not match your");
  lines.push("Node runtime, or your CLI install is incomplete.");
  lines.push("");
  if (info) {
    lines.push(`  binary tried:   ${info.binary}`);
    lines.push(`  absolute path:  ${info.resolvedPath ?? "<not found>"}`);
    lines.push(`  platform/arch:  ${info.platform} (${info.arch})`);
    lines.push(`  node:           ${info.execPath}`);
    lines.push("");
  }
  lines.push("Try, in order:");
  lines.push(`  1. consortium doctor --fix    (clean launchagents, stale daemons, caches)`);
  lines.push(`  2. npm uninstall -g ${packageName} && npm install -g ${packageName}@latest`);
  lines.push(`  3. Re-open your terminal (so you inherit a fresh PATH)`);
  lines.push("");
  return lines.join("\n");
}

var spawnDiagnostics = /*#__PURE__*/Object.freeze({
  __proto__: null,
  SpawnDiagnosticsError: SpawnDiagnosticsError,
  formatPosixSpawnpGuidance: formatPosixSpawnpGuidance,
  isPosixSpawnpError: isPosixSpawnpError,
  spawnPtyWithDiagnostics: spawnPtyWithDiagnostics
});

function platformKey() {
  const plat = process.platform;
  const arch = process.arch;
  if (plat === "linux" && arch === "x64") return "linux-x64";
  if (plat === "linux" && arch === "arm64") return "linux-arm64";
  if (plat === "darwin" && arch === "x64") return "darwin-x64";
  if (plat === "darwin" && arch === "arm64") return "darwin-arm64";
  if (plat === "win32" && arch === "x64") return "win32-x64";
  if (plat === "win32" && arch === "arm64") return "win32-arm64";
  return `${plat}-${arch}`;
}
function resolveTmuxBinary() {
  if (process.env.CONSORTIUM_MUX_PATH) {
    return process.env.CONSORTIUM_MUX_PATH;
  }
  try {
    const bundled = require.resolve(
      `consortium-mux-tmux-${platformKey()}/bin/tmux`
    );
    return bundled;
  } catch {
  }
  return "tmux";
}
function resolveZellijBinary() {
  const exe = process.platform === "win32" ? "zellij.exe" : "zellij";
  const env = process.env.CONSORTIUM_MUX_PATH;
  if (env && (env.endsWith("zellij") || env.endsWith("zellij.exe"))) {
    return env;
  }
  try {
    const bundled = require.resolve(
      `consortium-mux-zellij-${platformKey()}/bin/${exe}`
    );
    return bundled;
  } catch {
  }
  return "zellij";
}

const SENSITIVE_SUBSTRINGS = [
  "TOKEN",
  "SECRET",
  "PASSWORD",
  "API_KEY",
  "APIKEY",
  "PRIVATE_KEY",
  "CREDENTIAL",
  "AUTH_TOKEN"
];
const SENSITIVE_EXACT = /* @__PURE__ */ new Set(["DATABASE_URL", "REDIS_URL", "CODEX_HOME"]);
function filterEnv(env) {
  const out = {};
  for (const [key, value] of Object.entries(env)) {
    if (value === void 0) continue;
    const upper = key.toUpperCase();
    if (SENSITIVE_EXACT.has(upper)) continue;
    if (SENSITIVE_SUBSTRINGS.some((p) => upper.includes(p))) continue;
    out[key] = value;
  }
  return out;
}
function terminalEnv(base = process.env) {
  const env = filterEnv(base);
  if (!env.COLORTERM) env.COLORTERM = "truecolor";
  if (!env.LANG && !env.LC_ALL && !env.LC_CTYPE && process.platform !== "win32") {
    env.LANG = process.platform === "darwin" ? "en_US.UTF-8" : "C.UTF-8";
  }
  return env;
}

const COALESCE_MS = 16;
const IDLE_GAP_MS = 16;
function createOutputBatcher(onFlush) {
  let buffer = [];
  let timer = null;
  let lastFlush = 0;
  const flush = () => {
    timer = null;
    if (buffer.length === 0) return;
    const combined = buffer.length === 1 ? buffer[0] : Buffer.concat(buffer);
    buffer = [];
    lastFlush = Date.now();
    onFlush(combined);
  };
  return {
    push(chunk) {
      buffer.push(chunk);
      if (timer) return;
      const delay = Date.now() - lastFlush >= IDLE_GAP_MS ? 0 : COALESCE_MS;
      timer = setTimeout(flush, delay);
    },
    flushNow() {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      flush();
    },
    dispose() {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      buffer = [];
    }
  };
}

const execFile$1 = node_util.promisify(node_child_process.execFile);
const SESSION_PREFIX$1 = "consortium-";
function sessionName$1(id) {
  return SESSION_PREFIX$1 + id.replace(/[^a-zA-Z0-9_-]/g, "_");
}
function buildHostedNewSessionArgs(sessionName2, opts) {
  const args = [
    "new-session",
    "-d",
    "-P",
    "-F",
    "#{pane_pid}",
    "-s",
    sessionName2,
    "-x",
    String(opts.cols),
    "-y",
    String(opts.rows),
    "-c",
    opts.cwd
  ];
  for (const [key, value] of Object.entries(opts.env)) {
    if (value === void 0 || value === null) continue;
    if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
    args.push("-e", `${key}=${value}`);
  }
  args.push("--", ...opts.command);
  return args;
}
class TmuxBackend {
  mode = "tmux";
  /** tmux binary path (resolved once at construction). */
  bin;
  /** Whether the binary probe succeeded. */
  available;
  /** Map of terminal-id → map of viewer-id → ViewerState */
  viewers = /* @__PURE__ */ new Map();
  /** Weak set tracking known ids (for has()). Populated on create()/attach(). */
  knownIds = /* @__PURE__ */ new Set();
  constructor(bin, available) {
    this.bin = bin;
    this.available = available;
  }
  /** Factory: probes the binary and returns a ready (or unavailable) backend. */
  static async init() {
    const bin = resolveTmuxBinary();
    try {
      await execFile$1(bin, ["-V"], { timeout: 4e3 });
      logger.debug(`[TMUX] Backend initialised with binary: ${bin}`);
      await execFile$1(bin, ["set-option", "-g", "aggressive-resize", "on"], { timeout: 4e3 }).catch(() => {
      });
      return new TmuxBackend(bin, true);
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      logger.debug(`[TMUX] Binary probe failed (${bin}): ${msg} \u2014 backend marked unavailable`);
      return new TmuxBackend(bin, false);
    }
  }
  async create(opts) {
    if (!this.available) return { ok: false, error: "tmux not available" };
    const name = sessionName$1(opts.id);
    const shell = opts.shell ?? process.env.SHELL ?? (process.platform === "win32" ? "cmd.exe" : "/bin/bash");
    const cwd = opts.cwd ?? process.env.HOME ?? "/tmp";
    const env = terminalEnv({ ...process.env, ...opts.env ?? {} });
    const envArgs = [];
    for (const [k, v] of Object.entries(env)) {
      envArgs.push("-e", `${k}=${v}`);
    }
    try {
      await execFile$1(
        this.bin,
        [
          "new-session",
          "-d",
          "-s",
          name,
          "-x",
          String(opts.cols),
          "-y",
          String(opts.rows),
          ...envArgs,
          shell
        ],
        { cwd, timeout: 1e4 }
      );
      await execFile$1(this.bin, ["set-option", "-t", name, "aggressive-resize", "on"], { timeout: 4e3 }).catch(() => {
      });
      this.knownIds.add(opts.id);
      this.viewers.set(opts.id, /* @__PURE__ */ new Map());
      logger.debug(`[TMUX] Created session ${name} (${opts.cols}x${opts.rows})`);
      return { ok: true };
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      logger.debug(`[TMUX] create failed for ${name}: ${msg}`);
      return { ok: false, error: msg };
    }
  }
  /**
   * Host a program (an agent session) as the pane program of a new tmux
   * session. The program is exec'd directly — no shell — so the pane dies
   * with it and its exit is never masked by a lingering prompt.
   *
   * Tuned for a TUI that is watched from the app, not for a shell:
   *  - `status off`: the raw view must be the agent's screen, nothing else.
   *  - `escape-time 0` (server option): tmux's default 500 ms ESC delay makes
   *    every Esc keypress — Claude's interrupt — feel broken.
   *  - `window-size latest` + `aggressive-resize on`: the pane follows the
   *    device that last touched it instead of the smallest attached client.
   *  - Env is passed UNFILTERED via `-e`: the hosted program is the agent and
   *    needs its credentials (viewers only see the rendered screen).
   */
  async createHosted(opts) {
    if (!this.available) return { ok: false, error: "tmux not available" };
    if (opts.command.length === 0) return { ok: false, error: "empty command" };
    const name = sessionName$1(opts.id);
    const args = buildHostedNewSessionArgs(name, opts);
    try {
      const { stdout } = await execFile$1(this.bin, args, { cwd: opts.cwd, timeout: 1e4 });
      const pid = parseInt(stdout.trim(), 10);
      if (!Number.isFinite(pid)) {
        return { ok: false, error: `tmux did not report a pane pid (got "${stdout.trim()}")` };
      }
      const opt = (scope, key, value) => execFile$1(this.bin, ["set-option", ...scope, key, value], { timeout: 4e3 }).catch(() => {
      });
      await Promise.all([
        opt(["-t", name], "status", "off"),
        opt(["-t", name], "aggressive-resize", "on"),
        opt(["-t", name], "window-size", "latest"),
        opt(["-t", name], "history-limit", "50000"),
        opt(["-s"], "escape-time", "0")
      ]);
      this.knownIds.add(opts.id);
      this.viewers.set(opts.id, /* @__PURE__ */ new Map());
      logger.debug(`[TMUX] Hosted ${name} pid=${pid} (${opts.cols}x${opts.rows}) cmd=${opts.command[0]}`);
      return { ok: true, pid };
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      logger.debug(`[TMUX] createHosted failed for ${name}: ${msg}`);
      return { ok: false, error: msg };
    }
  }
  async attach(id, viewerId, cols, rows, onData, onExit) {
    if (!this.available) return { ok: false, error: "tmux not available" };
    const name = sessionName$1(id);
    try {
      await execFile$1(this.bin, ["has-session", "-t", name], { timeout: 4e3 });
    } catch {
      return { ok: false, error: `No tmux session: ${name}` };
    }
    let replay;
    try {
      const { stdout } = await execFile$1(
        this.bin,
        ["capture-pane", "-peJ", "-S", "-10000", "-t", name],
        { timeout: 5e3, maxBuffer: 4 * 1024 * 1024 }
      );
      if (stdout) replay = Buffer.from(stdout, "utf-8");
    } catch (err) {
      logger.debug(`[TMUX] capture-pane failed for ${name}: ${err instanceof Error ? err.message : err}`);
    }
    let pty;
    try {
      const nodePty = await import('node-pty');
      pty = nodePty.spawn(this.bin, ["attach", "-t", name], {
        name: "xterm-256color",
        cols,
        rows,
        cwd: process.env.HOME ?? "/tmp",
        env: terminalEnv(process.env),
        // Raw byte stream — see terminals/index.ts openPty for rationale.
        encoding: null
      });
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      return { ok: false, error: `node-pty spawn failed: ${msg}` };
    }
    this.knownIds.add(id);
    const viewerMap = this.viewers.get(id) ?? /* @__PURE__ */ new Map();
    this.viewers.set(id, viewerMap);
    const existing = viewerMap.get(viewerId);
    if (existing) {
      existing.replacing = true;
      existing.batcher.dispose();
      try {
        existing.pty.kill();
      } catch {
      }
      viewerMap.delete(viewerId);
      logger.debug(`[TMUX] Replaced existing viewer ${viewerId} on session ${name}`);
    }
    const state = { pty, batcher: createOutputBatcher(onData), replacing: false };
    viewerMap.set(viewerId, state);
    pty.onData((raw) => {
      state.batcher.push(Buffer.isBuffer(raw) ? raw : Buffer.from(raw, "utf-8"));
    });
    pty.onExit(({ exitCode }) => {
      if (viewerMap.get(viewerId) === state) {
        viewerMap.delete(viewerId);
      }
      if (state.replacing) {
        state.batcher.dispose();
        logger.debug(`[TMUX] Replaced viewer ${viewerId} pty exited (suppressed)`);
        return;
      }
      state.batcher.flushNow();
      logger.debug(`[TMUX] Viewer ${viewerId} detached from ${name} (exit ${exitCode})`);
      onExit(exitCode ?? 0);
    });
    logger.debug(`[TMUX] Attached viewer ${viewerId} to ${name} (${cols}x${rows})`);
    return { ok: true, replay };
  }
  detach(id, viewerId) {
    const viewerMap = this.viewers.get(id);
    if (!viewerMap) return;
    const state = viewerMap.get(viewerId);
    if (!state) return;
    state.batcher.dispose();
    try {
      state.pty.kill();
    } catch {
    }
    viewerMap.delete(viewerId);
    logger.debug(`[TMUX] Detached viewer ${viewerId} from session ${id}`);
  }
  write(id, data) {
    const viewerMap = this.viewers.get(id);
    if (!viewerMap || viewerMap.size === 0) return false;
    const [state] = viewerMap.values();
    state.pty.write(data);
    return true;
  }
  resize(id, viewerId, cols, rows) {
    const viewerMap = this.viewers.get(id);
    if (!viewerMap) return false;
    const state = viewerMap.get(viewerId);
    if (!state) return false;
    state.pty.resize(cols, rows);
    execFile$1(this.bin, ["refresh-client", "-t", sessionName$1(id), "-C", `${cols}x${rows}`], { timeout: 2e3 }).catch(() => {
    });
    return true;
  }
  async destroy(id) {
    const name = sessionName$1(id);
    const viewerMap = this.viewers.get(id);
    if (viewerMap) {
      for (const [vid] of viewerMap) this.detach(id, vid);
      this.viewers.delete(id);
    }
    this.knownIds.delete(id);
    try {
      await execFile$1(this.bin, ["kill-session", "-t", name], { timeout: 5e3 });
      logger.debug(`[TMUX] Destroyed session ${name}`);
    } catch (err) {
      logger.debug(`[TMUX] kill-session ${name}: ${err instanceof Error ? err.message : err}`);
    }
  }
  async list() {
    if (!this.available) return [];
    try {
      const { stdout } = await execFile$1(
        this.bin,
        ["list-sessions", "-F", "#{session_name}|#{session_created}|#{?session_attached,1,0}"],
        { timeout: 5e3 }
      );
      const descriptors = [];
      for (const line of stdout.split("\n")) {
        const trimmed = line.trim();
        if (!trimmed.startsWith(SESSION_PREFIX$1)) continue;
        const parts = trimmed.split("|");
        if (parts.length < 3) continue;
        const rawName = parts[0];
        const createdAt = parseInt(parts[1] ?? "0", 10) * 1e3;
        const id = rawName.slice(SESSION_PREFIX$1.length);
        descriptors.push({
          id,
          mode: "tmux",
          status: "running",
          createdAt: isNaN(createdAt) ? 0 : createdAt
        });
      }
      return descriptors;
    } catch {
      return [];
    }
  }
  has(id) {
    return this.knownIds.has(id);
  }
}

const execFile = node_util.promisify(node_child_process.execFile);
const SESSION_PREFIX = "consortium-";
const SESSION_INIT_GRACE_MS = 800;
function sessionName(id) {
  return SESSION_PREFIX + id.replace(/[^a-zA-Z0-9_-]/g, "_");
}
class ZellijBackend {
  mode = "zellij";
  bin;
  available;
  sessions = /* @__PURE__ */ new Map();
  knownIds = /* @__PURE__ */ new Set();
  constructor(bin, available) {
    this.bin = bin;
    this.available = available;
  }
  static async init() {
    const bin = resolveZellijBinary();
    try {
      await execFile(bin, ["--version"], { timeout: 4e3 });
      logger.debug(`[ZELLIJ] Backend initialised with binary: ${bin}`);
      return new ZellijBackend(bin, true);
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      logger.debug(`[ZELLIJ] Binary probe failed (${bin}): ${msg} \u2014 backend marked unavailable`);
      return new ZellijBackend(bin, false);
    }
  }
  async create(opts) {
    if (!this.available) return { ok: false, error: "zellij not available" };
    if (this.sessions.has(opts.id)) return { ok: false, error: "already exists" };
    const name = sessionName(opts.id);
    const cwd = opts.cwd ?? process.env.HOME ?? process.env.USERPROFILE ?? os.tmpdir();
    const env = terminalEnv({ ...process.env, ...opts.env ?? {} });
    let host;
    try {
      const nodePty = await import('node-pty');
      host = nodePty.spawn(this.bin, ["--session", name], {
        name: "xterm-256color",
        cols: opts.cols,
        rows: opts.rows,
        cwd,
        env
      });
      host.onData(() => {
      });
      host.onExit(() => {
        logger.debug(`[ZELLIJ] Host pty exited for session ${name}`);
        this.sessions.delete(opts.id);
        this.knownIds.delete(opts.id);
      });
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      return { ok: false, error: `node-pty spawn failed: ${msg}` };
    }
    await new Promise((resolve) => setTimeout(resolve, SESSION_INIT_GRACE_MS));
    this.sessions.set(opts.id, { host, viewers: /* @__PURE__ */ new Map() });
    this.knownIds.add(opts.id);
    logger.debug(`[ZELLIJ] Created session ${name} (${opts.cols}x${opts.rows})`);
    return { ok: true };
  }
  async attach(id, viewerId, cols, rows, onData, onExit) {
    if (!this.available) return { ok: false, error: "zellij not available" };
    const session = this.sessions.get(id);
    if (!session) return { ok: false, error: `No zellij session: ${sessionName(id)}` };
    const name = sessionName(id);
    let replay;
    let dumpDir;
    try {
      dumpDir = await fs$3.mkdtemp(path.join(os.tmpdir(), "consortium-zellij-"));
      const dumpPath = path.join(dumpDir, "screen.txt");
      await execFile(
        this.bin,
        ["--session", name, "action", "dump-screen", dumpPath],
        { timeout: 4e3 }
      );
      replay = await fs$3.readFile(dumpPath);
      await fs$3.unlink(dumpPath).catch(() => {
      });
      await fs$3.rmdir(dumpDir).catch(() => {
      });
    } catch (err) {
      logger.debug(`[ZELLIJ] dump-screen failed for ${name}: ${err instanceof Error ? err.message : err}`);
      if (dumpDir) await fs$3.rmdir(dumpDir).catch(() => {
      });
    }
    let pty;
    try {
      const nodePty = await import('node-pty');
      pty = nodePty.spawn(this.bin, ["--session", name, "attach"], {
        name: "xterm-256color",
        cols,
        rows,
        cwd: process.env.HOME ?? process.env.USERPROFILE ?? os.tmpdir(),
        env: terminalEnv(process.env),
        // Raw byte stream — see terminals/index.ts openPty for rationale.
        encoding: null
      });
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      return { ok: false, error: `node-pty spawn failed: ${msg}` };
    }
    this.knownIds.add(id);
    const state = { pty, batcher: createOutputBatcher(onData) };
    session.viewers.set(viewerId, state);
    pty.onData((raw) => {
      state.batcher.push(Buffer.isBuffer(raw) ? raw : Buffer.from(raw, "utf-8"));
    });
    pty.onExit(({ exitCode }) => {
      state.batcher.flushNow();
      session.viewers.delete(viewerId);
      logger.debug(`[ZELLIJ] Viewer ${viewerId} detached from ${name} (exit ${exitCode})`);
      onExit(exitCode ?? 0);
    });
    logger.debug(`[ZELLIJ] Attached viewer ${viewerId} to ${name} (${cols}x${rows})`);
    return { ok: true, replay };
  }
  detach(id, viewerId) {
    const session = this.sessions.get(id);
    if (!session) return;
    const state = session.viewers.get(viewerId);
    if (!state) return;
    state.batcher.dispose();
    try {
      state.pty.kill();
    } catch {
    }
    session.viewers.delete(viewerId);
    logger.debug(`[ZELLIJ] Detached viewer ${viewerId} from session ${id}`);
  }
  write(id, data) {
    const session = this.sessions.get(id);
    if (!session || session.viewers.size === 0) return false;
    const [state] = session.viewers.values();
    state.pty.write(data);
    return true;
  }
  resize(id, viewerId, cols, rows) {
    const session = this.sessions.get(id);
    if (!session) return false;
    const state = session.viewers.get(viewerId);
    if (!state) return false;
    state.pty.resize(cols, rows);
    return true;
  }
  async destroy(id) {
    const session = this.sessions.get(id);
    const name = sessionName(id);
    if (session) {
      for (const [vid] of session.viewers) this.detach(id, vid);
      try {
        session.host.kill();
      } catch {
      }
      this.sessions.delete(id);
    }
    this.knownIds.delete(id);
    try {
      await execFile(this.bin, ["kill-session", name], { timeout: 5e3 });
      logger.debug(`[ZELLIJ] Destroyed session ${name}`);
    } catch (err) {
      logger.debug(`[ZELLIJ] kill-session ${name}: ${err instanceof Error ? err.message : err}`);
    }
  }
  async list() {
    if (!this.available) return [];
    try {
      const { stdout } = await execFile(
        this.bin,
        ["list-sessions", "--no-formatting"],
        { timeout: 5e3 }
      );
      const descriptors = [];
      for (const line of stdout.split("\n")) {
        const trimmed = line.trim();
        if (!trimmed.startsWith(SESSION_PREFIX)) continue;
        const nameMatch = trimmed.match(/^(\S+)/);
        if (!nameMatch) continue;
        const rawName = nameMatch[1];
        const id = rawName.slice(SESSION_PREFIX.length);
        const exited = /EXITED/i.test(trimmed);
        descriptors.push({
          id,
          mode: "zellij",
          status: exited ? "exited" : "running",
          createdAt: 0
          // zellij doesn't expose creation epoch in list-sessions
        });
      }
      return descriptors;
    } catch {
      return [];
    }
  }
  has(id) {
    return this.knownIds.has(id);
  }
}

const REPLAY_LIMIT_BYTES = 256 * 1024;
class ExternalBackend {
  mode = "external";
  terminals = /* @__PURE__ */ new Map();
  /**
   * A session process has connected and is mirroring its PTY.
   * Returns a handle the socket layer drives; calling it again for the same
   * id replaces the registration (the session reconnected).
   */
  register(id, write) {
    const existing = this.terminals.get(id);
    const terminal = {
      write,
      replay: existing?.replay ?? Buffer.alloc(0),
      createdAt: existing?.createdAt ?? Date.now(),
      // Keep viewers across a reconnect: the app is attached to the
      // session, not to this particular socket.
      viewers: existing?.viewers ?? /* @__PURE__ */ new Map()
    };
    this.terminals.set(id, terminal);
    logger.debug(`[EXTERNAL] Registered mirrored terminal ${id} (${terminal.viewers.size} viewer(s) waiting)`);
    return {
      pushOutput: (chunk) => {
        const current = this.terminals.get(id);
        if (current !== terminal) return;
        const combined = terminal.replay.length === 0 ? Buffer.from(chunk) : Buffer.concat([terminal.replay, chunk]);
        terminal.replay = combined.length > REPLAY_LIMIT_BYTES ? combined.subarray(combined.length - REPLAY_LIMIT_BYTES) : combined;
        for (const viewer of terminal.viewers.values()) {
          viewer.onData(chunk);
        }
      },
      close: (code) => {
        const current = this.terminals.get(id);
        if (current !== terminal) return;
        for (const viewer of terminal.viewers.values()) {
          viewer.onExit(code);
        }
        this.terminals.delete(id);
        logger.debug(`[EXTERNAL] Mirrored terminal ${id} ended (code ${code})`);
      }
    };
  }
  async create() {
    return { ok: false, error: "External terminals cannot be created by the daemon" };
  }
  async attach(id, viewerId, _cols, _rows, onData, onExit) {
    const terminal = this.terminals.get(id);
    if (!terminal) return { ok: false, error: `No mirrored terminal: ${id}` };
    terminal.viewers.set(viewerId, { onData, onExit });
    logger.debug(`[EXTERNAL] Viewer ${viewerId} attached to ${id}`);
    return { ok: true, replay: terminal.replay.length > 0 ? terminal.replay : void 0 };
  }
  /**
   * No createHosted here on purpose: TerminalManager selects the hosting
   * backend by which one implements it, so advertising a version that always
   * fails would shadow tmux and break daemon-spawned session hosting.
   */
  detach(id, viewerId) {
    const terminal = this.terminals.get(id);
    if (!terminal) return;
    terminal.viewers.delete(viewerId);
    logger.debug(`[EXTERNAL] Viewer ${viewerId} detached from ${id}`);
  }
  write(id, data) {
    const terminal = this.terminals.get(id);
    if (!terminal) return false;
    terminal.write(data);
    return true;
  }
  /**
   * Deliberately a no-op — see the class comment. Returns true so callers
   * treat the terminal as found and do not fall through to the ephemeral
   * pty path with the same id.
   */
  resize(id, _viewerId, _cols, _rows) {
    return this.terminals.has(id);
  }
  async destroy(id) {
    const terminal = this.terminals.get(id);
    if (!terminal) return;
    terminal.viewers.clear();
    logger.debug(`[EXTERNAL] Dropped viewers for mirrored terminal ${id} (session keeps running)`);
  }
  async list() {
    return [...this.terminals.entries()].map(([id, t]) => ({
      id,
      mode: "external",
      status: "running",
      createdAt: t.createdAt
    }));
  }
  has(id) {
    return this.terminals.has(id);
  }
}

const MAX_TERMINALS = 20;
const HOSTED_SESSION_TERMINAL_PREFIX = "session-";
function isHostedSessionTerminalId(id) {
  return id.startsWith(HOSTED_SESSION_TERMINAL_PREFIX);
}
class TerminalManager {
  backends = {};
  /** Ephemeral sessions: back-compat with existing pty:open flow */
  ephemeralPtys = /* @__PURE__ */ new Map();
  /** Tracks which ids are managed by a persistent backend */
  persistentIds = /* @__PURE__ */ new Set();
  /** @internal — call init() instead */
  constructor() {
  }
  /** Async factory: initialises all available backends. */
  static async init() {
    const mgr = new TerminalManager();
    const [tmux, zellij] = await Promise.all([TmuxBackend.init(), ZellijBackend.init()]);
    mgr.backends.tmux = tmux;
    mgr.backends.zellij = zellij;
    mgr.backends.external = new ExternalBackend();
    logger.debug("[TerminalManager] Initialised. Capabilities: " + JSON.stringify(mgr.getCapabilities()));
    return mgr;
  }
  /** Returns which persistent backends are functional (binary present and probe succeeded). */
  getCapabilities() {
    const tmux = this.backends.tmux;
    const zellij = this.backends.zellij;
    return {
      tmux: !!tmux,
      zellij: !!zellij
    };
  }
  /** Pick a sensible default backend for the host platform. */
  defaultMode() {
    const caps = this.getCapabilities();
    if (process.platform === "win32") {
      if (caps.zellij) return "zellij";
      if (caps.tmux) return "tmux";
    } else {
      if (caps.tmux) return "tmux";
      if (caps.zellij) return "zellij";
    }
    return "ephemeral";
  }
  totalCount() {
    return this.ephemeralPtys.size + this.persistentIds.size;
  }
  pickBackend(requestedMode) {
    if (requestedMode === "ephemeral") return null;
    return this.backends[requestedMode] ?? null;
  }
  // -------------------------------------------------------------------------
  // Hosted agent sessions
  // -------------------------------------------------------------------------
  /** Registry for terminals mirrored by other processes (see ExternalBackend). */
  externalBackend() {
    return this.backends.external;
  }
  /** Which backend can host agent sessions on this machine, if any. */
  hostingMode() {
    for (const backend of Object.values(this.backends)) {
      if (backend?.createHosted) return backend.mode;
    }
    return null;
  }
  /**
   * Run a program inside a multiplexer session so its TUI is attachable
   * through the ordinary pty:open { persistentTerminalId } flow. Hosted
   * sessions do NOT count against MAX_TERMINALS — that cap protects against
   * runaway user terminals, and an agent session is bounded by the session
   * spawner instead.
   */
  async hostSession(opts) {
    if (!isHostedSessionTerminalId(opts.id)) {
      return { ok: false, error: `hosted terminal id must start with "${HOSTED_SESSION_TERMINAL_PREFIX}"` };
    }
    for (const backend of Object.values(this.backends)) {
      if (!backend?.createHosted) continue;
      const result = await backend.createHosted(opts);
      return { ...result, mode: backend.mode };
    }
    return { ok: false, error: "No multiplexer backend can host sessions on this machine" };
  }
  // -------------------------------------------------------------------------
  // Persistent terminal API
  // -------------------------------------------------------------------------
  async createTerminal(mode, opts) {
    if (mode === "ephemeral") {
      return { ok: false, mode, error: "Use openPty() for ephemeral terminals" };
    }
    if (this.totalCount() >= MAX_TERMINALS) {
      return { ok: false, mode, error: `Maximum terminal limit (${MAX_TERMINALS}) reached` };
    }
    const backend = this.pickBackend(mode);
    if (!backend) {
      return { ok: false, mode, error: `Backend '${mode}' not available` };
    }
    const result = await backend.create(opts);
    if (result.ok) {
      this.persistentIds.add(opts.id);
    }
    return { ok: result.ok, mode, error: result.error };
  }
  async attachTerminal(id, viewerId, cols, rows, onData, onExit) {
    for (const backend of Object.values(this.backends)) {
      if (!backend) continue;
      if (backend.has(id)) {
        return backend.attach(id, viewerId, cols, rows, onData, onExit);
      }
    }
    for (const backend of Object.values(this.backends)) {
      if (!backend) continue;
      const list = await backend.list();
      if (list.some((d) => d.id === id)) {
        this.persistentIds.add(id);
        return backend.attach(id, viewerId, cols, rows, onData, onExit);
      }
    }
    return { ok: false, error: `No persistent terminal found with id: ${id}` };
  }
  detachTerminal(id, viewerId) {
    for (const backend of Object.values(this.backends)) {
      if (backend?.has(id)) {
        backend.detach(id, viewerId);
        return;
      }
    }
  }
  writeTerminal(id, data) {
    for (const backend of Object.values(this.backends)) {
      if (backend?.has(id)) {
        return backend.write(id, data);
      }
    }
    return false;
  }
  resizeTerminal(id, viewerId, cols, rows) {
    for (const backend of Object.values(this.backends)) {
      if (backend?.has(id)) {
        return backend.resize(id, viewerId, cols, rows);
      }
    }
    return false;
  }
  async destroyTerminal(id) {
    for (const backend of Object.values(this.backends)) {
      if (backend?.has(id)) {
        await backend.destroy(id);
        this.persistentIds.delete(id);
        return;
      }
    }
    await Promise.all(
      Object.values(this.backends).map(
        (b) => b?.destroy(id).catch(() => {
        })
      )
    );
    this.persistentIds.delete(id);
  }
  async listTerminals() {
    const results = [];
    for (const backend of Object.values(this.backends)) {
      if (!backend) continue;
      const list = await backend.list().catch(() => []);
      results.push(...list);
    }
    return results.filter((d) => !isHostedSessionTerminalId(d.id));
  }
  // -------------------------------------------------------------------------
  // Ephemeral PTY API — preserved exactly from original ptyManager
  // -------------------------------------------------------------------------
  openPty(terminalId, cols, rows, onData, onExit) {
    if (this.ephemeralPtys.has(terminalId)) {
      logger.debug(`[PTY] Re-attach to existing ephemeral session ${terminalId}`);
      return { ok: true };
    }
    if (this.totalCount() >= MAX_TERMINALS) {
      return { ok: false, error: `Maximum terminal limit (${MAX_TERMINALS}) reached` };
    }
    try {
      const shell = process.env.SHELL ?? (process.platform === "win32" ? "cmd.exe" : "/bin/bash");
      const cwd = os.homedir();
      const filteredEnv = terminalEnv(process.env);
      const pty = spawnPtyWithDiagnostics(shell, [], {
        name: "xterm-256color",
        cols,
        rows,
        cwd,
        env: filteredEnv,
        // encoding:null → node-pty emits raw Buffers on onData and
        // accepts Buffers on write(). Keeps the byte stream exact so
        // multi-byte UTF-8 / split ANSI escape sequences reach xterm
        // intact (VS-Code-grade TUI rendering).
        encoding: null
      });
      const batcher = createOutputBatcher(onData);
      pty.onData((data) => {
        batcher.push(Buffer.isBuffer(data) ? data : Buffer.from(data, "utf-8"));
      });
      pty.onExit(() => {
        batcher.flushNow();
        this.ephemeralPtys.delete(terminalId);
        logger.debug(`[PTY] Session ${terminalId} exited`);
        onExit();
      });
      this.ephemeralPtys.set(terminalId, { pty, cols, rows });
      logger.debug(`[PTY] Opened session ${terminalId} (${cols}x${rows}, shell: ${shell})`);
      return { ok: true };
    } catch (err) {
      const message = err instanceof Error ? err.message : "Failed to spawn PTY";
      logger.debug(`[PTY] Failed to open session ${terminalId}: ${message}`);
      return { ok: false, error: message };
    }
  }
  writePty(terminalId, data) {
    const session = this.ephemeralPtys.get(terminalId);
    if (!session) return;
    session.pty.write(data);
  }
  resizePty(terminalId, cols, rows) {
    const session = this.ephemeralPtys.get(terminalId);
    if (!session) return;
    session.pty.resize(cols, rows);
    session.cols = cols;
    session.rows = rows;
  }
  closePty(terminalId) {
    const session = this.ephemeralPtys.get(terminalId);
    if (!session) return;
    try {
      session.pty.kill();
    } catch {
    }
    this.ephemeralPtys.delete(terminalId);
    logger.debug(`[PTY] Closed session ${terminalId}`);
  }
  closeAllPty() {
    for (const [terminalId, session] of this.ephemeralPtys) {
      try {
        session.pty.kill();
      } catch {
      }
      logger.debug(`[PTY] Closed session ${terminalId} (shutdown)`);
    }
    this.ephemeralPtys.clear();
  }
}
let _instance = null;
async function getTerminalManager() {
  if (!_instance) {
    _instance = await TerminalManager.init();
  }
  return _instance;
}

function expandHome$1(value, home) {
  if (value === "~") return home;
  if (value.startsWith("~/")) return path.join(home, value.slice(2));
  return value;
}
function resolveOverride(raw, fallback, home) {
  if (typeof raw !== "string") return fallback;
  const trimmed = raw.trim();
  if (trimmed.length === 0) return fallback;
  return expandHome$1(trimmed, home);
}
function resolveClaudeConfigDir(opts = {}) {
  const env = opts.env ?? process.env;
  const home = opts.homeDir ?? os.homedir();
  return resolveOverride(env.CLAUDE_CONFIG_DIR, path.join(home, ".claude"), home);
}
function resolveCodexHome(opts = {}) {
  const env = opts.env ?? process.env;
  const home = opts.homeDir ?? os.homedir();
  return resolveOverride(env.CODEX_HOME, path.join(home, ".codex"), home);
}

const BINARY_CHECK_TIMEOUT_MS = 4e3;
const defaultProbeRunCommand = (cmd, args) => new Promise((resolve) => {
  node_child_process.execFile(cmd, args, { timeout: BINARY_CHECK_TIMEOUT_MS }, (err, stdout, stderr) => {
    const so = stdout == null ? "" : String(stdout);
    const se = stderr == null ? "" : String(stderr);
    if (!err) {
      resolve({ stdout: so, stderr: se, code: 0 });
      return;
    }
    const code = err.code;
    resolve({ stdout: so, stderr: se, code: typeof code === "number" ? code : 1 });
  });
});
async function checkAgentBinary(run, binary, args = ["--version"]) {
  try {
    const result = await run(binary, args);
    if (result.code === 0) {
      const version = result.stdout.trim() || void 0;
      logger.debug(`[auth-probe] ${binary} binary present${version ? ` (${version})` : ""}`);
      return { present: true, version };
    }
    logger.debug(`[auth-probe] ${binary} binary check exit=${result.code} \u2014 treating as absent`);
    return { present: false };
  } catch (err) {
    logger.debug(
      `[auth-probe] ${binary} binary check threw (${err.message}) \u2014 treating as absent`
    );
    return { present: false };
  }
}

const AGENT_AUTH_KINDS = [
  "claude",
  "codex",
  "gemini",
  "grok",
  "pi",
  "opencode",
  "consortium"
];
const AGENT_AUTH_CAPABILITIES = {
  claude: {
    agent: "claude",
    binary: "claude",
    modes: ["native-local", "paste-code", "api-key"],
    localMode: "native-local",
    // Anthropic has no device-code grant; `code=true` on the authorize URL
    // makes claude.ai display an OOB code instead, which is the only
    // remotely-completable path.
    remoteMode: "paste-code",
    // Emits JSON: loggedIn / authMethod / email / orgName / subscriptionType.
    statusCommand: ["auth", "status"],
    loginCommand: ["auth", "login", "--claudeai"],
    remoteLoginCommand: null,
    credentialPaths: [
      "$CLAUDE_CONFIG_DIR/.credentials.json",
      "~/.claude/.credentials.json",
      "keychain:Claude Code-credentials"
    ],
    apiKeyEnvVars: ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
    verified: { cliVersion: "2.1.219", at: "2026-08-05" },
    notes: "`claude setup-token` also mints a long-lived token interactively; not used because it needs a TTY and paste-code already covers the remote case."
  },
  codex: {
    agent: "codex",
    binary: "codex",
    modes: ["native-local", "device-code", "api-key"],
    localMode: "native-local",
    // VERIFIED 2026-08-05: `codex login --device-auth` prints
    // https://auth.openai.com/codex/device + a XXXX-XXXXX code (15 min).
    // The CLI polls and writes its own auth.json — we neither hold the
    // device_code nor synthesize the credential file.
    remoteMode: "device-code",
    statusCommand: ["login", "status"],
    loginCommand: ["login"],
    remoteLoginCommand: ["login", "--device-auth"],
    credentialPaths: ["$CODEX_HOME/auth.json", "~/.codex/auth.json"],
    apiKeyEnvVars: ["OPENAI_API_KEY"],
    verified: { cliVersion: "0.144.4", at: "2026-08-05" },
    notes: "Also accepts `codex login --with-api-key` / `--with-access-token` on stdin, which avoids hand-writing auth.json (id_token required, last_refresh must be recent or codex rotates the refresh token \u2014 WAVE0_SPIKE_RESULTS \xA7S4\u2032)."
  },
  gemini: {
    agent: "gemini",
    binary: "gemini",
    // VERIFIED 2026-08-05 (0.53.1): there is NO `gemini auth`/`login`
    // subcommand. OAuth happens inside the interactive TUI only; headless
    // invocations refuse with "run the Gemini CLI in an interactive
    // terminal to log in, provide a GEMINI_API_KEY, or configure ADC".
    modes: ["api-key"],
    localMode: null,
    remoteMode: null,
    statusCommand: null,
    loginCommand: null,
    remoteLoginCommand: null,
    credentialPaths: [
      "~/.gemini/oauth_creds.json",
      "~/.gemini/google_accounts.json",
      "~/.config/gcloud/application_default_credentials.json"
    ],
    apiKeyEnvVars: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
    verified: { cliVersion: "0.53.1", at: "2026-08-05" },
    notes: "A user-code path DOES exist in the bundle (NO_BROWSER=true \u2192 authWithUserCode \u2192 https://codeassist.google.com/authcode) but it requires an interactive TTY and alternate-screen handling, so it is a cli-spawn candidate for a later wave, not a shipped mode. The previous gcloud device-code adapter captured Application Default Credentials \u2014 a DIFFERENT credential than the one gemini-cli reads \u2014 and was removed."
  },
  grok: {
    agent: "grok",
    binary: "grok",
    modes: ["native-local", "device-code", "api-key"],
    localMode: "native-local",
    // VERIFIED 2026-08-05: `grok login --device-auth` prints, on STDERR,
    // https://accounts.x.ai/oauth2/device?user_code=XXXX-XXXX plus the
    // bare code. The URL embeds the code (RFC 8628
    // verification_uri_complete), so the app can offer a single tap.
    remoteMode: "device-code",
    statusCommand: null,
    loginCommand: ["login", "--oauth"],
    remoteLoginCommand: ["login", "--device-auth"],
    credentialPaths: ["~/.grok/auth.json"],
    apiKeyEnvVars: ["XAI_API_KEY", "GROK_API_KEY", "GROK_CODE_XAI_API_KEY"],
    verified: { cliVersion: "0.2.118", at: "2026-08-05" },
    notes: "Device-auth prints to stderr, not stdout \u2014 a stdout-only parser sees nothing. Requires a SuperGrok / X Premium+ subscription; without one the CLI errors and the flow fails loud."
  },
  pi: {
    agent: "pi",
    binary: "pi",
    // VERIFIED 2026-08-05 (0.83.0): `pi auth` only PRINTS credentials for
    // external clients (print-api-key / print-bearer-token) — it is a
    // reader, not a login. Credentials come from --api-key or env.
    modes: ["api-key"],
    localMode: null,
    remoteMode: null,
    // Deliberately null: `pi auth print-api-key` writes the secret to
    // stdout, so it must never be used as a status probe.
    statusCommand: null,
    loginCommand: null,
    remoteLoginCommand: null,
    credentialPaths: ["~/.pi/settings.json"],
    apiKeyEnvVars: ["GOOGLE_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
    verified: { cliVersion: "0.83.0", at: "2026-08-05" },
    notes: "BYOK only; provider is selected per-invocation with --provider/--model."
  },
  opencode: {
    agent: "opencode",
    binary: "opencode",
    // VERIFIED 2026-08-05 (1.18.14): `opencode auth login` is an
    // interactive provider picker (clack TUI) covering ~75 providers
    // including Anthropic OAuth; `opencode auth list` reports stored
    // credentials WITHOUT printing them.
    modes: ["api-key", "cli-spawn"],
    localMode: "cli-spawn",
    remoteMode: null,
    statusCommand: ["auth", "list"],
    loginCommand: ["auth", "login"],
    remoteLoginCommand: null,
    credentialPaths: ["~/.local/share/opencode/auth.json"],
    apiKeyEnvVars: ["OPENCODE_API_KEY"],
    verified: { cliVersion: "1.18.14", at: "2026-08-05" },
    notes: "cli-spawn is listed but not yet driven by a daemon flow; until that lands the UI offers api-key only. Provider selection is part of the TUI, so a spawn adapter must relay the picker, not just a URL."
  },
  consortium: {
    agent: "consortium",
    binary: "consortium",
    // The managed proxy: credentials are the user's Consortium account,
    // established at app login. There is nothing per-machine to connect.
    modes: [],
    localMode: null,
    remoteMode: null,
    statusCommand: null,
    loginCommand: null,
    remoteLoginCommand: null,
    credentialPaths: ["$CONSORTIUM_HOME_DIR/access.key"],
    apiKeyEnvVars: ["CONSORTIUM_TOKEN"],
    verified: null,
    notes: "Managed proxy \u2014 no per-machine provider login."
  }
};
function getAgentAuthCapability(agent) {
  return AGENT_AUTH_CAPABILITIES[agent];
}
function connectableAgents() {
  return AGENT_AUTH_KINDS.map((a) => AGENT_AUTH_CAPABILITIES[a]).filter((c) => c.modes.length > 0);
}

function parseClaudeStatus(stdout) {
  const start = stdout.indexOf("{");
  const end = stdout.lastIndexOf("}");
  if (start < 0 || end <= start) return { status: "unknown", reason: "no JSON in output" };
  let parsed;
  try {
    parsed = JSON.parse(stdout.slice(start, end + 1));
  } catch {
    return { status: "unknown", reason: "unparseable JSON" };
  }
  if (parsed.loggedIn !== true) return { status: "missing" };
  const email = typeof parsed.email === "string" ? parsed.email : void 0;
  const account = typeof parsed.orgName === "string" ? parsed.orgName : void 0;
  const tier = typeof parsed.subscriptionType === "string" ? parsed.subscriptionType : void 0;
  const method = typeof parsed.authMethod === "string" ? parsed.authMethod : void 0;
  return {
    status: "present",
    identity: email || account ? { email, account } : void 0,
    // e.g. "claude.ai · pro" — shown as a subtitle, never a credential.
    detail: [method, tier].filter(Boolean).join(" \xB7 ") || void 0
  };
}
function parseCodexStatus(stdout) {
  const text = stdout.toLowerCase();
  if (text.includes("not logged in")) return { status: "missing" };
  if (text.includes("logged in")) {
    const method = /logged in using ([^\n.]+)/i.exec(stdout)?.[1]?.trim();
    return { status: "present", detail: method };
  }
  return { status: "unknown", reason: "unrecognised codex status output" };
}
function parseOpencodeStatus(stdout) {
  const match = /(\d+)\s+credentials?/i.exec(stdout);
  if (!match) return { status: "unknown", reason: "unrecognised opencode auth output" };
  const count = Number(match[1]);
  if (count <= 0) return { status: "missing" };
  return { status: "present", detail: `${count} provider${count === 1 ? "" : "s"}` };
}
const PARSERS = {
  claude: parseClaudeStatus,
  codex: parseCodexStatus,
  opencode: parseOpencodeStatus
};
async function probeCliStatus(agent, run) {
  const cap = getAgentAuthCapability(agent);
  const parser = PARSERS[agent];
  if (!cap?.statusCommand || !parser) {
    return { status: "unknown", reason: "no status command for this agent" };
  }
  let result;
  try {
    result = await run(cap.binary, [...cap.statusCommand]);
  } catch (err) {
    return { status: "unknown", reason: err instanceof Error ? err.message : String(err) };
  }
  const verdict = parser(`${result.stdout}
${result.stderr}`);
  logger.debug(`[auth-probe] ${agent} status command \u2192 ${verdict.status}`);
  return verdict;
}

const KEYCHAIN_SERVICE = "Claude Code-credentials";
function parseCredentialsBlob(blob) {
  if (!blob || typeof blob !== "object") return null;
  let obj = blob;
  if (obj.claudeAiOauth && typeof obj.claudeAiOauth === "object") {
    obj = obj.claudeAiOauth;
  }
  const token = typeof obj.access_token === "string" && obj.access_token || typeof obj.accessToken === "string" && obj.accessToken || typeof obj.token === "string" && obj.token || void 0;
  const identity = {};
  const expiresAtMs = typeof obj.expiresAt === "number" ? obj.expiresAt : void 0;
  const expiresAtSec = typeof obj.expires_at === "number" ? obj.expires_at : void 0;
  if (expiresAtMs !== void 0) {
    identity.expiresAt = expiresAtMs;
  } else if (expiresAtSec !== void 0) {
    identity.expiresAt = expiresAtSec * 1e3;
  }
  if (typeof obj.email === "string") {
    identity.email = obj.email;
  } else if (obj.account && typeof obj.account === "object") {
    const acct = obj.account;
    if (typeof acct.email === "string") identity.email = acct.email;
    else if (typeof acct.email_address === "string") identity.email = acct.email_address;
  }
  return { token: token || void 0, identity };
}
function readCredentialFile(path) {
  if (!fs.existsSync(path)) return null;
  let raw;
  try {
    raw = fs.readFileSync(path, "utf8");
  } catch (err) {
    logger.debug(
      `claude auth probe: unable to read ${path} (${err.message})`
    );
    return { path, state: "invalid" };
  }
  if (!raw.trim()) {
    logger.debug(`claude auth probe: ${path} exists but is empty`);
    return { path, state: "invalid" };
  }
  let parsed;
  try {
    parsed = JSON.parse(raw);
  } catch (err) {
    logger.debug(
      `claude auth probe: ${path} is not valid JSON (${err.message})`
    );
    return { path, state: "invalid" };
  }
  const result = parseCredentialsBlob(parsed);
  if (!result || !result.token) {
    logger.debug(`claude auth probe: ${path} parsed but no token field present`);
    return { path, state: "invalid" };
  }
  return { path, state: "present", identity: result.identity };
}
class ClaudeAuthProbe {
  env;
  home;
  platform;
  run;
  nowFn;
  constructor(options = {}) {
    this.env = options.env ?? process.env;
    this.home = options.homeDir ?? os.homedir();
    this.platform = options.platform ?? process.platform;
    this.run = options.runCommand ?? defaultProbeRunCommand;
    this.nowFn = options.now ?? Date.now;
  }
  /**
   * Ask Claude Code itself, and inspect the machine, at the same time.
   *
   * `claude auth status` is the authoritative answer — it reflects whatever
   * precedence Claude Code applies internally, and it returns the account
   * email and subscription tier, which no amount of file inspection can
   * produce. But it is only authoritative when it says YES: "not logged in"
   * coexists perfectly well with an `ANTHROPIC_API_KEY` in the environment
   * that the CLI would happily use, so a negative falls through to the
   * credential discovery below.
   *
   * Both run concurrently. Serialising them would stack a second exec
   * timeout in front of the macOS Keychain lookup and could blow the probe
   * registry's 5 s budget — the same reason the binary check is started
   * before, not after, credential discovery.
   */
  async probe() {
    const cliStatus = probeCliStatus("claude", this.run);
    const [discovered, verdict] = await Promise.all([this.discoverCredentials(), cliStatus]);
    if (verdict.status !== "present") return discovered;
    logger.debug(`claude auth probe: CLI reports signed in (${verdict.detail ?? "no detail"})`);
    return {
      ...discovered,
      status: "present",
      source: "cli",
      identity: verdict.identity ?? discovered.identity
    };
  }
  async discoverCredentials() {
    const lastCheckedAt = this.nowFn();
    let warning;
    const binaryCheck = checkAgentBinary(this.run, "claude");
    const base = async (partial) => {
      const binary = await binaryCheck;
      const out = {
        agent: "claude",
        lastCheckedAt,
        ...partial,
        binaryPresent: binary.present
      };
      if (!binary.present && out.status !== "missing") {
        warning = warning ?? "binary-missing";
      }
      if (warning && !out.warning) out.warning = warning;
      return out;
    };
    const oauthEnv = this.env.CLAUDE_CODE_OAUTH_TOKEN;
    if (oauthEnv && oauthEnv.length > 0) {
      logger.debug(
        `claude auth probe: env CLAUDE_CODE_OAUTH_TOKEN present <${oauthEnv.length} chars>`
      );
      return base({ status: "present", source: "env" });
    }
    const apiKeyEnv = this.env.ANTHROPIC_API_KEY;
    if (apiKeyEnv && apiKeyEnv.length > 0) {
      logger.debug(
        `claude auth probe: env ANTHROPIC_API_KEY present <${apiKeyEnv.length} chars>`
      );
      return base({ status: "present", source: "env" });
    }
    if (this.platform === "darwin") {
      try {
        const result = await this.run("security", [
          "find-generic-password",
          "-s",
          KEYCHAIN_SERVICE,
          "-w"
        ]);
        if (result.code === 0 && result.stdout.trim().length > 0) {
          logger.debug(
            `claude auth probe: keychain hit <${result.stdout.trim().length} chars>`
          );
          return base({ status: "present", source: "keychain" });
        }
        const stderrLower = (result.stderr || "").toLowerCase();
        const isLocked = result.code === 51 || stderrLower.includes("user interaction is not allowed") || stderrLower.includes("keychain access locked") || stderrLower.includes("keychain") && stderrLower.includes("locked");
        if (isLocked) {
          logger.debug(
            `claude auth probe: keychain locked (exit=${result.code}); falling through with warning`
          );
          warning = "keychain-locked";
        }
        logger.debug(
          `claude auth probe: keychain miss (exit=${result.code})`
        );
      } catch (err) {
        logger.debug(
          `claude auth probe: keychain lookup threw (${err.message})`
        );
      }
    }
    if (this.platform === "linux") {
      logger.debug(
        "claude auth probe: linux libsecret lookup not yet implemented; falling through to file probe"
      );
    }
    const candidates = [
      path.join(resolveClaudeConfigDir({ env: this.env, homeDir: this.home }), ".credentials.json"),
      path.join(this.home, ".config", "claude", "auth.json")
    ];
    let fileOutcome = null;
    for (const path of candidates) {
      const outcome = readCredentialFile(path);
      if (outcome) {
        fileOutcome = outcome;
        break;
      }
    }
    if (fileOutcome) {
      if (fileOutcome.state === "present") {
        logger.debug(
          `claude auth probe: present via file ${fileOutcome.path}` + (fileOutcome.identity?.email ? ` email=${fileOutcome.identity.email}` : "") + (fileOutcome.identity?.expiresAt ? ` expiresAt=${fileOutcome.identity.expiresAt}` : "")
        );
        return base({
          status: "present",
          source: "file",
          identity: fileOutcome.identity
        });
      }
      logger.debug(`claude auth probe: invalid via file ${fileOutcome.path}`);
      return base({ status: "invalid", source: "file" });
    }
    logger.debug("claude auth probe: no credentials found, returning missing");
    return base({ status: "missing" });
  }
}

function describeKey$4(key) {
  const trimmed = key.trim();
  const tail = trimmed.length >= 4 ? trimmed.slice(-4) : trimmed;
  return `<${trimmed.length} chars, \u2026${tail}>`;
}
function decodeJwtEmail(token) {
  try {
    const segments = token.split(".");
    if (segments.length < 2) return void 0;
    const payloadSeg = segments[1];
    const b64 = payloadSeg.replace(/-/g, "+").replace(/_/g, "/");
    const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
    const json = Buffer.from(padded, "base64").toString("utf8");
    const parsed = JSON.parse(json);
    if (parsed && typeof parsed === "object" && "email" in parsed) {
      const email = parsed.email;
      if (typeof email === "string" && email.length > 0) {
        return email;
      }
    }
    return void 0;
  } catch {
    return void 0;
  }
}
async function readAuthFile(path, now) {
  let raw;
  try {
    raw = await fs.promises.readFile(path, "utf8");
  } catch (err) {
    const code = err.code;
    if (code === "ENOENT" || code === "ENOTDIR") {
      return { state: null };
    }
    logger.debug(`[auth/codex] failed to read ${path}: ${code ?? "unknown"}`);
    return {
      state: {
        agent: "codex",
        status: "invalid",
        source: "file",
        lastCheckedAt: now,
        error: `read failed: ${code ?? "unknown"}`
      }
    };
  }
  let parsed;
  try {
    parsed = JSON.parse(raw);
  } catch {
    logger.debug(`[auth/codex] malformed JSON at ${path}`);
    return {
      state: {
        agent: "codex",
        status: "invalid",
        source: "file",
        lastCheckedAt: now,
        error: "malformed JSON"
      }
    };
  }
  if (!parsed || typeof parsed !== "object") {
    return {
      state: {
        agent: "codex",
        status: "invalid",
        source: "file",
        lastCheckedAt: now,
        error: "not a JSON object"
      }
    };
  }
  const apiKey = typeof parsed.OPENAI_API_KEY === "string" ? parsed.OPENAI_API_KEY : void 0;
  const tokensObj = parsed.tokens && typeof parsed.tokens === "object" ? parsed.tokens : void 0;
  const idToken = tokensObj && typeof tokensObj.id_token === "string" ? tokensObj.id_token : void 0;
  const accessToken = tokensObj && typeof tokensObj.access_token === "string" ? tokensObj.access_token : void 0;
  if (!apiKey && !idToken && !accessToken) {
    return {
      state: {
        agent: "codex",
        status: "invalid",
        source: "file",
        lastCheckedAt: now,
        error: "no recognized credential fields"
      }
    };
  }
  let identity;
  if (idToken) {
    const email = decodeJwtEmail(idToken);
    if (email) {
      identity = { email };
    } else {
      logger.debug("[auth/codex] id_token present but email claim could not be decoded");
    }
  }
  if (apiKey) {
    logger.debug(`[auth/codex] file contains OPENAI_API_KEY ${describeKey$4(apiKey)}`);
  }
  if (idToken) {
    logger.debug(`[auth/codex] file contains tokens.id_token <${idToken.length} chars>`);
  }
  if (accessToken) {
    logger.debug(`[auth/codex] file contains tokens.access_token <${accessToken.length} chars>`);
  }
  return {
    state: {
      agent: "codex",
      status: "present",
      source: "file",
      identity,
      lastCheckedAt: now
    }
  };
}
class CodexAuthProbe {
  now;
  env;
  homeDir;
  runCommand;
  constructor(options = {}) {
    this.now = options.now ?? Date.now;
    this.env = options.env ?? process.env;
    this.homeDir = options.homeDir ?? os.homedir();
    this.runCommand = options.runCommand ?? defaultProbeRunCommand;
  }
  async probe() {
    const now = this.now();
    const binaryCheck = checkAgentBinary(this.runCommand, "codex");
    const [credentialState, cliVerdict] = await Promise.all([
      this.probeCredentials(now),
      probeCliStatus("codex", this.runCommand)
    ]);
    const state = cliVerdict.status === "present" ? { ...credentialState, status: "present", source: "cli" } : credentialState;
    const binary = await binaryCheck;
    const warning = !binary.present && state.status !== "missing" ? state.warning ?? "binary-missing" : state.warning;
    return { ...state, binaryPresent: binary.present, ...warning ? { warning } : {} };
  }
  async probeCredentials(now) {
    const envKey = this.env.OPENAI_API_KEY;
    if (typeof envKey === "string" && envKey.length > 0) {
      if (envKey.startsWith("sk-")) {
        logger.debug(`[auth/codex] OpenAI key shape ${describeKey$4(envKey)}`);
      } else {
        logger.debug(`[auth/codex] non-standard key shape but accepting ${describeKey$4(envKey)}`);
      }
      return {
        agent: "codex",
        status: "present",
        source: "env",
        lastCheckedAt: now
      };
    }
    const primaryPath = path.join(
      resolveCodexHome({ env: this.env, homeDir: this.homeDir }),
      "auth.json"
    );
    const primary = await readAuthFile(primaryPath, now);
    if (primary.state) {
      return primary.state;
    }
    const fallbackPath = path.join(this.homeDir, ".config", "openai", "auth.json");
    const fallback = await readAuthFile(fallbackPath, now);
    if (fallback.state) {
      return fallback.state;
    }
    return {
      agent: "codex",
      status: "missing",
      lastCheckedAt: now
    };
  }
}

const GEMINI_AGENT = "gemini";
const ENV_VAR_NAMES$1 = [
  "GEMINI_API_KEY",
  "GOOGLE_API_KEY",
  "GOOGLE_AI_STUDIO_API_KEY"
];
const CREDENTIAL_KEY_FIELDS = ["api_key", "apiKey", "key", "token"];
function shapeOnly$1(secret) {
  const last4 = secret.length >= 4 ? secret.slice(-4) : secret;
  return `<${secret.length} chars, ...${last4}>`;
}
function makePresent$1(now, source, identity) {
  return {
    agent: GEMINI_AGENT,
    status: "present",
    source,
    identity,
    lastCheckedAt: now
  };
}
class GeminiAuthProbe {
  now;
  env;
  homeDir;
  runCommand;
  constructor(options = {}) {
    this.now = options.now ?? Date.now;
    this.env = options.env ?? process.env;
    this.homeDir = options.homeDir ?? os.homedir();
    this.runCommand = options.runCommand ?? defaultProbeRunCommand;
  }
  async probe() {
    const binaryCheck = checkAgentBinary(this.runCommand, "gemini");
    const state = await this.probeCredentials();
    const binary = await binaryCheck;
    const warning = !binary.present && state.status !== "missing" ? state.warning ?? "binary-missing" : state.warning;
    return { ...state, binaryPresent: binary.present, ...warning ? { warning } : {} };
  }
  async probeCredentials() {
    const envHit = this.probeEnv();
    if (envHit) return envHit;
    const fileHit = await this.probeCredentialsFile();
    if (fileHit) return fileHit;
    const adcHit = await this.probeAdc();
    if (adcHit) return adcHit;
    return {
      agent: GEMINI_AGENT,
      status: "missing",
      lastCheckedAt: this.now()
    };
  }
  /**
   * Best-effort `gemini --version` check the daemon may invoke when
   * it wants to know whether the CLI is on $PATH. Never used to
   * downgrade auth status; safe to skip.
   */
  async livenessCheck() {
    try {
      const result = await this.runCommand("gemini", ["--version"]);
      if (result.code === 0) {
        const version = result.stdout.trim() || void 0;
        return { alive: true, version };
      }
      return { alive: false };
    } catch {
      return { alive: false };
    }
  }
  probeEnv() {
    for (const name of ENV_VAR_NAMES$1) {
      const raw = this.env[name];
      if (typeof raw === "string" && raw.length > 0) {
        logger.debug(`[auth-probe gemini] env hit: ${name}=${shapeOnly$1(raw)}`);
        return makePresent$1(this.now(), "env");
      }
    }
    return void 0;
  }
  async probeCredentialsFile() {
    const filePath = path.join(this.homeDir, ".config", "gemini", "credentials.json");
    let raw;
    try {
      raw = await fs.promises.readFile(filePath, "utf8");
    } catch (err) {
      const code = err?.code;
      if (code === "ENOENT") return void 0;
      logger.debug(`[auth-probe gemini] credentials.json read error: ${code ?? "unknown"}`);
      return void 0;
    }
    let parsed;
    try {
      parsed = JSON.parse(raw);
    } catch {
      logger.debug("[auth-probe gemini] credentials.json malformed JSON");
      return {
        agent: GEMINI_AGENT,
        status: "invalid",
        source: "file",
        lastCheckedAt: this.now(),
        error: "credentials.json is not valid JSON"
      };
    }
    if (parsed === null || typeof parsed !== "object") {
      return {
        agent: GEMINI_AGENT,
        status: "invalid",
        source: "file",
        lastCheckedAt: this.now(),
        error: "credentials.json root is not an object"
      };
    }
    const obj = parsed;
    for (const field of CREDENTIAL_KEY_FIELDS) {
      const v = obj[field];
      if (typeof v === "string" && v.length > 0) {
        logger.debug(`[auth-probe gemini] file hit: field=${field} ${shapeOnly$1(v)}`);
        return makePresent$1(this.now(), "file");
      }
    }
    return void 0;
  }
  async probeAdc() {
    const filePath = path.join(
      this.homeDir,
      ".config",
      "gcloud",
      "application_default_credentials.json"
    );
    let raw;
    try {
      raw = await fs.promises.readFile(filePath, "utf8");
    } catch (err) {
      const code = err?.code;
      if (code === "ENOENT") return void 0;
      logger.debug(`[auth-probe gemini] ADC read error: ${code ?? "unknown"}`);
      return void 0;
    }
    let parsed;
    try {
      parsed = JSON.parse(raw);
    } catch {
      logger.debug("[auth-probe gemini] ADC malformed JSON");
      return void 0;
    }
    let identity;
    if (parsed !== null && typeof parsed === "object") {
      const email = parsed.client_email;
      if (typeof email === "string" && email.length > 0) {
        identity = { email };
      }
    }
    logger.debug("[auth-probe gemini] ADC hit");
    return makePresent$1(this.now(), "file", identity);
  }
}

const GROK_AGENT = "grok";
const ENV_VAR_NAMES = [
  "GROK_CODE_XAI_API_KEY",
  "XAI_API_KEY",
  "GROK_API_KEY"
];
const AUTH_JSON_SCOPES = [
  "https://auth.x.ai::b1a00492-073a-47ea-816f-4c329264a828",
  "https://accounts.x.ai/sign-in"
];
function shapeOnly(secret) {
  const last4 = secret.length >= 4 ? secret.slice(-4) : secret;
  return `<${secret.length} chars, ...${last4}>`;
}
function makePresent(now, source, identity) {
  return {
    agent: GROK_AGENT,
    status: "present",
    source,
    identity,
    lastCheckedAt: now
  };
}
class GrokAuthProbe {
  now;
  env;
  homeDir;
  runCommand;
  constructor(options = {}) {
    this.now = options.now ?? Date.now;
    this.env = options.env ?? process.env;
    this.homeDir = options.homeDir ?? os.homedir();
    this.runCommand = options.runCommand ?? defaultProbeRunCommand;
  }
  async probe() {
    const binaryCheck = checkAgentBinary(this.runCommand, "grok");
    const state = await this.probeCredentials();
    const binary = await binaryCheck;
    const warning = !binary.present && state.status !== "missing" ? state.warning ?? "binary-missing" : state.warning;
    return { ...state, binaryPresent: binary.present, ...warning ? { warning } : {} };
  }
  async probeCredentials() {
    const envHit = this.probeEnv();
    if (envHit) return envHit;
    const fileHit = await this.probeCredentialsFile();
    if (fileHit) return fileHit;
    return {
      agent: GROK_AGENT,
      status: "missing",
      lastCheckedAt: this.now()
    };
  }
  /**
   * Best-effort `grok --version` check the daemon may invoke when
   * it wants to know whether the CLI is on $PATH. Never used to
   * downgrade auth status; safe to skip.
   */
  async livenessCheck() {
    try {
      const result = await this.runCommand("grok", ["--version"]);
      if (result.code === 0) {
        const version = result.stdout.trim() || void 0;
        return { alive: true, version };
      }
      return { alive: false };
    } catch {
      return { alive: false };
    }
  }
  probeEnv() {
    for (const name of ENV_VAR_NAMES) {
      const raw = this.env[name];
      if (typeof raw === "string" && raw.length > 0) {
        logger.debug(`[auth-probe grok] env hit: ${name}=${shapeOnly(raw)}`);
        return makePresent(this.now(), "env");
      }
    }
    return void 0;
  }
  async probeCredentialsFile() {
    const filePath = path.join(this.homeDir, ".grok", "auth.json");
    let raw;
    try {
      raw = await fs.promises.readFile(filePath, "utf8");
    } catch (err) {
      const code = err?.code;
      if (code === "ENOENT") return void 0;
      logger.debug(`[auth-probe grok] auth.json read error: ${code ?? "unknown"}`);
      return void 0;
    }
    let parsed;
    try {
      parsed = JSON.parse(raw);
    } catch {
      logger.debug("[auth-probe grok] auth.json malformed JSON");
      return {
        agent: GROK_AGENT,
        status: "invalid",
        source: "file",
        lastCheckedAt: this.now(),
        error: "auth.json is not valid JSON"
      };
    }
    if (parsed === null || typeof parsed !== "object") {
      return {
        agent: GROK_AGENT,
        status: "invalid",
        source: "file",
        lastCheckedAt: this.now(),
        error: "auth.json root is not an object"
      };
    }
    const root = parsed;
    for (const scope of AUTH_JSON_SCOPES) {
      const scoped = root[scope];
      if (scoped === null || typeof scoped !== "object") continue;
      const key = scoped.key;
      if (typeof key === "string" && key.length > 0) {
        logger.debug(`[auth-probe grok] auth.json hit: scope=${scope} ${shapeOnly(key)}`);
        return makePresent(this.now(), "file");
      }
    }
    return void 0;
  }
}

const credentialsSchema = z.z.object({
  token: z.z.string().min(1),
  secret: z.z.string().base64().nullish(),
  encryption: z.z.object({
    publicKey: z.z.string().base64(),
    machineKey: z.z.string().base64()
  }).nullish()
}).passthrough();
function resolveCredentialsPath() {
  const home = process.env.CONSORTIUM_HOME_DIR ? process.env.CONSORTIUM_HOME_DIR.replace(/^~/, os.homedir()) : path.join(os.homedir(), ".consortium");
  return path.join(home, "access.key");
}
function envHasToken() {
  const tok = process.env.CONSORTIUM_TOKEN;
  return typeof tok === "string" && tok.trim().length > 0;
}
class ConsortiumAuthProbe {
  now;
  constructor(options = {}) {
    this.now = options.now ?? Date.now;
  }
  async probe() {
    const lastCheckedAt = this.now();
    if (envHasToken()) {
      return {
        agent: "consortium",
        status: "present",
        source: "env",
        lastCheckedAt
      };
    }
    const credPath = resolveCredentialsPath();
    try {
      if (!fs.existsSync(credPath)) {
        return {
          agent: "consortium",
          status: "missing",
          lastCheckedAt
        };
      }
    } catch (err) {
      return {
        agent: "consortium",
        status: "unknown",
        lastCheckedAt,
        error: err instanceof Error ? err.message : String(err)
      };
    }
    try {
      const raw = await fs$3.readFile(credPath, "utf8");
      const parsed = credentialsSchema.safeParse(JSON.parse(raw));
      if (!parsed.success) {
        return {
          agent: "consortium",
          status: "invalid",
          source: "file",
          lastCheckedAt,
          error: "credentials file failed structural validation"
        };
      }
      return {
        agent: "consortium",
        status: "present",
        source: "file",
        lastCheckedAt
      };
    } catch (err) {
      return {
        agent: "consortium",
        status: "invalid",
        source: "file",
        lastCheckedAt,
        error: err instanceof Error ? err.message : String(err)
      };
    }
  }
}

function expandHome(p, home) {
  return p.startsWith("~/") ? path.join(home, p.slice(2)) : p;
}
class ByokAgentProbe {
  agent;
  env;
  homeDir;
  now;
  runCommand;
  constructor(agent, options = {}) {
    this.agent = agent;
    this.env = options.env ?? process.env;
    this.homeDir = options.homeDir ?? os.homedir();
    this.now = options.now ?? Date.now;
    this.runCommand = options.runCommand ?? defaultProbeRunCommand;
  }
  async probe() {
    const lastCheckedAt = this.now();
    const cap = getAgentAuthCapability(this.agent);
    if (!cap) {
      return { agent: this.agent, status: "unknown", lastCheckedAt, error: "no capability row" };
    }
    const binaryCheck = checkAgentBinary(this.runCommand, cap.binary);
    const statusCheck = probeCliStatus(this.agent, this.runCommand);
    const envVar = cap.apiKeyEnvVars.find((name) => {
      const value = this.env[name];
      return typeof value === "string" && value.trim().length > 0;
    });
    const [binary, verdict] = await Promise.all([binaryCheck, statusCheck]);
    const base = (partial) => {
      const state = {
        agent: this.agent,
        lastCheckedAt,
        ...partial,
        binaryPresent: binary.present
      };
      if (!binary.present && state.status !== "missing") state.warning = "binary-missing";
      return state;
    };
    if (verdict.status === "present") return base({ status: "present", source: "cli" });
    if (envVar) return base({ status: "present", source: "env" });
    const credentialFile = cap.credentialPaths.map((p) => expandHome(p, this.homeDir)).find((p) => !p.includes("$") && fs.existsSync(p));
    if (credentialFile) return base({ status: "present", source: "file" });
    return base({ status: "missing" });
  }
}

const AGENT_KINDS = ["claude", "codex", "gemini", "grok", "pi", "opencode", "consortium"];
const PROBE_TIMEOUT_MS = 5e3;
const DEFAULT_CACHE_TTL_MS = 3e4;
const ENV_SIGNATURE_KEYS = [
  "CLAUDE_CODE_OAUTH_TOKEN",
  "ANTHROPIC_API_KEY",
  "OPENAI_API_KEY",
  "CODEX_API_KEY",
  "GEMINI_API_KEY",
  "GOOGLE_API_KEY",
  "GROK_CODE_XAI_API_KEY",
  "XAI_API_KEY",
  "GROK_API_KEY",
  "OPENCODE_API_KEY",
  "CONSORTIUM_TOKEN",
  "CONSORTIUM_HOME_DIR",
  // Config-dir overrides relocate the credential files the probes read
  // (see `auth/paths.ts`), so they belong in the cache signature.
  "CLAUDE_CONFIG_DIR",
  "CODEX_HOME",
  "HOME"
];
const DEFAULT_FACTORIES = {
  claude: (opts) => new ClaudeAuthProbe(opts),
  codex: (opts) => new CodexAuthProbe(opts),
  gemini: (opts) => new GeminiAuthProbe(opts),
  grok: (opts) => new GrokAuthProbe(opts),
  // BYOK-only agents (no login flow of their own) share one probe.
  pi: (opts) => new ByokAgentProbe("pi", opts),
  opencode: (opts) => new ByokAgentProbe("opencode", opts),
  consortium: (opts) => new ConsortiumAuthProbe(opts)
};
let factories = { ...DEFAULT_FACTORIES };
const cache$1 = /* @__PURE__ */ new Map();
function invalidateProbeCache() {
  cache$1.clear();
}
function buildCacheKey(opts) {
  const env = opts.env ?? process.env;
  const envSignatureKeys = ENV_SIGNATURE_KEYS.filter((k) => typeof env[k] === "string").sort();
  const agents = (opts.agents ?? AGENT_KINDS).slice().sort();
  return JSON.stringify({
    homeDir: opts.homeDir ?? null,
    envSignature: envSignatureKeys,
    agents
  });
}
async function runOneProbe(agent, options, timeoutMs, now) {
  const factory = factories[agent];
  let timer;
  try {
    const probe = factory(options);
    const result = await Promise.race([
      probe.probe(),
      new Promise((_, reject) => {
        timer = setTimeout(
          () => reject(new Error(`probe timed out after ${timeoutMs}ms`)),
          timeoutMs
        );
      })
    ]);
    return result;
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    logger.debug(`[auth-probe] ${agent} probe failed: ${message}`);
    return {
      agent,
      status: "unknown",
      lastCheckedAt: now(),
      error: message
    };
  } finally {
    if (timer) clearTimeout(timer);
  }
}
async function runAllProbes(opts = {}) {
  const now = opts.now ?? Date.now;
  const ttlMs = opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
  const timeoutMs = opts.probeTimeoutMs ?? PROBE_TIMEOUT_MS;
  const agents = opts.agents ?? AGENT_KINDS.slice();
  const cacheKey = buildCacheKey(opts);
  if (ttlMs > 0) {
    const hit = cache$1.get(cacheKey);
    if (hit && hit.expiresAt > now()) {
      return hit.result;
    }
  }
  const probeOptions = {
    now,
    env: opts.env,
    homeDir: opts.homeDir,
    runCommand: opts.runCommand
  };
  const settled = await Promise.allSettled(
    agents.map((agent) => runOneProbe(agent, probeOptions, timeoutMs, now))
  );
  const states = settled.map((entry, idx) => {
    const agent = agents[idx];
    if (entry.status === "fulfilled") return entry.value;
    const message = entry.reason instanceof Error ? entry.reason.message : String(entry.reason);
    return { agent, status: "unknown", lastCheckedAt: now(), error: message };
  });
  const result = { states, probedAt: now() };
  if (ttlMs > 0) {
    cache$1.set(cacheKey, { result, expiresAt: now() + ttlMs });
  }
  return result;
}

const APPLE_SILICON_GBS = {
  "m1": 68,
  "m1 pro": 200,
  "m1 max": 400,
  "m1 ultra": 800,
  "m2": 100,
  "m2 pro": 200,
  "m2 max": 400,
  "m2 ultra": 800,
  "m3": 100,
  "m3 pro": 150,
  // M3 Max ships as 300 (14-core CPU) and 400 (16-core) — take the lower.
  "m3 max": 300,
  "m3 ultra": 800,
  "m4": 120,
  "m4 pro": 273,
  // M4 Max ships as 410 (14-core CPU) and 546 (16-core) — take the lower.
  "m4 max": 410
};
const DISCRETE_GPU_GBS = {
  "rtx 5090": 1792,
  "rtx 4090": 1008,
  "rtx 4080": 717,
  "rtx 4070 ti": 504,
  "rtx 4070": 504,
  "rtx 3090 ti": 1008,
  "rtx 3090": 936,
  "rtx 3080": 760,
  "rtx 3070": 448,
  "rtx 3060": 360,
  "rtx a6000": 768,
  "a100": 1555,
  "h100": 3350,
  "l40s": 864
};
function appleSiliconBandwidthGBs(brandString) {
  const m = brandString.toLowerCase().match(/apple\s+(m\d+)(\s+(pro|max|ultra))?/);
  if (!m) return void 0;
  const key = m[3] ? `${m[1]} ${m[3]}` : m[1];
  return APPLE_SILICON_GBS[key];
}
function discreteGpuBandwidthGBs(gpuName) {
  const name = gpuName.toLowerCase();
  let best;
  for (const [key, gbs] of Object.entries(DISCRETE_GPU_GBS)) {
    if (!name.includes(key)) continue;
    if (!best || key.length > best.key.length) best = { key, gbs };
  }
  return best?.gbs;
}

const execFileAsync$3 = util.promisify(child_process.execFile);
const SYSCTL_TIMEOUT_MS = 1e3;
const PROFILER_TIMEOUT_MS = 5e3;
async function sysctl(keys) {
  const out = {};
  try {
    const { stdout } = await execFileAsync$3("sysctl", keys, { timeout: SYSCTL_TIMEOUT_MS });
    for (const line of stdout.split("\n")) {
      const idx = line.indexOf(":");
      if (idx === -1) continue;
      out[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
    }
  } catch {
  }
  return out;
}
function darwinUsableModelBytes(ramTotalBytes, wiredLimitMb) {
  if (wiredLimitMb > 0) return wiredLimitMb * 1024 * 1024;
  const GB = 1024 ** 3;
  return ramTotalBytes <= 36 * GB ? Math.floor(ramTotalBytes * 0.7) : ramTotalBytes - 8 * GB;
}
function parseDarwinDisplays(json, appleSilicon) {
  let parsed;
  try {
    parsed = JSON.parse(json);
  } catch {
    return [];
  }
  const gpus = [];
  for (const entry of parsed.SPDisplaysDataType ?? []) {
    const name = entry.sppci_model;
    if (!name) continue;
    const lower = name.toLowerCase();
    const vendor = lower.includes("apple") ? "apple" : lower.includes("nvidia") || lower.includes("geforce") ? "nvidia" : lower.includes("amd") || lower.includes("radeon") ? "amd" : lower.includes("intel") ? "intel" : "unknown";
    const vramBytes = appleSilicon ? void 0 : parseVramString(entry.spdisplays_vram);
    gpus.push({ vendor, name, vramBytes });
  }
  return gpus;
}
function parseVramString(raw) {
  if (!raw) return void 0;
  const m = raw.trim().match(/^([\d.]+)\s*(GB|MB)$/i);
  if (!m) return void 0;
  const value = parseFloat(m[1]);
  if (!Number.isFinite(value)) return void 0;
  return Math.round(value * (m[2].toUpperCase() === "GB" ? 1024 ** 3 : 1024 ** 2));
}
async function detectDarwin() {
  const warnings = [];
  const keys = await sysctl([
    "machdep.cpu.brand_string",
    "hw.memsize",
    "hw.physicalcpu",
    "hw.logicalcpu",
    "iogpu.wired_limit_mb"
  ]);
  const cpuModel = keys["machdep.cpu.brand_string"] || os$1.cpus()[0]?.model || "unknown";
  const ramTotalBytes = Number(keys["hw.memsize"]) || os$1.totalmem();
  const cpuCoresPhysical = Number(keys["hw.physicalcpu"]) || os$1.cpus().length;
  const cpuThreads = Number(keys["hw.logicalcpu"]) || os$1.cpus().length;
  const wiredLimitMb = Number(keys["iogpu.wired_limit_mb"]) || 0;
  const memBandwidthGBs = appleSiliconBandwidthGBs(cpuModel);
  const unifiedMemory = memBandwidthGBs !== void 0 || /apple m\d/i.test(cpuModel);
  let gpus = [];
  try {
    const { stdout } = await execFileAsync$3(
      "system_profiler",
      ["SPDisplaysDataType", "-json"],
      { timeout: PROFILER_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024 }
    );
    gpus = parseDarwinDisplays(stdout, unifiedMemory);
  } catch {
    warnings.push("system_profiler did not respond; GPU details unavailable");
  }
  const usableModelBytes = unifiedMemory ? darwinUsableModelBytes(ramTotalBytes, wiredLimitMb) : gpus.reduce((sum, g) => sum + (g.vramBytes ?? 0), 0) || Math.floor(ramTotalBytes * 0.5);
  return {
    cpuModel,
    cpuCoresPhysical,
    cpuThreads,
    ramTotalBytes,
    gpus,
    unifiedMemory,
    usableModelBytes,
    memBandwidthGBs,
    warnings
  };
}

const execFileAsync$2 = util.promisify(child_process.execFile);
const SMI_TIMEOUT_MS = 3e3;
function parseMemTotalBytes(meminfo) {
  const m = meminfo.match(/^MemTotal:\s+(\d+)\s+kB/m);
  return m ? parseInt(m[1], 10) * 1024 : void 0;
}
function parsePhysicalCores(cpuinfo) {
  const pairs = /* @__PURE__ */ new Set();
  let physicalId = "0";
  for (const line of cpuinfo.split("\n")) {
    const [rawKey, rawVal] = line.split(":");
    if (!rawVal) continue;
    const key = rawKey.trim();
    const val = rawVal.trim();
    if (key === "physical id") physicalId = val;
    else if (key === "core id") pairs.add(`${physicalId}:${val}`);
  }
  return pairs.size > 0 ? pairs.size : void 0;
}
function parseCpuModel(cpuinfo) {
  return cpuinfo.match(/^model name\s*:\s*(.+)$/m)?.[1]?.trim();
}
function parseNvidiaSmi(csv) {
  const gpus = [];
  for (const line of csv.split("\n")) {
    if (!line.trim()) continue;
    const parts = line.split(",").map((p) => p.trim());
    if (parts.length < 2) continue;
    const mib = parseFloat(parts[1]);
    gpus.push({
      vendor: "nvidia",
      name: parts[0],
      vramBytes: Number.isFinite(mib) ? Math.round(mib * 1024 * 1024) : void 0,
      driver: parts[2] || void 0,
      computeCapability: parts[3] || void 0
    });
  }
  return gpus;
}
async function detectNvidia(warnings) {
  try {
    const { stdout } = await execFileAsync$2("nvidia-smi", [
      "--query-gpu=name,memory.total,driver_version,compute_cap",
      "--format=csv,noheader,nounits"
    ], { timeout: SMI_TIMEOUT_MS });
    return parseNvidiaSmi(stdout);
  } catch (err) {
    if (err?.code !== "ENOENT") warnings.push("nvidia-smi present but did not respond");
    return [];
  }
}
async function detectAmd(warnings) {
  try {
    const { stdout } = await execFileAsync$2(
      "rocm-smi",
      ["--showproductname", "--showmeminfo", "vram", "--json"],
      { timeout: SMI_TIMEOUT_MS }
    );
    const parsed = JSON.parse(stdout);
    const gpus = [];
    for (const card of Object.values(parsed)) {
      const total = Object.entries(card).find(([k]) => /vram.*total|total.*vram/i.test(k))?.[1];
      const bytes = total ? parseInt(total, 10) : NaN;
      gpus.push({
        vendor: "amd",
        name: card["Card series"] || card["Card model"] || "AMD GPU",
        vramBytes: Number.isFinite(bytes) ? bytes : void 0
      });
    }
    return gpus;
  } catch (err) {
    if (err?.code !== "ENOENT") warnings.push("rocm-smi present but did not respond");
    return [];
  }
}
async function detectSysfsDrm() {
  const gpus = [];
  try {
    for (const card of await fs$1.promises.readdir("/sys/class/drm")) {
      if (!/^card\d+$/.test(card)) continue;
      try {
        const raw = await fs$1.promises.readFile(
          `/sys/class/drm/${card}/device/mem_info_vram_total`,
          "utf8"
        );
        const bytes = parseInt(raw.trim(), 10);
        if (Number.isFinite(bytes) && bytes > 0) {
          gpus.push({ vendor: "unknown", name: card, vramBytes: bytes });
        }
      } catch {
      }
    }
  } catch {
  }
  return gpus;
}
async function detectLinux() {
  const warnings = [];
  const [meminfo, cpuinfo] = await Promise.all([
    fs$1.promises.readFile("/proc/meminfo", "utf8").catch(() => ""),
    fs$1.promises.readFile("/proc/cpuinfo", "utf8").catch(() => "")
  ]);
  const ramTotalBytes = parseMemTotalBytes(meminfo) ?? os$1.totalmem();
  const cpuModel = parseCpuModel(cpuinfo) ?? os$1.cpus()[0]?.model ?? "unknown";
  const cpuThreads = os$1.cpus().length;
  const cpuCoresPhysical = parsePhysicalCores(cpuinfo) ?? cpuThreads;
  let gpus = await detectNvidia(warnings);
  if (gpus.length === 0) gpus = await detectAmd(warnings);
  if (gpus.length === 0) gpus = await detectSysfsDrm();
  const vramTotal = gpus.reduce((sum, g) => sum + (g.vramBytes ?? 0), 0);
  const usableModelBytes = vramTotal > 0 ? vramTotal : Math.floor(ramTotalBytes * 0.5);
  const memBandwidthGBs = gpus.length > 0 ? discreteGpuBandwidthGBs(gpus[0].name) : void 0;
  return {
    cpuModel,
    cpuCoresPhysical,
    cpuThreads,
    ramTotalBytes,
    gpus,
    unifiedMemory: false,
    usableModelBytes,
    memBandwidthGBs,
    warnings
  };
}

const execFileAsync$1 = util.promisify(child_process.execFile);
const PS_TIMEOUT_MS = 8e3;
async function powershell(script) {
  const { stdout } = await execFileAsync$1(
    "powershell.exe",
    ["-NoProfile", "-NonInteractive", "-Command", script],
    { timeout: PS_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024 }
  );
  return stdout;
}
function parseWindowsGpus(json) {
  let parsed;
  try {
    parsed = JSON.parse(json);
  } catch {
    return [];
  }
  const rows = Array.isArray(parsed) ? parsed : [parsed];
  const gpus = [];
  for (const row of rows) {
    if (!row?.Name) continue;
    const raw = typeof row.VramBytes === "string" ? parseInt(row.VramBytes, 10) : row.VramBytes;
    const lower = row.Name.toLowerCase();
    gpus.push({
      vendor: lower.includes("nvidia") || lower.includes("geforce") ? "nvidia" : lower.includes("amd") || lower.includes("radeon") ? "amd" : lower.includes("intel") ? "intel" : "unknown",
      name: row.Name,
      // Guard the uint32 saturation value even here, in case a driver key is
      // missing and something upstream fell back to AdapterRAM.
      vramBytes: typeof raw === "number" && Number.isFinite(raw) && raw > 0 && raw !== 4294967295 ? raw : void 0,
      driver: row.Driver || void 0
    });
  }
  return gpus;
}
const GPU_QUERY = `
$ErrorActionPreference='SilentlyContinue'
$base='HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}'
Get-ChildItem $base | Where-Object { $_.PSChildName -match '^\\d{4}$' } | ForEach-Object {
  $p = Get-ItemProperty $_.PSPath
  if ($p.DriverDesc) {
    [pscustomobject]@{
      Name      = $p.DriverDesc
      VramBytes = $p.'HardwareInformation.qwMemorySize'
      Driver    = $p.DriverVersion
    }
  }
} | ConvertTo-Json -Compress
`.trim();
async function detectWindows() {
  const warnings = [];
  const cpus = os$1.cpus();
  const cpuModel = cpus[0]?.model ?? "unknown";
  const cpuThreads = cpus.length;
  const ramTotalBytes = os$1.totalmem();
  let cpuCoresPhysical = cpuThreads;
  try {
    const out = await powershell(
      "(Get-CimInstance Win32_Processor | Measure-Object -Property NumberOfCores -Sum).Sum"
    );
    const cores = parseInt(out.trim(), 10);
    if (Number.isFinite(cores) && cores > 0) cpuCoresPhysical = cores;
  } catch {
    warnings.push("could not read physical core count; using logical count");
  }
  let gpus = [];
  try {
    const { stdout } = await execFileAsync$1("nvidia-smi", [
      "--query-gpu=name,memory.total,driver_version,compute_cap",
      "--format=csv,noheader,nounits"
    ], { timeout: PS_TIMEOUT_MS });
    gpus = parseNvidiaSmi(stdout);
  } catch {
  }
  if (gpus.length === 0) {
    try {
      gpus = parseWindowsGpus(await powershell(GPU_QUERY));
    } catch {
      warnings.push("could not enumerate display adapters");
    }
  }
  const vramTotal = gpus.reduce((sum, g) => sum + (g.vramBytes ?? 0), 0);
  const usableModelBytes = vramTotal > 0 ? vramTotal : Math.floor(ramTotalBytes * 0.5);
  const memBandwidthGBs = gpus.length > 0 ? discreteGpuBandwidthGBs(gpus[0].name) : void 0;
  return {
    cpuModel,
    cpuCoresPhysical,
    cpuThreads,
    ramTotalBytes,
    gpus,
    unifiedMemory: false,
    usableModelBytes,
    memBandwidthGBs,
    warnings
  };
}

const CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
let cache;
let inFlight;
async function diskFreeBytes(dir) {
  try {
    const stats = await fs$1.promises.statfs(dir);
    return Number(stats.bavail) * Number(stats.bsize);
  } catch {
    return 0;
  }
}
async function detectUncached() {
  const platform = process.platform;
  const base = platform === "darwin" ? await detectDarwin() : platform === "linux" ? await detectLinux() : platform === "win32" ? await detectWindows() : null;
  if (!base) {
    const ramTotalBytes = os$1.totalmem();
    return {
      schemaVersion: 1,
      platform,
      arch: process.arch,
      cpuModel: os$1.cpus()[0]?.model ?? "unknown",
      cpuCoresPhysical: os$1.cpus().length,
      cpuThreads: os$1.cpus().length,
      ramTotalBytes,
      gpus: [],
      unifiedMemory: false,
      usableModelBytes: Math.floor(ramTotalBytes * 0.5),
      bandwidthSource: "unknown",
      diskFreeBytes: await diskFreeBytes(os$1.homedir()),
      detectedAt: Date.now(),
      warnings: [`unsupported platform "${platform}"; reporting CPU/RAM only`]
    };
  }
  return {
    schemaVersion: 1,
    platform,
    arch: process.arch,
    cpuModel: base.cpuModel,
    cpuCoresPhysical: base.cpuCoresPhysical,
    cpuThreads: base.cpuThreads,
    ramTotalBytes: base.ramTotalBytes,
    gpus: base.gpus,
    unifiedMemory: base.unifiedMemory,
    usableModelBytes: base.usableModelBytes,
    memBandwidthGBs: base.memBandwidthGBs,
    bandwidthSource: base.memBandwidthGBs !== void 0 ? "chip-table" : "unknown",
    diskFreeBytes: await diskFreeBytes(os$1.homedir()),
    detectedAt: Date.now(),
    warnings: base.warnings
  };
}
async function detectHardware(opts) {
  if (!opts?.force && cache && Date.now() - cache.at < CACHE_TTL_MS) {
    return cache.profile;
  }
  if (inFlight) return inFlight;
  inFlight = detectUncached().then((profile) => {
    cache = { at: Date.now(), profile };
    const gpu = profile.gpus[0]?.name ?? "no GPU";
    logger.debug(
      `[HARDWARE] ${profile.cpuModel} | ${fmtGb(profile.ramTotalBytes)} RAM | ${gpu} | usable ${fmtGb(profile.usableModelBytes)} | ${profile.memBandwidthGBs ?? "?"} GB/s (${profile.bandwidthSource})` + (profile.warnings.length ? ` | warnings: ${profile.warnings.join("; ")}` : "")
    );
    return profile;
  }).finally(() => {
    inFlight = void 0;
  });
  return inFlight;
}
function fmtGb(bytes) {
  return `${(bytes / 1024 ** 3).toFixed(1)}GB`;
}

function recognizeClaude(parsed) {
  if (!parsed || typeof parsed !== "object") return false;
  let obj = parsed;
  if (obj.claudeAiOauth && typeof obj.claudeAiOauth === "object") {
    obj = obj.claudeAiOauth;
  }
  return typeof obj.access_token === "string" || typeof obj.accessToken === "string" || typeof obj.token === "string";
}
function recognizeCodex(parsed) {
  if (!parsed || typeof parsed !== "object") return false;
  const obj = parsed;
  if (typeof obj.OPENAI_API_KEY === "string") return true;
  if (obj.tokens && typeof obj.tokens === "object") {
    const t = obj.tokens;
    return typeof t.access_token === "string" || typeof t.id_token === "string" || typeof t.refresh_token === "string";
  }
  return false;
}
function recognizeGemini(parsed) {
  if (!parsed || typeof parsed !== "object") return false;
  const obj = parsed;
  return typeof obj.access_token === "string" || typeof obj.refresh_token === "string" || typeof obj.client_id === "string" || typeof obj.api_key === "string";
}
function recognizeConsortium(parsed) {
  if (!parsed || typeof parsed !== "object") return false;
  const obj = parsed;
  return typeof obj.token === "string" || typeof obj.secret === "string";
}
function recognizeGrok(parsed) {
  if (!parsed || typeof parsed !== "object") return false;
  return Object.values(parsed).some((v) => {
    if (!v || typeof v !== "object") return false;
    const key = v.key;
    return typeof key === "string" && key.length > 0;
  });
}
function candidatesFor(agent, opts) {
  const home = opts.homeDir ?? os.homedir();
  const env = opts.env ?? process.env;
  switch (agent) {
    case "claude":
      return [
        {
          path: path.join(
            resolveClaudeConfigDir({ env, homeDir: home }),
            ".credentials.json"
          ),
          recognize: recognizeClaude
        },
        { path: path.join(home, ".config", "claude", "auth.json"), recognize: recognizeClaude }
      ];
    case "codex":
      return [
        {
          path: path.join(resolveCodexHome({ env, homeDir: home }), "auth.json"),
          recognize: recognizeCodex
        },
        { path: path.join(home, ".config", "openai", "auth.json"), recognize: recognizeCodex }
      ];
    case "gemini":
      return [
        { path: path.join(home, ".config", "gemini", "credentials.json"), recognize: recognizeGemini }
      ];
    case "grok":
      return [
        { path: path.join(home, ".grok", "auth.json"), recognize: recognizeGrok }
      ];
    case "consortium": {
      const consortiumHome = env.CONSORTIUM_HOME_DIR || path.join(home, ".consortium");
      return [
        { path: path.join(consortiumHome, "access.key"), recognize: recognizeConsortium }
      ];
    }
    default:
      return [];
  }
}
function clearAgentCredentials(agent, opts = {}) {
  const candidates = candidatesFor(agent, opts);
  const cleared = [];
  const skipped = [];
  for (const cand of candidates) {
    if (!fs.existsSync(cand.path)) continue;
    let raw;
    try {
      raw = fs.readFileSync(cand.path, "utf8");
    } catch (err) {
      skipped.push({ path: cand.path, reason: `read failed: ${err.message}` });
      logger.info(`[auth-clear-creds] skip ${cand.path}: read failed`);
      continue;
    }
    let parsed;
    try {
      parsed = JSON.parse(raw);
    } catch {
      skipped.push({ path: cand.path, reason: "unrecognized shape (not JSON)" });
      logger.info(`[auth-clear-creds] refuse ${cand.path}: not JSON`);
      continue;
    }
    if (!cand.recognize(parsed)) {
      skipped.push({ path: cand.path, reason: "unrecognized shape" });
      logger.info(`[auth-clear-creds] refuse ${cand.path}: schema not recognized`);
      continue;
    }
    try {
      fs.unlinkSync(cand.path);
      cleared.push(cand.path);
      logger.info(`[auth-clear-creds] cleared ${cand.path} for agent=${agent}`);
    } catch (err) {
      skipped.push({ path: cand.path, reason: `unlink failed: ${err.message}` });
      logger.info(`[auth-clear-creds] unlink ${cand.path} failed: ${err.message}`);
    }
  }
  return { ok: cleared.length > 0 || skipped.length === 0, cleared, skipped };
}

const TERMINAL_RETENTION_MS$3 = 5 * 60 * 1e3;
class DeviceCodeFlow {
  records = /* @__PURE__ */ new Map();
  now;
  setTimer;
  clearTimer;
  constructor(opts = {}) {
    this.now = opts.now ?? (() => Date.now());
    this.setTimer = opts.setTimeout ?? ((fn, ms) => setTimeout(fn, ms));
    this.clearTimer = opts.clearTimeout ?? ((h) => clearTimeout(h));
  }
  /** Begin a new device-code flow against `adapter`. */
  async start(adapter) {
    const init = await adapter.initiate();
    const intervalSec = Math.max(1, Math.floor(init.interval));
    const expiresInSec = Math.max(1, Math.floor(init.expiresIn));
    const id = node_crypto.randomUUID();
    const startedAt = this.now();
    const expiresAtMs = startedAt + expiresInSec * 1e3;
    const handle = {
      id,
      agent: adapter.agent,
      kind: "device-code",
      startedAt
    };
    const record = {
      handle,
      status: {
        status: "awaiting-user",
        handle,
        payload: {
          kind: "device-code",
          verificationUrl: init.verificationUrl,
          userCode: init.userCode,
          expiresAt: expiresAtMs
        }
      },
      adapter,
      deviceCode: init.deviceCode,
      expiresAtMs,
      intervalMs: intervalSec * 1e3,
      pollTimer: null,
      gcTimer: null,
      cancelled: false
    };
    this.records.set(id, record);
    logger.debug(`[auth-flow ${adapter.agent}] device-code started id=${id} expiresIn=${expiresInSec}s interval=${intervalSec}s`);
    this.schedulePoll(record);
    return handle;
  }
  /** Snapshot of the current flow status. */
  async getStatus(id) {
    const record = this.records.get(id);
    if (!record) {
      throw new Error(`device-code flow not found: ${id}`);
    }
    return record.status;
  }
  /** Stop the poller, mark `cancelled`, run adapter.cleanup. */
  async cancel(id) {
    const record = this.records.get(id);
    if (!record) return;
    if (this.isTerminal(record.status)) return;
    record.cancelled = true;
    this.stopPoll(record);
    const finishedAt = this.now();
    record.status = {
      status: "cancelled",
      handle: record.handle,
      finishedAt
    };
    logger.debug(`[auth-flow ${record.adapter.agent}] cancelled id=${id}`);
    if (record.adapter.cleanup) {
      try {
        await record.adapter.cleanup();
      } catch (err) {
        logger.debug(`[auth-flow ${record.adapter.agent}] cleanup error: ${shapeError(err)}`);
      }
    }
    this.scheduleGc(record);
  }
  /**
   * Test/admin hook: drop all in-memory state and clear timers. Idempotent.
   */
  dispose() {
    for (const record of this.records.values()) {
      this.stopPoll(record);
      this.cancelGc(record);
    }
    this.records.clear();
  }
  // ---- internals -------------------------------------------------------
  schedulePoll(record) {
    if (record.cancelled || this.isTerminal(record.status)) return;
    const remaining = record.expiresAtMs - this.now();
    if (remaining <= 0) {
      this.transitionExpired(record);
      return;
    }
    const delay = Math.min(record.intervalMs, remaining);
    record.pollTimer = this.setTimer(() => {
      void this.runPollOnce(record);
    }, delay);
  }
  stopPoll(record) {
    if (record.pollTimer != null) {
      this.clearTimer(record.pollTimer);
      record.pollTimer = null;
    }
  }
  async runPollOnce(record) {
    record.pollTimer = null;
    if (record.cancelled || this.isTerminal(record.status)) return;
    if (this.now() >= record.expiresAtMs) {
      this.transitionExpired(record);
      return;
    }
    let result;
    try {
      result = await record.adapter.poll(record.deviceCode);
    } catch (err) {
      logger.debug(`[auth-flow ${record.adapter.agent}] poll threw: ${shapeError(err)} \u2014 treating as pending`);
      this.schedulePoll(record);
      return;
    }
    if (record.cancelled || this.isTerminal(record.status)) return;
    switch (result.state) {
      case "pending":
        this.schedulePoll(record);
        return;
      case "success": {
        try {
          await record.adapter.persist(result.credentials);
        } catch (err) {
          this.transitionFailed(record, `persist failed: ${shapeError(err)}`);
          return;
        }
        if (record.cancelled) return;
        const identity = record.adapter.extractIdentity?.(result.credentials);
        record.status = {
          status: "succeeded",
          handle: record.handle,
          finishedAt: this.now(),
          identity
        };
        logger.debug(`[auth-flow ${record.adapter.agent}] succeeded id=${record.handle.id}`);
        invalidateProbeCache();
        this.scheduleGc(record);
        return;
      }
      case "denied":
      case "expired":
      case "error":
        if (result.state === "expired") {
          this.transitionExpired(record);
        } else {
          this.transitionFailed(record, result.error);
        }
        return;
    }
  }
  transitionFailed(record, error) {
    this.stopPoll(record);
    record.status = {
      status: "failed",
      handle: record.handle,
      finishedAt: this.now(),
      error
    };
    logger.debug(`[auth-flow ${record.adapter.agent}] failed id=${record.handle.id}: ${error}`);
    this.scheduleGc(record);
  }
  transitionExpired(record) {
    this.stopPoll(record);
    record.status = {
      status: "expired",
      handle: record.handle,
      finishedAt: this.now()
    };
    logger.debug(`[auth-flow ${record.adapter.agent}] expired id=${record.handle.id}`);
    this.scheduleGc(record);
  }
  scheduleGc(record) {
    this.cancelGc(record);
    record.gcTimer = this.setTimer(() => {
      this.records.delete(record.handle.id);
    }, TERMINAL_RETENTION_MS$3);
  }
  cancelGc(record) {
    if (record.gcTimer != null) {
      this.clearTimer(record.gcTimer);
      record.gcTimer = null;
    }
  }
  isTerminal(status) {
    return status.status === "succeeded" || status.status === "failed" || status.status === "cancelled" || status.status === "expired";
  }
}
function shapeError(err) {
  if (err instanceof Error) return err.message;
  return String(err);
}

const DEFAULT_GATEWAY_PORT = 18789;
const GATEWAY_CHECK_TIMEOUT_MS = 2e3;
const COMMON_BINARY_PATHS = [
  "/usr/local/bin/openclaw",
  "/usr/bin/openclaw",
  "/opt/homebrew/bin/openclaw",
  path$1.join(os$1.homedir(), ".nvm/versions/node"),
  // handled specially below
  path$1.join(os$1.homedir(), ".local/share/fnm/node-versions"),
  // handled specially below
  path$1.join(os$1.homedir(), ".local/bin/openclaw"),
  path$1.join(os$1.homedir(), ".npm-global/bin/openclaw")
];
function whichBinary(name) {
  return new Promise((resolve) => {
    const extraPaths = [
      "/usr/local/bin",
      "/opt/homebrew/bin",
      path$1.join(os$1.homedir(), ".local/bin"),
      path$1.join(os$1.homedir(), ".npm-global/bin")
    ];
    const extendedPath = [process.env.PATH, ...extraPaths].filter(Boolean).join(":");
    child_process.execFile("which", [name], { timeout: 5e3, env: { ...process.env, PATH: extendedPath } }, (error, stdout) => {
      if (!error && stdout.trim()) {
        resolve(stdout.trim());
        return;
      }
      (async () => {
        for (const p of COMMON_BINARY_PATHS) {
          if (p.includes(".nvm") || p.includes("fnm")) continue;
          try {
            await fs$2.access(p);
            logger.debug(`[OPENCLAW DETECT] Found binary via fallback path: ${p}`);
            resolve(p);
            return;
          } catch {
          }
        }
        const nvmDir = path$1.join(os$1.homedir(), ".nvm/versions/node");
        try {
          const versions = await fs$2.readdir(nvmDir);
          for (const ver of versions.sort().reverse()) {
            const binPath = path$1.join(nvmDir, ver, "bin", name);
            try {
              await fs$2.access(binPath);
              logger.debug(`[OPENCLAW DETECT] Found binary in nvm: ${binPath}`);
              resolve(binPath);
              return;
            } catch {
            }
          }
        } catch {
        }
        const fnmDir = path$1.join(os$1.homedir(), ".local/share/fnm/node-versions");
        try {
          const versions = await fs$2.readdir(fnmDir);
          for (const ver of versions.sort().reverse()) {
            const binPath = path$1.join(fnmDir, ver, "installation/bin", name);
            try {
              await fs$2.access(binPath);
              logger.debug(`[OPENCLAW DETECT] Found binary in fnm: ${binPath}`);
              resolve(binPath);
              return;
            } catch {
            }
          }
        } catch {
        }
        resolve(null);
      })();
    });
  });
}
function getOpenClawVersion(binaryPath) {
  return new Promise((resolve) => {
    child_process.execFile(binaryPath, ["--version"], { timeout: 5e3 }, (error, stdout) => {
      if (error || !stdout.trim()) {
        resolve(null);
        return;
      }
      const match = stdout.trim().match(/(\d+\.\d+\.\d+[\w.-]*)/);
      resolve(match ? match[1] : stdout.trim());
    });
  });
}
async function readOpenClawConfig(workspaceDir) {
  try {
    const configPath = path$1.join(workspaceDir, "openclaw.json");
    const raw = await fs$2.readFile(configPath, "utf-8");
    const config = JSON.parse(raw);
    const channels = [];
    if (config.channels && typeof config.channels === "object") {
      for (const [name, channelConfig] of Object.entries(config.channels)) {
        if (channelConfig && typeof channelConfig === "object" && channelConfig.enabled !== false) {
          channels.push(name);
        }
      }
    }
    const gatewayPort = typeof config.gateway?.port === "number" ? config.gateway.port : DEFAULT_GATEWAY_PORT;
    return { channels, gatewayPort };
  } catch {
    return { channels: [], gatewayPort: DEFAULT_GATEWAY_PORT };
  }
}
async function detectAgents(workspaceDir, binaryPath) {
  const bin = binaryPath || "openclaw";
  let cliFailed = false;
  try {
    const cliResult = await new Promise((resolve) => {
      child_process.execFile(bin, ["agents", "list", "--json"], { timeout: 1e4 }, (error, stdout) => {
        if (error || !stdout.trim()) {
          cliFailed = true;
          resolve({ count: 0, agents: [] });
          return;
        }
        try {
          const parsed = JSON.parse(stdout);
          const list = Array.isArray(parsed) ? parsed : parsed.agents || [];
          const agents = list.map((a) => ({
            id: a.id || a.name || "unknown",
            name: a.identityName || a.name || a.id || "unknown",
            status: "unknown",
            ...typeof a.model === "string" && a.model ? { model: a.model } : {}
          }));
          resolve({ count: agents.length, agents });
        } catch {
          cliFailed = true;
          resolve({ count: 0, agents: [] });
        }
      });
    });
    if (!cliFailed) return { ...cliResult, degraded: false };
  } catch {
    cliFailed = true;
  }
  try {
    const files = await fs$2.readdir(workspaceDir);
    const agents = [];
    if (files.includes("AGENTS.md")) {
      agents.push({ id: "default", name: "Default Agent", status: "unknown" });
    }
    try {
      const agentsDir = path$1.join(workspaceDir, "agents");
      const agentDirs = await fs$2.readdir(agentsDir);
      for (const dir of agentDirs) {
        agents.push({ id: dir, name: dir, status: "unknown" });
      }
    } catch {
    }
    return { count: agents.length, agents, degraded: cliFailed };
  } catch {
    return { count: 0, agents: [], degraded: cliFailed };
  }
}
function checkGatewayHealth(port) {
  return new Promise((resolve) => {
    const req = http.get({
      hostname: "127.0.0.1",
      port,
      path: "/health",
      timeout: GATEWAY_CHECK_TIMEOUT_MS
    }, (res) => {
      resolve(res.statusCode !== void 0 && res.statusCode >= 200 && res.statusCode < 300);
      res.resume();
    });
    const fallback = () => {
      child_process.execFile("pgrep", ["-f", "openclaw-gateway"], { timeout: 3e3 }, (err, stdout) => {
        if (err || stdout.trim().length === 0) {
          resolve(false);
          return;
        }
        const probe = net.connect({ host: "127.0.0.1", port });
        const done = (ok) => {
          probe.removeAllListeners();
          probe.destroy();
          resolve(ok);
        };
        probe.setTimeout(GATEWAY_CHECK_TIMEOUT_MS);
        probe.once("connect", () => done(true));
        probe.once("error", () => done(false));
        probe.once("timeout", () => done(false));
      });
    };
    req.on("error", fallback);
    req.on("timeout", () => {
      req.destroy();
      fallback();
    });
  });
}
function detectRunningSandboxes() {
  return new Promise((resolve) => {
    child_process.execFile("/bin/ps", ["aux"], { timeout: 5e3 }, (error, stdout) => {
      if (error || !stdout.trim()) {
        resolve([]);
        return;
      }
      const seen = /* @__PURE__ */ new Set();
      const agents = [];
      for (const line of stdout.split("\n")) {
        const match = line.match(/openclaw-sbx-agent-([a-zA-Z0-9_-]+)-[0-9a-f]+/);
        if (match && !seen.has(match[1])) {
          seen.add(match[1]);
          agents.push({ id: match[1], name: match[1], status: "active" });
        }
      }
      resolve(agents);
    });
  });
}
async function detectOpenClaw(customWorkspaceDir) {
  const workspaceDir = customWorkspaceDir || process.env.OPENCLAW_WORKSPACE_DIR || path$1.join(os$1.homedir(), ".openclaw");
  const [binaryPath, workspaceExists] = await Promise.all([
    whichBinary("openclaw"),
    fs$2.access(workspaceDir).then(() => true).catch(() => false)
  ]);
  const installed = binaryPath !== null;
  if (!installed && !workspaceExists) {
    return {
      installed: false,
      binaryPath: null,
      version: null,
      workspaceExists: false,
      workspaceDir,
      gatewayRunning: false,
      gatewayPort: DEFAULT_GATEWAY_PORT,
      channels: [],
      agentCount: 0,
      agents: []
    };
  }
  const [version, config, agentInfo, sandboxAgents] = await Promise.all([
    binaryPath ? getOpenClawVersion(binaryPath) : Promise.resolve(null),
    workspaceExists ? readOpenClawConfig(workspaceDir) : Promise.resolve({ channels: [], gatewayPort: DEFAULT_GATEWAY_PORT }),
    detectAgents(workspaceDir, binaryPath),
    detectRunningSandboxes()
  ]);
  const gatewayRunning = await checkGatewayHealth(config.gatewayPort);
  const mergedAgents = [...agentInfo.agents];
  const knownIds = new Set(mergedAgents.map((a) => a.id));
  for (const sbx of sandboxAgents) {
    if (!knownIds.has(sbx.id)) {
      mergedAgents.push(sbx);
      knownIds.add(sbx.id);
    } else {
      const existing = mergedAgents.find((a) => a.id === sbx.id);
      if (existing) existing.status = "active";
    }
  }
  const result = {
    installed,
    binaryPath,
    version,
    workspaceExists,
    workspaceDir,
    gatewayRunning,
    gatewayPort: config.gatewayPort,
    channels: config.channels,
    agentCount: mergedAgents.length,
    agents: mergedAgents,
    detectionDegraded: agentInfo.degraded
  };
  logger.debug(`[OPENCLAW DETECT] Detection result: installed=${installed}, workspace=${workspaceExists}, gateway=${gatewayRunning}, version=${version}, channels=[${config.channels.join(",")}], agents=${mergedAgents.length} (configured=${agentInfo.count}, sandboxes=${sandboxAgents.length})`);
  return result;
}
function toMetadataFields(result) {
  return {
    openclawInstalled: result.installed,
    openclawVersion: result.version,
    openclawWorkspaceExists: result.workspaceExists,
    openclawGatewayRunning: result.gatewayRunning,
    openclawGatewayPort: result.gatewayPort,
    openclawChannels: result.channels,
    openclawAgentCount: result.agentCount
  };
}

const URL_RE$1 = /(https?:\/\/[^\s'"<>]+)/;
const URL_CODE_RE = /[?&]user_code=([A-Za-z0-9._-]+)/;
const BARE_CODE_RE = /\b([A-Z0-9]{3,}-[A-Z0-9]{3,})\b/;
const EXPIRY_RE = /expires? in (\d+)\s*(minute|min|second|sec)/i;
const DEFAULT_EXPIRES_IN = 600;
const DEFAULT_POLL_INTERVAL = 3;
const PARSE_TIMEOUT_MS = 45e3;
function extendedPathEnv$2() {
  const extra = [
    "/usr/local/bin",
    "/opt/homebrew/bin",
    path.join(os.homedir(), ".local/bin"),
    path.join(os.homedir(), ".npm-global/bin")
  ];
  return { ...process.env, PATH: [process.env.PATH, ...extra].filter(Boolean).join(":") };
}
function parseDeviceAuthOutput(buffer) {
  const urlMatch = buffer.match(URL_RE$1);
  if (!urlMatch) return null;
  const url = urlMatch[1].replace(/[.,)\]]+$/, "");
  const fromUrl = url.match(URL_CODE_RE)?.[1];
  const code = fromUrl ?? buffer.match(BARE_CODE_RE)?.[1] ?? "";
  const expiryMatch = buffer.match(EXPIRY_RE);
  const expiresIn = expiryMatch ? Number(expiryMatch[1]) * (expiryMatch[2].startsWith("min") ? 60 : 1) : null;
  return { url, code, urlIncludesCode: fromUrl !== void 0, expiresIn };
}
class NativeDeviceAuthAdapter {
  agent;
  /** Set once initiate() parses the CLI's output; read by the RPC layer. */
  urlIncludesCode = false;
  binary;
  argv;
  spawnImpl;
  resolveBinary;
  parseTimeoutMs;
  defaultExpiresIn;
  child = null;
  exitCode = void 0;
  buffer = "";
  constructor(agent, opts = {}) {
    const cap = getAgentAuthCapability(agent);
    if (!cap || cap.remoteMode !== "device-code" || !cap.remoteLoginCommand) {
      throw new Error(`native device-auth is not available for agent '${agent}'`);
    }
    this.agent = agent;
    this.binary = cap.binary;
    this.argv = cap.remoteLoginCommand;
    this.spawnImpl = opts.spawnImpl ?? node_child_process.spawn;
    this.resolveBinary = opts.resolveBinary ?? whichBinary;
    this.parseTimeoutMs = opts.parseTimeoutMs ?? PARSE_TIMEOUT_MS;
    this.defaultExpiresIn = opts.defaultExpiresIn ?? DEFAULT_EXPIRES_IN;
  }
  async initiate() {
    const binaryPath = await this.resolveBinary(this.binary);
    if (!binaryPath) {
      throw new Error(`${this.binary} CLI not found on PATH \u2014 install it first`);
    }
    const spawnOptions = { env: extendedPathEnv$2(), stdio: ["ignore", "pipe", "pipe"] };
    const child = this.spawnImpl(binaryPath, [...this.argv], spawnOptions);
    this.child = child;
    child.stdout?.on("data", (c) => {
      this.buffer += c.toString();
    });
    child.stderr?.on("data", (c) => {
      this.buffer += c.toString();
    });
    child.on("exit", (code) => {
      this.exitCode = code;
    });
    child.on("error", (err) => {
      this.exitCode = this.exitCode ?? -1;
      this.buffer += `
${err.message}`;
    });
    const parsed = await this.waitForPrompt();
    this.urlIncludesCode = parsed.urlIncludesCode;
    logger.debug(
      `[auth-flow ${this.agent}] device-auth prompt parsed (codeInUrl=${parsed.urlIncludesCode}, hasCode=${parsed.code.length > 0})`
    );
    return {
      verificationUrl: parsed.url,
      userCode: parsed.code,
      // The CLI owns the real device_code; the runner only echoes this
      // back to poll(), which ignores it.
      deviceCode: "native",
      expiresIn: parsed.expiresIn ?? this.defaultExpiresIn,
      interval: DEFAULT_POLL_INTERVAL
    };
  }
  async poll(_deviceCode) {
    if (this.exitCode === void 0) return { state: "pending" };
    if (this.exitCode === 0) {
      logger.debug(`[auth-flow ${this.agent}] CLI exited 0 \u2014 credentials written by the CLI`);
      return { state: "success", credentials: { native: true, agent: this.agent } };
    }
    const tail = this.buffer.split("\n").map((l) => l.trim()).filter(Boolean).slice(-3).join(" | ");
    return {
      state: "error",
      error: `${this.binary} ${this.argv.join(" ")} exited ${this.exitCode}${tail ? `: ${tail}` : ""}`
    };
  }
  /** No-op: the agent CLI already wrote its own credential file. */
  async persist() {
  }
  async cleanup() {
    if (this.child && this.exitCode === void 0) {
      try {
        this.child.kill("SIGTERM");
      } catch {
      }
    }
    this.buffer = "";
  }
  waitForPrompt() {
    const started = Date.now();
    return new Promise((resolve, reject) => {
      const tick = () => {
        const parsed = parseDeviceAuthOutput(this.buffer);
        if (parsed) {
          resolve(parsed);
          return;
        }
        if (this.exitCode !== void 0) {
          const tail = this.buffer.split("\n").map((l) => l.trim()).filter(Boolean).slice(-3).join(" | ");
          reject(new Error(
            `${this.binary} exited ${this.exitCode} before printing a verification URL${tail ? `: ${tail}` : ""}`
          ));
          return;
        }
        if (Date.now() - started > this.parseTimeoutMs) {
          void this.cleanup();
          reject(new Error(`${this.binary} did not print a verification URL in time`));
          return;
        }
        setTimeout(tick, 250);
      };
      tick();
    });
  }
}

const CODE_PROMPT_RE = /paste code|enter the code|authorization code[^\n]*>|code here/i;
const URL_RE = /(https?:\/\/[^\s'"<>]+)/;
const DEFAULT_TTL_MS = 10 * 60 * 1e3;
const TERMINAL_RETENTION_MS$2 = 5 * 60 * 1e3;
const PROMPT_TIMEOUT_MS = 3e4;
function extendedPathEnv$1() {
  const extra = [
    "/usr/local/bin",
    "/opt/homebrew/bin",
    path.join(os.homedir(), ".local/bin"),
    path.join(os.homedir(), ".npm-global/bin")
  ];
  return { ...process.env, PATH: [process.env.PATH, ...extra].filter(Boolean).join(":") };
}
class CliLoginFlow {
  records = /* @__PURE__ */ new Map();
  spawnImpl;
  resolveBinary;
  now;
  ttlMs;
  promptTimeoutMs;
  constructor(opts = {}) {
    this.spawnImpl = opts.spawnImpl ?? node_child_process.spawn;
    this.resolveBinary = opts.resolveBinary ?? whichBinary;
    this.now = opts.now ?? (() => Date.now());
    this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
    this.promptTimeoutMs = opts.promptTimeoutMs ?? PROMPT_TIMEOUT_MS;
  }
  /** True when this agent's own CLI can run the login. */
  static supports(agent) {
    const cap = getAgentAuthCapability(agent);
    return Boolean(cap?.loginCommand);
  }
  async start(agent) {
    const cap = getAgentAuthCapability(agent);
    if (!cap?.loginCommand) {
      throw new Error(`auth-flow: ${agent} has no CLI login command`);
    }
    const binaryPath = await this.resolveBinary(cap.binary);
    if (!binaryPath) {
      throw new Error(`${cap.binary} is not installed on this machine \u2014 install it first`);
    }
    const startedAt = this.now();
    const handle = { id: node_crypto.randomUUID(), agent, kind: "paste-code", startedAt };
    const child = this.spawnImpl(binaryPath, [...cap.loginCommand], {
      env: extendedPathEnv$1(),
      stdio: ["pipe", "pipe", "pipe"]
    });
    const record = {
      handle,
      child,
      buffer: "",
      settled: false,
      expiresAtMs: startedAt + this.ttlMs,
      gcTimer: null,
      status: { status: "pending", handle },
      ready: Promise.resolve()
    };
    child.stdout?.on("data", (c) => {
      record.buffer += c.toString();
    });
    child.stderr?.on("data", (c) => {
      record.buffer += c.toString();
    });
    child.on("exit", (code) => this.onExit(record, code));
    child.on("error", (err) => this.fail(record, err.message));
    this.records.set(handle.id, record);
    record.ready = this.waitForPrompt(record);
    await record.ready;
    logger.debug(`[auth-flow ${agent}] CLI login started id=${handle.id}`);
    return record.status;
  }
  poll(id) {
    const record = this.records.get(id);
    if (!record) return void 0;
    if (!record.settled && this.now() >= record.expiresAtMs) {
      this.kill(record);
      record.status = { status: "expired", handle: record.handle, finishedAt: this.now() };
      record.settled = true;
      this.scheduleGc(record);
    }
    return record.status;
  }
  /**
   * Relay the code the user copied from the vendor's page into the CLI's
   * stdin. The CLI performs the exchange, so a wrong code fails inside the
   * vendor's own client with the vendor's own error, not ours.
   */
  async submit(id, code) {
    const record = this.records.get(id);
    if (!record) throw new Error(`cli login flow not found: ${id}`);
    if (record.settled) return record.status;
    const trimmed = code.trim();
    if (!trimmed) return this.fail(record, "no authorization code provided");
    const stdin = record.child.stdin;
    if (!stdin || stdin.destroyed) {
      return this.fail(record, "the login process is no longer accepting input");
    }
    stdin.write(`${trimmed}
`);
    record.status = { status: "pending", handle: record.handle };
    logger.debug(`[auth-flow ${record.handle.agent}] relayed authorization code to the CLI`);
    return record.status;
  }
  async cancel(id) {
    const record = this.records.get(id);
    if (!record || record.settled) return;
    this.kill(record);
    record.status = { status: "cancelled", handle: record.handle, finishedAt: this.now() };
    record.settled = true;
    this.scheduleGc(record);
  }
  dispose() {
    for (const record of this.records.values()) {
      if (record.gcTimer) clearTimeout(record.gcTimer);
      if (!record.settled) this.kill(record);
    }
    this.records.clear();
  }
  // ── internals ────────────────────────────────────────────────────────
  /**
   * Wait for the CLI to print its authorize URL, then publish
   * `awaiting-user`. If the CLI finishes or dies first, the exit handler has
   * already settled the record and we leave it alone.
   */
  async waitForPrompt(record) {
    const deadline = this.now() + this.promptTimeoutMs;
    for (; ; ) {
      if (record.settled) return;
      const url = record.buffer.match(URL_RE)?.[1]?.replace(/[.,)\]]+$/, "");
      if (url) {
        const wantsCode = CODE_PROMPT_RE.test(record.buffer);
        record.status = {
          status: "awaiting-user",
          handle: record.handle,
          payload: {
            kind: "paste-code",
            authorizationUrl: url,
            instructions: wantsCode ? "Open the link, sign in, then paste the code shown in your browser." : "Open the link and approve the sign-in. This finishes on its own.",
            expiresAt: record.expiresAtMs
          }
        };
        return;
      }
      if (this.now() > deadline) {
        this.fail(record, `${record.handle.agent} did not print a sign-in link in time`);
        return;
      }
      await new Promise((r) => setTimeout(r, 200));
    }
  }
  onExit(record, code) {
    if (record.settled) return;
    if (code === 0) {
      record.status = { status: "succeeded", handle: record.handle, finishedAt: this.now() };
      record.settled = true;
      invalidateProbeCache();
      this.scheduleGc(record);
      logger.debug(`[auth-flow ${record.handle.agent}] CLI login succeeded`);
      return;
    }
    const tail = record.buffer.split("\n").map((l) => l.trim()).filter(Boolean).slice(-3).join(" | ");
    this.fail(record, `sign-in exited ${code}${tail ? `: ${tail}` : ""}`);
  }
  fail(record, error) {
    if (record.settled) return record.status;
    record.status = { status: "failed", handle: record.handle, finishedAt: this.now(), error };
    record.settled = true;
    this.kill(record);
    this.scheduleGc(record);
    logger.debug(`[auth-flow ${record.handle.agent}] CLI login failed: ${error}`);
    return record.status;
  }
  kill(record) {
    try {
      record.child.kill("SIGTERM");
    } catch {
    }
  }
  scheduleGc(record) {
    if (record.gcTimer) clearTimeout(record.gcTimer);
    record.gcTimer = setTimeout(() => this.records.delete(record.handle.id), TERMINAL_RETENTION_MS$2);
  }
}

async function startAuthFlow(agent, mode, runners) {
  const log = runners.log ?? (() => {
  });
  if (mode === "device-code") {
    const adapter = runners.pickDeviceCodeAdapter(agent);
    const handle = await runners.deviceCode.start(adapter);
    return await runners.deviceCode.getStatus(handle.id);
  }
  if (mode === "paste") {
    const handle = runners.paste.start(agent);
    const status = runners.paste.poll(handle.id);
    if (!status) {
      throw new Error(`auth-flow-start: paste flow ${handle.id} vanished immediately after start`);
    }
    return status;
  }
  if (mode === "paste-code" || mode === "cli-spawn") {
    if (runners.cliLogin.supports(agent)) {
      try {
        return await runners.cliLogin.start(agent);
      } catch (err) {
        log(`cli login unavailable for ${agent}: ${err instanceof Error ? err.message : String(err)}`);
      }
    }
    if (agent !== "claude") {
      throw new Error(
        `auth-flow-start: ${agent} has no paste-code login without its CLI installed`
      );
    }
    return runners.claudePasteCode.start();
  }
  throw new Error(`auth-flow-start: unsupported mode '${mode}'`);
}

const ENDPOINT$1 = "https://api.anthropic.com/v1/messages";
const TIMEOUT_MS$3 = 5e3;
function describeKey$3(key) {
  const trimmed = key.trim();
  const tail = trimmed.length >= 4 ? trimmed.slice(-4) : trimmed;
  return `<${trimmed.length} chars, \u2026${tail}>`;
}
const validateClaudeKey = async (key) => {
  const trimmed = key.trim();
  if (!trimmed) {
    return { valid: false, error: "empty key" };
  }
  logger.debug(`[auth/paste/claude] validating key ${describeKey$3(trimmed)}`);
  try {
    const res = await axios.post(
      ENDPOINT$1,
      {
        model: "claude-3-5-haiku-20241022",
        max_tokens: 1,
        messages: [{ role: "user", content: "hi" }]
      },
      {
        timeout: TIMEOUT_MS$3,
        headers: {
          "x-api-key": trimmed,
          "anthropic-version": "2023-06-01",
          "content-type": "application/json"
        },
        validateStatus: () => true
      }
    );
    if (res.status === 200) {
      return { valid: true };
    }
    if (res.status === 401 || res.status === 403) {
      return { valid: false, error: `unauthorized (status ${res.status})` };
    }
    if (res.status === 400) {
      const body = res.data;
      const t = body?.error?.type;
      if (t === "authentication_error" || t === "permission_error") {
        return { valid: false, error: t };
      }
      return { valid: true };
    }
    return { valid: false, error: `unexpected status ${res.status}` };
  } catch (err) {
    const ax = err;
    const msg = ax.code ?? ax.message ?? "network error";
    logger.debug(`[auth/paste/claude] validator threw: ${msg}`);
    return { valid: false, error: msg };
  }
};

const ENDPOINT = "https://api.openai.com/v1/models";
const TIMEOUT_MS$2 = 5e3;
function describeKey$2(key) {
  const trimmed = key.trim();
  const tail = trimmed.length >= 4 ? trimmed.slice(-4) : trimmed;
  return `<${trimmed.length} chars, \u2026${tail}>`;
}
const validateCodexKey = async (key) => {
  const trimmed = key.trim();
  if (!trimmed) {
    return { valid: false, error: "empty key" };
  }
  logger.debug(`[auth/paste/codex] validating key ${describeKey$2(trimmed)}`);
  try {
    const res = await axios.get(ENDPOINT, {
      timeout: TIMEOUT_MS$2,
      headers: {
        Authorization: `Bearer ${trimmed}`
      },
      validateStatus: () => true
    });
    if (res.status === 200) {
      const orgHeader = res.headers["openai-organization"] ?? res.headers["x-organization"];
      const result = { valid: true };
      if (orgHeader && typeof orgHeader === "string") {
        result.identity = { account: orgHeader };
      }
      return result;
    }
    if (res.status === 401 || res.status === 403) {
      return { valid: false, error: `unauthorized (status ${res.status})` };
    }
    return { valid: false, error: `unexpected status ${res.status}` };
  } catch (err) {
    const ax = err;
    const msg = ax.code ?? ax.message ?? "network error";
    logger.debug(`[auth/paste/codex] validator threw: ${msg}`);
    return { valid: false, error: msg };
  }
};

const BASE$1 = "https://generativelanguage.googleapis.com/v1/models";
const TIMEOUT_MS$1 = 5e3;
function describeKey$1(key) {
  const trimmed = key.trim();
  const tail = trimmed.length >= 4 ? trimmed.slice(-4) : trimmed;
  return `<${trimmed.length} chars, \u2026${tail}>`;
}
const validateGeminiKey = async (key) => {
  const trimmed = key.trim();
  if (!trimmed) {
    return { valid: false, error: "empty key" };
  }
  logger.debug(`[auth/paste/gemini] validating key ${describeKey$1(trimmed)}`);
  try {
    const res = await axios.get(BASE$1, {
      timeout: TIMEOUT_MS$1,
      params: { key: trimmed },
      validateStatus: () => true
    });
    if (res.status === 200) {
      return { valid: true };
    }
    if (res.status === 400 || res.status === 401 || res.status === 403) {
      return { valid: false, error: `unauthorized (status ${res.status})` };
    }
    return { valid: false, error: `unexpected status ${res.status}` };
  } catch (err) {
    const ax = err;
    const msg = ax.code ?? ax.message ?? "network error";
    logger.debug(`[auth/paste/gemini] validator threw: ${msg}`);
    return { valid: false, error: msg };
  }
};

const BASE = "https://api.x.ai/v1/models";
const TIMEOUT_MS = 5e3;
function describeKey(key) {
  const trimmed = key.trim();
  const tail = trimmed.length >= 4 ? trimmed.slice(-4) : trimmed;
  return `<${trimmed.length} chars, \u2026${tail}>`;
}
const validateGrokKey = async (key) => {
  const trimmed = key.trim();
  if (!trimmed) {
    return { valid: false, error: "empty key" };
  }
  logger.debug(`[auth/paste/grok] validating key ${describeKey(trimmed)}`);
  try {
    const res = await axios.get(BASE, {
      timeout: TIMEOUT_MS,
      headers: { Authorization: `Bearer ${trimmed}` },
      validateStatus: () => true
    });
    if (res.status === 200) {
      return { valid: true };
    }
    if (res.status === 401 || res.status === 403) {
      return { valid: false, error: `unauthorized (status ${res.status})` };
    }
    return { valid: false, error: `unexpected status ${res.status}` };
  } catch (err) {
    const ax = err;
    const msg = ax.code ?? ax.message ?? "network error";
    logger.debug(`[auth/paste/grok] validator threw: ${msg}`);
    return { valid: false, error: msg };
  }
};

const validateConsortiumKey = async () => {
  throw new Error("not-applicable: consortium auth does not use BYOK paste");
};

function atomicWriteSecret(path$1, contents) {
  const parent = path.dirname(path$1);
  fs.mkdirSync(parent, { recursive: true });
  const tmp = `${path$1}.tmp`;
  const fd = fs.openSync(tmp, "w", 384);
  try {
    fs.writeSync(fd, contents);
    fs.fsyncSync(fd);
  } finally {
    fs.closeSync(fd);
  }
  fs.chmodSync(tmp, 384);
  fs.renameSync(tmp, path$1);
  fs.chmodSync(path$1, 384);
}

const persistClaudeKey = async (key, options) => {
  const path$1 = path.join(options.homeDir, ".claude", ".credentials.json");
  const payload = JSON.stringify({
    access_token: key,
    source: "consortium-byok"
  });
  atomicWriteSecret(path$1, payload);
  return { path: path$1 };
};

const persistCodexKey = async (key, options) => {
  const env = options.env ?? process.env;
  const codexHome = env.CODEX_HOME && env.CODEX_HOME.length > 0 ? env.CODEX_HOME : path.join(options.homeDir, ".codex");
  const path$1 = path.join(codexHome, "auth.json");
  const payload = JSON.stringify({ OPENAI_API_KEY: key });
  atomicWriteSecret(path$1, payload);
  return { path: path$1 };
};

const persistGeminiKey = async (key, options) => {
  const path$1 = path.join(options.homeDir, ".config", "gemini", "credentials.json");
  const payload = JSON.stringify({ api_key: key });
  atomicWriteSecret(path$1, payload);
  return { path: path$1 };
};

const persistGrokKey = async (key, options) => {
  const path$1 = path.join(options.homeDir, ".config", "grok", "credentials.json");
  const payload = JSON.stringify({ api_key: key });
  atomicWriteSecret(path$1, payload);
  return { path: path$1 };
};

const persistConsortiumKey = async () => {
  throw new Error("not-applicable: consortium auth does not use BYOK paste");
};

const DEFAULT_VALIDATORS = {
  claude: validateClaudeKey,
  codex: validateCodexKey,
  gemini: validateGeminiKey,
  grok: validateGrokKey,
  consortium: validateConsortiumKey
};
const DEFAULT_PERSISTERS = {
  claude: persistClaudeKey,
  codex: persistCodexKey,
  gemini: persistGeminiKey,
  grok: persistGrokKey,
  consortium: persistConsortiumKey
};
const INSTRUCTIONS = {
  claude: "Paste your Anthropic API key (starts with `sk-ant-`). Get one at https://console.anthropic.com/settings/keys.",
  codex: "Paste your OpenAI API key (starts with `sk-`). Get one at https://platform.openai.com/api-keys.",
  gemini: "Paste your Google Gemini API key. Get one at https://aistudio.google.com/app/apikey.",
  grok: "Paste your xAI API key (starts with `xai-`). Get one at https://console.x.ai/.",
  consortium: "Consortium uses a QR / email / browser flow \u2014 paste is not applicable for this agent."
};
class PasteFlow {
  entries = /* @__PURE__ */ new Map();
  validators;
  persisters;
  homeDir;
  env;
  nowFn;
  invalidate;
  constructor(options = {}) {
    this.validators = { ...DEFAULT_VALIDATORS, ...options.validators };
    this.persisters = { ...DEFAULT_PERSISTERS, ...options.persisters };
    this.homeDir = options.homeDir ?? os.homedir();
    this.env = options.env ?? process.env;
    this.nowFn = options.now ?? Date.now;
    this.invalidate = options.invalidateProbeCache ?? (() => {
    });
  }
  start(agent) {
    if (!this.persisters[agent]) {
      throw new Error(
        `auth-flow: ${agent} has no BYOK paste path \u2014 it reads its key from an environment variable or its own config, not from a Consortium-written file`
      );
    }
    const handle = {
      id: node_crypto.randomUUID(),
      agent,
      kind: "paste",
      startedAt: this.nowFn()
    };
    const status = {
      status: "awaiting-user",
      handle,
      payload: {
        kind: "paste",
        instructions: INSTRUCTIONS[agent] ?? "Paste your API key for this provider."
      }
    };
    this.entries.set(handle.id, { handle, status, settled: false });
    logger.debug(`[auth/paste] started flow agent=${agent} id=${handle.id}`);
    return handle;
  }
  /**
   * Look up the current status for a handle. Mirrors the
   * DeviceCodeFlow contract so `auth-flow-poll` can fan out.
   */
  poll(handleId) {
    return this.entries.get(handleId)?.status;
  }
  async submit(handleId, key) {
    const entry = this.entries.get(handleId);
    if (!entry) {
      throw new Error(`unknown paste flow handle: ${handleId}`);
    }
    if (entry.settled) {
      return entry.status;
    }
    const { handle } = entry;
    const agent = handle.agent;
    const validator = this.validators[agent];
    let result;
    try {
      result = await validator(key);
    } catch (err) {
      const msg = err.message ?? "validator threw";
      const failed = {
        status: "failed",
        handle,
        finishedAt: this.nowFn(),
        error: msg
      };
      entry.status = failed;
      entry.settled = true;
      logger.debug(`[auth/paste] validator threw agent=${agent} id=${handleId}: ${msg}`);
      return failed;
    }
    if (!result.valid) {
      const failed = {
        status: "failed",
        handle,
        finishedAt: this.nowFn(),
        error: result.error ?? "invalid key"
      };
      entry.status = failed;
      entry.settled = true;
      logger.debug(
        `[auth/paste] invalid key agent=${agent} id=${handleId} error=${result.error ?? "unknown"}`
      );
      return failed;
    }
    const persister = this.persisters[agent];
    try {
      const persisted = await persister(key, { homeDir: this.homeDir, env: this.env });
      logger.debug(
        `[auth/paste] persisted agent=${agent} id=${handleId} path=${persisted.path}`
      );
    } catch (err) {
      const msg = err.message ?? "persist failed";
      const failed = {
        status: "failed",
        handle,
        finishedAt: this.nowFn(),
        error: `persist: ${msg}`
      };
      entry.status = failed;
      entry.settled = true;
      return failed;
    }
    try {
      this.invalidate(agent);
    } catch (err) {
      logger.debug(
        `[auth/paste] invalidate hook threw (ignored): ${err.message}`
      );
    }
    const succeeded = {
      status: "succeeded",
      handle,
      finishedAt: this.nowFn(),
      identity: result.identity
    };
    entry.status = succeeded;
    entry.settled = true;
    return succeeded;
  }
  async cancel(handleId) {
    const entry = this.entries.get(handleId);
    if (!entry) return;
    if (entry.settled) return;
    const cancelled = {
      status: "cancelled",
      handle: entry.handle,
      finishedAt: this.nowFn()
    };
    entry.status = cancelled;
    entry.settled = true;
    logger.debug(`[auth/paste] cancelled id=${handleId}`);
  }
}

const CLAUDE_LOOPBACK_PORT = 54545;
const CLAUDE_OAUTH = {
  CLIENT_ID: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
  AUTHORIZE_URL: "https://claude.ai/oauth/authorize",
  TOKEN_URL: "https://console.anthropic.com/v1/oauth/token",
  REDIRECT_URI: `http://localhost:${CLAUDE_LOOPBACK_PORT}/callback`,
  SCOPE: "user:inference"
};
const CLAUDE_OOB_REDIRECT_URI = CLAUDE_OAUTH.REDIRECT_URI;
function base64url(buf) {
  return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
function generateClaudePkce() {
  const verifier = base64url(node_crypto.randomBytes(32));
  const challenge = base64url(node_crypto.createHash("sha256").update(verifier).digest());
  return { verifier, challenge };
}
function generateClaudeState() {
  return base64url(node_crypto.randomBytes(32));
}
function buildClaudeAuthorizeUrl(opts) {
  const params = new URLSearchParams({
    client_id: CLAUDE_OAUTH.CLIENT_ID,
    response_type: "code",
    redirect_uri: opts.redirectUri ?? CLAUDE_OAUTH.REDIRECT_URI,
    scope: CLAUDE_OAUTH.SCOPE,
    code_challenge: opts.challenge,
    code_challenge_method: "S256",
    state: opts.state
  });
  if (opts.displayCode !== false) params.set("code", "true");
  return `${CLAUDE_OAUTH.AUTHORIZE_URL}?${params}`;
}
async function exchangeClaudeCode(opts) {
  const doFetch = opts.fetchImpl ?? globalThis.fetch;
  const response = await doFetch(CLAUDE_OAUTH.TOKEN_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      grant_type: "authorization_code",
      code: opts.code,
      redirect_uri: opts.redirectUri,
      client_id: CLAUDE_OAUTH.CLIENT_ID,
      code_verifier: opts.verifier,
      state: opts.state
    })
  });
  if (!response.ok) {
    throw new Error(`Token exchange failed: ${response.status} ${response.statusText}`);
  }
  const data = await response.json();
  return {
    raw: data,
    token: data.access_token,
    expires: Date.now() + data.expires_in * 1e3
  };
}
function parsePastedClaudeCode(pasted) {
  const trimmed = pasted.trim();
  if (/^https?:\/\//i.test(trimmed)) {
    try {
      const url = new URL(trimmed);
      const code2 = url.searchParams.get("code") ?? "";
      const state2 = url.searchParams.get("state") ?? void 0;
      if (code2) return { code: code2, state: state2 };
    } catch {
    }
  }
  const [code, state] = trimmed.split("#", 2);
  return { code: code.trim(), state: state?.trim() || void 0 };
}

const SCOPE = CLAUDE_OAUTH.SCOPE;
const REDIRECT_URI = CLAUDE_OOB_REDIRECT_URI;
const EXPIRES_IN_MS = 10 * 60 * 1e3;
const TERMINAL_RETENTION_MS$1 = 5 * 60 * 1e3;
class ClaudePasteCodeFlow {
  records = /* @__PURE__ */ new Map();
  homeDir;
  fetchImpl;
  now;
  constructor(opts = {}) {
    this.homeDir = opts.homeDir ?? os.homedir();
    this.fetchImpl = opts.fetch ?? globalThis.fetch;
    this.now = opts.now ?? (() => Date.now());
  }
  start() {
    const { verifier, challenge } = generateClaudePkce();
    const state = node_crypto.randomUUID();
    const id = node_crypto.randomUUID();
    const startedAt = this.now();
    const expiresAtMs = startedAt + EXPIRES_IN_MS;
    const authorizationUrl = buildClaudeAuthorizeUrl({
      challenge,
      state,
      redirectUri: REDIRECT_URI,
      displayCode: true
    });
    const handle = { id, agent: "claude", kind: "paste-code", startedAt };
    const status = {
      status: "awaiting-user",
      handle,
      payload: {
        kind: "paste-code",
        authorizationUrl,
        instructions: "Open the link, sign in to your Claude account, then copy the code shown in the browser and paste it here.",
        expiresAt: expiresAtMs
      }
    };
    this.records.set(id, { handle, status, verifier, state, expiresAtMs, settled: false, gcTimer: null });
    logger.debug(`[auth-flow claude] paste-code started id=${id}`);
    return status;
  }
  poll(id) {
    const record = this.records.get(id);
    if (!record) return void 0;
    if (!record.settled && this.now() >= record.expiresAtMs) {
      record.status = { status: "expired", handle: record.handle, finishedAt: this.now() };
      record.settled = true;
      this.scheduleGc(record);
    }
    return record.status;
  }
  async submit(id, codeAndState) {
    const record = this.records.get(id);
    if (!record) throw new Error(`claude paste-code flow not found: ${id}`);
    if (record.settled) return record.status;
    const { code, state: returnedState } = parsePastedClaudeCode(codeAndState);
    if (!code) return this.fail(record, "no authorization code provided");
    if (returnedState && returnedState !== record.state) {
      return this.fail(record, "state mismatch \u2014 restart the login");
    }
    let json;
    try {
      const res = await this.fetchImpl(CLAUDE_OAUTH.TOKEN_URL, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          grant_type: "authorization_code",
          code,
          state: record.state,
          client_id: CLAUDE_OAUTH.CLIENT_ID,
          redirect_uri: REDIRECT_URI,
          code_verifier: record.verifier
        })
      });
      json = await res.json().catch(() => ({}));
      if (!res.ok || !json.access_token) {
        return this.fail(record, json.error_description ?? json.error ?? `token exchange failed (HTTP ${res.status})`);
      }
    } catch (err) {
      return this.fail(record, err instanceof Error ? err.message : "token exchange failed");
    }
    const expiresAt = json.expires_in ? this.now() + json.expires_in * 1e3 : void 0;
    try {
      await this.persist(json, expiresAt);
    } catch (err) {
      return this.fail(record, `persist failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    const identity = {
      email: json.account?.email_address,
      account: json.organization?.name,
      expiresAt
    };
    record.status = { status: "succeeded", handle: record.handle, finishedAt: this.now(), identity };
    record.settled = true;
    invalidateProbeCache();
    this.scheduleGc(record);
    logger.debug(`[auth-flow claude] paste-code succeeded id=${id}`);
    return record.status;
  }
  async cancel(id) {
    const record = this.records.get(id);
    if (!record || record.settled) return;
    record.status = { status: "cancelled", handle: record.handle, finishedAt: this.now() };
    record.settled = true;
    this.scheduleGc(record);
  }
  dispose() {
    for (const r of this.records.values()) if (r.gcTimer) clearTimeout(r.gcTimer);
    this.records.clear();
  }
  fail(record, error) {
    record.status = { status: "failed", handle: record.handle, finishedAt: this.now(), error };
    record.settled = true;
    this.scheduleGc(record);
    logger.debug(`[auth-flow claude] paste-code failed id=${record.handle.id}: ${error}`);
    return record.status;
  }
  scheduleGc(record) {
    if (record.gcTimer) clearTimeout(record.gcTimer);
    record.gcTimer = setTimeout(() => this.records.delete(record.handle.id), TERMINAL_RETENTION_MS$1);
  }
  /** Write ~/.claude/.credentials.json in the nested shape the CLI reads. */
  async persist(token, expiresAt) {
    const creds = {
      claudeAiOauth: {
        accessToken: token.access_token,
        refreshToken: token.refresh_token,
        expiresAt,
        scopes: token.scope ? token.scope.split(" ") : SCOPE.split(" ")
      }
    };
    const dir = path.join(this.homeDir, ".claude");
    const file = path.join(dir, ".credentials.json");
    await fs$3.mkdir(dir, { recursive: true, mode: 448 });
    const tmp = `${file}.tmp-${process.pid}-${this.now()}`;
    await fs$3.writeFile(tmp, JSON.stringify(creds, null, 2), { mode: 384 });
    try {
      await fs$3.chmod(tmp, 384);
      await fs$3.rename(tmp, file);
      await fs$3.chmod(file, 384);
    } catch (err) {
      await fs$3.rm(tmp, { force: true }).catch(() => {
      });
      throw err;
    }
    await fs$3.chmod(dir, 448).catch(() => {
    });
  }
}

const INSTALL_AGENT_KINDS = [
  "claude",
  "codex",
  "gemini",
  "grok",
  "pi"
];
const INSTALL_RECIPES = {
  claude: {
    agent: "claude",
    binary: "claude",
    install: { method: "npm", npmPackage: "@anthropic-ai/claude-code" }
  },
  codex: {
    agent: "codex",
    binary: "codex",
    install: { method: "npm", npmPackage: "@openai/codex" }
  },
  gemini: {
    agent: "gemini",
    binary: "gemini",
    install: { method: "npm", npmPackage: "@google/gemini-cli" }
  },
  grok: {
    agent: "grok",
    binary: "grok",
    // Official xAI installer. Note: the npm name `grok-build-cli` seen in
    // older app code is third-party — do NOT use it. The curl installer
    // is the vetted source for the `grok` binary (`grok agent stdio`,
    // `~/.grok/auth.json`).
    install: { method: "curl", scriptUrl: "https://x.ai/cli/install.sh" }
  },
  pi: {
    agent: "pi",
    binary: "pi",
    // `--ignore-scripts`: pi's postinstall is not needed for the binary
    // and avoids running arbitrary lifecycle scripts during a remote
    // install.
    install: {
      method: "npm",
      npmPackage: "@earendil-works/pi-coding-agent",
      npmExtraArgs: ["--ignore-scripts"]
    }
  }
};
function isInstallAgentKind(value) {
  return typeof value === "string" && INSTALL_AGENT_KINDS.includes(value);
}

const TERMINAL_RETENTION_MS = 5 * 60 * 1e3;
const MAX_LOG_LINES = 800;
const MAX_LINE_LEN = 2e3;
const VERSION_PROBE_TIMEOUT_MS = 1e4;
const TERMINAL_PHASES = /* @__PURE__ */ new Set([
  "succeeded",
  "failed",
  "unsupported",
  "cancelled"
]);
function extendedPathEnv() {
  const extra = [
    "/usr/local/bin",
    "/opt/homebrew/bin",
    path.join(os.homedir(), ".local/bin"),
    path.join(os.homedir(), ".npm-global/bin")
  ];
  const PATH = [process.env.PATH, ...extra].filter(Boolean).join(":");
  return { ...process.env, PATH };
}
class InstallFlow {
  jobs = /* @__PURE__ */ new Map();
  now;
  constructor(opts = {}) {
    this.now = opts.now ?? (() => Date.now());
  }
  /**
   * Begin (or rejoin) an install for `agent`. Returns the initial status
   * immediately; the child runs in the background. A second call while a
   * job is in flight returns that job's handle (de-dupe / rejoin).
   */
  start(agent, _action = "install") {
    if (!isInstallAgentKind(agent)) {
      throw new Error(`agent-install-start: unsupported agent '${agent}'`);
    }
    const existing = this.findActiveByAgent(agent);
    if (existing) {
      return this.snapshot(existing, 0);
    }
    const recipe = INSTALL_RECIPES[agent];
    const job = {
      handleId: node_crypto.randomUUID(),
      agent,
      recipe,
      phase: "preflight",
      startedAt: this.now(),
      logs: [],
      nextSeq: 0,
      child: null,
      cancelled: false,
      gcTimer: null
    };
    this.jobs.set(job.handleId, job);
    this.appendLog(job, "info", `Installing ${agent}\u2026`);
    void this.run(job);
    return this.snapshot(job, 0);
  }
  poll(handleId, sinceCursor = 0) {
    const job = this.jobs.get(handleId);
    if (!job) throw new Error(`agent-install-poll: unknown handleId ${handleId}`);
    return this.snapshot(job, sinceCursor);
  }
  cancel(handleId) {
    const job = this.jobs.get(handleId);
    if (!job) return { ok: false };
    if (TERMINAL_PHASES.has(job.phase)) return { ok: true };
    job.cancelled = true;
    if (job.child && !job.child.killed) {
      try {
        job.child.kill("SIGTERM");
      } catch {
      }
    }
    this.toTerminal(job, "cancelled");
    return { ok: true };
  }
  dispose() {
    for (const job of this.jobs.values()) {
      if (job.gcTimer) clearTimeout(job.gcTimer);
      if (job.child && !job.child.killed) {
        try {
          job.child.kill("SIGKILL");
        } catch {
        }
      }
    }
    this.jobs.clear();
  }
  // ── internals ────────────────────────────────────────────────────────
  async run(job) {
    try {
      if (job.recipe.install.method === "npm") {
        await this.runNpm(job);
      } else {
        await this.runCurl(job);
      }
    } catch (err) {
      if (job.cancelled) return;
      this.fail(job, "install-failed", err instanceof Error ? err.message : String(err));
    }
  }
  async runNpm(job) {
    const recipe = job.recipe;
    if (recipe.install.method !== "npm") return;
    const npmPath = await whichBinary("npm");
    if (job.cancelled) return;
    if (!npmPath) {
      this.toUnsupported(
        job,
        "no-npm",
        "npm was not found on this machine. Install Node.js (which bundles npm) and try again."
      );
      return;
    }
    const versioned = recipe.pinVersion ? `${recipe.install.npmPackage}@${recipe.pinVersion}` : recipe.install.npmPackage;
    job.packageSpec = versioned;
    const args = ["install", "-g", versioned, ...recipe.install.npmExtraArgs ?? []];
    this.appendLog(job, "info", `npm ${args.join(" ")}`);
    await this.spawnAndStream(job, npmPath, args);
  }
  async runCurl(job) {
    const recipe = job.recipe;
    if (recipe.install.method !== "curl") return;
    const curlPath = await whichBinary("curl");
    if (job.cancelled) return;
    if (!curlPath) {
      this.toUnsupported(
        job,
        "no-curl",
        "curl was not found on this machine. Install curl and try again."
      );
      return;
    }
    const url = recipe.install.scriptUrl;
    job.packageSpec = url;
    this.appendLog(job, "info", `curl -fsSL ${url} | sh`);
    await this.spawnAndStream(job, "/bin/sh", ["-c", 'curl -fsSL "$0" | sh', url]);
  }
  /** Spawn, stream stdout/stderr line-by-line, then verify on exit. */
  spawnAndStream(job, command, args) {
    return new Promise((resolve) => {
      job.phase = "installing";
      const child = node_child_process.spawn(command, args, { env: extendedPathEnv() });
      job.child = child;
      let outBuf = "";
      let errBuf = "";
      const pump = (chunk, stream) => {
        const acc = (stream === "stdout" ? outBuf : errBuf) + chunk.toString("utf8");
        const lines = acc.split("\n");
        const tail = lines.pop() ?? "";
        if (stream === "stdout") outBuf = tail;
        else errBuf = tail;
        for (const line of lines) {
          if (line.length > 0) this.appendLog(job, stream, line);
        }
      };
      child.stdout?.on("data", (c) => pump(c, "stdout"));
      child.stderr?.on("data", (c) => pump(c, "stderr"));
      child.on("error", (err) => {
        if (job.cancelled) {
          resolve();
          return;
        }
        this.fail(job, "spawn-failed", err.message);
        resolve();
      });
      child.on("close", (code) => {
        if (outBuf.length > 0) this.appendLog(job, "stdout", outBuf);
        if (errBuf.length > 0) this.appendLog(job, "stderr", errBuf);
        job.child = null;
        if (job.cancelled) {
          resolve();
          return;
        }
        if (code !== 0) {
          this.classifyAndFail(job, errBuf, code);
          resolve();
          return;
        }
        void this.verify(job).then(resolve);
      });
    });
  }
  async verify(job) {
    job.phase = "verifying";
    this.appendLog(job, "info", `Verifying ${job.recipe.binary} on PATH\u2026`);
    const binPath = await whichBinary(job.recipe.binary);
    if (job.cancelled) return;
    if (!binPath) {
      this.fail(
        job,
        "not-on-path",
        `Installed, but '${job.recipe.binary}' is not on the daemon's PATH. Check the npm global bin location.`
      );
      return;
    }
    const version = await this.probeVersion(binPath);
    if (job.cancelled) return;
    job.version = version ?? void 0;
    this.appendLog(job, "info", version ? `Installed ${job.recipe.binary} ${version}` : `Installed ${job.recipe.binary}`);
    this.toTerminal(job, "succeeded");
  }
  probeVersion(binPath) {
    return new Promise((resolve) => {
      node_child_process.execFile(binPath, ["--version"], { timeout: VERSION_PROBE_TIMEOUT_MS }, (error, stdout) => {
        if (error) {
          resolve(null);
          return;
        }
        resolve(stdout.split("\n")[0]?.trim() || null);
      });
    });
  }
  classifyAndFail(job, stderrTail, code) {
    const hay = (stderrTail + job.logs.slice(-20).map((l) => l.text).join("\n")).toLowerCase();
    if (hay.includes("eacces") || hay.includes("permission denied")) {
      this.fail(
        job,
        "eacces",
        "Permission denied writing the npm global directory. Use a Node version manager or fix npm global permissions, then retry."
      );
      return;
    }
    if (hay.includes("eai_again") || hay.includes("network") || hay.includes("etimedout") || hay.includes("enotfound")) {
      this.fail(job, "network", "Network error reaching the package registry. Check connectivity and retry.");
      return;
    }
    this.fail(job, "install-failed", `Install exited with code ${code ?? "null"}.`);
  }
  fail(job, code, message) {
    job.error = { code, message };
    this.appendLog(job, "info", `Failed: ${message}`);
    this.toTerminal(job, "failed");
  }
  toUnsupported(job, code, message) {
    job.error = { code, message };
    this.appendLog(job, "info", message);
    this.toTerminal(job, "unsupported");
  }
  toTerminal(job, phase) {
    if (TERMINAL_PHASES.has(job.phase)) return;
    job.phase = phase;
    job.finishedAt = this.now();
    logger.debug(`[cli-install] ${job.agent} ${phase}${job.error ? ` code=${job.error.code}` : ""}`);
    if (job.gcTimer) clearTimeout(job.gcTimer);
    job.gcTimer = setTimeout(() => this.jobs.delete(job.handleId), TERMINAL_RETENTION_MS);
  }
  appendLog(job, stream, text) {
    const clipped = text.length > MAX_LINE_LEN ? `${text.slice(0, MAX_LINE_LEN)}\u2026` : text;
    job.logs.push({ seq: job.nextSeq++, stream, text: clipped });
    if (job.logs.length > MAX_LOG_LINES) job.logs.splice(0, job.logs.length - MAX_LOG_LINES);
  }
  findActiveByAgent(agent) {
    for (const job of this.jobs.values()) {
      if (job.agent === agent && !TERMINAL_PHASES.has(job.phase)) return job;
    }
    return void 0;
  }
  snapshot(job, sinceCursor) {
    const logs = sinceCursor > 0 ? job.logs.filter((l) => l.seq >= sinceCursor) : job.logs.slice();
    return {
      handleId: job.handleId,
      agent: job.agent,
      phase: job.phase,
      package: job.packageSpec,
      version: job.version,
      error: job.error,
      startedAt: job.startedAt,
      finishedAt: job.finishedAt,
      logCursor: job.nextSeq,
      logs
    };
  }
}

function installsDisabled() {
  return process.env.CONSORTIUM_AGENT_INSTALL === "0";
}
function registerCliInstallHandlers(rpcHandlerManager, installFlow) {
  rpcHandlerManager.registerHandler("agent-install-start", async (params) => {
    if (installsDisabled()) {
      throw new Error("Remote installs are disabled on this machine (CONSORTIUM_AGENT_INSTALL=0).");
    }
    const agent = String(params?.agent ?? "");
    return installFlow.start(agent, "install");
  });
  rpcHandlerManager.registerHandler("agent-install-poll", async (params) => {
    const handleId = String(params?.handleId ?? "");
    if (!handleId) throw new Error("agent-install-poll: handleId required");
    const sinceCursor = Number(params?.sinceCursor ?? 0);
    return installFlow.poll(handleId, Number.isFinite(sinceCursor) ? sinceCursor : 0);
  });
  rpcHandlerManager.registerHandler("agent-install-cancel", async (params) => {
    const handleId = String(params?.handleId ?? "");
    if (!handleId) throw new Error("agent-install-cancel: handleId required");
    return installFlow.cancel(handleId);
  });
}

const AgentKindSchema = z.z.enum(["claude", "codex", "gemini", "grok", "pi", "opencode", "consortium"]);
z.z.enum(["machine", "managed", "byok", "auto"]);
const AuthCredentialOriginSchema = z.z.enum(["env", "keychain", "file", "cli"]);
const AuthIdentitySchema = z.z.object({
  email: z.z.string().optional(),
  account: z.z.string().optional(),
  expiresAt: z.z.number().optional()
});
const AuthWarningSchema = z.z.enum(["binary-missing", "keychain-locked"]);
const AuthStateSchema = z.z.object({
  agent: AgentKindSchema,
  status: z.z.enum(["present", "missing", "invalid", "unknown"]),
  source: AuthCredentialOriginSchema.optional(),
  identity: AuthIdentitySchema.optional(),
  scopes: z.z.array(z.z.string()).optional(),
  lastCheckedAt: z.z.number(),
  error: z.z.string().optional(),
  /**
   * Soft warning, never set on existing returns from older probes.
   * Backward compatible: optional, ignored by old clients.
   */
  warning: AuthWarningSchema.optional(),
  /**
   * Whether the agent's CLI binary is installed and runnable on this
   * machine (`<binary> --version` exits 0). Orthogonal to `status`,
   * which describes credentials only — together they give the four
   * distinguishable states of defect A-9:
   *
   *   status=present + binaryPresent=true   → ready
   *   status=present + binaryPresent=false  → logged in, CLI missing
   *                                           (also sets warning
   *                                           `binary-missing`)
   *   status=missing + binaryPresent=true   → installed but logged out
   *   status=missing + binaryPresent=false  → not installed
   *
   * `undefined` means "not applicable / not checked" — e.g. the
   * `consortium` probe (its binary is this very process) or an older
   * daemon that predates this field. Consumers MUST treat `undefined`
   * as unknown, never as `false`.
   */
  binaryPresent: z.z.boolean().optional()
});
z.z.object({
  states: z.z.array(AuthStateSchema),
  probedAt: z.z.number()
});
const AuthFlowKindSchema = z.z.enum(["device-code", "browser", "paste", "paste-code", "cli-spawn"]);
const AuthFlowHandleSchema = z.z.object({
  id: z.z.string(),
  agent: AgentKindSchema,
  kind: AuthFlowKindSchema,
  startedAt: z.z.number()
});
const AuthFlowDeviceCodePayloadSchema = z.z.object({
  kind: z.z.literal("device-code"),
  verificationUrl: z.z.string(),
  userCode: z.z.string(),
  expiresAt: z.z.number()
});
const AuthFlowBrowserPayloadSchema = z.z.object({
  kind: z.z.literal("browser"),
  authorizationUrl: z.z.string(),
  expiresAt: z.z.number()
});
const AuthFlowPastePayloadSchema = z.z.object({
  kind: z.z.literal("paste"),
  instructions: z.z.string()
});
const AuthFlowPasteCodePayloadSchema = z.z.object({
  kind: z.z.literal("paste-code"),
  authorizationUrl: z.z.string(),
  instructions: z.z.string(),
  expiresAt: z.z.number()
});
const AuthFlowCliSpawnPayloadSchema = z.z.object({
  kind: z.z.literal("cli-spawn"),
  command: z.z.string(),
  args: z.z.array(z.z.string())
});
const AuthFlowAwaitingUserPayloadSchema = z.z.discriminatedUnion("kind", [
  AuthFlowDeviceCodePayloadSchema,
  AuthFlowBrowserPayloadSchema,
  AuthFlowPastePayloadSchema,
  AuthFlowPasteCodePayloadSchema,
  AuthFlowCliSpawnPayloadSchema
]);
z.z.discriminatedUnion("status", [
  z.z.object({
    status: z.z.literal("pending"),
    handle: AuthFlowHandleSchema
  }),
  z.z.object({
    status: z.z.literal("awaiting-user"),
    handle: AuthFlowHandleSchema,
    payload: AuthFlowAwaitingUserPayloadSchema
  }),
  z.z.object({
    status: z.z.literal("succeeded"),
    handle: AuthFlowHandleSchema,
    finishedAt: z.z.number(),
    identity: AuthIdentitySchema.optional()
  }),
  z.z.object({
    status: z.z.literal("failed"),
    handle: AuthFlowHandleSchema,
    finishedAt: z.z.number(),
    error: z.z.string()
  }),
  z.z.object({
    status: z.z.literal("cancelled"),
    handle: AuthFlowHandleSchema,
    finishedAt: z.z.number()
  }),
  z.z.object({
    status: z.z.literal("expired"),
    handle: AuthFlowHandleSchema,
    finishedAt: z.z.number()
  })
]);

class ProvisioningEventEmitter {
  inner = new node_events.EventEmitter();
  on(event, listener) {
    this.inner.on(event, listener);
    return this;
  }
  once(event, listener) {
    this.inner.once(event, listener);
    return this;
  }
  off(event, listener) {
    this.inner.off(event, listener);
    return this;
  }
  emit(event, payload) {
    return this.inner.emit(event, payload);
  }
  /**
   * Remove all listeners for a given event, or all listeners across
   * all events if no event is specified. Used by tests.
   */
  removeAllListeners(event) {
    if (event !== void 0) {
      this.inner.removeAllListeners(event);
    } else {
      this.inner.removeAllListeners();
    }
    return this;
  }
}
const provisioningEvents = new ProvisioningEventEmitter();

let sentry = null;
let initialized = false;
function loadSentry() {
  try {
    const mod = require("@sentry/node");
    return mod;
  } catch (err) {
    logger.debug("[observability] @sentry/node not installed; observability disabled", err);
    return null;
  }
}
const PII_KEY_PATTERN = /^(authorization|cookie|token|secret|access[_-]?key|refresh[_-]?token|password|email|host|hostname|machineid)$/i;
function scrub(data) {
  if (!data) return void 0;
  const out = {};
  for (const [k, v] of Object.entries(data)) {
    if (PII_KEY_PATTERN.test(k)) {
      out[k] = "<redacted>";
      continue;
    }
    if (typeof v === "string" && (v.includes("Bearer ") || /eyJ[A-Za-z0-9_-]{10,}/.test(v))) {
      out[k] = "<redacted>";
      continue;
    }
    out[k] = v;
  }
  return out;
}
function initObservability() {
  if (initialized) return;
  initialized = true;
  const dsn = process.env.CONSORTIUM_SENTRY_DSN;
  if (!dsn) {
    logger.debug("[observability] CONSORTIUM_SENTRY_DSN not set; observability disabled");
    return;
  }
  const candidate = loadSentry();
  if (!candidate) return;
  sentry = candidate;
  try {
    sentry.init({
      dsn,
      release: `consortium-cli@${configuration.currentCliVersion}`,
      environment: process.env.CONSORTIUM_ENV ?? "production",
      // We want to capture unhandled errors but NOT auto-attach
      // request bodies (which may contain bearer tokens).
      sendDefaultPii: false,
      // Use a low traces sample rate; this is for errors, not perf.
      tracesSampleRate: 0,
      beforeSend(event) {
        try {
          const req = event.request;
          if (req?.headers) {
            for (const k of Object.keys(req.headers)) {
              if (PII_KEY_PATTERN.test(k)) req.headers[k] = "<redacted>";
            }
          }
          if (req && "cookies" in req) req.cookies = void 0;
        } catch {
        }
        return event;
      }
    });
    sentry.setTag("component", "daemon");
    sentry.setTag("cli_version", configuration.currentCliVersion);
  } catch (err) {
    logger.debug("[observability] Sentry.init threw; disabling", err);
    sentry = null;
    return;
  }
  installGlobalHandlers();
  installProvisioningBreadcrumbs();
}
function captureError(err, context) {
  if (!sentry) return;
  try {
    sentry.captureException(err, { extra: scrub(context) });
  } catch (innerErr) {
    logger.debug("[observability] captureError failed", innerErr);
  }
}
function captureBreadcrumb(message, data) {
  if (!sentry) return;
  try {
    sentry.addBreadcrumb({
      category: "daemon",
      message,
      data: scrub(data),
      level: "info"
    });
  } catch {
  }
}
function incrementCounter(name, tags) {
  if (!sentry) return;
  try {
    sentry.captureMessage(`counter.${name}`, {
      level: "info",
      tags: { counter: name, ...tags ?? {} }
    });
  } catch {
  }
}
function installGlobalHandlers() {
  process.on("unhandledRejection", (reason) => {
    captureError(reason, { source: "unhandledRejection" });
  });
  process.on("uncaughtException", (err) => {
    captureError(err, { source: "uncaughtException" });
  });
}
function installProvisioningBreadcrumbs() {
  const safeOn = (name, fn) => {
    try {
      provisioningEvents.on(name, (e) => {
        try {
          fn(e);
        } catch {
        }
      });
    } catch {
    }
  };
  safeOn("account-switching", (e) => {
    captureBreadcrumb("provisioning.account-switching", {
      sessionCount: Array.isArray(e?.sessions) ? e.sessions.length : 0
    });
  });
  safeOn("account-switched", () => {
    captureBreadcrumb("provisioning.account-switched");
  });
  safeOn("restart-requested", (e) => {
    captureBreadcrumb("provisioning.restart-requested", { reason: e?.reason });
    incrementCounter("daemon-restart-reason", { reason: String(e?.reason ?? "unknown") });
  });
  safeOn("deprovisioned", (e) => {
    captureBreadcrumb("provisioning.deprovisioned", {
      terminatedSessions: Array.isArray(e?.terminatedSessions) ? e.terminatedSessions.length : 0
    });
  });
  safeOn("provisioned", (e) => {
    captureBreadcrumb("provisioning.provisioned", { nextState: e?.nextState });
    incrementCounter("provision-success", { nextState: String(e?.nextState ?? "unknown") });
  });
}

const ACCOUNT_USAGE_MAX_AGE_MS = 30 * 60 * 1e3;
const MAX_ENTRIES = 512;
const registry = /* @__PURE__ */ new Map();
function keyOf(snapshot) {
  return `${snapshot.accountKeyId ?? ""}|${snapshot.providerId}|${snapshot.limitKind}`;
}
function isExpired(snapshot, now) {
  if (snapshot.resetsAt !== void 0 && snapshot.resetsAt <= now) return true;
  return now - snapshot.fetchedAt > ACCOUNT_USAGE_MAX_AGE_MS;
}
function recordAccountUsage(snapshot) {
  if (!snapshot || !snapshot.accountKeyId || !snapshot.providerId) return;
  if (!Number.isFinite(snapshot.fetchedAt)) return;
  const normalized = { ...snapshot, source: "daemon-headers" };
  const key = keyOf(normalized);
  const existing = registry.get(key);
  if (existing && existing.fetchedAt > normalized.fetchedAt) return;
  if (!existing && registry.size >= MAX_ENTRIES) {
    pruneExpired(Date.now());
    if (registry.size >= MAX_ENTRIES) return;
  }
  registry.set(key, normalized);
}
function recordAccountUsageBatch(snapshots) {
  for (const s of snapshots) recordAccountUsage(s);
}
function pruneExpired(now) {
  for (const [key, snapshot] of registry) {
    if (isExpired(snapshot, now)) registry.delete(key);
  }
}
function listAccountUsage(now = Date.now()) {
  pruneExpired(now);
  return [...registry.values()].sort((a, b) => b.fetchedAt - a.fetchedAt);
}
function snapshotsFromCodexRateLimits(raw, ctx) {
  if (!raw || typeof raw !== "object") return [];
  const now = ctx.now ?? Date.now();
  const providerId = ctx.providerId ?? "openai";
  const obj = raw;
  const windows = [];
  for (const label of ["primary", "secondary"]) {
    const w = obj[label];
    if (w && typeof w === "object") windows.push({ window: w, label });
  }
  if (windows.length === 0 && typeof obj.used_percent === "number") {
    windows.push({ window: obj, label: "primary" });
  }
  const out = [];
  for (const { window, label } of windows) {
    const pct = typeof window.used_percent === "number" && Number.isFinite(window.used_percent) ? window.used_percent : void 0;
    if (pct === void 0) continue;
    let resetsAt;
    if (typeof window.resets_in_seconds === "number" && Number.isFinite(window.resets_in_seconds)) {
      resetsAt = now + window.resets_in_seconds * 1e3;
    } else if (typeof window.resets_at === "number" && Number.isFinite(window.resets_at)) {
      resetsAt = window.resets_at < 1e12 ? window.resets_at * 1e3 : window.resets_at;
    } else if (typeof window.resets_at === "string") {
      const parsed = Date.parse(window.resets_at);
      if (Number.isFinite(parsed)) resetsAt = parsed;
    }
    const minutes = typeof window.window_minutes === "number" ? window.window_minutes : void 0;
    out.push({
      accountKeyId: ctx.accountKeyId,
      providerId,
      source: "daemon-headers",
      limitKind: "plan-window",
      status: pct >= 100 ? "limited" : pct >= 80 ? "warning" : "ok",
      pct,
      unit: "percent",
      resetsAt,
      note: minutes !== void 0 ? `${label} window: ${minutes}m` : label,
      fetchedAt: now
    });
  }
  return out;
}

function buildMachineIdempotencyKey(machineId, callId, ordinal) {
  const base = `ue1:mch:${machineId}:${callId}`;
  const n = ordinal ?? 0;
  return n === 0 ? base : `${base}:${n}`;
}
const DEFAULTS = {
  maxBytes: 50 * 1024 * 1024,
  segmentMaxBytes: 4 * 1024 * 1024,
  flushIntervalMs: 250,
  flushMaxEntries: 64,
  maxEntryBytes: 32 * 1024
};
const SEGMENT_PREFIX = "seg-";
const SEGMENT_SUFFIX = ".jsonl";
const STATS_FILE = "stats.json";
function emptyStats() {
  return {
    droppedEntries: 0,
    rejectedEntries: 0,
    corruptLines: 0,
    uploadedEntries: 0,
    discardedEntries: 0,
    lastDropAt: null
  };
}
function defaultUsageQueueDir() {
  return path.join(configuration.consortiumHomeDir, "usage-queue");
}
class UsageQueue {
  dir;
  maxBytes;
  segmentMaxBytes;
  flushIntervalMs;
  flushMaxEntries;
  maxEntryBytes;
  activeName = null;
  activeFd = null;
  activeBytes = 0;
  pending = [];
  pendingBytes = 0;
  flushTimer = null;
  seq = 0;
  closed = false;
  counters;
  constructor(options = {}) {
    this.dir = options.dir ?? defaultUsageQueueDir();
    this.maxBytes = options.maxBytes ?? DEFAULTS.maxBytes;
    this.segmentMaxBytes = options.segmentMaxBytes ?? DEFAULTS.segmentMaxBytes;
    this.flushIntervalMs = options.flushIntervalMs ?? DEFAULTS.flushIntervalMs;
    this.flushMaxEntries = options.flushMaxEntries ?? DEFAULTS.flushMaxEntries;
    this.maxEntryBytes = options.maxEntryBytes ?? DEFAULTS.maxEntryBytes;
    fs.mkdirSync(this.dir, { recursive: true, mode: 448 });
    this.counters = this.loadStats();
    for (const name of this.allSegments()) {
      const n = Number(name.slice(SEGMENT_PREFIX.length).split("-")[1]);
      if (Number.isFinite(n) && n >= this.seq) this.seq = n + 1;
    }
  }
  // ── Append (hot path) ────────────────────────────────────────────────────
  /**
   * Buffer one entry for durable append. Synchronous, allocation-light,
   * never throws. Returns `'rejected'` only for an entry that can never be
   * written (unserialisable or absurdly large) — the caller should not retry.
   */
  append(entry) {
    if (this.closed) return "rejected";
    let line;
    try {
      line = JSON.stringify(entry);
    } catch {
      this.bumpStat("rejectedEntries");
      return "rejected";
    }
    if (line.includes("\n") || Buffer.byteLength(line) > this.maxEntryBytes) {
      this.bumpStat("rejectedEntries");
      return "rejected";
    }
    this.pending.push(line);
    this.pendingBytes += Buffer.byteLength(line) + 1;
    if (this.pending.length >= this.flushMaxEntries) {
      this.flushSync();
    } else if (!this.flushTimer) {
      this.flushTimer = setTimeout(() => {
        this.flushSync();
      }, this.flushIntervalMs);
      this.flushTimer.unref?.();
    }
    return "queued";
  }
  /** Buffered-but-not-yet-fsync'd entry count. Test/observability seam. */
  get pendingCount() {
    return this.pending.length;
  }
  // ── Durability ───────────────────────────────────────────────────────────
  /**
   * Write every buffered line to the active segment and fsync it. Safe to
   * call at any time (shutdown hook, before a drain, from the timer). Never
   * throws: a full or read-only disk must not take the daemon down.
   */
  flushSync() {
    if (this.flushTimer) {
      clearTimeout(this.flushTimer);
      this.flushTimer = null;
    }
    if (this.pending.length === 0) return;
    const payload = this.pending.join("\n") + "\n";
    const bytes = Buffer.byteLength(payload);
    try {
      const fd = this.openActive();
      fs.writeSync(fd, payload);
      fs.fsyncSync(fd);
      this.activeBytes += bytes;
      this.pending = [];
      this.pendingBytes = 0;
    } catch (err) {
      const lost = this.pending.length;
      this.pending = [];
      this.pendingBytes = 0;
      this.bumpStat("rejectedEntries", lost);
      logger.debug(`[USAGE QUEUE] flush failed, dropped ${lost} entries: ${err}`);
      return;
    }
    if (this.activeBytes >= this.segmentMaxBytes) this.sealActive();
    this.enforceCap();
  }
  /**
   * Close the active segment so a drain may read it. The next append starts
   * a fresh segment. Idempotent.
   */
  sealActive() {
    if (this.activeFd !== null) {
      try {
        fs.closeSync(this.activeFd);
      } catch {
      }
    }
    this.activeFd = null;
    this.activeName = null;
    this.activeBytes = 0;
  }
  /** flushSync + sealActive + close. Call from the daemon shutdown path. */
  close() {
    this.flushSync();
    this.sealActive();
    this.closed = true;
  }
  openActive() {
    if (this.activeFd !== null) return this.activeFd;
    const name = `${SEGMENT_PREFIX}${String(Date.now()).padStart(15, "0")}-${String(this.seq++).padStart(6, "0")}${SEGMENT_SUFFIX}`;
    const full = path.join(this.dir, name);
    const fd = fs.openSync(full, "a", 384);
    this.activeFd = fd;
    this.activeName = name;
    this.activeBytes = (() => {
      try {
        return fs.statSync(full).size;
      } catch {
        return 0;
      }
    })();
    return fd;
  }
  // ── Segment inventory ────────────────────────────────────────────────────
  allSegments() {
    let names;
    try {
      names = fs.readdirSync(this.dir);
    } catch {
      return [];
    }
    return names.filter((n) => n.startsWith(SEGMENT_PREFIX) && n.endsWith(SEGMENT_SUFFIX)).sort();
  }
  /** Sealed (drainable) segments, oldest first. Excludes the open segment. */
  sealedSegments() {
    return this.allSegments().filter((n) => n !== this.activeName);
  }
  /** Total on-disk bytes across all segments. */
  totalBytes() {
    let total = 0;
    for (const name of this.allSegments()) {
      try {
        total += fs.statSync(path.join(this.dir, name)).size;
      } catch {
      }
    }
    return total;
  }
  /**
   * Parse one sealed segment. Tolerant by construction: a truncated trailing
   * line (the crash signature) or any unparsable line is skipped and counted
   * as `corruptLines` rather than poisoning the whole segment.
   */
  readSegment(name) {
    let raw;
    try {
      raw = fs.readFileSync(path.join(this.dir, name), "utf8");
    } catch {
      return [];
    }
    const out = [];
    let corrupt = 0;
    for (const line of raw.split("\n")) {
      if (line.length === 0) continue;
      try {
        const parsed = JSON.parse(line);
        if (parsed && parsed.v === 1 && typeof parsed.idempotencyKey === "string" && parsed.event) {
          out.push(parsed);
        } else {
          corrupt++;
        }
      } catch {
        corrupt++;
      }
    }
    if (corrupt > 0) this.bumpStat("corruptLines", corrupt);
    return out;
  }
  /** Every queued entry across every SEALED segment, oldest first. */
  readAll() {
    const out = [];
    for (const name of this.sealedSegments()) out.push(...this.readSegment(name));
    return out;
  }
  /**
   * Replace a segment with the subset that has NOT settled yet, atomically
   * (write tmp → fsync → rename). A crash mid-rewrite therefore either keeps
   * the old segment (re-upload, deduped server-side) or lands the new one —
   * never a half file.
   */
  rewriteSegment(name, entries) {
    const full = path.join(this.dir, name);
    if (entries.length === 0) {
      this.removeSegment(name);
      return;
    }
    const tmp = `${full}.tmp`;
    try {
      const payload = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
      const fd = fs.openSync(tmp, "w", 384);
      try {
        fs.writeSync(fd, payload);
        fs.fsyncSync(fd);
      } finally {
        fs.closeSync(fd);
      }
      fs.renameSync(tmp, full);
    } catch (err) {
      try {
        fs.unlinkSync(tmp);
      } catch {
      }
      logger.debug(`[USAGE QUEUE] segment rewrite failed for ${name}: ${err}`);
    }
  }
  removeSegment(name) {
    try {
      fs.unlinkSync(path.join(this.dir, name));
    } catch {
    }
  }
  // ── Backpressure ─────────────────────────────────────────────────────────
  /**
   * Enforce the byte ceiling by deleting the OLDEST sealed segments first.
   * Oldest-dropped (not newest) because a stale month-old event is worth
   * strictly less than the call that just happened, and because dropping the
   * head keeps the queue draining in order.
   */
  enforceCap() {
    let total = this.totalBytes();
    if (total <= this.maxBytes) return;
    for (const name of this.sealedSegments()) {
      if (total <= this.maxBytes) break;
      const full = path.join(this.dir, name);
      let size = 0;
      let lines = 0;
      try {
        size = fs.statSync(full).size;
        lines = this.readSegment(name).length;
      } catch {
      }
      this.removeSegment(name);
      total -= size;
      if (lines > 0) {
        this.counters.droppedEntries += lines;
        this.counters.lastDropAt = Date.now();
      }
    }
    if (total > this.maxBytes && this.activeName) {
      this.sealActive();
    }
    this.persistStats();
    logger.debug(`[USAGE QUEUE] backpressure: dropped to ${total} bytes (total dropped entries ${this.counters.droppedEntries})`);
  }
  // ── Stats ────────────────────────────────────────────────────────────────
  stats() {
    return { ...this.counters };
  }
  /** Uploader bookkeeping: entries the server accepted / permanently refused. */
  recordUploaded(count) {
    this.bumpStat("uploadedEntries", count);
  }
  recordDiscarded(count) {
    this.bumpStat("discardedEntries", count);
  }
  bumpStat(key, by = 1) {
    if (key === "lastDropAt") return;
    this.counters[key] += by;
    if (key === "droppedEntries" || key === "discardedEntries") this.counters.lastDropAt = Date.now();
    this.persistStats();
  }
  loadStats() {
    try {
      const raw = JSON.parse(fs.readFileSync(path.join(this.dir, STATS_FILE), "utf8"));
      const base = emptyStats();
      for (const k of Object.keys(base)) {
        const v = raw[k];
        if (typeof v === "number") base[k] = v;
      }
      return base;
    } catch {
      return emptyStats();
    }
  }
  persistStats() {
    const full = path.join(this.dir, STATS_FILE);
    const tmp = `${full}.tmp`;
    try {
      fs.writeFileSync(tmp, JSON.stringify(this.counters), { mode: 384 });
      fs.renameSync(tmp, full);
    } catch {
    }
  }
}
function createUsageQueueSink(queue, opts) {
  return {
    emit(event) {
      try {
        if (!event || typeof event.callId !== "string" || event.callId.length === 0) return;
        queue.append({
          v: 1,
          idempotencyKey: buildMachineIdempotencyKey(opts.machineId, event.callId, event.ordinal),
          machineId: opts.machineId,
          queuedAt: Date.now(),
          event
        });
      } catch (err) {
        logger.debug(`[USAGE QUEUE] sink emit failed: ${err}`);
      }
    }
  };
}
const DEFAULT_BATCH = 500;
const DEFAULT_INTERVAL_MS = 6e4;
const DEFAULT_MIN_BACKOFF_MS = 5e3;
const DEFAULT_MAX_BACKOFF_MS = 15 * 6e4;
function toWire(entry) {
  return { idempotencyKey: entry.idempotencyKey, ...entry.event };
}
async function defaultPost(url, body, headers) {
  const res = await axios.post(url, body, {
    headers,
    timeout: 3e4,
    // We branch on the status ourselves — a 4xx must not become a throw,
    // because "permanently rejected" and "retry later" need different
    // queue handling.
    validateStatus: () => true
  });
  return { status: res.status, data: res.data };
}
class UsageQueueUploader {
  queue;
  machineId;
  url;
  getToken;
  batchSize;
  intervalMs;
  minBackoffMs;
  maxBackoffMs;
  post;
  timer = null;
  running = false;
  stopped = true;
  backoffMs;
  constructor(options) {
    this.queue = options.queue;
    this.machineId = options.machineId;
    this.url = `${options.serverUrl.replace(/\/+$/, "")}/v1/usage-events`;
    this.getToken = options.getToken;
    this.batchSize = Math.max(1, Math.min(options.batchSize ?? DEFAULT_BATCH, DEFAULT_BATCH));
    this.intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
    this.minBackoffMs = options.minBackoffMs ?? DEFAULT_MIN_BACKOFF_MS;
    this.maxBackoffMs = options.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS;
    this.post = options.post ?? defaultPost;
    this.backoffMs = this.minBackoffMs;
  }
  /** Begin the periodic drain. Idempotent. */
  start() {
    if (!this.stopped) return;
    this.stopped = false;
    this.schedule(0);
  }
  stop() {
    this.stopped = true;
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = null;
    }
  }
  schedule(delayMs) {
    if (this.stopped) return;
    if (this.timer) clearTimeout(this.timer);
    this.timer = setTimeout(() => {
      this.timer = null;
      void this.tick();
    }, delayMs);
    this.timer.unref?.();
  }
  async tick() {
    if (this.stopped) return;
    let next = this.intervalMs;
    try {
      const result = await this.drainOnce();
      if (result.backoff || result.ledgerDisabled) {
        next = this.backoffMs;
        this.backoffMs = Math.min(this.backoffMs * 2, this.maxBackoffMs);
      } else {
        this.backoffMs = this.minBackoffMs;
        next = this.queue.sealedSegments().length > 0 ? 0 : this.intervalMs;
      }
    } catch (err) {
      logger.debug(`[USAGE QUEUE] drain threw: ${err}`);
      next = this.backoffMs;
      this.backoffMs = Math.min(this.backoffMs * 2, this.maxBackoffMs);
    }
    this.schedule(next);
  }
  /** One full pass over the sealed segments. Never throws. */
  async drainOnce() {
    const out = {
      batches: 0,
      written: 0,
      duplicate: 0,
      retained: 0,
      discarded: 0,
      ledgerDisabled: false,
      backoff: false
    };
    if (this.running) return out;
    this.running = true;
    try {
      this.queue.flushSync();
      this.queue.sealActive();
      for (const segment of this.queue.sealedSegments()) {
        const entries = this.queue.readSegment(segment);
        if (entries.length === 0) {
          this.queue.removeSegment(segment);
          continue;
        }
        const retained = [];
        let aborted = false;
        for (let i = 0; i < entries.length; i += this.batchSize) {
          const chunk = entries.slice(i, i + this.batchSize);
          const verdict = await this.postBatch(chunk);
          out.batches++;
          if (verdict.kind === "retry" || verdict.kind === "disabled") {
            if (verdict.kind === "disabled") out.ledgerDisabled = true;
            else out.backoff = true;
            retained.push(...entries.slice(i));
            aborted = true;
            break;
          }
          for (const entry of chunk) {
            const r = verdict.results.get(entry.idempotencyKey);
            if (r === "written") out.written++;
            else if (r === "duplicate") out.duplicate++;
            else if (r === "invalid") out.discarded++;
            else retained.push(entry);
          }
        }
        this.queue.rewriteSegment(segment, retained);
        out.retained += retained.length;
        if (aborted) break;
      }
      if (out.written + out.duplicate > 0) this.queue.recordUploaded(out.written + out.duplicate);
      if (out.discarded > 0) this.queue.recordDiscarded(out.discarded);
      return out;
    } finally {
      this.running = false;
    }
  }
  async postBatch(chunk) {
    let status;
    let data;
    try {
      const res = await this.post(
        this.url,
        { machineId: this.machineId, events: chunk.map(toWire) },
        {
          "Content-Type": "application/json",
          Authorization: `Bearer ${this.getToken()}`
        }
      );
      status = res.status;
      data = res.data;
    } catch (err) {
      logger.debug(`[USAGE QUEUE] upload transport error: ${err}`);
      return { kind: "retry" };
    }
    if (status === 200 || status === 202) {
      const body = data ?? {};
      if (body.ledgerDisabled) return { kind: "disabled" };
      const results2 = /* @__PURE__ */ new Map();
      for (const r of body.results ?? []) {
        if (r && typeof r.key === "string") results2.set(r.key, r.result);
      }
      return { kind: "settled", results: results2 };
    }
    if (status === 401 || status === 403 || status === 408 || status === 429 || status >= 500) {
      logger.debug(`[USAGE QUEUE] upload retryable status ${status}`);
      return { kind: "retry" };
    }
    logger.debug(`[USAGE QUEUE] upload permanently rejected with status ${status}; dropping ${chunk.length} entries`);
    const results = /* @__PURE__ */ new Map();
    for (const entry of chunk) results.set(entry.idempotencyKey, "invalid");
    return { kind: "settled", results };
  }
}
let sharedQueue = null;
function getUsageQueue(options) {
  if (!sharedQueue) sharedQueue = new UsageQueue(options);
  return sharedQueue;
}

const AuthProbeRequestSchema = z.z.object({
  agents: z.z.array(AgentKindSchema).optional()
});
const execFileAsync = util.promisify(child_process.execFile);
const HEARTBEAT_INTERVAL_MS = 2e4;
const RECONNECT_WATCHDOG_MS = 3e4;
async function getAvailableMemoryBytes() {
  if (process.platform === "darwin") {
    try {
      const { stdout } = await execFileAsync("vm_stat", [], { timeout: 1e3 });
      const pageSizeMatch = stdout.match(/page size of (\d+) bytes/);
      if (!pageSizeMatch) return null;
      const pageSize = parseInt(pageSizeMatch[1], 10);
      const num = (label) => {
        const m = stdout.match(new RegExp(`Pages ${label}:\\s+(\\d+)\\.`));
        return m ? parseInt(m[1], 10) : 0;
      };
      const reclaimablePages = num("free") + num("inactive") + num("speculative") + num("purgeable");
      return reclaimablePages * pageSize;
    } catch {
      return null;
    }
  }
  if (process.platform === "linux") {
    try {
      const data = await fs$1.promises.readFile("/proc/meminfo", "utf8");
      const availMatch = data.match(/^MemAvailable:\s+(\d+)\s+kB/m);
      if (availMatch) return parseInt(availMatch[1], 10) * 1024;
      const free = data.match(/^MemFree:\s+(\d+)\s+kB/m);
      const buffers = data.match(/^Buffers:\s+(\d+)\s+kB/m);
      const cached = data.match(/^Cached:\s+(\d+)\s+kB/m);
      if (free && buffers && cached) {
        return (parseInt(free[1], 10) + parseInt(buffers[1], 10) + parseInt(cached[1], 10)) * 1024;
      }
      return null;
    } catch {
      return null;
    }
  }
  return null;
}
async function sampleCpuPercent() {
  const sample = () => {
    let idle = 0;
    let total = 0;
    for (const c of os$1.cpus()) {
      for (const t of Object.values(c.times)) total += t;
      idle += c.times.idle;
    }
    return { idle, total };
  };
  const a = sample();
  await new Promise((r) => setTimeout(r, 200));
  const b = sample();
  const idleDelta = b.idle - a.idle;
  const totalDelta = b.total - a.total;
  if (totalDelta <= 0) return 0;
  return Math.max(0, Math.min(100, Math.round((1 - idleDelta / totalDelta) * 100)));
}
async function handleFsReaddir(requestedPath) {
  const realPath = path$1.resolve(requestedPath);
  try {
    const entries = await fs$1.promises.readdir(realPath, { withFileTypes: true });
    const result = await Promise.all(entries.map(async (entry) => {
      const fullPath = path$1.join(realPath, entry.name);
      try {
        const stat = await fs$1.promises.stat(fullPath);
        return {
          name: entry.name,
          type: entry.isDirectory() ? "directory" : entry.isSymbolicLink() ? "symlink" : "file",
          size: stat.size,
          modified: stat.mtimeMs
        };
      } catch {
        return {
          name: entry.name,
          type: entry.isDirectory() ? "directory" : "file",
          size: 0,
          modified: 0
        };
      }
    }));
    return { entries: result };
  } catch (e) {
    logger.debug(`[API MACHINE] fs-readdir error for "${realPath}": ${e.message}`);
    return { entries: [], error: e.message };
  }
}
async function handleFsStat(requestedPath) {
  const realPath = path$1.resolve(requestedPath);
  try {
    const stat = await fs$1.promises.stat(realPath);
    return {
      stat: {
        size: stat.size,
        modified: stat.mtimeMs,
        isDirectory: stat.isDirectory(),
        isFile: stat.isFile(),
        permissions: (stat.mode & 511).toString(8)
      }
    };
  } catch (e) {
    logger.debug(`[API MACHINE] fs-stat error for "${realPath}": ${e.message}`);
    return { stat: null, error: e.message };
  }
}
class ApiMachineClient {
  constructor(token, machine) {
    this.token = token;
    this.machine = machine;
    this.rpcHandlerManager = new RpcHandlerManager({
      scopePrefix: this.machine.id,
      encryptionKey: this.machine.encryptionKey,
      encryptionVariant: this.machine.encryptionVariant,
      logger: (msg, data) => logger.debug(msg, data),
      // Every failed machine RPC (decryption failure or handler throw)
      // lands in crash reporting; no-op when Sentry DSN is unset.
      onError: (error, context) => captureError(error, context)
    });
    registerCommonHandlers(this.rpcHandlerManager, process.cwd());
  }
  socket;
  /**
   * Escape hatch for subsystems (e.g. harness profile-sync) that need to
   * register custom events on the machine-scoped socket. Returns the live
   * Socket.IO client; may be undefined before connect() is called.
   */
  getRawSocket() {
    return this.socket;
  }
  /** Initialised async in connect(); guarded by the null check in handlers */
  terminalManager = null;
  keepAliveInterval = null;
  rpcHandlerManager;
  /**
   * Single shared device-code flow runner per daemon process. Wave 2
   * of agent-auth-foundation. Handles `claude login` / `gcloud auth
   * login --no-browser` and any future RFC 8628 device-code flow.
   */
  deviceCodeFlow = new DeviceCodeFlow();
  pasteFlow = new PasteFlow({ invalidateProbeCache: () => invalidateProbeCache() });
  /** Claude subscription paste-code (OOB OAuth) flow. */
  claudePasteCodeFlow = new ClaudePasteCodeFlow();
  /**
   * Drives an agent's OWN `login` command (see auth/flows/cliLogin.ts). This
   * is preferred over every daemon-implemented OAuth path when the binary is
   * installed, because the CLI holds its own PKCE verifier, talks to its own
   * (current) endpoints, and writes its own credential file.
   */
  cliLoginFlow = new CliLoginFlow();
  /**
   * Single shared agent-CLI install runner (start → poll → cancel). Gated
   * daemon-side by CONSORTIUM_AGENT_INSTALL=0. See cli-install/installFlow.ts.
   */
  installFlow = new InstallFlow();
  hasConnectedOnce = false;
  consecutiveAuthErrors = 0;
  disconnectedAt = null;
  reconnectTimer = null;
  manualReconnectAttempts = 0;
  destroyed = false;
  /**
   * L3.3 — drains the durable machine-origin UsageEvent queue to
   * `POST /v1/usage-events`. The daemon owns it because the daemon owns the
   * server credential; the per-session LMP only ever writes to the queue.
   * Created lazily on first successful connect (see `startUsageUploader`).
   */
  usageUploader = null;
  setRPCHandlers({
    spawnSession,
    stopSession,
    reprovisionSession,
    requestShutdown
  }) {
    this.rpcHandlerManager.registerHandler("spawn-consortium-session", async (params) => {
      const { directory, sessionId, machineId, approvedNewDirectoryCreation, agent, token, environmentVariables, resume, resumeSessionId, profileKeyMode, pmMode, worktreeMode, cardId, cardSlug, projectId, repoBaseBranch, authSource, authIdentity, providerId, modelId, byokEnvVar, byokBaseURL, accountKeyId, _reqId } = params || {};
      logger.debug(`[API MACHINE] Spawning session with params: ${JSON.stringify(params)}`);
      if (!directory) {
        throw new Error("Directory is required");
      }
      const result = await spawnSession({ directory, sessionId, machineId, approvedNewDirectoryCreation, agent, token, environmentVariables, resume, resumeSessionId, profileKeyMode, pmMode, worktreeMode, cardId, cardSlug, projectId, repoBaseBranch, authSource, authIdentity, providerId, modelId, byokEnvVar, byokBaseURL, accountKeyId, requestId: typeof _reqId === "string" ? _reqId : void 0 });
      switch (result.type) {
        case "success":
          logger.debug(`[API MACHINE] Spawned session ${result.sessionId}`);
          return { type: "success", sessionId: result.sessionId };
        case "requestToApproveDirectoryCreation":
          logger.debug(`[API MACHINE] Requesting directory creation approval for: ${result.directory}`);
          return { type: "requestToApproveDirectoryCreation", directory: result.directory };
        case "error":
          throw new Error(result.errorMessage);
        case "mint_failed":
          logger.debug(`[API MACHINE] Spawn refused: mint_failed scope=${result.scope} code=${result.errorCode}`);
          return {
            type: "mint_failed",
            errorCode: result.errorCode,
            message: result.message,
            scope: result.scope
          };
      }
    });
    this.rpcHandlerManager.registerHandler("stop-session", (params) => {
      const { sessionId } = params || {};
      if (!sessionId) {
        throw new Error("Session ID is required");
      }
      const success = stopSession(sessionId);
      if (!success) {
        throw new Error("Session not found or failed to stop");
      }
      logger.debug(`[API MACHINE] Stopped session ${sessionId}`);
      return { message: "Session stopped" };
    });
    this.rpcHandlerManager.registerHandler("reprovision-session", async (params) => {
      const sessionId = typeof params?.sessionId === "string" ? params.sessionId : void 0;
      if (!sessionId) {
        throw new Error("Session ID is required");
      }
      const resume = params?.resume && typeof params.resume === "object" ? {
        sessionId: typeof params.resume.sessionId === "string" ? params.resume.sessionId : void 0,
        rolloutPath: typeof params.resume.rolloutPath === "string" ? params.resume.rolloutPath : void 0
      } : void 0;
      const result = await reprovisionSession(sessionId, resume);
      switch (result.type) {
        case "success":
          logger.debug(`[API MACHINE] Reprovisioned session ${sessionId} -> ${result.sessionId}`);
          return { type: "success", sessionId: result.sessionId };
        case "error":
          throw new Error(result.errorMessage);
        case "requestToApproveDirectoryCreation":
          throw new Error(`Reprovision failed: working directory '${result.directory}' is unexpectedly missing`);
        case "mint_failed":
          logger.debug(`[API MACHINE] Reprovision refused: mint_failed scope=${result.scope} code=${result.errorCode}`);
          return {
            type: "mint_failed",
            errorCode: result.errorCode,
            message: result.message,
            scope: result.scope
          };
      }
    });
    this.rpcHandlerManager.registerHandler("stop-daemon", () => {
      logger.debug("[API MACHINE] Received stop-daemon RPC request");
      setTimeout(() => {
        logger.debug("[API MACHINE] Initiating daemon shutdown from RPC");
        requestShutdown();
      }, 100);
      return { message: "Daemon stop request acknowledged, starting shutdown sequence..." };
    });
    this.rpcHandlerManager.registerHandler("auth-probe", async (params) => {
      const parsed = AuthProbeRequestSchema.safeParse(params ?? {});
      if (!parsed.success) {
        throw new Error(`Invalid auth-probe request: ${parsed.error.message}`);
      }
      const requested = parsed.data.agents;
      const result = await runAllProbes({ agents: requested });
      const states = requested ? result.states.filter((s) => requested.includes(s.agent)) : result.states;
      logger.debug(`[API MACHINE] auth-probe completed: ${states.map((s) => `${s.agent}=${s.status}`).join(", ")}`);
      return { states, probedAt: result.probedAt };
    });
    this.rpcHandlerManager.registerHandler("auth-capabilities", async () => {
      return {
        capabilities: connectableAgents(),
        // Included so the app can show "verified against codex 0.144.4"
        // and so a stale daemon is visible rather than silent.
        all: AGENT_AUTH_CAPABILITIES,
        cliVersion: configuration.currentCliVersion
      };
    });
    this.rpcHandlerManager.registerHandler("account-usage", async (params) => {
      const requested = typeof params?.accountKeyId === "string" ? params.accountKeyId : void 0;
      const providerId = typeof params?.providerId === "string" ? params.providerId : void 0;
      let snapshots = listAccountUsage();
      if (requested) snapshots = snapshots.filter((s) => s.accountKeyId === requested);
      if (providerId) snapshots = snapshots.filter((s) => s.providerId === providerId);
      return { ok: true, snapshots, fetchedAt: Date.now() };
    });
    this.rpcHandlerManager.registerHandler("get-util", async () => {
      const cpuCount = os$1.cpus().length;
      const loadOneMin = os$1.loadavg()[0];
      const memTotalBytes = os$1.totalmem();
      const cpuPercent = process.platform === "win32" ? await sampleCpuPercent() : loadOneMin > 0 ? Math.min(100, Math.round(loadOneMin / cpuCount * 100)) : null;
      const availableBytes = await getAvailableMemoryBytes() ?? os$1.freemem();
      return {
        cpuPercent,
        memoryUsedMb: Math.round((memTotalBytes - availableBytes) / 1024 / 1024),
        memoryTotalMb: Math.round(memTotalBytes / 1024 / 1024)
      };
    });
    this.rpcHandlerManager.registerHandler("get-hardware", async (params) => {
      return await detectHardware({ force: !!params?.force });
    });
    this.rpcHandlerManager.registerHandler("auth-flow-start", async (params) => {
      const agent = AgentKindSchema.parse(params?.agent);
      const mode = AuthFlowKindSchema.parse(params?.mode);
      return await startAuthFlow(agent, mode, {
        deviceCode: this.deviceCodeFlow,
        paste: this.pasteFlow,
        claudePasteCode: this.claudePasteCodeFlow,
        cliLogin: {
          supports: (a) => CliLoginFlow.supports(a),
          start: (a) => this.cliLoginFlow.start(a)
        },
        pickDeviceCodeAdapter,
        log: (m) => logger.debug(`[API MACHINE] ${m}`)
      });
    });
    this.rpcHandlerManager.registerHandler("auth-flow-poll", async (params) => {
      const handleId = String(params?.handleId ?? "");
      if (!handleId) throw new Error("auth-flow-poll: handleId required");
      const cliLoginStatus = this.cliLoginFlow.poll(handleId);
      if (cliLoginStatus) return cliLoginStatus;
      const pasteCodeStatus = this.claudePasteCodeFlow.poll(handleId);
      if (pasteCodeStatus) return pasteCodeStatus;
      const dcStatus = await this.deviceCodeFlow.getStatus(handleId);
      if (dcStatus) return dcStatus;
      const pasteStatus = this.pasteFlow.poll(handleId);
      if (pasteStatus) return pasteStatus;
      throw new Error(`auth-flow-poll: unknown handleId ${handleId}`);
    });
    this.rpcHandlerManager.registerHandler("auth-flow-submit-paste", async (params) => {
      const handleId = String(params?.handleId ?? "");
      const key = String(params?.key ?? "");
      if (!handleId) throw new Error("auth-flow-submit-paste: handleId required");
      if (!key) throw new Error("auth-flow-submit-paste: key required");
      if (this.cliLoginFlow.poll(handleId)) {
        return await this.cliLoginFlow.submit(handleId, key);
      }
      if (this.claudePasteCodeFlow.poll(handleId)) {
        return await this.claudePasteCodeFlow.submit(handleId, key);
      }
      return await this.pasteFlow.submit(handleId, key);
    });
    this.rpcHandlerManager.registerHandler("auth-flow-cancel", async (params) => {
      const handleId = String(params?.handleId ?? "");
      if (!handleId) throw new Error("auth-flow-cancel: handleId required");
      if (this.cliLoginFlow.poll(handleId)) {
        await this.cliLoginFlow.cancel(handleId);
        return { ok: true };
      }
      if (this.claudePasteCodeFlow.poll(handleId)) {
        await this.claudePasteCodeFlow.cancel(handleId);
        return { ok: true };
      }
      const dcStatus = await this.deviceCodeFlow.getStatus(handleId);
      if (dcStatus) {
        await this.deviceCodeFlow.cancel(handleId);
        return { ok: true };
      }
      const pasteStatus = this.pasteFlow.poll(handleId);
      if (pasteStatus) {
        await this.pasteFlow.cancel(handleId);
        return { ok: true };
      }
      throw new Error(`auth-flow-cancel: unknown handleId ${handleId}`);
    });
    this.rpcHandlerManager.registerHandler("auth-clear-creds", async (params) => {
      const agent = AgentKindSchema.parse(params?.agent);
      logger.debug(`[API MACHINE] auth-clear-creds for agent=${agent}`);
      const result = clearAgentCredentials(agent);
      invalidateProbeCache();
      return { ok: result.ok, cleared: result.cleared, skipped: result.skipped };
    });
    registerCliInstallHandlers(this.rpcHandlerManager, this.installFlow);
  }
  /**
   * Update machine metadata
   * Currently unused, changes from the mobile client are more likely
   * for example to set a custom name.
   */
  async updateMachineMetadata(handler) {
    await backoff(async () => {
      const updated = handler(this.machine.metadata);
      const answer = await this.socket.emitWithAck("machine-update-metadata", {
        machineId: this.machine.id,
        metadata: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
        expectedVersion: this.machine.metadataVersion
      });
      if (answer.result === "success") {
        this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
        this.machine.metadataVersion = answer.version;
        logger.debug("[API MACHINE] Metadata updated successfully");
      } else if (answer.result === "version-mismatch") {
        if (answer.version > this.machine.metadataVersion) {
          this.machine.metadataVersion = answer.version;
          this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
        }
        throw new Error("Metadata version mismatch");
      }
    });
  }
  /**
   * Update daemon state (runtime info) - similar to session updateAgentState
   * Simplified without lock - relies on backoff for retry
   */
  async updateDaemonState(handler) {
    await backoff(async () => {
      const updated = handler(this.machine.daemonState);
      const answer = await this.socket.emitWithAck("machine-update-state", {
        machineId: this.machine.id,
        daemonState: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
        expectedVersion: this.machine.daemonStateVersion
      });
      if (answer.result === "success") {
        this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
        this.machine.daemonStateVersion = answer.version;
        logger.debug("[API MACHINE] Daemon state updated successfully");
      } else if (answer.result === "version-mismatch") {
        if (answer.version > this.machine.daemonStateVersion) {
          this.machine.daemonStateVersion = answer.version;
          this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
        }
        throw new Error("Daemon state version mismatch");
      }
    });
  }
  connect(options) {
    if (this.socket) {
      logger.debug("[API MACHINE] connect() called with a socket already present \u2014 detaching the previous one");
      this.socket.removeAllListeners();
      this.socket.io.removeAllListeners();
      this.socket.close();
    }
    const serverUrl = configuration.serverUrl.replace(/^http/, "ws");
    logger.debug(`[API MACHINE] Connecting to ${serverUrl}`);
    this.socket = socket_ioClient.io(serverUrl, {
      transports: ["websocket"],
      auth: {
        token: this.token,
        clientType: "machine-scoped",
        machineId: this.machine.id
      },
      path: "/v1/updates",
      reconnection: true,
      reconnectionDelay: 1e3,
      reconnectionDelayMax: 1e4,
      reconnectionAttempts: Infinity,
      ackTimeout: 3e4
    });
    if (!this.terminalManager) {
      getTerminalManager().then((mgr) => {
        this.terminalManager = mgr;
      }).catch((err) => {
        logger.debug(`[API MACHINE] TerminalManager init failed: ${err}`);
      });
    }
    this.socket.on("connect", async () => {
      this.stopKeepAlive();
      const isReconnect = this.hasConnectedOnce;
      this.hasConnectedOnce = true;
      this.consecutiveAuthErrors = 0;
      this.manualReconnectAttempts = 0;
      if (this.reconnectTimer) {
        clearTimeout(this.reconnectTimer);
        this.reconnectTimer = null;
      }
      if (isReconnect) {
        const disconnectDuration = this.disconnectedAt ? Date.now() - this.disconnectedAt : Infinity;
        this.disconnectedAt = null;
        logger.debug(`[API MACHINE] Reconnected to server (was disconnected for ${Math.round(disconnectDuration / 1e3)}s)`);
        if (disconnectDuration > 18e4) {
          this.terminalManager?.closeAllPty();
          logger.debug("[API MACHINE] Closed orphan PTY sessions after extended disconnect");
        }
        if (options?.onReconnect) {
          try {
            const freshMachine = await options.onReconnect();
            this.machine.metadata = freshMachine.metadata;
            this.machine.metadataVersion = freshMachine.metadataVersion;
            this.machine.daemonState = freshMachine.daemonState;
            this.machine.daemonStateVersion = freshMachine.daemonStateVersion;
            logger.debug("[API MACHINE] Re-registered machine after reconnect");
          } catch (err) {
            logger.debug(`[API MACHINE] Re-registration failed, resetting version numbers to avoid permanent mismatch loop: ${err}`);
            this.machine.metadataVersion = 0;
            this.machine.daemonStateVersion = 0;
          }
        }
      } else {
        logger.debug("[API MACHINE] Connected to server");
      }
      if (options?.organizationId) {
        this.socket.emit("org:join", { organizationId: options.organizationId });
        logger.debug(`[API MACHINE] Joined org room: ${options.organizationId}`);
      }
      this.updateDaemonState((state) => ({
        ...state,
        status: "running",
        pid: process.pid,
        httpPort: this.machine.daemonState?.httpPort,
        startedAt: Date.now()
      })).catch((err) => {
        logger.debug(`[API MACHINE] Failed to update daemon state to running: ${err}`);
      });
      await this.rpcHandlerManager.onSocketConnect(this.socket);
      if (!this.socket.connected) return;
      this.socket.emit("rpc-ready");
      if (this.socket.connected) {
        this.startKeepAlive();
      }
      this.startUsageUploader();
      if (this.terminalManager && this.socket.connected) {
        this.terminalManager.listTerminals().then((terminals) => {
          if (this.socket.connected) {
            this.socket.emit("terminal:list", {
              machineId: this.machine.id,
              terminals
            });
          }
        }).catch(() => {
        });
      }
    });
    this.socket.on("disconnect", (reason) => {
      logger.debug(`[API MACHINE] Disconnected from server (reason: ${reason})`);
      this.disconnectedAt = Date.now();
      this.rpcHandlerManager.onSocketDisconnect();
      this.stopKeepAlive();
      this.scheduleRecoveryFromDisconnect(options, reason);
    });
    this.socket.on("server-shutting-down", (data) => {
      logger.debug(`[API MACHINE] Server shutting down (reason: ${data.reason}), will reconnect automatically`);
      this.socket.io.engine?.close();
    });
    this.socket.on("rpc-request", async (data, callback) => {
      logger.debugLargeJson(`[API MACHINE] Received RPC request:`, data);
      callback(await this.rpcHandlerManager.handleRequest(data));
    });
    this.socket.on("update", (data) => {
      if (data.body.t === "update-machine" && data.body.machineId === this.machine.id) {
        const update = data.body;
        if (update.metadata) {
          logger.debug("[API MACHINE] Received external metadata update");
          this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(update.metadata.value));
          this.machine.metadataVersion = update.metadata.version;
        }
        if (update.daemonState) {
          logger.debug("[API MACHINE] Received external daemon state update");
          this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(update.daemonState.value));
          this.machine.daemonStateVersion = update.daemonState.version;
        }
      } else {
        logger.debug(`[API MACHINE] Received unknown update type: ${data.body.t}`);
      }
    });
    this.socket.on("ephemeral", async (data) => {
      try {
        if (data && data.type === "automation-run") {
          if (this.rpcHandlerManager.hasHandler("automation-run")) {
            logger.debug(`[API MACHINE] automation-run ephemeral \u2192 local handler (run ${data.runId ?? "?"})`);
            await this.rpcHandlerManager.callLocal("automation-run", data);
          } else {
            logger.debug("[API MACHINE] automation-run ephemeral: no local handler registered");
          }
          return;
        }
        if (!data || data.type !== "harness-rpc") return;
        const command = String(data.command || "");
        const harnessType = String(data.harnessType || "");
        if (!command || !harnessType) return;
        const method = `harness-${command}`;
        if (!this.rpcHandlerManager.hasHandler(method)) {
          logger.debug(`[API MACHINE] harness-rpc: no local handler for ${method}`);
          return;
        }
        const { type: _type, command: _command, ...forward } = data;
        logger.debug(`[API MACHINE] harness-rpc \u2192 ${method} (${harnessType}${data.vpsInstanceId ? `, vps=${data.vpsInstanceId}` : ""})`);
        await this.rpcHandlerManager.callLocal(method, forward);
      } catch (e) {
        logger.debug(`[API MACHINE] harness-rpc dispatch failed: ${e.message}`);
      }
    });
    const viewerToPersistent = /* @__PURE__ */ new Map();
    const resolveTerminal = (terminalId) => viewerToPersistent.get(terminalId) ?? terminalId;
    this.socket.on("pty:open", async (data, callback) => {
      const { terminalId, cols, rows, persistentTerminalId } = data;
      if (!terminalId || !cols || !rows) {
        callback({ ok: false, error: "Missing required parameters" });
        return;
      }
      const mgr = this.terminalManager;
      const emitData = (outputData) => {
        const encrypted = encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, outputData.toString("base64")));
        this.socket.emit("pty:data", {
          machineId: this.machine.id,
          terminalId,
          data: encrypted
        });
      };
      const emitExit = () => {
        this.socket.emit("pty:exit", {
          machineId: this.machine.id,
          terminalId
        });
      };
      if (persistentTerminalId && mgr) {
        const attachResult = await mgr.attachTerminal(
          persistentTerminalId,
          terminalId,
          // use the pty-scoped terminalId as the viewerId
          Number(cols),
          Number(rows),
          emitData,
          () => emitExit()
        );
        if (attachResult.ok) {
          viewerToPersistent.set(terminalId, persistentTerminalId);
          if (attachResult.replay && attachResult.replay.length > 0) {
            emitData(attachResult.replay);
          }
          callback({ ok: true });
          return;
        }
        logger.debug(`[API MACHINE] Persistent attach failed for ${persistentTerminalId}: ${attachResult.error}`);
        callback({ ok: false, error: attachResult.error ?? "Persistent terminal no longer exists" });
        return;
      }
      const result = mgr ? mgr.openPty(terminalId, Number(cols), Number(rows), emitData, emitExit) : { ok: false, error: "TerminalManager not ready" };
      callback(result);
    });
    this.socket.on("pty:data", (data) => {
      const { terminalId, data: encryptedData } = data;
      if (!terminalId || !encryptedData) return;
      const decrypted = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(encryptedData));
      if (!decrypted || !this.terminalManager) return;
      const buf = Buffer.from(decrypted, "base64");
      if (!this.terminalManager.writeTerminal(resolveTerminal(terminalId), buf)) {
        this.terminalManager.writePty(terminalId, buf);
      }
    });
    this.socket.on("pty:resize", (data) => {
      const { terminalId, cols, rows } = data;
      if (!terminalId || !cols || !rows || !this.terminalManager) return;
      const c = Number(cols);
      const r = Number(rows);
      if (!this.terminalManager.resizeTerminal(resolveTerminal(terminalId), terminalId, c, r)) {
        this.terminalManager.resizePty(terminalId, c, r);
      }
    });
    this.socket.on("pty:close", (data) => {
      const { terminalId } = data;
      if (!terminalId || !this.terminalManager) return;
      this.terminalManager.detachTerminal(resolveTerminal(terminalId), terminalId);
      viewerToPersistent.delete(terminalId);
      this.terminalManager.closePty(terminalId);
    });
    this.socket.on("terminal:create", async (data, callback) => {
      const mgr = this.terminalManager;
      const requestedMode = data.persistenceMode ?? "tmux";
      if (!mgr) {
        callback({ ok: false, mode: requestedMode, error: "TerminalManager not ready" });
        return;
      }
      const caps = mgr.getCapabilities();
      let mode = requestedMode;
      if (mode === "ephemeral" || mode === "tmux" && !caps.tmux || mode === "zellij" && !caps.zellij) {
        mode = mgr.defaultMode();
      }
      if (mode === "ephemeral") {
        callback({ ok: false, mode, error: "No persistent terminal backend available on this machine" });
        return;
      }
      try {
        const result = await mgr.createTerminal(mode, {
          id: data.terminalId,
          cols: Number(data.cols ?? 80),
          rows: Number(data.rows ?? 24),
          cwd: data.cwd,
          shell: data.shell
        });
        callback(result);
      } catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        callback({ ok: false, mode, error: message });
      }
    });
    this.socket.on("terminal:list", async (_data, callback) => {
      const mgr = this.terminalManager;
      if (!mgr) {
        callback({ ok: true, terminals: [] });
        return;
      }
      const terminals = await mgr.listTerminals();
      callback({ ok: true, terminals });
    });
    this.socket.on("terminal:destroy", async (data, callback) => {
      const mgr = this.terminalManager;
      if (!mgr) {
        callback({ ok: false, error: "TerminalManager not ready" });
        return;
      }
      await mgr.destroyTerminal(data.terminalId);
      callback({ ok: true });
    });
    this.socket.on("machine:fs-readdir", async (data) => {
      const result = await handleFsReaddir(data.path);
      this.socket.emit("machine:fs-readdir-result", {
        requestId: data.requestId,
        payload: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, result))
      });
    });
    this.socket.on("machine:fs-stat", async (data) => {
      const result = await handleFsStat(data.path);
      this.socket.emit("machine:fs-stat-result", {
        requestId: data.requestId,
        payload: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, result))
      });
    });
    this.socket.on("connect_error", (error) => {
      const isAuthError = error.message.includes("authentication") || error.message.includes("Invalid") || error.message.includes("suspended");
      if (isAuthError) {
        this.consecutiveAuthErrors++;
        logger.debug(`[API MACHINE] Auth error #${this.consecutiveAuthErrors}: ${error.message}`);
        if (this.consecutiveAuthErrors >= 5) {
          logger.debug(`[API MACHINE] Authentication permanently failed after ${this.consecutiveAuthErrors} attempts. Shutting down.`);
          this.socket.disconnect();
          if (options?.onAuthFailure) {
            options.onAuthFailure();
          }
          return;
        }
      }
      logger.debug(`[API MACHINE] Connection error: ${error.message}`);
      this.scheduleManualReconnect(options, isAuthError);
    });
    this.socket.io.on("error", (error) => {
      logger.debug("[API MACHINE] Socket error:", error);
    });
  }
  /**
   * Recovery for a socket that was ESTABLISHED and then dropped.
   *
   * `scheduleManualReconnect` covers handshake failures (`connect_error`).
   * Nothing covered a *post-connect* drop: the disconnect handler only
   * stopped the keep-alive and trusted `reconnection: true` to bring the
   * socket back. That trust is misplaced for two of Socket.IO v4's
   * disconnect reasons, and the gap is a real outage — on 2026-08-16
   * Station 1 logged `Disconnected from server` and then went silent for
   * hours: process alive, log still writing, zero sockets open, machine
   * reading offline. `Restart=always` cannot help because nothing exits.
   *
   * Reasons and who owns recovery:
   *   `io client disconnect` — we called disconnect(). Intentional; do
   *                            nothing, or shutdown() would reopen itself.
   *   `io server disconnect` — the server called socket.disconnect(). The
   *                            Manager treats this as terminal and will
   *                            NOT retry. We must reconnect ourselves.
   *   everything else        — transport close/error, ping timeout, parse
   *                            error. The Manager does retry these, so
   *                            arm a watchdog instead of racing it, and
   *                            take over only if it has not recovered.
   *
   * The watchdog shares `reconnectTimer` with the manual scheduler on
   * purpose: a successful `connect` clears that timer, so recovering by
   * either route disarms the other automatically.
   */
  scheduleRecoveryFromDisconnect(options, reason) {
    if (this.destroyed) return;
    if (reason === "io client disconnect") return;
    if (reason === "io server disconnect") {
      logger.debug("[API MACHINE] Server closed the socket; Socket.IO will not retry it \u2014 reconnecting manually");
      this.scheduleManualReconnect(options, false);
      return;
    }
    if (this.reconnectTimer) return;
    this.reconnectTimer = setTimeout(() => {
      this.reconnectTimer = null;
      if (this.destroyed || this.socket.connected) return;
      logger.debug(`[API MACHINE] Still disconnected ${RECONNECT_WATCHDOG_MS / 1e3}s after "${reason}" \u2014 taking over from Socket.IO's reconnection`);
      this.scheduleManualReconnect(options, false);
    }, RECONNECT_WATCHDOG_MS);
  }
  /**
   * Schedule a manual reconnect with exponential backoff + jitter. Called
   * from the connect_error handler because Socket.IO v4 does not retry
   * middleware-rejected handshakes on its own (see comment in the handler).
   *
   * If a refreshToken callback is provided and the rejection looked like
   * an auth error, the new token is swapped into socket.auth before the
   * next attempt. Backoff: 1s, 2s, 4s, 8s, 10s (cap), with up to 1s of
   * random jitter on top. The 10s cap matches socket.io's
   * reconnectionDelayMax and keeps the worst-case heartbeat gap under the
   * app's 60s offline threshold so a reconnecting daemon never reads as
   * offline. Reset to 0 on successful connect.
   */
  scheduleManualReconnect(options, refreshIfPossible) {
    if (this.destroyed) return;
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    this.manualReconnectAttempts++;
    const delay = Math.min(1e3 * Math.pow(2, this.manualReconnectAttempts - 1), 1e4) + Math.floor(Math.random() * 1e3);
    logger.debug(`[API MACHINE] Scheduling manual reconnect attempt #${this.manualReconnectAttempts} in ${delay}ms (refresh=${refreshIfPossible && !!options?.refreshToken})`);
    this.reconnectTimer = setTimeout(async () => {
      this.reconnectTimer = null;
      if (this.destroyed) return;
      if (this.socket.connected) return;
      if (refreshIfPossible && options?.refreshToken) {
        try {
          const fresh = await options.refreshToken();
          if (fresh && fresh !== this.token) {
            this.token = fresh;
            this.socket.auth.token = fresh;
            logger.debug("[API MACHINE] Refreshed auth token before manual reconnect");
          } else {
            logger.debug("[API MACHINE] Token refresh returned no new token; will retry with existing token");
          }
        } catch (err) {
          logger.debug(`[API MACHINE] Token refresh threw: ${err}`);
        }
      }
      logger.debug(`[API MACHINE] Manual reconnect attempt #${this.manualReconnectAttempts} firing`);
      this.socket.connect();
    }, delay);
  }
  /**
   * L3.3 — start the durable usage-queue drain.
   *
   * Idempotent: the uploader is created once per client and `start()` is a
   * no-op while it is already running, so reconnect storms cannot stack
   * timers. `getToken` is read lazily on every batch so a token refreshed by
   * `scheduleManualReconnect` is picked up without recreating anything.
   *
   * Never fatal: a queue that cannot be opened (read-only home dir) must not
   * stop the daemon from serving sessions — metering is telemetry, not the
   * product.
   */
  startUsageUploader() {
    if (this.destroyed) return;
    try {
      if (!this.usageUploader) {
        this.usageUploader = new UsageQueueUploader({
          queue: getUsageQueue(),
          machineId: this.machine.id,
          serverUrl: configuration.serverUrl,
          getToken: () => this.token
        });
      }
      this.usageUploader.start();
    } catch (err) {
      logger.debug(`[API MACHINE] usage-event uploader unavailable: ${err}`);
    }
  }
  startKeepAlive() {
    this.stopKeepAlive();
    const sendHeartbeat = () => {
      const payload = {
        machineId: this.machine.id,
        time: Date.now()
      };
      if (process.env.DEBUG) {
        logger.debugLargeJson(`[API MACHINE] Emitting machine-alive`, payload);
      }
      this.socket.emit("machine-alive", payload);
    };
    sendHeartbeat();
    this.keepAliveInterval = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL_MS);
    logger.debug(`[API MACHINE] Keep-alive started (immediate + ${HEARTBEAT_INTERVAL_MS / 1e3}s interval)`);
  }
  stopKeepAlive() {
    if (this.keepAliveInterval) {
      clearInterval(this.keepAliveInterval);
      this.keepAliveInterval = null;
      logger.debug("[API MACHINE] Keep-alive stopped");
    }
  }
  /**
   * Emit an encrypted OpenClaw activity event to be relayed to the mobile app.
   * The encryptedPayload should already be base64(encrypted(event data)).
   */
  emitOpenClawActivity(eventType, agentId, encryptedPayload) {
    if (!this.socket?.connected) {
      logger.debug("[API MACHINE] Cannot emit openclaw:activity - socket not connected");
      return;
    }
    this.socket.emit("openclaw:activity", {
      machineId: this.machine.id,
      eventType,
      agentId,
      encryptedPayload,
      timestamp: Date.now()
    });
  }
  /**
   * Emit one streaming agent-chat frame. The payload is encrypted here
   * with the machine key so the server relays ciphertext only; the app
   * decrypts with the same machine key it already holds.
   */
  emitAgentChatFrame(frame) {
    if (!this.socket?.connected) {
      logger.debug("[API MACHINE] Cannot emit harness:agent-chat-frame - socket not connected");
      return;
    }
    this.socket.emit("harness:agent-chat-frame", {
      machineId: this.machine.id,
      requestId: frame.requestId,
      agentId: frame.agentId,
      seq: frame.seq,
      encryptedPayload: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, frame.payload)),
      done: frame.done,
      timestamp: Date.now()
    });
  }
  /**
   * Emit an OpenClaw status update (unencrypted, for quick display).
   */
  emitOpenClawStatus(status, version, gatewayPort, channels, agentCount) {
    if (!this.socket?.connected) {
      logger.debug("[API MACHINE] Cannot emit openclaw:status - socket not connected");
      return;
    }
    this.socket.emit("openclaw:status", {
      machineId: this.machine.id,
      status,
      version,
      gatewayPort,
      channels,
      agentCount,
      timestamp: Date.now()
    });
  }
  /**
   * Emit a deployment health status event to the server for mobile app display.
   *
   * Accepts the full set of deployment lifecycle statuses (health, container,
   * deploy/stop/remove) plus optional metadata fields used by the deployment
   * RPC handlers in daemon/run.ts.
   */
  emitDeploymentStatus(data) {
    if (!this.socket?.connected) {
      logger.debug("[API MACHINE] Cannot emit deployment-status - socket not connected");
      return;
    }
    this.socket.emit("deployment-status", { timestamp: Date.now(), ...data });
  }
  /** Exposes the RPC handler manager for registering additional handlers (e.g. OpenClaw) */
  get rpcHandlers() {
    return this.rpcHandlerManager;
  }
  /** Exposes the machine's encryption key for the bridge to encrypt activity payloads */
  get encryptionKey() {
    return this.machine.encryptionKey;
  }
  /** Exposes the encryption variant */
  get encryptionVariant() {
    return this.machine.encryptionVariant;
  }
  shutdown() {
    logger.debug("[API MACHINE] Shutting down");
    this.destroyed = true;
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    this.terminalManager?.closeAllPty();
    this.stopKeepAlive();
    this.usageUploader?.stop();
    try {
      getUsageQueue().close();
    } catch {
    }
    if (this.socket) {
      this.socket.close();
      logger.debug("[API MACHINE] Socket closed");
    }
    this.deviceCodeFlow.dispose();
    this.claudePasteCodeFlow.dispose();
    this.cliLoginFlow.dispose();
    this.installFlow.dispose();
  }
}
function pickDeviceCodeAdapter(agent) {
  const cap = getAgentAuthCapability(agent);
  if (cap?.remoteMode === "device-code") {
    return new NativeDeviceAuthAdapter(agent);
  }
  const alternative = cap?.remoteMode ?? cap?.localMode;
  throw new Error(
    `auth-flow: ${agent} does not support device-code` + (alternative ? ` \u2014 use mode '${alternative}'` : " \u2014 no subscription login exists for this agent; use an API key")
  );
}

const SCHEMA_VERSION = 1;
function storePath() {
  return `${configuration.consortiumHomeDir}/session-keys.json`;
}
function lockPath() {
  return `${storePath()}.lock`;
}
async function readStoreRaw() {
  const path = storePath();
  if (!fs.existsSync(path)) {
    return { schemaVersion: SCHEMA_VERSION, keys: {} };
  }
  try {
    const content = await fs$3.readFile(path, "utf8");
    const parsed = JSON.parse(content);
    const keys = parsed.keys && typeof parsed.keys === "object" ? parsed.keys : {};
    return {
      ...parsed,
      schemaVersion: parsed.schemaVersion ?? SCHEMA_VERSION,
      keys
    };
  } catch (err) {
    logger.warn("[sessionKeyStore] failed to read store, treating as empty", err);
    return { schemaVersion: SCHEMA_VERSION, keys: {} };
  }
}
async function withLock(fn) {
  const LOCK_RETRY_INTERVAL_MS = 100;
  const MAX_LOCK_ATTEMPTS = 50;
  const STALE_LOCK_TIMEOUT_MS = 1e4;
  await fs$3.mkdir(configuration.consortiumHomeDir, { recursive: true, mode: 448 });
  let handle;
  for (let attempts = 0; attempts < MAX_LOCK_ATTEMPTS; attempts++) {
    try {
      handle = await fs$3.open(lockPath(), fs$3.constants.O_CREAT | fs$3.constants.O_EXCL | fs$3.constants.O_WRONLY);
      break;
    } catch (err) {
      const code = err?.code;
      if (code !== "EEXIST") throw err;
      try {
        const stat = await (await fs$3.open(lockPath(), fs$3.constants.O_RDONLY)).stat();
        if (Date.now() - stat.mtimeMs > STALE_LOCK_TIMEOUT_MS) {
          await fs$3.unlink(lockPath()).catch(() => {
          });
          continue;
        }
      } catch {
      }
      await new Promise((r) => setTimeout(r, LOCK_RETRY_INTERVAL_MS));
    }
  }
  if (!handle) {
    throw new Error("[sessionKeyStore] could not acquire lock after 5s");
  }
  try {
    return await fn();
  } finally {
    try {
      await handle.close();
    } catch {
    }
    try {
      await fs$3.unlink(lockPath());
    } catch {
    }
  }
}
async function saveSessionKey(sessionId, encryptionKey, encryptionVariant) {
  await withLock(async () => {
    const store = await readStoreRaw();
    store.keys[sessionId] = {
      encryptionKey: encodeBase64(encryptionKey),
      encryptionVariant,
      createdAt: Date.now()
    };
    store.schemaVersion = SCHEMA_VERSION;
    const tmp = `${storePath()}.tmp`;
    await fs$3.writeFile(tmp, JSON.stringify(store, null, 2), { mode: 384 });
    await fs$3.rename(tmp, storePath());
  });
}

class PushNotificationClient {
  token;
  baseUrl;
  expo;
  constructor(token, baseUrl = "https://api.consortium.dev") {
    this.token = token;
    this.baseUrl = baseUrl;
    this.expo = new expoServerSdk.Expo();
  }
  /**
   * Fetch all push tokens for the authenticated user
   */
  async fetchPushTokens() {
    try {
      const response = await axios.get(
        `${this.baseUrl}/v1/push-tokens`,
        {
          headers: {
            "Authorization": `Bearer ${this.token}`,
            "Content-Type": "application/json"
          }
        }
      );
      logger.debug(`Fetched ${response.data.tokens.length} push tokens`);
      response.data.tokens.forEach((token, index) => {
        logger.debug(`[PUSH] Token ${index + 1}: id=${token.id}, created=${new Date(token.createdAt).toISOString()}, updated=${new Date(token.updatedAt).toISOString()}`);
      });
      return response.data.tokens;
    } catch (error) {
      logger.debug("[PUSH] [ERROR] Failed to fetch push tokens:", error);
      throw new Error(`Failed to fetch push tokens: ${error instanceof Error ? error.message : "Unknown error"}`);
    }
  }
  /**
   * Send push notification via Expo Push API with retry
   * @param messages - Array of push messages to send
   */
  async sendPushNotifications(messages) {
    logger.debug(`Sending ${messages.length} push notifications`);
    const validMessages = messages.filter((message) => {
      if (Array.isArray(message.to)) {
        return message.to.every((token) => expoServerSdk.Expo.isExpoPushToken(token));
      }
      return expoServerSdk.Expo.isExpoPushToken(message.to);
    });
    if (validMessages.length === 0) {
      logger.debug("No valid Expo push tokens found");
      return;
    }
    const chunks = this.expo.chunkPushNotifications(validMessages);
    for (const chunk of chunks) {
      const startTime = Date.now();
      const timeout = 3e5;
      let attempt = 0;
      while (true) {
        try {
          const ticketChunk = await this.expo.sendPushNotificationsAsync(chunk);
          const errors = ticketChunk.filter((ticket) => ticket.status === "error");
          if (errors.length > 0) {
            const errorDetails = errors.map((e) => ({ message: e.message, details: e.details }));
            logger.debug("[PUSH] Some notifications failed:", errorDetails);
          }
          if (errors.length === ticketChunk.length) {
            throw new Error("All push notifications in chunk failed");
          }
          break;
        } catch (error) {
          const elapsed = Date.now() - startTime;
          if (elapsed >= timeout) {
            logger.debug("[PUSH] Timeout reached after 5 minutes, giving up on chunk");
            break;
          }
          attempt++;
          const delay = Math.min(1e3 * Math.pow(2, attempt), 3e4);
          const remainingTime = timeout - elapsed;
          const waitTime = Math.min(delay, remainingTime);
          if (waitTime > 0) {
            logger.debug(`[PUSH] Retrying in ${waitTime}ms (attempt ${attempt})`);
            await new Promise((resolve) => setTimeout(resolve, waitTime));
          }
        }
      }
    }
    logger.debug(`Push notifications sent successfully`);
  }
  /**
   * Send a push notification to all registered devices for the user
   * @param title - Notification title
   * @param body - Notification body
   * @param data - Additional data to send with the notification
   */
  sendToAllDevices(title, body, data) {
    logger.debug(`[PUSH] sendToAllDevices called with title: "${title}", body: "${body}"`);
    (async () => {
      try {
        logger.debug("[PUSH] Fetching push tokens...");
        const tokens = await this.fetchPushTokens();
        logger.debug(`[PUSH] Fetched ${tokens.length} push tokens`);
        tokens.forEach((token, index) => {
          logger.debug(`[PUSH] Using token ${index + 1}: id=${token.id}`);
        });
        if (tokens.length === 0) {
          logger.debug("No push tokens found for user");
          return;
        }
        const messages = tokens.map((token, index) => {
          logger.debug(`[PUSH] Creating message ${index + 1} for token`);
          return {
            to: token.token,
            title,
            body,
            data,
            sound: "default",
            priority: "high"
          };
        });
        logger.debug(`[PUSH] Sending ${messages.length} push notifications...`);
        await this.sendPushNotifications(messages);
        logger.debug("[PUSH] Push notifications sent successfully");
      } catch (error) {
        logger.debug("[PUSH] Error sending to all devices:", error);
      }
    })();
  }
}

function startOfflineReconnection(config) {
  let reconnected = false;
  let session = null;
  let timeoutId = null;
  let failureCount = 0;
  let cancelled = false;
  const defaultHealthCheck = async () => {
    await axios.get(`${config.serverUrl}/v1/sessions`, {
      timeout: 5e3,
      validateStatus: (status) => status < 500
      // 4xx = server is up, 5xx = server error
    });
  };
  const healthCheck = config.healthCheck ?? defaultHealthCheck;
  const initialDelayMs = config.initialDelayMs ?? 5e3;
  const attemptReconnect = async () => {
    if (reconnected || cancelled) return;
    try {
      await healthCheck();
      if (cancelled) return;
      session = await config.onReconnected();
      if (cancelled) return;
      reconnected = true;
      config.onNotify("\u2705 Reconnected! Session syncing in background.");
      logger.debug("[OfflineReconnection] Successfully reconnected");
    } catch (e) {
      if (axios.isAxiosError(e) && e.response?.status === 401) {
        logger.debug("[OfflineReconnection] Authentication error, stopping retries");
        cancelled = true;
        config.onNotify("\u274C Authentication failed. Please re-authenticate with `consortium auth`.");
        config.onCleanup?.();
        return;
      }
      failureCount++;
      const delay = exponentialBackoffDelay(failureCount, 5e3, 6e4, 10);
      logger.debug(`[OfflineReconnection] Attempt ${failureCount} failed, retrying in ${delay}ms`);
      if (!cancelled) {
        timeoutId = setTimeout(attemptReconnect, delay);
      }
    }
  };
  timeoutId = setTimeout(attemptReconnect, initialDelayMs);
  return {
    cancel: () => {
      cancelled = true;
      if (timeoutId) {
        clearTimeout(timeoutId);
        timeoutId = null;
      }
      config.onCleanup?.();
    },
    getSession: () => session,
    isReconnected: () => reconnected
  };
}
const NETWORK_ERROR_CODES = [
  "ECONNREFUSED",
  "ENOTFOUND",
  "ETIMEDOUT",
  "ECONNRESET",
  "EHOSTUNREACH",
  "ENETUNREACH"
];
function isNetworkError(code) {
  return code !== void 0 && NETWORK_ERROR_CODES.includes(code);
}
const ERROR_DESCRIPTIONS = {
  // Network errors (Node.js)
  ECONNREFUSED: "server not accepting connections",
  ENOTFOUND: "server hostname not found",
  ETIMEDOUT: "connection timed out",
  ECONNRESET: "connection reset by server",
  EHOSTUNREACH: "server host unreachable",
  ENETUNREACH: "network unreachable",
  // HTTP errors
  "401": "authentication failed - run `consortium auth`",
  "403": "access forbidden",
  "404": "endpoint not found, check server deployment",
  "429": "rate limit exceeded",
  "500": "server internal error",
  "502": "bad gateway",
  "503": "service unavailable"
};
class OfflineState {
  state = "online";
  failures = /* @__PURE__ */ new Map();
  // Dedupe by operation
  backend = "Claude";
  /** Report failure - accumulates context, prints once on first offline transition */
  fail(failure) {
    this.failures.set(failure.operation, failure);
    if (this.state === "online") {
      this.state = "offline";
      this.print();
    }
  }
  /** Reset on reconnection */
  recover() {
    this.state = "online";
    this.failures.clear();
  }
  /** Set backend name before API calls */
  setBackend(name) {
    this.backend = name;
  }
  /** Check current state */
  isOffline() {
    return this.state === "offline";
  }
  /** Reset for testing - clears all state */
  reset() {
    this.state = "online";
    this.failures.clear();
    this.backend = "Claude";
  }
  print() {
    const summary = [...this.failures.values()].map((f) => {
      const desc = f.errorCode ? `${f.errorCode} - ${ERROR_DESCRIPTIONS[f.errorCode] || "unknown error"}` : "unknown error";
      const url = f.url ? ` at ${f.url}` : "";
      return `${f.operation} failed: ${desc}${url}`;
    }).join("; ");
    console.log(`\u26A0\uFE0F  Consortium server unreachable, offline mode with auto-reconnect enabled - error details: ${summary}`);
    const allDetails = [...this.failures.values()].flatMap((f) => f.details || []);
    allDetails.forEach((line) => console.log(chalk.yellow(`   \u2192 ${line}`)));
  }
}
const connectionState = new OfflineState();

function isAuthResetSignal(err) {
  if (!err || typeof err !== "object") return false;
  const resp = err.response;
  if (resp && resp.status === 401) {
    const headers = resp.headers ?? {};
    const headerVal = headers["x-auth-reset"] ?? headers["X-Auth-Reset"];
    if (headerVal === "1" || headerVal === 1) return true;
    const body = resp.data;
    if (body && body.code === "AUTH_RESET") return true;
    return false;
  }
  const data = err.data;
  return !!data && data.code === "AUTH_RESET";
}

const RATE_LIMIT_MAX_RETRIES = 3;
const RATE_LIMIT_DEFAULT_DELAY_MS = 5e3;
class AuthExpiredError extends Error {
  constructor() {
    super("Authentication token is invalid or expired");
    this.name = "AuthExpiredError";
  }
}
function parseRetryAfterMs(error) {
  const retryAfter = error.response?.headers?.["retry-after"];
  if (retryAfter) {
    const seconds = Number(retryAfter);
    if (!isNaN(seconds) && seconds > 0) return seconds * 1e3;
  }
  const body = error.response?.data;
  if (body?.message && typeof body.message === "string") {
    const match = body.message.match(/retry in (\d+) second/);
    if (match) return Number(match[1]) * 1e3;
  }
  return RATE_LIMIT_DEFAULT_DELAY_MS;
}
class ApiClient {
  static async create(credential) {
    return new ApiClient(credential);
  }
  credential;
  pushClient;
  constructor(credential) {
    this.credential = credential;
    this.pushClient = new PushNotificationClient(credential.token, configuration.serverUrl);
  }
  /**
   * Create a new session or load existing one with the given tag
   */
  async getOrCreateSession(opts) {
    if (!this.credential.encryption) {
      throw new Error("Cannot create session without encryption credentials. Please re-authenticate with mobile or web.");
    }
    let dataEncryptionKey = null;
    let encryptionKey;
    let encryptionVariant;
    if (this.credential.encryption.type === "dataKey") {
      encryptionKey = getRandomBytes(32);
      encryptionVariant = "dataKey";
      let encryptedDataKey = libsodiumEncryptForPublicKey(encryptionKey, this.credential.encryption.publicKey);
      dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
      dataEncryptionKey.set([0], 0);
      dataEncryptionKey.set(encryptedDataKey, 1);
    } else {
      encryptionKey = this.credential.encryption.secret;
      encryptionVariant = "legacy";
    }
    for (let attempt = 0; attempt <= RATE_LIMIT_MAX_RETRIES; attempt++) {
      try {
        const response = await axios.post(
          `${configuration.serverUrl}/v1/sessions`,
          {
            tag: opts.tag,
            metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
            agentState: opts.state ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.state)) : null,
            dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : null,
            organizationId: opts.organizationId || void 0,
            ...opts.projectId ? { projectId: opts.projectId } : {}
          },
          {
            headers: {
              "Authorization": `Bearer ${this.credential.token}`,
              "Content-Type": "application/json"
            },
            timeout: 6e4
            // 1 minute timeout for very bad network connections
          }
        );
        logger.debug(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
        let raw = response.data.session;
        let session = {
          id: raw.id,
          tag: raw.tag,
          seq: raw.seq,
          metadata: decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)),
          metadataVersion: raw.metadataVersion,
          agentState: raw.agentState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.agentState)) : null,
          agentStateVersion: raw.agentStateVersion,
          encryptionKey,
          encryptionVariant,
          userContentPublicKey: this.credential.encryption?.type === "dataKey" ? this.credential.encryption.publicKey : void 0
        };
        try {
          await saveSessionKey(session.id, session.encryptionKey, session.encryptionVariant);
        } catch (err) {
          logger.warn("[api] failed to persist session key (non-fatal)", err);
        }
        return session;
      } catch (error) {
        if (axios.isAxiosError(error) && error.response?.status === 429) {
          const retryMs = parseRetryAfterMs(error);
          if (attempt < RATE_LIMIT_MAX_RETRIES) {
            logger.debug(`[API] Rate limited on session creation, retrying in ${retryMs}ms (attempt ${attempt + 1}/${RATE_LIMIT_MAX_RETRIES})`);
            console.log(chalk.yellow(`\u26A0\uFE0F  Server rate limit hit, retrying in ${Math.ceil(retryMs / 1e3)}s...`));
            await delay(retryMs);
            continue;
          }
          logger.debug("[API] Rate limit retries exhausted for session creation, falling back to offline mode");
          connectionState.fail({
            operation: "Session creation",
            errorCode: "429",
            url: `${configuration.serverUrl}/v1/sessions`,
            details: ["Rate limit exceeded after retries, will retry automatically"]
          });
          return null;
        }
        logger.debug("[API] [ERROR] Failed to get or create session:", error);
        if (error && typeof error === "object" && "code" in error) {
          const errorCode = error.code;
          if (isNetworkError(errorCode)) {
            connectionState.fail({
              operation: "Session creation",
              caller: "api.getOrCreateSession",
              errorCode,
              url: `${configuration.serverUrl}/v1/sessions`
            });
            return null;
          }
        }
        if (axios.isAxiosError(error) && error.response?.status === 401) {
          if (isAuthResetSignal(error)) {
            try {
              const { clearCredentials } = await Promise.resolve().then(function () { return persistence; });
              await clearCredentials();
            } catch {
            }
            throw new AuthExpiredError();
          }
          logger.debug("[API] Transient 401 (no AUTH_RESET) on session creation; keeping creds, will retry");
          connectionState.fail({
            operation: "Session creation",
            caller: "api.getOrCreateSession",
            errorCode: "401",
            url: `${configuration.serverUrl}/v1/sessions`
          });
          return null;
        }
        const is404Error = axios.isAxiosError(error) && error.response?.status === 404 || error && typeof error === "object" && "response" in error && error.response?.status === 404;
        if (is404Error) {
          connectionState.fail({
            operation: "Session creation",
            errorCode: "404",
            url: `${configuration.serverUrl}/v1/sessions`
          });
          return null;
        }
        if (axios.isAxiosError(error) && error.response?.status) {
          const status = error.response.status;
          if (status >= 500) {
            connectionState.fail({
              operation: "Session creation",
              errorCode: String(status),
              url: `${configuration.serverUrl}/v1/sessions`,
              details: ["Server encountered an error, will retry automatically"]
            });
            return null;
          }
        }
        throw new Error(`Failed to get or create session: ${error instanceof Error ? error.message : "Unknown error"}`);
      }
    }
    return null;
  }
  /**
   * Register or update machine with the server
   * Returns the current machine state from the server with decrypted metadata and daemonState
   */
  async getOrCreateMachine(opts) {
    if (!this.credential.encryption) {
      throw new Error("Cannot register machine without encryption credentials. Please re-authenticate with mobile or web.");
    }
    let dataEncryptionKey = null;
    let encryptionKey;
    let encryptionVariant;
    if (this.credential.encryption.type === "dataKey") {
      encryptionVariant = "dataKey";
      encryptionKey = this.credential.encryption.machineKey;
      let encryptedDataKey = libsodiumEncryptForPublicKey(this.credential.encryption.machineKey, this.credential.encryption.publicKey);
      dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
      dataEncryptionKey.set([0], 0);
      dataEncryptionKey.set(encryptedDataKey, 1);
    } else {
      encryptionKey = this.credential.encryption.secret;
      encryptionVariant = "legacy";
    }
    const createMinimalMachine = () => ({
      id: opts.machineId,
      encryptionKey,
      encryptionVariant,
      metadata: opts.metadata,
      metadataVersion: 0,
      daemonState: opts.daemonState || null,
      daemonStateVersion: 0,
      registered: false
    });
    for (let attempt = 0; attempt <= RATE_LIMIT_MAX_RETRIES; attempt++) {
      try {
        const response = await axios.post(
          `${configuration.serverUrl}/v1/machines`,
          {
            id: opts.machineId,
            metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
            daemonState: opts.daemonState ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.daemonState)) : void 0,
            dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : void 0,
            vpsInstanceId: opts.vpsInstanceId,
            organizationId: opts.organizationId || void 0
          },
          {
            headers: {
              "Authorization": `Bearer ${this.credential.token}`,
              "Content-Type": "application/json"
            },
            timeout: 6e4
            // 1 minute timeout for very bad network connections
          }
        );
        const raw = response.data.machine;
        logger.debug(`[API] Machine ${opts.machineId} registered/updated with server`);
        const machine = {
          id: raw.id,
          encryptionKey,
          encryptionVariant,
          metadata: raw.metadata ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)) : null,
          metadataVersion: raw.metadataVersion || 0,
          daemonState: raw.daemonState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.daemonState)) : null,
          daemonStateVersion: raw.daemonStateVersion || 0,
          registered: true
        };
        return machine;
      } catch (error) {
        if (axios.isAxiosError(error) && error.response?.status === 429) {
          const retryMs = parseRetryAfterMs(error);
          if (attempt < RATE_LIMIT_MAX_RETRIES) {
            logger.debug(`[API] Rate limited on machine registration, retrying in ${retryMs}ms (attempt ${attempt + 1}/${RATE_LIMIT_MAX_RETRIES})`);
            console.log(chalk.yellow(`\u26A0\uFE0F  Server rate limit hit, retrying in ${Math.ceil(retryMs / 1e3)}s...`));
            await delay(retryMs);
            continue;
          }
          logger.debug("[API] Rate limit retries exhausted for machine registration, falling back to offline mode");
          connectionState.fail({
            operation: "Machine registration",
            errorCode: "429",
            url: `${configuration.serverUrl}/v1/machines`,
            details: ["Rate limit exceeded after retries, will retry automatically"]
          });
          return createMinimalMachine();
        }
        if (axios.isAxiosError(error) && error.code && isNetworkError(error.code)) {
          connectionState.fail({
            operation: "Machine registration",
            caller: "api.getOrCreateMachine",
            errorCode: error.code,
            url: `${configuration.serverUrl}/v1/machines`
          });
          return createMinimalMachine();
        }
        if (axios.isAxiosError(error) && error.response?.status) {
          const status = error.response.status;
          if (status === 401) {
            if (isAuthResetSignal(error)) {
              try {
                const { clearCredentials } = await Promise.resolve().then(function () { return persistence; });
                await clearCredentials();
                logger.debug("[API] Cleared stale credentials after AUTH_RESET 401");
              } catch {
              }
              throw new AuthExpiredError();
            }
            logger.debug("[API] Transient 401 (no AUTH_RESET) on machine registration; keeping creds, using minimal machine");
            return createMinimalMachine();
          }
          if (status === 409) {
            const errorBody = error.response?.data;
            if (errorBody?.error === "machine_id_conflict" && attempt === 0) {
              const { randomUUID } = await import('node:crypto');
              const { updateSettings } = await Promise.resolve().then(function () { return persistence; });
              const newMachineId = randomUUID();
              try {
                await updateSettings(async (s) => ({ ...s, machineId: newMachineId }));
                logger.debug(`[API] machine_id_conflict on ${opts.machineId}; rotated to ${newMachineId} and retrying`);
                console.log(chalk.yellow(
                  `\u26A0\uFE0F  Machine ID was registered to a different account; rotating to a fresh ID for this account.`
                ));
                opts.machineId = newMachineId;
                continue;
              } catch (rotErr) {
                logger.debug("[API] failed to rotate machineId after 409:", rotErr);
              }
            }
            console.log(chalk.yellow(
              `\u26A0\uFE0F  Machine registration rejected by the server with status ${status}`
            ));
            console.log(chalk.yellow(
              `   \u2192 This machine ID is already registered to another account on the server`
            ));
            console.log(chalk.yellow(
              `   \u2192 Run 'consortium doctor clean' to reset local state and generate a new machine ID`
            ));
            return createMinimalMachine();
          }
          if (status === 403) {
            console.log(chalk.yellow(
              `\u26A0\uFE0F  Machine registration rejected by the server with status 403`
            ));
            return createMinimalMachine();
          }
          if (status >= 500) {
            connectionState.fail({
              operation: "Machine registration",
              errorCode: String(status),
              url: `${configuration.serverUrl}/v1/machines`,
              details: ["Server encountered an error, will retry automatically"]
            });
            return createMinimalMachine();
          }
          if (status === 404) {
            connectionState.fail({
              operation: "Machine registration",
              errorCode: "404",
              url: `${configuration.serverUrl}/v1/machines`
            });
            return createMinimalMachine();
          }
        }
        throw error;
      }
    }
    return createMinimalMachine();
  }
  sessionSyncClient(session) {
    return new ApiSessionClient(this.credential.token, session);
  }
  machineSyncClient(machine) {
    return new ApiMachineClient(this.credential.token, machine);
  }
  push() {
    return this.pushClient;
  }
  /**
   * Register a vendor API token with the server
   * The token is sent as a JSON string - server handles encryption
   */
  async registerVendorToken(vendor, apiKey) {
    try {
      const response = await axios.post(
        `${configuration.serverUrl}/v1/connect/${vendor}/register`,
        {
          token: JSON.stringify(apiKey)
        },
        {
          headers: {
            "Authorization": `Bearer ${this.credential.token}`,
            "Content-Type": "application/json"
          },
          timeout: 5e3
        }
      );
      if (response.status !== 200 && response.status !== 201) {
        throw new Error(`Server returned status ${response.status}`);
      }
      logger.debug(`[API] Vendor token for ${vendor} registered successfully`);
    } catch (error) {
      logger.debug(`[API] [ERROR] Failed to register vendor token:`, error);
      throw new Error(`Failed to register vendor token: ${error instanceof Error ? error.message : "Unknown error"}`);
    }
  }
  /**
   * Get vendor API token from the server
   * Returns the token if it exists, null otherwise
   */
  async getVendorToken(vendor) {
    try {
      const response = await axios.get(
        `${configuration.serverUrl}/v1/connect/${vendor}/token`,
        {
          headers: {
            "Authorization": `Bearer ${this.credential.token}`,
            "Content-Type": "application/json"
          },
          timeout: 5e3
        }
      );
      if (response.status === 404) {
        logger.debug(`[API] No vendor token found for ${vendor}`);
        return null;
      }
      if (response.status !== 200) {
        throw new Error(`Server returned status ${response.status}`);
      }
      logger.debug(`[API] Raw vendor token response:`, {
        status: response.status,
        dataKeys: Object.keys(response.data || {}),
        hasToken: "token" in (response.data || {}),
        tokenType: typeof response.data?.token
      });
      let tokenData = null;
      if (response.data?.token) {
        if (typeof response.data.token === "string") {
          try {
            tokenData = JSON.parse(response.data.token);
          } catch (parseError) {
            logger.debug(`[API] Failed to parse token as JSON, using as string:`, parseError);
            tokenData = response.data.token;
          }
        } else if (response.data.token !== null) {
          tokenData = response.data.token;
        } else {
          logger.debug(`[API] Token is null for ${vendor}, treating as not found`);
          return null;
        }
      } else if (response.data && typeof response.data === "object") {
        if (response.data.token === null && Object.keys(response.data).length === 1) {
          logger.debug(`[API] Response contains only null token for ${vendor}, treating as not found`);
          return null;
        }
        tokenData = response.data;
      }
      if (tokenData === null || tokenData && typeof tokenData === "object" && tokenData.token === null && Object.keys(tokenData).length === 1) {
        logger.debug(`[API] Token data is null for ${vendor}`);
        return null;
      }
      logger.debug(`[API] Vendor token for ${vendor} retrieved successfully`, {
        tokenDataType: typeof tokenData,
        tokenDataKeys: tokenData && typeof tokenData === "object" ? Object.keys(tokenData) : "not an object"
      });
      return tokenData;
    } catch (error) {
      if (error.response?.status === 404) {
        logger.debug(`[API] No vendor token found for ${vendor}`);
        return null;
      }
      logger.debug(`[API] [ERROR] Failed to get vendor token:`, error);
      return null;
    }
  }
  /**
   * Fetches the list of organizations the user belongs to.
   * Used for the interactive org picker on CLI startup.
   *
   * THROWS on failure (network / 5xx / auth). Previously this swallowed
   * errors and returned `[]`, which conflated "the user has no orgs" with
   * "we couldn't reach the server". The daemon path used that empty array to
   * register the machine UNSCOPED, so a transient 5xx during startup
   * orphaned the machine outside any org. Callers must now distinguish the
   * two: a real empty list returns `[]`, an error rejects. Interactive
   * callers (`auth`) already wrap this in try/catch and degrade gracefully;
   * the daemon falls back to the persisted org or refuses to register
   * unscoped on a fetch failure.
   */
  async fetchOrgs() {
    try {
      const response = await axios.get(
        `${configuration.serverUrl}/v1/orgs`,
        {
          headers: {
            "Authorization": `Bearer ${this.credential.token}`,
            "Content-Type": "application/json"
          },
          timeout: 1e4
        }
      );
      return response.data ?? [];
    } catch (error) {
      const status = axios.isAxiosError(error) ? error.response?.status : void 0;
      logger.debug(`[API] Failed to fetch orgs (status=${status ?? "n/a"}):`, error);
      throw error;
    }
  }
}

var api = /*#__PURE__*/Object.freeze({
  __proto__: null,
  ApiClient: ApiClient,
  AuthExpiredError: AuthExpiredError
});

const UsageSchema = z.z.object({
  input_tokens: z.z.number().int().nonnegative(),
  cache_creation_input_tokens: z.z.number().int().nonnegative().optional(),
  cache_read_input_tokens: z.z.number().int().nonnegative().optional(),
  output_tokens: z.z.number().int().nonnegative(),
  service_tier: z.z.string().optional()
}).passthrough();
const RawJSONLinesSchema = z.z.discriminatedUnion("type", [
  // User message - validates uuid and message.content
  z.z.object({
    type: z.z.literal("user"),
    isSidechain: z.z.boolean().optional(),
    isMeta: z.z.boolean().optional(),
    uuid: z.z.string(),
    // Used in getMessageKey()
    message: z.z.object({
      content: z.z.union([z.z.string(), z.z.any()])
      // Used in sessionScanner.ts
    }).passthrough()
  }).passthrough(),
  // Assistant message - only validates uuid and type
  // message object is optional to handle synthetic error messages (isApiErrorMessage: true)
  // which may have different structure than normal assistant messages
  z.z.object({
    uuid: z.z.string(),
    type: z.z.literal("assistant"),
    message: z.z.object({
      usage: UsageSchema.optional(),
      // Used in apiSession.ts
      model: z.z.string().optional()
      // Used for cost calculation
    }).passthrough().optional()
  }).passthrough(),
  // Summary message - validates summary and leafUuid
  z.z.object({
    type: z.z.literal("summary"),
    summary: z.z.string(),
    // Used in apiSession.ts
    leafUuid: z.z.string()
    // Used in getMessageKey()
  }).passthrough(),
  // System message - validates uuid
  z.z.object({
    type: z.z.literal("system"),
    uuid: z.z.string()
    // Used in getMessageKey()
  }).passthrough()
]);

exports.ApiClient = ApiClient;
exports.ApiSessionClient = ApiSessionClient;
exports.AsyncLock = AsyncLock;
exports.AuthExpiredError = AuthExpiredError;
exports.CLAUDE_LOOPBACK_PORT = CLAUDE_LOOPBACK_PORT;
exports.CLAUDE_OOB_REDIRECT_URI = CLAUDE_OOB_REDIRECT_URI;
exports.DEFAULT_GATEWAY_PORT = DEFAULT_GATEWAY_PORT;
exports.HOSTED_SESSION_TERMINAL_PREFIX = HOSTED_SESSION_TERMINAL_PREFIX;
exports.RawJSONLinesSchema = RawJSONLinesSchema;
exports.acquireDaemonLock = acquireDaemonLock;
exports.api = api;
exports.authChallenge = authChallenge;
exports.backoff = backoff;
exports.buildClaudeAuthorizeUrl = buildClaudeAuthorizeUrl;
exports.captureError = captureError;
exports.checkGatewayHealth = checkGatewayHealth;
exports.clearActiveOrgId = clearActiveOrgId;
exports.clearCredentials = clearCredentials;
exports.clearDaemonState = clearDaemonState;
exports.clearMachineId = clearMachineId;
exports.configuration = configuration;
exports.connectionState = connectionState;
exports.createUsageQueueSink = createUsageQueueSink;
exports.decodeBase64 = decodeBase64;
exports.decodeBase64$1 = decodeBase64$1;
exports.decrypt = decrypt;
exports.decryptBox = decryptBox;
exports.decryptDriveContent = decryptDriveContent;
exports.decryptDriveDek = decryptDriveDek;
exports.decryptDriveMetadata = decryptDriveMetadata;
exports.decryptWithDataKey = decryptWithDataKey;
exports.delay = delay;
exports.detectHardware = detectHardware;
exports.detectOpenClaw = detectOpenClaw;
exports.encodeBase64 = encodeBase64;
exports.encodeBase64$1 = encodeBase64$1;
exports.encodeBase64Url = encodeBase64Url;
exports.encrypt = encrypt;
exports.encryptBox = encryptBox;
exports.encryptDriveContent = encryptDriveContent;
exports.encryptDriveDek = encryptDriveDek;
exports.encryptDriveMetadata = encryptDriveMetadata;
exports.encryptWithDataKey = encryptWithDataKey;
exports.exchangeClaudeCode = exchangeClaudeCode;
exports.forceCleanupStaleLock = forceCleanupStaleLock;
exports.formatPosixSpawnpGuidance = formatPosixSpawnpGuidance;
exports.generateClaudePkce = generateClaudePkce;
exports.generateClaudeState = generateClaudeState;
exports.generateDriveDek = generateDriveDek;
exports.getAccountBundle = getAccountBundle;
exports.getActiveBrain = getActiveBrain;
exports.getActiveOrgId = getActiveOrgId;
exports.getAgentAuthCapability = getAgentAuthCapability;
exports.getBrain = getBrain;
exports.getCryptoEnv = getCryptoEnv;
exports.getLatestDaemonLog = getLatestDaemonLog;
exports.getProfileEnvironmentVariables = getProfileEnvironmentVariables;
exports.getProviderKey = getProviderKey;
exports.getTerminalManager = getTerminalManager;
exports.getUsageQueue = getUsageQueue;
exports.incrementCounter = incrementCounter;
exports.initObservability = initObservability;
exports.isAuthResetSignal = isAuthResetSignal;
exports.isPosixSpawnpError = isPosixSpawnpError;
exports.isSealedDekContent = isSealedDekContent;
exports.isSealedDekReadable = isSealedDekReadable;
exports.listAccounts = listAccounts;
exports.listBrains = listBrains;
exports.logger = logger;
exports.markAccountStatus = markAccountStatus;
exports.maskedTailFromBundle = maskedTailFromBundle;
exports.matchErrorSignature = matchErrorSignature;
exports.openSealedDekBytesSync = openSealedDekBytesSync;
exports.openSealedDekBytesWithDek = openSealedDekBytesWithDek;
exports.openSealedDekContentSync = openSealedDekContentSync;
exports.packageJson = packageJson;
exports.parsePastedClaudeCode = parsePastedClaudeCode;
exports.persistence = persistence;
exports.projectPath = projectPath;
exports.provisioningEvents = provisioningEvents;
exports.readCredentials = readCredentials;
exports.readDaemonState = readDaemonState;
exports.readDaemonStateSync = readDaemonStateSync;
exports.readSettings = readSettings;
exports.recordAccountUsageBatch = recordAccountUsageBatch;
exports.registerAccount = registerAccount;
exports.registerProviderKey = registerProviderKey;
exports.releaseDaemonLock = releaseDaemonLock;
exports.removeAccount = removeAccount;
exports.removeProviderKey = removeProviderKey;
exports.resolveAccountForProvider = resolveAccountForProvider;
exports.resolveClaudeConfigDir = resolveClaudeConfigDir;
exports.resolveTmuxBinary = resolveTmuxBinary;
exports.resolveZellijBinary = resolveZellijBinary;
exports.runStartupMigration = runStartupMigration;
exports.setActiveBrainKey = setActiveBrainKey;
exports.setActiveOrgId = setActiveOrgId;
exports.setBrainSeq = setBrainSeq;
exports.setProviderDefault = setProviderDefault;
exports.setProviderFailover = setProviderFailover;
exports.signChallenge = signChallenge;
exports.snapshotsFromCodexRateLimits = snapshotsFromCodexRateLimits;
exports.sodium = sodium;
exports.spawnDiagnostics = spawnDiagnostics;
exports.spawnPtyWithDiagnostics = spawnPtyWithDiagnostics;
exports.startOfflineReconnection = startOfflineReconnection;
exports.toMetadataFields = toMetadataFields;
exports.touchDaemonLock = touchDaemonLock;
exports.updateAccountBundle = updateAccountBundle;
exports.updateSettings = updateSettings;
exports.upsertBrain = upsertBrain;
exports.usageFromAgentTokenCount = usageFromAgentTokenCount;
exports.usageFromCodexLastTokenUsage = usageFromCodexLastTokenUsage;
exports.validateProfileForAgent = validateProfileForAgent;
exports.whichBinary = whichBinary;
exports.writeCredentialsDataKey = writeCredentialsDataKey;
exports.writeCredentialsLegacy = writeCredentialsLegacy;
exports.writeCredentialsTokenOnly = writeCredentialsTokenOnly;
exports.writeDaemonState = writeDaemonState;