#!/usr/bin/env node
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));

// src/pty-worker.ts
var readline = __toESM(require("readline"));

// src/adapters/adapter-factory.ts
var import_adapter_types = require("adapter-types");

// src/adapters/adapter-registry.ts
var import_adapter_types2 = require("adapter-types");

// src/adapters/base-adapter.ts
var import_adapter_types3 = require("adapter-types");

// src/adapters/shell-adapter.ts
var ShellAdapter = class {
  adapterType = "shell";
  displayName = "Shell";
  autoResponseRules = [];
  shell;
  promptStr;
  constructor(options = {}) {
    this.shell = options.shell || process.env.SHELL || "/bin/bash";
    this.promptStr = options.prompt || "pty> ";
  }
  getCommand() {
    return this.shell;
  }
  getArgs(_config) {
    return [];
  }
  getEnv(_config) {
    return {
      PS1: this.promptStr
    };
  }
  detectLogin(_output) {
    return { required: false };
  }
  detectBlockingPrompt(_output) {
    return { detected: false };
  }
  detectReady(output) {
    if (this.isContinuationPrompt(output)) {
      return false;
    }
    return this.getPromptPattern().test(this.stripAnsi(output));
  }
  /**
   * Detect shell continuation prompts that indicate the shell is NOT ready
   * for a new command (e.g., unclosed quote, heredoc, backtick).
   */
  isContinuationPrompt(output) {
    const stripped = this.stripAnsi(output);
    return /(?:quote|dquote|heredoc|bquote|cmdsubst|pipe|then|else|do|loop)>\s*$/.test(
      stripped
    ) || // Also match bare > that's preceded by one of these words on the same line
    /(?:quote|dquote|heredoc|bquote)>\s*$/m.test(stripped);
  }
  detectExit(output) {
    if (output.includes("exit")) {
      return { exited: true, code: 0 };
    }
    return { exited: false };
  }
  parseOutput(output) {
    const cleaned = this.stripAnsi(output).trim();
    if (!cleaned) return null;
    return {
      type: "response",
      content: cleaned,
      isComplete: true,
      isQuestion: false
    };
  }
  formatInput(message) {
    return message;
  }
  getPromptPattern() {
    const escaped = this.promptStr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    return new RegExp(`(?:${escaped}|\\$|#)\\s*$`, "m");
  }
  async validateInstallation() {
    return { installed: true };
  }
  stripAnsi(str) {
    return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
  }
};

// src/pty-manager.ts
var import_node_events2 = require("events");

// src/logger.ts
var consoleLogger = {
  debug: (...args) => {
    if (typeof args[0] === "string") {
      console.debug(args[0], args[1]);
    } else {
      console.debug(args[1], args[0]);
    }
  },
  info: (...args) => {
    if (typeof args[0] === "string") {
      console.info(args[0], args[1]);
    } else {
      console.info(args[1], args[0]);
    }
  },
  warn: (...args) => {
    if (typeof args[0] === "string") {
      console.warn(args[0], args[1]);
    } else {
      console.warn(args[1], args[0]);
    }
  },
  error: (...args) => {
    if (typeof args[0] === "string") {
      console.error(args[0], args[1]);
    } else {
      console.error(args[1], args[0]);
    }
  }
};

// src/pty-session.ts
var import_node_crypto = require("crypto");
var import_node_events = require("events");

// src/ensure-pty.ts
var import_node_child_process = require("child_process");
var import_node_fs = require("fs");
var import_node_path = require("path");
var TAG = "[pty-preflight]";
var platformArch = `${process.platform}-${process.arch}`;
var checked = false;
function findNodePtyRoots() {
  const roots = [];
  try {
    const nodePtyMain = require.resolve("node-pty");
    const nodePtyRoot = (0, import_node_path.dirname)((0, import_node_path.dirname)(nodePtyMain));
    if ((0, import_node_fs.existsSync)((0, import_node_path.join)(nodePtyRoot, "package.json"))) {
      roots.push(nodePtyRoot);
    }
  } catch {
  }
  const packageRoot = (0, import_node_path.join)(__dirname, "..");
  const candidates = [
    (0, import_node_path.join)(packageRoot, "node_modules", "node-pty"),
    // Monorepo hoisted
    (0, import_node_path.join)(packageRoot, "..", "node-pty"),
    (0, import_node_path.join)(packageRoot, "..", "..", "node-pty")
  ];
  for (const candidate of candidates) {
    if ((0, import_node_fs.existsSync)((0, import_node_path.join)(candidate, "package.json")) && !roots.includes(candidate)) {
      roots.push(candidate);
    }
  }
  return roots;
}
function findNativeBinary(nodePtyRoot) {
  const prebuildPath = (0, import_node_path.join)(nodePtyRoot, "prebuilds", platformArch, "pty.node");
  if ((0, import_node_fs.existsSync)(prebuildPath)) {
    return { type: "prebuild", path: prebuildPath };
  }
  const gypPath = (0, import_node_path.join)(nodePtyRoot, "build", "Release", "pty.node");
  if ((0, import_node_fs.existsSync)(gypPath)) {
    return { type: "gyp", path: gypPath };
  }
  return null;
}
function fixSpawnHelpers(nodePtyRoot, log) {
  if (process.platform === "win32") return;
  const prebuildsDir = (0, import_node_path.join)(nodePtyRoot, "prebuilds");
  if (!(0, import_node_fs.existsSync)(prebuildsDir)) return;
  try {
    for (const entry of (0, import_node_fs.readdirSync)(prebuildsDir)) {
      const helperPath = (0, import_node_path.join)(prebuildsDir, entry, "spawn-helper");
      if ((0, import_node_fs.existsSync)(helperPath)) {
        try {
          const stat = (0, import_node_fs.statSync)(helperPath);
          if ((stat.mode & 73) === 0) {
            (0, import_node_fs.chmodSync)(helperPath, 493);
            log(
              `${TAG} Fixed spawn-helper permissions: ${(0, import_node_path.relative)(nodePtyRoot, helperPath)}`
            );
          }
        } catch {
        }
      }
    }
  } catch {
  }
}
function rebuildNodePty(nodePtyRoot, log) {
  log(`${TAG} No native binary found \u2014 attempting node-gyp rebuild...`);
  try {
    (0, import_node_child_process.execSync)("node-gyp rebuild", {
      cwd: nodePtyRoot,
      stdio: "pipe",
      timeout: 12e4
    });
    log(`${TAG} node-gyp rebuild succeeded`);
    return true;
  } catch (err) {
    log(
      `${TAG} node-gyp rebuild failed: ${err instanceof Error ? err.message : err}`
    );
    return false;
  }
}
function ensurePty(log = console.log) {
  if (checked) return;
  checked = true;
  const roots = findNodePtyRoots();
  if (roots.length === 0) {
    throw new Error(
      `${TAG} node-pty not found. Install it with: npm install node-pty`
    );
  }
  let anyBinaryFound = false;
  for (const root of roots) {
    const binary = findNativeBinary(root);
    if (binary) {
      log(`${TAG} Found ${binary.type} binary for ${platformArch}`);
      anyBinaryFound = true;
    }
    fixSpawnHelpers(root, log);
    if (!binary) {
      if (rebuildNodePty(root, log)) {
        if (findNativeBinary(root)) {
          anyBinaryFound = true;
        }
      }
    }
    if (anyBinaryFound) break;
  }
  if (!anyBinaryFound) {
    throw new Error(
      `${TAG} No node-pty native binary available for ${platformArch}. Try: cd node_modules/node-pty && node-gyp rebuild`
    );
  }
}

// src/pty-session.ts
var ptyCache = null;
function loadPty() {
  if (!ptyCache) {
    try {
      ptyCache = require("node-pty");
    } catch {
      throw new Error(
        "node-pty is required but not installed. Run: npm install node-pty"
      );
    }
  }
  return ptyCache;
}
function generateId() {
  return `pty-${Date.now()}-${(0, import_node_crypto.randomUUID)().slice(0, 8)}`;
}
var SPECIAL_KEYS = {
  // Control keys (Ctrl+letter = ASCII control code)
  "ctrl+a": "",
  "ctrl+b": "",
  "ctrl+c": "",
  "ctrl+d": "",
  "ctrl+e": "",
  "ctrl+f": "",
  "ctrl+g": "\x07",
  "ctrl+h": "\b",
  "ctrl+i": "	",
  "ctrl+j": "\n",
  "ctrl+k": "\v",
  "ctrl+l": "\f",
  "ctrl+m": "\r",
  "ctrl+n": "",
  "ctrl+o": "",
  "ctrl+p": "",
  "ctrl+q": "",
  "ctrl+r": "",
  "ctrl+s": "",
  "ctrl+t": "",
  "ctrl+u": "",
  "ctrl+v": "",
  "ctrl+w": "",
  "ctrl+x": "",
  "ctrl+y": "",
  "ctrl+z": "",
  "ctrl+[": "\x1B",
  "ctrl+\\": "",
  "ctrl+]": "",
  "ctrl+^": "",
  "ctrl+_": "",
  // Alt+letter (Meta key = ESC + letter)
  "alt+a": "\x1Ba",
  "alt+b": "\x1Bb",
  "alt+c": "\x1Bc",
  "alt+d": "\x1Bd",
  "alt+e": "\x1Be",
  "alt+f": "\x1Bf",
  "alt+g": "\x1Bg",
  "alt+h": "\x1Bh",
  "alt+i": "\x1Bi",
  "alt+j": "\x1Bj",
  "alt+k": "\x1Bk",
  "alt+l": "\x1Bl",
  "alt+m": "\x1Bm",
  "alt+n": "\x1Bn",
  "alt+o": "\x1Bo",
  "alt+p": "\x1Bp",
  "alt+q": "\x1Bq",
  "alt+r": "\x1Br",
  "alt+s": "\x1Bs",
  "alt+t": "\x1Bt",
  "alt+u": "\x1Bu",
  "alt+v": "\x1Bv",
  "alt+w": "\x1Bw",
  "alt+x": "\x1Bx",
  "alt+y": "\x1By",
  "alt+z": "\x1Bz",
  "alt+backspace": "\x1B\x7F",
  // Delete word backward
  // Navigation - plain
  up: "\x1B[A",
  down: "\x1B[B",
  right: "\x1B[C",
  left: "\x1B[D",
  home: "\x1B[H",
  end: "\x1B[F",
  pageup: "\x1B[5~",
  pagedown: "\x1B[6~",
  // Navigation - with Shift (modifier 2)
  "shift+up": "\x1B[1;2A",
  "shift+down": "\x1B[1;2B",
  "shift+right": "\x1B[1;2C",
  "shift+left": "\x1B[1;2D",
  "shift+home": "\x1B[1;2H",
  "shift+end": "\x1B[1;2F",
  "shift+pageup": "\x1B[5;2~",
  "shift+pagedown": "\x1B[6;2~",
  // Navigation - with Alt (modifier 3)
  "alt+up": "\x1B[1;3A",
  "alt+down": "\x1B[1;3B",
  "alt+right": "\x1B[1;3C",
  // Forward word
  "alt+left": "\x1B[1;3D",
  // Backward word
  // Navigation - with Ctrl (modifier 5)
  "ctrl+up": "\x1B[1;5A",
  "ctrl+down": "\x1B[1;5B",
  "ctrl+right": "\x1B[1;5C",
  // Forward word
  "ctrl+left": "\x1B[1;5D",
  // Backward word
  "ctrl+home": "\x1B[1;5H",
  "ctrl+end": "\x1B[1;5F",
  // Navigation - with Ctrl+Shift (modifier 6) - select word
  "ctrl+shift+up": "\x1B[1;6A",
  "ctrl+shift+down": "\x1B[1;6B",
  "ctrl+shift+right": "\x1B[1;6C",
  "ctrl+shift+left": "\x1B[1;6D",
  "ctrl+shift+home": "\x1B[1;6H",
  "ctrl+shift+end": "\x1B[1;6F",
  // Navigation - with Shift+Alt (modifier 4)
  "shift+alt+up": "\x1B[1;4A",
  "shift+alt+down": "\x1B[1;4B",
  "shift+alt+right": "\x1B[1;4C",
  "shift+alt+left": "\x1B[1;4D",
  // Editing
  enter: "\r",
  return: "\r",
  tab: "	",
  "shift+tab": "\x1B[Z",
  // Reverse tab
  backspace: "\x7F",
  delete: "\x1B[3~",
  "shift+delete": "\x1B[3;2~",
  "ctrl+delete": "\x1B[3;5~",
  // Delete word forward
  insert: "\x1B[2~",
  escape: "\x1B",
  esc: "\x1B",
  space: " ",
  // Function keys - plain
  f1: "\x1BOP",
  f2: "\x1BOQ",
  f3: "\x1BOR",
  f4: "\x1BOS",
  f5: "\x1B[15~",
  f6: "\x1B[17~",
  f7: "\x1B[18~",
  f8: "\x1B[19~",
  f9: "\x1B[20~",
  f10: "\x1B[21~",
  f11: "\x1B[23~",
  f12: "\x1B[24~",
  // Function keys - with Shift (modifier 2)
  "shift+f1": "\x1B[1;2P",
  "shift+f2": "\x1B[1;2Q",
  "shift+f3": "\x1B[1;2R",
  "shift+f4": "\x1B[1;2S",
  "shift+f5": "\x1B[15;2~",
  "shift+f6": "\x1B[17;2~",
  "shift+f7": "\x1B[18;2~",
  "shift+f8": "\x1B[19;2~",
  "shift+f9": "\x1B[20;2~",
  "shift+f10": "\x1B[21;2~",
  "shift+f11": "\x1B[23;2~",
  "shift+f12": "\x1B[24;2~",
  // Function keys - with Ctrl (modifier 5)
  "ctrl+f1": "\x1B[1;5P",
  "ctrl+f2": "\x1B[1;5Q",
  "ctrl+f3": "\x1B[1;5R",
  "ctrl+f4": "\x1B[1;5S",
  "ctrl+f5": "\x1B[15;5~",
  "ctrl+f6": "\x1B[17;5~",
  "ctrl+f7": "\x1B[18;5~",
  "ctrl+f8": "\x1B[19;5~",
  "ctrl+f9": "\x1B[20;5~",
  "ctrl+f10": "\x1B[21;5~",
  "ctrl+f11": "\x1B[23;5~",
  "ctrl+f12": "\x1B[24;5~"
};
var BRACKETED_PASTE_START = "\x1B[200~";
var BRACKETED_PASTE_END = "\x1B[201~";
var PTYSession = class _PTYSession extends import_node_events.EventEmitter {
  constructor(adapter, config, logger, stallDetectionEnabled, defaultStallTimeoutMs) {
    super();
    this.adapter = adapter;
    this.id = config.id || generateId();
    this.config = { ...config, id: this.id };
    this.logger = logger || consoleLogger;
    this._stallDetectionEnabled = stallDetectionEnabled ?? false;
    this._stallTimeoutMs = config.stallTimeoutMs ?? defaultStallTimeoutMs ?? 8e3;
    this._stallBackoffMs = this._stallTimeoutMs;
    if (config.ruleOverrides) {
      for (const [key, value] of Object.entries(config.ruleOverrides)) {
        if (value === null) {
          this._disabledRulePatterns.add(key);
        } else {
          this._ruleOverrides.set(key, value);
        }
      }
    }
  }
  ptyProcess = null;
  outputBuffer = "";
  _status = "pending";
  _startedAt = null;
  _lastActivityAt = null;
  messageCounter = 0;
  logger;
  sessionRules = [];
  _firedOnceRules = /* @__PURE__ */ new Set();
  _lastBlockingPromptHash = null;
  _lastBlockingPromptEmitAt = 0;
  static BLOCKING_PROMPT_DEBOUNCE_MS = 250;
  _ruleOverrides = /* @__PURE__ */ new Map();
  _disabledRulePatterns = /* @__PURE__ */ new Set();
  // Stall detection
  _stallTimer = null;
  _stallTimeoutMs;
  _stallDetectionEnabled;
  _lastStallHash = null;
  _stallStartedAt = null;
  _lastContentHash = null;
  _stallBackoffMs = 0;
  // Initialized in constructor from _stallTimeoutMs
  static MAX_STALL_BACKOFF_MS = 3e4;
  _stallEmissionCount = 0;
  static MAX_STALL_EMISSIONS = 5;
  // Task completion detection (idle detection when busy)
  _taskCompleteTimer = null;
  _taskCompletePending = false;
  static TASK_COMPLETE_DEBOUNCE_MS = 1500;
  // Ready detection settle delay — defers session_ready until output goes quiet
  _readySettleTimer = null;
  _readySettlePending = false;
  // Tool running deduplication — only emit when tool changes
  _lastToolRunningName = null;
  // Deferred output processing — prevents node-pty's synchronous data
  // delivery from starving the event loop (timers, I/O callbacks, etc.)
  _processScheduled = false;
  // Output buffer cap — prevents unbounded growth during long tasks
  static MAX_OUTPUT_BUFFER = 1e5;
  // 100 KB
  id;
  config;
  get status() {
    return this._status;
  }
  get pid() {
    return this.ptyProcess?.pid;
  }
  get startedAt() {
    return this._startedAt ?? void 0;
  }
  get lastActivityAt() {
    return this._lastActivityAt ?? void 0;
  }
  // ─────────────────────────────────────────────────────────────────────────────
  // Runtime Auto-Response Rules API
  // ─────────────────────────────────────────────────────────────────────────────
  /**
   * Add an auto-response rule to this session.
   * Session rules are checked before adapter rules.
   */
  addAutoResponseRule(rule) {
    const existingIndex = this.sessionRules.findIndex(
      (r) => r.pattern.source === rule.pattern.source && r.pattern.flags === rule.pattern.flags
    );
    if (existingIndex >= 0) {
      this.sessionRules[existingIndex] = rule;
      this.logger.debug(
        { sessionId: this.id, pattern: rule.pattern.source, type: rule.type },
        "Replaced existing auto-response rule"
      );
    } else {
      this.sessionRules.push(rule);
      this.logger.debug(
        { sessionId: this.id, pattern: rule.pattern.source, type: rule.type },
        "Added auto-response rule"
      );
    }
  }
  /**
   * Remove an auto-response rule by pattern.
   * Returns true if a rule was removed.
   */
  removeAutoResponseRule(pattern) {
    const initialLength = this.sessionRules.length;
    this.sessionRules = this.sessionRules.filter(
      (r) => !(r.pattern.source === pattern.source && r.pattern.flags === pattern.flags)
    );
    const removed = this.sessionRules.length < initialLength;
    if (removed) {
      this.logger.debug(
        { sessionId: this.id, pattern: pattern.source },
        "Removed auto-response rule"
      );
    }
    return removed;
  }
  /**
   * Set all session auto-response rules, replacing existing ones.
   */
  setAutoResponseRules(rules) {
    this.sessionRules = [...rules];
    this.logger.debug(
      { sessionId: this.id, count: rules.length },
      "Set auto-response rules"
    );
  }
  /**
   * Get all session auto-response rules.
   */
  getAutoResponseRules() {
    return [...this.sessionRules];
  }
  /**
   * Clear all session auto-response rules.
   */
  clearAutoResponseRules() {
    this.sessionRules = [];
    this.logger.debug({ sessionId: this.id }, "Cleared auto-response rules");
  }
  // ─────────────────────────────────────────────────────────────────────────────
  // Stall Detection
  // ─────────────────────────────────────────────────────────────────────────────
  /**
   * Start or reset the stall detection timer.
   * Active when status is "busy" or "authenticating" and stall detection is enabled.
   *
   * Content-based: hashes the ANSI-stripped buffer tail and only resets the
   * timer when visible content actually changes. This prevents TUI spinners
   * (which produce new ANSI sequences but no new visible text) from endlessly
   * resetting the timer.
   */
  resetStallTimer() {
    if (!this._stallDetectionEnabled || this._status !== "busy" && this._status !== "authenticating") {
      this.clearStallTimer();
      return;
    }
    const stripped = this.stripAnsiForStall(this.outputBuffer).trim();
    const visible = this.stripAnsiForClassifier(this.outputBuffer).trim();
    const tail = stripped.slice(-500);
    const fallbackTail = visible.slice(-500);
    const hash = this.simpleHash(tail || fallbackTail);
    if (hash === this._lastContentHash) {
      return;
    }
    this._lastContentHash = hash;
    this._stallEmissionCount = 0;
    if (this._stallTimer) {
      clearTimeout(this._stallTimer);
      this._stallTimer = null;
    }
    this._stallStartedAt = Date.now();
    this._lastStallHash = null;
    this._stallBackoffMs = this._stallTimeoutMs;
    this._stallTimer = setTimeout(() => {
      this.onStallTimerFired();
    }, this._stallTimeoutMs);
  }
  /**
   * Clear the stall detection timer.
   */
  clearStallTimer() {
    if (this._stallTimer) {
      clearTimeout(this._stallTimer);
      this._stallTimer = null;
    }
    this._stallStartedAt = null;
    this._lastContentHash = null;
    this._stallBackoffMs = this._stallTimeoutMs;
    this._stallEmissionCount = 0;
  }
  /**
   * Called when the stall timer fires (no output for stallTimeoutMs).
   */
  onStallTimerFired() {
    if (this._status !== "busy" && this._status !== "authenticating") {
      return;
    }
    if (this._status === "busy" && this.adapter.detectTaskComplete?.(this.outputBuffer)) {
      this._status = "ready";
      this._lastBlockingPromptHash = null;
      this._lastBlockingPromptEmitAt = 0;
      this.outputBuffer = "";
      this.clearStallTimer();
      this.emit("status_changed", "ready");
      this.emit("task_complete");
      this.logger.info(
        { sessionId: this.id },
        "Task complete (adapter fast-path) \u2014 agent returned to idle prompt"
      );
      return;
    }
    if (this.adapter.detectLoading?.(this.outputBuffer)) {
      this.logger.debug(
        { sessionId: this.id },
        "Loading pattern detected \u2014 suppressing stall emission"
      );
      this._stallTimer = setTimeout(
        () => this.onStallTimerFired(),
        this._stallBackoffMs
      );
      return;
    }
    const toolInfo = this.adapter.detectToolRunning?.(this.outputBuffer);
    if (toolInfo) {
      if (toolInfo.toolName !== this._lastToolRunningName) {
        this._lastToolRunningName = toolInfo.toolName;
        this.emit("tool_running", toolInfo);
      }
      this.logger.debug(
        { sessionId: this.id, tool: toolInfo.toolName },
        "Tool running \u2014 suppressing stall emission"
      );
      this._stallTimer = setTimeout(
        () => this.onStallTimerFired(),
        this._stallBackoffMs
      );
      return;
    }
    if (this._lastToolRunningName) {
      this._lastToolRunningName = null;
    }
    const tail = this.outputBuffer.slice(-500);
    const hash = this.simpleHash(tail);
    if (hash === this._lastStallHash) {
      this._stallTimer = setTimeout(
        () => this.onStallTimerFired(),
        this._stallBackoffMs
      );
      return;
    }
    this._lastStallHash = hash;
    this._stallEmissionCount++;
    if (this._stallEmissionCount > _PTYSession.MAX_STALL_EMISSIONS) {
      this.logger.warn(
        { sessionId: this.id, count: this._stallEmissionCount },
        "Max stall emissions reached \u2014 suspending stall detection for this task"
      );
      this.clearStallTimer();
      return;
    }
    const recentRaw = this.outputBuffer.slice(-2e3);
    const recentOutput = this.stripAnsiForClassifier(recentRaw).trim();
    const stallDurationMs = this._stallStartedAt ? Date.now() - this._stallStartedAt : this._stallTimeoutMs;
    this.logger.debug(
      {
        sessionId: this.id,
        stallDurationMs,
        bufferTailLength: tail.length,
        recentOutputLength: recentOutput.length,
        recentOutputHash: this.simpleHash(recentOutput.slice(-500))
      },
      "Stall detected"
    );
    this.emit("stall_detected", recentOutput, stallDurationMs);
    this._stallTimer = setTimeout(
      () => this.onStallTimerFired(),
      this._stallBackoffMs
    );
  }
  /**
   * Promise-based delay helper.
   */
  delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
  /**
   * Simple string hash for deduplication.
   */
  simpleHash(str) {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = (hash << 5) - hash + char;
      hash |= 0;
    }
    return hash.toString(36);
  }
  mapLoginTypeToAuthMethod(type) {
    switch (type) {
      case "api_key":
        return "api_key";
      case "cli_auth":
        return "cli_auth";
      case "device_code":
        return "device_code";
      case "oauth":
      case "browser":
        return "oauth_browser";
      default:
        return "unknown";
    }
  }
  extractDeviceCode(text) {
    const stripped = this.stripAnsiForClassifier(text);
    const explicitMatch = stripped.match(
      /(?:one-time|one time|device)?\s*code[:\s]+([A-Z0-9]{3,}(?:-[A-Z0-9]{3,})+)/i
    );
    if (explicitMatch?.[1]) {
      return explicitMatch[1].toUpperCase();
    }
    if (!/code/i.test(stripped)) {
      return void 0;
    }
    const fallbackMatch = stripped.match(
      /\b([A-Z0-9]{3,}(?:-[A-Z0-9]{3,})+)\b/
    );
    return fallbackMatch?.[1]?.toUpperCase();
  }
  getPromptSnippet(maxChars = 280) {
    const normalized = this.stripAnsiForClassifier(this.outputBuffer).replace(/\s+/g, " ").trim();
    if (!normalized) {
      return void 0;
    }
    return normalized.length <= maxChars ? normalized : normalized.slice(-maxChars);
  }
  emitAuthRequired(details) {
    const info = {
      method: this.mapLoginTypeToAuthMethod(details.type),
      url: details.url,
      deviceCode: details.deviceCode ?? this.extractDeviceCode(this.outputBuffer),
      instructions: details.instructions,
      promptSnippet: this.getPromptSnippet()
    };
    this.emit("auth_required", info);
    this.emit("login_required", info.instructions, info.url);
  }
  /**
   * Strip ANSI codes, cursor movement, box-drawing, and spinner characters.
   * Used for stall detection hashing and auto-response pattern matching.
   *
   * Cursor movement codes are replaced with spaces (not removed) to preserve
   * word boundaries — e.g. "Do\x1b[5Cyou" becomes "Do you", not "Doyou".
   */
  stripAnsiForStall(str) {
    let result = str.replace(/\x1b\[\d*[CDABGdEF]/g, " ");
    result = result.replace(/\x1b\[\d*(?:;\d+)?[Hf]/g, " ");
    result = result.replace(/\x1b\[\d*[JK]/g, " ");
    result = result.replace(
      /\x1b\](?:[^\x07\x1b]|\x1b[^\\])*(?:\x07|\x1b\\)/g,
      ""
    );
    result = result.replace(/\x1bP(?:[^\x1b]|\x1b[^\\])*\x1b\\/g, "");
    result = result.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
    result = result.replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "");
    result = result.replace(/\xa0/g, " ");
    result = result.replace(
      /[│╭╰╮╯─═╌║╔╗╚╝╠╣╦╩╬┌┐└┘├┤┬┴┼●○❯❮▶◀⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣾⣽⣻⢿⡿⣟⣯⣷✻✶✳✢⏺←→↑↓⬆⬇◆◇▪▫■□▲△▼▽◈⟨⟩⌘⏎⏏⌫⌦⇧⇪⌥]/g,
      " "
    );
    result = result.replace(/\d+[hms](?:\s+\d+[hms])*/g, "0s");
    result = result.replace(/ {2,}/g, " ");
    return result;
  }
  /**
   * Less-aggressive ANSI stripping for classifier context.
   * Preserves visible TUI symbols (e.g. ❯, ✻) and durations while removing
   * escape/control sequences so the classifier keeps useful evidence.
   */
  stripAnsiForClassifier(str) {
    let result = str.replace(/\x1b\[\d*[CDABGdEF]/g, " ");
    result = result.replace(/\x1b\[\d*(?:;\d+)?[Hf]/g, " ");
    result = result.replace(/\x1b\[\d*[JK]/g, " ");
    result = result.replace(
      /\x1b\](?:[^\x07\x1b]|\x1b[^\\])*(?:\x07|\x1b\\)/g,
      ""
    );
    result = result.replace(/\x1bP(?:[^\x1b]|\x1b[^\\])*\x1b\\/g, "");
    result = result.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
    result = result.replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "");
    result = result.replace(/\xa0/g, " ");
    result = result.replace(/ {2,}/g, " ");
    return result;
  }
  /**
   * Handle external stall classification result.
   * Called by the manager after onStallClassify resolves.
   */
  handleStallClassification(classification) {
    if (this._status !== "busy" && this._status !== "authenticating") {
      return;
    }
    if (!classification || classification.state === "still_working") {
      this._stallBackoffMs = Math.min(
        this._stallBackoffMs * 2,
        _PTYSession.MAX_STALL_BACKOFF_MS
      );
      this.logger.debug(
        { sessionId: this.id, nextCheckMs: this._stallBackoffMs },
        "Still working \u2014 backing off stall check interval"
      );
      this._lastContentHash = null;
      this._lastStallHash = null;
      if (this._stallTimer) {
        clearTimeout(this._stallTimer);
        this._stallTimer = null;
      }
      this._stallTimer = setTimeout(
        () => this.onStallTimerFired(),
        this._stallBackoffMs
      );
      return;
    }
    switch (classification.state) {
      case "waiting_for_input": {
        const promptInfo = {
          type: "stall_classified",
          prompt: classification.prompt,
          canAutoRespond: !!classification.suggestedResponse
        };
        if (classification.suggestedResponse) {
          this.logger.info(
            { sessionId: this.id, response: classification.suggestedResponse },
            "Auto-responding to stall-classified prompt"
          );
          const resp = classification.suggestedResponse;
          if (resp.startsWith("keys:")) {
            const keys = resp.slice(5).split(",").map((k) => k.trim());
            this.sendKeySequence(keys);
          } else {
            this.writeRaw(`${resp}\r`);
          }
          this.emit("blocking_prompt", promptInfo, true);
          this.outputBuffer = "";
        } else {
          this.emit("blocking_prompt", promptInfo, false);
        }
        break;
      }
      case "task_complete":
        this._status = "ready";
        this._lastBlockingPromptHash = null;
        this._lastBlockingPromptEmitAt = 0;
        this.outputBuffer = "";
        this.clearStallTimer();
        this.emit("ready");
        this.logger.info(
          { sessionId: this.id },
          "Stall classified as task_complete, transitioning to ready"
        );
        break;
      case "error":
        this.clearStallTimer();
        this.emit(
          "error",
          new Error(classification.prompt || "Stall classified as error")
        );
        break;
    }
  }
  // ─────────────────────────────────────────────────────────────────────────────
  // Task Completion Detection
  // ─────────────────────────────────────────────────────────────────────────────
  /**
   * Schedule a task_complete transition after a debounce period.
   * Uses a settle pattern: each call resets the debounce timer instead of
   * being a no-op when already scheduled. This allows TUI agents that
   * continue rendering decorative output (status bar, update notices) after
   * the prompt to eventually settle, rather than having the timer cancelled
   * by every new data chunk. The callback re-verifies the task-complete
   * signal before transitioning, so stale triggers are safe.
   */
  scheduleTaskComplete() {
    const wasPending = this._taskCompletePending;
    this.traceTaskCompletion("debounce_schedule", {
      wasPending,
      debounceMs: _PTYSession.TASK_COMPLETE_DEBOUNCE_MS
    });
    if (this._taskCompleteTimer) {
      clearTimeout(this._taskCompleteTimer);
    }
    this._taskCompletePending = true;
    this._taskCompleteTimer = setTimeout(() => {
      this._taskCompleteTimer = null;
      this._taskCompletePending = false;
      const signal = this.isTaskCompleteSignal(this.outputBuffer);
      this.traceTaskCompletion("debounce_fire", { signal });
      if (this._status !== "busy") {
        this.traceTaskCompletion("debounce_reject_status", { signal });
        return;
      }
      if (!signal) {
        this.traceTaskCompletion("debounce_reject_signal", { signal });
        return;
      }
      this._status = "ready";
      this._lastBlockingPromptHash = null;
      this._lastBlockingPromptEmitAt = 0;
      this.outputBuffer = "";
      this.clearStallTimer();
      this.emit("status_changed", "ready");
      this.emit("task_complete");
      this.traceTaskCompletion("transition_ready", { signal: true });
      this.logger.info(
        { sessionId: this.id },
        "Task complete \u2014 agent returned to idle prompt"
      );
    }, _PTYSession.TASK_COMPLETE_DEBOUNCE_MS);
  }
  /**
   * Whether the adapter's `detectLoading()` currently classifies the
   * session as actively processing work.
   *
   * This wraps `adapter.detectLoading(outputBuffer)` for consumers outside
   * the PTY layer (e.g. milady's swarm idle watchdog) that need a
   * reliable "is the agent busy right now?" signal without reimplementing
   * heuristics over raw terminal output.
   *
   * Returns `false` if the adapter does not implement `detectLoading`.
   */
  isLoading() {
    return this.adapter.detectLoading?.(this.outputBuffer) ?? false;
  }
  /**
   * Adapter-level task completion check with compatibility fallback.
   * Prefer detectTaskComplete() because detectReady() may be broad for TUIs.
   */
  isTaskCompleteSignal(output) {
    if (this.adapter.detectTaskComplete) {
      return this.adapter.detectTaskComplete(output);
    }
    return this.adapter.detectReady(output);
  }
  /**
   * Claude-oriented task completion traces for PTY debugging.
   * Disabled by default; enable via config.traceTaskCompletion.
   */
  traceTaskCompletion(event, ctx = {}) {
    if (!this.shouldTraceTaskCompletion()) return;
    const output = this.outputBuffer;
    const detectTaskComplete = this.adapter.detectTaskComplete ? this.adapter.detectTaskComplete(output) : void 0;
    const detectReady = this.adapter.detectReady(output);
    const detectLoading = this.adapter.detectLoading ? this.adapter.detectLoading(output) : void 0;
    const normalizedTail = this.stripAnsiForStall(output.slice(-280));
    this.logger.debug(
      {
        sessionId: this.id,
        adapterType: this.adapter.adapterType,
        event,
        status: this._status,
        taskCompletePending: this._taskCompletePending,
        signal: ctx.signal,
        wasPending: ctx.wasPending,
        debounceMs: ctx.debounceMs,
        detectTaskComplete,
        detectReady,
        detectLoading,
        tailHash: this.simpleHash(normalizedTail),
        tailSnippet: normalizedTail.slice(-140)
      },
      "Task completion trace"
    );
  }
  shouldTraceTaskCompletion() {
    return this.config.traceTaskCompletion === true;
  }
  /**
   * Cancel a pending task_complete timer (new output arrived that
   * doesn't match the idle prompt, so the agent is still working).
   */
  cancelTaskComplete() {
    if (this._taskCompleteTimer) {
      clearTimeout(this._taskCompleteTimer);
      this._taskCompleteTimer = null;
    }
    this._taskCompletePending = false;
  }
  // ─────────────────────────────────────────────────────────────────────────────
  // Ready Detection Settle Delay
  // ─────────────────────────────────────────────────────────────────────────────
  /**
   * Schedule or reset the ready-settle timer.
   * Defers emitting session_ready until output goes quiet for readySettleMs
   * after detectReady first matches. This prevents sending input while
   * TUI agents are still rendering (status bar, shortcuts, update notices).
   */
  scheduleReadySettle() {
    this._readySettlePending = true;
    if (this._readySettleTimer) {
      clearTimeout(this._readySettleTimer);
    }
    const settleMs = this.config.readySettleMs ?? this.adapter.readySettleMs ?? 100;
    this._readySettleTimer = setTimeout(() => {
      this._readySettleTimer = null;
      this._readySettlePending = false;
      if (this._status !== "starting" && this._status !== "authenticating")
        return;
      if (!this.adapter.detectReady(this.outputBuffer)) return;
      this._status = "ready";
      this._lastBlockingPromptHash = null;
      this._lastBlockingPromptEmitAt = 0;
      this.outputBuffer = "";
      this.clearStallTimer();
      this.emit("ready");
      this.logger.info({ sessionId: this.id }, "Session ready (after settle)");
    }, settleMs);
  }
  /**
   * Cancel a pending ready-settle timer (ready indicator disappeared
   * or session status changed).
   */
  cancelReadySettle() {
    if (this._readySettleTimer) {
      clearTimeout(this._readySettleTimer);
      this._readySettleTimer = null;
    }
    this._readySettlePending = false;
  }
  // ─────────────────────────────────────────────────────────────────────────────
  // Lifecycle
  // ─────────────────────────────────────────────────────────────────────────────
  /**
   * Start the PTY session
   */
  async start() {
    if (this.ptyProcess) {
      throw new Error("Session already started");
    }
    ensurePty((msg) => this.logger.info({ sessionId: this.id }, msg));
    const nodePty = loadPty();
    this._status = "starting";
    this._startedAt = /* @__PURE__ */ new Date();
    const command = this.adapter.getCommand();
    const args = this.adapter.getArgs(this.config);
    const adapterEnv = this.adapter.getEnv(this.config);
    const env = _PTYSession.buildSpawnEnv(this.config, adapterEnv);
    this.logger.info(
      { sessionId: this.id, command, args: args.join(" ") },
      "Starting PTY session"
    );
    try {
      this.ptyProcess = nodePty.spawn(command, args, {
        name: "xterm-256color",
        cols: this.config.cols || 120,
        rows: this.config.rows || 40,
        cwd: this.config.workdir || process.cwd(),
        env
      });
      this.setupEventHandlers();
      this.logger.info(
        { sessionId: this.id, pid: this.ptyProcess.pid },
        "PTY session started"
      );
    } catch (error) {
      this._status = "error";
      this.logger.error(
        { sessionId: this.id, error },
        "Failed to start PTY session"
      );
      throw error;
    }
  }
  /**
   * Set up event handlers for the PTY
   */
  setupEventHandlers() {
    if (!this.ptyProcess) return;
    this.ptyProcess.onData((data) => {
      this._lastActivityAt = /* @__PURE__ */ new Date();
      this.outputBuffer += data;
      if (this.outputBuffer.length > _PTYSession.MAX_OUTPUT_BUFFER) {
        this.outputBuffer = this.outputBuffer.slice(
          -_PTYSession.MAX_OUTPUT_BUFFER
        );
      }
      this.emit("output", data);
      if (!this._processScheduled) {
        this._processScheduled = true;
        setImmediate(() => {
          this._processScheduled = false;
          this.processOutputBuffer();
        });
      }
    });
    this.ptyProcess.onExit(({ exitCode, signal }) => {
      this._status = "stopped";
      this.clearStallTimer();
      this.logger.info(
        { sessionId: this.id, exitCode, signal },
        "PTY session exited"
      );
      this.emit("exit", exitCode);
    });
  }
  /**
   * Process the accumulated output buffer.
   * Called via setImmediate() from the onData handler so that heavy regex
   * work runs in its own event-loop tick, not inside node-pty's native callback.
   */
  processOutputBuffer() {
    if (this._status === "busy" || this._status === "authenticating") {
      this.resetStallTimer();
    }
    if (this._readySettlePending) {
      if ((this._status === "starting" || this._status === "authenticating") && this.adapter.detectReady(this.outputBuffer)) {
        this.scheduleReadySettle();
      } else {
        this.cancelReadySettle();
      }
      return;
    }
    if ((this._status === "starting" || this._status === "authenticating") && this.adapter.detectReady(this.outputBuffer)) {
      this.scheduleReadySettle();
      return;
    }
    if (this._status === "busy") {
      const toolInfo = this.adapter.detectToolRunning?.(this.outputBuffer);
      if (toolInfo) {
        if (toolInfo.toolName !== this._lastToolRunningName) {
          this._lastToolRunningName = toolInfo.toolName;
          this.emit("tool_running", toolInfo);
        }
      } else if (this._lastToolRunningName) {
        this._lastToolRunningName = null;
      }
    }
    if (this._status === "busy") {
      const signal = this.isTaskCompleteSignal(this.outputBuffer);
      if (this._taskCompletePending || signal) {
        this.traceTaskCompletion("busy_signal", { signal });
        this.scheduleTaskComplete();
      }
    }
    if (this._status !== "stopping" && this._status !== "stopped") {
      const blockingPrompt = this.detectAndHandleBlockingPrompt();
      if (blockingPrompt) {
        return;
      }
    }
    if (this._status !== "ready" && this._status !== "busy") {
      const loginDetection = this.adapter.detectLogin(this.outputBuffer);
      if (loginDetection.required && this._status !== "authenticating") {
        this._status = "authenticating";
        this.clearStallTimer();
        this.emitAuthRequired({
          type: loginDetection.type,
          url: loginDetection.url,
          deviceCode: loginDetection.deviceCode,
          instructions: loginDetection.instructions
        });
        this.logger.warn(
          { sessionId: this.id, loginType: loginDetection.type },
          "Login required"
        );
        return;
      }
    }
    const exitDetection = this.adapter.detectExit(this.outputBuffer);
    if (exitDetection.exited) {
      this._status = "stopped";
      this.clearStallTimer();
      this.emit("exit", exitDetection.code || 0);
    }
    if (this._status === "ready") {
      this.tryParseOutput();
    }
  }
  /**
   * Detect blocking prompts and handle them with auto-responses or user notification.
   * Deduplicates emissions - won't re-emit the same blocking prompt repeatedly.
   */
  detectAndHandleBlockingPrompt() {
    const autoHandled = this.tryAutoResponse();
    if (autoHandled) {
      return true;
    }
    if (this.adapter.detectBlockingPrompt) {
      const detection = this.adapter.detectBlockingPrompt(this.outputBuffer);
      if (detection.detected) {
        const normalizedPrompt = (detection.prompt || "").replace(/\s+/g, " ").replace(/\d+/g, "#").trim().slice(0, 100);
        const promptHash = `${detection.type}:${normalizedPrompt}`;
        if (promptHash === this._lastBlockingPromptHash) {
          return true;
        }
        const now = Date.now();
        if (now - this._lastBlockingPromptEmitAt < _PTYSession.BLOCKING_PROMPT_DEBOUNCE_MS) {
          return true;
        }
        this._lastBlockingPromptHash = promptHash;
        this._lastBlockingPromptEmitAt = now;
        const promptInfo = {
          type: detection.type || "unknown",
          prompt: detection.prompt,
          options: detection.options,
          canAutoRespond: detection.canAutoRespond || false,
          instructions: detection.instructions,
          url: detection.url
        };
        if (detection.canAutoRespond && detection.suggestedResponse && !this.config.skipAdapterAutoResponse) {
          this.logger.info(
            {
              sessionId: this.id,
              promptType: detection.type,
              response: detection.suggestedResponse
            },
            "Auto-responding to blocking prompt"
          );
          const resp = detection.suggestedResponse;
          if (resp.startsWith("keys:")) {
            const keys = resp.slice(5).split(",").map((k) => k.trim());
            this.sendKeySequence(keys);
          } else {
            this.writeRaw(`${resp}\r`);
          }
          this.outputBuffer = "";
          this.emit("blocking_prompt", promptInfo, true);
          return true;
        }
        if (detection.type === "login") {
          this._status = "authenticating";
          const inferred = this.adapter.detectLogin(this.outputBuffer);
          this.emitAuthRequired({
            type: inferred.required ? inferred.type : void 0,
            url: detection.url ?? inferred.url,
            deviceCode: inferred.required ? inferred.deviceCode : void 0,
            instructions: detection.instructions ?? inferred.instructions
          });
        }
        this.logger.warn(
          {
            sessionId: this.id,
            promptType: detection.type,
            prompt: detection.prompt
          },
          "Blocking prompt requires user intervention"
        );
        this.emit("blocking_prompt", promptInfo, false);
        return true;
      } else {
        this._lastBlockingPromptHash = null;
        this._lastBlockingPromptEmitAt = 0;
      }
    }
    return false;
  }
  /**
   * Try to match and apply auto-response rules.
   * Session rules are checked first, then adapter rules.
   */
  tryAutoResponse() {
    const adapterRules = (this.adapter.autoResponseRules || []).filter((r) => !this._disabledRulePatterns.has(r.pattern.source)).map((r) => {
      const override = this._ruleOverrides.get(r.pattern.source);
      return override ? { ...r, ...override } : r;
    });
    const allRules = [...this.sessionRules, ...adapterRules];
    if (allRules.length === 0) {
      return false;
    }
    const stripped = this.stripAnsiForStall(this.outputBuffer);
    for (const rule of allRules) {
      if (rule.once) {
        const ruleKey = `${rule.pattern.source}:${rule.pattern.flags}`;
        if (this._firedOnceRules.has(ruleKey)) {
          continue;
        }
      }
      if (rule.pattern.test(stripped)) {
        const safe = rule.safe !== false;
        const isSessionRule = this.sessionRules.includes(rule);
        if (safe) {
          this.logger.info(
            {
              sessionId: this.id,
              promptType: rule.type,
              description: rule.description,
              response: rule.response,
              source: isSessionRule ? "session" : "adapter"
            },
            "Applying auto-response rule"
          );
          const useKeys = rule.keys && rule.keys.length > 0;
          const isTuiDefault = !rule.responseType && !rule.keys && this.adapter.usesTuiMenus;
          if (useKeys) {
            this.sendKeySequence(rule.keys);
          } else if (isTuiDefault) {
            this.sendKeys("enter");
          } else {
            this.writeRaw(`${rule.response}\r`);
          }
          if (rule.once) {
            const ruleKey = `${rule.pattern.source}:${rule.pattern.flags}`;
            this._firedOnceRules.add(ruleKey);
          }
          this.outputBuffer = "";
          const promptInfo = {
            type: rule.type,
            prompt: rule.description,
            canAutoRespond: true
          };
          this.emit("blocking_prompt", promptInfo, true);
          return true;
        } else {
          const promptInfo = {
            type: rule.type,
            prompt: rule.description,
            canAutoRespond: false,
            instructions: `Prompt matched but requires user confirmation: ${rule.description}`
          };
          this.emit("blocking_prompt", promptInfo, false);
          return true;
        }
      }
    }
    return false;
  }
  /**
   * Try to parse the output buffer into structured messages
   */
  tryParseOutput() {
    const parsed = this.adapter.parseOutput(this.outputBuffer);
    if (parsed?.isComplete) {
      this.outputBuffer = "";
      const message = {
        id: `${this.id}-msg-${++this.messageCounter}`,
        sessionId: this.id,
        direction: "outbound",
        type: parsed.type,
        content: parsed.content,
        metadata: parsed.metadata,
        timestamp: /* @__PURE__ */ new Date()
      };
      this.emit("message", message);
      if (parsed.isQuestion) {
        this.emit("question", parsed.content);
      }
    }
  }
  /**
   * Write data to the PTY (formatted by adapter)
   */
  write(data) {
    if (!this.ptyProcess) {
      throw new Error("Session not started");
    }
    this._lastActivityAt = /* @__PURE__ */ new Date();
    const formatted = this.adapter.formatInput(data);
    this.ptyProcess.write(`${formatted}\r`);
    this.logger.debug(
      { sessionId: this.id, input: data },
      "Sent input to session"
    );
  }
  /**
   * Write raw data directly to the PTY (no formatting)
   */
  writeRaw(data) {
    if (!this.ptyProcess) {
      throw new Error("Session not started");
    }
    this._lastActivityAt = /* @__PURE__ */ new Date();
    this.ptyProcess.write(data);
  }
  /**
   * Send a task/message to the session
   *
   * Text and Enter are sent as separate writes with a small delay.
   * This is required for TUI-based CLIs (Gemini CLI, ink/React-based tools)
   * which drop the trailing \r if it arrives in the same write buffer
   * during a render cycle.
   */
  send(message) {
    this._status = "busy";
    this.outputBuffer = "";
    this.emit("status_changed", "busy");
    this._stallEmissionCount = 0;
    this.resetStallTimer();
    const msg = {
      id: `${this.id}-msg-${++this.messageCounter}`,
      sessionId: this.id,
      direction: "inbound",
      type: "task",
      content: message,
      timestamp: /* @__PURE__ */ new Date()
    };
    const formatted = this.adapter.formatInput(message);
    this.writeRaw(formatted);
    setTimeout(() => this.sendKeys("enter"), 50);
    this.logger.debug(
      { sessionId: this.id, input: message },
      "Sent input to session"
    );
    return msg;
  }
  /**
   * Resize the PTY terminal
   */
  resize(cols, rows) {
    this.ptyProcess?.resize(cols, rows);
  }
  /**
   * Send special keys to the PTY
   *
   * Supported keys:
   * - Control: ctrl+c, ctrl+d, ctrl+z, ctrl+l, ctrl+a, ctrl+e, ctrl+k, ctrl+u, ctrl+w, ctrl+r
   * - Navigation: up, down, left, right, home, end, pageup, pagedown
   * - Editing: enter, tab, backspace, delete, insert, escape
   * - Function: f1-f12
   *
   * @param keys - Key name(s) to send, e.g. "ctrl+c" or ["up", "up", "enter"]
   */
  sendKeys(keys) {
    if (!this.ptyProcess) {
      throw new Error("Session not started");
    }
    const keyList = Array.isArray(keys) ? keys : [keys];
    const normalized = _PTYSession.normalizeKeyList(keyList);
    this._stallEmissionCount = 0;
    this._lastBlockingPromptHash = null;
    this._lastBlockingPromptEmitAt = 0;
    this.outputBuffer = "";
    this.resetStallTimer();
    for (const key of normalized) {
      const sequence = SPECIAL_KEYS[key];
      if (sequence) {
        this._lastActivityAt = /* @__PURE__ */ new Date();
        this.ptyProcess.write(sequence);
        this.logger.debug({ sessionId: this.id, key }, "Sent special key");
      } else {
        this.logger.warn(
          { sessionId: this.id, key },
          "Unknown special key, sending as literal"
        );
        this.ptyProcess.write(key);
      }
    }
  }
  /**
   * Build the environment object for spawning a PTY process.
   * Merges base env (process.env unless opted out), adapter env, and config env,
   * with TERM/COLORTERM always forced.
   */
  static buildSpawnEnv(config, adapterEnv) {
    const baseEnv = config.inheritProcessEnv !== false ? process.env : {};
    return {
      ...baseEnv,
      ...adapterEnv,
      ...config.env,
      TERM: "xterm-256color",
      COLORTERM: "truecolor"
    };
  }
  /**
   * Normalize a list of key names for SPECIAL_KEYS lookup.
   *
   * Handles two problems:
   * 1. Modifier aliases: "control" → "ctrl", "command" → "meta", "option" → "alt"
   * 2. Comma-separated compound keys from stall classifier: ["control", "c"] → ["ctrl+c"]
   *    A bare modifier followed by a single char/key is joined with "+".
   */
  static normalizeKeyList(keys) {
    const MODIFIER_MAP = {
      control: "ctrl",
      command: "meta",
      cmd: "meta",
      option: "alt",
      opt: "alt"
    };
    const MODIFIER_NAMES = /* @__PURE__ */ new Set([
      "ctrl",
      "alt",
      "shift",
      "meta",
      // Also match the aliases so we can detect them before remapping
      ...Object.keys(MODIFIER_MAP)
    ]);
    const result = [];
    let i = 0;
    while (i < keys.length) {
      let key = keys[i].toLowerCase().trim();
      if (MODIFIER_MAP[key]) {
        key = MODIFIER_MAP[key];
      }
      if (MODIFIER_NAMES.has(key) && i + 1 < keys.length) {
        let nextKey = keys[i + 1].toLowerCase().trim();
        if (MODIFIER_MAP[nextKey]) {
          nextKey = MODIFIER_MAP[nextKey];
        }
        if (!MODIFIER_NAMES.has(nextKey)) {
          result.push(`${key}+${nextKey}`);
          i += 2;
          continue;
        }
      }
      result.push(key);
      i++;
    }
    return result;
  }
  /**
   * Select a TUI menu option by index (0-based).
   * Sends Down arrow `optionIndex` times, then Enter, with 50ms delays.
   */
  async selectMenuOption(optionIndex) {
    for (let i = 0; i < optionIndex; i++) {
      this.sendKeys("down");
      await this.delay(50);
    }
    this.sendKeys("enter");
  }
  /**
   * Send a sequence of keys with staggered timing.
   * Each key is sent 50ms apart using setTimeout to keep the caller synchronous.
   */
  sendKeySequence(keys) {
    keys.forEach((key, i) => {
      setTimeout(() => this.sendKeys(key), i * 50);
    });
  }
  /**
   * Paste text using bracketed paste mode
   *
   * Bracketed paste mode wraps the pasted text in escape sequences
   * that tell the terminal this is pasted content, not typed input.
   * This prevents issues with pasting text that contains special characters
   * or looks like commands.
   *
   * @param text - Text to paste
   * @param useBracketedPaste - Whether to use bracketed paste mode (default: true)
   */
  paste(text, useBracketedPaste = true) {
    if (!this.ptyProcess) {
      throw new Error("Session not started");
    }
    this._lastActivityAt = /* @__PURE__ */ new Date();
    if (useBracketedPaste) {
      this.ptyProcess.write(BRACKETED_PASTE_START + text + BRACKETED_PASTE_END);
      this.logger.debug(
        { sessionId: this.id, length: text.length },
        "Pasted text with bracketed paste mode"
      );
    } else {
      this.ptyProcess.write(text);
      this.logger.debug(
        { sessionId: this.id, length: text.length },
        "Pasted text without bracketed paste"
      );
    }
  }
  /**
   * Kill the PTY process
   */
  /**
   * Notify the session of an external hook event (e.g. from Claude Code HTTP hooks).
   * Resets the stall timer so hook-managed sessions don't get false stall escalations.
   * For "task_complete" events, transitions the session to ready.
   */
  notifyHookEvent(event) {
    switch (event) {
      case "tool_running":
        this._lastActivityAt = /* @__PURE__ */ new Date();
        this.resetStallTimer();
        break;
      case "task_complete":
        this._status = "ready";
        this._lastBlockingPromptHash = null;
        this._lastBlockingPromptEmitAt = 0;
        this.outputBuffer = "";
        this.clearStallTimer();
        this.emit("status_changed", "ready");
        this.emit("task_complete");
        this.logger.info(
          { sessionId: this.id, event },
          "Hook event: task_complete \u2192 ready"
        );
        break;
      case "permission_approved":
        this._lastActivityAt = /* @__PURE__ */ new Date();
        this.outputBuffer = "";
        this.resetStallTimer();
        break;
      default:
        this._lastActivityAt = /* @__PURE__ */ new Date();
        this.resetStallTimer();
        break;
    }
  }
  kill(signal) {
    if (this.ptyProcess) {
      this._status = "stopping";
      this.clearStallTimer();
      this.cancelTaskComplete();
      this.cancelReadySettle();
      this.ptyProcess.kill(signal);
      this.logger.info({ sessionId: this.id, signal }, "Killing PTY session");
    }
  }
  /**
   * Get current output buffer
   */
  getOutputBuffer() {
    return this.outputBuffer;
  }
  /**
   * Clear output buffer
   */
  clearOutputBuffer() {
    this.outputBuffer = "";
  }
  /**
   * Convert to SessionHandle
   */
  toHandle() {
    return {
      id: this.id,
      name: this.config.name,
      type: this.config.type,
      status: this._status,
      pid: this.pid,
      startedAt: this._startedAt ?? void 0,
      lastActivityAt: this._lastActivityAt ?? void 0
    };
  }
};

// src/pty-manager.ts
var PTYManager = class extends import_node_events2.EventEmitter {
  sessions = /* @__PURE__ */ new Map();
  outputLogs = /* @__PURE__ */ new Map();
  maxLogLines;
  logger;
  adapters;
  // Stall detection config
  _stallDetectionEnabled;
  _stallTimeoutMs;
  _onStallClassify;
  constructor(config = {}) {
    super();
    this.adapters = new import_adapter_types2.AdapterRegistry();
    this.logger = config.logger || consoleLogger;
    this.maxLogLines = config.maxLogLines || 1e3;
    this._stallDetectionEnabled = config.stallDetectionEnabled ?? false;
    this._stallTimeoutMs = config.stallTimeoutMs ?? 8e3;
    this._onStallClassify = config.onStallClassify;
  }
  /**
   * Register a CLI adapter
   */
  registerAdapter(adapter) {
    this.adapters.register(adapter);
  }
  /**
   * Spawn a new PTY session
   */
  async spawn(config) {
    const adapter = this.adapters.get(config.type);
    if (!adapter) {
      throw new Error(
        `No adapter found for type: ${config.type}. Registered adapters: ${this.adapters.list().join(", ") || "none"}`
      );
    }
    if (config.id && this.sessions.has(config.id)) {
      throw new Error(`Session with ID ${config.id} already exists`);
    }
    this.logger.info(
      { type: config.type, name: config.name },
      "Spawning session"
    );
    const session = new PTYSession(
      adapter,
      config,
      this.logger,
      this._stallDetectionEnabled,
      this._stallTimeoutMs
    );
    this.setupSessionEvents(session);
    this.sessions.set(session.id, session);
    this.outputLogs.set(session.id, []);
    await session.start();
    const handle = session.toHandle();
    this.emit("session_started", handle);
    return handle;
  }
  /**
   * Set up event handlers for a session
   */
  setupSessionEvents(session) {
    session.on("output", (data) => {
      const logs = this.outputLogs.get(session.id) || [];
      const lines = data.split("\n");
      logs.push(...lines);
      while (logs.length > this.maxLogLines) {
        logs.shift();
      }
      this.outputLogs.set(session.id, logs);
    });
    session.on("ready", () => {
      this.emit("session_ready", session.toHandle());
    });
    session.on("login_required", (instructions, url) => {
      this.emit("login_required", session.toHandle(), instructions, url);
    });
    session.on("auth_required", (info) => {
      this.emit("auth_required", session.toHandle(), info);
    });
    session.on(
      "blocking_prompt",
      (promptInfo, autoResponded) => {
        this.emit(
          "blocking_prompt",
          session.toHandle(),
          promptInfo,
          autoResponded
        );
      }
    );
    session.on("message", (message) => {
      this.emit("message", message);
    });
    session.on("question", (question) => {
      this.emit("question", session.toHandle(), question);
    });
    session.on("exit", (code) => {
      const reason = code === 0 ? "normal exit" : `exit code ${code}`;
      this.emit("session_stopped", session.toHandle(), reason);
    });
    session.on("error", (error) => {
      this.emit("session_error", session.toHandle(), error.message);
    });
    session.on("status_changed", () => {
      this.emit("session_status_changed", session.toHandle());
    });
    session.on("task_complete", () => {
      this.emit("task_complete", session.toHandle());
    });
    session.on("tool_running", (info) => {
      this.emit("tool_running", session.toHandle(), info);
    });
    session.on(
      "stall_detected",
      (recentOutput, stallDurationMs) => {
        const handle = session.toHandle();
        this.emit("stall_detected", handle, recentOutput, stallDurationMs);
        if (this._onStallClassify) {
          const sanitized = recentOutput.slice(-1500).replace(
            /\b(ignore|disregard|forget)\s+(all\s+)?(previous|above|prior)\s+(instructions?|prompts?|rules?)\b/gi,
            "[REDACTED]"
          ).replace(
            /\b(you\s+are|act\s+as|pretend\s+to\s+be|you\s+must|system\s*:)\b/gi,
            "[REDACTED]"
          );
          this._onStallClassify(session.id, sanitized, stallDurationMs).then((classification) => {
            session.handleStallClassification(classification);
          }).catch((err) => {
            this.logger.error(
              { sessionId: session.id, error: err },
              "Stall classification callback failed"
            );
            session.handleStallClassification(null);
          });
        }
      }
    );
  }
  /**
   * Stop a session
   */
  async stop(sessionId, options) {
    const session = this.sessions.get(sessionId);
    if (!session) {
      throw new Error(`Session not found: ${sessionId}`);
    }
    this.logger.info({ sessionId, force: options?.force }, "Stopping session");
    const timeout = options?.timeout || 5e3;
    return new Promise((resolve) => {
      const timer = setTimeout(() => {
        session.kill("SIGKILL");
        resolve();
      }, timeout);
      session.once("exit", () => {
        clearTimeout(timer);
        session.removeAllListeners();
        this.sessions.delete(sessionId);
        this.outputLogs.delete(sessionId);
        resolve();
      });
      session.kill(options?.force ? "SIGKILL" : "SIGTERM");
    });
  }
  /**
   * Stop all sessions
   */
  async stopAll(options) {
    const stopPromises = Array.from(this.sessions.keys()).map(
      (id) => this.stop(id, options).catch((err) => {
        this.logger.warn(
          { sessionId: id, error: err },
          "Error stopping session"
        );
      })
    );
    await Promise.all(stopPromises);
  }
  /**
   * Get a session by ID
   */
  get(sessionId) {
    const session = this.sessions.get(sessionId);
    return session ? session.toHandle() : null;
  }
  /**
   * List all sessions
   */
  list(filter) {
    const handles = [];
    for (const session of this.sessions.values()) {
      const handle = session.toHandle();
      if (filter) {
        if (filter.status) {
          const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
          if (!statuses.includes(handle.status)) continue;
        }
        if (filter.type) {
          const types = Array.isArray(filter.type) ? filter.type : [filter.type];
          if (!types.includes(handle.type)) continue;
        }
      }
      handles.push(handle);
    }
    return handles;
  }
  /**
   * Send a message to a session
   */
  send(sessionId, message) {
    const session = this.sessions.get(sessionId);
    if (!session) {
      throw new Error(`Session not found: ${sessionId}`);
    }
    return session.send(message);
  }
  /**
   * Get logs for a session
   */
  async *logs(sessionId, options) {
    const logBuffer = this.outputLogs.get(sessionId);
    if (!logBuffer) {
      throw new Error(`Session not found: ${sessionId}`);
    }
    const lines = options?.tail ? logBuffer.slice(-options.tail) : logBuffer;
    for (const line of lines) {
      yield line;
    }
  }
  /**
   * Get metrics for a session
   */
  metrics(sessionId) {
    const session = this.sessions.get(sessionId);
    if (!session) {
      return null;
    }
    const handle = session.toHandle();
    const uptime = handle.startedAt ? Math.floor((Date.now() - handle.startedAt.getTime()) / 1e3) : void 0;
    return { uptime };
  }
  /**
   * Shutdown manager and stop all sessions
   */
  async shutdown() {
    this.logger.info(
      { count: this.sessions.size },
      "Shutting down all sessions"
    );
    await this.stopAll({ timeout: 3e3 });
    this.sessions.clear();
    this.outputLogs.clear();
  }
  /**
   * Get count of sessions by status
   */
  getStatusCounts() {
    const counts = {
      pending: 0,
      starting: 0,
      authenticating: 0,
      ready: 0,
      busy: 0,
      stopping: 0,
      stopped: 0,
      error: 0
    };
    for (const session of this.sessions.values()) {
      counts[session.status]++;
    }
    return counts;
  }
  /**
   * Attach to a session's terminal for raw I/O streaming
   */
  attachTerminal(sessionId) {
    const session = this.sessions.get(sessionId);
    if (!session) {
      return null;
    }
    return {
      /**
       * Subscribe to raw terminal output
       * Returns an unsubscribe function
       */
      onData: (callback) => {
        session.on("output", callback);
        return () => session.off("output", callback);
      },
      /**
       * Write raw data to terminal (no formatting applied)
       */
      write: (data) => {
        session.writeRaw(data);
      },
      /**
       * Resize the terminal
       */
      resize: (cols, rows) => {
        session.resize(cols, rows);
      }
    };
  }
  /**
   * Check if a session exists
   */
  has(sessionId) {
    return this.sessions.has(sessionId);
  }
  /**
   * Get the underlying PTYSession (for advanced use)
   */
  getSession(sessionId) {
    return this.sessions.get(sessionId);
  }
  /**
   * Whether the adapter currently classifies the session as actively
   * processing work (e.g. Codex's "esc to interrupt" status row).
   *
   * Orchestrators (like milady's swarm idle watchdog) should consult
   * this before assuming a session is idle based on output byte diffs,
   * which are fooled by TUIs that redraw the same status row in place.
   *
   * Returns `false` for unknown sessions or adapters that don't
   * implement `detectLoading`.
   */
  isSessionLoading(sessionId) {
    return this.sessions.get(sessionId)?.isLoading() ?? false;
  }
  // ─────────────────────────────────────────────────────────────────────────────
  // Stall Detection Configuration
  // ─────────────────────────────────────────────────────────────────────────────
  /**
   * Configure stall detection at runtime.
   * Affects newly spawned sessions only — existing sessions keep their config.
   */
  configureStallDetection(enabled, timeoutMs, classify) {
    this._stallDetectionEnabled = enabled;
    if (timeoutMs !== void 0) {
      this._stallTimeoutMs = timeoutMs;
    }
    if (classify !== void 0) {
      this._onStallClassify = classify;
    }
  }
  // ─────────────────────────────────────────────────────────────────────────────
  // Runtime Auto-Response Rules API
  // ─────────────────────────────────────────────────────────────────────────────
  /**
   * Add an auto-response rule to a session.
   * Session rules are checked before adapter rules.
   */
  addAutoResponseRule(sessionId, rule) {
    const session = this.sessions.get(sessionId);
    if (!session) {
      throw new Error(`Session not found: ${sessionId}`);
    }
    session.addAutoResponseRule(rule);
  }
  /**
   * Remove an auto-response rule from a session by pattern.
   * Returns true if a rule was removed.
   */
  removeAutoResponseRule(sessionId, pattern) {
    const session = this.sessions.get(sessionId);
    if (!session) {
      throw new Error(`Session not found: ${sessionId}`);
    }
    return session.removeAutoResponseRule(pattern);
  }
  /**
   * Set all auto-response rules for a session, replacing existing ones.
   */
  setAutoResponseRules(sessionId, rules) {
    const session = this.sessions.get(sessionId);
    if (!session) {
      throw new Error(`Session not found: ${sessionId}`);
    }
    session.setAutoResponseRules(rules);
  }
  /**
   * Get all auto-response rules for a session.
   */
  getAutoResponseRules(sessionId) {
    const session = this.sessions.get(sessionId);
    if (!session) {
      throw new Error(`Session not found: ${sessionId}`);
    }
    return session.getAutoResponseRules();
  }
  /**
   * Clear all auto-response rules for a session.
   */
  clearAutoResponseRules(sessionId) {
    const session = this.sessions.get(sessionId);
    if (!session) {
      throw new Error(`Session not found: ${sessionId}`);
    }
    session.clearAutoResponseRules();
  }
};

// src/pty-worker.ts
var manager = new PTYManager();
manager.registerAdapter(new ShellAdapter());
var attachments = /* @__PURE__ */ new Map();
function emit(event) {
  process.stdout.write(`${JSON.stringify(event)}
`);
}
function ack(cmd, id, success = true, error) {
  emit({ event: "ack", cmd, id, success, error });
}
manager.on("session_started", (handle) => {
  emit({
    event: "spawned",
    id: handle.id,
    name: handle.name,
    type: handle.type,
    pid: handle.pid,
    cols: 120,
    // Default cols
    rows: 40
    // Default rows
  });
});
manager.on("session_ready", (handle) => {
  emit({ event: "ready", id: handle.id });
});
manager.on("session_stopped", (handle, reason) => {
  emit({
    event: "exit",
    id: handle.id,
    code: handle.exitCode || 0,
    reason
  });
  attachments.delete(handle.id);
});
manager.on("session_error", (handle, error) => {
  emit({ event: "error", id: handle.id, message: error });
});
manager.on(
  "blocking_prompt",
  (handle, promptInfo, autoResponded) => {
    emit({
      event: "blocking_prompt",
      id: handle.id,
      promptInfo,
      autoResponded
    });
  }
);
manager.on(
  "login_required",
  (handle, instructions, url) => {
    emit({
      event: "login_required",
      id: handle.id,
      instructions,
      url
    });
  }
);
manager.on("auth_required", (handle, info) => {
  emit({
    event: "auth_required",
    id: handle.id,
    info
  });
});
manager.on("message", (message) => {
  emit({
    event: "message",
    message: {
      ...message,
      timestamp: message.timestamp.toISOString()
    }
  });
});
manager.on("question", (handle, question) => {
  emit({
    event: "question",
    id: handle.id,
    question
  });
});
manager.on("session_status_changed", (handle) => {
  emit({
    event: "status_changed",
    id: handle.id,
    status: handle.status
  });
});
manager.on("task_complete", (handle) => {
  emit({
    event: "task_complete",
    id: handle.id
  });
});
manager.on("tool_running", (handle, info) => {
  emit({
    event: "tool_running",
    id: handle.id,
    info
  });
});
manager.on(
  "stall_detected",
  (handle, recentOutput, stallDurationMs) => {
    emit({
      event: "stall_detected",
      id: handle.id,
      recentOutput,
      stallDurationMs
    });
  }
);
async function handleSpawn(id, config) {
  try {
    if (manager.has(id)) {
      ack("spawn", id, false, `Session ${id} already exists`);
      return;
    }
    const handle = await manager.spawn({ ...config, id });
    const attachment = manager.attachTerminal(handle.id);
    if (attachment) {
      attachments.set(handle.id, attachment);
      attachment.onData((data) => {
        emit({ event: "output", id: handle.id, data });
      });
    }
    ack("spawn", id, true);
  } catch (err) {
    ack("spawn", id, false, err instanceof Error ? err.message : String(err));
  }
}
function handleSend(id, data) {
  try {
    manager.send(id, data);
    ack("send", id, true);
  } catch (err) {
    ack("send", id, false, err instanceof Error ? err.message : String(err));
  }
}
function handleSendKeys(id, keys) {
  try {
    const session = manager.getSession(id);
    if (!session) {
      ack("sendKeys", id, false, `Session ${id} not found`);
      return;
    }
    session.sendKeys(keys);
    ack("sendKeys", id, true);
  } catch (err) {
    ack(
      "sendKeys",
      id,
      false,
      err instanceof Error ? err.message : String(err)
    );
  }
}
function handleWriteRaw(id, data) {
  try {
    const session = manager.getSession(id);
    if (!session) {
      ack("writeRaw", id, false, `Session ${id} not found`);
      return;
    }
    session.writeRaw(data);
    ack("writeRaw", id, true);
  } catch (err) {
    ack(
      "writeRaw",
      id,
      false,
      err instanceof Error ? err.message : String(err)
    );
  }
}
function handlePaste(id, text, bracketed = true) {
  try {
    const session = manager.getSession(id);
    if (!session) {
      ack("paste", id, false, `Session ${id} not found`);
      return;
    }
    session.paste(text, bracketed);
    ack("paste", id, true);
  } catch (err) {
    ack("paste", id, false, err instanceof Error ? err.message : String(err));
  }
}
function handleResize(id, cols, rows) {
  try {
    const attachment = attachments.get(id);
    if (!attachment) {
      ack("resize", id, false, `Session ${id} not found or not attached`);
      return;
    }
    attachment.resize(cols, rows);
    ack("resize", id, true);
  } catch (err) {
    ack("resize", id, false, err instanceof Error ? err.message : String(err));
  }
}
async function handleKill(id, signal) {
  try {
    const force = signal === "SIGKILL";
    await manager.stop(id, force ? { force: true } : void 0);
    ack("kill", id, true);
  } catch (err) {
    ack("kill", id, false, err instanceof Error ? err.message : String(err));
  }
}
function handleIsSessionLoading(id) {
  try {
    const loading = manager.isSessionLoading(id);
    emit({ event: "isSessionLoading", id, loading });
  } catch (err) {
    emit({
      event: "isSessionLoading",
      id,
      loading: false,
      error: err instanceof Error ? err.message : String(err)
    });
  }
}
function handleList() {
  const sessions = manager.list();
  const sessionList = sessions.map((s) => ({
    id: s.id,
    name: s.name,
    type: s.type,
    status: s.status,
    pid: s.pid,
    cols: 120,
    rows: 40,
    startedAt: s.startedAt?.toISOString() || (/* @__PURE__ */ new Date()).toISOString(),
    lastActivityAt: s.lastActivityAt?.toISOString(),
    error: s.error,
    exitCode: s.exitCode
  }));
  emit({ event: "list", sessions: sessionList });
}
async function handleShutdown() {
  try {
    await manager.shutdown();
    ack("shutdown", void 0, true);
  } catch (err) {
    ack(
      "shutdown",
      void 0,
      false,
      err instanceof Error ? err.message : String(err)
    );
  }
  setTimeout(() => process.exit(0), 100);
}
function handleRegisterAdapters(modules) {
  try {
    for (const modulePath of modules) {
      const mod = require(modulePath);
      if (typeof mod.createAllAdapters === "function") {
        const adapters = mod.createAllAdapters();
        for (const adapter of adapters) {
          manager.registerAdapter(adapter);
        }
      } else if (Array.isArray(mod.default)) {
        for (const adapter of mod.default) {
          manager.registerAdapter(adapter);
        }
      } else if (mod.default && typeof mod.default.adapterType === "string") {
        manager.registerAdapter(mod.default);
      } else {
        throw new Error(
          `Module ${modulePath} does not export createAllAdapters() or adapters`
        );
      }
    }
    ack("registerAdapters", void 0, true);
  } catch (err) {
    ack(
      "registerAdapters",
      void 0,
      false,
      err instanceof Error ? err.message : String(err)
    );
  }
}
function deserializeRule(serialized) {
  return {
    pattern: new RegExp(serialized.pattern, serialized.flags || ""),
    type: serialized.type,
    response: serialized.response,
    responseType: serialized.responseType,
    keys: serialized.keys,
    description: serialized.description,
    safe: serialized.safe,
    once: serialized.once
  };
}
function serializeRule(rule) {
  return {
    pattern: rule.pattern.source,
    flags: rule.pattern.flags || void 0,
    type: rule.type,
    response: rule.response,
    responseType: rule.responseType,
    keys: rule.keys,
    description: rule.description,
    safe: rule.safe,
    once: rule.once
  };
}
function handleAddRule(id, serializedRule) {
  try {
    const rule = deserializeRule(serializedRule);
    manager.addAutoResponseRule(id, rule);
    ack("addRule", id, true);
  } catch (err) {
    ack("addRule", id, false, err instanceof Error ? err.message : String(err));
  }
}
function handleRemoveRule(id, pattern, flags) {
  try {
    const regex = new RegExp(pattern, flags || "");
    const removed = manager.removeAutoResponseRule(id, regex);
    ack("removeRule", id, removed, removed ? void 0 : "Rule not found");
  } catch (err) {
    ack(
      "removeRule",
      id,
      false,
      err instanceof Error ? err.message : String(err)
    );
  }
}
function handleSetRules(id, serializedRules) {
  try {
    const rules = serializedRules.map(deserializeRule);
    manager.setAutoResponseRules(id, rules);
    ack("setRules", id, true);
  } catch (err) {
    ack(
      "setRules",
      id,
      false,
      err instanceof Error ? err.message : String(err)
    );
  }
}
function handleGetRules(id) {
  try {
    const rules = manager.getAutoResponseRules(id);
    const serialized = rules.map(serializeRule);
    emit({ event: "rules", id, rules: serialized });
  } catch (err) {
    emit({
      event: "error",
      id,
      message: err instanceof Error ? err.message : String(err)
    });
  }
}
function handleClearRules(id) {
  try {
    manager.clearAutoResponseRules(id);
    ack("clearRules", id, true);
  } catch (err) {
    ack(
      "clearRules",
      id,
      false,
      err instanceof Error ? err.message : String(err)
    );
  }
}
function handleConfigureStallDetection(enabled, timeoutMs) {
  try {
    manager.configureStallDetection(enabled, timeoutMs);
    ack("configureStallDetection", void 0, true);
  } catch (err) {
    ack(
      "configureStallDetection",
      void 0,
      false,
      err instanceof Error ? err.message : String(err)
    );
  }
}
function handleSelectMenuOption(id, optionIndex) {
  const session = manager.getSession(id);
  if (!session) {
    ack("selectMenuOption", id, false, `Session ${id} not found`);
    return;
  }
  session.selectMenuOption(optionIndex).then(() => ack("selectMenuOption", id, true)).catch(
    (err) => ack(
      "selectMenuOption",
      id,
      false,
      err instanceof Error ? err.message : String(err)
    )
  );
}
function handleClassifyStallResult(id, classification) {
  try {
    const session = manager.getSession(id);
    if (!session) {
      ack("classifyStallResult", id, false, `Session ${id} not found`);
      return;
    }
    session.handleStallClassification(classification);
    ack("classifyStallResult", id, true);
  } catch (err) {
    ack(
      "classifyStallResult",
      id,
      false,
      err instanceof Error ? err.message : String(err)
    );
  }
}
function handleNotifyHookEvent(id, hookEvent) {
  try {
    const session = manager.getSession(id);
    if (!session) {
      ack("notifyHookEvent", id, false, `Session ${id} not found`);
      return;
    }
    session.notifyHookEvent(hookEvent);
    ack("notifyHookEvent", id, true);
  } catch (err) {
    ack(
      "notifyHookEvent",
      id,
      false,
      err instanceof Error ? err.message : String(err)
    );
  }
}
function processCommand(line) {
  let command;
  try {
    command = JSON.parse(line);
  } catch {
    emit({ event: "error", message: "Invalid JSON command" });
    return;
  }
  switch (command.cmd) {
    case "spawn":
      if (!command.id || !command.config) {
        ack("spawn", command.id, false, "Missing id or config");
        return;
      }
      handleSpawn(command.id, command.config);
      break;
    case "send":
      if (!command.id || command.data === void 0) {
        ack("send", command.id, false, "Missing id or data");
        return;
      }
      handleSend(command.id, command.data);
      break;
    case "sendKeys":
      if (!command.id || !command.keys) {
        ack("sendKeys", command.id, false, "Missing id or keys");
        return;
      }
      handleSendKeys(command.id, command.keys);
      break;
    case "writeRaw":
      if (!command.id || command.data === void 0) {
        ack("writeRaw", command.id, false, "Missing id or data");
        return;
      }
      handleWriteRaw(command.id, command.data);
      break;
    case "paste":
      if (!command.id || command.text === void 0) {
        ack("paste", command.id, false, "Missing id or text");
        return;
      }
      handlePaste(command.id, command.text, command.bracketed);
      break;
    case "resize":
      if (!command.id || !command.cols || !command.rows) {
        ack("resize", command.id, false, "Missing id, cols, or rows");
        return;
      }
      handleResize(command.id, command.cols, command.rows);
      break;
    case "kill":
      if (!command.id) {
        ack("kill", command.id, false, "Missing id");
        return;
      }
      handleKill(command.id, command.signal);
      break;
    case "list":
      handleList();
      break;
    case "isSessionLoading":
      if (!command.id) {
        ack("isSessionLoading", command.id, false, "Missing id");
        return;
      }
      handleIsSessionLoading(command.id);
      break;
    case "shutdown":
      handleShutdown();
      break;
    case "registerAdapters":
      if (!command.modules || !Array.isArray(command.modules)) {
        ack("registerAdapters", void 0, false, "Missing modules array");
        return;
      }
      handleRegisterAdapters(command.modules);
      break;
    case "addRule":
      if (!command.id || !command.rule) {
        ack("addRule", command.id, false, "Missing id or rule");
        return;
      }
      handleAddRule(command.id, command.rule);
      break;
    case "removeRule":
      if (!command.id || !command.pattern) {
        ack("removeRule", command.id, false, "Missing id or pattern");
        return;
      }
      handleRemoveRule(command.id, command.pattern, command.flags);
      break;
    case "setRules":
      if (!command.id || !command.rules) {
        ack("setRules", command.id, false, "Missing id or rules");
        return;
      }
      handleSetRules(command.id, command.rules);
      break;
    case "getRules":
      if (!command.id) {
        ack("getRules", command.id, false, "Missing id");
        return;
      }
      handleGetRules(command.id);
      break;
    case "clearRules":
      if (!command.id) {
        ack("clearRules", command.id, false, "Missing id");
        return;
      }
      handleClearRules(command.id);
      break;
    case "selectMenuOption":
      if (!command.id || command.optionIndex === void 0) {
        ack("selectMenuOption", command.id, false, "Missing id or optionIndex");
        return;
      }
      handleSelectMenuOption(command.id, command.optionIndex);
      break;
    case "configureStallDetection":
      handleConfigureStallDetection(
        command.enabled ?? false,
        command.timeoutMs
      );
      break;
    case "classifyStallResult":
      if (!command.id) {
        ack("classifyStallResult", command.id, false, "Missing id");
        return;
      }
      handleClassifyStallResult(command.id, command.classification ?? null);
      break;
    case "notifyHookEvent":
      if (!command.id || !command.hookEvent) {
        ack("notifyHookEvent", command.id, false, "Missing id or hookEvent");
        return;
      }
      handleNotifyHookEvent(command.id, command.hookEvent);
      break;
    default:
      emit({ event: "error", message: `Unknown command: ${command.cmd}` });
  }
}
var rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: false
});
rl.on("line", processCommand);
rl.on("close", () => {
  handleShutdown();
});
process.on("SIGTERM", () => handleShutdown());
process.on("SIGINT", () => handleShutdown());
emit({ event: "worker_ready" });
