/** * Repo materialization for GitHub-backed repositories. * * A GitHub repo is never cloned onto the server host. The repo is cloned * *inside* the session's sandbox, so the agent's file tools and command tools * operate entirely against the remote checkout. * * - `materializeRepo(row, token)` clones the repo inside the sandbox when no * checkout exists yet (a base-image boot, a wiped disk), using a short-lived * installation token that is scrubbed from the git remote afterwards so it * never persists in the VM. A checkout that is already there, from a repo * template image or an earlier start, is left exactly as it is. * * This module owns everything git/GitHub: clone, commit/push, setup/teardown commands, * and `gh pr create`. Workdir layout lives in `../sandbox/workdir`. */ import type { ExecutableSandbox, SandboxCommandResult } from '../../sandbox/materialization.js'; import type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js'; type MaterializationStore = Pick; interface RepoMaterializationBinding { id: string; sandboxWorkdir: string; materializedAt: Date | null; } /** * Single-quote a string for safe POSIX shell interpolation. Wraps the value in * single quotes and escapes any embedded single quote using the canonical * close-quote / escaped-quote / reopen-quote sequence (`'\''`). This is the * standard POSIX-safe construction and prevents the quoted string from being * terminated early. */ export declare function shellQuote(value: string): string; /** * Default hang guard for sandbox shell commands. Generous by design — large * clones and dependency installs legitimately take minutes; the guard exists * so a wedged sandbox surfaces a failure instead of hanging the request that * triggered materialization forever. */ export declare const DEFAULT_COMMAND_TIMEOUT_MS: number; /** Branch checkout only fetches one ref — a much tighter budget applies. */ export declare const CHECKOUT_COMMAND_TIMEOUT_MS: number; interface ShOptions { /** Override the hang-guard budget for this command. */ timeoutMs?: number; /** Human-readable phase name included in the timeout error. */ phase?: string; } /** * Run a shell script in the sandbox via `sh -c`, bounded by a hang guard. * Transient transport-level 5xx failures (proxy hiccups while the VM boots) * are retried with a short backoff; every script routed through here is safe * to re-run. Hang-guard timeouts are NOT retried — the budget applies to the * command as a whole. */ export declare function sh(sandbox: ExecutableSandbox, script: string, options?: ShOptions): Promise; /** Error raised when the sandbox cannot materialize the repo (actionable). */ export declare class MaterializeError extends Error { readonly code: 'git-missing' | 'egress-blocked' | 'clone-failed' | 'pull-failed' | 'push-failed' | 'commit-failed' | 'gh-missing' | 'pr-failed'; constructor(message: string, code: 'git-missing' | 'egress-blocked' | 'clone-failed' | 'pull-failed' | 'push-failed' | 'commit-failed' | 'gh-missing' | 'pr-failed'); } /** Repo metadata needed to materialize, read from the org-owned project row. */ export interface RepoMaterializeInfo { repoFullName: string; defaultBranch: string; } /** Options for {@link materializeRepo}. */ export interface MaterializeRepoOptions { /** The per-(project,user) sandbox binding whose workdir this materializes into. */ row: RepoMaterializationBinding; /** Repo metadata from the org-owned project row. */ repoInfo: RepoMaterializeInfo; /** The live sandbox to run git inside. */ sandbox: ExecutableSandbox; /** A freshly minted, short-lived installation access token. */ token: string; storage: MaterializationStore; } /** * Materialize the repo inside the user's sandbox: clone when no checkout of * this repo exists, otherwise nothing. Scrubs the install token from the * remote after a clone and sets `materialized_at` on the per-user sandbox * binding row. */ export declare function materializeRepo(options: MaterializeRepoOptions): Promise; export interface SessionBranchOptions { branch: string; baseBranch: string; token: string; repoFullName: string; /** A pull-request card's session starts on the PR head instead of the base tip. */ pullRequestNumber?: number; } /** Check out a session's branch inside its isolated repository clone. */ export declare function checkoutSessionBranch(sandbox: ExecutableSandbox, workdir: string, options: SessionBranchOptions): Promise; /** * Validate a git ref (branch) name. Server-side defense-in-depth: only allow a * conservative character set so a branch can never be built into a shell * command in a way that escapes quoting. Mirrors the route-layer check. */ export declare function isValidGitRef(value: unknown): value is string; /** Identity used to author commits inside the sandbox. */ export interface GitIdentity { name?: string | null; email?: string | null; /** GitHub login, used to derive a stable noreply identity when name/email are absent. */ login?: string | null; } /** * Resolve a concrete `{ name, email }` for git authorship from a possibly-sparse * identity. Falls back to a GitHub-style noreply identity so commits are never * authored with an empty or host-derived identity. */ export declare function resolveGitIdentity(identity: GitIdentity): { name: string; email: string; }; /** * Configure `user.name` / `user.email` for the given repo working tree inside * the sandbox so commits are authored correctly. Values are shell-quoted. */ export declare function configureGitIdentity(sandbox: ExecutableSandbox, workdir: string, identity: GitIdentity): Promise; /** * Temporarily rewrite `origin` to a tokenized URL, run `fn` (e.g. a push), and * **always** scrub the remote back to the tokenless URL afterwards. The token * therefore only ever lives in the remote URL for the duration of the * operation and is never left in the VM's git config. * * Once the tokenized URL is installed a failed scrub may leave the token * persisted, so it is always surfaced: on its own after a successful `fn`, * appended to `fn`'s own error otherwise — `fn`'s error is never replaced. * Only a failed set-url (the token never reached the remote) downgrades the * scrub to best-effort. */ export declare function withInstallToken(sandbox: ExecutableSandbox, workdir: string, repoFullName: string, token: string, fn: () => Promise): Promise; /** * Push a branch back to GitHub from inside the sandbox using a short-lived * installation token. The branch is ref-validated, the token is injected only * into the remote URL via `withInstallToken`, and egress failures are * classified into actionable errors. */ export declare function pushBranch(sandbox: ExecutableSandbox, workdir: string, branch: string, token: string, repoFullName: string): Promise; export interface CommitResult { /** True when a commit was created; false when there was nothing to commit. */ committed: boolean; } /** * Stage every change in the working tree and create a commit inside the * sandbox. The git identity is configured first so authorship is correct. When * there is nothing to commit this is a no-op (`committed: false`) rather than an * error, so callers can safely commit-then-push without first diffing. * * @param sandbox the live sandbox containing the checkout * @param workdir the session workdir to commit in * @param message the commit message (quoted; arbitrary text is safe) * @param identity authorship identity for the commit */ export declare function commitAll(sandbox: ExecutableSandbox, workdir: string, message: string, identity: GitIdentity): Promise; /** Error raised when the org's setup or teardown command fails in the sandbox. */ export declare class SetupCommandError extends Error { readonly code: 'setup-failed' | 'teardown-failed'; constructor(message: string, code: 'setup-failed' | 'teardown-failed'); } export declare function runSetupCommand(sandbox: ExecutableSandbox, workdir: string, command: string): Promise; /** * Run the repository's best-effort teardown command from the materialized * session workdir. Callers own lifecycle policy: this helper reports failures * so the retirement coordinator can log them while still continuing with * scrub, pooling/destruction, cache invalidation, and row deletion. */ export declare function runTeardownCommand(sandbox: ExecutableSandbox, workdir: string, command: string, options?: { timeoutMs?: number; }): Promise; export interface CreatePullRequestArgs { /** Short-lived installation token, injected only into the `gh` process env. */ token: string; /** Base branch the PR merges into. Ref-validated. */ base: string; /** Head branch the PR is opened from. Ref-validated. */ head: string; /** PR title. */ title: string; /** PR body (optional). */ body?: string; } export interface CreatePullRequestResult { /** The PR URL parsed from `gh pr create` stdout. */ url: string; } /** * Open a pull request from inside the sandbox via `gh pr create`. The token is * passed only through a per-invocation `GH_TOKEN` env scoped to the single `gh` * process (never persisted), all arguments are shell-quoted, and the resulting * PR URL is parsed from stdout. * * @param sandbox live sandbox containing the checkout * @param workdir the worktree (or repo) path the PR head branch is checked out in */ export declare function createPullRequest(sandbox: ExecutableSandbox, workdir: string, { token, base, head, title, body }: CreatePullRequestArgs): Promise; export {}; //# sourceMappingURL=sandbox.d.ts.map