import {
  TraceEmitter
} from "./chunk-VQMK5FMP.js";
import {
  ConfigError
} from "./chunk-ONWEPEDO.js";

// src/sandbox-harness.ts
var vitestTestParser = {
  id: "vitest",
  parse(stdout) {
    const m = stdout.match(/Tests\s+(\d+)\s+(passed|failed)(?:\s*\|\s*(\d+)\s+(passed|failed))?/i);
    if (!m) return void 0;
    let passed = 0;
    let failed = 0;
    const a = parseInt(m[1], 10);
    const aLabel = m[2].toLowerCase();
    if (aLabel === "passed") passed += a;
    else failed += a;
    if (m[3] && m[4]) {
      const b = parseInt(m[3], 10);
      if (m[4].toLowerCase() === "passed") passed += b;
      else failed += b;
    }
    return { testsTotal: passed + failed, testsPassed: passed };
  }
};
var pytestTestParser = {
  id: "pytest",
  parse(stdout) {
    const total = stdout.match(/collected\s+(\d+)\s+items?/i);
    const passed = stdout.match(/(\d+)\s+passed/);
    if (!total || !passed) return void 0;
    return { testsTotal: parseInt(total[1], 10), testsPassed: parseInt(passed[1], 10) };
  }
};
var jestTestParser = {
  id: "jest",
  parse(stdout) {
    const m = stdout.match(/Tests:\s+(?:(\d+)\s+failed[^,]*,\s*)?(\d+)\s+passed,\s+(\d+)\s+total/i);
    if (!m) return void 0;
    return { testsTotal: parseInt(m[3], 10), testsPassed: parseInt(m[2], 10) };
  }
};
function composeParsers(...parsers) {
  return {
    id: parsers.map((p) => p.id).join("|"),
    parse(stdout, stderr, exitCode) {
      for (const p of parsers) {
        const res = p.parse(stdout, stderr, exitCode);
        if (res) return res;
      }
      return void 0;
    }
  };
}
var SubprocessSandboxDriver = class {
  id = "subprocess";
  defaultCwd;
  defaultEnv;
  constructor(options = {}) {
    this.defaultCwd = options.cwd;
    this.defaultEnv = options.env;
  }
  async exec(phase, command, config) {
    const { spawn } = await import("child_process");
    const start = Date.now();
    const effectiveCwd = config.cwd ?? this.defaultCwd;
    const effectiveEnv = { ...process.env, ...this.defaultEnv ?? {}, ...config.env ?? {} };
    const maxOutputBytes = config.maxOutputBytes ?? 16 * 1024 * 1024;
    return await new Promise((resolve) => {
      const child = spawn(command, {
        shell: true,
        cwd: effectiveCwd,
        env: effectiveEnv,
        // Own process group so a timeout can SIGKILL the whole tree, not
        // just the shell — otherwise a runaway grandchild keeps the pipes
        // open and `close` never fires (the timeout silently does nothing).
        detached: process.platform !== "win32"
      });
      let stdout = "";
      let stderr = "";
      let outputBytes = 0;
      let outputTruncated = false;
      let killedByTimeout = false;
      let settled = false;
      const onStdout = (d) => {
        const chunk = capture(String(d));
        if (chunk) stdout += chunk;
      };
      const onStderr = (d) => {
        const chunk = capture(String(d));
        if (chunk) stderr += chunk;
      };
      function capture(s) {
        if (outputBytes >= maxOutputBytes) {
          outputTruncated = true;
          return "";
        }
        const room = maxOutputBytes - outputBytes;
        if (s.length > room) {
          outputBytes = maxOutputBytes;
          outputTruncated = true;
          return s.slice(0, room);
        }
        outputBytes += s.length;
        return s;
      }
      child.stdout?.on("data", onStdout);
      child.stderr?.on("data", onStderr);
      const cleanup = () => {
        clearTimeout(timeout);
        if (forceResolve) clearTimeout(forceResolve);
        child.stdout?.off("data", onStdout);
        child.stderr?.off("data", onStderr);
        child.removeAllListeners("close");
        child.removeAllListeners("error");
      };
      const finish = (outcome) => {
        if (settled) return;
        settled = true;
        cleanup();
        const wallMs = Date.now() - start;
        const exitCode = killedByTimeout ? outcome.code && outcome.code !== 0 ? outcome.code : 124 : outcome.code ?? 1;
        const parsed = !killedByTimeout && phase === "test" && config.testParser ? config.testParser.parse(stdout, stderr, exitCode) : void 0;
        resolve({
          phase,
          exitCode,
          stdout,
          stderr: outcome.runnerError ? stderr + outcome.runnerError : stderr,
          wallMs,
          testsTotal: parsed?.testsTotal,
          testsPassed: parsed?.testsPassed,
          killedByTimeout: killedByTimeout || void 0,
          outputTruncated: outputTruncated || void 0
        });
      };
      const killTree = () => {
        try {
          if (process.platform !== "win32" && typeof child.pid === "number") {
            process.kill(-child.pid, "SIGKILL");
          } else {
            child.kill("SIGKILL");
          }
        } catch (err) {
          console.warn("[sandbox-harness] SIGKILL on timeout failed:", err);
        }
      };
      let forceResolve;
      const timeout = setTimeout(
        () => {
          killedByTimeout = true;
          killTree();
          forceResolve = setTimeout(() => finish({ code: null }), 2e3);
        },
        config.timeoutMs ?? 10 * 6e4
      );
      child.on("close", (code) => {
        if (forceResolve) clearTimeout(forceResolve);
        finish({ code });
      });
      child.on("error", (err) => {
        if (forceResolve) clearTimeout(forceResolve);
        finish({ code: 127, runnerError: String(err) });
      });
    });
  }
};
var DockerSandboxDriver = class {
  id = "docker";
  async exec(phase, command, config) {
    if (!config.image) throw new ConfigError("DockerSandboxDriver requires config.image");
    const sub = new SubprocessSandboxDriver();
    const envArgs = Object.entries(config.env ?? {}).map(([k, v]) => `-e ${shellQuote(k)}=${shellQuote(v)}`).join(" ");
    const wrapped = `docker run --rm ${envArgs} ${shellQuote(config.image)} sh -c ${shellQuote(command)}`;
    return sub.exec(phase, wrapped, { ...config, env: void 0 });
  }
};
function shellQuote(v) {
  if (/^[A-Za-z0-9_\-/.@:=]+$/.test(v)) return v;
  return `'${v.replace(/'/g, `'\\''`)}'`;
}
var SandboxHarness = class {
  driver;
  constructor(driver = new SubprocessSandboxDriver()) {
    this.driver = driver;
  }
  async run(config, emitter) {
    const handle = await emitter.sandbox({
      name: `sandbox(${this.driver.id})`,
      image: config.image,
      command: [config.setupCommand, config.runCommand, config.testCommand].filter(Boolean).join(" && ")
    });
    const result = { passed: false, totalWallMs: 0, score: 0 };
    try {
      if (config.setupCommand) {
        result.setup = await this.driver.exec("setup", config.setupCommand, config);
        result.totalWallMs += result.setup.wallMs;
        if (result.setup.killedByTimeout || result.setup.exitCode !== 0) {
          const reason = result.setup.killedByTimeout ? `setup timed out after ${result.setup.wallMs}ms` : `setup failed (exit ${result.setup.exitCode})`;
          await handle.fail(reason, {
            exitCode: result.setup.exitCode,
            wallMs: result.totalWallMs
          });
          return result;
        }
      }
      if (config.runCommand) {
        result.run = await this.driver.exec("run", config.runCommand, config);
        result.totalWallMs += result.run.wallMs;
        if (result.run.killedByTimeout || result.run.exitCode !== 0) {
          const reason = result.run.killedByTimeout ? `run timed out after ${result.run.wallMs}ms` : `run failed (exit ${result.run.exitCode})`;
          await handle.fail(reason, {
            exitCode: result.run.exitCode,
            wallMs: result.totalWallMs
          });
          return result;
        }
      }
      if (config.testCommand) {
        result.test = await this.driver.exec("test", config.testCommand, config);
        result.totalWallMs += result.test.wallMs;
        const passed = !result.test.killedByTimeout && result.test.exitCode === 0;
        result.passed = passed;
        if (result.test.testsTotal !== void 0 && result.test.testsTotal > 0) {
          result.score = (result.test.testsPassed ?? 0) / result.test.testsTotal;
        } else {
          result.score = passed ? 1 : 0;
        }
        await handle.end({
          exitCode: result.test.exitCode,
          testsTotal: result.test.testsTotal,
          testsPassed: result.test.testsPassed,
          wallMs: result.totalWallMs,
          status: passed ? "ok" : "error"
        });
      } else {
        result.passed = true;
        result.score = 1;
        await handle.end({ wallMs: result.totalWallMs });
      }
    } catch (err) {
      await handle.fail(err instanceof Error ? err : String(err));
      throw err;
    }
    return result;
  }
};

// src/test-graded-scenario.ts
async function runTestGradedScenario(scenario, store, options = {}) {
  const emitter = new TraceEmitter(store);
  await emitter.startRun({
    scenarioId: scenario.id,
    variantId: options.variantId,
    datasetVersion: scenario.datasetVersion,
    tags: scenario.tags,
    ...options.provenance
  });
  const harness = new SandboxHarness(options.driver);
  const result = await harness.run(scenario.harness, emitter);
  const threshold = scenario.passThreshold ?? 1;
  const pass = result.passed && result.score >= threshold;
  const setupFailed = result.setup !== void 0 && result.setup.exitCode !== 0;
  const runFailed = result.run !== void 0 && result.run.exitCode !== 0;
  const testFailed = result.test !== void 0 && result.test.exitCode !== 0;
  const failureClass = pass ? "success" : setupFailed || runFailed ? "sandbox_failure" : testFailed ? "format_drift" : "unknown";
  await emitter.endRun({
    pass,
    score: result.score,
    failureClass,
    notes: pass ? void 0 : reasonForFailure(result)
  });
  return {
    runId: emitter.runId,
    scenario,
    harness: result,
    pass,
    score: result.score,
    failureClass
  };
}
function reasonForFailure(result) {
  if (result.setup && result.setup.exitCode !== 0)
    return `setup failed: exit ${result.setup.exitCode}`;
  if (result.run && result.run.exitCode !== 0) return `run failed: exit ${result.run.exitCode}`;
  if (result.test) {
    if (result.test.testsTotal !== void 0) {
      return `tests: ${result.test.testsPassed ?? 0}/${result.test.testsTotal}`;
    }
    return `test exit ${result.test.exitCode}`;
  }
  return "no test command";
}

export {
  vitestTestParser,
  pytestTestParser,
  jestTestParser,
  composeParsers,
  SubprocessSandboxDriver,
  DockerSandboxDriver,
  SandboxHarness,
  runTestGradedScenario
};
//# sourceMappingURL=chunk-K6N6XJJX.js.map