/** * 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), supplying the * short-lived token only in that Git process's environment. 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 { RepositoryAccess } from '../../capabilities/version-control.js'; import { isValidGitRef } from '../../sandbox/git-ref.js'; 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`. This is reserved for the * explicitly configured repository lifecycle hooks; programmatic commands use * {@link execute} with an argv array. */ 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; /** Provider-supplied, credential-free HTTPS clone URL. */ cloneUrl?: string; /** Username paired with the bearer token for git-over-HTTPS. */ authUsername?: 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. Credentials are process-scoped and the * stored remote remains credential-free. 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; cloneUrl?: string; authUsername?: string; /** A pull-request card's session starts on the PR head instead of the base tip. */ pullRequestNumber?: number; /** A GitLab merge-request card's session starts on the MR head instead of the base tip. */ mergeRequestNumber?: number; } /** Check out a session's branch inside its isolated repository clone. */ export declare function checkoutSessionBranch(sandbox: ExecutableSandbox, workdir: string, options: SessionBranchOptions): Promise; export interface SyncEnvironmentRepositoryOptions { /** The session branch to resume when the remote already carries it. */ branch: string; /** The repository's default branch; new session branches start at its remote tip. */ defaultBranch: string; token: string; repoFullName: string; cloneUrl?: string; authUsername?: string; } export interface EnvironmentSyncResult { outcome: 'resumed' | 'created' | 'kept'; /** The branch the checkout ends on; `null` when it stays detached. */ branch: string | null; } /** * Bring a secondary environment repository onto the session branch at * session start. The primary repository keeps {@link checkoutSessionBranch}; * every other repository follows a conservative rule: resume the session * branch when the remote or the local clone has it, otherwise create it from * the default branch tip (a detached HEAD, the template image pinned at its * build commit, is moved there first). A checkout that sits on any other * branch is left alone: it may carry local-only commits. */ export declare function syncEnvironmentRepository(sandbox: ExecutableSandbox, workdir: string, options: SyncEnvironmentRepositoryOptions): Promise; /** Refresh an existing GitLab review checkout without exposing its credential to the agent. */ export declare function refreshMergeRequestCheckout(sandbox: ExecutableSandbox, workdir: string, input: { branch: string; mergeRequestNumber: number; expectedHeadSha: string; access: RepositoryAccess; }): Promise<{ headSha: string; changed: boolean; }>; export { isValidGitRef }; /** 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 repository-local commit identity through direct Git argv calls. */ export declare function configureGitIdentity(sandbox: ExecutableSandbox, workdir: string, identity: GitIdentity): Promise; /** Whether the checkout has a local `refs/heads/`. */ export declare function hasLocalBranch(sandbox: ExecutableSandbox, workdir: string, branch: string): Promise; /** * Make sure the checkout has a local ``. Where it was never created, * create it at `HEAD` and switch to it, so the agent's next commits land on * the branch every push sends; the index and working tree carry over. */ export declare function ensureLocalBranch(sandbox: ExecutableSandbox, workdir: string, branch: string): Promise; /** Whether `workdir` is a checkout of the repository at `cloneUrl`. */ export declare function isCheckoutOf(sandbox: ExecutableSandbox, workdir: string, cloneUrl: string): Promise; /** * Push a branch back to GitHub with credentials scoped to the Git process. * The token never enters argv, a repository URL, or persistent configuration. */ export declare function pushBranch(sandbox: ExecutableSandbox, workdir: string, branch: string, token: string, repoFullName: string): Promise; /** * Push the active session branch through the provider-neutral repository * access contract without persisting or exposing provider credentials. */ export declare function pushRepositoryBranch(sandbox: ExecutableSandbox, workdir: string, branch: string, access: RepositoryAccess, 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 (passed as one argv value) * @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 passed as structured argv, 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; //# sourceMappingURL=sandbox.d.ts.map