UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

76 lines (75 loc) • 3.09 kB
import { randomUUID } from "node:crypto"; import { thrownLabel, thrownText } from "./thrown-value.js"; export const WORKTREE_PARENT = ".sema-worktrees"; function shq(s) { return `'${s.replace(/'/g, "'\\''")}'`; } async function removeWorktree(baseEnv, repoRoot, worktreeDir) { let detail = ""; for (let attempt = 0; attempt < 2; attempt++) { try { const r = await baseEnv.exec(`git worktree remove --force ${shq(worktreeDir)}`, { cwd: repoRoot }); if (r.ok && r.value.exitCode === 0) return { ok: true }; detail = r.ok ? r.value.stderr || r.value.stdout || `exit ${r.value.exitCode}` : thrownText(r.error); } catch (err) { detail = thrownLabel(err); } } return { ok: false, detail }; } export async function addWorktree(baseEnv, opts) { const safe = (opts.sessionId || "task").replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 64); const worktreeDir = `${opts.repoRoot}/${WORKTREE_PARENT}/${safe}-${randomUUID().slice(0, 8)}`; const add = await baseEnv.exec(`git worktree add --detach ${shq(worktreeDir)} ${shq(opts.commit ?? "HEAD")}`, { cwd: opts.repoRoot, }); if (!add.ok || add.value.exitCode !== 0) { const detail = add.ok ? add.value.stderr || add.value.stdout : thrownText(add.error); throw new Error(`git worktree add failed: ${detail}`); } const excludeLine = shq(`/${WORKTREE_PARENT}/`); await baseEnv .exec(`ex="$(git rev-parse --git-path info/exclude)" && mkdir -p "$(dirname "$ex")" && ` + `{ grep -qxF ${excludeLine} "$ex" 2>/dev/null || printf '%s\\n' ${excludeLine} >> "$ex"; }`, { cwd: opts.repoRoot }) .catch(() => undefined); let inner; try { inner = await opts.rootEnvAt(worktreeDir); } catch (err) { await removeWorktree(baseEnv, opts.repoRoot, worktreeDir); throw err; } const innerDestroy = inner.destroy; const env = inner; env.destroy = async () => { try { if (typeof innerDestroy === "function") await innerDestroy.call(inner); } finally { const removed = await removeWorktree(baseEnv, opts.repoRoot, worktreeDir); if (!removed.ok) { throw new Error(`git worktree remove failed twice — ${worktreeDir} is now an orphan (dir + registration remain; ` + `\`git worktree prune\` cannot reclaim it while the dir exists — remove it manually): ${removed.detail}`); } } }; return { env, worktreeDir }; } export async function pruneWorktrees(baseEnv, repoRoot) { try { const r = await baseEnv.exec("git worktree prune", { cwd: repoRoot }); if (r.ok && r.value.exitCode === 0) return { ok: true }; return { ok: false, detail: r.ok ? (r.value.stderr || r.value.stdout || `exit ${r.value.exitCode}`).trim() : thrownText(r.error), }; } catch (err) { return { ok: false, detail: thrownLabel(err) }; } }