UNPKG

consortium

Version:

Remote control and session sharing CLI for AI coding agents

13,227 lines • 476 kB
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import axios, { isAxiosError } from 'axios';
import chalk from 'chalk';
import fs, { appendFileSync, existsSync as existsSync$1 } from 'fs';
import fs__default$1, { readFileSync, existsSync, mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync, chmodSync, renameSync, openSync, constants, writeSync, fsyncSync, closeSync, utimesSync, promises } from 'node:fs';
import os__default, { homedir, userInfo, tmpdir } from 'node:os';
import path__default, { join, basename, dirname as dirname$1, extname, isAbsolute } from 'node:path';
import { EventEmitter } from 'node:events';
import { io } from 'socket.io-client';
import * as z from 'zod';
import { z as z$1 } from 'zod';
import { randomBytes, createDecipheriv, createCipheriv, scryptSync, createHash as createHash$1, randomUUID } from 'node:crypto';
import tweetnacl from 'tweetnacl';
import { createRequire } from 'node:module';
import { spawn, execFile as execFile$2, exec } from 'child_process';
import { promisify } from 'util';
import { mkdir, readFile, appendFile, writeFile, stat, readdir, access } from 'fs/promises';
import os__default$1, { platform, homedir as homedir$1 } from 'os';
import { createHash } from 'crypto';
import path__default$1, { dirname, resolve, join as join$1 } from 'path';
import { fileURLToPath } from 'url';
import { execFile as execFile$3, execFileSync, spawn as spawn$1 } from 'node:child_process';
import fs__default, { readFile as readFile$1, mkdir as mkdir$1, chmod, open, stat as stat$1, unlink, writeFile as writeFile$1, rename, mkdtemp, rmdir, constants as constants$1 } from 'node:fs/promises';
import { promisify as promisify$1 } from 'node:util';
import http from 'http';
import net__default from 'net';
import { Expo } from 'expo-server-sdk';

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(/^~/, homedir());
      this.consortiumHomeDir = expandedPath;
    } else {
      this.consortiumHomeDir = join(homedir(), ".consortium");
    }
    this.settingsFile = join(this.consortiumHomeDir, "settings.json");
    let persistedServerUrl;
    try {
      const rawSettings = 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 = join(this.consortiumHomeDir, "logs");
    this.privateKeyFile = join(this.consortiumHomeDir, "access.key");
    this.daemonStateFile = join(this.consortiumHomeDir, "daemon.state.json");
    this.daemonLockFile = 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 (!existsSync(this.consortiumHomeDir)) {
      mkdirSync(this.consortiumHomeDir, { recursive: true, mode: 448 });
    }
    if (!existsSync(this.logsDir)) {
      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 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 {
      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 {
      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 (!existsSync(logsDir)) {
      return [];
    }
    const logs = readdirSync(logsDir).filter((file) => file.endsWith("-daemon.log")).map((file) => {
      const fullPath = join(logsDir, file);
      const stats = 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 && existsSync(state.daemonLogPath)) {
        const stats = statSync(state.daemonLogPath);
        const persisted = {
          file: 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$1.object({
  c: z$1.string(),
  // Base64 encoded encrypted content
  t: z$1.literal("encrypted")
});
const UpdateBodySchema = z$1.object({
  message: z$1.object({
    id: z$1.string(),
    seq: z$1.number(),
    content: SessionMessageContentSchema
  }),
  sid: z$1.string(),
  // Session ID
  t: z$1.literal("new-message")
});
const UpdateSessionBodySchema = z$1.object({
  t: z$1.literal("update-session"),
  sid: z$1.string(),
  metadata: z$1.object({
    version: z$1.number(),
    value: z$1.string()
  }).nullish(),
  agentState: z$1.object({
    version: z$1.number(),
    value: z$1.string()
  }).nullish()
});
const UpdateMachineBodySchema = z$1.object({
  t: z$1.literal("update-machine"),
  machineId: z$1.string(),
  metadata: z$1.object({
    version: z$1.number(),
    value: z$1.string()
  }).nullish(),
  daemonState: z$1.object({
    version: z$1.number(),
    value: z$1.string()
  }).nullish()
});
z$1.object({
  id: z$1.string(),
  seq: z$1.number(),
  body: z$1.union([
    UpdateBodySchema,
    UpdateSessionBodySchema,
    UpdateMachineBodySchema
  ]),
  createdAt: z$1.number()
});
const HardwareProfileSchema = z$1.object({
  schemaVersion: z$1.literal(1),
  platform: z$1.string(),
  arch: z$1.string(),
  cpuModel: z$1.string(),
  cpuCoresPhysical: z$1.number(),
  cpuThreads: z$1.number(),
  ramTotalBytes: z$1.number(),
  gpus: z$1.array(z$1.object({
    vendor: z$1.enum(["apple", "nvidia", "amd", "intel", "unknown"]),
    name: z$1.string(),
    vramBytes: z$1.number().optional(),
    driver: z$1.string().optional(),
    computeCapability: z$1.string().optional()
  })),
  unifiedMemory: z$1.boolean(),
  usableModelBytes: z$1.number(),
  memBandwidthGBs: z$1.number().optional(),
  bandwidthSource: z$1.enum(["measured", "chip-table", "unknown"]),
  diskFreeBytes: z$1.number(),
  detectedAt: z$1.number(),
  warnings: z$1.array(z$1.string())
});
z$1.object({
  host: z$1.string(),
  platform: z$1.string(),
  consortiumCliVersion: z$1.string(),
  homeDir: z$1.string(),
  consortiumHomeDir: z$1.string(),
  consortiumLibDir: z$1.string(),
  // Hardware specs — collected once at daemon startup from os module
  // (no syscall, libuv-cached). Optional so older daemons stay valid.
  cpuCount: z$1.number().optional(),
  memoryTotalMb: z$1.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$1.boolean().optional(),
  openclawVersion: z$1.string().nullable().optional(),
  openclawWorkspaceExists: z$1.boolean().optional(),
  openclawGatewayRunning: z$1.boolean().optional(),
  openclawGatewayPort: z$1.number().optional(),
  openclawChannels: z$1.array(z$1.string()).optional(),
  openclawAgentCount: z$1.number().optional(),
  openclawAgents: z$1.array(z$1.object({
    name: z$1.string(),
    workspace: z$1.string(),
    agentDir: z$1.string(),
    model: z$1.string(),
    routingRules: z$1.number(),
    isDefault: z$1.boolean()
  })).optional(),
  // Generic per-harness runtime detection, keyed by harnessType. Populated by
  // the daemon heartbeat for every registered harness (openclaw, hermes, …).
  harnessInstances: z$1.record(z$1.string(), z$1.object({
    type: z$1.string(),
    family: z$1.string().optional(),
    installed: z$1.boolean(),
    version: z$1.string().nullable().optional(),
    workspaceExists: z$1.boolean().optional(),
    gatewayRunning: z$1.boolean().optional(),
    gatewayPort: z$1.number().optional(),
    channels: z$1.array(z$1.string()).optional(),
    agentCount: z$1.number().optional(),
    agents: z$1.array(z$1.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$1.string().optional(),
      name: z$1.string(),
      // Runtime liveness as the harness reports it ('active' | 'stopped' | …).
      status: z$1.string().optional(),
      port: z$1.number().optional(),
      workspace: z$1.string().optional(),
      agentDir: z$1.string().optional(),
      model: z$1.string().optional(),
      routingRules: z$1.number().optional(),
      isDefault: z$1.boolean().optional()
    })).optional()
  })).optional(),
  // Terminal multiplexer capabilities — populated at daemon startup.
  // Tells the mobile app whether "New persistent terminal" should be enabled.
  terminalCapabilities: z$1.object({
    tmux: z$1.boolean(),
    zellij: z$1.boolean()
  }).optional()
});
z$1.object({
  status: z$1.union([
    z$1.enum(["running", "shutting-down"]),
    z$1.string()
    // Forward compatibility
  ]),
  pid: z$1.number().optional(),
  httpPort: z$1.number().optional(),
  startedAt: z$1.number().optional(),
  shutdownRequestedAt: z$1.number().optional(),
  shutdownSource: z$1.union([
    z$1.enum(["mobile-app", "cli", "os-signal", "unknown"]),
    z$1.string()
    // Forward compatibility
  ]).optional()
});
z$1.object({
  content: SessionMessageContentSchema,
  createdAt: z$1.number(),
  id: z$1.string(),
  seq: z$1.number(),
  updatedAt: z$1.number()
});
const MessageMetaSchema = z$1.object({
  sentFrom: z$1.string().optional(),
  // Source identifier
  permissionMode: z$1.enum(["default", "acceptEdits", "bypassPermissions", "plan", "read-only", "safe-yolo", "yolo"]).optional(),
  // Permission mode for this message
  model: z$1.string().nullable().optional(),
  // Model name for this message (null = reset)
  fallbackModel: z$1.string().nullable().optional(),
  // Fallback model for this message (null = reset)
  customSystemPrompt: z$1.string().nullable().optional(),
  // Custom system prompt for this message (null = reset)
  appendSystemPrompt: z$1.string().nullable().optional(),
  // Append to system prompt for this message (null = reset)
  allowedTools: z$1.array(z$1.string()).nullable().optional(),
  // Allowed tools for this message (null = reset)
  disallowedTools: z$1.array(z$1.string()).nullable().optional()
  // Disallowed tools for this message (null = reset)
});
z$1.object({
  session: z$1.object({
    id: z$1.string(),
    tag: z$1.string(),
    seq: z$1.number(),
    createdAt: z$1.number(),
    updatedAt: z$1.number(),
    metadata: z$1.string(),
    metadataVersion: z$1.number(),
    agentState: z$1.string().nullable(),
    agentStateVersion: z$1.number()
  })
});
const UserMessageSchema = z$1.object({
  role: z$1.literal("user"),
  content: z$1.object({
    type: z$1.literal("text"),
    text: z$1.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$1.array(z$1.string()).optional(),
    localId: z$1.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$1.string().optional(),
    driveNodeIds: z$1.array(z$1.string()).optional(),
    driveDek: z$1.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$1.array(z$1.object({
      nodeId: z$1.string(),
      driveId: z$1.string(),
      kind: z$1.string(),
      key: z$1.string().optional(),
      name: z$1.string().optional(),
      mime: z$1.string().optional(),
      size: z$1.number().optional(),
      linkUri: z$1.string().optional()
    })).optional()
  }),
  localKey: z$1.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$1.array(z$1.string()).optional(),
  localId: z$1.string().optional(),
  driveId: z$1.string().optional(),
  driveDek: z$1.string().optional()
});
const AgentMessageSchema = z$1.object({
  role: z$1.literal("agent"),
  content: z$1.object({
    type: z$1.literal("output"),
    data: z$1.any()
  }),
  meta: MessageMetaSchema.optional()
});
z$1.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 = createRequire(import.meta.url);
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(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 = 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(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 = 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 = 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 = dirname(fileURLToPath(import.meta.url));
function projectPath() {
  const path = resolve(__dirname$1, "..");
  return path;
}

function run$1(args, options) {
  const RUNNER_PATH = resolve(join$1(projectPath(), "scripts", "ripgrep_launcher.cjs"));
  return new Promise((resolve2, reject) => {
    const child = 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 = platform();
  const binaryName = platformName === "win32" ? "difft.exe" : "difft";
  return resolve(join$1(projectPath(), "tools", "unpacked", binaryName));
}
function run(args, options) {
  const binaryPath = getBinaryPath();
  return new Promise((resolve2, reject) => {
    const child = 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 = resolve(workingDirectory, targetPath);
  const resolvedWorkingDir = 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 = promisify(execFile$2);
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 = join$1(repoPath, ".gitignore");
  try {
    const content = await readFile(gitignorePath, "utf8");
    if (!content.includes(".dev/") && !content.includes(".dev\n")) {
      await appendFile(gitignorePath, "\n# Worktree directory\n.dev/\n");
    }
  } catch {
    await 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 = join$1(repoPath, worktreeDir);
  assertSafeGitRef(branchName, "branchName");
  await ensureDevGitignore(repoPath);
  await mkdir(join$1(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 = promisify(execFile$2);
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 readFile(join$1(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 writeFile(join$1(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 = promisify(exec);
const execFileAsync$4 = promisify(execFile$2);
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 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 readFile(data.path);
          const existingHash = 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 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 writeFile(data.path, buffer);
      const hash = 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 readdir(data.path, { withFileTypes: true });
      const directoryEntries = await Promise.all(
        entries.map(async (entry) => {
          const fullPath = join$1(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 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 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 readdir(path, { withFileTypes: true });
          const children = [];
          await Promise.all(
            entries.map(async (entry) => {
              if (entry.isSymbolicLink()) {
                logger.debug(`Skipping symlink: ${join$1(path, entry.name)}`);
                return;
              }
              const childPath = join$1(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 || join$1(homedir$1(), "consortium", "projects");
      const target = join$1(projectsRoot, data.projectId, "repo");
      if (existsSync$1(join$1(target, ".git"))) {
        return { success: true, path: target, alreadyPresent: true };
      }
      await mkdir(join$1(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 = execFile$3(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 join(configuration.consortiumHomeDir, "byok.json");
}
function readAccessKeyMaterial() {
  try {
    if (existsSync(configuration.privateKeyFile)) {
      return 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 scryptSync(material, salt, SCRYPT_DKLEN, {
    N: SCRYPT_N,
    r: SCRYPT_r,
    p: SCRYPT_p,
    maxmem: SCRYPT_MAXMEM
  });
}
function deriveLegacyAesKey() {
  const raw = readAccessKeyMaterial();
  return createHash$1("sha256").update(raw).update("consortium-byok-v1").digest();
}
function sealWithMaterial(plaintext, material) {
  const salt = randomBytes(SALT_LEN);
  const key = scryptSync(material, salt, SCRYPT_DKLEN, {
    N: SCRYPT_N,
    r: SCRYPT_r,
    p: SCRYPT_p,
    maxmem: SCRYPT_MAXMEM
  });
  const iv = randomBytes(IV_LEN);
  const cipher = 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 = scryptSync(material, Buffer.from(blob.salt, "base64"), SCRYPT_DKLEN, {
      N: SCRYPT_N,
      r: SCRYPT_r,
      p: SCRYPT_p,
      maxmem: SCRYPT_MAXMEM
    });
    const decipher = 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 = randomBytes(IV_LEN);
  const cipher = 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 = 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 = 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 = getKeystorePath();
  try {
    mkdirSync(dirname$1(path), { recursive: true });
  } catch {
  }
  const tmp = `${path}.tmp.${process.pid}.${Date.now()}`;
  writeFileSync(tmp, JSON.stringify(data, null, 2), { encoding: "utf8", mode: 384 });
  try {
    chmodSync(tmp, 384);
  } catch {
  }
  renameSync(tmp, path);
  try {
    chmodSync(path, 384);
  } catch {
  }
}
function emptyV3() {
  return {
    version: 3,
    salt: 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 (!existsSync(path)) return emptyV3();
  let parsed;
  try {
    parsed = JSON.parse(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 {
      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 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 = 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 {
    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 {
    execFileSync(
      "security",
      ["delete-generic-password", "-a", account, "-s", KEYCHAIN_SERVICE$1],
      { timeout: EXEC_TIMEOUT_MS, stdio: "ignore" }
    );
  } catch {
  }
}
function readLinux(account) {
  try {
    const out = 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 {
    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 {
    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 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.object({
  baseUrl: z.string().url().optional(),
  authToken: z.string().optional(),
  model: z.string().optional()
});
const OpenAIConfigSchema = z.object({
  apiKey: z.string().optional(),
  baseUrl: z.string().url().optional(),
  model: z.string().optional()
});
const AzureOpenAIConfigSchema = z.object({
  apiKey: z.string().optional(),
  endpoint: z.string().url().optional(),
  apiVersion: z.string().optional(),
  deploymentName: z.string().optional()
});
const TogetherAIConfigSchema = z.object({
  apiKey: z.string().optional(),
  model: z.string().optional()
});
const TmuxConfigSchema = z.object({
  sessionName: z.string().optional(),
  tmpDir: z.string().optional(),
  updateEnvironment: z.boolean().optional()
});
const EnvironmentVariableSchema = z.object({
  name: z.string().regex(/^[A-Z_][A-Z0-9_]*$/, "Invalid environment variable name"),
  value: z.string()
});
const ProfileCompatibilitySchema = z.object({
  claude: z.boolean().default(true),
  codex: z.boolean().default(true),
  gemini: z.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.boolean().default(true)
}).passthrough();
const AIBackendProfileSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1).max(100),
  description: z.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.array(EnvironmentVariableSchema).default([]),
  // Default session type for this profile
  defaultSessionType: z.enum(["simple", "worktree"]).optional(),
  // Default permission mode for this profile (supports both Claude and Codex modes)
  defaultPermissionMode: z.enum([
    "default",
    "acceptEdits",
    "bypassPermissions",
    "plan",
    // Claude modes
    "read-only",
    "safe-yolo",
    "yolo"
    // Codex modes
  ]).optional(),
  // Default model mode for this profile
  defaultModelMode: z.string().optional(),
  // Compatibility metadata
  compatibility: ProfileCompatibilitySchema.default({ claude: true, codex: true, gemini: true, "consortium-code": true }),
  // Built-in profile indicator
  isBuiltIn: z.boolean().default(false),
  // Metadata
  createdAt: z.number().default(() => Date.now()),
  updatedAt: z.number().default(() => Date.now()),
  version: z.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 (!existsSync(configuration.settingsFile)) {
    return { ...defaultSettings };
  }
  try {
    const content = await readFile$1(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 open(lockFile, constants.O_CREAT | constants.O_EXCL | 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 stat$1(lockFile);
          if (Date.now() - stats.mtimeMs > STALE_LOCK_TIMEOUT_MS) {
            await 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 (!existsSync(configuration.consortiumHomeDir)) {
      await mkdir$1(configuration.consortiumHomeDir, { recursive: true, mode: 448 });
    }
    await writeFile$1(tmpFile, JSON.stringify(updated, null, 2), { mode: 384 });
    await rename(tmpFile, configuration.settingsFile);
    return updated;
  } finally {
    await fileHandle.close();
    await unlink(lockFile).catch(() => {
    });
  }
}
const credentialsSchema$1 = z.object({
  token: z.string(),
  secret: z.string().base64().nullish(),
  // Legacy
  encryption: z.object({
    publicKey: z.string().base64(),
    machineKey: z.string().base64()
  }).nullish()
}).passthrough();
const CRED_ENVELOPE_TYPE = "consortium-cred-v1";
function credKekPath() {
  return join(configuration.consortiumHomeDir, "access.kek");
}
function credKekFileOps() {
  const p = credKekPath();
  return {
    read: () => {
      try {
        if (existsSync(p)) {
          const buf = readFileSync(p);
          if (buf.length >= 32) return buf;
        }
      } catch {
      }
      return null;
    },
    write: (kek) => {
      if (!existsSync(configuration.consortiumHomeDir)) {
        mkdirSync(configuration.consortiumHomeDir, { recursive: true, mode: 448 });
      }
      writeFileSync(p, kek, { mode: 384 });
      try {
        chmodSync(p, 384);
      } catch {
      }
    },
    remove: () => {
      try {
        if (existsSync(p)) unlinkSync(p);
      } catch {
      }
    },
    mint: () => randomBytes(32)
  };
}
function readOrCreateCredKek() {
  return resolveCredKek({
    keychainEnabled: isKeychainKekEnabled(),
    backend: systemKeychainBackend,
    file: credKekFileOps()
  }).kek;
}
async function readCredentialPayload() {
  const raw = await readFile$1(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 (!existsSync(configuration.consortiumHomeDir)) {
    await mkdir$1(configuration.consortiumHomeDir, { recursive: true, mode: 448 });
  }
  const canonical = JSON.stringify(payload);
  if (existsSync(configuration.privateKeyFile)) {
    try {
      const existing = await readCredentialPayload();
      if (existing.wasEncrypted && JSON.stringify(existing.obj) === canonical) {
        try {
          await 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 (!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 (existsSync(configuration.privateKeyFile)) {
    await unlink(configuration.privateKeyFile);
  }
  try {
    if (existsSync(credKekPath())) await unlink(credKekPath());
  } catch {
  }
  if (isKeychainKekEnabled()) {
    try {
      deleteKeychainKek();
    } catch {
    }
  }
}
async function clearMachineId() {
  await updateSettings((settings) => ({
    ...settings,
    machineId: void 0
  }));
}
async function readDaemonState() {
  try {
    if (!existsSync(configuration.daemonStateFile)) {
      return null;
    }
    const content = await readFile$1(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 (!existsSync(configuration.daemonStateFile)) {
      return null;
    }
    const content = 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 = openSync(tmp, constants.O_CREAT | constants.O_TRUNC | constants.O_WRONLY, 384);
  try {
    writeSync(fd, payload);
    fsyncSync(fd);
  } finally {
    closeSync(fd);
  }
  try {
    renameSync(tmp, target);
  } catch (err) {
    try {
      unlinkSync(tmp);
    } catch {
    }
    throw err;
  }
  try {
    chmodSync(target, 384);
  } catch {
  }
}
async function clearDaemonState() {
  if (existsSync(configuration.daemonStateFile)) {
    try {
      await unlink(configuration.daemonStateFile);
    } catch (err) {
      if (err.code !== "ENOENT") throw err;
    }
  }
}
async function forceCleanupStaleLock() {
  if (existsSync(configuration.daemonLockFile)) {
    try {
      await unlink(configuration.daemonLockFile);
    } catch {
    }
  }
}
const STALE_LOCK_MTIME_MS = 15 * 60 * 1e3;
function touchDaemonLock() {
  try {
    const now = /* @__PURE__ */ new Date();
    utimesSync(configuration.daemonLockFile, now, now);
  } catch {
  }
}
async function acquireDaemonLock(maxAttempts = 5, delayIncrementMs = 200) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      const fileHandle = await open(
        configuration.daemonLockFile,
        constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,
        384
      );
      await fileHandle.writeFile(String(process.pid));
      return fileHandle;
    } catch (error) {
      if (error.code === "EEXIST") {
        try {
          const lockPid = 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 {
                renameSync(configuration.daemonLockFile, tmpLockFile);
                const confirmedPid = readFileSync(tmpLockFile, "utf-8").trim();
                if (confirmedPid === lockPid) {
                  unlinkSync(tmpLockFile);
                } else {
                  try {
                    renameSync(tmpLockFile, configuration.daemonLockFile);
                  } catch {
                    try {
                      unlinkSync(tmpLockFile);
                    } catch {
                    }
                  }
                }
              } catch (renameErr) {
                if (renameErr.code === "ENOENT") {
                } else {
                  throw renameErr;
                }
              }
              continue;
            }
          }
        } catch (readErr) {
          if (readErr.code !== "ENOENT") {
            try {
              const ageMs = Date.now() - statSync(configuration.daemonLockFile).mtimeMs;
              if (ageMs > STALE_LOCK_MTIME_MS) {
                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 (existsSync(configuration.daemonLockFile)) {
      unlinkSync(configuration.daemonLockFile);
    }
  } catch {
  }
}
function activeOrgKey(serverUrl, token) {
  const material = token ? `${serverUrl}\0${token}` : `${serverUrl}\0__nocreds__`;
  return createHash$1("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 = 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: 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 (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 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 = 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 = 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: 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: 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: 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}:${createHash$1("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 import('./scanForArtifacts-CEBkINgX.mjs');
      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: 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) {
    const ext = extname(path).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 = isAbsolute(filePath) ? filePath : 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 = 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: 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: randomUUID() });
      }
    } catch (err) {
      logger.debug("[apiSession] file-write artifact promotion failed (non-fatal):", err);
    }
  }
  sendSessionEvent(event, id) {
    let content = {
      role: "agent",
      content: {
        id: id ?? 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 ?? 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 = createRequire(import.meta.url);
function collectDiagnostics(binary, args) {
  const resolvedPath = 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 = promisify$1(execFile$3);
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 = promisify$1(execFile$3);
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 ?? 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 mkdtemp(join(tmpdir(), "consortium-zellij-"));
      const dumpPath = join(dumpDir, "screen.txt");
      await execFile(
        this.bin,
        ["--session", name, "action", "dump-screen", dumpPath],
        { timeout: 4e3 }
      );
      replay = await readFile$1(dumpPath);
      await unlink(dumpPath).catch(() => {
      });
      await rmdir(dumpDir).catch(() => {
      });
    } catch (err) {
      logger.debug(`[ZELLIJ] dump-screen failed for ${name}: ${err instanceof Error ? err.message : err}`);
      if (dumpDir) await 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 ?? 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__default.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 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 ?? homedir();
  return resolveOverride(env.CLAUDE_CONFIG_DIR, join(home, ".claude"), home);
}
function resolveCodexHome(opts = {}) {
  const env = opts.env ?? process.env;
  const home = opts.homeDir ?? homedir();
  return resolveOverride(env.CODEX_HOME, join(home, ".codex"), home);
}

const BINARY_CHECK_TIMEOUT_MS = 4e3;
const defaultProbeRunCommand = (cmd, args) => new Promise((resolve) => {
  execFile$3(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 (!existsSync(path)) return null;
  let raw;
  try {
    raw = 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 ?? 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 = [
      join(resolveClaudeConfigDir({ env: this.env, homeDir: this.home }), ".credentials.json"),
      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 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 ?? 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 = join(
      resolveCodexHome({ env: this.env, homeDir: this.homeDir }),
      "auth.json"
    );
    const primary = await readAuthFile(primaryPath, now);
    if (primary.state) {
      return primary.state;
    }
    const fallbackPath = 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__default.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__default.join(this.homeDir, ".config", "gemini", "credentials.json");
    let raw;
    try {
      raw = await 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__default.join(
      this.homeDir,
      ".config",
      "gcloud",
      "application_default_credentials.json"
    );
    let raw;
    try {
      raw = await 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__default.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__default.join(this.homeDir, ".grok", "auth.json");
    let raw;
    try {
      raw = await 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$1.object({
  token: z$1.string().min(1),
  secret: z$1.string().base64().nullish(),
  encryption: z$1.object({
    publicKey: z$1.string().base64(),
    machineKey: z$1.string().base64()
  }).nullish()
}).passthrough();
function resolveCredentialsPath() {
  const home = process.env.CONSORTIUM_HOME_DIR ? process.env.CONSORTIUM_HOME_DIR.replace(/^~/, homedir()) : join(homedir(), ".consortium");
  return 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 (!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 readFile$1(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("~/") ? 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 ?? 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("$") && 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 = promisify(execFile$2);
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__default$1.cpus()[0]?.model || "unknown";
  const ramTotalBytes = Number(keys["hw.memsize"]) || os__default$1.totalmem();
  const cpuCoresPhysical = Number(keys["hw.physicalcpu"]) || os__default$1.cpus().length;
  const cpuThreads = Number(keys["hw.logicalcpu"]) || os__default$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 = promisify(execFile$2);
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.promises.readdir("/sys/class/drm")) {
      if (!/^card\d+$/.test(card)) continue;
      try {
        const raw = await fs.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.promises.readFile("/proc/meminfo", "utf8").catch(() => ""),
    fs.promises.readFile("/proc/cpuinfo", "utf8").catch(() => "")
  ]);
  const ramTotalBytes = parseMemTotalBytes(meminfo) ?? os__default$1.totalmem();
  const cpuModel = parseCpuModel(cpuinfo) ?? os__default$1.cpus()[0]?.model ?? "unknown";
  const cpuThreads = os__default$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 = promisify(execFile$2);
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__default$1.cpus();
  const cpuModel = cpus[0]?.model ?? "unknown";
  const cpuThreads = cpus.length;
  const ramTotalBytes = os__default$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.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__default$1.totalmem();
    return {
      schemaVersion: 1,
      platform,
      arch: process.arch,
      cpuModel: os__default$1.cpus()[0]?.model ?? "unknown",
      cpuCoresPhysical: os__default$1.cpus().length,
      cpuThreads: os__default$1.cpus().length,
      ramTotalBytes,
      gpus: [],
      unifiedMemory: false,
      usableModelBytes: Math.floor(ramTotalBytes * 0.5),
      bandwidthSource: "unknown",
      diskFreeBytes: await diskFreeBytes(os__default$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__default$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 ?? homedir();
  const env = opts.env ?? process.env;
  switch (agent) {
    case "claude":
      return [
        {
          path: join(
            resolveClaudeConfigDir({ env, homeDir: home }),
            ".credentials.json"
          ),
          recognize: recognizeClaude
        },
        { path: join(home, ".config", "claude", "auth.json"), recognize: recognizeClaude }
      ];
    case "codex":
      return [
        {
          path: join(resolveCodexHome({ env, homeDir: home }), "auth.json"),
          recognize: recognizeCodex
        },
        { path: join(home, ".config", "openai", "auth.json"), recognize: recognizeCodex }
      ];
    case "gemini":
      return [
        { path: join(home, ".config", "gemini", "credentials.json"), recognize: recognizeGemini }
      ];
    case "grok":
      return [
        { path: join(home, ".grok", "auth.json"), recognize: recognizeGrok }
      ];
    case "consortium": {
      const consortiumHome = env.CONSORTIUM_HOME_DIR || join(home, ".consortium");
      return [
        { 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 (!existsSync(cand.path)) continue;
    let raw;
    try {
      raw = 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 {
      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 = 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",
  join$1(homedir$1(), ".nvm/versions/node"),
  // handled specially below
  join$1(homedir$1(), ".local/share/fnm/node-versions"),
  // handled specially below
  join$1(homedir$1(), ".local/bin/openclaw"),
  join$1(homedir$1(), ".npm-global/bin/openclaw")
];
function whichBinary(name) {
  return new Promise((resolve) => {
    const extraPaths = [
      "/usr/local/bin",
      "/opt/homebrew/bin",
      join$1(homedir$1(), ".local/bin"),
      join$1(homedir$1(), ".npm-global/bin")
    ];
    const extendedPath = [process.env.PATH, ...extraPaths].filter(Boolean).join(":");
    execFile$2("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 access(p);
            logger.debug(`[OPENCLAW DETECT] Found binary via fallback path: ${p}`);
            resolve(p);
            return;
          } catch {
          }
        }
        const nvmDir = join$1(homedir$1(), ".nvm/versions/node");
        try {
          const versions = await readdir(nvmDir);
          for (const ver of versions.sort().reverse()) {
            const binPath = join$1(nvmDir, ver, "bin", name);
            try {
              await access(binPath);
              logger.debug(`[OPENCLAW DETECT] Found binary in nvm: ${binPath}`);
              resolve(binPath);
              return;
            } catch {
            }
          }
        } catch {
        }
        const fnmDir = join$1(homedir$1(), ".local/share/fnm/node-versions");
        try {
          const versions = await readdir(fnmDir);
          for (const ver of versions.sort().reverse()) {
            const binPath = join$1(fnmDir, ver, "installation/bin", name);
            try {
              await 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) => {
    execFile$2(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 = join$1(workspaceDir, "openclaw.json");
    const raw = await 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) => {
      execFile$2(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 readdir(workspaceDir);
    const agents = [];
    if (files.includes("AGENTS.md")) {
      agents.push({ id: "default", name: "Default Agent", status: "unknown" });
    }
    try {
      const agentsDir = join$1(workspaceDir, "agents");
      const agentDirs = await 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 = () => {
      execFile$2("pgrep", ["-f", "openclaw-gateway"], { timeout: 3e3 }, (err, stdout) => {
        if (err || stdout.trim().length === 0) {
          resolve(false);
          return;
        }
        const probe = net__default.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) => {
    execFile$2("/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 || join$1(homedir$1(), ".openclaw");
  const [binaryPath, workspaceExists] = await Promise.all([
    whichBinary("openclaw"),
    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",
    join(homedir(), ".local/bin"),
    join(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 ?? spawn$1;
    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",
    join(homedir(), ".local/bin"),
    join(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 ?? spawn$1;
    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: 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, contents) {
  const parent = dirname$1(path);
  mkdirSync(parent, { recursive: true });
  const tmp = `${path}.tmp`;
  const fd = openSync(tmp, "w", 384);
  try {
    writeSync(fd, contents);
    fsyncSync(fd);
  } finally {
    closeSync(fd);
  }
  chmodSync(tmp, 384);
  renameSync(tmp, path);
  chmodSync(path, 384);
}

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

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

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

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

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 ?? 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: 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(randomBytes(32));
  const challenge = base64url(createHash$1("sha256").update(verifier).digest());
  return { verifier, challenge };
}
function generateClaudeState() {
  return base64url(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__default.homedir();
    this.fetchImpl = opts.fetch ?? globalThis.fetch;
    this.now = opts.now ?? (() => Date.now());
  }
  start() {
    const { verifier, challenge } = generateClaudePkce();
    const state = randomUUID();
    const id = 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__default.join(this.homeDir, ".claude");
    const file = path__default.join(dir, ".credentials.json");
    await fs__default.mkdir(dir, { recursive: true, mode: 448 });
    const tmp = `${file}.tmp-${process.pid}-${this.now()}`;
    await fs__default.writeFile(tmp, JSON.stringify(creds, null, 2), { mode: 384 });
    try {
      await fs__default.chmod(tmp, 384);
      await fs__default.rename(tmp, file);
      await fs__default.chmod(file, 384);
    } catch (err) {
      await fs__default.rm(tmp, { force: true }).catch(() => {
      });
      throw err;
    }
    await fs__default.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",
    join(homedir(), ".local/bin"),
    join(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: 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 = spawn$1(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) => {
      execFile$3(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$1.enum(["claude", "codex", "gemini", "grok", "pi", "opencode", "consortium"]);
z$1.enum(["machine", "managed", "byok", "auto"]);
const AuthCredentialOriginSchema = z$1.enum(["env", "keychain", "file", "cli"]);
const AuthIdentitySchema = z$1.object({
  email: z$1.string().optional(),
  account: z$1.string().optional(),
  expiresAt: z$1.number().optional()
});
const AuthWarningSchema = z$1.enum(["binary-missing", "keychain-locked"]);
const AuthStateSchema = z$1.object({
  agent: AgentKindSchema,
  status: z$1.enum(["present", "missing", "invalid", "unknown"]),
  source: AuthCredentialOriginSchema.optional(),
  identity: AuthIdentitySchema.optional(),
  scopes: z$1.array(z$1.string()).optional(),
  lastCheckedAt: z$1.number(),
  error: z$1.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$1.boolean().optional()
});
z$1.object({
  states: z$1.array(AuthStateSchema),
  probedAt: z$1.number()
});
const AuthFlowKindSchema = z$1.enum(["device-code", "browser", "paste", "paste-code", "cli-spawn"]);
const AuthFlowHandleSchema = z$1.object({
  id: z$1.string(),
  agent: AgentKindSchema,
  kind: AuthFlowKindSchema,
  startedAt: z$1.number()
});
const AuthFlowDeviceCodePayloadSchema = z$1.object({
  kind: z$1.literal("device-code"),
  verificationUrl: z$1.string(),
  userCode: z$1.string(),
  expiresAt: z$1.number()
});
const AuthFlowBrowserPayloadSchema = z$1.object({
  kind: z$1.literal("browser"),
  authorizationUrl: z$1.string(),
  expiresAt: z$1.number()
});
const AuthFlowPastePayloadSchema = z$1.object({
  kind: z$1.literal("paste"),
  instructions: z$1.string()
});
const AuthFlowPasteCodePayloadSchema = z$1.object({
  kind: z$1.literal("paste-code"),
  authorizationUrl: z$1.string(),
  instructions: z$1.string(),
  expiresAt: z$1.number()
});
const AuthFlowCliSpawnPayloadSchema = z$1.object({
  kind: z$1.literal("cli-spawn"),
  command: z$1.string(),
  args: z$1.array(z$1.string())
});
const AuthFlowAwaitingUserPayloadSchema = z$1.discriminatedUnion("kind", [
  AuthFlowDeviceCodePayloadSchema,
  AuthFlowBrowserPayloadSchema,
  AuthFlowPastePayloadSchema,
  AuthFlowPasteCodePayloadSchema,
  AuthFlowCliSpawnPayloadSchema
]);
z$1.discriminatedUnion("status", [
  z$1.object({
    status: z$1.literal("pending"),
    handle: AuthFlowHandleSchema
  }),
  z$1.object({
    status: z$1.literal("awaiting-user"),
    handle: AuthFlowHandleSchema,
    payload: AuthFlowAwaitingUserPayloadSchema
  }),
  z$1.object({
    status: z$1.literal("succeeded"),
    handle: AuthFlowHandleSchema,
    finishedAt: z$1.number(),
    identity: AuthIdentitySchema.optional()
  }),
  z$1.object({
    status: z$1.literal("failed"),
    handle: AuthFlowHandleSchema,
    finishedAt: z$1.number(),
    error: z$1.string()
  }),
  z$1.object({
    status: z$1.literal("cancelled"),
    handle: AuthFlowHandleSchema,
    finishedAt: z$1.number()
  }),
  z$1.object({
    status: z$1.literal("expired"),
    handle: AuthFlowHandleSchema,
    finishedAt: z$1.number()
  })
]);

class ProvisioningEventEmitter {
  inner = new 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__default.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__default$1.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__default$1.writeSync(fd, payload);
      fs__default$1.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__default$1.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__default.join(this.dir, name);
    const fd = fs__default$1.openSync(full, "a", 384);
    this.activeFd = fd;
    this.activeName = name;
    this.activeBytes = (() => {
      try {
        return fs__default$1.statSync(full).size;
      } catch {
        return 0;
      }
    })();
    return fd;
  }
  // ── Segment inventory ────────────────────────────────────────────────────
  allSegments() {
    let names;
    try {
      names = fs__default$1.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__default$1.statSync(path__default.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__default$1.readFileSync(path__default.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__default.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__default$1.openSync(tmp, "w", 384);
      try {
        fs__default$1.writeSync(fd, payload);
        fs__default$1.fsyncSync(fd);
      } finally {
        fs__default$1.closeSync(fd);
      }
      fs__default$1.renameSync(tmp, full);
    } catch (err) {
      try {
        fs__default$1.unlinkSync(tmp);
      } catch {
      }
      logger.debug(`[USAGE QUEUE] segment rewrite failed for ${name}: ${err}`);
    }
  }
  removeSegment(name) {
    try {
      fs__default$1.unlinkSync(path__default.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__default.join(this.dir, name);
      let size = 0;
      let lines = 0;
      try {
        size = fs__default$1.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__default$1.readFileSync(path__default.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__default.join(this.dir, STATS_FILE);
    const tmp = `${full}.tmp`;
    try {
      fs__default$1.writeFileSync(tmp, JSON.stringify(this.counters), { mode: 384 });
      fs__default$1.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$1.object({
  agents: z$1.array(AgentKindSchema).optional()
});
const execFileAsync = promisify(execFile$2);
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.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__default$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__default$1.resolve(requestedPath);
  try {
    const entries = await fs.promises.readdir(realPath, { withFileTypes: true });
    const result = await Promise.all(entries.map(async (entry) => {
      const fullPath = path__default$1.join(realPath, entry.name);
      try {
        const stat = await fs.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__default$1.resolve(requestedPath);
  try {
    const stat = await fs.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__default$1.cpus().length;
      const loadOneMin = os__default$1.loadavg()[0];
      const memTotalBytes = os__default$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__default$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 = 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 (!existsSync(path)) {
    return { schemaVersion: SCHEMA_VERSION, keys: {} };
  }
  try {
    const content = await readFile$1(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 mkdir$1(configuration.consortiumHomeDir, { recursive: true, mode: 448 });
  let handle;
  for (let attempts = 0; attempts < MAX_LOCK_ATTEMPTS; attempts++) {
    try {
      handle = await open(lockPath(), constants$1.O_CREAT | constants$1.O_EXCL | constants$1.O_WRONLY);
      break;
    } catch (err) {
      const code = err?.code;
      if (code !== "EEXIST") throw err;
      try {
        const stat = await (await open(lockPath(), constants$1.O_RDONLY)).stat();
        if (Date.now() - stat.mtimeMs > STALE_LOCK_TIMEOUT_MS) {
          await 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 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 writeFile$1(tmp, JSON.stringify(store, null, 2), { mode: 384 });
    await rename(tmp, storePath());
  });
}

class PushNotificationClient {
  token;
  baseUrl;
  expo;
  constructor(token, baseUrl = "https://api.consortium.dev") {
    this.token = token;
    this.baseUrl = baseUrl;
    this.expo = new 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) => Expo.isExpoPushToken(token));
      }
      return 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$1.object({
  input_tokens: z$1.number().int().nonnegative(),
  cache_creation_input_tokens: z$1.number().int().nonnegative().optional(),
  cache_read_input_tokens: z$1.number().int().nonnegative().optional(),
  output_tokens: z$1.number().int().nonnegative(),
  service_tier: z$1.string().optional()
}).passthrough();
const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
  // User message - validates uuid and message.content
  z$1.object({
    type: z$1.literal("user"),
    isSidechain: z$1.boolean().optional(),
    isMeta: z$1.boolean().optional(),
    uuid: z$1.string(),
    // Used in getMessageKey()
    message: z$1.object({
      content: z$1.union([z$1.string(), z$1.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$1.object({
    uuid: z$1.string(),
    type: z$1.literal("assistant"),
    message: z$1.object({
      usage: UsageSchema.optional(),
      // Used in apiSession.ts
      model: z$1.string().optional()
      // Used for cost calculation
    }).passthrough().optional()
  }).passthrough(),
  // Summary message - validates summary and leafUuid
  z$1.object({
    type: z$1.literal("summary"),
    summary: z$1.string(),
    // Used in apiSession.ts
    leafUuid: z$1.string()
    // Used in getMessageKey()
  }).passthrough(),
  // System message - validates uuid
  z$1.object({
    type: z$1.literal("system"),
    uuid: z$1.string()
    // Used in getMessageKey()
  }).passthrough()
]);

export { registerAccount as $, ApiClient as A, markAccountStatus as B, readCredentials as C, readDaemonState as D, clearDaemonState as E, forceCleanupStaleLock as F, getActiveOrgId as G, HOSTED_SESSION_TERMINAL_PREFIX as H, clearActiveOrgId as I, setActiveOrgId as J, clearCredentials as K, updateSettings as L, encodeBase64Url as M, authChallenge as N, signChallenge as O, provisioningEvents as P, clearMachineId as Q, RawJSONLinesSchema as R, registerProviderKey as S, getProviderKey as T, removeProviderKey as U, exchangeClaudeCode as V, getAgentAuthCapability as W, generateClaudePkce as X, buildClaudeAuthorizeUrl as Y, CLAUDE_OOB_REDIRECT_URI as Z, parsePastedClaudeCode as _, ApiSessionClient as a, spawnDiagnostics as a$, maskedTailFromBundle as a0, listAccounts as a1, updateAccountBundle as a2, sodium as a3, decryptDriveMetadata as a4, decryptDriveContent as a5, decodeBase64$1 as a6, encryptDriveMetadata as a7, encodeBase64$1 as a8, encryptDriveContent as a9, incrementCounter as aA, validateProfileForAgent as aB, getProfileEnvironmentVariables as aC, decrypt as aD, encryptBox as aE, decryptBox as aF, encryptWithDataKey as aG, decryptWithDataKey as aH, isSealedDekContent as aI, isSealedDekReadable as aJ, openSealedDekContentSync as aK, openSealedDekBytesWithDek as aL, openSealedDekBytesSync as aM, generateClaudeState as aN, CLAUDE_LOOPBACK_PORT as aO, getBrain as aP, listBrains as aQ, setBrainSeq as aR, getActiveBrain as aS, upsertBrain as aT, setActiveBrainKey as aU, isPosixSpawnpError as aV, readDaemonStateSync as aW, getLatestDaemonLog as aX, AuthExpiredError as aY, formatPosixSpawnpGuidance as aZ, persistence as a_, getCryptoEnv as aa, generateDriveDek as ab, encryptDriveDek as ac, decryptDriveDek as ad, writeDaemonState as ae, matchErrorSignature as af, captureError as ag, getTerminalManager as ah, encrypt as ai, whichBinary as aj, detectOpenClaw as ak, checkGatewayHealth as al, DEFAULT_GATEWAY_PORT as am, initObservability as an, isAuthResetSignal as ao, acquireDaemonLock as ap, releaseDaemonLock as aq, runStartupMigration as ar, toMetadataFields as as, removeAccount as at, setProviderDefault as au, setProviderFailover as av, detectHardware as aw, touchDaemonLock as ax, resolveZellijBinary as ay, resolveTmuxBinary as az, packageJson as b, api as b0, configuration as c, decodeBase64 as d, encodeBase64 as e, writeCredentialsDataKey as f, delay as g, connectionState as h, spawnPtyWithDiagnostics as i, writeCredentialsTokenOnly as j, recordAccountUsageBatch as k, logger as l, snapshotsFromCodexRateLimits as m, usageFromAgentTokenCount as n, resolveClaudeConfigDir as o, projectPath as p, backoff as q, readSettings as r, startOfflineReconnection as s, AsyncLock as t, usageFromCodexLastTokenUsage as u, getAccountBundle as v, writeCredentialsLegacy as w, resolveAccountForProvider as x, createUsageQueueSink as y, getUsageQueue as z };