// Force strict mode and setup for ESM
"use strict";
import {
  HEADLESS_YOLO_NO_SANDBOX_WARNING
} from "./chunk-BJBWRCSK.js";
import {
  normalizeServeChannelSelection
} from "./chunk-MX2YXRER.js";
import {
  loadSettings
} from "./chunk-AFPB2TZB.js";
import {
  DEFAULT_RING_SIZE
} from "./chunk-ALX6BUYW.js";
import {
  writeStderrLine
} from "./chunk-Y6Z2O3WR.js";
import {
  MCP_BUDGET_WARN_FRACTION,
  parsePositiveIntegerEnv
} from "./chunk-IQ7FOOIH.js";
import {
  openBrowserSecurely,
  shouldLaunchBrowser
} from "./chunk-22IFUCVR.js";
import {
  init_esbuild_shims
} from "./chunk-A4BMJM77.js";
import {
  __name
} from "./chunk-J2S4EL5Y.js";

// packages/cli/src/commands/serve.ts
init_esbuild_shims();
function blockForever() {
  return new Promise(() => {
  });
}
__name(blockForever, "blockForever");
async function maybeOpenWebShellBrowser(handle, open) {
  if (!open || !handle.webShellMounted || !shouldLaunchBrowser()) return;
  try {
    await handle.runtimeReady;
  } catch (runtimeErr) {
    writeStderrLine(
      `qwen serve: Web Shell runtime not ready; skipping --open: ${runtimeErr instanceof Error ? runtimeErr.message : String(runtimeErr)}`
    );
    return;
  }
  try {
    const target = new URL(handle.url);
    if (target.hostname === "0.0.0.0" || target.hostname === "[::]") {
      target.hostname = "127.0.0.1";
    }
    if (handle.resolvedToken) {
      target.hash = `token=${encodeURIComponent(handle.resolvedToken)}`;
      writeStderrLine(
        "qwen serve: --open passes the token in the browser launch command (visible via `ps` / /proc); on a multi-user host open the URL manually instead."
      );
    }
    await openBrowserSecurely(target.toString());
  } catch (browserErr) {
    writeStderrLine(
      `qwen serve: failed to open browser: ${browserErr instanceof Error ? browserErr.message : String(browserErr)}`
    );
  }
}
__name(maybeOpenWebShellBrowser, "maybeOpenWebShellBrowser");
var serveCommand = {
  command: "serve",
  describe: "Run Qwen Code as a local HTTP daemon (Stage 1 experimental: --http-bridge)",
  builder: /* @__PURE__ */ __name((yargs) => yargs.option("port", {
    type: "number",
    default: 4170,
    description: "TCP port to bind (use 0 for an OS-assigned ephemeral port)"
  }).option("hostname", {
    type: "string",
    default: "127.0.0.1",
    description: "Interface to bind. Loopback (127.0.0.1, localhost, ::1, [::1]) is auth-free; anything else requires a token."
  }).option("token", {
    type: "string",
    description: "Bearer token required on every request. Falls back to the QWEN_SERVER_TOKEN env var."
  }).option("max-sessions", {
    type: "number",
    default: 20,
    description: "Cap on concurrent live sessions. New spawn requests beyond this return 503; attach to existing sessions still works. Set to 0 to disable."
  }).option("max-pending-prompts-per-session", {
    type: "number",
    default: 5,
    description: "Per-session cap on accepted prompts waiting or running. New prompts beyond this return 503. Set to 0 to disable."
  }).option("workspace", {
    type: "string",
    description: "Absolute workspace path this daemon binds to. POST /session requests with a mismatched cwd return 400 workspace_mismatch. Defaults to process.cwd() when omitted. For multi-workspace deployments, run one `qwen serve` per workspace on separate ports (or behind an external orchestrator)."
  }).option("max-connections", {
    type: "number",
    default: 256,
    description: "Listener-level TCP connection cap (server.maxConnections). Bounds raw sockets \u2014 slow/phantom SSE clients get rejected at accept time once full. Set to 0 to disable."
  }).option("require-auth", {
    type: "boolean",
    default: false,
    description: "Refuse to start without a bearer token, even on loopback. Hardens the loopback developer default for shared dev hosts / CI runners / multi-tenant workstations where any local user can hit 127.0.0.1. Requires --token or QWEN_SERVER_TOKEN. /health also requires Authorization when enabled (no loopback exemption \u2014 k8s/Compose probes must pass the bearer too)."
  }).option("enable-session-shell", {
    type: "boolean",
    default: false,
    description: "Enable direct POST /session/:id/shell execution. Requires a bearer token and a session-bound client id on each call."
  }).option("tls-cert", {
    type: "string",
    description: "Path to a PEM certificate file. Serve over HTTPS instead of HTTP. Required for secure-context browser APIs (voice input/getUserMedia, WebRTC) when accessed over a LAN IP. Must be used together with --tls-key. Generate a local cert with mkcert."
  }).option("tls-key", {
    type: "string",
    description: "Path to a PEM private key file. Must be used together with --tls-cert."
  }).option("experimental-lsp", {
    type: "boolean",
    default: false,
    description: "Forward the experimental LSP opt-in to spawned agent sessions."
  }).option("channel", {
    type: "string",
    array: true,
    description: "Experimental: start a daemon-managed channel worker for the named channel. Repeat to select multiple channels, or use --channel all."
  }).option("web", {
    type: "boolean",
    default: true,
    description: "Serve the Web Shell UI at the daemon root path. Use --no-web for an API-only daemon."
  }).option("open", {
    type: "boolean",
    default: false,
    description: "Open the Web Shell in a browser once the daemon is listening. With a token configured, the launch URL (token included) is handed to the browser launcher and is visible in the process list, so prefer opening the URL manually on multi-user hosts. No-op with --no-web, when the UI assets are absent, or in headless/CI/SSH environments."
  }).option("event-ring-size", {
    type: "number",
    // Single source of truth — `DEFAULT_RING_SIZE` is also what
    // the bridge falls back to when the
    // option is undefined. Importing here keeps a future bump in
    // one place rather than drifting between CLI and bus.
    default: DEFAULT_RING_SIZE,
    description: "Per-session SSE replay ring depth. Sets the replay backlog available to `GET /session/:id/events` reconnects that send a `Last-Event-ID: N` header. Larger = more reconnect headroom at the cost of a few hundred KB extra RAM per session. Must be a positive finite integer."
  }).option("http-bridge", {
    type: "boolean",
    default: true,
    description: "Stage 1 mode: one `qwen --acp` child per daemon (the daemon binds to one workspace at boot, multiplexing N sessions onto that child via the agent's native `newSession()`). Stage 2 native in-process mode is not yet implemented; this flag will become opt-in then."
  }).option("mcp-client-budget", {
    type: "number",
    description: "Cap on live MCP clients spawned inside the ACP child for the bound workspace. Positive integer. Combine with --mcp-budget-mode to control behavior at the cap. When unset, mode defaults to off (no accounting-driven enforcement, but GET /workspace/mcp still reports `clientCount`). Distinct from claude-code MCP_SERVER_CONNECTION_BATCH_SIZE which gates startup concurrency, not the total client count."
  }).option("mcp-budget-mode", {
    choices: ["enforce", "warn", "off"],
    description: 'How --mcp-client-budget is enforced. `warn` (default when budget set): no refusal, snapshot surfaces warning at >=75% of budget. `enforce`: connects past the cap are refused (`disabledReason: "budget"`, deterministic by mcpServers declaration order). `off`: pure observability. Boot rejects `enforce` without a budget.'
  }).option("allow-origin", {
    type: "string",
    array: true,
    description: "Cross-origin allowlist for browser webui clients."
  }).option("allow-private-auth-base-url", {
    type: "boolean",
    default: false,
    description: "Allow /workspace/auth/provider to install localhost/private-network baseUrl values. Use only for local development with trusted clients."
  }).option("prompt-deadline-ms", {
    type: "number",
    description: "Server-side wallclock cap on POST /session/:id/prompt (ms). Falls back to QWEN_SERVE_PROMPT_DEADLINE_MS. Positive integer."
  }).option("writer-idle-timeout-ms", {
    type: "number",
    description: "Per-SSE-connection idle deadline (ms). Falls back to QWEN_SERVE_WRITER_IDLE_TIMEOUT_MS. Positive integer."
  }).option("channel-idle-timeout-ms", {
    type: "number",
    description: "Milliseconds to keep ACP child alive after last session closes. 0 or unset = immediate kill (default)."
  }).option("session-reap-interval-ms", {
    type: "number",
    description: "Session reaper scan interval (ms). 0 = disabled. Default: 60000."
  }).option("session-idle-timeout-ms", {
    type: "number",
    description: "Idle timeout before a disconnected session is reaped (ms). 0 = disabled. Default: 1800000 (30 min)."
  }).option("permission-response-timeout-ms", {
    type: "number",
    description: "Wall-clock timeout for a single human permission / ask_user_question response in daemon (ACP) mode (ms). 0 = disabled (wait forever). Default: 300000 (5 min)."
  }).option("rate-limit", {
    type: "boolean",
    description: "Enable per-tier HTTP rate limiting. Tiers: prompt (10/min), mutation (30/min), read (120/min). Health, heartbeat, SSE, and /acp are exempt."
  }).option("rate-limit-prompt", {
    type: "number",
    description: "Max prompt requests per window per client (default 10). Requires --rate-limit."
  }).option("rate-limit-mutation", {
    type: "number",
    description: "Max mutation requests per window per client (default 30). Requires --rate-limit."
  }).option("rate-limit-read", {
    type: "number",
    description: "Max read requests per window per client (default 120). Requires --rate-limit."
  }).option("rate-limit-window-ms", {
    type: "number",
    description: "Rate limit window duration in ms (default 60000). Requires --rate-limit."
  }), "builder"),
  handler: /* @__PURE__ */ __name(async (argv) => {
    if (!argv["http-bridge"]) {
      writeStderrLine(
        "qwen serve: --no-http-bridge (native mode) is not yet implemented; falling back to http-bridge."
      );
    }
    if (argv.token) {
      writeStderrLine(
        "qwen serve: --token is visible in the process command line; prefer the QWEN_SERVER_TOKEN env var for any non-trivial deployment."
      );
    }
    let channelSelection;
    try {
      channelSelection = normalizeServeChannelSelection(argv.channel);
    } catch (err) {
      writeStderrLine(
        `qwen serve: ${err instanceof Error ? err.message : String(err)}`
      );
      process.exit(1);
    }
    const mcpClientBudget = argv["mcp-client-budget"];
    const mcpBudgetMode = argv["mcp-budget-mode"];
    if (mcpClientBudget !== void 0) {
      if (!Number.isFinite(mcpClientBudget) || !Number.isInteger(mcpClientBudget) || mcpClientBudget <= 0) {
        writeStderrLine(
          "qwen serve: --mcp-client-budget must be a positive integer."
        );
        process.exit(1);
      }
    }
    if (mcpBudgetMode === "enforce" && mcpClientBudget === void 0) {
      writeStderrLine(
        "qwen serve: --mcp-budget-mode=enforce requires --mcp-client-budget=N."
      );
      process.exit(1);
    }
    const resolvedMcpMode = mcpBudgetMode ?? (mcpClientBudget !== void 0 ? "warn" : "off");
    const maxPendingPromptsPerSession = argv["max-pending-prompts-per-session"];
    if (maxPendingPromptsPerSession !== Number.POSITIVE_INFINITY && (!Number.isFinite(maxPendingPromptsPerSession) || !Number.isInteger(maxPendingPromptsPerSession) || maxPendingPromptsPerSession < 0)) {
      writeStderrLine(
        "qwen serve: --max-pending-prompts-per-session must be a non-negative integer (0 / Infinity = unlimited)."
      );
      process.exit(1);
    }
    if (mcpClientBudget !== void 0) {
      writeStderrLine(
        `qwen serve: --mcp-client-budget=${mcpClientBudget} mode=${resolvedMcpMode}` + (resolvedMcpMode === "enforce" ? " (servers past the cap will be refused at discovery)" : resolvedMcpMode === "warn" ? ` (warnings at >=${Math.ceil(mcpClientBudget * MCP_BUDGET_WARN_FRACTION)}, no refusal)` : "")
      );
    }
    try {
      const loaded = loadSettings(argv.workspace ?? process.cwd());
      const merged = loaded.merged;
      const approvalMode = merged.tools?.approvalMode;
      const sandbox = merged.tools?.sandbox;
      const sandboxEnv = process.env["SANDBOX"];
      const suppress = process.env["QWEN_CODE_SUPPRESS_YOLO_WARNING"];
      const suppressed = suppress === "1" || suppress === "true";
      if (approvalMode === "yolo" /* YOLO */ && !sandbox && !sandboxEnv && !suppressed) {
        writeStderrLine(HEADLESS_YOLO_NO_SANDBOX_WARNING);
      }
    } catch {
    }
    const rateLimit = argv["rate-limit"] ?? (process.env["QWEN_SERVE_RATE_LIMIT"] === "1" || process.env["QWEN_SERVE_RATE_LIMIT"] === "true");
    let rateLimitPrompt;
    let rateLimitMutation;
    let rateLimitRead;
    let rateLimitWindowMs;
    if (rateLimit) {
      const envInt = /* @__PURE__ */ __name((key) => {
        const raw = process.env[key];
        if (raw === void 0 || raw === "") return void 0;
        return parsePositiveIntegerEnv(raw, Number.NaN);
      }, "envInt");
      rateLimitPrompt = argv["rate-limit-prompt"] ?? envInt("QWEN_SERVE_RATE_LIMIT_PROMPT");
      rateLimitMutation = argv["rate-limit-mutation"] ?? envInt("QWEN_SERVE_RATE_LIMIT_MUTATION");
      rateLimitRead = argv["rate-limit-read"] ?? envInt("QWEN_SERVE_RATE_LIMIT_READ");
      rateLimitWindowMs = argv["rate-limit-window-ms"] ?? envInt("QWEN_SERVE_RATE_LIMIT_WINDOW_MS");
      for (const [name, value] of [
        ["--rate-limit-prompt", rateLimitPrompt],
        ["--rate-limit-mutation", rateLimitMutation],
        ["--rate-limit-read", rateLimitRead]
      ]) {
        if (value !== void 0 && (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0)) {
          writeStderrLine(`qwen serve: ${name} must be a positive integer.`);
          process.exit(1);
        }
      }
      if (rateLimitWindowMs !== void 0 && (!Number.isFinite(rateLimitWindowMs) || !Number.isInteger(rateLimitWindowMs) || rateLimitWindowMs < 1e3)) {
        writeStderrLine(
          "qwen serve: --rate-limit-window-ms must be an integer >= 1000."
        );
        process.exit(1);
      }
    }
    const { runQwenServe } = await import("./run-qwen-serve-M57LYOQT.js");
    try {
      const handle = await runQwenServe({
        port: argv.port,
        hostname: argv.hostname,
        token: argv.token,
        mode: "http-bridge",
        maxSessions: argv["max-sessions"],
        maxPendingPromptsPerSession,
        maxConnections: argv["max-connections"],
        eventRingSize: argv["event-ring-size"],
        workspace: argv.workspace,
        requireAuth: argv["require-auth"],
        enableSessionShell: argv["enable-session-shell"],
        serveWebShell: argv.web,
        ...argv["tls-cert"] !== void 0 ? { tlsCert: argv["tls-cert"] } : {},
        ...argv["tls-key"] !== void 0 ? { tlsKey: argv["tls-key"] } : {},
        allowPrivateAuthBaseUrl: argv["allow-private-auth-base-url"],
        mcpClientBudget,
        mcpBudgetMode: resolvedMcpMode,
        ...argv["allow-origin"] && argv["allow-origin"].length > 0 ? { allowOrigins: argv["allow-origin"] } : {},
        ...argv["prompt-deadline-ms"] !== void 0 ? { promptDeadlineMs: argv["prompt-deadline-ms"] } : {},
        ...argv["writer-idle-timeout-ms"] !== void 0 ? { writerIdleTimeoutMs: argv["writer-idle-timeout-ms"] } : {},
        ...argv["channel-idle-timeout-ms"] !== void 0 ? { channelIdleTimeoutMs: argv["channel-idle-timeout-ms"] } : {},
        ...argv["session-reap-interval-ms"] !== void 0 ? { sessionReapIntervalMs: argv["session-reap-interval-ms"] } : {},
        ...argv["session-idle-timeout-ms"] !== void 0 ? { sessionIdleTimeoutMs: argv["session-idle-timeout-ms"] } : {},
        ...argv["permission-response-timeout-ms"] !== void 0 ? {
          permissionResponseTimeoutMs: argv["permission-response-timeout-ms"]
        } : {},
        ...rateLimit ? { rateLimit: true } : {},
        ...rateLimitPrompt !== void 0 ? { rateLimitPrompt } : {},
        ...rateLimitMutation !== void 0 ? { rateLimitMutation } : {},
        ...rateLimitRead !== void 0 ? { rateLimitRead } : {},
        ...rateLimitWindowMs !== void 0 ? { rateLimitWindowMs } : {},
        ...argv.experimentalLsp === true ? { experimentalLsp: true } : {},
        ...channelSelection !== void 0 ? { channelSelection } : {}
      });
      await maybeOpenWebShellBrowser(handle, argv.open);
    } catch (err) {
      writeStderrLine(
        `qwen serve: ${err instanceof Error ? err.message : String(err)}`
      );
      process.exit(1);
    }
    await blockForever();
  }, "handler")
};

export {
  maybeOpenWebShellBrowser,
  serveCommand
};
/**
 * @license
 * Copyright 2025 Qwen Team
 * SPDX-License-Identifier: Apache-2.0
 */
