@mastra/factory 0.24.0 → 0.25.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -9
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +18 -4
- package/dist/auth.js.map +1 -1
- package/dist/boards/review.d.ts.map +1 -1
- package/dist/boards/review.js +3 -2
- package/dist/boards/review.js.map +1 -1
- package/dist/factory.d.ts +3 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +90 -97
- package/dist/factory.js.map +1 -1
- package/dist/integrations/base.d.ts +14 -1
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/github/integration.d.ts +3 -0
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +4 -0
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +4 -0
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +2 -0
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts +3 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +5 -0
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/{session → integrations/slack}/channel-reply-processor.d.ts +4 -3
- package/dist/integrations/slack/channel-reply-processor.d.ts.map +1 -0
- package/dist/integrations/slack/channel-reply-processor.js +61 -0
- package/dist/integrations/slack/channel-reply-processor.js.map +1 -0
- package/dist/integrations/slack/channel-session-link-processor.d.ts.map +1 -0
- package/dist/{session → integrations/slack}/channel-session-link-processor.js +13 -4
- package/dist/integrations/slack/channel-session-link-processor.js.map +1 -0
- package/dist/integrations/slack/integration.d.ts +25 -1
- package/dist/integrations/slack/integration.d.ts.map +1 -1
- package/dist/integrations/slack/integration.js +55 -5
- package/dist/integrations/slack/integration.js.map +1 -1
- package/dist/integrations/slack/reply-reminder-processor.d.ts +23 -0
- package/dist/integrations/slack/reply-reminder-processor.d.ts.map +1 -0
- package/dist/integrations/slack/reply-reminder-processor.js +58 -0
- package/dist/integrations/slack/reply-reminder-processor.js.map +1 -0
- package/dist/integrations/slack/reply-tool.d.ts +31 -0
- package/dist/integrations/slack/reply-tool.d.ts.map +1 -0
- package/dist/integrations/slack/reply-tool.js +203 -0
- package/dist/integrations/slack/reply-tool.js.map +1 -0
- package/dist/integrations/slack/share-file-tool.d.ts +45 -0
- package/dist/integrations/slack/share-file-tool.d.ts.map +1 -0
- package/dist/integrations/slack/share-file-tool.js +106 -0
- package/dist/integrations/slack/share-file-tool.js.map +1 -0
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +54 -6
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/mcp/events.d.ts +73 -0
- package/dist/mcp/events.d.ts.map +1 -0
- package/dist/mcp/events.js +158 -0
- package/dist/mcp/events.js.map +1 -0
- package/dist/mcp/routes.d.ts +0 -1
- package/dist/mcp/routes.d.ts.map +1 -1
- package/dist/mcp/routes.js +30 -3
- package/dist/mcp/routes.js.map +1 -1
- package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
- package/dist/platform-auth-host.d.ts.map +1 -1
- package/dist/platform-auth-host.js +1 -1
- package/dist/platform-auth-host.js.map +1 -1
- package/dist/routes/config.d.ts +2 -0
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +52 -11
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/contracts.d.ts +2 -0
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/contracts.js +1 -1
- package/dist/routes/contracts.js.map +1 -1
- package/dist/routes/session-access.d.ts +7 -2
- package/dist/routes/session-access.d.ts.map +1 -1
- package/dist/routes/session-access.js +41 -4
- package/dist/routes/session-access.js.map +1 -1
- package/dist/routes/skills.d.ts.map +1 -1
- package/dist/routes/skills.js +4 -3
- package/dist/routes/skills.js.map +1 -1
- package/dist/routes/source-control-sessions.d.ts +2 -0
- package/dist/routes/source-control-sessions.d.ts.map +1 -1
- package/dist/routes/source-control-sessions.js +161 -24
- package/dist/routes/source-control-sessions.js.map +1 -1
- package/dist/routes/surface.d.ts +2 -0
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +10 -4
- package/dist/routes/surface.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +11 -3
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts +2 -2
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +3 -3
- package/dist/rules/processor.js.map +1 -1
- package/dist/sandbox/session-retirement.d.ts +11 -0
- package/dist/sandbox/session-retirement.d.ts.map +1 -1
- package/dist/sandbox/session-retirement.js +38 -1
- package/dist/sandbox/session-retirement.js.map +1 -1
- package/dist/sandbox/session-sandbox.d.ts +2 -0
- package/dist/sandbox/session-sandbox.d.ts.map +1 -1
- package/dist/sandbox/session-sandbox.js +5 -1
- package/dist/sandbox/session-sandbox.js.map +1 -1
- package/dist/session/credential-policy.d.ts +4 -2
- package/dist/session/credential-policy.d.ts.map +1 -1
- package/dist/session/credential-policy.js +11 -1
- package/dist/session/credential-policy.js.map +1 -1
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +14 -1
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/memory-settings-hydration.d.ts +0 -8
- package/dist/session/memory-settings-hydration.d.ts.map +1 -1
- package/dist/session/memory-settings-hydration.js +1 -19
- package/dist/session/memory-settings-hydration.js.map +1 -1
- package/dist/session/run-audit.d.ts +1 -0
- package/dist/session/run-audit.d.ts.map +1 -1
- package/dist/session/run-audit.js +7 -2
- package/dist/session/run-audit.js.map +1 -1
- package/dist/session/session-threads.d.ts +5 -0
- package/dist/session/session-threads.d.ts.map +1 -1
- package/dist/session/session-threads.js +8 -1
- package/dist/session/session-threads.js.map +1 -1
- package/dist/session/thinking-default.d.ts +6 -13
- package/dist/session/thinking-default.d.ts.map +1 -1
- package/dist/session/thinking-default.js +18 -22
- package/dist/session/thinking-default.js.map +1 -1
- package/dist/session-owner-signal.d.ts +2 -0
- package/dist/session-owner-signal.d.ts.map +1 -0
- package/dist/session-owner-signal.js +6 -0
- package/dist/session-owner-signal.js.map +1 -0
- package/dist/skills/service.d.ts +6 -7
- package/dist/skills/service.d.ts.map +1 -1
- package/dist/skills/service.js +9 -19
- package/dist/skills/service.js.map +1 -1
- package/dist/storage/domains/audit/actions.d.ts +1 -0
- package/dist/storage/domains/audit/actions.d.ts.map +1 -1
- package/dist/storage/domains/audit/actions.js +2 -1
- package/dist/storage/domains/audit/actions.js.map +1 -1
- package/dist/storage/domains/audit/base.d.ts +18 -2
- package/dist/storage/domains/audit/base.d.ts.map +1 -1
- package/dist/storage/domains/audit/base.js +22 -4
- package/dist/storage/domains/audit/base.js.map +1 -1
- package/dist/storage/domains/model-defaults/base.d.ts +8 -0
- package/dist/storage/domains/model-defaults/base.d.ts.map +1 -1
- package/dist/storage/domains/model-defaults/base.js +18 -0
- package/dist/storage/domains/model-defaults/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +26 -1
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +69 -5
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +19 -1
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +30 -2
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/tools/permissions.d.ts.map +1 -1
- package/dist/tools/permissions.js +2 -1
- package/dist/tools/permissions.js.map +1 -1
- package/dist/tools/sandbox-tools.d.ts +12 -0
- package/dist/tools/sandbox-tools.d.ts.map +1 -0
- package/dist/tools/sandbox-tools.js +40 -0
- package/dist/tools/sandbox-tools.js.map +1 -0
- package/dist/tools/scope.d.ts.map +1 -1
- package/dist/tools/scope.js +2 -5
- package/dist/tools/scope.js.map +1 -1
- package/dist/tools/session-tools.d.ts.map +1 -1
- package/dist/tools/session-tools.js +6 -2
- package/dist/tools/session-tools.js.map +1 -1
- package/dist/work-item-branch.d.ts +11 -3
- package/dist/work-item-branch.d.ts.map +1 -1
- package/dist/work-item-branch.js +34 -9
- package/dist/work-item-branch.js.map +1 -1
- package/dist/workspace.d.ts +2 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +10 -2
- package/dist/workspace.js.map +1 -1
- package/factory-skills/configure-factory-rules/SKILL.md +2 -2
- package/package.json +15 -14
- package/dist/session/channel-reply-processor.d.ts.map +0 -1
- package/dist/session/channel-reply-processor.js +0 -46
- package/dist/session/channel-reply-processor.js.map +0 -1
- package/dist/session/channel-session-link-processor.d.ts.map +0 -1
- package/dist/session/channel-session-link-processor.js.map +0 -1
- /package/dist/{session → integrations/slack}/channel-session-link-processor.d.ts +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.js","names":[],"sources":["../../../src/integrations/github/sandbox.ts"],"sourcesContent":["/**\n * Repo materialization for GitHub-backed repositories.\n *\n * A GitHub repo is never cloned onto the server host. The repo is cloned\n * *inside* the session's sandbox, so the agent's file tools and command tools\n * operate entirely against the remote checkout.\n *\n * - `materializeRepo(row, token)` clones the repo inside the sandbox when no\n * checkout exists yet (a base-image boot, a wiped disk), supplying the\n * short-lived token only in that Git process's environment. A checkout that\n * is already there, from a repo template image or an earlier start, is left\n * exactly as it is.\n *\n * This module owns everything git/GitHub: clone, commit/push, setup/teardown commands,\n * and `gh pr create`. Workdir layout lives in `../sandbox/workdir`.\n */\n\nimport type { RepositoryAccess } from '../../capabilities/version-control.js';\nimport { isValidGitRef } from '../../sandbox/git-ref.js';\nimport type { ExecutableSandbox, SandboxCommandResult } from '../../sandbox/materialization.js';\nimport type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';\nimport { timedPhase } from '../../timing.js';\n\ntype MaterializationStore = Pick<SourceControlStorageHandle['sessions'], 'markMaterialized'>;\n\ninterface RepoMaterializationBinding {\n id: string;\n sandboxWorkdir: string;\n materializedAt: Date | null;\n}\n\n/**\n * Single-quote a string for safe POSIX shell interpolation. Wraps the value in\n * single quotes and escapes any embedded single quote using the canonical\n * close-quote / escaped-quote / reopen-quote sequence (`'\\''`). This is the\n * standard POSIX-safe construction and prevents the quoted string from being\n * terminated early.\n */\nexport function shellQuote(value: string): string {\n // Replace each ' with the four-character sequence: ' \\ ' '\n return `'` + value.split(`'`).join(`'\\\\''`) + `'`;\n}\n\n/**\n * Default hang guard for sandbox shell commands. Generous by design — large\n * clones and dependency installs legitimately take minutes; the guard exists\n * so a wedged sandbox surfaces a failure instead of hanging the request that\n * triggered materialization forever.\n */\nexport const DEFAULT_COMMAND_TIMEOUT_MS = 15 * 60_000;\n/** Branch checkout only fetches one ref — a much tighter budget applies. */\nexport const CHECKOUT_COMMAND_TIMEOUT_MS = 5 * 60_000;\n\ninterface ShOptions {\n /** Override the hang-guard budget for this command. */\n timeoutMs?: number;\n /** Human-readable phase name included in the timeout error. */\n phase?: string;\n}\n\ninterface CommandOptions extends ShOptions {\n cwd?: string;\n env?: Record<string, string | undefined>;\n}\n\n/**\n * A thrown transport-level failure that is worth retrying: remote sandbox\n\n * providers (e.g. the platform workspace proxy) surface transient 5xx errors\n * as exceptions carrying an HTTP `status` — typically while a freshly\n * provisioned VM is still coming up. Command failures are NOT exceptions\n * (they resolve with a non-zero exit code), so retrying here never re-runs a\n * command that the sandbox already executed and rejected.\n */\nfunction isTransientTransportError(error: unknown): boolean {\n const status = (error as { status?: unknown })?.status;\n return typeof status === 'number' && status >= 500;\n}\n\nconst SH_RETRIES = 2;\nconst SH_RETRY_DELAY_MS = 2000;\n\n/**\n * Run a shell script in the sandbox via `sh -c`. This is reserved for the\n * explicitly configured repository lifecycle hooks; programmatic commands use\n * {@link execute} with an argv array.\n */\nexport async function sh(\n sandbox: ExecutableSandbox,\n script: string,\n options: ShOptions = {},\n): Promise<SandboxCommandResult> {\n return execute(sandbox, 'sh', ['-c', script], options);\n}\n\n/**\n * Execute one program with an argv array, bounded by a hang guard. Transient\n * sandbox transport failures are retried without ever interpolating arguments\n * into a shell command.\n */\nasync function execute(\n sandbox: ExecutableSandbox,\n command: string,\n args: string[],\n options: CommandOptions = {},\n): Promise<SandboxCommandResult> {\n const deadlineMs = Date.now() + (options.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS);\n for (let attempt = 0; ; attempt++) {\n const started = performance.now();\n try {\n const result = await executeOnce(sandbox, command, args, {\n ...options,\n timeoutMs: Math.max(deadlineMs - Date.now(), 1),\n });\n if (options.phase) {\n process.stderr.write(\n `[factory:timing] ${options.phase} attempt=${attempt + 1} exit=${result.exitCode} ${Math.round(performance.now() - started)}ms\\n`,\n );\n }\n return result;\n } catch (error) {\n if (options.phase) {\n process.stderr.write(\n `[factory:timing] ${options.phase} attempt=${attempt + 1} threw after ${Math.round(performance.now() - started)}ms: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n }\n if (attempt >= SH_RETRIES || !isTransientTransportError(error)) throw error;\n const delayMs = SH_RETRY_DELAY_MS * (attempt + 1);\n if (deadlineMs - Date.now() <= delayMs) throw error;\n await new Promise(resolve => setTimeout(resolve, delayMs));\n }\n }\n}\n\n/** Single structured command execution attempt, bounded by the hang guard. */\nasync function executeOnce(\n sandbox: ExecutableSandbox,\n command: string,\n args: string[],\n options: CommandOptions,\n): Promise<SandboxCommandResult> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const hangGuard = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n const phase = options.phase ? ` during ${options.phase}` : '';\n reject(new Error(`Sandbox command timed out after ${Math.round(timeoutMs / 1000)}s${phase}.`));\n }, timeoutMs);\n timer.unref?.();\n });\n try {\n return await Promise.race([\n sandbox.executeCommand(command, args, {\n timeout: timeoutMs,\n ...(options.cwd !== undefined ? { cwd: options.cwd } : {}),\n ...(options.env !== undefined ? { env: options.env } : {}),\n }),\n hangGuard,\n ]);\n } finally {\n clearTimeout(timer);\n }\n}\n\nconst GIT_TRANSFER_RETRIES = 2;\nconst GIT_TRANSFER_RETRY_DELAY_MS = 2000;\n\n/**\n * True when a git transfer died mid-flight rather than being refused.\n *\n * `sh` already retries transport errors the sandbox provider *throws*, but a\n * git command that reaches the network and then loses it exits non-zero\n * instead — so a single HTTP/2 framing glitch or dropped connection to\n * github.com would otherwise permanently fail opening a workspace. These\n * patterns all mean \"the bytes stopped arriving\", which says nothing about\n * whether the operation would succeed if attempted again.\n *\n * Deliberately narrow: a refusal (bad credentials, missing repo, blocked\n * egress) is terminal and must surface immediately rather than be retried into\n * a slow failure.\n */\nfunction isTransientGitFailure(result: SandboxCommandResult): boolean {\n const output = `${result.stderr || ''}\\n${result.stdout || ''}`;\n return /HTTP2 framing layer|RPC failed; curl|RPC failed; HTTP 5\\d\\d|the remote end hung up unexpectedly|early EOF|unexpected disconnect|connection reset by peer|Recv failure|Send failure|GnuTLS recv error|TLS connection was non-properly terminated|502 Bad Gateway|503 Service Unavailable/i.test(\n output,\n );\n}\n\n/**\n * Run a git command that only *reads* from the remote, retrying it when the\n * transfer dies mid-flight. Restricted to read-only transfers on purpose:\n * re-running a clone or a fetch is free, whereas re-running a push could\n * duplicate work already accepted by the remote before the connection dropped.\n *\n * `beforeRetry` lets a call site clear whatever the aborted attempt left\n * behind — a half-written clone directory blocks the next `git clone` outright.\n */\nasync function gitTransfer(\n sandbox: ExecutableSandbox,\n args: string[],\n options: CommandOptions & { beforeRetry?: (attempt: number) => Promise<void> } = {},\n): Promise<SandboxCommandResult> {\n const { beforeRetry, ...commandOptions } = options;\n const deadlineMs = Date.now() + (commandOptions.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS);\n for (let attempt = 0; ; attempt++) {\n const result = await execute(sandbox, 'git', args, {\n ...commandOptions,\n timeoutMs: Math.max(deadlineMs - Date.now(), 1),\n });\n if (result.exitCode === 0 || attempt >= GIT_TRANSFER_RETRIES || !isTransientGitFailure(result)) return result;\n process.stderr.write(\n `[factory:timing] git ${commandOptions.phase ?? 'transfer'} retrying after attempt ${attempt + 1}\\n`,\n );\n const delayMs = GIT_TRANSFER_RETRY_DELAY_MS * (attempt + 1);\n if (deadlineMs - Date.now() <= delayMs) return result;\n await new Promise(resolve => setTimeout(resolve, delayMs));\n await beforeRetry?.(attempt + 1);\n }\n}\n\n/** Error raised when the sandbox cannot materialize the repo (actionable). */\nexport class MaterializeError extends Error {\n constructor(\n message: string,\n readonly code:\n | 'git-missing'\n | 'egress-blocked'\n | 'clone-failed'\n | 'pull-failed'\n | 'push-failed'\n | 'commit-failed'\n | 'gh-missing'\n | 'pr-failed',\n ) {\n super(message);\n this.name = 'MaterializeError';\n }\n}\n\nfunction cleanUrl(repoFullName: string): string {\n return `https://github.com/${repoFullName}.git`;\n}\n\nfunction credentialScope(\n cloneUrl: string,\n code: 'clone-failed' | 'pull-failed' | 'push-failed' = 'clone-failed',\n): string {\n let url: URL;\n try {\n url = new URL(cloneUrl);\n } catch {\n throw new MaterializeError('Refusing to configure credentials for an invalid repository clone URL.', code);\n }\n if (url.protocol !== 'https:' || !url.hostname || url.username || url.password || url.search || url.hash) {\n throw new MaterializeError('Refusing to configure credentials for an invalid repository clone URL.', code);\n }\n const repositoryPath = url.pathname.replace(/\\/+$/, '');\n if (!repositoryPath || repositoryPath === '/') {\n throw new MaterializeError('Refusing to configure credentials for an invalid repository clone URL.', code);\n }\n return url.origin + repositoryPath;\n}\n\n/**\n * Provide HTTP Basic credentials to one Git process without putting a secret\n * in argv, a remote URL, or persistent git config. Git reads the URL-scoped\n * extra header from its process environment and discards it on exit.\n */\nfunction gitAuthenticationEnvironment(\n cloneUrl: string,\n token: string,\n username: string,\n code: 'clone-failed' | 'pull-failed' | 'push-failed' = 'clone-failed',\n): Record<string, string> {\n const scope = credentialScope(cloneUrl, code);\n if (!token || !username) {\n throw new MaterializeError('Repository access did not include usable credentials.', code);\n }\n const authorization = Buffer.from(`${username}:${token}`, 'utf8').toString('base64');\n // Git applies `http.<url>.*` only when the config URL matches the remote at\n // a path boundary, and `origin` may spell the repository either way: Factory\n // clones with `.git`, a sandbox template clones from the normalized URL\n // without it. Scope the header to both so neither checkout prompts.\n const alternate = scope.endsWith('.git') ? scope.slice(0, -4) : `${scope}.git`;\n return {\n GIT_CONFIG_COUNT: '2',\n GIT_CONFIG_KEY_0: `http.${scope}.extraHeader`,\n GIT_CONFIG_VALUE_0: `Authorization: Basic ${authorization}`,\n GIT_CONFIG_KEY_1: `http.${alternate}.extraHeader`,\n GIT_CONFIG_VALUE_1: `Authorization: Basic ${authorization}`,\n GIT_TERMINAL_PROMPT: '0',\n };\n}\n\nfunction normalizedRemoteUrl(value: string): string | null {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return null;\n }\n if (url.protocol !== 'https:' || !url.hostname || url.search || url.hash) return null;\n const pathname = url.pathname.replace(/\\/+$/, '').replace(/\\.git$/i, '');\n const repositoryPath = url.hostname.toLowerCase() === 'github.com' ? pathname.toLowerCase() : pathname;\n return 'https://' + url.host.toLowerCase() + repositoryPath;\n}\n\n/** Repo metadata needed to materialize, read from the org-owned project row. */\nexport interface RepoMaterializeInfo {\n repoFullName: string;\n defaultBranch: string;\n /** Provider-supplied, credential-free HTTPS clone URL. */\n cloneUrl?: string;\n /** Username paired with the bearer token for git-over-HTTPS. */\n authUsername?: string;\n}\n\n/** Options for {@link materializeRepo}. */\nexport interface MaterializeRepoOptions {\n /** The per-(project,user) sandbox binding whose workdir this materializes into. */\n row: RepoMaterializationBinding;\n /** Repo metadata from the org-owned project row. */\n repoInfo: RepoMaterializeInfo;\n /** The live sandbox to run git inside. */\n sandbox: ExecutableSandbox;\n /** A freshly minted, short-lived installation access token. */\n token: string;\n storage: MaterializationStore;\n}\n\nasync function clearWorkdir(sandbox: ExecutableSandbox, workdir: string): Promise<void> {\n const mkdir = await execute(sandbox, 'mkdir', ['-p', workdir]);\n if (mkdir.exitCode !== 0) throw classifyGitFailure(mkdir, 'clone-failed');\n const clear = await execute(sandbox, 'find', [\n workdir,\n '-mindepth',\n '1',\n '-maxdepth',\n '1',\n '-exec',\n 'rm',\n '-rf',\n '--',\n '{}',\n '+',\n ]);\n if (clear.exitCode !== 0) throw classifyGitFailure(clear, 'clone-failed');\n}\n/**\n * Materialize the repo inside the user's sandbox: clone when no checkout of\n * this repo exists, otherwise nothing. Credentials are process-scoped and the\n * stored remote remains credential-free. Sets `materialized_at` on the\n * per-user sandbox binding row.\n */\nexport async function materializeRepo(options: MaterializeRepoOptions): Promise<void> {\n return timedPhase('workspace.materialize', () => materializeRepoImpl(options));\n}\n\nasync function materializeRepoImpl(options: MaterializeRepoOptions): Promise<void> {\n const { row: sandboxRow, repoInfo, sandbox, token, storage } = options;\n const workdir = sandboxRow.sandboxWorkdir;\n const repo = repoInfo.repoFullName;\n\n // 0. Defense in depth: never build a git command from values that aren't\n // strictly shaped, even if a malformed row reached the DB. Inputs are also\n // validated at the route boundary before storage.\n if (!/^[\\w.-]+(?:\\/[\\w.-]+)+$/.test(repo)) {\n throw new MaterializeError(`Refusing to materialize: invalid repo full name '${repo}'.`, 'clone-failed');\n }\n if (!/^[A-Za-z0-9_./-]+$/.test(repoInfo.defaultBranch)) {\n throw new MaterializeError(\n `Refusing to materialize: invalid default branch '${repoInfo.defaultBranch}'.`,\n 'clone-failed',\n );\n }\n\n // 1. Preflight: git must be installed in the sandbox template.\n const gitVersion = await execute(sandbox, 'git', ['--version']);\n if (gitVersion.exitCode !== 0) {\n throw new MaterializeError(\n 'git is not installed in the sandbox. The sandbox template must include git.',\n 'git-missing',\n );\n }\n\n // The DB's `materializedAt` can drift from disk in both directions: a fresh\n // binding row over an already-populated workdir (a repo template image,\n // local dev DB resets, repaired rows) must not fail `git clone` on the\n // non-empty directory, and a stale `materializedAt` over an empty sandbox\n // (an expired/recreated VM whose disk was wiped) must re-clone instead of\n // running `git -C <workdir>` against a directory that no longer exists.\n // Disk is the source of truth: detect the checkout instead of trusting the\n // row. An existing checkout is left as it is, whatever it is on: a template\n // image sits detached at its pinned commit, a resumed session on its\n // branch. Syncing with the remote is the session's business; the branch\n // checkout that follows fetches the base branch it needs.\n const cleanCloneUrl = repoInfo.cloneUrl ?? cleanUrl(repo);\n const existing = await existingCheckoutRemote(sandbox, workdir, cleanCloneUrl);\n if (existing !== null) {\n // A token an earlier start failed to scrub must not outlive it; the\n // remote already carries the plain URL otherwise, so this costs nothing\n // on the common path.\n if (/\\/\\/[^/]*@/.test(existing)) await scrubRemote(sandbox, workdir, repo, cleanCloneUrl);\n } else {\n // 2. First open: shallow-clone the default branch into the workdir, the\n // same clone a repo template bakes into its image. The workdir holds no\n // usable checkout of this repo, but it may not be empty: a checkpoint\n // seed or a clone that died partway (a crashed or OOM-killed server)\n // leaves a partial tree behind, and `git clone` refuses a non-empty\n // destination with a non-retryable fatal. Nothing here is recoverable,\n // the probe above already ruled out a checkout of this repo, so\n // clear its contents before cloning, exactly as the retry path does. Keep\n // the workdir itself because LocalSandbox runs commands with this\n // directory as the child process cwd.\n const authEnv = gitAuthenticationEnvironment(\n cleanCloneUrl,\n token,\n repoInfo.authUsername ?? 'x-access-token',\n 'clone-failed',\n );\n await clearWorkdir(sandbox, workdir);\n const clone = await gitTransfer(\n sandbox,\n ['clone', '--depth=1', '--single-branch', '--branch', repoInfo.defaultBranch, '--', cleanCloneUrl, workdir],\n {\n env: authEnv,\n phase: 'repository clone',\n beforeRetry: async () => {\n // A clone that died partway leaves the destination non-empty, which\n // git refuses to clone into. Clear its contents so the retry starts\n // clean without removing LocalSandbox's process cwd.\n await clearWorkdir(sandbox, workdir);\n },\n },\n );\n if (clone.exitCode !== 0) throw classifyGitFailure(clone, 'clone-failed');\n }\n\n // 4. Mark materialized.\n await storage.markMaterialized({ id: sandboxRow.id });\n}\n\nexport interface SessionBranchOptions {\n branch: string;\n baseBranch: string;\n token: string;\n repoFullName: string;\n cloneUrl?: string;\n authUsername?: string;\n /** A pull-request card's session starts on the PR head instead of the base tip. */\n pullRequestNumber?: number;\n /** A GitLab merge-request card's session starts on the MR head instead of the base tip. */\n mergeRequestNumber?: number;\n}\n\n/**\n * Past file contents of a blob-less history load on demand; git asks gh, which\n * answers from the session's `GH_TOKEN`, so no credential is ever written.\n */\nconst GH_CREDENTIAL_HELPER = '!gh auth git-credential';\n\nasync function fetchStartPoint(\n sandbox: ExecutableSandbox,\n workdir: string,\n {\n baseBranch,\n pullRequestNumber,\n mergeRequestNumber,\n }: Pick<SessionBranchOptions, 'baseBranch' | 'pullRequestNumber' | 'mergeRequestNumber'>,\n shallowClone: boolean,\n env: Record<string, string>,\n): Promise<SandboxCommandResult> {\n const changeRequestRef =\n pullRequestNumber !== undefined\n ? `refs/pull/${pullRequestNumber}/head`\n : mergeRequestNumber !== undefined\n ? `refs/merge-requests/${mergeRequestNumber}/head`\n : undefined;\n const baseArgs = [\n '-C',\n workdir,\n 'fetch',\n ...(changeRequestRef && shallowClone ? ['--unshallow'] : []),\n ...(pullRequestNumber !== undefined ? ['--filter=blob:none'] : []),\n 'origin',\n baseBranch,\n ];\n const base = await gitTransfer(sandbox, baseArgs, {\n env,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'branch checkout fetch',\n });\n if (base.exitCode !== 0 || !changeRequestRef) return base;\n return gitTransfer(\n sandbox,\n [\n '-C',\n workdir,\n 'fetch',\n ...(pullRequestNumber !== undefined ? ['--filter=blob:none'] : []),\n 'origin',\n changeRequestRef,\n ],\n {\n env,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'change request head fetch',\n },\n );\n}\n\n/** Check out a session's branch inside its isolated repository clone. */\nexport async function checkoutSessionBranch(\n sandbox: ExecutableSandbox,\n workdir: string,\n options: SessionBranchOptions,\n): Promise<void> {\n return timedPhase('workspace.checkout', () => checkoutSessionBranchImpl(sandbox, workdir, options));\n}\n\nexport interface SyncEnvironmentRepositoryOptions {\n /** The session branch to resume when the remote already carries it. */\n branch: string;\n /** The repository's default branch; new session branches start at its remote tip. */\n defaultBranch: string;\n token: string;\n repoFullName: string;\n cloneUrl?: string;\n authUsername?: string;\n}\n\nexport interface EnvironmentSyncResult {\n outcome: 'resumed' | 'created' | 'kept';\n /** The branch the checkout ends on; `null` when it stays detached. */\n branch: string | null;\n}\n\n/**\n * Bring a secondary environment repository onto the session branch at\n * session start. The primary repository keeps {@link checkoutSessionBranch};\n * every other repository follows a conservative rule: resume the session\n * branch when the remote or the local clone has it, otherwise create it from\n * the default branch tip (a detached HEAD, the template image pinned at its\n * build commit, is moved there first). A checkout that sits on any other\n * branch is left alone: it may carry local-only commits.\n */\nexport async function syncEnvironmentRepository(\n sandbox: ExecutableSandbox,\n workdir: string,\n options: SyncEnvironmentRepositoryOptions,\n): Promise<EnvironmentSyncResult> {\n const { branch, defaultBranch, token, repoFullName, cloneUrl, authUsername } = options;\n if (!isValidGitRef(branch) || !isValidGitRef(defaultBranch)) {\n throw new MaterializeError('Refusing to sync a repository from an invalid branch name.', 'pull-failed');\n }\n const cleanCloneUrl = cloneUrl ?? cleanUrl(repoFullName);\n const authEnv = gitAuthenticationEnvironment(cleanCloneUrl, token, authUsername ?? 'x-access-token', 'pull-failed');\n\n const current = await execute(sandbox, 'git', ['-C', workdir, 'branch', '--show-current']);\n if (current.exitCode !== 0) throw classifyGitFailure(current, 'pull-failed');\n const currentBranch = current.stdout.trim();\n const kept = { outcome: 'kept' as const, branch: currentBranch || null };\n if (currentBranch === branch) return kept;\n\n // A local session branch may hold commits the remote never saw; switch\n // to it instead of resetting it onto the remote tip.\n const local = await execute(sandbox, 'git', [\n '-C',\n workdir,\n 'rev-parse',\n '--verify',\n '--quiet',\n `refs/heads/${branch}`,\n ]);\n if (local.exitCode === 0) {\n const checkout = await execute(sandbox, 'git', ['-C', workdir, 'checkout', branch], {\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'environment branch checkout',\n });\n if (checkout.exitCode === 0) return { outcome: 'resumed', branch };\n if (isBlockedByLocalWork(checkout)) return kept;\n throw classifyGitFailure(checkout, 'pull-failed');\n }\n\n const remoteHeads = await gitTransfer(\n sandbox,\n ['-C', workdir, 'ls-remote', '--heads', 'origin', `refs/heads/${branch}`],\n { env: authEnv, timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS, phase: 'environment branch probe' },\n );\n if (remoteHeads.exitCode !== 0) throw classifyGitFailure(remoteHeads, 'pull-failed');\n if (remoteHeads.stdout.trim()) {\n const fetch = await gitTransfer(sandbox, ['-C', workdir, 'fetch', 'origin', branch], {\n env: authEnv,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'environment branch fetch',\n });\n if (fetch.exitCode !== 0) throw classifyGitFailure(fetch, 'pull-failed');\n const checkout = await execute(sandbox, 'git', ['-C', workdir, 'checkout', '-B', branch, 'FETCH_HEAD'], {\n env: authEnv,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'environment branch checkout',\n });\n if (checkout.exitCode === 0) return { outcome: 'resumed', branch };\n if (isBlockedByLocalWork(checkout)) return kept;\n throw classifyGitFailure(checkout, 'pull-failed');\n }\n\n // A checkout on any other branch stays where it is: it may hold work the\n // remote never saw. The default branch and a detached HEAD (the template\n // image pinned at its build commit) become the session branch.\n if (currentBranch && currentBranch !== defaultBranch) return kept;\n if (currentBranch) {\n const status = await execute(sandbox, 'git', ['-C', workdir, 'status', '--porcelain']);\n if (status.exitCode !== 0) throw classifyGitFailure(status, 'pull-failed');\n if (status.stdout.trim()) return kept;\n }\n const fetch = await gitTransfer(sandbox, ['-C', workdir, 'fetch', 'origin', defaultBranch], {\n env: authEnv,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'environment default fetch',\n });\n if (fetch.exitCode !== 0) throw classifyGitFailure(fetch, 'pull-failed');\n if (currentBranch) {\n // Do not discard local-only commits on the default branch. A shallow\n // checkout whose ancestry cannot be established is also left untouched.\n const ancestor = await execute(sandbox, 'git', [\n '-C',\n workdir,\n 'merge-base',\n '--is-ancestor',\n 'HEAD',\n 'FETCH_HEAD',\n ]);\n if (ancestor.exitCode === 1) return kept;\n if (ancestor.exitCode !== 0) throw classifyGitFailure(ancestor, 'pull-failed');\n } else {\n const checkout = await execute(sandbox, 'git', ['-C', workdir, 'checkout', '-B', defaultBranch, 'FETCH_HEAD'], {\n env: authEnv,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'environment default checkout',\n });\n if (checkout.exitCode !== 0) {\n if (isBlockedByLocalWork(checkout)) return kept;\n throw classifyGitFailure(checkout, 'pull-failed');\n }\n }\n const created = await execute(sandbox, 'git', ['-C', workdir, 'checkout', '-b', branch, 'FETCH_HEAD'], {\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'environment branch create',\n });\n if (created.exitCode === 0) return { outcome: 'created', branch };\n throw classifyGitFailure(created, 'pull-failed');\n}\n\n/** Refresh an existing GitLab review checkout without exposing its credential to the agent. */\nexport async function refreshMergeRequestCheckout(\n sandbox: ExecutableSandbox,\n workdir: string,\n input: { branch: string; mergeRequestNumber: number; expectedHeadSha: string; access: RepositoryAccess },\n): Promise<{ headSha: string; changed: boolean }> {\n const { branch, mergeRequestNumber, expectedHeadSha, access } = input;\n if (\n !isValidGitRef(branch) ||\n !Number.isSafeInteger(mergeRequestNumber) ||\n mergeRequestNumber <= 0 ||\n !/^[0-9a-f]{40}$/i.test(expectedHeadSha)\n ) {\n throw new Error('Refusing to refresh a GitLab review with invalid session or head metadata.');\n }\n const token = access.authorization?.token;\n if (!token) throw new Error('GitLab repository access did not include a bearer token.');\n const env = gitAuthenticationEnvironment(\n access.cloneUrl,\n token,\n access.authorization?.username ?? 'oauth2',\n 'pull-failed',\n );\n const current = await execute(sandbox, 'git', ['-C', workdir, 'branch', '--show-current']);\n if (current.exitCode !== 0 || current.stdout.trim() !== branch) {\n throw new Error('The active checkout is not on its bound review branch.');\n }\n const status = await execute(sandbox, 'git', ['-C', workdir, 'status', '--porcelain', '--untracked-files=all']);\n if (status.exitCode !== 0 || status.stdout.trim()) {\n throw new Error('The review checkout has local changes; refusing to replace them.');\n }\n const before = await execute(sandbox, 'git', ['-C', workdir, 'rev-parse', 'HEAD']);\n if (before.exitCode !== 0) throw new Error('Could not read the current review checkout head.');\n const fetch = await gitTransfer(\n sandbox,\n ['-C', workdir, 'fetch', 'origin', `refs/merge-requests/${mergeRequestNumber}/head`],\n {\n env,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'GitLab review head refresh',\n },\n );\n if (fetch.exitCode !== 0) throw classifyGitFailure(fetch, 'pull-failed');\n const fetched = await execute(sandbox, 'git', ['-C', workdir, 'rev-parse', 'FETCH_HEAD']);\n if (fetched.exitCode !== 0 || fetched.stdout.trim().toLowerCase() !== expectedHeadSha.toLowerCase()) {\n throw new Error('The fetched GitLab review head differs from the provider-reported head; retry after it settles.');\n }\n if (before.stdout.trim().toLowerCase() === expectedHeadSha.toLowerCase()) {\n return { headSha: expectedHeadSha, changed: false };\n }\n const updated = await execute(sandbox, 'git', ['-C', workdir, 'checkout', '-B', branch, 'FETCH_HEAD'], { env });\n if (updated.exitCode !== 0) throw classifyGitFailure(updated, 'pull-failed');\n return { headSha: expectedHeadSha, changed: true };\n}\n\nasync function checkoutSessionBranchImpl(\n sandbox: ExecutableSandbox,\n workdir: string,\n options: SessionBranchOptions,\n): Promise<void> {\n const { branch, baseBranch, token, repoFullName, pullRequestNumber, mergeRequestNumber, cloneUrl, authUsername } =\n options;\n if (\n !isValidGitRef(branch) ||\n !isValidGitRef(baseBranch) ||\n (pullRequestNumber !== undefined && mergeRequestNumber !== undefined) ||\n (mergeRequestNumber !== undefined && (!Number.isSafeInteger(mergeRequestNumber) || mergeRequestNumber <= 0))\n ) {\n throw new MaterializeError('Refusing to create a session from an invalid branch name.', 'clone-failed');\n }\n\n const changeRequestSession = pullRequestNumber !== undefined || mergeRequestNumber !== undefined;\n // GitHub delegates later authenticated operations to `gh`. Other providers\n // deliberately receive no persistent credential helper. Remove the legacy\n // helper when reopening an older checkout so a stale sandbox cannot retain\n // credentials injected by an earlier implementation.\n const cleanCloneUrl = cloneUrl ?? cleanUrl(repoFullName);\n const authEnv = gitAuthenticationEnvironment(cleanCloneUrl, token, authUsername ?? 'x-access-token', 'pull-failed');\n const credentialKey = authUsername ? 'credential.' + credentialScope(cleanCloneUrl) + '.helper' : 'credential.helper';\n if (authUsername) {\n await execute(sandbox, 'git', ['-C', workdir, 'config', '--unset-all', credentialKey]);\n } else {\n const configured = await execute(sandbox, 'git', ['-C', workdir, 'config', credentialKey, GH_CREDENTIAL_HELPER]);\n if (configured.exitCode !== 0) throw classifyGitFailure(configured, 'pull-failed');\n }\n\n const current = await execute(sandbox, 'git', ['-C', workdir, 'branch', '--show-current']);\n if (current.exitCode === 0 && current.stdout.trim() === branch) return;\n\n const local = await execute(sandbox, 'git', [\n '-C',\n workdir,\n 'show-ref',\n '--verify',\n '--quiet',\n `refs/heads/${branch}`,\n ]);\n if (local.exitCode === 0) {\n const checkout = await execute(sandbox, 'git', ['-C', workdir, 'checkout', branch], { env: authEnv });\n if (checkout.exitCode !== 0) {\n if (isBlockedByLocalWork(checkout)) return;\n throw classifyGitFailure(checkout, 'clone-failed');\n }\n return;\n }\n\n const shallowClone =\n changeRequestSession &&\n (await execute(sandbox, 'git', ['-C', workdir, 'rev-parse', '--is-shallow-repository'])).stdout.trim() === 'true';\n const fetch = await fetchStartPoint(sandbox, workdir, options, shallowClone, authEnv);\n if (fetch.exitCode !== 0) throw classifyGitFailure(fetch, 'pull-failed');\n\n const create = await execute(sandbox, 'git', ['-C', workdir, 'checkout', '-b', branch, 'FETCH_HEAD'], {\n env: authEnv,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'branch checkout',\n });\n if (create.exitCode === 0 || isBlockedByLocalWork(create)) return;\n if (!isBranchCollision(create)) throw classifyGitFailure(create, 'clone-failed');\n\n // The branch exists even though the show-ref probe missed it: either another\n // materialization created it concurrently (adopt it), or the sandbox carries\n // a broken loose ref (remove it and retry).\n const adopt = await execute(sandbox, 'git', ['-C', workdir, 'checkout', branch], { env: authEnv });\n if (adopt.exitCode === 0 || isBlockedByLocalWork(adopt)) return;\n\n let drop = await execute(sandbox, 'git', ['-C', workdir, 'update-ref', '--no-deref', '-d', `refs/heads/${branch}`]);\n if (drop.exitCode !== 0) {\n const gitDir = await execute(sandbox, 'git', ['-C', workdir, 'rev-parse', '--absolute-git-dir']);\n if (gitDir.exitCode !== 0 || !gitDir.stdout.trim()) throw classifyGitFailure(create, 'clone-failed');\n drop = await execute(sandbox, 'rm', ['-f', '--', `${gitDir.stdout.trim()}/refs/heads/${branch}`]);\n }\n if (drop.exitCode !== 0) throw classifyGitFailure(create, 'clone-failed');\n\n const retry = await execute(sandbox, 'git', ['-C', workdir, 'checkout', '-b', branch, 'FETCH_HEAD'], {\n env: authEnv,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'branch checkout retry',\n });\n if (retry.exitCode !== 0) {\n if (isBlockedByLocalWork(retry)) return;\n throw classifyGitFailure(retry, 'clone-failed');\n }\n}\n\n/**\n * True when `git checkout -b` failed only because the branch ref already\n * exists — the collision Factory hits when a pooled sandbox carries a ref the\n * show-ref probe could not see (broken loose ref) or a concurrent\n * materialization created the branch after the probe ran.\n */\nfunction isBranchCollision(result: SandboxCommandResult): boolean {\n return /a branch named .* already exists/i.test(`${result.stderr || ''}\\n${result.stdout || ''}`);\n}\n\n/**\n * True when a failed `git checkout` just means uncommitted or untracked files\n * in the working tree would be clobbered by the branch switch. Those files are\n * a session's work in progress — the switch must yield to them, never the\n * other way around.\n */\nfunction isBlockedByLocalWork(result: SandboxCommandResult): boolean {\n const output = `${result.stderr || ''}\\n${result.stdout || ''}`;\n return /Your local changes to the following files would be overwritten by checkout|untracked working tree files would be overwritten by checkout/i.test(\n output,\n );\n}\n\n/**\n * Return the origin URL when the workdir contains this repository, including a\n * legacy checkout whose remote still embeds credentials.\n */\nasync function existingCheckoutRemote(\n sandbox: ExecutableSandbox,\n workdir: string,\n cloneUrl: string,\n): Promise<string | null> {\n const result = await execute(sandbox, 'git', ['-C', workdir, 'remote', 'get-url', 'origin']);\n if (result.exitCode !== 0) return null;\n const url = result.stdout.trim();\n const actual = normalizedRemoteUrl(url);\n const expected = normalizedRemoteUrl(cloneUrl);\n return actual !== null && expected !== null && actual === expected ? url : null;\n}\n\n/** Remove credentials persisted by an older implementation from origin. */\nasync function scrubRemote(\n sandbox: ExecutableSandbox,\n workdir: string,\n repoFullName: string,\n cloneUrl: string = cleanUrl(repoFullName),\n): Promise<void> {\n let failure: string;\n try {\n const result = await execute(sandbox, 'git', ['-C', workdir, 'remote', 'set-url', 'origin', cloneUrl]);\n if (result.exitCode === 0) return;\n failure = result.stderr.trim() || result.stdout.trim();\n } catch (error) {\n failure = error instanceof Error ? error.message : String(error);\n }\n throw new MaterializeError(`Failed to scrub installation token from git remote: ${failure}`, 'pull-failed');\n}\n\n/**\n * Turn a failed git command into an actionable error, detecting the common\n * \"cannot reach github.com\" egress failure.\n */\nfunction classifyGitFailure(\n result: SandboxCommandResult,\n fallback: 'clone-failed' | 'pull-failed' | 'push-failed',\n): MaterializeError {\n const stderr = result.stderr || '';\n if (/could not resolve host|failed to connect|network is unreachable|Connection timed out/i.test(stderr)) {\n return new MaterializeError(\n 'The sandbox could not reach github.com. The sandbox network must allow outbound egress to github.com.',\n 'egress-blocked',\n );\n }\n const verb = fallback === 'clone-failed' ? 'clone' : fallback === 'pull-failed' ? 'pull' : 'push';\n return new MaterializeError(`git ${verb} failed: ${stderr}`, fallback);\n}\n\n// ---------------------------------------------------------------------------\n// Phase 1 — git identity + token-scoped push primitive\n//\n// These helpers let the sandbox author and push commits safely. The install\n// token is short-lived, minted per-operation server-side, and supplied only to\n// the Git process through its environment. It never enters argv or persistent\n// repository configuration.\n// ---------------------------------------------------------------------------\n\nexport { isValidGitRef };\n\n/** Identity used to author commits inside the sandbox. */\nexport interface GitIdentity {\n name?: string | null;\n email?: string | null;\n /** GitHub login, used to derive a stable noreply identity when name/email are absent. */\n login?: string | null;\n}\n\n/**\n * Resolve a concrete `{ name, email }` for git authorship from a possibly-sparse\n * identity. Falls back to a GitHub-style noreply identity so commits are never\n * authored with an empty or host-derived identity.\n */\nexport function resolveGitIdentity(identity: GitIdentity): { name: string; email: string } {\n const login = (identity.login || '').trim();\n const name = (identity.name || '').trim() || login || 'Mastra Code';\n const email =\n (identity.email || '').trim() ||\n (login ? `${login}@users.noreply.github.com` : 'mastra-code@users.noreply.github.com');\n return { name, email };\n}\n\n/** Configure repository-local commit identity through direct Git argv calls. */\nexport async function configureGitIdentity(\n sandbox: ExecutableSandbox,\n workdir: string,\n identity: GitIdentity,\n): Promise<void> {\n const { name, email } = resolveGitIdentity(identity);\n const setName = await execute(sandbox, 'git', ['-C', workdir, 'config', 'user.name', name]);\n if (setName.exitCode !== 0) {\n throw new MaterializeError(`Failed to set git user.name: ${setName.stderr.trim()}`, 'commit-failed');\n }\n const setEmail = await execute(sandbox, 'git', ['-C', workdir, 'config', 'user.email', email]);\n if (setEmail.exitCode !== 0) {\n throw new MaterializeError(`Failed to set git user.email: ${setEmail.stderr.trim()}`, 'commit-failed');\n }\n}\n\nasync function pushAuthenticatedBranch(\n sandbox: ExecutableSandbox,\n workdir: string,\n branch: string,\n cloneUrl: string,\n token: string,\n username: string,\n): Promise<void> {\n const env = gitAuthenticationEnvironment(cloneUrl, token, username, 'push-failed');\n const push = await execute(sandbox, 'git', ['-C', workdir, 'push', '-u', 'origin', branch], { env });\n if (push.exitCode !== 0) throw classifyGitFailure(push, 'push-failed');\n}\n\n/** Whether the checkout has a local `refs/heads/<branch>`. */\nexport async function hasLocalBranch(sandbox: ExecutableSandbox, workdir: string, branch: string): Promise<boolean> {\n const ref = await execute(sandbox, 'git', ['-C', workdir, 'show-ref', '--verify', '--quiet', `refs/heads/${branch}`]);\n if (ref.exitCode === 0) return true;\n if (ref.exitCode === 1) return false;\n throw classifyGitFailure(ref, 'push-failed');\n}\n\n/**\n * Advance the session branch to HEAD and switch to it, carrying over local work.\n * Leave an already-ahead branch and its checkout unchanged; never discard its commits.\n */\nexport async function ensureLocalBranch(sandbox: ExecutableSandbox, workdir: string, branch: string): Promise<void> {\n if (!isValidGitRef(branch)) {\n throw new MaterializeError(`Refusing to materialize: invalid branch name '${branch}'.`, 'push-failed');\n }\n const exists = await hasLocalBranch(sandbox, workdir, branch);\n if (exists) {\n const ref = `refs/heads/${branch}`;\n const tip = await execute(sandbox, 'git', ['-C', workdir, 'rev-parse', '--verify', ref]);\n if (tip.exitCode !== 0) throw classifyGitFailure(tip, 'push-failed');\n const head = await execute(sandbox, 'git', ['-C', workdir, 'rev-parse', '--verify', 'HEAD']);\n if (head.exitCode !== 0) throw classifyGitFailure(head, 'push-failed');\n const oldOid = tip.stdout.trim();\n const headOid = head.stdout.trim();\n const ancestor = await execute(sandbox, 'git', ['-C', workdir, 'merge-base', '--is-ancestor', oldOid, headOid]);\n if (ancestor.exitCode === 1) {\n const ahead = await execute(sandbox, 'git', ['-C', workdir, 'merge-base', '--is-ancestor', headOid, oldOid]);\n if (ahead.exitCode === 0) return;\n if (ahead.exitCode !== 1) throw classifyGitFailure(ahead, 'push-failed');\n throw new MaterializeError(\n `Refusing to move session branch '${branch}': it has diverged from the active HEAD. Check out or merge the session branch before pushing.`,\n 'push-failed',\n );\n }\n if (ancestor.exitCode !== 0) throw classifyGitFailure(ancestor, 'push-failed');\n const current = await execute(sandbox, 'git', ['-C', workdir, 'symbolic-ref', '--quiet', 'HEAD']);\n if (current.exitCode !== 0 && current.exitCode !== 1) throw classifyGitFailure(current, 'push-failed');\n if (current.stdout.trim() === ref) return;\n // update-ref does not enforce worktree ownership, unlike branch -f.\n const worktrees = await execute(sandbox, 'git', ['-C', workdir, 'worktree', 'list', '--porcelain', '-z']);\n if (worktrees.exitCode !== 0) throw classifyGitFailure(worktrees, 'push-failed');\n if (worktrees.stdout.split('\\0').includes(`branch ${ref}`)) {\n throw new MaterializeError(\n `Refusing to move session branch '${branch}': it is checked out in another worktree.`,\n 'push-failed',\n );\n }\n const advanced = await execute(sandbox, 'git', ['-C', workdir, 'update-ref', ref, headOid, oldOid]);\n if (advanced.exitCode !== 0) throw classifyGitFailure(advanced, 'push-failed');\n }\n const switched = await execute(sandbox, 'git', ['-C', workdir, 'switch', ...(exists ? [] : ['-c']), branch]);\n if (switched.exitCode !== 0) throw classifyGitFailure(switched, 'push-failed');\n}\n\n/** Whether `workdir` is a checkout of the repository at `cloneUrl`. */\nexport async function isCheckoutOf(sandbox: ExecutableSandbox, workdir: string, cloneUrl: string): Promise<boolean> {\n return (await existingCheckoutRemote(sandbox, workdir, cloneUrl)) !== null;\n}\n\n/**\n * Push a branch back to GitHub with credentials scoped to the Git process.\n * The token never enters argv, a repository URL, or persistent configuration.\n */\nexport async function pushBranch(\n sandbox: ExecutableSandbox,\n workdir: string,\n branch: string,\n token: string,\n repoFullName: string,\n): Promise<void> {\n if (!isValidGitRef(branch)) {\n throw new MaterializeError(`Refusing to push: invalid branch name '${branch}'.`, 'push-failed');\n }\n if (!/^[\\w.-]+(?:\\/[\\w.-]+)+$/.test(repoFullName)) {\n throw new MaterializeError(`Refusing to push: invalid repo full name '${repoFullName}'.`, 'push-failed');\n }\n await pushAuthenticatedBranch(sandbox, workdir, branch, cleanUrl(repoFullName), token, 'x-access-token');\n}\n\n/**\n * Push the active session branch through the provider-neutral repository\n * access contract without persisting or exposing provider credentials.\n */\nexport async function pushRepositoryBranch(\n sandbox: ExecutableSandbox,\n workdir: string,\n branch: string,\n access: RepositoryAccess,\n repoFullName: string,\n): Promise<void> {\n if (!isValidGitRef(branch)) {\n throw new MaterializeError(`Refusing to push: invalid branch name '${branch}'.`, 'push-failed');\n }\n if (!/^[\\w.-]+(?:\\/[\\w.-]+)+$/.test(repoFullName)) {\n throw new MaterializeError(`Refusing to push: invalid repo full name '${repoFullName}'.`, 'push-failed');\n }\n const authorization = access.authorization;\n if (!authorization?.token) {\n throw new MaterializeError('Repository access did not include push credentials.', 'push-failed');\n }\n await pushAuthenticatedBranch(\n sandbox,\n workdir,\n branch,\n access.cloneUrl,\n authorization.token,\n authorization.username ?? 'x-access-token',\n );\n}\n\nexport interface CommitResult {\n /** True when a commit was created; false when there was nothing to commit. */\n committed: boolean;\n}\n\n/**\n * Stage every change in the working tree and create a commit inside the\n * sandbox. The git identity is configured first so authorship is correct. When\n * there is nothing to commit this is a no-op (`committed: false`) rather than an\n * error, so callers can safely commit-then-push without first diffing.\n *\n * @param sandbox the live sandbox containing the checkout\n * @param workdir the session workdir to commit in\n * @param message the commit message (passed as one argv value)\n * @param identity authorship identity for the commit\n */\nexport async function commitAll(\n sandbox: ExecutableSandbox,\n workdir: string,\n message: string,\n identity: GitIdentity,\n): Promise<CommitResult> {\n await configureGitIdentity(sandbox, workdir, identity);\n\n const add = await execute(sandbox, 'git', ['-C', workdir, 'add', '-A']);\n if (add.exitCode !== 0) {\n throw new MaterializeError(`git add failed: ${add.stderr.trim() || add.stdout.trim()}`, 'commit-failed');\n }\n\n // Nothing staged → nothing to commit. `git diff --cached --quiet` exits 1 when\n // there are staged changes, 0 when the index is clean.\n const staged = await execute(sandbox, 'git', ['-C', workdir, 'diff', '--cached', '--quiet']);\n if (staged.exitCode === 0) {\n return { committed: false };\n }\n\n const commit = await execute(sandbox, 'git', ['-C', workdir, 'commit', '-m', message]);\n if (commit.exitCode !== 0) {\n throw new MaterializeError(`git commit failed: ${commit.stderr.trim() || commit.stdout.trim()}`, 'commit-failed');\n }\n\n return { committed: true };\n}\n\n// ---------------------------------------------------------------------------\n// Phase 2 — setup / teardown lifecycle commands\n//\n// The org-configured setup and teardown shell commands run in the session's\n// materialized workdir. The workdir is always resolved server-side from the\n// live sandbox; client input never reaches a filesystem path.\n// ---------------------------------------------------------------------------\n\n/** Error raised when the org's setup or teardown command fails in the sandbox. */\nexport class SetupCommandError extends Error {\n constructor(\n message: string,\n readonly code: 'setup-failed' | 'teardown-failed',\n ) {\n super(message);\n this.name = 'SetupCommandError';\n }\n}\n\n/**\n * Run the project's setup command (e.g. `pnpm i && pnpm build`) inside the\n * freshly materialized session workdir. Called before the checkout is handed\n * to any agent run so it is ready to build/test. A non-zero exit is a hard\n * error — starting agent work in a half-set-up tree is worse than failing the\n * request.\n *\n * Security model: the command is intentionally arbitrary shell — that is the\n * feature (install deps, build, seed fixtures). It is only configurable by\n * authenticated org members (the settings route is gated by\n * `resolveOrgTenant` + org-scoped project lookup, with length and\n * control-character validation), and it executes exclusively inside the\n * project's isolated sandbox — the same environment where org members already\n * run arbitrary shell via the agent's command tool. It never runs on the web\n * server host, so it grants no privilege beyond what sandbox access already\n * provides.\n *\n * @param sandbox live sandbox containing the checkout\n * @param workdir the server-resolved session workdir the command runs in\n * @param command the org-configured setup shell command\n */\nasync function runLifecycleCommand(\n sandbox: ExecutableSandbox,\n workdir: string,\n command: string,\n options: { phase: 'setup' | 'teardown'; timeoutMs?: number },\n): Promise<void> {\n const result = await sh(sandbox, `cd ${shellQuote(workdir)} && { ${command}\\n}`, {\n phase: `${options.phase} command`,\n ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),\n });\n if (result.exitCode !== 0) {\n const detail = (result.stderr.trim() || result.stdout.trim()).slice(-1800);\n const label = options.phase === 'setup' ? 'Setup' : 'Teardown';\n throw new SetupCommandError(\n `${label} command failed (exit ${result.exitCode}): ${detail}`,\n options.phase === 'setup' ? 'setup-failed' : 'teardown-failed',\n );\n }\n}\n\nexport async function runSetupCommand(sandbox: ExecutableSandbox, workdir: string, command: string): Promise<void> {\n return runLifecycleCommand(sandbox, workdir, command, { phase: 'setup' });\n}\n\n/**\n * Run the repository's best-effort teardown command from the materialized\n * session workdir. Callers own lifecycle policy: this helper reports failures\n * so the retirement coordinator can log them while still continuing with\n * scrub, pooling/destruction, cache invalidation, and row deletion.\n */\nexport async function runTeardownCommand(\n sandbox: ExecutableSandbox,\n workdir: string,\n command: string,\n options: { timeoutMs?: number } = {},\n): Promise<void> {\n return runLifecycleCommand(sandbox, workdir, command, {\n phase: 'teardown',\n ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),\n });\n}\n\nexport interface CreatePullRequestArgs {\n /** Short-lived installation token, injected only into the `gh` process env. */\n token: string;\n /** Base branch the PR merges into. Ref-validated. */\n base: string;\n /** Head branch the PR is opened from. Ref-validated. */\n head: string;\n /** PR title. */\n title: string;\n /** PR body (optional). */\n body?: string;\n}\n\nexport interface CreatePullRequestResult {\n /** The PR URL parsed from `gh pr create` stdout. */\n url: string;\n}\n\n/**\n * Preflight that `gh` is installed in the sandbox. Only called on the PR path so\n * a missing `gh` never blocks clone/open. Surfaces an actionable error naming\n * the sandbox template requirement.\n */\nasync function assertGhAvailable(sandbox: ExecutableSandbox): Promise<void> {\n const version = await execute(sandbox, 'gh', ['--version']);\n if (version.exitCode !== 0) {\n throw new MaterializeError(\n 'The GitHub CLI (gh) is not installed in the sandbox. The sandbox template must include gh to open pull requests.',\n 'gh-missing',\n );\n }\n}\n\n/** Match the first GitHub PR URL in `gh pr create` output. */\nfunction parsePullRequestUrl(stdout: string): string | undefined {\n const match = stdout.match(/https:\\/\\/github\\.com\\/[^\\s]+\\/pull\\/\\d+/);\n return match?.[0];\n}\n\n/**\n * Open a pull request from inside the sandbox via `gh pr create`. The token is\n * passed only through a per-invocation `GH_TOKEN` env scoped to the single `gh`\n * process (never persisted), all arguments are passed as structured argv, and\n * the resulting PR URL is parsed from stdout.\n *\n * @param sandbox live sandbox containing the checkout\n * @param workdir the worktree (or repo) path the PR head branch is checked out in\n */\nexport async function createPullRequest(\n sandbox: ExecutableSandbox,\n workdir: string,\n { token, base, head, title, body }: CreatePullRequestArgs,\n): Promise<CreatePullRequestResult> {\n if (!isValidGitRef(base)) {\n throw new MaterializeError(`Refusing to open PR: invalid base branch '${base}'.`, 'pr-failed');\n }\n if (!isValidGitRef(head)) {\n throw new MaterializeError(`Refusing to open PR: invalid head branch '${head}'.`, 'pr-failed');\n }\n\n await assertGhAvailable(sandbox);\n\n const result = await execute(\n sandbox,\n 'gh',\n ['pr', 'create', '--base', base, '--head', head, '--title', title, '--body', body ?? ''],\n { cwd: workdir, env: { GH_TOKEN: token } },\n );\n if (result.exitCode !== 0) {\n const classified = classifyGitFailure(result, 'push-failed');\n if (classified.code === 'egress-blocked') {\n throw classified;\n }\n throw new MaterializeError(`gh pr create failed: ${result.stderr.trim() || result.stdout.trim()}`, 'pr-failed');\n }\n\n const url = parsePullRequestUrl(result.stdout);\n if (!url) {\n throw new MaterializeError(\n `gh pr create succeeded but no PR URL was found in its output: ${result.stdout.trim()}`,\n 'pr-failed',\n );\n }\n\n return { url };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sandbox.js","names":[],"sources":["../../../src/integrations/github/sandbox.ts"],"sourcesContent":["/**\n * Repo materialization for GitHub-backed repositories.\n *\n * A GitHub repo is never cloned onto the server host. The repo is cloned\n * *inside* the session's sandbox, so the agent's file tools and command tools\n * operate entirely against the remote checkout.\n *\n * - `materializeRepo(row, token)` clones the repo inside the sandbox when no\n * checkout exists yet (a base-image boot, a wiped disk), supplying the\n * short-lived token only in that Git process's environment. A checkout that\n * is already there, from a repo template image or an earlier start, is left\n * exactly as it is.\n *\n * This module owns everything git/GitHub: clone, commit/push, setup/teardown commands,\n * and `gh pr create`. Workdir layout lives in `../sandbox/workdir`.\n */\n\nimport type { RepositoryAccess } from '../../capabilities/version-control.js';\nimport { isValidGitRef } from '../../sandbox/git-ref.js';\nimport type { ExecutableSandbox, SandboxCommandResult } from '../../sandbox/materialization.js';\nimport type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';\nimport { timedPhase } from '../../timing.js';\n\ntype MaterializationStore = Pick<SourceControlStorageHandle['sessions'], 'markMaterialized'>;\n\ninterface RepoMaterializationBinding {\n id: string;\n sandboxWorkdir: string;\n materializedAt: Date | null;\n}\n\n/**\n * Single-quote a string for safe POSIX shell interpolation. Wraps the value in\n * single quotes and escapes any embedded single quote using the canonical\n * close-quote / escaped-quote / reopen-quote sequence (`'\\''`). This is the\n * standard POSIX-safe construction and prevents the quoted string from being\n * terminated early.\n */\nexport function shellQuote(value: string): string {\n // Replace each ' with the four-character sequence: ' \\ ' '\n return `'` + value.split(`'`).join(`'\\\\''`) + `'`;\n}\n\n/**\n * Default hang guard for sandbox shell commands. Generous by design — large\n * clones and dependency installs legitimately take minutes; the guard exists\n * so a wedged sandbox surfaces a failure instead of hanging the request that\n * triggered materialization forever.\n */\nexport const DEFAULT_COMMAND_TIMEOUT_MS = 15 * 60_000;\n/** Branch checkout only fetches one ref — a much tighter budget applies. */\nexport const CHECKOUT_COMMAND_TIMEOUT_MS = 5 * 60_000;\n\ninterface ShOptions {\n /** Override the hang-guard budget for this command. */\n timeoutMs?: number;\n /** Human-readable phase name included in the timeout error. */\n phase?: string;\n}\n\ninterface CommandOptions extends ShOptions {\n cwd?: string;\n env?: Record<string, string | undefined>;\n}\n\n/**\n * A thrown transport-level failure that is worth retrying: remote sandbox\n\n * providers (e.g. the platform workspace proxy) surface transient 5xx errors\n * as exceptions carrying an HTTP `status` — typically while a freshly\n * provisioned VM is still coming up. Command failures are NOT exceptions\n * (they resolve with a non-zero exit code), so retrying here never re-runs a\n * command that the sandbox already executed and rejected.\n */\nfunction isTransientTransportError(error: unknown): boolean {\n const status = (error as { status?: unknown })?.status;\n return typeof status === 'number' && status >= 500;\n}\n\nconst SH_RETRIES = 2;\nconst SH_RETRY_DELAY_MS = 2000;\n\n/**\n * Run a shell script in the sandbox via `sh -c`. This is reserved for the\n * explicitly configured repository lifecycle hooks; programmatic commands use\n * {@link execute} with an argv array.\n */\nexport async function sh(\n sandbox: ExecutableSandbox,\n script: string,\n options: ShOptions = {},\n): Promise<SandboxCommandResult> {\n return execute(sandbox, 'sh', ['-c', script], options);\n}\n\n/**\n * Execute one program with an argv array, bounded by a hang guard. Transient\n * sandbox transport failures are retried without ever interpolating arguments\n * into a shell command.\n */\nasync function execute(\n sandbox: ExecutableSandbox,\n command: string,\n args: string[],\n options: CommandOptions = {},\n): Promise<SandboxCommandResult> {\n const deadlineMs = Date.now() + (options.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS);\n for (let attempt = 0; ; attempt++) {\n const started = performance.now();\n try {\n const result = await executeOnce(sandbox, command, args, {\n ...options,\n timeoutMs: Math.max(deadlineMs - Date.now(), 1),\n });\n if (options.phase) {\n process.stderr.write(\n `[factory:timing] ${options.phase} attempt=${attempt + 1} exit=${result.exitCode} ${Math.round(performance.now() - started)}ms\\n`,\n );\n }\n return result;\n } catch (error) {\n if (options.phase) {\n process.stderr.write(\n `[factory:timing] ${options.phase} attempt=${attempt + 1} threw after ${Math.round(performance.now() - started)}ms: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n }\n if (attempt >= SH_RETRIES || !isTransientTransportError(error)) throw error;\n const delayMs = SH_RETRY_DELAY_MS * (attempt + 1);\n if (deadlineMs - Date.now() <= delayMs) throw error;\n await new Promise(resolve => setTimeout(resolve, delayMs));\n }\n }\n}\n\n/** Single structured command execution attempt, bounded by the hang guard. */\nasync function executeOnce(\n sandbox: ExecutableSandbox,\n command: string,\n args: string[],\n options: CommandOptions,\n): Promise<SandboxCommandResult> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const hangGuard = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n const phase = options.phase ? ` during ${options.phase}` : '';\n reject(new Error(`Sandbox command timed out after ${Math.round(timeoutMs / 1000)}s${phase}.`));\n }, timeoutMs);\n timer.unref?.();\n });\n try {\n return await Promise.race([\n sandbox.executeCommand(command, args, {\n timeout: timeoutMs,\n ...(options.cwd !== undefined ? { cwd: options.cwd } : {}),\n ...(options.env !== undefined ? { env: options.env } : {}),\n }),\n hangGuard,\n ]);\n } finally {\n clearTimeout(timer);\n }\n}\n\nconst GIT_TRANSFER_RETRIES = 2;\nconst GIT_TRANSFER_RETRY_DELAY_MS = 2000;\n\n/**\n * True when a git transfer died mid-flight rather than being refused.\n *\n * `sh` already retries transport errors the sandbox provider *throws*, but a\n * git command that reaches the network and then loses it exits non-zero\n * instead — so a single HTTP/2 framing glitch or dropped connection to\n * github.com would otherwise permanently fail opening a workspace. These\n * patterns all mean \"the bytes stopped arriving\", which says nothing about\n * whether the operation would succeed if attempted again.\n *\n * Deliberately narrow: a refusal (bad credentials, missing repo, blocked\n * egress) is terminal and must surface immediately rather than be retried into\n * a slow failure.\n */\nfunction isTransientGitFailure(result: SandboxCommandResult): boolean {\n const output = `${result.stderr || ''}\\n${result.stdout || ''}`;\n return /HTTP2 framing layer|RPC failed; curl|RPC failed; HTTP 5\\d\\d|the remote end hung up unexpectedly|early EOF|unexpected disconnect|connection reset by peer|Recv failure|Send failure|GnuTLS recv error|TLS connection was non-properly terminated|502 Bad Gateway|503 Service Unavailable/i.test(\n output,\n );\n}\n\n/**\n * Run a git command that only *reads* from the remote, retrying it when the\n * transfer dies mid-flight. Restricted to read-only transfers on purpose:\n * re-running a clone or a fetch is free, whereas re-running a push could\n * duplicate work already accepted by the remote before the connection dropped.\n *\n * `beforeRetry` lets a call site clear whatever the aborted attempt left\n * behind — a half-written clone directory blocks the next `git clone` outright.\n */\nasync function gitTransfer(\n sandbox: ExecutableSandbox,\n args: string[],\n options: CommandOptions & { beforeRetry?: (attempt: number) => Promise<void> } = {},\n): Promise<SandboxCommandResult> {\n const { beforeRetry, ...commandOptions } = options;\n const deadlineMs = Date.now() + (commandOptions.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS);\n for (let attempt = 0; ; attempt++) {\n const result = await execute(sandbox, 'git', args, {\n ...commandOptions,\n timeoutMs: Math.max(deadlineMs - Date.now(), 1),\n });\n if (result.exitCode === 0 || attempt >= GIT_TRANSFER_RETRIES || !isTransientGitFailure(result)) return result;\n process.stderr.write(\n `[factory:timing] git ${commandOptions.phase ?? 'transfer'} retrying after attempt ${attempt + 1}\\n`,\n );\n const delayMs = GIT_TRANSFER_RETRY_DELAY_MS * (attempt + 1);\n if (deadlineMs - Date.now() <= delayMs) return result;\n await new Promise(resolve => setTimeout(resolve, delayMs));\n await beforeRetry?.(attempt + 1);\n }\n}\n\n/** Error raised when the sandbox cannot materialize the repo (actionable). */\nexport class MaterializeError extends Error {\n constructor(\n message: string,\n readonly code:\n | 'git-missing'\n | 'egress-blocked'\n | 'clone-failed'\n | 'pull-failed'\n | 'push-failed'\n | 'commit-failed'\n | 'gh-missing'\n | 'pr-failed',\n ) {\n super(message);\n this.name = 'MaterializeError';\n }\n}\n\nfunction cleanUrl(repoFullName: string): string {\n return `https://github.com/${repoFullName}.git`;\n}\n\nfunction credentialScope(\n cloneUrl: string,\n code: 'clone-failed' | 'pull-failed' | 'push-failed' = 'clone-failed',\n): string {\n let url: URL;\n try {\n url = new URL(cloneUrl);\n } catch {\n throw new MaterializeError('Refusing to configure credentials for an invalid repository clone URL.', code);\n }\n if (url.protocol !== 'https:' || !url.hostname || url.username || url.password || url.search || url.hash) {\n throw new MaterializeError('Refusing to configure credentials for an invalid repository clone URL.', code);\n }\n const repositoryPath = url.pathname.replace(/\\/+$/, '');\n if (!repositoryPath || repositoryPath === '/') {\n throw new MaterializeError('Refusing to configure credentials for an invalid repository clone URL.', code);\n }\n return url.origin + repositoryPath;\n}\n\n/**\n * Provide HTTP Basic credentials to one Git process without putting a secret\n * in argv, a remote URL, or persistent git config. Git reads the URL-scoped\n * extra header from its process environment and discards it on exit.\n */\nfunction gitAuthenticationEnvironment(\n cloneUrl: string,\n token: string,\n username: string,\n code: 'clone-failed' | 'pull-failed' | 'push-failed' = 'clone-failed',\n): Record<string, string> {\n const scope = credentialScope(cloneUrl, code);\n if (!token || !username) {\n throw new MaterializeError('Repository access did not include usable credentials.', code);\n }\n const authorization = Buffer.from(`${username}:${token}`, 'utf8').toString('base64');\n // Git applies `http.<url>.*` only when the config URL matches the remote at\n // a path boundary, and `origin` may spell the repository either way: Factory\n // clones with `.git`, a sandbox template clones from the normalized URL\n // without it. Scope the header to both so neither checkout prompts.\n const alternate = scope.endsWith('.git') ? scope.slice(0, -4) : `${scope}.git`;\n return {\n GIT_CONFIG_COUNT: '2',\n GIT_CONFIG_KEY_0: `http.${scope}.extraHeader`,\n GIT_CONFIG_VALUE_0: `Authorization: Basic ${authorization}`,\n GIT_CONFIG_KEY_1: `http.${alternate}.extraHeader`,\n GIT_CONFIG_VALUE_1: `Authorization: Basic ${authorization}`,\n GIT_TERMINAL_PROMPT: '0',\n };\n}\n\nfunction normalizedRemoteUrl(value: string): string | null {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return null;\n }\n if (url.protocol !== 'https:' || !url.hostname || url.search || url.hash) return null;\n const pathname = url.pathname.replace(/\\/+$/, '').replace(/\\.git$/i, '');\n const repositoryPath = url.hostname.toLowerCase() === 'github.com' ? pathname.toLowerCase() : pathname;\n return 'https://' + url.host.toLowerCase() + repositoryPath;\n}\n\n/** Repo metadata needed to materialize, read from the org-owned project row. */\nexport interface RepoMaterializeInfo {\n repoFullName: string;\n defaultBranch: string;\n /** Provider-supplied, credential-free HTTPS clone URL. */\n cloneUrl?: string;\n /** Username paired with the bearer token for git-over-HTTPS. */\n authUsername?: string;\n}\n\n/** Options for {@link materializeRepo}. */\nexport interface MaterializeRepoOptions {\n /** The per-(project,user) sandbox binding whose workdir this materializes into. */\n row: RepoMaterializationBinding;\n /** Repo metadata from the org-owned project row. */\n repoInfo: RepoMaterializeInfo;\n /** The live sandbox to run git inside. */\n sandbox: ExecutableSandbox;\n /** A freshly minted, short-lived installation access token. */\n token: string;\n storage: MaterializationStore;\n}\n\nasync function clearWorkdir(sandbox: ExecutableSandbox, workdir: string): Promise<void> {\n const mkdir = await execute(sandbox, 'mkdir', ['-p', workdir]);\n if (mkdir.exitCode !== 0) throw classifyGitFailure(mkdir, 'clone-failed');\n const clear = await execute(sandbox, 'find', [\n workdir,\n '-mindepth',\n '1',\n '-maxdepth',\n '1',\n '-exec',\n 'rm',\n '-rf',\n '--',\n '{}',\n '+',\n ]);\n if (clear.exitCode !== 0) throw classifyGitFailure(clear, 'clone-failed');\n}\n/**\n * Materialize the repo inside the user's sandbox: clone when no checkout of\n * this repo exists, otherwise nothing. Credentials are process-scoped and the\n * stored remote remains credential-free. Sets `materialized_at` on the\n * per-user sandbox binding row.\n */\nexport async function materializeRepo(options: MaterializeRepoOptions): Promise<void> {\n return timedPhase('workspace.materialize', () => materializeRepoImpl(options));\n}\n\nasync function materializeRepoImpl(options: MaterializeRepoOptions): Promise<void> {\n const { row: sandboxRow, repoInfo, sandbox, token, storage } = options;\n const workdir = sandboxRow.sandboxWorkdir;\n const repo = repoInfo.repoFullName;\n\n // 0. Defense in depth: never build a git command from values that aren't\n // strictly shaped, even if a malformed row reached the DB. Inputs are also\n // validated at the route boundary before storage.\n if (!/^[\\w.-]+(?:\\/[\\w.-]+)+$/.test(repo)) {\n throw new MaterializeError(`Refusing to materialize: invalid repo full name '${repo}'.`, 'clone-failed');\n }\n if (!/^[A-Za-z0-9_./-]+$/.test(repoInfo.defaultBranch)) {\n throw new MaterializeError(\n `Refusing to materialize: invalid default branch '${repoInfo.defaultBranch}'.`,\n 'clone-failed',\n );\n }\n\n // 1. Preflight: git must be installed in the sandbox template.\n const gitVersion = await execute(sandbox, 'git', ['--version']);\n if (gitVersion.exitCode !== 0) {\n throw new MaterializeError(\n 'git is not installed in the sandbox. The sandbox template must include git.',\n 'git-missing',\n );\n }\n\n // The DB's `materializedAt` can drift from disk in both directions: a fresh\n // binding row over an already-populated workdir (a repo template image,\n // local dev DB resets, repaired rows) must not fail `git clone` on the\n // non-empty directory, and a stale `materializedAt` over an empty sandbox\n // (an expired/recreated VM whose disk was wiped) must re-clone instead of\n // running `git -C <workdir>` against a directory that no longer exists.\n // Disk is the source of truth: detect the checkout instead of trusting the\n // row. An existing checkout is left as it is, whatever it is on: a template\n // image sits detached at its pinned commit, a resumed session on its\n // branch. Syncing with the remote is the session's business; the branch\n // checkout that follows fetches the base branch it needs.\n const cleanCloneUrl = repoInfo.cloneUrl ?? cleanUrl(repo);\n const existing = await existingCheckoutRemote(sandbox, workdir, cleanCloneUrl);\n if (existing !== null) {\n // A token an earlier start failed to scrub must not outlive it; the\n // remote already carries the plain URL otherwise, so this costs nothing\n // on the common path.\n if (/\\/\\/[^/]*@/.test(existing)) await scrubRemote(sandbox, workdir, repo, cleanCloneUrl);\n } else {\n // 2. First open: shallow-clone the default branch into the workdir, the\n // same clone a repo template bakes into its image. The workdir holds no\n // usable checkout of this repo, but it may not be empty: a checkpoint\n // seed or a clone that died partway (a crashed or OOM-killed server)\n // leaves a partial tree behind, and `git clone` refuses a non-empty\n // destination with a non-retryable fatal. Nothing here is recoverable,\n // the probe above already ruled out a checkout of this repo, so\n // clear its contents before cloning, exactly as the retry path does. Keep\n // the workdir itself because LocalSandbox runs commands with this\n // directory as the child process cwd.\n const authEnv = gitAuthenticationEnvironment(\n cleanCloneUrl,\n token,\n repoInfo.authUsername ?? 'x-access-token',\n 'clone-failed',\n );\n await clearWorkdir(sandbox, workdir);\n const clone = await gitTransfer(\n sandbox,\n ['clone', '--depth=1', '--single-branch', '--branch', repoInfo.defaultBranch, '--', cleanCloneUrl, workdir],\n {\n env: authEnv,\n phase: 'repository clone',\n beforeRetry: async () => {\n // A clone that died partway leaves the destination non-empty, which\n // git refuses to clone into. Clear its contents so the retry starts\n // clean without removing LocalSandbox's process cwd.\n await clearWorkdir(sandbox, workdir);\n },\n },\n );\n if (clone.exitCode !== 0) throw classifyGitFailure(clone, 'clone-failed');\n }\n\n // 4. Mark materialized.\n await storage.markMaterialized({ id: sandboxRow.id });\n}\n\nexport interface SessionBranchOptions {\n /** Repository-local authorship configured before setup commands or agent access. */\n identity?: GitIdentity;\n branch: string;\n baseBranch: string;\n token: string;\n repoFullName: string;\n cloneUrl?: string;\n authUsername?: string;\n /** A pull-request card's session starts on the PR head instead of the base tip. */\n pullRequestNumber?: number;\n /** A GitLab merge-request card's session starts on the MR head instead of the base tip. */\n mergeRequestNumber?: number;\n}\n\n/**\n * Past file contents of a blob-less history load on demand; git asks gh, which\n * answers from the session's `GH_TOKEN`, so no credential is ever written.\n */\nconst GH_CREDENTIAL_HELPER = '!gh auth git-credential';\n\nasync function fetchStartPoint(\n sandbox: ExecutableSandbox,\n workdir: string,\n {\n baseBranch,\n pullRequestNumber,\n mergeRequestNumber,\n }: Pick<SessionBranchOptions, 'baseBranch' | 'pullRequestNumber' | 'mergeRequestNumber'>,\n shallowClone: boolean,\n env: Record<string, string>,\n): Promise<SandboxCommandResult> {\n const changeRequestRef =\n pullRequestNumber !== undefined\n ? `refs/pull/${pullRequestNumber}/head`\n : mergeRequestNumber !== undefined\n ? `refs/merge-requests/${mergeRequestNumber}/head`\n : undefined;\n const baseArgs = [\n '-C',\n workdir,\n 'fetch',\n ...(changeRequestRef && shallowClone ? ['--unshallow'] : []),\n ...(pullRequestNumber !== undefined ? ['--filter=blob:none'] : []),\n 'origin',\n baseBranch,\n ];\n const base = await gitTransfer(sandbox, baseArgs, {\n env,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'branch checkout fetch',\n });\n if (base.exitCode !== 0 || !changeRequestRef) return base;\n return gitTransfer(\n sandbox,\n [\n '-C',\n workdir,\n 'fetch',\n ...(pullRequestNumber !== undefined ? ['--filter=blob:none'] : []),\n 'origin',\n changeRequestRef,\n ],\n {\n env,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'change request head fetch',\n },\n );\n}\n\n/** Check out a session's branch inside its isolated repository clone. */\nexport async function checkoutSessionBranch(\n sandbox: ExecutableSandbox,\n workdir: string,\n options: SessionBranchOptions,\n): Promise<void> {\n return timedPhase('workspace.checkout', () => checkoutSessionBranchImpl(sandbox, workdir, options));\n}\n\nexport interface SyncEnvironmentRepositoryOptions {\n /** Repository-local authorship configured before setup commands or agent access. */\n identity?: GitIdentity;\n /** The session branch to resume when the remote already carries it. */\n branch: string;\n /** The repository's default branch; new session branches start at its remote tip. */\n defaultBranch: string;\n token: string;\n repoFullName: string;\n cloneUrl?: string;\n authUsername?: string;\n}\n\nexport interface EnvironmentSyncResult {\n outcome: 'resumed' | 'created' | 'kept';\n /** The branch the checkout ends on; `null` when it stays detached. */\n branch: string | null;\n}\n\n/**\n * Bring a secondary environment repository onto the session branch at\n * session start. The primary repository keeps {@link checkoutSessionBranch};\n * every other repository follows a conservative rule: resume the session\n * branch when the remote or the local clone has it, otherwise create it from\n * the default branch tip (a detached HEAD, the template image pinned at its\n * build commit, is moved there first). A checkout that sits on any other\n * branch is left alone: it may carry local-only commits.\n */\nexport async function syncEnvironmentRepository(\n sandbox: ExecutableSandbox,\n workdir: string,\n options: SyncEnvironmentRepositoryOptions,\n): Promise<EnvironmentSyncResult> {\n const { branch, defaultBranch, token, repoFullName, cloneUrl, authUsername } = options;\n if (!isValidGitRef(branch) || !isValidGitRef(defaultBranch)) {\n throw new MaterializeError('Refusing to sync a repository from an invalid branch name.', 'pull-failed');\n }\n const cleanCloneUrl = cloneUrl ?? cleanUrl(repoFullName);\n const authEnv = gitAuthenticationEnvironment(cleanCloneUrl, token, authUsername ?? 'x-access-token', 'pull-failed');\n\n await configureGitIdentity(sandbox, workdir, options.identity ?? {});\n\n const current = await execute(sandbox, 'git', ['-C', workdir, 'branch', '--show-current']);\n if (current.exitCode !== 0) throw classifyGitFailure(current, 'pull-failed');\n const currentBranch = current.stdout.trim();\n const kept = { outcome: 'kept' as const, branch: currentBranch || null };\n if (currentBranch === branch) return kept;\n\n // A local session branch may hold commits the remote never saw; switch\n // to it instead of resetting it onto the remote tip.\n const local = await execute(sandbox, 'git', [\n '-C',\n workdir,\n 'rev-parse',\n '--verify',\n '--quiet',\n `refs/heads/${branch}`,\n ]);\n if (local.exitCode === 0) {\n const checkout = await execute(sandbox, 'git', ['-C', workdir, 'checkout', branch], {\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'environment branch checkout',\n });\n if (checkout.exitCode === 0) return { outcome: 'resumed', branch };\n if (isBlockedByLocalWork(checkout)) return kept;\n throw classifyGitFailure(checkout, 'pull-failed');\n }\n\n const remoteHeads = await gitTransfer(\n sandbox,\n ['-C', workdir, 'ls-remote', '--heads', 'origin', `refs/heads/${branch}`],\n { env: authEnv, timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS, phase: 'environment branch probe' },\n );\n if (remoteHeads.exitCode !== 0) throw classifyGitFailure(remoteHeads, 'pull-failed');\n if (remoteHeads.stdout.trim()) {\n const fetch = await gitTransfer(sandbox, ['-C', workdir, 'fetch', 'origin', branch], {\n env: authEnv,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'environment branch fetch',\n });\n if (fetch.exitCode !== 0) throw classifyGitFailure(fetch, 'pull-failed');\n const checkout = await execute(sandbox, 'git', ['-C', workdir, 'checkout', '-B', branch, 'FETCH_HEAD'], {\n env: authEnv,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'environment branch checkout',\n });\n if (checkout.exitCode === 0) return { outcome: 'resumed', branch };\n if (isBlockedByLocalWork(checkout)) return kept;\n throw classifyGitFailure(checkout, 'pull-failed');\n }\n\n // A checkout on any other branch stays where it is: it may hold work the\n // remote never saw. The default branch and a detached HEAD (the template\n // image pinned at its build commit) become the session branch.\n if (currentBranch && currentBranch !== defaultBranch) return kept;\n if (currentBranch) {\n const status = await execute(sandbox, 'git', ['-C', workdir, 'status', '--porcelain']);\n if (status.exitCode !== 0) throw classifyGitFailure(status, 'pull-failed');\n if (status.stdout.trim()) return kept;\n }\n const fetch = await gitTransfer(sandbox, ['-C', workdir, 'fetch', 'origin', defaultBranch], {\n env: authEnv,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'environment default fetch',\n });\n if (fetch.exitCode !== 0) throw classifyGitFailure(fetch, 'pull-failed');\n if (currentBranch) {\n // Do not discard local-only commits on the default branch. A shallow\n // checkout whose ancestry cannot be established is also left untouched.\n const ancestor = await execute(sandbox, 'git', [\n '-C',\n workdir,\n 'merge-base',\n '--is-ancestor',\n 'HEAD',\n 'FETCH_HEAD',\n ]);\n if (ancestor.exitCode === 1) return kept;\n if (ancestor.exitCode !== 0) throw classifyGitFailure(ancestor, 'pull-failed');\n } else {\n const checkout = await execute(sandbox, 'git', ['-C', workdir, 'checkout', '-B', defaultBranch, 'FETCH_HEAD'], {\n env: authEnv,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'environment default checkout',\n });\n if (checkout.exitCode !== 0) {\n if (isBlockedByLocalWork(checkout)) return kept;\n throw classifyGitFailure(checkout, 'pull-failed');\n }\n }\n const created = await execute(sandbox, 'git', ['-C', workdir, 'checkout', '-b', branch, 'FETCH_HEAD'], {\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'environment branch create',\n });\n if (created.exitCode === 0) return { outcome: 'created', branch };\n throw classifyGitFailure(created, 'pull-failed');\n}\n\n/** Refresh an existing GitLab review checkout without exposing its credential to the agent. */\nexport async function refreshMergeRequestCheckout(\n sandbox: ExecutableSandbox,\n workdir: string,\n input: { branch: string; mergeRequestNumber: number; expectedHeadSha: string; access: RepositoryAccess },\n): Promise<{ headSha: string; changed: boolean }> {\n const { branch, mergeRequestNumber, expectedHeadSha, access } = input;\n if (\n !isValidGitRef(branch) ||\n !Number.isSafeInteger(mergeRequestNumber) ||\n mergeRequestNumber <= 0 ||\n !/^[0-9a-f]{40}$/i.test(expectedHeadSha)\n ) {\n throw new Error('Refusing to refresh a GitLab review with invalid session or head metadata.');\n }\n const token = access.authorization?.token;\n if (!token) throw new Error('GitLab repository access did not include a bearer token.');\n const env = gitAuthenticationEnvironment(\n access.cloneUrl,\n token,\n access.authorization?.username ?? 'oauth2',\n 'pull-failed',\n );\n const current = await execute(sandbox, 'git', ['-C', workdir, 'branch', '--show-current']);\n if (current.exitCode !== 0 || current.stdout.trim() !== branch) {\n throw new Error('The active checkout is not on its bound review branch.');\n }\n const status = await execute(sandbox, 'git', ['-C', workdir, 'status', '--porcelain', '--untracked-files=all']);\n if (status.exitCode !== 0 || status.stdout.trim()) {\n throw new Error('The review checkout has local changes; refusing to replace them.');\n }\n const before = await execute(sandbox, 'git', ['-C', workdir, 'rev-parse', 'HEAD']);\n if (before.exitCode !== 0) throw new Error('Could not read the current review checkout head.');\n const fetch = await gitTransfer(\n sandbox,\n ['-C', workdir, 'fetch', 'origin', `refs/merge-requests/${mergeRequestNumber}/head`],\n {\n env,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'GitLab review head refresh',\n },\n );\n if (fetch.exitCode !== 0) throw classifyGitFailure(fetch, 'pull-failed');\n const fetched = await execute(sandbox, 'git', ['-C', workdir, 'rev-parse', 'FETCH_HEAD']);\n if (fetched.exitCode !== 0 || fetched.stdout.trim().toLowerCase() !== expectedHeadSha.toLowerCase()) {\n throw new Error('The fetched GitLab review head differs from the provider-reported head; retry after it settles.');\n }\n if (before.stdout.trim().toLowerCase() === expectedHeadSha.toLowerCase()) {\n return { headSha: expectedHeadSha, changed: false };\n }\n const updated = await execute(sandbox, 'git', ['-C', workdir, 'checkout', '-B', branch, 'FETCH_HEAD'], { env });\n if (updated.exitCode !== 0) throw classifyGitFailure(updated, 'pull-failed');\n return { headSha: expectedHeadSha, changed: true };\n}\n\nasync function checkoutSessionBranchImpl(\n sandbox: ExecutableSandbox,\n workdir: string,\n options: SessionBranchOptions,\n): Promise<void> {\n const { branch, baseBranch, token, repoFullName, pullRequestNumber, mergeRequestNumber, cloneUrl, authUsername } =\n options;\n if (\n !isValidGitRef(branch) ||\n !isValidGitRef(baseBranch) ||\n (pullRequestNumber !== undefined && mergeRequestNumber !== undefined) ||\n (mergeRequestNumber !== undefined && (!Number.isSafeInteger(mergeRequestNumber) || mergeRequestNumber <= 0))\n ) {\n throw new MaterializeError('Refusing to create a session from an invalid branch name.', 'clone-failed');\n }\n\n const changeRequestSession = pullRequestNumber !== undefined || mergeRequestNumber !== undefined;\n // GitHub delegates later authenticated operations to `gh`. Other providers\n // deliberately receive no persistent credential helper. Remove the legacy\n // helper when reopening an older checkout so a stale sandbox cannot retain\n // credentials injected by an earlier implementation.\n const cleanCloneUrl = cloneUrl ?? cleanUrl(repoFullName);\n const authEnv = gitAuthenticationEnvironment(cleanCloneUrl, token, authUsername ?? 'x-access-token', 'pull-failed');\n const credentialKey = authUsername ? 'credential.' + credentialScope(cleanCloneUrl) + '.helper' : 'credential.helper';\n if (authUsername) {\n await execute(sandbox, 'git', ['-C', workdir, 'config', '--unset-all', credentialKey]);\n } else {\n const configured = await execute(sandbox, 'git', ['-C', workdir, 'config', credentialKey, GH_CREDENTIAL_HELPER]);\n if (configured.exitCode !== 0) throw classifyGitFailure(configured, 'pull-failed');\n }\n\n await configureGitIdentity(sandbox, workdir, options.identity ?? {});\n\n const current = await execute(sandbox, 'git', ['-C', workdir, 'branch', '--show-current']);\n if (current.exitCode === 0 && current.stdout.trim() === branch) return;\n\n const local = await execute(sandbox, 'git', [\n '-C',\n workdir,\n 'show-ref',\n '--verify',\n '--quiet',\n `refs/heads/${branch}`,\n ]);\n if (local.exitCode === 0) {\n const checkout = await execute(sandbox, 'git', ['-C', workdir, 'checkout', branch], { env: authEnv });\n if (checkout.exitCode !== 0) {\n if (isBlockedByLocalWork(checkout)) return;\n throw classifyGitFailure(checkout, 'clone-failed');\n }\n return;\n }\n\n const shallowClone =\n changeRequestSession &&\n (await execute(sandbox, 'git', ['-C', workdir, 'rev-parse', '--is-shallow-repository'])).stdout.trim() === 'true';\n const fetch = await fetchStartPoint(sandbox, workdir, options, shallowClone, authEnv);\n if (fetch.exitCode !== 0) throw classifyGitFailure(fetch, 'pull-failed');\n\n const create = await execute(sandbox, 'git', ['-C', workdir, 'checkout', '-b', branch, 'FETCH_HEAD'], {\n env: authEnv,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'branch checkout',\n });\n if (create.exitCode === 0 || isBlockedByLocalWork(create)) return;\n if (!isBranchCollision(create)) throw classifyGitFailure(create, 'clone-failed');\n\n // The branch exists even though the show-ref probe missed it: either another\n // materialization created it concurrently (adopt it), or the sandbox carries\n // a broken loose ref (remove it and retry).\n const adopt = await execute(sandbox, 'git', ['-C', workdir, 'checkout', branch], { env: authEnv });\n if (adopt.exitCode === 0 || isBlockedByLocalWork(adopt)) return;\n\n let drop = await execute(sandbox, 'git', ['-C', workdir, 'update-ref', '--no-deref', '-d', `refs/heads/${branch}`]);\n if (drop.exitCode !== 0) {\n const gitDir = await execute(sandbox, 'git', ['-C', workdir, 'rev-parse', '--absolute-git-dir']);\n if (gitDir.exitCode !== 0 || !gitDir.stdout.trim()) throw classifyGitFailure(create, 'clone-failed');\n drop = await execute(sandbox, 'rm', ['-f', '--', `${gitDir.stdout.trim()}/refs/heads/${branch}`]);\n }\n if (drop.exitCode !== 0) throw classifyGitFailure(create, 'clone-failed');\n\n const retry = await execute(sandbox, 'git', ['-C', workdir, 'checkout', '-b', branch, 'FETCH_HEAD'], {\n env: authEnv,\n timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS,\n phase: 'branch checkout retry',\n });\n if (retry.exitCode !== 0) {\n if (isBlockedByLocalWork(retry)) return;\n throw classifyGitFailure(retry, 'clone-failed');\n }\n}\n\n/**\n * True when `git checkout -b` failed only because the branch ref already\n * exists — the collision Factory hits when a pooled sandbox carries a ref the\n * show-ref probe could not see (broken loose ref) or a concurrent\n * materialization created the branch after the probe ran.\n */\nfunction isBranchCollision(result: SandboxCommandResult): boolean {\n return /a branch named .* already exists/i.test(`${result.stderr || ''}\\n${result.stdout || ''}`);\n}\n\n/**\n * True when a failed `git checkout` just means uncommitted or untracked files\n * in the working tree would be clobbered by the branch switch. Those files are\n * a session's work in progress — the switch must yield to them, never the\n * other way around.\n */\nfunction isBlockedByLocalWork(result: SandboxCommandResult): boolean {\n const output = `${result.stderr || ''}\\n${result.stdout || ''}`;\n return /Your local changes to the following files would be overwritten by checkout|untracked working tree files would be overwritten by checkout/i.test(\n output,\n );\n}\n\n/**\n * Return the origin URL when the workdir contains this repository, including a\n * legacy checkout whose remote still embeds credentials.\n */\nasync function existingCheckoutRemote(\n sandbox: ExecutableSandbox,\n workdir: string,\n cloneUrl: string,\n): Promise<string | null> {\n const result = await execute(sandbox, 'git', ['-C', workdir, 'remote', 'get-url', 'origin']);\n if (result.exitCode !== 0) return null;\n const url = result.stdout.trim();\n const actual = normalizedRemoteUrl(url);\n const expected = normalizedRemoteUrl(cloneUrl);\n return actual !== null && expected !== null && actual === expected ? url : null;\n}\n\n/** Remove credentials persisted by an older implementation from origin. */\nasync function scrubRemote(\n sandbox: ExecutableSandbox,\n workdir: string,\n repoFullName: string,\n cloneUrl: string = cleanUrl(repoFullName),\n): Promise<void> {\n let failure: string;\n try {\n const result = await execute(sandbox, 'git', ['-C', workdir, 'remote', 'set-url', 'origin', cloneUrl]);\n if (result.exitCode === 0) return;\n failure = result.stderr.trim() || result.stdout.trim();\n } catch (error) {\n failure = error instanceof Error ? error.message : String(error);\n }\n throw new MaterializeError(`Failed to scrub installation token from git remote: ${failure}`, 'pull-failed');\n}\n\n/**\n * Turn a failed git command into an actionable error, detecting the common\n * \"cannot reach github.com\" egress failure.\n */\nfunction classifyGitFailure(\n result: SandboxCommandResult,\n fallback: 'clone-failed' | 'pull-failed' | 'push-failed',\n): MaterializeError {\n const stderr = result.stderr || '';\n if (/could not resolve host|failed to connect|network is unreachable|Connection timed out/i.test(stderr)) {\n return new MaterializeError(\n 'The sandbox could not reach github.com. The sandbox network must allow outbound egress to github.com.',\n 'egress-blocked',\n );\n }\n const verb = fallback === 'clone-failed' ? 'clone' : fallback === 'pull-failed' ? 'pull' : 'push';\n return new MaterializeError(`git ${verb} failed: ${stderr}`, fallback);\n}\n\n// ---------------------------------------------------------------------------\n// Phase 1 — git identity + token-scoped push primitive\n//\n// These helpers let the sandbox author and push commits safely. The install\n// token is short-lived, minted per-operation server-side, and supplied only to\n// the Git process through its environment. It never enters argv or persistent\n// repository configuration.\n// ---------------------------------------------------------------------------\n\nexport { isValidGitRef };\n\n/** Identity used to author commits inside the sandbox. */\nexport interface GitIdentity {\n name?: string | null;\n email?: string | null;\n /** GitHub login, used to derive a stable noreply identity when name/email are absent. */\n login?: string | null;\n}\n\n/**\n * Resolve a concrete `{ name, email }` for git authorship from a possibly-sparse\n * identity. Falls back to a GitHub-style noreply identity so commits are never\n * authored with an empty or host-derived identity.\n */\nexport function resolveGitIdentity(identity: GitIdentity): { name: string; email: string } {\n const login = (identity.login || '').trim();\n const name = (identity.name || '').trim() || login || 'Mastra Code';\n const email =\n (identity.email || '').trim() ||\n (login ? `${login}@users.noreply.github.com` : 'mastra-code@users.noreply.github.com');\n return { name, email };\n}\n\n/** Configure repository-local commit identity through direct Git argv calls. */\nexport async function configureGitIdentity(\n sandbox: ExecutableSandbox,\n workdir: string,\n identity: GitIdentity,\n): Promise<void> {\n const { name, email } = resolveGitIdentity(identity);\n const setName = await execute(sandbox, 'git', ['-C', workdir, 'config', 'user.name', name]);\n if (setName.exitCode !== 0) {\n throw new MaterializeError(`Failed to set git user.name: ${setName.stderr.trim()}`, 'commit-failed');\n }\n const setEmail = await execute(sandbox, 'git', ['-C', workdir, 'config', 'user.email', email]);\n if (setEmail.exitCode !== 0) {\n throw new MaterializeError(`Failed to set git user.email: ${setEmail.stderr.trim()}`, 'commit-failed');\n }\n}\n\nasync function pushAuthenticatedBranch(\n sandbox: ExecutableSandbox,\n workdir: string,\n branch: string,\n cloneUrl: string,\n token: string,\n username: string,\n): Promise<void> {\n const env = gitAuthenticationEnvironment(cloneUrl, token, username, 'push-failed');\n const push = await execute(sandbox, 'git', ['-C', workdir, 'push', '-u', 'origin', branch], { env });\n if (push.exitCode !== 0) throw classifyGitFailure(push, 'push-failed');\n}\n\n/** Whether the checkout has a local `refs/heads/<branch>`. */\nexport async function hasLocalBranch(sandbox: ExecutableSandbox, workdir: string, branch: string): Promise<boolean> {\n const ref = await execute(sandbox, 'git', ['-C', workdir, 'show-ref', '--verify', '--quiet', `refs/heads/${branch}`]);\n if (ref.exitCode === 0) return true;\n if (ref.exitCode === 1) return false;\n throw classifyGitFailure(ref, 'push-failed');\n}\n\n/**\n * Advance the session branch to HEAD and switch to it, carrying over local work.\n * Leave an already-ahead branch and its checkout unchanged; never discard its commits.\n */\nexport async function ensureLocalBranch(sandbox: ExecutableSandbox, workdir: string, branch: string): Promise<void> {\n if (!isValidGitRef(branch)) {\n throw new MaterializeError(`Refusing to materialize: invalid branch name '${branch}'.`, 'push-failed');\n }\n const exists = await hasLocalBranch(sandbox, workdir, branch);\n if (exists) {\n const ref = `refs/heads/${branch}`;\n const tip = await execute(sandbox, 'git', ['-C', workdir, 'rev-parse', '--verify', ref]);\n if (tip.exitCode !== 0) throw classifyGitFailure(tip, 'push-failed');\n const head = await execute(sandbox, 'git', ['-C', workdir, 'rev-parse', '--verify', 'HEAD']);\n if (head.exitCode !== 0) throw classifyGitFailure(head, 'push-failed');\n const oldOid = tip.stdout.trim();\n const headOid = head.stdout.trim();\n const ancestor = await execute(sandbox, 'git', ['-C', workdir, 'merge-base', '--is-ancestor', oldOid, headOid]);\n if (ancestor.exitCode === 1) {\n const ahead = await execute(sandbox, 'git', ['-C', workdir, 'merge-base', '--is-ancestor', headOid, oldOid]);\n if (ahead.exitCode === 0) return;\n if (ahead.exitCode !== 1) throw classifyGitFailure(ahead, 'push-failed');\n throw new MaterializeError(\n `Refusing to move session branch '${branch}': it has diverged from the active HEAD. Check out or merge the session branch before pushing.`,\n 'push-failed',\n );\n }\n if (ancestor.exitCode !== 0) throw classifyGitFailure(ancestor, 'push-failed');\n const current = await execute(sandbox, 'git', ['-C', workdir, 'symbolic-ref', '--quiet', 'HEAD']);\n if (current.exitCode !== 0 && current.exitCode !== 1) throw classifyGitFailure(current, 'push-failed');\n if (current.stdout.trim() === ref) return;\n // update-ref does not enforce worktree ownership, unlike branch -f.\n const worktrees = await execute(sandbox, 'git', ['-C', workdir, 'worktree', 'list', '--porcelain', '-z']);\n if (worktrees.exitCode !== 0) throw classifyGitFailure(worktrees, 'push-failed');\n if (worktrees.stdout.split('\\0').includes(`branch ${ref}`)) {\n throw new MaterializeError(\n `Refusing to move session branch '${branch}': it is checked out in another worktree.`,\n 'push-failed',\n );\n }\n const advanced = await execute(sandbox, 'git', ['-C', workdir, 'update-ref', ref, headOid, oldOid]);\n if (advanced.exitCode !== 0) throw classifyGitFailure(advanced, 'push-failed');\n }\n const switched = await execute(sandbox, 'git', ['-C', workdir, 'switch', ...(exists ? [] : ['-c']), branch]);\n if (switched.exitCode !== 0) throw classifyGitFailure(switched, 'push-failed');\n}\n\n/** Whether `workdir` is a checkout of the repository at `cloneUrl`. */\nexport async function isCheckoutOf(sandbox: ExecutableSandbox, workdir: string, cloneUrl: string): Promise<boolean> {\n return (await existingCheckoutRemote(sandbox, workdir, cloneUrl)) !== null;\n}\n\n/**\n * Push a branch back to GitHub with credentials scoped to the Git process.\n * The token never enters argv, a repository URL, or persistent configuration.\n */\nexport async function pushBranch(\n sandbox: ExecutableSandbox,\n workdir: string,\n branch: string,\n token: string,\n repoFullName: string,\n): Promise<void> {\n if (!isValidGitRef(branch)) {\n throw new MaterializeError(`Refusing to push: invalid branch name '${branch}'.`, 'push-failed');\n }\n if (!/^[\\w.-]+(?:\\/[\\w.-]+)+$/.test(repoFullName)) {\n throw new MaterializeError(`Refusing to push: invalid repo full name '${repoFullName}'.`, 'push-failed');\n }\n await pushAuthenticatedBranch(sandbox, workdir, branch, cleanUrl(repoFullName), token, 'x-access-token');\n}\n\n/**\n * Push the active session branch through the provider-neutral repository\n * access contract without persisting or exposing provider credentials.\n */\nexport async function pushRepositoryBranch(\n sandbox: ExecutableSandbox,\n workdir: string,\n branch: string,\n access: RepositoryAccess,\n repoFullName: string,\n): Promise<void> {\n if (!isValidGitRef(branch)) {\n throw new MaterializeError(`Refusing to push: invalid branch name '${branch}'.`, 'push-failed');\n }\n if (!/^[\\w.-]+(?:\\/[\\w.-]+)+$/.test(repoFullName)) {\n throw new MaterializeError(`Refusing to push: invalid repo full name '${repoFullName}'.`, 'push-failed');\n }\n const authorization = access.authorization;\n if (!authorization?.token) {\n throw new MaterializeError('Repository access did not include push credentials.', 'push-failed');\n }\n await pushAuthenticatedBranch(\n sandbox,\n workdir,\n branch,\n access.cloneUrl,\n authorization.token,\n authorization.username ?? 'x-access-token',\n );\n}\n\nexport interface CommitResult {\n /** True when a commit was created; false when there was nothing to commit. */\n committed: boolean;\n}\n\n/**\n * Stage every change in the working tree and create a commit inside the\n * sandbox. The git identity is configured first so authorship is correct. When\n * there is nothing to commit this is a no-op (`committed: false`) rather than an\n * error, so callers can safely commit-then-push without first diffing.\n *\n * @param sandbox the live sandbox containing the checkout\n * @param workdir the session workdir to commit in\n * @param message the commit message (passed as one argv value)\n * @param identity authorship identity for the commit\n */\nexport async function commitAll(\n sandbox: ExecutableSandbox,\n workdir: string,\n message: string,\n identity: GitIdentity,\n): Promise<CommitResult> {\n await configureGitIdentity(sandbox, workdir, identity);\n\n const add = await execute(sandbox, 'git', ['-C', workdir, 'add', '-A']);\n if (add.exitCode !== 0) {\n throw new MaterializeError(`git add failed: ${add.stderr.trim() || add.stdout.trim()}`, 'commit-failed');\n }\n\n // Nothing staged → nothing to commit. `git diff --cached --quiet` exits 1 when\n // there are staged changes, 0 when the index is clean.\n const staged = await execute(sandbox, 'git', ['-C', workdir, 'diff', '--cached', '--quiet']);\n if (staged.exitCode === 0) {\n return { committed: false };\n }\n\n const commit = await execute(sandbox, 'git', ['-C', workdir, 'commit', '-m', message]);\n if (commit.exitCode !== 0) {\n throw new MaterializeError(`git commit failed: ${commit.stderr.trim() || commit.stdout.trim()}`, 'commit-failed');\n }\n\n return { committed: true };\n}\n\n// ---------------------------------------------------------------------------\n// Phase 2 — setup / teardown lifecycle commands\n//\n// The org-configured setup and teardown shell commands run in the session's\n// materialized workdir. The workdir is always resolved server-side from the\n// live sandbox; client input never reaches a filesystem path.\n// ---------------------------------------------------------------------------\n\n/** Error raised when the org's setup or teardown command fails in the sandbox. */\nexport class SetupCommandError extends Error {\n constructor(\n message: string,\n readonly code: 'setup-failed' | 'teardown-failed',\n ) {\n super(message);\n this.name = 'SetupCommandError';\n }\n}\n\n/**\n * Run the project's setup command (e.g. `pnpm i && pnpm build`) inside the\n * freshly materialized session workdir. Called before the checkout is handed\n * to any agent run so it is ready to build/test. A non-zero exit is a hard\n * error — starting agent work in a half-set-up tree is worse than failing the\n * request.\n *\n * Security model: the command is intentionally arbitrary shell — that is the\n * feature (install deps, build, seed fixtures). It is only configurable by\n * authenticated org members (the settings route is gated by\n * `resolveOrgTenant` + org-scoped project lookup, with length and\n * control-character validation), and it executes exclusively inside the\n * project's isolated sandbox — the same environment where org members already\n * run arbitrary shell via the agent's command tool. It never runs on the web\n * server host, so it grants no privilege beyond what sandbox access already\n * provides.\n *\n * @param sandbox live sandbox containing the checkout\n * @param workdir the server-resolved session workdir the command runs in\n * @param command the org-configured setup shell command\n */\nasync function runLifecycleCommand(\n sandbox: ExecutableSandbox,\n workdir: string,\n command: string,\n options: { phase: 'setup' | 'teardown'; timeoutMs?: number },\n): Promise<void> {\n const result = await sh(sandbox, `cd ${shellQuote(workdir)} && { ${command}\\n}`, {\n phase: `${options.phase} command`,\n ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),\n });\n if (result.exitCode !== 0) {\n const detail = (result.stderr.trim() || result.stdout.trim()).slice(-1800);\n const label = options.phase === 'setup' ? 'Setup' : 'Teardown';\n throw new SetupCommandError(\n `${label} command failed (exit ${result.exitCode}): ${detail}`,\n options.phase === 'setup' ? 'setup-failed' : 'teardown-failed',\n );\n }\n}\n\nexport async function runSetupCommand(sandbox: ExecutableSandbox, workdir: string, command: string): Promise<void> {\n return runLifecycleCommand(sandbox, workdir, command, { phase: 'setup' });\n}\n\n/**\n * Run the repository's best-effort teardown command from the materialized\n * session workdir. Callers own lifecycle policy: this helper reports failures\n * so the retirement coordinator can log them while still continuing with\n * scrub, pooling/destruction, cache invalidation, and row deletion.\n */\nexport async function runTeardownCommand(\n sandbox: ExecutableSandbox,\n workdir: string,\n command: string,\n options: { timeoutMs?: number } = {},\n): Promise<void> {\n return runLifecycleCommand(sandbox, workdir, command, {\n phase: 'teardown',\n ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),\n });\n}\n\nexport interface CreatePullRequestArgs {\n /** Short-lived installation token, injected only into the `gh` process env. */\n token: string;\n /** Base branch the PR merges into. Ref-validated. */\n base: string;\n /** Head branch the PR is opened from. Ref-validated. */\n head: string;\n /** PR title. */\n title: string;\n /** PR body (optional). */\n body?: string;\n}\n\nexport interface CreatePullRequestResult {\n /** The PR URL parsed from `gh pr create` stdout. */\n url: string;\n}\n\n/**\n * Preflight that `gh` is installed in the sandbox. Only called on the PR path so\n * a missing `gh` never blocks clone/open. Surfaces an actionable error naming\n * the sandbox template requirement.\n */\nasync function assertGhAvailable(sandbox: ExecutableSandbox): Promise<void> {\n const version = await execute(sandbox, 'gh', ['--version']);\n if (version.exitCode !== 0) {\n throw new MaterializeError(\n 'The GitHub CLI (gh) is not installed in the sandbox. The sandbox template must include gh to open pull requests.',\n 'gh-missing',\n );\n }\n}\n\n/** Match the first GitHub PR URL in `gh pr create` output. */\nfunction parsePullRequestUrl(stdout: string): string | undefined {\n const match = stdout.match(/https:\\/\\/github\\.com\\/[^\\s]+\\/pull\\/\\d+/);\n return match?.[0];\n}\n\n/**\n * Open a pull request from inside the sandbox via `gh pr create`. The token is\n * passed only through a per-invocation `GH_TOKEN` env scoped to the single `gh`\n * process (never persisted), all arguments are passed as structured argv, and\n * the resulting PR URL is parsed from stdout.\n *\n * @param sandbox live sandbox containing the checkout\n * @param workdir the worktree (or repo) path the PR head branch is checked out in\n */\nexport async function createPullRequest(\n sandbox: ExecutableSandbox,\n workdir: string,\n { token, base, head, title, body }: CreatePullRequestArgs,\n): Promise<CreatePullRequestResult> {\n if (!isValidGitRef(base)) {\n throw new MaterializeError(`Refusing to open PR: invalid base branch '${base}'.`, 'pr-failed');\n }\n if (!isValidGitRef(head)) {\n throw new MaterializeError(`Refusing to open PR: invalid head branch '${head}'.`, 'pr-failed');\n }\n\n await assertGhAvailable(sandbox);\n\n const result = await execute(\n sandbox,\n 'gh',\n ['pr', 'create', '--base', base, '--head', head, '--title', title, '--body', body ?? ''],\n { cwd: workdir, env: { GH_TOKEN: token } },\n );\n if (result.exitCode !== 0) {\n const classified = classifyGitFailure(result, 'push-failed');\n if (classified.code === 'egress-blocked') {\n throw classified;\n }\n throw new MaterializeError(`gh pr create failed: ${result.stderr.trim() || result.stdout.trim()}`, 'pr-failed');\n }\n\n const url = parsePullRequestUrl(result.stdout);\n if (!url) {\n throw new MaterializeError(\n `gh pr create succeeded but no PR URL was found in its output: ${result.stdout.trim()}`,\n 'pr-failed',\n );\n }\n\n return { url };\n}\n"],"mappings":"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"}
|
|
@@ -10,6 +10,7 @@ import { GithubAppIdentity } from '../../github/app-identity.js';
|
|
|
10
10
|
import type { GithubEventRules, GithubRuleOverrides } from '../../github/default-rules.js';
|
|
11
11
|
import type { GithubIntegration, GithubRepositoryPermission, GithubTriageCommentUpsertInput, GithubTriageCommentUpsertResult, RepoSummary } from '../../github/integration.js';
|
|
12
12
|
import type { ReconcileIssueState, ReconcilePullRequestState } from '../../github/rules.js';
|
|
13
|
+
import type { GitIdentity } from '../../github/sandbox.js';
|
|
13
14
|
import type { GithubSubscriptionStorage } from '../../github/subscriptions.js';
|
|
14
15
|
export declare class PlatformGithubIntegration implements FactoryIntegration {
|
|
15
16
|
#private;
|
|
@@ -34,6 +35,8 @@ export declare class PlatformGithubIntegration implements FactoryIntegration {
|
|
|
34
35
|
/** GitHub App slug used to recognize Factory's own webhook writes. */
|
|
35
36
|
slug?: string;
|
|
36
37
|
});
|
|
38
|
+
/** Commit authorship follows the Platform App's authoritative writer identity. */
|
|
39
|
+
get gitIdentity(): GitIdentity | undefined;
|
|
37
40
|
/** GitHub App slug when the deployment explicitly provides it. */
|
|
38
41
|
get slug(): string | undefined;
|
|
39
42
|
isFactoryCommentAuthor(login: string | null | undefined): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/github/integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAKxD,OAAO,KAAK,EAEV,MAAM,EAIP,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EA0BV,cAAc,EACf,MAAM,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+CAA+C,CAAC;AAC9F,OAAO,KAAK,EAEV,0BAA0B,EAC3B,MAAM,iDAAiD,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC9F,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAE3F,OAAO,KAAK,EACV,iBAAiB,EACjB,0BAA0B,EAC1B,8BAA8B,EAC9B,+BAA+B,EAC/B,WAAW,EACZ,MAAM,6BAA6B,CAAC;AAKrC,OAAO,KAAK,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/github/integration.ts"],"names":[],"mappings":"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"}
|
|
@@ -415,6 +415,11 @@ var PlatformGithubIntegration = class {
|
|
|
415
415
|
this.#pullRequestReconcileIntervalMs = reconcileInterval(process.env.MASTRACODE_PLATFORM_GITHUB_PR_RECONCILE_INTERVAL_MS) ?? legacyReconcileIntervalMs;
|
|
416
416
|
this.#issueReconcileIntervalMs = reconcileInterval(process.env.MASTRACODE_PLATFORM_GITHUB_ISSUE_RECONCILE_INTERVAL_MS) ?? legacyReconcileIntervalMs;
|
|
417
417
|
}
|
|
418
|
+
/** Commit authorship follows the Platform App's authoritative writer identity. */
|
|
419
|
+
get gitIdentity() {
|
|
420
|
+
const login = this.identity.login;
|
|
421
|
+
return login ? { login } : void 0;
|
|
422
|
+
}
|
|
418
423
|
/** GitHub App slug when the deployment explicitly provides it. */
|
|
419
424
|
get slug() {
|
|
420
425
|
return this.#slug;
|