import {
  parseNDJSONLines
} from "./chunk-PJZ5TQYW.js";
import {
  childSpan,
  newTrace
} from "./chunk-AU4NAQGA.js";
import {
  buildErrorPayload,
  classifyError
} from "./chunk-RIEAZ4TR.js";
import {
  injectMcpAuthHeaders
} from "./chunk-DBFPJSOY.js";
import {
  incrementRetry,
  resetRetry,
  retryDelay,
  shouldRetry
} from "./chunk-72BKGVBE.js";
import {
  loadSessionSecrets
} from "./chunk-HLP24IVO.js";
import {
  isProxied
} from "./chunk-DZIKCTP6.js";
import {
  acquireForFirstTurn,
  installSkills,
  provisionResources,
  wrapProviderWithSecrets
} from "./chunk-JUNQ5KRS.js";
import {
  BLOCKED_ENV_KEYS,
  resolveVaultSecrets
} from "./chunk-2RZARCTJ.js";
import {
  appendEvent,
  appendEventsBatch
} from "./chunk-4NRHKTBW.js";
import {
  bumpSessionStats,
  getOutcomeCriteria,
  getSession,
  getSessionRow,
  setBackendSessionId,
  setIdleSince,
  setOutcomeCriteria,
  updateSessionMutable,
  updateSessionStatus
} from "./chunk-SEGKFEG5.js";
import {
  listEvents,
  markUserEventProcessed
} from "./chunk-LH3NEWMT.js";
import {
  ContainerGone
} from "./chunk-CTVBTCTF.js";
import {
  getBySession,
  unregister
} from "./chunk-EFOIR7R3.js";
import {
  resolveProvider,
  resolveProviderName
} from "./chunk-ZKZME3DE.js";
import {
  getEnvironment
} from "./chunk-GTYDSYTR.js";
import {
  getAgent
} from "./chunk-LGPJPAHU.js";
import {
  resolveBackend
} from "./chunk-QCUM5WMI.js";
import {
  PERMISSION_BRIDGE_PENDING_PATH,
  PERMISSION_BRIDGE_REQUEST_PATH,
  PERMISSION_BRIDGE_RESPONSE_PATH
} from "./chunk-CY6AWCC6.js";
import {
  TOOL_BRIDGE_PENDING_PATH,
  TOOL_BRIDGE_REQUEST_PATH,
  TOOL_BRIDGE_RESPONSE_PATH
} from "./chunk-JCBK3HZX.js";
import {
  resolveToolset
} from "./chunk-FX2AEKOV.js";
import {
  getConfig
} from "./chunk-2MAQ6JFL.js";
import {
  init_clock,
  nowMs,
  toIso
} from "./chunk-HFDLUBWN.js";
import {
  getDb,
  init_client
} from "./chunk-TLEKKGH7.js";
import {
  isAnthropicOAuthToken
} from "./chunk-J5Z5DSH4.js";
import {
  ApiError
} from "./chunk-EZYKRG4W.js";
import {
  drainPendingUserInputs,
  getRuntime
} from "./chunk-UYTSKFGK.js";

// src/sessions/driver.ts
init_clock();
init_client();
function formatStopReason(reason, eventIds) {
  if (reason === "custom_tool_call") {
    return { type: "requires_action", event_ids: eventIds ?? [] };
  }
  return { type: reason };
}
async function runTurn(sessionId, inputs, _depth = 0, parentTrace) {
  const trace = parentTrace ? childSpan(parentTrace) : newTrace();
  const emit = (type, payload, opts = {}) => {
    const input = {
      type,
      payload,
      origin: "server",
      processedAt: opts.at ?? nowMs(),
      traceId: trace.trace_id,
      spanId: trace.span_id,
      parentSpanId: opts.parentOverride !== void 0 ? opts.parentOverride : trace.parent_span_id
    };
    appendEvent(sessionId, input);
  };
  if (_depth > 25) {
    emit("session.error", { error: { type: "server_error", message: "max recursion depth exceeded" } });
    updateSessionStatus(sessionId, "idle", "error");
    return;
  }
  const session = getSession(sessionId);
  if (!session) return;
  if (inputs.length === 0) return;
  const agent = getAgent(session.agent.id, session.agent.version);
  if (!agent) {
    emit("session.error", { error: { type: "server_error", message: "agent not found" } });
    emit("session.status_idle", { stop_reason: formatStopReason("error") });
    updateSessionStatus(sessionId, "idle", "error");
    return;
  }
  const backend = resolveBackend(agent.engine);
  const vaultEntries = [];
  if (session.vault_ids && session.vault_ids.length > 0) {
    const secrets2 = loadSessionSecrets(session.vault_ids, session.user_profile_id);
    vaultEntries.push(...secrets2.map((s) => ({ key: s.key, value: s.value })));
  }
  const hasVaultKeys = vaultEntries.length > 0;
  if (!hasVaultKeys) {
    const runtimeErr = backend.validateRuntime?.();
    if (runtimeErr) {
      emit("session.error", { error: { type: "invalid_request_error", message: runtimeErr } });
      emit("session.status_idle", { stop_reason: formatStopReason("error") });
      updateSessionStatus(sessionId, "idle", "error");
      return;
    }
  }
  const budgetRow = getSessionRow(sessionId);
  if (budgetRow?.max_budget_usd != null && budgetRow.usage_cost_usd >= budgetRow.max_budget_usd) {
    emit("session.error", {
      error: {
        type: "budget_exceeded",
        scope: "session",
        message: `usage $${budgetRow.usage_cost_usd.toFixed(4)} >= session budget $${budgetRow.max_budget_usd.toFixed(4)}`
      }
    });
    emit("session.status_idle", { stop_reason: formatStopReason("error") });
    updateSessionStatus(sessionId, "idle", "error");
    return;
  }
  if (budgetRow?.api_key_id) {
    const { getApiKeyById } = await import("./db/api_keys.js");
    const key = getApiKeyById(budgetRow.api_key_id);
    if (key && key.budget_usd != null && (key.spent_usd ?? 0) >= key.budget_usd) {
      emit("session.error", {
        error: {
          type: "budget_exceeded",
          scope: "key",
          message: `api key "${key.name}" spent $${(key.spent_usd ?? 0).toFixed(4)} >= budget $${key.budget_usd.toFixed(4)}`
        }
      });
      emit("session.status_idle", { stop_reason: formatStopReason("error") });
      updateSessionStatus(sessionId, "idle", "error");
      return;
    }
  }
  if (budgetRow != null) {
    const totalTokens = (budgetRow.usage_input_tokens ?? 0) + (budgetRow.usage_output_tokens ?? 0);
    if (budgetRow.max_tokens != null && totalTokens >= budgetRow.max_tokens) {
      emit("session.error", {
        error: {
          type: "quota_exceeded",
          scope: "session",
          message: `tokens used ${totalTokens} >= session token limit ${budgetRow.max_tokens}`
        }
      });
      emit("session.status_idle", { stop_reason: formatStopReason("error") });
      updateSessionStatus(sessionId, "idle", "error");
      return;
    }
  }
  if (budgetRow != null && budgetRow.max_wall_duration_ms != null) {
    const ageMs = nowMs() - budgetRow.created_at;
    if (ageMs >= budgetRow.max_wall_duration_ms) {
      emit("session.error", {
        error: {
          type: "quota_exceeded",
          scope: "session",
          message: `session age ${ageMs}ms >= wall limit ${budgetRow.max_wall_duration_ms}ms`
        }
      });
      emit("session.status_idle", { stop_reason: formatStopReason("error") });
      updateSessionStatus(sessionId, "idle", "error");
      return;
    }
  }
  for (const p of inputs) markUserEventProcessed(p.eventId, nowMs());
  const row = getSessionRow(sessionId);
  if (row && row.title == null) {
    const firstText = inputs.find((i) => i.kind === "text");
    if (firstText?.text) {
      const sanitized = firstText.text.replace(/[\x00-\x1F\x7F\u200B-\u200F\u2028-\u202F\uFEFF]/g, "").replace(/^Image(?=[A-Z])/g, "").replace(/\s+/g, " ").trim();
      if (sanitized) {
        updateSessionMutable(sessionId, { title: sanitized.slice(0, 60) });
      }
    }
  }
  console.log(`[driver] ${sessionId} acquiring container...`);
  let sandboxName;
  try {
    try {
      sandboxName = await acquireForFirstTurn(sessionId);
    } catch (firstErr) {
      const firstMsg = firstErr instanceof Error ? firstErr.message : String(firstErr);
      if (firstMsg.includes("httpExec: 502") || firstMsg.includes("httpExec: 503")) {
        console.warn(`[driver] ${sessionId} transient container setup error, retrying: ${firstMsg}`);
        await new Promise((r) => setTimeout(r, 2e3));
        sandboxName = await acquireForFirstTurn(sessionId);
      } else {
        throw firstErr;
      }
    }
    console.log(`[driver] ${sessionId} container ready: ${sandboxName}`);
    const postAcquireSecrets = getBySession(sessionId)?.vaultSecrets ?? (session.vault_ids?.length ? resolveVaultSecrets(session.vault_ids) : void 0);
    const latestAgent = getAgent(session.agent.id);
    if (latestAgent && latestAgent.skills && latestAgent.skills.length > 0) {
      const currentSkills = new Map((agent.skills ?? []).map((s) => [s.name, s.content.length]));
      const newSkills = latestAgent.skills.filter(
        (s) => !currentSkills.has(s.name) || currentSkills.get(s.name) !== s.content.length
      );
      if (newSkills.length > 0) {
        console.log(`[driver] ${sessionId} injecting ${newSkills.length} new skill(s)...`);
        const envRow = getEnvironment(session.environment_id);
        const baseProvider = await resolveProvider({ envConfigProvider: envRow?.config?.provider });
        const sp = wrapProviderWithSecrets(baseProvider, postAcquireSecrets);
        await installSkills(sandboxName, sp, newSkills, agent.engine);
        console.log(`[driver] ${sessionId} skills injected`);
      }
    }
    const freshSession2 = getSession(sessionId);
    if (freshSession2?.resources && freshSession2.resources.length > 0) {
      const envRow = getEnvironment(session.environment_id);
      const baseProvider = await resolveProvider({ envConfigProvider: envRow?.config?.provider });
      const sp = wrapProviderWithSecrets(baseProvider, postAcquireSecrets);
      await provisionResources(sandboxName, freshSession2.resources, sp);
    }
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    emit("session.error", { error: { type: "server_error", message: `container creation failed: ${msg}` } });
    emit("session.status_idle", { stop_reason: formatStopReason("error") });
    updateSessionStatus(sessionId, "idle", "error");
    return;
  }
  updateSessionStatus(sessionId, "running");
  const turnStartMs = nowMs();
  emit("session.status_running", {}, { at: turnStartMs });
  emit("span.model_request_start", { model: agent.model.id }, { at: turnStartMs });
  const promptText = inputs.filter((i) => i.kind === "text").map((i) => i.text).join("\n\n");
  const toolResults = inputs.filter((i) => i.kind === "tool_result").map((i) => ({
    custom_tool_use_id: i.custom_tool_use_id,
    content: i.content
  }));
  const agentForTurn = injectMcpAuthHeaders(agent, vaultEntries);
  let mountedMemoryStores;
  try {
    const { listResources: listSessionResources } = await import("./db/session-resources.js");
    const { getMemoryStore } = await import("./db/memory.js");
    const sessionResources = listSessionResources(sessionId);
    const memStoreResources = sessionResources.filter((r) => r.type === "memory_store" && r.memory_store_id);
    if (memStoreResources.length > 0) {
      mountedMemoryStores = [];
      for (const r of memStoreResources) {
        const store = getMemoryStore(r.memory_store_id);
        if (store) {
          mountedMemoryStores.push({
            name: store.name,
            access: r.access ?? "read_write",
            description: store.description,
            instructions: r.instructions
          });
        }
      }
    }
  } catch {
  }
  let turnBuild;
  try {
    turnBuild = backend.buildTurn({
      agent: agentForTurn,
      backendSessionId: getSessionRow(sessionId)?.claude_session_id ?? null,
      promptText,
      toolResults,
      memoryStores: mountedMemoryStores
    });
  } catch (err) {
    const msg = err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err);
    const type = err instanceof ApiError ? err.type : "server_error";
    emit("span.model_request_end", { model: agent.model.id, model_usage: null, status: "error" });
    emit("session.error", { error: { type, message: msg } });
    emit("session.status_idle", { stop_reason: formatStopReason("error") });
    updateSessionStatus(sessionId, "idle", "error");
    return;
  }
  if (backend.applyProviderQuirks) {
    const envRow = getEnvironment(session.environment_id);
    const provName = resolveProviderName({ envConfigProvider: envRow?.config?.provider });
    backend.applyProviderQuirks(turnBuild, provName);
  }
  console.log(`[driver] ${sessionId} executing turn (engine: ${agent.engine}, model: ${agent.model.id})`);
  const argv = [backend.wrapperPath, ...turnBuild.argv];
  const freshSession = getSession(sessionId);
  if (freshSession?.resources && freshSession.resources.length > 0) {
    turnBuild.env.RESOURCES_DIR = "/tmp/resources";
  }
  if (backend.mcpTimeoutMs && !turnBuild.env.MCP_TIMEOUT) {
    turnBuild.env.MCP_TIMEOUT = String(backend.mcpTimeoutMs);
  }
  const MCP_KEY_RE = /^MCP_(AUTH|HEADER)_/i;
  for (const entry of vaultEntries) {
    if (!BLOCKED_ENV_KEYS.has(entry.key) && !MCP_KEY_RE.test(entry.key)) {
      turnBuild.env[entry.key] = entry.value;
    }
  }
  if (turnBuild.env.OPENAI_API_KEY && !turnBuild.env.CODEX_API_KEY) {
    turnBuild.env.CODEX_API_KEY = turnBuild.env.OPENAI_API_KEY;
  }
  if (isAnthropicOAuthToken(turnBuild.env.ANTHROPIC_API_KEY)) {
    turnBuild.env.CLAUDE_CODE_OAUTH_TOKEN = turnBuild.env.ANTHROPIC_API_KEY;
    delete turnBuild.env.ANTHROPIC_API_KEY;
  }
  if (turnBuild.env.CLAUDE_CODE_OAUTH_TOKEN && turnBuild.env.ANTHROPIC_API_KEY) {
    delete turnBuild.env.ANTHROPIC_API_KEY;
  }
  const ollamaCloudPrefixes = ["claude-", "gpt-", "o1-", "o3-", "o4-", "codex-", "chatgpt-", "gemini-"];
  const isOllamaModel = !agent.model.id.includes("/") && !ollamaCloudPrefixes.some((p) => agent.model.id.startsWith(p));
  if (isOllamaModel) {
    let ollamaHostPort;
    if (!turnBuild.env.OLLAMA_HOST) {
      const envRow = getEnvironment(session.environment_id);
      const provName = resolveProviderName({ envConfigProvider: envRow?.config?.provider });
      if (provName === "docker" || provName === "podman") {
        ollamaHostPort = "host.docker.internal:11434";
      } else if (provName === "apple-container" || provName === "apple-firecracker") {
        const customUrl = getConfig().ollamaUrl;
        ollamaHostPort = customUrl !== "http://localhost:11434" ? customUrl.replace(/^https?:\/\//, "") : "192.168.64.1:11434";
      }
      if (ollamaHostPort) {
        turnBuild.env.OLLAMA_HOST = ollamaHostPort;
      }
    } else {
      ollamaHostPort = turnBuild.env.OLLAMA_HOST;
    }
    if (ollamaHostPort && !turnBuild.env.CODEX_OSS_BASE_URL) {
      const host = ollamaHostPort.replace(/^https?:\/\//, "");
      turnBuild.env.CODEX_OSS_BASE_URL = `http://${host}/v1`;
    }
    if (ollamaHostPort && turnBuild.env.OPENCODE_CONFIG_CONTENT) {
      try {
        const cfg = JSON.parse(turnBuild.env.OPENCODE_CONFIG_CONTENT);
        const host = ollamaHostPort.replace(/^https?:\/\//, "");
        if (cfg.provider?.ollama?.options) {
          cfg.provider.ollama.options.baseURL = `http://${host}/v1`;
          turnBuild.env.OPENCODE_CONFIG_CONTENT = JSON.stringify(cfg);
        }
      } catch {
      }
    }
    if (!turnBuild.env.OPENAI_API_KEY) {
      turnBuild.env.OPENAI_API_KEY = "ollama";
    }
    if (!turnBuild.env.CODEX_API_KEY) {
      turnBuild.env.CODEX_API_KEY = "ollama";
    }
  }
  const envLines = Object.entries(turnBuild.env).map(([k, v]) => `${k}=${v}`).join("\n");
  const stdin = `${envLines}

${turnBuild.stdin}`;
  const env = getEnvironment(session.environment_id);
  const provider = await resolveProvider({ envConfigProvider: env?.config?.provider });
  const poolEntry = getBySession(sessionId);
  const secrets = poolEntry?.vaultSecrets ?? (session.vault_ids?.length ? resolveVaultSecrets(session.vault_ids) : void 0);
  const tools = resolveToolset(agent.tools);
  if (agent.threads_enabled) {
    tools.customToolNames.add("spawn_agent");
  }
  const translator = backend.createTranslator({
    customToolNames: tools.customToolNames,
    isFirstTurn: getSessionRow(sessionId)?.claude_session_id == null,
    // Observability: allow the translator to mint per-tool child spans
    // nested under the turn's root span. Translators that don't implement
    // this simply ignore the option.
    turnSpanId: trace.span_id
  });
  if (backend.supportsCustomTools && toolResults.length > 0) {
    const { TOOL_BRIDGE_RESPONSE_PATH: TOOL_BRIDGE_RESPONSE_PATH2, TOOL_BRIDGE_PENDING_PATH: TOOL_BRIDGE_PENDING_PATH2 } = await import("./backends/claude/tool-bridge.js");
    const sandboxName2 = getSessionRow(sessionId)?.sandbox_name;
    if (sandboxName2) {
      for (const r of toolResults) {
        const responseJson = JSON.stringify({ content: r.content });
        await provider.exec(
          sandboxName2,
          ["sh", "-c", `cat > ${TOOL_BRIDGE_RESPONSE_PATH2}`],
          { stdin: responseJson, secrets }
        ).catch((err) => {
          console.warn(`[driver] failed to write tool bridge response:`, err);
        });
        await provider.exec(
          sandboxName2,
          ["rm", "-f", TOOL_BRIDGE_PENDING_PATH2],
          { secrets }
        ).catch(() => {
        });
      }
    }
  }
  try {
    const sessionRow = getSessionRow(sessionId);
    if (sessionRow?.debug_prompt_json === '{"pending":true}') {
      const { redactEnv } = await import("./handlers/debug-prompt.js");
      const captured = {
        captured_at: toIso(nowMs()),
        backend: agent.engine,
        model: agent.model.id,
        argv,
        env: redactEnv(turnBuild.env),
        prompt: turnBuild.stdin,
        system: agent.system
      };
      try {
        getDb().prepare(
          `UPDATE sessions SET debug_prompt_json = ?, updated_at = ? WHERE id = ?`
        ).run(JSON.stringify(captured), nowMs(), sessionId);
      } catch (err) {
        console.warn(`[debug-prompt] persist failed for ${sessionId}:`, err);
      }
    }
  } catch (err) {
    console.warn(`[debug-prompt] capture failed for ${sessionId}:`, err);
  }
  const runtime = getRuntime();
  const controller = new AbortController();
  runtime.inFlightRuns.set(sessionId, {
    sessionId,
    controller,
    startedAt: turnStartMs
  });
  let exec;
  try {
    exec = await provider.startExec(sandboxName, {
      argv,
      stdin,
      signal: controller.signal,
      secrets
    });
  } catch (err) {
    if (err instanceof ContainerGone) {
      console.warn(`[driver] ${sessionId} sandbox ${sandboxName} reaped upstream; re-acquiring and retrying once`);
      try {
        unregister(sessionId);
        await provider.delete(sandboxName, secrets).catch((err2) => {
          console.warn(`[driver] best-effort delete of reaped sandbox ${sandboxName} failed:`, err2);
        });
        sandboxName = await acquireForFirstTurn(sessionId);
        await backend.prepareOnSandbox(sandboxName, wrapProviderWithSecrets(provider, secrets));
        if (agent.skills && agent.skills.length > 0) {
          await installSkills(sandboxName, wrapProviderWithSecrets(provider, secrets), agent.skills, agent.engine);
        }
        exec = await provider.startExec(sandboxName, {
          argv,
          stdin,
          signal: controller.signal,
          secrets
        });
      } catch (retryErr) {
        runtime.inFlightRuns.delete(sessionId);
        const msg = retryErr instanceof Error ? retryErr.message : String(retryErr);
        emit("span.model_request_end", { model: agent.model.id, model_usage: null, status: "error" });
        emit("session.error", { error: { type: "server_error", message: `exec failed after re-acquire: ${msg}` } });
        emit("session.status_idle", { stop_reason: formatStopReason("error") });
        updateSessionStatus(sessionId, "idle", "error");
        return;
      }
    } else {
      runtime.inFlightRuns.delete(sessionId);
      const msg = err instanceof Error ? err.message : String(err);
      emit("span.model_request_end", { model: agent.model.id, model_usage: null, status: "error" });
      emit("session.error", { error: { type: "server_error", message: `exec failed: ${msg}` } });
      emit("session.status_idle", { stop_reason: formatStopReason("error") });
      updateSessionStatus(sessionId, "idle", "error");
      return;
    }
  }
  const CONTROL_CHARS = /[\x00-\x08\x0B\x0C\x0E-\x1F]/g;
  let buffer = "";
  let aborted = false;
  let toolCallsInTurn = 0;
  let permissionPollTimer = null;
  if (agent.confirmation_mode) {
    permissionPollTimer = setInterval(() => {
      void checkPermissionSentinel(sessionId, sandboxName, provider).catch(
        (err) => {
          console.warn(`[driver] permission sentinel check failed:`, err);
        }
      );
    }, 2e3);
  }
  let toolBridgePollTimer = null;
  if (backend.supportsCustomTools && tools.customToolNames.size > 0) {
    toolBridgePollTimer = setInterval(() => {
      void checkToolBridgeSentinel(sessionId, sandboxName, provider, secrets, trace).catch(
        (err) => {
          console.warn(`[driver] tool bridge sentinel check failed:`, err);
        }
      );
    }, 1e3);
  }
  try {
    const reader = exec.stdout.getReader();
    const decoder = new TextDecoder();
    for (; ; ) {
      const { done, value } = await reader.read();
      if (done) break;
      const raw = decoder.decode(value, { stream: true });
      buffer += provider.stripControlChars ? raw.replace(CONTROL_CHARS, "") : raw;
      const batch = [];
      buffer = parseNDJSONLines(buffer, (raw2) => {
        if (process.env.DEBUG_NDJSON) console.log("[ndjson]", JSON.stringify(raw2));
        const translated = translator.translate(raw2);
        for (const t of translated) batch.push(t);
      });
      if (batch.length > 0) {
        const batchInputs = batch.map((t) => {
          if (t.type.endsWith("tool_use") || t.type.endsWith("mcp_tool_use") || t.type.endsWith("custom_tool_use")) {
            toolCallsInTurn++;
          }
          const spanId = t.spanId ?? trace.span_id;
          const parentSpanId = t.parentSpanId ?? trace.parent_span_id;
          return {
            type: t.type,
            payload: t.payload,
            origin: "server",
            processedAt: nowMs(),
            traceId: trace.trace_id,
            spanId,
            parentSpanId
          };
        });
        appendEventsBatch(sessionId, batchInputs);
      }
    }
    if (buffer.trim()) {
      try {
        const trailing = JSON.parse(buffer.trim());
        if (process.env.DEBUG_NDJSON) console.log("[ndjson] flush-trailing", JSON.stringify(trailing));
        const translated = translator.translate(trailing);
        if (translated.length > 0) {
          const batchInputs = translated.map((t) => ({
            type: t.type,
            payload: t.payload,
            origin: "server",
            processedAt: nowMs(),
            traceId: trace.trace_id,
            spanId: t.spanId ?? trace.span_id,
            parentSpanId: t.parentSpanId ?? trace.parent_span_id
          }));
          appendEventsBatch(sessionId, batchInputs);
        }
      } catch {
      }
      buffer = "";
    }
    const exitResult = await exec.exit;
    if (process.env.DEBUG_NDJSON && exitResult) {
      console.log(`[driver] ${sessionId} exit code: ${exitResult.code}`);
    }
    const exitCode = exitResult?.code;
    const translatorSawOutput = toolCallsInTurn > 0 || translator.getTurnResult()?.stopReason != null;
    if (exitCode !== 0 && !translatorSawOutput) {
      const code = exitCode ?? "unknown";
      emit("session.error", {
        error: {
          type: "backend_error",
          message: `Backend CLI exited with code ${code} and no output. Check that the engine is installed and the API key is valid.`
        }
      });
    }
  } catch (err) {
    if (controller.signal.aborted) {
      aborted = true;
    } else {
      const msg = err instanceof Error ? err.message : String(err);
      const classified = classifyError(msg);
      const retry = shouldRetry(sessionId, classified);
      if (retry) {
        incrementRetry(sessionId);
        emit("session.error", { error: { type: "server_error", message: msg, classified } });
        emit("session.status_rescheduled", { retry_attempt: retry.attempt, retry_delay_ms: retry.delayMs });
        runtime.inFlightRuns.delete(sessionId);
        await retryDelay(retry.delayMs);
        return runTurn(sessionId, inputs, _depth + 1, parentTrace);
      }
      emit("span.model_request_end", { model: agent.model.id, model_usage: null, status: "error" });
      const retryStatus = classified.retryable ? "exhausted" : "terminal";
      emit("session.error", { error: buildErrorPayload(classified, retryStatus) });
      emit("session.status_idle", { stop_reason: formatStopReason("error") });
      updateSessionStatus(sessionId, "idle", "error");
      runtime.inFlightRuns.delete(sessionId);
      return;
    }
  } finally {
    if (permissionPollTimer) clearInterval(permissionPollTimer);
    if (toolBridgePollTimer) clearInterval(toolBridgePollTimer);
    getPendingToolBridgeCalls().delete(sessionId);
    customToolCallCounts.delete(sessionId);
    runtime.inFlightRuns.delete(sessionId);
  }
  if (aborted) {
    const partial = translator.getTurnResult();
    emit("span.model_request_end", {
      model: agent.model.id,
      model_usage: partial?.usage ?? null,
      status: "interrupted"
    });
    emit("session.status_idle", { stop_reason: formatStopReason("interrupted") });
    updateSessionStatus(sessionId, "idle", "interrupted");
    setIdleSince(sessionId, nowMs());
    scheduleDrain(sessionId);
    return;
  }
  const result = translator.getTurnResult();
  const backendSid = translator.getBackendSessionId();
  if (backendSid) setBackendSessionId(sessionId, backendSid);
  const turnDurationSec = Math.max(0, (nowMs() - turnStartMs) / 1e3);
  bumpSessionStats(
    sessionId,
    {
      turn_count: 1,
      tool_calls_count: toolCallsInTurn,
      duration_seconds: turnDurationSec,
      active_seconds: turnDurationSec
    },
    result?.usage
  );
  const sessionRowForSync = getSessionRow(sessionId);
  if (sessionRowForSync?.sandbox_name && !isProxied(sessionId)) {
    try {
      const { syncContainerFiles } = await import("./sync/container-file-sync.js");
      const syncResult = await syncContainerFiles({
        sessionId,
        sandboxName: sessionRowForSync.sandbox_name,
        provider,
        secrets
      });
      for (const ae of syncResult.autoExecuted) {
        emit("session.script_executed", {
          script_path: ae.scriptPath,
          success: ae.success
        });
      }
      if (syncResult.synced > 0) {
        console.log(`[driver] synced ${syncResult.synced} files from container (${syncResult.skipped} skipped)`);
        for (const f of syncResult.files) {
          emit("session.file_synced", {
            file: {
              id: f.id,
              filename: f.filename,
              size_bytes: f.size_bytes,
              mime_type: f.mime_type,
              container_path: f.container_path
            }
          });
        }
      }
    } catch (err) {
      console.warn("[driver] container file sync failed:", err?.stack ?? err);
    }
    try {
      const { syncMemoryStores } = await import("./sync/memory-sync.js");
      await syncMemoryStores({
        sessionId,
        sandboxName: sessionRowForSync.sandbox_name,
        provider,
        secrets
      });
    } catch (err) {
      console.warn("[driver] memory store sync failed:", err?.stack ?? err);
    }
  }
  const now = nowMs();
  const stopReason = result?.stopReason ?? "end_turn";
  if (stopReason === "custom_tool_call") {
    const customToolEventIds = listEvents(sessionId, { limit: 20, order: "desc" }).filter((e) => e.type === "agent.custom_tool_use").map((e) => e.id);
    const serverToolResult = await handleServerSideTool(sessionId, agent, trace);
    if (serverToolResult) {
      emit(
        "span.model_request_end",
        { model: agent.model.id, model_usage: result?.usage ?? null, status: "ok" },
        { at: now }
      );
      emit("session.status_idle", { stop_reason: formatStopReason("custom_tool_call", customToolEventIds) }, { at: now });
      updateSessionStatus(sessionId, "idle", "custom_tool_call");
      const { TOOL_BRIDGE_RESPONSE_PATH: TOOL_BRIDGE_RESPONSE_PATH2, TOOL_BRIDGE_PENDING_PATH: TOOL_BRIDGE_PENDING_PATH2 } = await import("./backends/claude/tool-bridge.js");
      const sprName = getSessionRow(sessionId)?.sandbox_name;
      if (sprName) {
        const responseJson = JSON.stringify({ content: [{ type: "text", text: serverToolResult.text }] });
        const envForSession = getEnvironment(session.environment_id);
        const providerForReentry = await resolveProvider({ envConfigProvider: envForSession?.config?.provider });
        const reentrySecrets = getBySession(sessionId)?.vaultSecrets;
        await providerForReentry.exec(
          sprName,
          ["sh", "-c", `cat > ${TOOL_BRIDGE_RESPONSE_PATH2}`],
          { stdin: responseJson, secrets: reentrySecrets }
        ).catch((err) => {
          console.warn(`[driver] failed to write spawn_agent response:`, err);
        });
        await providerForReentry.exec(
          sprName,
          ["rm", "-f", TOOL_BRIDGE_PENDING_PATH2],
          { secrets: reentrySecrets }
        ).catch(() => {
        });
      }
      await runTurn(sessionId, [{
        kind: "tool_result",
        eventId: `server_tool_${nowMs()}`,
        custom_tool_use_id: serverToolResult.toolUseId,
        content: [{ type: "text", text: serverToolResult.text }]
      }], _depth + 1, trace);
      return;
    }
  }
  emit(
    "span.model_request_end",
    { model: agent.model.id, model_usage: result?.usage ?? null, status: "ok" },
    { at: now }
  );
  const finalEventIds = stopReason === "custom_tool_call" ? listEvents(sessionId, { limit: 20, order: "desc" }).filter((e) => e.type === "agent.custom_tool_use").map((e) => e.id) : void 0;
  emit("session.status_idle", { stop_reason: formatStopReason(stopReason, finalEventIds) }, { at: now });
  updateSessionStatus(sessionId, "idle", stopReason);
  resetRetry(sessionId);
  console.log(`[driver] ${sessionId} turn complete: stop_reason=${stopReason}`);
  setIdleSince(sessionId, now);
  if (stopReason === "end_turn") {
    const criteria = getOutcomeCriteria(sessionId);
    if (criteria?.rubric && criteria?.status === "running") {
      try {
        const { runGraderEvaluation } = await import("./sessions/grader.js");
        const maxIter = criteria.max_iterations ?? 3;
        const iteration = criteria.grader_iteration ?? 0;
        const outcomeId = criteria.outcome_id ?? null;
        const recentEvents = listEvents(sessionId, { limit: 50, order: "desc" });
        let lastAgentText = "";
        for (const evt of recentEvents) {
          if (evt.type === "agent.message") {
            const payload = JSON.parse(evt.payload_json);
            const text = (payload.content ?? []).filter((b) => b.type === "text" && b.text).map((b) => b.text).join("");
            if (text) {
              lastAgentText = text;
              break;
            }
          }
        }
        emit("span.outcome_evaluation_start", { outcome_id: outcomeId, iteration });
        const evaluation = await runGraderEvaluation(
          criteria.rubric,
          lastAgentText,
          agent.model.id
        );
        if (evaluation.usage.input_tokens || evaluation.usage.output_tokens) {
          bumpSessionStats(sessionId, {}, {
            input_tokens: evaluation.usage.input_tokens,
            output_tokens: evaluation.usage.output_tokens,
            cost_usd: 0
          });
        }
        setOutcomeCriteria(sessionId, {
          ...criteria,
          grader_iteration: iteration + 1
        });
        const finalResult = iteration + 1 >= maxIter && evaluation.result === "needs_revision" ? "max_iterations_reached" : evaluation.result;
        emit("span.outcome_evaluation_end", {
          outcome_id: outcomeId,
          result: finalResult,
          explanation: evaluation.feedback,
          iteration,
          usage: evaluation.usage
        });
        if (finalResult === "satisfied" || finalResult === "max_iterations_reached" || finalResult === "failed") {
          setOutcomeCriteria(sessionId, {
            ...criteria,
            grader_iteration: iteration + 1,
            status: finalResult,
            completed_at: new Date(nowMs()).toISOString(),
            explanation: evaluation.feedback
          });
        }
        if (evaluation.result === "needs_revision" && iteration + 1 < maxIter) {
          await runTurn(sessionId, [{
            kind: "text",
            eventId: `grader_feedback_${nowMs()}`,
            text: `[Grader feedback \u2014 iteration ${iteration + 1}/${maxIter}]

${evaluation.feedback}`
          }], _depth + 1, trace);
          return;
        }
      } catch (err) {
        console.warn(`[driver] outcome evaluation failed for ${sessionId}:`, err);
      }
    }
  }
  scheduleDrain(sessionId);
}
async function handleServerSideTool(sessionId, agent, parentTrace) {
  const recentEvents = listEvents(sessionId, { limit: 20, order: "desc" });
  for (const evt of recentEvents) {
    if (evt.type === "agent.custom_tool_use") {
      const payload = JSON.parse(evt.payload_json);
      const toolName = payload.name;
      const toolUseId = payload.tool_use_id ?? evt.id;
      if (toolName === "spawn_agent" && agent.threads_enabled) {
        const input = payload.input;
        if (input?.agent_id && input?.prompt) {
          if (agent.callable_agents.length > 0) {
            const allowed = agent.callable_agents.some((ca) => ca.id === input.agent_id);
            if (!allowed) {
              return { toolUseId, text: `Error: agent ${input.agent_id} is not in callable_agents list` };
            }
          }
          const { handleSpawnAgent } = await import("./sessions/threads.js");
          const sessionRow = getSessionRow(sessionId);
          const depth = sessionRow?.thread_depth ?? 0;
          try {
            const text = await handleSpawnAgent(
              sessionId,
              input.agent_id,
              input.prompt,
              depth,
              parentTrace
            );
            return { toolUseId, text };
          } catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            return { toolUseId, text: `Error: ${msg}` };
          }
        }
      }
      break;
    }
  }
  return null;
}
function scheduleDrain(sessionId) {
  const pending = drainPendingUserInputs(sessionId);
  if (pending.length === 0) return;
  void runTurn(sessionId, pending).catch((err) => {
    console.error(`[driver] scheduleDrain runTurn failed:`, err);
  });
}
var gd = globalThis;
function getPendingConfirmations() {
  if (!gd.__caPendingConfirmations) gd.__caPendingConfirmations = /* @__PURE__ */ new Set();
  return gd.__caPendingConfirmations;
}
async function checkPermissionSentinel(sessionId, sandboxName, provider) {
  if (getPendingConfirmations().has(sessionId)) return;
  try {
    const result = await provider.exec(
      sandboxName,
      ["test", "-f", PERMISSION_BRIDGE_PENDING_PATH],
      { timeoutMs: 5e3 }
    );
    if (result.exit_code !== 0) return;
  } catch {
    return;
  }
  let request;
  try {
    const result = await provider.exec(
      sandboxName,
      ["cat", PERMISSION_BRIDGE_REQUEST_PATH],
      { timeoutMs: 5e3 }
    );
    request = JSON.parse(result.stdout);
  } catch (err) {
    console.warn(`[driver] failed to read permission request for ${sessionId}:`, err);
    return;
  }
  getPendingConfirmations().add(sessionId);
  appendEvent(sessionId, {
    type: "agent.tool_confirmation_request",
    payload: {
      tool_name: request.tool_name ?? "unknown",
      tool_input: request.tool_input ?? {},
      tool_use_id: request.tool_use_id ?? ""
    },
    origin: "server",
    processedAt: nowMs()
  });
}
async function writePermissionResponse(sessionId, result, denyMessage) {
  const row = getSessionRow(sessionId);
  if (!row?.sandbox_name) {
    console.warn(`[driver] no sandbox for session ${sessionId}, cannot write permission response`);
    return;
  }
  const env = getEnvironment(row.environment_id);
  const provider = await resolveProvider({ envConfigProvider: env?.config?.provider });
  const permSecrets = getBySession(sessionId)?.vaultSecrets;
  const response = JSON.stringify({
    result,
    deny_message: denyMessage ?? void 0
  });
  try {
    await provider.exec(
      row.sandbox_name,
      ["sh", "-c", `cat > ${PERMISSION_BRIDGE_RESPONSE_PATH}`],
      { stdin: response, secrets: permSecrets }
    );
  } catch (err) {
    console.warn(`[driver] failed to write permission response for ${sessionId}:`, err);
  }
  getPendingConfirmations().delete(sessionId);
}
var pendingToolBridgeCalls = /* @__PURE__ */ new Set();
var customToolCallCounts = /* @__PURE__ */ new Map();
var MAX_CUSTOM_TOOL_CALLS_PER_TURN = 10;
function getPendingToolBridgeCalls() {
  return pendingToolBridgeCalls;
}
async function checkToolBridgeSentinel(sessionId, sandboxName, provider, secrets, trace) {
  if (pendingToolBridgeCalls.has(sessionId)) return;
  const callCount = customToolCallCounts.get(sessionId) ?? 0;
  if (callCount >= MAX_CUSTOM_TOOL_CALLS_PER_TURN) {
    console.warn(`[driver] ${sessionId}: custom tool call limit reached (${MAX_CUSTOM_TOOL_CALLS_PER_TURN}), skipping`);
    return;
  }
  try {
    const result = await provider.exec(
      sandboxName,
      ["test", "-f", TOOL_BRIDGE_PENDING_PATH],
      { secrets, timeoutMs: 5e3 }
    );
    if (result.exit_code !== 0) return;
  } catch {
    return;
  }
  let request;
  try {
    const result = await provider.exec(
      sandboxName,
      ["cat", TOOL_BRIDGE_REQUEST_PATH],
      { secrets, timeoutMs: 5e3 }
    );
    if (result.exit_code !== 0) {
      console.warn(
        `[driver] tool bridge request file unreadable for ${sessionId} (exit ${result.exit_code}): ${(result.stderr || result.stdout).slice(0, 200)}`
      );
      await provider.exec(sandboxName, ["rm", "-f", TOOL_BRIDGE_PENDING_PATH], { secrets }).catch((err) => {
        console.warn(`[driver] failed to remove stale tool-bridge sentinel for ${sessionId}:`, err);
      });
      return;
    }
    const clean = result.stdout.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, "").trim();
    if (!clean.startsWith("{")) {
      console.warn(
        `[driver] tool bridge request for ${sessionId} is not JSON (starts with ${JSON.stringify(clean.slice(0, 40))}), removing stale sentinel`
      );
      await provider.exec(sandboxName, ["rm", "-f", TOOL_BRIDGE_PENDING_PATH], { secrets }).catch((err) => {
        console.warn(`[driver] failed to remove non-JSON tool-bridge sentinel for ${sessionId}:`, err);
      });
      return;
    }
    const raw = JSON.parse(clean);
    if (raw.params && typeof raw.params === "object") {
      const params = raw.params;
      request = {
        tool_use_id: String(raw.id ?? ""),
        name: params.name ?? "",
        input: params.arguments ?? {}
      };
    } else {
      request = raw;
    }
  } catch (err) {
    console.warn(`[driver] failed to read tool bridge request for ${sessionId}:`, err);
    await provider.exec(sandboxName, ["rm", "-f", TOOL_BRIDGE_PENDING_PATH], { secrets }).catch(() => {
    });
    return;
  }
  pendingToolBridgeCalls.add(sessionId);
  customToolCallCounts.set(sessionId, (customToolCallCounts.get(sessionId) ?? 0) + 1);
  console.log(`[driver] ${sessionId} custom tool call #${customToolCallCounts.get(sessionId)}: ${request.name} (${request.tool_use_id})`);
  appendEvent(sessionId, {
    type: "agent.custom_tool_use",
    payload: {
      tool_use_id: request.tool_use_id ?? "",
      name: request.name ?? "unknown",
      input: request.input ?? {}
    },
    origin: "server",
    processedAt: nowMs(),
    traceId: trace.trace_id,
    spanId: trace.span_id,
    parentSpanId: trace.parent_span_id
  });
}
async function writeToolBridgeResponse(sessionId, content) {
  const row = getSessionRow(sessionId);
  if (!row?.sandbox_name) {
    console.warn(`[driver] no sandbox for session ${sessionId}, cannot write tool bridge response`);
    return;
  }
  const env = getEnvironment(row.environment_id);
  const provider = await resolveProvider({ envConfigProvider: env?.config?.provider });
  const bridgeSecrets = getBySession(sessionId)?.vaultSecrets;
  const augmentedContent = [
    ...Array.isArray(content) ? content : [],
    { type: "text", text: "\n[Tool result delivered. Your turn is complete \u2014 do not call this tool again.]" }
  ];
  const response = JSON.stringify({ content: augmentedContent });
  try {
    await provider.exec(
      row.sandbox_name,
      ["sh", "-c", `cat > ${TOOL_BRIDGE_RESPONSE_PATH}`],
      { stdin: response, secrets: bridgeSecrets }
    );
    await provider.exec(
      row.sandbox_name,
      ["rm", "-f", TOOL_BRIDGE_PENDING_PATH],
      { secrets: bridgeSecrets }
    );
    console.log(`[driver] ${sessionId} tool bridge response written`);
  } catch (err) {
    console.warn(`[driver] failed to write tool bridge response for ${sessionId}:`, err);
  }
  pendingToolBridgeCalls.delete(sessionId);
}

export {
  runTurn,
  writePermissionResponse,
  getPendingToolBridgeCalls,
  checkToolBridgeSentinel,
  writeToolBridgeResponse
};
