import {
  dockerProvider
} from "./chunk-4XXQAVKE.js";
import {
  resolveVaultSecrets
} from "./chunk-2RZARCTJ.js";
import {
  appendEvent
} from "./chunk-4NRHKTBW.js";
import {
  getSession,
  getSessionRow,
  setSessionSandbox
} from "./chunk-SEGKFEG5.js";
import {
  deleteSprite,
  listSprites
} from "./chunk-5UXN6QXJ.js";
import {
  allSessionSandboxes,
  countInEnv,
  register,
  unregister
} from "./chunk-EFOIR7R3.js";
import {
  resolveProvider,
  tryResolveProvider
} from "./chunk-ZKZME3DE.js";
import {
  getEnvironment,
  getEnvironmentRow,
  listEnvironments
} from "./chunk-GTYDSYTR.js";
import {
  addWarm,
  claimWarm,
  countInflight,
  countWarm,
  decrementInflight,
  incrementInflight
} from "./chunk-G7KUVNDY.js";
import {
  getAgent
} from "./chunk-LGPJPAHU.js";
import {
  resolveBackend
} from "./chunk-QCUM5WMI.js";
import {
  getConfig
} from "./chunk-2MAQ6JFL.js";
import {
  init_clock,
  nowMs
} from "./chunk-HFDLUBWN.js";
import {
  ApiError
} from "./chunk-EZYKRG4W.js";

// src/containers/lifecycle.ts
init_clock();
import { ulid } from "ulid";
var lcLog = (...args) => {
  if (process.env.DEBUG_LIFECYCLE === "1") console.log(...args);
};
var SANDBOX_NAME_PREFIX = "ca-sess-";
function wrapProviderWithSecrets(provider, secrets) {
  if (!secrets || Object.keys(secrets).length === 0) return provider;
  return {
    ...provider,
    exec: (n, argv, opts) => provider.exec(n, argv, { ...opts, secrets }),
    startExec: (n, opts) => provider.startExec(n, { ...opts, secrets }),
    create: (opts) => provider.create({ ...opts, secrets }),
    delete: (n, s) => provider.delete(n, s ?? secrets)
  };
}
async function installSkills(sandboxName, provider, skills, engine) {
  if (!skills || skills.length === 0) return;
  const SAFE_NAME_RE = /^[a-zA-Z0-9_.-]+$/;
  const safeSkills = skills.filter((s) => {
    if (!SAFE_NAME_RE.test(s.name)) {
      console.warn(`[lifecycle] skipping skill with unsafe name: ${s.name.slice(0, 40)}`);
      return false;
    }
    return true;
  });
  const homeResult = await provider.exec(sandboxName, ["sh", "-c", "echo $HOME"]);
  const homeDir = homeResult.stdout.replace(/[\x00-\x1f]/g, "").trim() || "/home/agent";
  async function writeSkillFiles(skill, baseDirPath) {
    await provider.exec(sandboxName, ["mkdir", "-p", baseDirPath]);
    await provider.exec(sandboxName, ["sh", "-c", 'cat > "$1"', "sh", `${baseDirPath}/SKILL.md`], {
      stdin: skill.content
    });
    if (skill.files) {
      for (const [relativePath, fileContent] of Object.entries(skill.files)) {
        if (relativePath === "SKILL.md") continue;
        const fullPath = `${baseDirPath}/${relativePath}`;
        const fileDir = fullPath.substring(0, fullPath.lastIndexOf("/"));
        await provider.exec(sandboxName, ["mkdir", "-p", fileDir]);
        if (fileContent.startsWith("base64:")) {
          await provider.exec(sandboxName, ["sh", "-c", 'base64 -d > "$1"', "sh", fullPath], {
            stdin: fileContent.slice(7)
          });
        } else {
          await provider.exec(sandboxName, ["sh", "-c", 'cat > "$1"', "sh", fullPath], {
            stdin: fileContent
          });
        }
      }
    }
  }
  const backend = resolveBackend(engine);
  const skillDirs = [...backend.extraSkillDirs ?? [], ".agents/skills"];
  for (const dir of skillDirs) {
    await provider.exec(sandboxName, ["mkdir", "-p", `${homeDir}/${dir}`]);
    for (const skill of safeSkills) {
      await writeSkillFiles(skill, `${homeDir}/${dir}/${skill.name}`);
    }
  }
}
function deriveSandboxName(sessionId) {
  const tail = sessionId.replace(/^[a-z]+_/, "").toLowerCase();
  return `${SANDBOX_NAME_PREFIX}${tail}`;
}
async function acquireForFirstTurn(sessionId) {
  const row = getSessionRow(sessionId);
  if (!row) throw new ApiError(404, "not_found_error", `session not found: ${sessionId}`);
  if (row.sandbox_name) return row.sandbox_name;
  const env = getEnvironmentRow(row.environment_id);
  if (!env) throw new ApiError(404, "not_found_error", "environment not found");
  if (env.state !== "ready") {
    throw new ApiError(
      400,
      "invalid_request_error",
      `environment is not ready (state=${env.state})`
    );
  }
  const config = getConfig();
  const envObj = getEnvironment(row.environment_id);
  const maxSandboxes = envObj?.config?.max_sandboxes ?? config.maxSandboxesPerEnv;
  let active = countInEnv(env.id);
  let warmCount = countWarm(env.id);
  if (active + warmCount >= maxSandboxes) {
    const evicted = await evictOldestIdleInEnv(env.id);
    if (evicted) {
      active = countInEnv(env.id);
      warmCount = countWarm(env.id);
    }
    if (active + warmCount >= maxSandboxes) {
      throw new ApiError(
        503,
        "server_busy",
        `environment ${env.id} sandbox pool exhausted (${active} active + ${warmCount} warm, limit ${maxSandboxes})`
      );
    }
  }
  const agent = getAgent(row.agent_id, row.agent_version);
  if (!agent) {
    throw new ApiError(404, "not_found_error", "agent not found for session");
  }
  const backend = resolveBackend(agent.engine);
  const provider = await resolveProvider({ envConfigProvider: envObj?.config?.provider });
  const sessionForSecrets = getSession(sessionId);
  const secrets = sessionForSecrets?.vault_ids?.length ? resolveVaultSecrets(sessionForSecrets.vault_ids) : {};
  if (provider.checkAvailability) {
    const result = await provider.checkAvailability(secrets);
    if (!result.available) {
      throw new Error(`provider ${provider.name} is not available: ${result.message}`);
    }
  }
  const sp = wrapProviderWithSecrets(provider, secrets);
  const warm = claimWarm(env.id, backend.name, provider.name);
  if (warm) {
    lcLog(`[lifecycle] ${sessionId} claimed warm container: ${warm.sandboxName}`);
    const name2 = warm.sandboxName;
    try {
      if (backend.supportsCustomTools) {
        const customTools = agent.tools.filter(
          (t) => t.type === "custom"
        );
        const allBridgeTools = [...customTools];
        if (agent.threads_enabled) {
          allBridgeTools.push({
            type: "custom",
            name: "spawn_agent",
            description: "Spawn a sub-agent to handle a task. Returns the sub-agent's response.",
            input_schema: {
              type: "object",
              properties: {
                agent_id: { type: "string", description: "ID of the agent to spawn" },
                prompt: { type: "string", description: "Task for the sub-agent" }
              },
              required: ["agent_id", "prompt"]
            }
          });
        }
        if (allBridgeTools.length > 0) {
          const { installToolBridge } = await import("./backends/claude/index.js");
          await installToolBridge(name2, allBridgeTools, sp);
        }
        if (agent.confirmation_mode) {
          const { installPermissionHook } = await import("./backends/claude/index.js");
          await installPermissionHook(name2, sp);
        }
      }
      if (agent.skills && agent.skills.length > 0) {
        lcLog(`[lifecycle] ${sessionId} installing ${agent.skills.length} skill(s) on warm container...`);
        await installSkills(name2, sp, agent.skills, agent.engine);
      }
    } catch (err) {
      await sp.delete(name2).catch(() => {
      });
      throw err;
    }
    const { listResources: listSessionResources2 } = await import("./db/session-resources.js");
    const tableResources2 = listSessionResources2(sessionId);
    if (tableResources2.length > 0) {
      const mapped = tableResources2.map((r) => {
        if (r.type === "file") return { type: "file", file_id: r.file_id, mount_path: r.mount_path };
        if (r.type === "github_repository") {
          const checkout = r.checkout;
          return {
            type: "github_repository",
            repository_url: r.url,
            mount_path: r.mount_path,
            branch: checkout?.type === "branch" ? checkout.name : void 0,
            commit: checkout?.type === "commit" ? checkout.name : void 0
          };
        }
        return { type: "uri", uri: r.url, mount_path: r.mount_path };
      });
      await provisionResources(name2, mapped, sp);
    } else {
      const session = getSession(sessionId);
      if (session?.resources && session.resources.length > 0) {
        await provisionResources(name2, session.resources, sp);
      }
    }
    await mountMemoryStores(name2, sessionId, sp);
    register({
      sandboxName: name2,
      envId: env.id,
      sessionId,
      createdAt: nowMs(),
      vaultSecrets: Object.keys(secrets).length > 0 ? secrets : void 0
    });
    setSessionSandbox(sessionId, name2);
    void replenishWarmPool(env.id, backend.name, provider, secrets);
    return name2;
  }
  const name = deriveSandboxName(sessionId);
  lcLog(`[lifecycle] ${sessionId} creating container via ${sp.name}...`);
  try {
    await sp.create({ name });
    lcLog(`[lifecycle] ${sessionId} container created: ${name}`);
  } catch (err) {
    if (String(err).includes("already exists")) {
      lcLog(`[lifecycle] ${sessionId} container ${name} already exists, reusing`);
    } else {
      throw err;
    }
  }
  const needsSlowPrep = backend.name !== "claude";
  if (needsSlowPrep) {
    appendEvent(sessionId, {
      type: "span.environment_setup_start",
      payload: { backend: backend.name },
      origin: "server",
      processedAt: nowMs()
    });
  }
  try {
    lcLog(`[lifecycle] ${sessionId} installing ${backend.name} engine on container...`);
    await backend.prepareOnSandbox(name, sp);
    lcLog(`[lifecycle] ${sessionId} engine installed`);
    if (backend.supportsCustomTools) {
      const customTools = agent.tools.filter(
        (t) => t.type === "custom"
      );
      const allBridgeTools = [...customTools];
      if (agent.threads_enabled) {
        allBridgeTools.push({
          type: "custom",
          name: "spawn_agent",
          description: "Spawn a sub-agent to handle a task. Returns the sub-agent's response.",
          input_schema: {
            type: "object",
            properties: {
              agent_id: { type: "string", description: "ID of the agent to spawn" },
              prompt: { type: "string", description: "Task for the sub-agent" }
            },
            required: ["agent_id", "prompt"]
          }
        });
      }
      if (allBridgeTools.length > 0) {
        const { installToolBridge } = await import("./backends/claude/index.js");
        await installToolBridge(name, allBridgeTools, sp);
      }
      if (agent.confirmation_mode) {
        const { installPermissionHook } = await import("./backends/claude/index.js");
        await installPermissionHook(name, sp);
      }
    }
    if (agent.skills && agent.skills.length > 0) {
      lcLog(`[lifecycle] ${sessionId} installing ${agent.skills.length} skill(s)...`);
      await installSkills(name, sp, agent.skills, agent.engine);
      lcLog(`[lifecycle] ${sessionId} skills installed`);
    }
  } catch (err) {
    await sp.delete(name).catch(() => {
    });
    throw err;
  }
  if (needsSlowPrep) {
    appendEvent(sessionId, {
      type: "span.environment_setup_end",
      payload: { backend: backend.name },
      origin: "server",
      processedAt: nowMs()
    });
  }
  const { listResources: listSessionResources } = await import("./db/session-resources.js");
  const tableResources = listSessionResources(sessionId);
  if (tableResources.length > 0) {
    const mapped = tableResources.map((r) => {
      if (r.type === "file") return { type: "file", file_id: r.file_id, mount_path: r.mount_path };
      if (r.type === "github_repository") {
        const checkout = r.checkout;
        return {
          type: "github_repository",
          repository_url: r.url,
          mount_path: r.mount_path,
          branch: checkout?.type === "branch" ? checkout.name : void 0,
          commit: checkout?.type === "commit" ? checkout.name : void 0
        };
      }
      return { type: "uri", uri: r.url, mount_path: r.mount_path };
    });
    await provisionResources(name, mapped, sp);
  } else {
    const session = getSession(sessionId);
    if (session?.resources && session.resources.length > 0) {
      await provisionResources(name, session.resources, sp);
    }
  }
  await mountMemoryStores(name, sessionId, sp);
  register({
    sandboxName: name,
    envId: env.id,
    sessionId,
    createdAt: nowMs(),
    vaultSecrets: Object.keys(secrets).length > 0 ? secrets : void 0
  });
  setSessionSandbox(sessionId, name);
  return name;
}
function effectiveWarmPoolSize(envId) {
  const cfg = getConfig();
  const envObj = getEnvironment(envId);
  return envObj?.config?.warm_pool_size ?? cfg.warmPoolSize;
}
async function replenishWarmPool(envId, engine, provider, secrets) {
  if (!provider.supportsWarmPool) return;
  const target = effectiveWarmPoolSize(envId);
  if (target <= 0) return;
  if (countWarm(envId) + countInflight(envId) >= target) return;
  const cfg = getConfig();
  const envObj = getEnvironment(envId);
  const maxSandboxes = envObj?.config?.max_sandboxes ?? cfg.maxSandboxesPerEnv;
  if (countInEnv(envId) + countWarm(envId) + countInflight(envId) >= maxSandboxes) return;
  incrementInflight(envId);
  try {
    const sp = wrapProviderWithSecrets(provider, secrets);
    const name = `ca-warm-${ulid().toLowerCase()}`;
    lcLog(`[warm-pool] creating warm container for env=${envId} engine=${engine}: ${name}`);
    await sp.create({ name });
    const backend = resolveBackend(engine);
    await backend.prepareOnSandbox(name, sp);
    const cfg2 = getConfig();
    addWarm({
      sandboxName: name,
      envId,
      engine,
      provider: provider.name,
      createdAt: nowMs(),
      expiresAt: nowMs() + cfg2.warmPoolTtlMs,
      vaultSecrets: Object.keys(secrets).length > 0 ? secrets : void 0
    });
    lcLog(`[warm-pool] warm container ready: ${name} (env=${envId})`);
  } catch (err) {
    console.warn(`[warm-pool] replenish failed for env=${envId} engine=${engine}:`, err);
  } finally {
    decrementInflight(envId);
  }
}
async function fillWarmPools() {
  const cfg = getConfig();
  const envs = listEnvironments({ includeArchived: false, limit: 100 });
  const readyEnvs = envs.filter((e) => e.state === "ready");
  if (cfg.warmPoolSize <= 0 && !readyEnvs.some((e) => (e.config?.warm_pool_size ?? 0) > 0)) return;
  if (readyEnvs.length === 0) return;
  const CONCURRENCY = 3;
  let active = 0;
  let idx = 0;
  async function processNext() {
    while (idx < readyEnvs.length) {
      if (active >= CONCURRENCY) {
        await new Promise((r) => setTimeout(r, 0));
        continue;
      }
      const env = readyEnvs[idx++];
      active++;
      void fillOneEnv(env).finally(() => active--);
    }
  }
  void processNext();
}
async function fillOneEnv(env) {
  const target = effectiveWarmPoolSize(env.id);
  if (target <= 0) return;
  try {
    const provider = await tryResolveProvider({ envConfigProvider: env.config?.provider });
    if (!provider) return;
    if (!provider.supportsWarmPool) return;
    const engine = env.config?.default_engine ?? "claude";
    const backend = resolveBackend(engine);
    const needed = target - countWarm(env.id) - countInflight(env.id);
    for (let i = 0; i < needed; i++) {
      void replenishWarmPool(env.id, backend.name, provider, {});
    }
  } catch (err) {
    console.warn(`[warm-pool] fillOneEnv failed for env=${env.id}:`, err);
  }
}
async function provisionResources(sandboxName, resources, provider) {
  await provider.exec(sandboxName, ["mkdir", "-p", "/mnt/session/resources", "/mnt/session/uploads", "/mnt/session/outputs"]);
  const MAX_RESOURCE_BYTES = 50 * 1024 * 1024;
  const SAFE_PATH_RE = /^[a-zA-Z0-9_./\-]+$/;
  function sanitizeMountPath(p) {
    if (!p) return void 0;
    if (!SAFE_PATH_RE.test(p) || p.includes("..")) {
      console.warn(`[lifecycle] rejecting unsafe mount_path: ${p.slice(0, 40)}`);
      return void 0;
    }
    return p;
  }
  for (let i = 0; i < resources.length; i++) {
    const r = resources[i];
    const safeMountPath = sanitizeMountPath(r.mount_path);
    let mountTarget;
    if (safeMountPath) {
      mountTarget = safeMountPath.startsWith("/") ? safeMountPath : `/mnt/session/uploads/${safeMountPath}`;
    } else if (r.type === "file" && r.file_id) {
      const { getFile: getFileRow } = await import("./db/files.js");
      const fileRow = getFileRow(r.file_id);
      const fname = fileRow?.filename ?? `file_${i}`;
      mountTarget = `/mnt/session/uploads/${r.file_id}/${fname}`;
    } else {
      mountTarget = `/mnt/session/resources/resource_${i}`;
    }
    const dir = mountTarget.substring(0, mountTarget.lastIndexOf("/"));
    if (dir) {
      await provider.exec(sandboxName, ["mkdir", "-p", dir]);
    }
    const writeTo = async (target, content) => {
      await provider.exec(sandboxName, ["sh", "-c", 'cat > "$1"', "sh", target], { stdin: content });
      await provider.exec(sandboxName, ["chmod", "a-w", target]);
    };
    if (r.type === "uri" && r.uri) {
      try {
        const resp = await fetch(r.uri, { signal: AbortSignal.timeout(3e4) });
        if (!resp.ok) {
          console.warn(`[lifecycle] failed to fetch resource ${r.uri}: ${resp.status}`);
          continue;
        }
        const contentLength = resp.headers.get("Content-Length");
        if (contentLength && parseInt(contentLength, 10) > MAX_RESOURCE_BYTES) {
          console.warn(`[lifecycle] skipping resource ${r.uri}: too large`);
          continue;
        }
        const content = await resp.text();
        if (Buffer.byteLength(content, "utf8") > MAX_RESOURCE_BYTES) {
          console.warn(`[lifecycle] skipping resource ${r.uri}: body too large`);
          continue;
        }
        await writeTo(mountTarget, content);
      } catch (err) {
        console.warn(`[lifecycle] failed to provision URI resource ${r.uri}:`, err);
      }
    } else if (r.type === "text" && r.content) {
      try {
        await writeTo(mountTarget, r.content);
      } catch (err) {
        console.warn(`[lifecycle] failed to provision text resource:`, err);
      }
    } else if (r.type === "file" && r.file_id) {
      try {
        const { getFile } = await import("./db/files.js");
        const { readFile: readStoredFile } = await import("./files/storage.js");
        const fileRow = getFile(r.file_id);
        if (!fileRow) {
          console.warn(`[lifecycle] file not found: ${r.file_id}`);
          continue;
        }
        const data = readStoredFile(fileRow.storage_path);
        if (data.length > MAX_RESOURCE_BYTES) {
          console.warn(`[lifecycle] skipping file ${r.file_id}: too large`);
          continue;
        }
        const b64 = data.toString("base64");
        await provider.exec(
          sandboxName,
          ["sh", "-c", 'base64 -d > "$1"', "sh", mountTarget],
          { stdin: b64 }
        );
        await provider.exec(sandboxName, ["chmod", "a-w", mountTarget]);
      } catch (err) {
        console.warn(`[lifecycle] failed to provision file resource ${r.file_id}:`, err);
      }
    } else if (r.type === "github_repository") {
      const cloneUrl = r.repository_url ?? r.url;
      if (!cloneUrl) continue;
      try {
        const repoDir = safeMountPath ? safeMountPath.startsWith("/") ? safeMountPath : `/mnt/session/uploads/${safeMountPath}` : `/mnt/session/resources/repo_${i}`;
        const SAFE_REF_RE = /^[a-zA-Z0-9_./\-]+$/;
        const safeBranch = r.branch && SAFE_REF_RE.test(r.branch) ? r.branch : void 0;
        const safeCommit = r.commit && SAFE_REF_RE.test(r.commit) ? r.commit : void 0;
        if (r.branch && !safeBranch) console.warn(`[lifecycle] rejecting unsafe branch: ${r.branch.slice(0, 40)}`);
        if (r.commit && !safeCommit) console.warn(`[lifecycle] rejecting unsafe commit: ${r.commit.slice(0, 40)}`);
        const gitArgs = ["git", "clone", "--depth", "1"];
        if (safeBranch) {
          gitArgs.push("--branch", safeBranch);
        }
        gitArgs.push("--", cloneUrl, repoDir);
        const result = await provider.exec(sandboxName, gitArgs, { timeoutMs: 12e4 });
        if (safeCommit && result.exit_code === 0) {
          await provider.exec(sandboxName, ["git", "-C", repoDir, "checkout", safeCommit], { timeoutMs: 3e4 });
        }
        if (result.exit_code !== 0) {
          console.warn(`[lifecycle] git clone failed for ${cloneUrl}: ${result.stderr.slice(0, 200)}`);
        }
      } catch (err) {
        console.warn(`[lifecycle] failed to clone repo ${cloneUrl}:`, err);
      }
    }
  }
}
async function mountMemoryStores(sandboxName, sessionId, provider) {
  const { listResources: listSessionResources } = await import("./db/session-resources.js");
  const { getMemoryStore, listMemories } = await import("./db/memory.js");
  const resources = listSessionResources(sessionId);
  const memStoreResources = resources.filter((r) => r.type === "memory_store" && r.memory_store_id);
  if (memStoreResources.length === 0) return;
  await provider.exec(sandboxName, ["mkdir", "-p", "/mnt/memory"]);
  for (const r of memStoreResources) {
    const store = getMemoryStore(r.memory_store_id);
    if (!store) {
      lcLog(`[lifecycle] memory store not found: ${r.memory_store_id}`);
      continue;
    }
    const storeName = store.name.replace(/[^a-zA-Z0-9_.-]/g, "_");
    const storeDir = `/mnt/memory/${storeName}`;
    await provider.exec(sandboxName, ["mkdir", "-p", storeDir]);
    const memories = listMemories(store.id);
    for (const mem of memories) {
      const memPath = `${storeDir}/${mem.path}`;
      const dir = memPath.substring(0, memPath.lastIndexOf("/"));
      if (dir && dir !== storeDir) {
        await provider.exec(sandboxName, ["mkdir", "-p", dir]);
      }
      await provider.exec(sandboxName, ["sh", "-c", 'cat > "$1"', "sh", memPath], {
        stdin: mem.content
      });
    }
    lcLog(`[lifecycle] ${sessionId} mounted ${memories.length} memories from store "${store.name}" at ${storeDir}`);
  }
}
var EVICT_MIN_IDLE_MS = Number(process.env.EVICT_MIN_IDLE_MS ?? 3e4);
async function evictOldestIdleInEnv(envId) {
  const candidates = allSessionSandboxes().filter((e) => e.envId === envId);
  if (candidates.length === 0) return null;
  const now = nowMs();
  const eligible = [];
  for (const entry of candidates) {
    const row = getSessionRow(entry.sessionId);
    if (!row || row.status !== "idle" || row.archived_at != null) continue;
    const idleSince = row.idle_since ?? row.created_at;
    if (now - idleSince < EVICT_MIN_IDLE_MS) continue;
    eligible.push({ sessionId: entry.sessionId, idleSince });
  }
  if (eligible.length === 0) return null;
  eligible.sort((a, b) => a.idleSince - b.idleSince);
  const victim = eligible[0].sessionId;
  console.log(`[lifecycle] evict-on-pressure: env ${envId} pool full, evicting idle session ${victim} (idle for ${Math.round((now - eligible[0].idleSince) / 1e3)}s)`);
  try {
    await releaseSession(victim);
    const { updateSessionStatus, archiveSession } = await import("./db/sessions.js");
    appendEvent(victim, {
      type: "session.status_terminated",
      payload: { reason: "evicted_on_pressure" },
      origin: "server",
      processedAt: nowMs()
    });
    updateSessionStatus(victim, "terminated", "evicted_on_pressure");
    archiveSession(victim);
  } catch (err) {
    console.warn(`[lifecycle] evict-on-pressure: failed to release ${victim}:`, err);
    return null;
  }
  return victim;
}
async function releaseSession(sessionId) {
  const entry = unregister(sessionId);
  const row = getSessionRow(sessionId);
  const name = entry?.sandboxName ?? row?.sandbox_name ?? null;
  if (name) {
    const envObj = row ? getEnvironment(row.environment_id) : null;
    const provider = await tryResolveProvider({ envConfigProvider: envObj?.config?.provider });
    if (!provider) return;
    await provider.delete(name, entry?.vaultSecrets).catch((err) => {
      console.warn(`releaseSession: failed to delete container ${name}:`, err);
    });
  }
  if (row?.sandbox_name) setSessionSandbox(sessionId, null);
}
async function reconcileOrphanSandboxes() {
  let deleted = 0;
  let kept = 0;
  const liveNames = new Set(
    allSessionSandboxes().map((e) => e.sandboxName).filter(Boolean)
  );
  let token;
  for (; ; ) {
    const res = await listSprites({
      prefix: SANDBOX_NAME_PREFIX,
      max_results: 100,
      continuation_token: token
    });
    for (const s of res.sprites) {
      if (liveNames.has(s.name)) {
        kept++;
      } else {
        await deleteSprite(s.name).catch(() => {
        });
        deleted++;
      }
    }
    if (!res.has_more) break;
    token = res.next_continuation_token ?? void 0;
    if (!token) break;
  }
  return { deleted, kept };
}
async function reconcileDockerOrphanSandboxes() {
  let deleted = 0;
  let kept = 0;
  const liveNames = new Set(
    allSessionSandboxes().map((e) => e.sandboxName).filter(Boolean)
  );
  const containers = await dockerProvider.list({ prefix: SANDBOX_NAME_PREFIX });
  for (const c of containers) {
    if (liveNames.has(c.name)) {
      kept++;
    } else {
      await dockerProvider.delete(c.name).catch(() => {
      });
      deleted++;
    }
  }
  return { deleted, kept };
}

export {
  wrapProviderWithSecrets,
  installSkills,
  acquireForFirstTurn,
  replenishWarmPool,
  fillWarmPools,
  provisionResources,
  mountMemoryStores,
  releaseSession,
  reconcileOrphanSandboxes,
  reconcileDockerOrphanSandboxes
};
