UNPKG

@tanstack/ai-sandbox

Version:

Provider-agnostic sandbox layer for TanStack AI — run harness adapters inside isolated sandboxes (defineSandbox, defineWorkspace, withSandbox) with a uniform SandboxHandle, workspace bootstrap, policy, and resumable lifecycle.

111 lines (110 loc) 4.66 kB
import { resolveAllSecrets, resolveSecret } from "./secrets.js"; import { resolveHarnessCwd } from "./harness-cwd.js"; import { buildSetupPlan } from "./setup-plan.js"; import { createBootstrapShell } from "./shell.js"; import { mergeAgentsContent, resolveGitSkillDir, writeAgentsFile } from "./agents-file.js"; //#region src/bootstrap.ts /** * Workspace bootstrap engine — provider-agnostic because it only uses the * {@link SandboxHandle} contract. Runs once when a sandbox is freshly created * (or restored without its working tree): land the source, inject secrets, * detect the package manager, and run setup commands. * * Harness-specific projection (CLAUDE.md, agent skills, MCP config) is NOT done * here — that's each adapter's `projectWorkspace()` hook, since the format * differs per harness. */ var LOCKFILES = { pnpm: "pnpm-lock.yaml", yarn: "yarn.lock", bun: "bun.lockb", npm: "package-lock.json" }; var DEFAULT_WORKSPACE_ROOT = "/workspace"; /** Resolve the package manager, detecting from a lockfile when `'auto'`. */ async function detectPackageManager(handle, workspace, root) { const pm = workspace.packageManager ?? "auto"; if (pm !== "auto") return pm; for (const [manager, lockfile] of Object.entries(LOCKFILES)) if (await handle.fs.exists(`${root}/${lockfile}`)) return manager; } /** * Bootstrap a freshly created sandbox's workspace. Idempotent enough to be safe * on restore: a git clone into a populated dir is skipped by checking for the * target dir first. */ async function bootstrapWorkspace(handle, workspace, options = {}) { const root = workspace.root ?? "/workspace"; if (workspace.secrets !== void 0) { const resolved = resolveAllSecrets(workspace.secrets); if (Object.keys(resolved).length > 0) await handle.env.set(resolved); } if (workspace.source.type === "git") { if (!await handle.fs.exists(`${root}/.git`)) await handle.git.clone({ url: workspace.source.url, ref: workspace.source.ref, auth: workspace.source.auth, ...workspace.source.depth !== void 0 ? { depth: workspace.source.depth } : {} }); } const skills = workspace.skills ?? []; for (const skill of skills) if (skill.kind === "git") { const url = skill.repo.startsWith("http") ? skill.repo : `https://github.com/${skill.repo}.git`; const dir = resolveHarnessCwd(handle, skill.into ?? resolveGitSkillDir(root, skill)); const auth = skill.secret !== void 0 && workspace.secrets !== void 0 ? { token: resolveSecret(workspace.secrets, skill.secret) } : void 0; await handle.git.clone({ url, dir, ...auth !== void 0 ? { auth } : {}, depth: 1 }); } let agentsContent; if (workspace.instructions !== void 0 && workspace.instructions.length > 0) agentsContent = workspace.instructions; else { const agentsFileSkill = skills.find((s) => s.kind === "file" && s.path === "AGENTS.md"); if (agentsFileSkill !== void 0) agentsContent = agentsFileSkill.content; } agentsContent = mergeAgentsContent(agentsContent, workspace.scripts); if (agentsContent !== void 0) await writeAgentsFile(handle, root, agentsContent); for (const skill of skills) if (skill.kind === "file" && skill.path !== "AGENTS.md") await handle.fs.write(`${root}/${skill.path}`, skill.content); const packageManager = await detectPackageManager(handle, workspace, root); const ranSetup = []; const plan = buildSetupPlan(workspace.setup); if (plan.length > 0) { const shell = await createBootstrapShell(handle, { cwd: root }); try { for (const group of plan) if (group.kind === "serial") { const result = await shell.run(group.command); if (result.exitCode !== 0) { const tail = result.stdout.trim().slice(-1500); throw new Error(`setup step failed: ${group.command} (exit ${result.exitCode})${tail ? `\n${tail}` : ""}`); } ranSetup.push(group.command); } else { const { cwd, env } = await shell.forkState(); const failed = (await Promise.all(group.commands.map((command) => handle.process.exec(command, { cwd, env, ...options.signal ? { signal: options.signal } : {} }).then((res) => ({ command, res }))))).find((entry) => entry.res.exitCode !== 0); if (failed !== void 0) { const tail = `${failed.res.stdout}\n${failed.res.stderr}`.trim().slice(-1500); throw new Error(`setup step failed: ${failed.command} (exit ${failed.res.exitCode})${tail ? `\n${tail}` : ""}`); } ranSetup.push(...group.commands); } } finally { await shell.dispose(); } } return { packageManager, ranSetup }; } //#endregion export { DEFAULT_WORKSPACE_ROOT, bootstrapWorkspace, detectPackageManager }; //# sourceMappingURL=bootstrap.js.map