#!/usr/bin/env bun
// @bun

// src/agents/autoaccept.ts
import { execFileSync } from "node:child_process";
var PROMPTS = [
  {
    name: "dev-channels-loading",
    match: /Loading.{1,30}development.{1,30}channels/,
    keys: ["Enter"]
  },
  {
    name: "dev-channels-local",
    match: /using this for local development/,
    keys: ["Enter"]
  },
  {
    name: "dev-channels",
    match: /I.{0,5}accept.{0,80}development.{0,10}channels/,
    keys: ["Down", "Enter"]
  },
  {
    name: "mcp-trust",
    match: /Use this and all future MCP servers/,
    keys: ["Enter"]
  },
  {
    name: "theme",
    match: /Choose.{1,30}text.{1,30}style/,
    keys: ["Enter"]
  },
  {
    name: "provider",
    match: /Anthropic.{1,80}Bedrock/,
    keys: ["Enter"]
  },
  {
    name: "enter-to-confirm",
    match: /Enter.{1,30}confirm/,
    keys: ["Enter"]
  }
];
var DEFAULT_IDLE_MS = 30000;
var DEFAULT_POLL_MS = 250;
function defaultSleep(ms) {
  return new Promise((r) => setTimeout(r, ms));
}
function capturePane(agentName) {
  const socket = `switchroom-${agentName}`;
  try {
    const out = execFileSync("tmux", ["-L", socket, "capture-pane", "-p", "-t", agentName], {
      timeout: 3000,
      stdio: ["ignore", "pipe", "pipe"],
      maxBuffer: 4 * 1024 * 1024
    });
    return out.toString("utf8");
  } catch (err) {
    console.error(`[autoaccept] ${agentName}: capture-pane failed: ${err.message}`);
    return "";
  }
}
function sendKeys(agentName, keys) {
  const socket = `switchroom-${agentName}`;
  try {
    execFileSync("tmux", ["-L", socket, "send-keys", "-t", agentName, ...keys], { timeout: 3000, stdio: ["ignore", "pipe", "pipe"] });
    return true;
  } catch (err) {
    console.error(`[autoaccept] ${agentName}: send-keys ${keys.join(" ")} failed: ${err.message}`);
    return false;
  }
}
async function runAutoaccept(opts) {
  const idleTimeoutMs = opts.idleTimeoutMs ?? DEFAULT_IDLE_MS;
  const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_MS;
  const rules = (opts.prompts ?? PROMPTS).map((r) => ({
    rule: r,
    fired: 0,
    cap: r.maxFires ?? 1
  }));
  const fired = [];
  const now = opts.now ?? Date.now;
  const sleep = opts.sleep ?? defaultSleep;
  const maxPolls = opts.maxPolls ?? Number.POSITIVE_INFINITY;
  let lastFire = now();
  let polls = 0;
  while (polls < maxPolls) {
    polls++;
    const text = capturePane(opts.agentName);
    let matchedThisPoll = false;
    if (text) {
      for (const entry of rules) {
        if (entry.fired >= entry.cap)
          continue;
        if (entry.rule.match.test(text)) {
          entry.fired++;
          matchedThisPoll = true;
          fired.push(entry.rule.name);
          console.error(`[autoaccept] ${opts.agentName}: fired ${entry.rule.name} (${entry.rule.keys.join("+")})`);
          sendKeys(opts.agentName, entry.rule.keys);
        }
      }
    }
    if (matchedThisPoll) {
      lastFire = now();
    } else if (now() - lastFire >= idleTimeoutMs) {
      return { fired, reason: "idle-timeout" };
    }
    await sleep(pollIntervalMs);
  }
  return { fired, reason: "manual-stop" };
}

// src/agents/autoaccept.ts
import { execFileSync as execFileSync2 } from "node:child_process";
var PROMPTS2 = [
  {
    name: "dev-channels-loading",
    match: /Loading.{1,30}development.{1,30}channels/,
    keys: ["Enter"]
  },
  {
    name: "dev-channels-local",
    match: /using this for local development/,
    keys: ["Enter"]
  },
  {
    name: "dev-channels",
    match: /I.{0,5}accept.{0,80}development.{0,10}channels/,
    keys: ["Down", "Enter"]
  },
  {
    name: "mcp-trust",
    match: /Use this and all future MCP servers/,
    keys: ["Enter"]
  },
  {
    name: "theme",
    match: /Choose.{1,30}text.{1,30}style/,
    keys: ["Enter"]
  },
  {
    name: "provider",
    match: /Anthropic.{1,80}Bedrock/,
    keys: ["Enter"]
  },
  {
    name: "enter-to-confirm",
    match: /Enter.{1,30}confirm/,
    keys: ["Enter"]
  }
];
function capturePane2(agentName) {
  const socket = `switchroom-${agentName}`;
  try {
    const out = execFileSync2("tmux", ["-L", socket, "capture-pane", "-p", "-t", agentName], {
      timeout: 3000,
      stdio: ["ignore", "pipe", "pipe"],
      maxBuffer: 4 * 1024 * 1024
    });
    return out.toString("utf8");
  } catch (err) {
    console.error(`[autoaccept] ${agentName}: capture-pane failed: ${err.message}`);
    return "";
  }
}
function sendKeys2(agentName, keys) {
  const socket = `switchroom-${agentName}`;
  try {
    execFileSync2("tmux", ["-L", socket, "send-keys", "-t", agentName, ...keys], { timeout: 3000, stdio: ["ignore", "pipe", "pipe"] });
    return true;
  } catch (err) {
    console.error(`[autoaccept] ${agentName}: send-keys ${keys.join(" ")} failed: ${err.message}`);
    return false;
  }
}

// src/agents/wedge-watchdog.ts
var WEDGE_FOOTER_SIGNATURE = /(?=[\s\S]*[Ee]sc(?:ape)?[^\n]*cancel)(?=[\s\S]*(?:to select|to navigate|\u2191\/\u2193))/;
var RATE_LIMIT_MENU_SIGNATURE = /(?=[\s\S]*Stop and wait for)(?=[\s\S]*(?:usage credits|Upgrade your plan|\/rate-limit-options))/;
var MONTHS = {
  jan: 0,
  feb: 1,
  mar: 2,
  apr: 3,
  may: 4,
  jun: 5,
  jul: 6,
  aug: 7,
  sep: 8,
  oct: 9,
  nov: 10,
  dec: 11
};
function parseWeeklyReset(text, nowMs = Date.now()) {
  const m = text.match(/resets\s+([A-Za-z]{3,9})\s+(\d{1,2}),?\s+(\d{1,2})(?::(\d{2}))?\s*([ap]m)?\s*(?:\(([^)]+)\))?/i);
  if (!m)
    return null;
  const mon = MONTHS[m[1].slice(0, 3).toLowerCase()];
  if (mon === undefined)
    return null;
  const day = Number(m[2]);
  let hour = Number(m[3]);
  const minute = m[4] ? Number(m[4]) : 0;
  const ampm = m[5]?.toLowerCase();
  if (ampm === "pm" && hour < 12)
    hour += 12;
  if (ampm === "am" && hour === 12)
    hour = 0;
  if (!Number.isFinite(day) || !Number.isFinite(hour) || day < 1 || day > 31 || hour > 23) {
    return null;
  }
  const tz = m[6]?.trim();
  const probeYear = new Date(nowMs).getUTCFullYear();
  for (const year of [probeYear, probeYear + 1]) {
    const epoch = wallClockToEpoch(year, mon, day, hour, minute, tz);
    if (epoch != null && epoch > nowMs - 60000)
      return epoch;
  }
  return null;
}
function wallClockToEpoch(year, month, day, hour, minute, tz) {
  const asUtc = Date.UTC(year, month, day, hour, minute, 0);
  if (!tz)
    return asUtc;
  try {
    const fmt = new Intl.DateTimeFormat("en-US", {
      timeZone: tz,
      year: "numeric",
      month: "2-digit",
      day: "2-digit",
      hour: "2-digit",
      minute: "2-digit",
      second: "2-digit",
      hour12: false
    });
    const parts = Object.fromEntries(fmt.formatToParts(new Date(asUtc)).filter((p) => p.type !== "literal").map((p) => [p.type, p.value]));
    const shown = Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day), Number(parts.hour) % 24, Number(parts.minute), Number(parts.second));
    const offset = shown - asUtc;
    return asUtc - offset;
  } catch {
    return null;
  }
}
var DEFAULT_POLL_MS2 = 5000;
var DEFAULT_STABILITY_THRESHOLD = 3;
var DEFAULT_COOLDOWN_MS = 60000;
function defaultSleep2(ms) {
  return new Promise((r) => setTimeout(r, ms));
}
function stabilityKey(text) {
  return text.split(`
`).map((l) => l.replace(/\s+$/, "")).join(`
`);
}
async function runWedgeWatchdog(opts) {
  const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_MS2;
  const stabilityThreshold = opts.stabilityThreshold ?? DEFAULT_STABILITY_THRESHOLD;
  const cooldownMs = opts.cooldownMs ?? DEFAULT_COOLDOWN_MS;
  const deferToPrompts = opts.deferToPrompts ?? PROMPTS2;
  const signature = opts.wedgeSignature ?? WEDGE_FOOTER_SIGNATURE;
  const rateLimitSignature = opts.rateLimitSignature === null ? null : opts.rateLimitSignature ?? RATE_LIMIT_MENU_SIGNATURE;
  const onRateLimitMenu = opts.onRateLimitMenu;
  const parseReset = opts.parseReset ?? parseWeeklyReset;
  const maxPolls = opts.maxPolls ?? Number.POSITIVE_INFINITY;
  const now = opts.now ?? Date.now;
  const sleep = opts.sleep ?? defaultSleep2;
  const capture = opts.capture ?? capturePane2;
  const send = opts.send ?? sendKeys2;
  let stableCount = 0;
  let lastKey = null;
  let cooldownUntil = 0;
  let fires = 0;
  let rateLimitFires = 0;
  let polls = 0;
  while (polls < maxPolls) {
    polls++;
    let text = "";
    try {
      text = capture(opts.agentName);
    } catch (err) {
      console.error(`[wedge-watchdog] ${opts.agentName}: capture threw: ${err.message}`);
      text = "";
    }
    const isRateLimitMenu = !!text && rateLimitSignature !== null && rateLimitSignature.test(text);
    const isBlockingModal = !isRateLimitMenu && !!text && signature.test(text) && !deferToPrompts.some((p) => p.match.test(text));
    if (isRateLimitMenu) {
      const key = stabilityKey(text);
      if (key === lastKey) {
        stableCount++;
      } else {
        stableCount = 1;
        lastKey = key;
      }
      if (stableCount >= stabilityThreshold && now() >= cooldownUntil) {
        const resetAt = parseReset(text, now());
        console.error(`[wedge-watchdog] ${opts.agentName}: rate-limit (weekly-quota) menu detected ` + `after ${stableCount} stable polls \u2014 signalling failover` + (resetAt != null ? ` (resets ${new Date(resetAt).toISOString()})` : " (reset unparsed)") + ` then parking with Esc`);
        if (onRateLimitMenu) {
          try {
            onRateLimitMenu(opts.agentName, resetAt);
          } catch (err) {
            console.error(`[wedge-watchdog] ${opts.agentName}: onRateLimitMenu threw: ${err.message}`);
          }
        }
        try {
          send(opts.agentName, ["Escape"]);
        } catch (err) {
          console.error(`[wedge-watchdog] ${opts.agentName}: send threw: ${err.message}`);
        }
        fires++;
        rateLimitFires++;
        cooldownUntil = now() + cooldownMs;
        stableCount = 0;
        lastKey = null;
      }
    } else if (isBlockingModal) {
      const key = stabilityKey(text);
      if (key === lastKey) {
        stableCount++;
      } else {
        stableCount = 1;
        lastKey = key;
      }
      if (stableCount >= stabilityThreshold && now() >= cooldownUntil) {
        console.error(`[wedge-watchdog] ${opts.agentName}: dismissing stuck blocking prompt ` + `(Esc) after ${stableCount} stable polls (~${stableCount * pollIntervalMs / 1000}s) \u2014 no human to answer it`);
        try {
          send(opts.agentName, ["Escape"]);
        } catch (err) {
          console.error(`[wedge-watchdog] ${opts.agentName}: send threw: ${err.message}`);
        }
        fires++;
        cooldownUntil = now() + cooldownMs;
        stableCount = 0;
        lastKey = null;
      }
    } else {
      stableCount = 0;
      lastKey = null;
    }
    await sleep(pollIntervalMs);
  }
  return { fires, rateLimitFires, polls, reason: "max-polls" };
}

// src/agents/rate-limit-signal.ts
import { createConnection } from "node:net";
import { join } from "node:path";
function resolveGatewaySocketPath() {
  const stateDir = process.env.TELEGRAM_STATE_DIR ?? "/state/agent/telegram";
  return process.env.SWITCHROOM_GATEWAY_SOCKET ?? join(stateDir, "gateway.sock");
}
function signalQuotaWall(agentName, resetAt, opts = {}) {
  const socketPath = opts.socketPath ?? resolveGatewaySocketPath();
  const connect = opts._connect ?? ((p) => createConnection(p));
  const log = opts._log ?? ((m) => console.error(m));
  const connectTimeoutMs = opts.connectTimeoutMs ?? 5000;
  const msg = { type: "quota_wall_detected", agentName };
  if (resetAt != null)
    msg.resetAt = resetAt;
  const line = JSON.stringify(msg) + `
`;
  return new Promise((resolve) => {
    let settled = false;
    const done = (ok) => {
      if (settled)
        return;
      settled = true;
      resolve(ok);
    };
    let sock;
    try {
      sock = connect(socketPath);
    } catch (err) {
      log(`[rate-limit-signal] ${agentName}: connect threw: ${err.message}`);
      done(false);
      return;
    }
    const timer = setTimeout(() => {
      log(`[rate-limit-signal] ${agentName}: connect timed out`);
      try {
        sock.destroy();
      } catch {}
      done(false);
    }, connectTimeoutMs);
    sock.on("connect", () => {
      try {
        sock.write(line, () => {
          clearTimeout(timer);
          try {
            sock.end();
          } catch {}
          log(`[rate-limit-signal] ${agentName}: quota_wall_detected sent`);
          done(true);
        });
      } catch (err) {
        clearTimeout(timer);
        log(`[rate-limit-signal] ${agentName}: write threw: ${err.message}`);
        try {
          sock.destroy();
        } catch {}
        done(false);
      }
    });
    sock.on("error", (err) => {
      clearTimeout(timer);
      log(`[rate-limit-signal] ${agentName}: socket error: ${err.message}`);
      done(false);
    });
  });
}

// src/cli/autoaccept-poll.ts
async function main() {
  const agentName = process.argv[2];
  if (!agentName) {
    console.error("[autoaccept-poll] missing agent name argv");
    process.exit(0);
  }
  try {
    const res = await runAutoaccept({ agentName });
    console.error(`[autoaccept-poll] ${agentName}: boot done reason=${res.reason} fired=${res.fired.length ? res.fired.join(",") : "(none)"}`);
  } catch (err) {
    console.error(`[autoaccept-poll] ${agentName}: boot unexpected throw: ${err.message}`);
  }
  if (process.env.SWITCHROOM_WEDGE_WATCHDOG === "0") {
    console.error(`[autoaccept-poll] ${agentName}: wedge-watchdog disabled (SWITCHROOM_WEDGE_WATCHDOG=0) \u2014 exiting after boot phase`);
    process.exit(0);
  }
  const rateLimitDetect = process.env.SWITCHROOM_RATE_LIMIT_DETECT !== "0";
  try {
    console.error(`[autoaccept-poll] ${agentName}: entering wedge-watchdog (continuous)` + (rateLimitDetect ? " +rate-limit-detect" : " (rate-limit-detect OFF)"));
    const res = await runWedgeWatchdog({
      agentName,
      rateLimitSignature: rateLimitDetect ? undefined : null,
      onRateLimitMenu: rateLimitDetect ? (name, resetAt) => {
        signalQuotaWall(name, resetAt);
      } : undefined
    });
    console.error(`[autoaccept-poll] ${agentName}: wedge-watchdog returned reason=${res.reason} fires=${res.fires} rateLimitFires=${res.rateLimitFires}`);
  } catch (err) {
    console.error(`[autoaccept-poll] ${agentName}: wedge-watchdog unexpected throw: ${err.message}`);
  }
  process.exit(0);
}
main();
