@mastra/factory 0.12.0-alpha.9 → 0.12.0

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.
Files changed (88) hide show
  1. package/README.md +35 -10
  2. package/dist/factory.d.ts +8 -0
  3. package/dist/factory.d.ts.map +1 -1
  4. package/dist/factory.js +4 -3
  5. package/dist/factory.js.map +1 -1
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/integrations/github/acceptance-labels.d.ts +17 -0
  9. package/dist/integrations/github/acceptance-labels.d.ts.map +1 -0
  10. package/dist/integrations/github/acceptance-labels.js +55 -0
  11. package/dist/integrations/github/acceptance-labels.js.map +1 -0
  12. package/dist/integrations/github/integration.d.ts.map +1 -1
  13. package/dist/integrations/github/integration.js +47 -10
  14. package/dist/integrations/github/integration.js.map +1 -1
  15. package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
  16. package/dist/integrations/github/reconcile-worker.js +2 -2
  17. package/dist/integrations/github/reconcile-worker.js.map +1 -1
  18. package/dist/integrations/github/sandbox.d.ts +10 -13
  19. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  20. package/dist/integrations/github/sandbox.js +74 -65
  21. package/dist/integrations/github/sandbox.js.map +1 -1
  22. package/dist/integrations/github/settle-or-abort.d.ts +7 -0
  23. package/dist/integrations/github/settle-or-abort.d.ts.map +1 -0
  24. package/dist/integrations/github/settle-or-abort.js +25 -0
  25. package/dist/integrations/github/settle-or-abort.js.map +1 -0
  26. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
  27. package/dist/integrations/platform/github/event-worker.js +3 -3
  28. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  29. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  30. package/dist/integrations/platform/github/integration.js +37 -5
  31. package/dist/integrations/platform/github/integration.js.map +1 -1
  32. package/dist/packages/_internals/workspace/dist/index.js +35 -0
  33. package/dist/packages/_internals/workspace/dist/index.js.map +1 -0
  34. package/dist/routes/config.d.ts.map +1 -1
  35. package/dist/routes/config.js +6 -0
  36. package/dist/routes/config.js.map +1 -1
  37. package/dist/routes/oauth.d.ts +3 -0
  38. package/dist/routes/oauth.d.ts.map +1 -1
  39. package/dist/routes/oauth.js +10 -2
  40. package/dist/routes/oauth.js.map +1 -1
  41. package/dist/routes/om-seed.d.ts +22 -0
  42. package/dist/routes/om-seed.d.ts.map +1 -0
  43. package/dist/routes/om-seed.js +40 -0
  44. package/dist/routes/om-seed.js.map +1 -0
  45. package/dist/routes/surface.d.ts +1 -1
  46. package/dist/routes/surface.d.ts.map +1 -1
  47. package/dist/routes/surface.js +4 -1
  48. package/dist/routes/surface.js.map +1 -1
  49. package/dist/routes/work-items.d.ts.map +1 -1
  50. package/dist/routes/work-items.js +7 -0
  51. package/dist/routes/work-items.js.map +1 -1
  52. package/dist/rules/defaults.d.ts.map +1 -1
  53. package/dist/rules/defaults.js +3 -0
  54. package/dist/rules/defaults.js.map +1 -1
  55. package/dist/rules/dispatcher.d.ts.map +1 -1
  56. package/dist/rules/dispatcher.js +16 -1
  57. package/dist/rules/dispatcher.js.map +1 -1
  58. package/dist/rules/tools.d.ts.map +1 -1
  59. package/dist/rules/tools.js +1 -23
  60. package/dist/rules/tools.js.map +1 -1
  61. package/dist/rules/transition-service.d.ts +12 -1
  62. package/dist/rules/transition-service.d.ts.map +1 -1
  63. package/dist/rules/transition-service.js +28 -3
  64. package/dist/rules/transition-service.js.map +1 -1
  65. package/dist/sandbox/session-sandbox.d.ts +22 -8
  66. package/dist/sandbox/session-sandbox.d.ts.map +1 -1
  67. package/dist/sandbox/session-sandbox.js +36 -37
  68. package/dist/sandbox/session-sandbox.js.map +1 -1
  69. package/dist/session/factory-session.js.map +1 -1
  70. package/dist/session/memory-settings-hydration.d.ts +3 -3
  71. package/dist/session/memory-settings-hydration.js +3 -3
  72. package/dist/session/memory-settings-hydration.js.map +1 -1
  73. package/dist/session/org-seed.d.ts +5 -5
  74. package/dist/session/org-seed.js +2 -2
  75. package/dist/session/org-seed.js.map +1 -1
  76. package/dist/storage/domains/work-items/base.d.ts +8 -0
  77. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  78. package/dist/storage/domains/work-items/base.js +10 -1
  79. package/dist/storage/domains/work-items/base.js.map +1 -1
  80. package/dist/workspace.d.ts +8 -0
  81. package/dist/workspace.d.ts.map +1 -1
  82. package/dist/workspace.js +21 -6
  83. package/dist/workspace.js.map +1 -1
  84. package/package.json +11 -10
  85. package/dist/session/checkpoint-capture.d.ts +0 -18
  86. package/dist/session/checkpoint-capture.d.ts.map +0 -1
  87. package/dist/session/checkpoint-capture.js +0 -27
  88. package/dist/session/checkpoint-capture.js.map +0 -1
@@ -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)` runs `git clone` (first open) or `git pull`\n * (re-open) inside the sandbox, using a short-lived installation token that is\n * scrubbed from the git remote afterwards so it never persists in the VM.\n *\n * This module owns everything git/GitHub: clone/pull, commit/push, setup/teardown commands,\n * and `gh pr create`. Workdir layout lives in `../sandbox/workdir`.\n */\n\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\n/**\n * A thrown transport-level failure that is worth retrying: remote sandbox\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`, bounded by a hang guard.\n * Transient transport-level 5xx failures (proxy hiccups while the VM boots)\n * are retried with a short backoff; every script routed through here is safe\n * to re-run. Hang-guard timeouts are NOT retried — the budget applies to the\n * command as a whole.\n */\nexport async function sh(\n sandbox: ExecutableSandbox,\n script: string,\n options: ShOptions = {},\n): Promise<SandboxCommandResult> {\n // One budget for the command as a whole: each attempt only gets the time\n // remaining, so transport retries can never multiply the hang guard.\n const deadlineMs = Date.now() + (options.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS);\n for (let attempt = 0; ; attempt++) {\n try {\n return await shOnce(sandbox, script, { ...options, timeoutMs: Math.max(deadlineMs - Date.now(), 1) });\n } catch (error) {\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 `sh -c` execution attempt, bounded by the hang guard. */\nasync function shOnce(\n sandbox: ExecutableSandbox,\n script: string,\n options: ShOptions,\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 // Forward the budget to the provider too so it can terminate the wedged\n // process; the race stays as the outer guard for providers that ignore it.\n return await Promise.race([sandbox.executeCommand('sh', ['-c', script], { timeout: timeoutMs }), hangGuard]);\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 script: string,\n options: ShOptions & { beforeRetry?: (attempt: number) => Promise<void> } = {},\n): Promise<SandboxCommandResult> {\n const { beforeRetry, ...shOptions } = options;\n const deadlineMs = Date.now() + (shOptions.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS);\n for (let attempt = 0; ; attempt++) {\n const result = await sh(sandbox, script, {\n ...shOptions,\n timeoutMs: Math.max(deadlineMs - Date.now(), 1),\n });\n if (result.exitCode === 0 || attempt >= GIT_TRANSFER_RETRIES || !isTransientGitFailure(result)) return result;\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\n/**\n * Build the token-auth clone/pull URL for a repo. The token lives only inside\n * this URL and is scrubbed from the remote after the operation.\n */\nfunction tokenUrl(repoFullName: string, token: string): string {\n return `https://x-access-token:${token}@github.com/${repoFullName}.git`;\n}\n\nfunction cleanUrl(repoFullName: string): string {\n return `https://github.com/${repoFullName}.git`;\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}\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\n/**\n * Materialize the repo inside the user's sandbox. Clones on first open, pulls on\n * re-open. Always scrubs the install token from the remote afterwards and sets\n * `materialized_at` on the 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 sh(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 const authUrl = tokenUrl(repo, token);\n\n // The DB's `materializedAt` can drift from disk in both directions: a fresh\n // binding row over an already-populated workdir (local dev DB resets,\n // repaired rows, earlier flows) must pull instead of failing `git clone` on\n // the non-empty directory, and a stale `materializedAt` over an empty\n // sandbox (an expired/recreated VM whose disk was wiped) must re-clone\n // instead of running `git -C <workdir>` against a directory that no longer\n // exists. Disk is the source of truth: detect the checkout instead of\n // trusting the row.\n const alreadyMaterialized = await hasExistingCheckout(sandbox, workdir, repo);\n\n let tokenInRemote = false;\n try {\n if (!alreadyMaterialized) {\n // 2a. First open: shallow-clone the default branch into the workdir. A\n // shallow single-branch clone is dramatically faster for large repos; the\n // later re-open uses `git pull --ff-only`, which works on shallow clones.\n // The workdir holds no usable checkout of this repo, but it may not be\n // empty: a checkpoint seed or a clone that died partway (a crashed or\n // OOM-killed server) leaves a partial tree behind, and `git clone`\n // refuses a non-empty destination with a non-retryable fatal. Nothing\n // here is recoverable — `hasExistingCheckout` already ruled out a\n // checkout of this repo — so clear its contents before cloning, exactly\n // as the retry path does. Keep the workdir itself because LocalSandbox\n // runs commands with this directory as the child process cwd.\n await sh(\n sandbox,\n `mkdir -p ${shellQuote(workdir)} && find ${shellQuote(workdir)} -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +`,\n );\n const clone = await gitTransfer(\n sandbox,\n `git clone --depth=1 --single-branch --branch ${shellQuote(repoInfo.defaultBranch)} ${shellQuote(authUrl)} ${shellQuote(workdir)}`,\n {\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 sh(\n sandbox,\n `mkdir -p ${shellQuote(workdir)} && find ${shellQuote(workdir)} -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +`,\n );\n },\n },\n );\n if (clone.exitCode !== 0) {\n // git can fail after creating the checkout (\"Clone succeeded, but\n // checkout failed\") with the tokenized origin persisted — probe the\n // disk instead of assuming the failed clone left nothing behind.\n tokenInRemote = await hasGitDir(sandbox, workdir);\n throw classifyGitFailure(clone, 'clone-failed');\n }\n tokenInRemote = true;\n } else {\n // 2b. Re-open: refresh remote to the token URL and fast-forward pull.\n const setUrl = await sh(sandbox, `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(authUrl)}`);\n if (setUrl.exitCode !== 0) {\n throw new MaterializeError(`Failed to set git remote: ${setUrl.stderr}`, 'pull-failed');\n }\n tokenInRemote = true;\n const pull = await gitTransfer(sandbox, `git -C ${shellQuote(workdir)} pull --ff-only`, {\n phase: 'repository pull',\n });\n if (pull.exitCode !== 0) {\n if (!isBenignNonFastForward(pull)) {\n throw classifyGitFailure(pull, 'pull-failed');\n }\n // The workdir was left on a session's working branch that can't be\n // fast-forwarded (diverged from upstream, no upstream, or detached\n // HEAD), or its configured upstream ref was deleted after merge.\n // That checkout still holds usable work — never rebase or reset it\n // here. Leave it as-is and let the session reconcile with the remote\n // itself.\n }\n }\n } catch (primary) {\n // 3a. The clone/pull failed — still scrub the token from the VM's git\n // config. The scrub must never hide the actionable failure, but once the\n // token reached the remote its own failure can't stay silent either:\n // report both, primary cause and classification first.\n throw await scrubbedFailure(sandbox, workdir, repo, tokenInRemote, primary, 'pull-failed');\n }\n\n // 3b. Success — the token is in the remote and the workdir has a `.git`, so\n // a failed scrub means the token may still be persisted: surface it.\n await scrubRemote(sandbox, workdir, repo, tokenInRemote);\n\n // 4. Mark materialized.\n await storage.markMaterialized({ id: sandboxRow.id });\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: { branch: string; baseBranch: string; token: string; repoFullName: string },\n): Promise<void> {\n return timedPhase('workspace.checkout', () => checkoutSessionBranchImpl(sandbox, workdir, options));\n}\n\nasync function checkoutSessionBranchImpl(\n sandbox: ExecutableSandbox,\n workdir: string,\n {\n branch,\n baseBranch,\n token,\n repoFullName,\n }: { branch: string; baseBranch: string; token: string; repoFullName: string },\n): Promise<void> {\n if (!isValidGitRef(branch) || !isValidGitRef(baseBranch)) {\n throw new MaterializeError('Refusing to create a session from an invalid branch name.', 'clone-failed');\n }\n\n const current = await sh(sandbox, `git -C ${shellQuote(workdir)} branch --show-current`);\n if (current.exitCode === 0 && current.stdout.trim() === branch) return;\n\n const local = await sh(\n sandbox,\n `git -C ${shellQuote(workdir)} show-ref --verify --quiet refs/heads/${shellQuote(branch)}`,\n );\n if (local.exitCode === 0) {\n const checkout = await sh(sandbox, `git -C ${shellQuote(workdir)} checkout ${shellQuote(branch)}`);\n if (checkout.exitCode !== 0) {\n // The session's agent may have switched branches itself (e.g. `gh pr\n // checkout`) and left uncommitted work in the tree. Git refuses to\n // switch back over those files — that work must win. The checkout is\n // intact and usable on its current branch; keep it as-is rather than\n // fail the workspace open, and never reset or stash to force the\n // switch through.\n if (isBlockedByLocalWork(checkout)) return;\n throw classifyGitFailure(checkout, 'clone-failed');\n }\n return;\n }\n\n const authUrl = tokenUrl(repoFullName, token);\n try {\n const setUrl = await sh(sandbox, `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(authUrl)}`);\n if (setUrl.exitCode !== 0) throw classifyGitFailure(setUrl, 'pull-failed');\n const fetch = await sh(\n sandbox,\n `git -C ${shellQuote(workdir)} fetch origin ${shellQuote(baseBranch)} && git -C ${shellQuote(workdir)} checkout -b ${shellQuote(branch)} FETCH_HEAD`,\n { timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS, phase: 'branch checkout' },\n );\n if (fetch.exitCode !== 0) {\n // Same rule as above: uncommitted work in the tree blocks the switch\n // to the new branch. Leave the checkout on its current branch.\n if (isBlockedByLocalWork(fetch)) return;\n if (!isBranchCollision(fetch)) throw classifyGitFailure(fetch, 'clone-failed');\n // The branch exists even though the show-ref probe missed it: either a\n // concurrent materialization of this session created it between the\n // probe and `checkout -b` (adopt it), or a reused sandbox carries a\n // broken loose ref the probe cannot resolve (replace it and retry —\n // \"already exists\" means the fetch half succeeded, so FETCH_HEAD is\n // set).\n const adopt = await sh(sandbox, `git -C ${shellQuote(workdir)} checkout ${shellQuote(branch)}`);\n if (adopt.exitCode === 0 || isBlockedByLocalWork(adopt)) return;\n const drop = await sh(\n sandbox,\n // `--no-deref` so a broken symref is deleted itself instead of git\n // following it to some other branch. `update-ref -d` can still refuse\n // a broken ref; fall back to removing the loose ref file (branch\n // passed isValidGitRef, so the interpolation inside the double quotes\n // is inert).\n `git -C ${shellQuote(workdir)} update-ref --no-deref -d refs/heads/${shellQuote(branch)} || rm -f -- \"$(git -C ${shellQuote(workdir)} rev-parse --absolute-git-dir)/refs/heads/${branch}\"`,\n );\n if (drop.exitCode !== 0) throw classifyGitFailure(fetch, 'clone-failed');\n const retry = await sh(sandbox, `git -C ${shellQuote(workdir)} checkout -b ${shellQuote(branch)} FETCH_HEAD`, {\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 } finally {\n await sh(sandbox, `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(cleanUrl(repoFullName))}`);\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 * True when the workdir already holds a git checkout whose `origin` points at\n * this exact repo. Matches both the clean and token-auth URL forms; any other\n * remote (or no git dir at all) falls back to the clone path.\n */\nexport async function hasExistingCheckout(\n sandbox: ExecutableSandbox,\n workdir: string,\n repoFullName: string,\n): Promise<boolean> {\n const result = await sh(sandbox, `git -C ${shellQuote(workdir)} remote get-url origin`);\n if (result.exitCode !== 0) return false;\n const url = result.stdout.trim().toLowerCase();\n const suffix = `github.com/${repoFullName.toLowerCase()}`;\n return url.endsWith(`${suffix}.git`) || url.endsWith(suffix);\n}\n\n/** Probed without `git -C` so a missing workdir returns false instead of throwing. */\nasync function hasGitDir(sandbox: ExecutableSandbox, workdir: string): Promise<boolean> {\n const probe = await sh(sandbox, `test -d ${shellQuote(`${workdir}/.git`)}`).catch(() => null);\n return probe?.exitCode === 0;\n}\n\n/**\n * Reset the git remote back to the tokenless URL. Strict when the token\n * reached the remote: any failure — a non-zero exit or a provider throw —\n * means the token may still be persisted, so it is thrown for the caller to\n * surface. Best-effort when it never did: the workdir may not exist (e.g. a\n * failed clone), which makes providers that spawn with `cwd` throw rather\n * than return a non-zero exit code; both outcomes are tolerated so neither\n * masks the primary failure.\n */\nasync function scrubRemote(\n sandbox: ExecutableSandbox,\n workdir: string,\n repoFullName: string,\n tokenInRemote: boolean,\n): Promise<void> {\n const scrub = `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(cleanUrl(repoFullName))}`;\n if (!tokenInRemote) {\n await sh(sandbox, scrub).catch(() => undefined);\n return;\n }\n let failure: string;\n try {\n const result = await sh(sandbox, scrub);\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 * Scrub after `primary` already failed and return the error to throw. A failed\n * scrub is appended to `primary` rather than replacing it, so the caller gets\n * the error it would have had without the scrub — same class, same `code` —\n * carrying the leaked-token warning in its message.\n */\nasync function scrubbedFailure(\n sandbox: ExecutableSandbox,\n workdir: string,\n repoFullName: string,\n tokenInRemote: boolean,\n primary: unknown,\n fallback: MaterializeError['code'],\n): Promise<unknown> {\n try {\n await scrubRemote(sandbox, workdir, repoFullName, tokenInRemote);\n return primary;\n } catch (scrubError) {\n const scrubMessage = scrubError instanceof Error ? scrubError.message : String(scrubError);\n if (!(primary instanceof Error)) {\n return new MaterializeError(`${String(primary)} — additionally: ${scrubMessage}`, fallback);\n }\n primary.message = `${primary.message} — additionally: ${scrubMessage}`;\n return primary;\n }\n}\n\n/**\n * True when a failed `git pull --ff-only` on re-open just means the current\n * branch can't be fast-forwarded — not that anything is broken. The shared\n * workdir is routinely left on a session's working branch, which may have\n * local commits diverging from upstream, no upstream at all (session branches\n * are created from `FETCH_HEAD`), or a detached HEAD. A checkout can also\n * hold uncommitted or untracked files (a build or script run left residue,\n * or an older session worked directly in the shared checkout), which makes\n * git refuse the merge outright. A checkout carrying `pull.rebase` in its git\n * config refuses for the same reason but says so in rebase's words instead of\n * merge's. After a PR merges with branch auto-delete, the configured upstream\n * ref may also be gone (`no such ref was fetched` / `couldn't find remote ref`);\n * there is nothing to pull and the checkout is still intact. In all of these\n * cases materialization must keep it as-is rather than fail the workspace open\n * — and must never discard the local state to force the pull through.\n */\nfunction isDeletedUpstreamRef(result: SandboxCommandResult): boolean {\n const output = `${result.stderr || ''}\\n${result.stdout || ''}`;\n return /no such ref was fetched|couldn't find remote ref/i.test(output);\n}\n\nfunction isBenignNonFastForward(result: SandboxCommandResult): boolean {\n const output = `${result.stderr || ''}\\n${result.stdout || ''}`;\n return (\n isDeletedUpstreamRef(result) ||\n /Not possible to fast-forward|Diverging branches can't be fast-forwarded|no tracking information for the current branch|You are not currently on a branch|Your local changes to the following files would be overwritten by merge|untracked working tree files would be overwritten by merge|cannot pull with rebase|cannot rebase: You have unstaged changes|Your index contains uncommitted changes/i.test(\n output,\n )\n );\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, injected only into\n// the temporary remote URL inside the sandbox, and always scrubbed afterwards\n// so it never persists in `.git/config`.\n// ---------------------------------------------------------------------------\n\n/**\n * Validate a git ref (branch) name. Server-side defense-in-depth: only allow a\n * conservative character set so a branch can never be built into a shell\n * command in a way that escapes quoting. Mirrors the route-layer check.\n */\nexport function isValidGitRef(value: unknown): value is string {\n return (\n typeof value === 'string' &&\n value.length > 0 &&\n value.length <= 255 &&\n // Reject leading-dash refs (e.g. `--mirror`) so the value can never be\n // parsed as a git option when interpolated into a command.\n !value.startsWith('-') &&\n /^[A-Za-z0-9_./-]+$/.test(value)\n );\n}\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/**\n * Configure `user.name` / `user.email` for the given repo working tree inside\n * the sandbox so commits are authored correctly. Values are shell-quoted.\n */\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 sh(sandbox, `git -C ${shellQuote(workdir)} config user.name ${shellQuote(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 sh(sandbox, `git -C ${shellQuote(workdir)} config user.email ${shellQuote(email)}`);\n if (setEmail.exitCode !== 0) {\n throw new MaterializeError(`Failed to set git user.email: ${setEmail.stderr.trim()}`, 'commit-failed');\n }\n}\n\n/**\n * Temporarily rewrite `origin` to a tokenized URL, run `fn` (e.g. a push), and\n * **always** scrub the remote back to the tokenless URL afterwards. The token\n * therefore only ever lives in the remote URL for the duration of the\n * operation and is never left in the VM's git config.\n *\n * Once the tokenized URL is installed a failed scrub may leave the token\n * persisted, so it is always surfaced: on its own after a successful `fn`,\n * appended to `fn`'s own error otherwise — `fn`'s error is never replaced.\n * Only a failed set-url (the token never reached the remote) downgrades the\n * scrub to best-effort.\n */\nexport async function withInstallToken<T>(\n sandbox: ExecutableSandbox,\n workdir: string,\n repoFullName: string,\n token: string,\n fn: () => Promise<T>,\n): Promise<T> {\n if (!/^[\\w.-]+\\/[\\w.-]+$/.test(repoFullName)) {\n throw new MaterializeError(`Refusing to push: invalid repo full name '${repoFullName}'.`, 'push-failed');\n }\n\n const setUrl = await sh(\n sandbox,\n `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(tokenUrl(repoFullName, token))}`,\n );\n if (setUrl.exitCode !== 0) {\n // Best-effort scrub even though set-url failed, then surface the failure.\n await scrubRemote(sandbox, workdir, repoFullName, false);\n throw new MaterializeError(`Failed to set git remote: ${setUrl.stderr.trim()}`, 'push-failed');\n }\n\n let result: T;\n try {\n result = await fn();\n } catch (primary) {\n throw await scrubbedFailure(sandbox, workdir, repoFullName, true, primary, 'push-failed');\n }\n // Restore the tokenless remote. The workdir has a `.git` (we just rewrote\n // its remote) so a scrub failure means the token may still persist — surface it.\n await scrubRemote(sandbox, workdir, repoFullName, true);\n return result;\n}\n\n/**\n * Push a branch back to GitHub from inside the sandbox using a short-lived\n * installation token. The branch is ref-validated, the token is injected only\n * into the remote URL via `withInstallToken`, and egress failures are\n * classified into actionable errors.\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\n await withInstallToken(sandbox, workdir, repoFullName, token, async () => {\n const push = await sh(sandbox, `git -C ${shellQuote(workdir)} push -u origin ${shellQuote(branch)}`);\n if (push.exitCode !== 0) {\n throw classifyGitFailure(push, 'push-failed');\n }\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 (quoted; arbitrary text is safe)\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 sh(sandbox, `git -C ${shellQuote(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 sh(sandbox, `git -C ${shellQuote(workdir)} diff --cached --quiet`);\n if (staged.exitCode === 0) {\n return { committed: false };\n }\n\n const commit = await sh(sandbox, `git -C ${shellQuote(workdir)} commit -m ${shellQuote(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(\n sandbox: ExecutableSandbox,\n workdir: string,\n command: string,\n): 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 sh(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 shell-quoted, and the resulting\n * 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 // GH_TOKEN is prefixed inline so it is exported only to the single `gh`\n // process and never to the wider shell session, git config, or VM env. `gh`\n // is run from inside the checkout so it targets the correct repo/head branch.\n const ghCommand = [\n `GH_TOKEN=${shellQuote(token)} gh pr create`,\n `--base ${shellQuote(base)}`,\n `--head ${shellQuote(head)}`,\n `--title ${shellQuote(title)}`,\n `--body ${shellQuote(body ?? '')}`,\n ].join(' ');\n const script = `cd ${shellQuote(workdir)} && ${ghCommand}`;\n\n const result = await sh(sandbox, script);\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), using a short-lived\n * installation token that is scrubbed from the git remote afterwards so it\n * never persists in the VM. A checkout that is already there, from a repo\n * template image or an earlier start, is left 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 { repoCloneCommand } from '@internal/workspace';\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\n/**\n * A thrown transport-level failure that is worth retrying: remote sandbox\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`, bounded by a hang guard.\n * Transient transport-level 5xx failures (proxy hiccups while the VM boots)\n * are retried with a short backoff; every script routed through here is safe\n * to re-run. Hang-guard timeouts are NOT retried — the budget applies to the\n * command as a whole.\n */\nexport async function sh(\n sandbox: ExecutableSandbox,\n script: string,\n options: ShOptions = {},\n): Promise<SandboxCommandResult> {\n // One budget for the command as a whole: each attempt only gets the time\n // remaining, so transport retries can never multiply the hang guard.\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 shOnce(sandbox, script, { ...options, timeoutMs: Math.max(deadlineMs - Date.now(), 1) });\n // Phased commands are the session start path; report each so a slow\n // start names the command that took the time rather than the phase.\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 `sh -c` execution attempt, bounded by the hang guard. */\nasync function shOnce(\n sandbox: ExecutableSandbox,\n script: string,\n options: ShOptions,\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 // Forward the budget to the provider too so it can terminate the wedged\n // process; the race stays as the outer guard for providers that ignore it.\n return await Promise.race([sandbox.executeCommand('sh', ['-c', script], { timeout: timeoutMs }), hangGuard]);\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 script: string,\n options: ShOptions & { beforeRetry?: (attempt: number) => Promise<void> } = {},\n): Promise<SandboxCommandResult> {\n const { beforeRetry, ...shOptions } = options;\n const deadlineMs = Date.now() + (shOptions.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS);\n for (let attempt = 0; ; attempt++) {\n const result = await sh(sandbox, script, {\n ...shOptions,\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(`[factory:timing] git ${shOptions.phase ?? 'transfer'} retrying after attempt ${attempt + 1}\\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\n/**\n * Build the token-auth clone/pull URL for a repo. The token lives only inside\n * this URL and is scrubbed from the remote after the operation.\n */\nfunction tokenUrl(repoFullName: string, token: string): string {\n return `https://x-access-token:${token}@github.com/${repoFullName}.git`;\n}\n\nfunction cleanUrl(repoFullName: string): string {\n return `https://github.com/${repoFullName}.git`;\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}\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\n/**\n * Materialize the repo inside the user's sandbox: clone when no checkout of\n * this repo exists, otherwise nothing. Scrubs the install token from the\n * remote after a clone and sets `materialized_at` on the per-user sandbox\n * 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 sh(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 existing = await existingCheckoutRemote(sandbox, workdir, repo);\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, true);\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 await sh(\n sandbox,\n `mkdir -p ${shellQuote(workdir)} && find ${shellQuote(workdir)} -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +`,\n );\n let tokenInRemote = false;\n try {\n const clone = await gitTransfer(\n sandbox,\n repoCloneCommand({ cloneUrl: tokenUrl(repo, token), destination: workdir, branch: repoInfo.defaultBranch }),\n {\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 sh(\n sandbox,\n `mkdir -p ${shellQuote(workdir)} && find ${shellQuote(workdir)} -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +`,\n );\n },\n },\n );\n if (clone.exitCode !== 0) {\n // git can fail after creating the checkout (\"Clone succeeded, but\n // checkout failed\") with the tokenized origin persisted: probe the\n // disk instead of assuming the failed clone left nothing behind.\n tokenInRemote = await hasGitDir(sandbox, workdir);\n throw classifyGitFailure(clone, 'clone-failed');\n }\n tokenInRemote = true;\n } catch (primary) {\n // 3a. The clone failed: still scrub the token from the VM's git config.\n // The scrub must never hide the actionable failure, but once the token\n // reached the remote its own failure can't stay silent either: report\n // both, primary cause and classification first.\n throw await scrubbedFailure(sandbox, workdir, repo, tokenInRemote, primary, 'clone-failed');\n }\n\n // 3b. Success: the token is in the remote and the workdir has a `.git`, so\n // a failed scrub means the token may still be persisted: surface it.\n await scrubRemote(sandbox, workdir, repo, tokenInRemote);\n }\n\n // 4. Mark materialized.\n await storage.markMaterialized({ id: sandboxRow.id });\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: { branch: string; baseBranch: string; token: string; repoFullName: string },\n): Promise<void> {\n return timedPhase('workspace.checkout', () => checkoutSessionBranchImpl(sandbox, workdir, options));\n}\n\nasync function checkoutSessionBranchImpl(\n sandbox: ExecutableSandbox,\n workdir: string,\n {\n branch,\n baseBranch,\n token,\n repoFullName,\n }: { branch: string; baseBranch: string; token: string; repoFullName: string },\n): Promise<void> {\n if (!isValidGitRef(branch) || !isValidGitRef(baseBranch)) {\n throw new MaterializeError('Refusing to create a session from an invalid branch name.', 'clone-failed');\n }\n\n const current = await sh(sandbox, `git -C ${shellQuote(workdir)} branch --show-current`);\n if (current.exitCode === 0 && current.stdout.trim() === branch) return;\n\n const local = await sh(\n sandbox,\n `git -C ${shellQuote(workdir)} show-ref --verify --quiet refs/heads/${shellQuote(branch)}`,\n );\n if (local.exitCode === 0) {\n const checkout = await sh(sandbox, `git -C ${shellQuote(workdir)} checkout ${shellQuote(branch)}`);\n if (checkout.exitCode !== 0) {\n // The session's agent may have switched branches itself (e.g. `gh pr\n // checkout`) and left uncommitted work in the tree. Git refuses to\n // switch back over those files — that work must win. The checkout is\n // intact and usable on its current branch; keep it as-is rather than\n // fail the workspace open, and never reset or stash to force the\n // switch through.\n if (isBlockedByLocalWork(checkout)) return;\n throw classifyGitFailure(checkout, 'clone-failed');\n }\n return;\n }\n\n const authUrl = tokenUrl(repoFullName, token);\n try {\n const setUrl = await sh(sandbox, `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(authUrl)}`, {\n phase: 'branch checkout remote',\n });\n if (setUrl.exitCode !== 0) throw classifyGitFailure(setUrl, 'pull-failed');\n const fetch = await sh(\n sandbox,\n `git -C ${shellQuote(workdir)} fetch origin ${shellQuote(baseBranch)} && git -C ${shellQuote(workdir)} checkout -b ${shellQuote(branch)} FETCH_HEAD`,\n { timeoutMs: CHECKOUT_COMMAND_TIMEOUT_MS, phase: 'branch checkout' },\n );\n if (fetch.exitCode !== 0) {\n // Same rule as above: uncommitted work in the tree blocks the switch\n // to the new branch. Leave the checkout on its current branch.\n if (isBlockedByLocalWork(fetch)) return;\n if (!isBranchCollision(fetch)) throw classifyGitFailure(fetch, 'clone-failed');\n // The branch exists even though the show-ref probe missed it: either a\n // concurrent materialization of this session created it between the\n // probe and `checkout -b` (adopt it), or a reused sandbox carries a\n // broken loose ref the probe cannot resolve (replace it and retry —\n // \"already exists\" means the fetch half succeeded, so FETCH_HEAD is\n // set).\n const adopt = await sh(sandbox, `git -C ${shellQuote(workdir)} checkout ${shellQuote(branch)}`);\n if (adopt.exitCode === 0 || isBlockedByLocalWork(adopt)) return;\n const drop = await sh(\n sandbox,\n // `--no-deref` so a broken symref is deleted itself instead of git\n // following it to some other branch. `update-ref -d` can still refuse\n // a broken ref; fall back to removing the loose ref file (branch\n // passed isValidGitRef, so the interpolation inside the double quotes\n // is inert).\n `git -C ${shellQuote(workdir)} update-ref --no-deref -d refs/heads/${shellQuote(branch)} || rm -f -- \"$(git -C ${shellQuote(workdir)} rev-parse --absolute-git-dir)/refs/heads/${branch}\"`,\n );\n if (drop.exitCode !== 0) throw classifyGitFailure(fetch, 'clone-failed');\n const retry = await sh(sandbox, `git -C ${shellQuote(workdir)} checkout -b ${shellQuote(branch)} FETCH_HEAD`, {\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 } finally {\n await sh(sandbox, `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(cleanUrl(repoFullName))}`);\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 * The `origin` URL of a checkout of this exact repo in the workdir, or null\n * when there is none. Matches both the clean and token-auth URL forms; any\n * other remote (or no git dir at all) sends materialize down the clone path.\n */\nasync function existingCheckoutRemote(\n sandbox: ExecutableSandbox,\n workdir: string,\n repoFullName: string,\n): Promise<string | null> {\n const result = await sh(sandbox, `git -C ${shellQuote(workdir)} remote get-url origin`);\n if (result.exitCode !== 0) return null;\n const url = result.stdout.trim();\n return isRemoteForRepo(url, repoFullName) ? url : null;\n}\n\n/** True only for `https://github.com/<repo>[.git]`, with or without embedded credentials. */\nfunction isRemoteForRepo(url: string, repoFullName: string): boolean {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return false;\n }\n if (parsed.protocol !== 'https:' || parsed.hostname.toLowerCase() !== 'github.com') return false;\n if (parsed.port !== '' || parsed.search !== '' || parsed.hash !== '') return false;\n return parsed.pathname.replace(/\\.git$/, '').toLowerCase() === `/${repoFullName.toLowerCase()}`;\n}\n\n/** Probed without `git -C` so a missing workdir returns false instead of throwing. */\nasync function hasGitDir(sandbox: ExecutableSandbox, workdir: string): Promise<boolean> {\n const probe = await sh(sandbox, `test -d ${shellQuote(`${workdir}/.git`)}`).catch(() => null);\n return probe?.exitCode === 0;\n}\n\n/**\n * Reset the git remote back to the tokenless URL. Strict when the token\n * reached the remote: any failure — a non-zero exit or a provider throw —\n * means the token may still be persisted, so it is thrown for the caller to\n * surface. Best-effort when it never did: the workdir may not exist (e.g. a\n * failed clone), which makes providers that spawn with `cwd` throw rather\n * than return a non-zero exit code; both outcomes are tolerated so neither\n * masks the primary failure.\n */\nasync function scrubRemote(\n sandbox: ExecutableSandbox,\n workdir: string,\n repoFullName: string,\n tokenInRemote: boolean,\n): Promise<void> {\n const scrub = `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(cleanUrl(repoFullName))}`;\n if (!tokenInRemote) {\n await sh(sandbox, scrub).catch(() => undefined);\n return;\n }\n let failure: string;\n try {\n const result = await sh(sandbox, scrub);\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 * Scrub after `primary` already failed and return the error to throw. A failed\n * scrub is appended to `primary` rather than replacing it, so the caller gets\n * the error it would have had without the scrub — same class, same `code` —\n * carrying the leaked-token warning in its message.\n */\nasync function scrubbedFailure(\n sandbox: ExecutableSandbox,\n workdir: string,\n repoFullName: string,\n tokenInRemote: boolean,\n primary: unknown,\n fallback: MaterializeError['code'],\n): Promise<unknown> {\n try {\n await scrubRemote(sandbox, workdir, repoFullName, tokenInRemote);\n return primary;\n } catch (scrubError) {\n const scrubMessage = scrubError instanceof Error ? scrubError.message : String(scrubError);\n if (!(primary instanceof Error)) {\n return new MaterializeError(`${String(primary)} — additionally: ${scrubMessage}`, fallback);\n }\n primary.message = `${primary.message} — additionally: ${scrubMessage}`;\n return primary;\n }\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, injected only into\n// the temporary remote URL inside the sandbox, and always scrubbed afterwards\n// so it never persists in `.git/config`.\n// ---------------------------------------------------------------------------\n\n/**\n * Validate a git ref (branch) name. Server-side defense-in-depth: only allow a\n * conservative character set so a branch can never be built into a shell\n * command in a way that escapes quoting. Mirrors the route-layer check.\n */\nexport function isValidGitRef(value: unknown): value is string {\n return (\n typeof value === 'string' &&\n value.length > 0 &&\n value.length <= 255 &&\n // Reject leading-dash refs (e.g. `--mirror`) so the value can never be\n // parsed as a git option when interpolated into a command.\n !value.startsWith('-') &&\n /^[A-Za-z0-9_./-]+$/.test(value)\n );\n}\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/**\n * Configure `user.name` / `user.email` for the given repo working tree inside\n * the sandbox so commits are authored correctly. Values are shell-quoted.\n */\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 sh(sandbox, `git -C ${shellQuote(workdir)} config user.name ${shellQuote(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 sh(sandbox, `git -C ${shellQuote(workdir)} config user.email ${shellQuote(email)}`);\n if (setEmail.exitCode !== 0) {\n throw new MaterializeError(`Failed to set git user.email: ${setEmail.stderr.trim()}`, 'commit-failed');\n }\n}\n\n/**\n * Temporarily rewrite `origin` to a tokenized URL, run `fn` (e.g. a push), and\n * **always** scrub the remote back to the tokenless URL afterwards. The token\n * therefore only ever lives in the remote URL for the duration of the\n * operation and is never left in the VM's git config.\n *\n * Once the tokenized URL is installed a failed scrub may leave the token\n * persisted, so it is always surfaced: on its own after a successful `fn`,\n * appended to `fn`'s own error otherwise — `fn`'s error is never replaced.\n * Only a failed set-url (the token never reached the remote) downgrades the\n * scrub to best-effort.\n */\nexport async function withInstallToken<T>(\n sandbox: ExecutableSandbox,\n workdir: string,\n repoFullName: string,\n token: string,\n fn: () => Promise<T>,\n): Promise<T> {\n if (!/^[\\w.-]+\\/[\\w.-]+$/.test(repoFullName)) {\n throw new MaterializeError(`Refusing to push: invalid repo full name '${repoFullName}'.`, 'push-failed');\n }\n\n const setUrl = await sh(\n sandbox,\n `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(tokenUrl(repoFullName, token))}`,\n );\n if (setUrl.exitCode !== 0) {\n // Best-effort scrub even though set-url failed, then surface the failure.\n await scrubRemote(sandbox, workdir, repoFullName, false);\n throw new MaterializeError(`Failed to set git remote: ${setUrl.stderr.trim()}`, 'push-failed');\n }\n\n let result: T;\n try {\n result = await fn();\n } catch (primary) {\n throw await scrubbedFailure(sandbox, workdir, repoFullName, true, primary, 'push-failed');\n }\n // Restore the tokenless remote. The workdir has a `.git` (we just rewrote\n // its remote) so a scrub failure means the token may still persist — surface it.\n await scrubRemote(sandbox, workdir, repoFullName, true);\n return result;\n}\n\n/**\n * Push a branch back to GitHub from inside the sandbox using a short-lived\n * installation token. The branch is ref-validated, the token is injected only\n * into the remote URL via `withInstallToken`, and egress failures are\n * classified into actionable errors.\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\n await withInstallToken(sandbox, workdir, repoFullName, token, async () => {\n const push = await sh(sandbox, `git -C ${shellQuote(workdir)} push -u origin ${shellQuote(branch)}`);\n if (push.exitCode !== 0) {\n throw classifyGitFailure(push, 'push-failed');\n }\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 (quoted; arbitrary text is safe)\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 sh(sandbox, `git -C ${shellQuote(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 sh(sandbox, `git -C ${shellQuote(workdir)} diff --cached --quiet`);\n if (staged.exitCode === 0) {\n return { committed: false };\n }\n\n const commit = await sh(sandbox, `git -C ${shellQuote(workdir)} commit -m ${shellQuote(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(\n sandbox: ExecutableSandbox,\n workdir: string,\n command: string,\n): 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 sh(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 shell-quoted, and the resulting\n * 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 // GH_TOKEN is prefixed inline so it is exported only to the single `gh`\n // process and never to the wider shell session, git config, or VM env. `gh`\n // is run from inside the checkout so it targets the correct repo/head branch.\n const ghCommand = [\n `GH_TOKEN=${shellQuote(token)} gh pr create`,\n `--base ${shellQuote(base)}`,\n `--head ${shellQuote(head)}`,\n `--title ${shellQuote(title)}`,\n `--body ${shellQuote(body ?? '')}`,\n ].join(' ');\n const script = `cd ${shellQuote(workdir)} && ${ghCommand}`;\n\n const result = await sh(sandbox, script);\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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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Resolve to `fallback` when `signal` aborts before `promise` settles, leaving
3
+ * `promise` itself running: callers that share one coalesced upstream request
4
+ * keep their own cancellation without cancelling each other's answer.
5
+ */
6
+ export declare function settleOrAbort<T>(promise: Promise<T>, signal: AbortSignal | undefined, fallback: T): Promise<T>;
7
+ //# sourceMappingURL=settle-or-abort.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settle-or-abort.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/settle-or-abort.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,GAAG,SAAS,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAiB9G"}
@@ -0,0 +1,25 @@
1
+ //#region src/integrations/github/settle-or-abort.ts
2
+ /**
3
+ * Resolve to `fallback` when `signal` aborts before `promise` settles, leaving
4
+ * `promise` itself running: callers that share one coalesced upstream request
5
+ * keep their own cancellation without cancelling each other's answer.
6
+ */
7
+ function settleOrAbort(promise, signal, fallback) {
8
+ if (!signal) return promise;
9
+ if (signal.aborted) return Promise.resolve(fallback);
10
+ return new Promise((resolve) => {
11
+ const onAbort = () => resolve(fallback);
12
+ signal.addEventListener("abort", onAbort, { once: true });
13
+ promise.then((value) => {
14
+ signal.removeEventListener("abort", onAbort);
15
+ resolve(value);
16
+ }, () => {
17
+ signal.removeEventListener("abort", onAbort);
18
+ resolve(fallback);
19
+ });
20
+ });
21
+ }
22
+ //#endregion
23
+ export { settleOrAbort };
24
+
25
+ //# sourceMappingURL=settle-or-abort.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settle-or-abort.js","names":[],"sources":["../../../src/integrations/github/settle-or-abort.ts"],"sourcesContent":["/**\n * Resolve to `fallback` when `signal` aborts before `promise` settles, leaving\n * `promise` itself running: callers that share one coalesced upstream request\n * keep their own cancellation without cancelling each other's answer.\n */\nexport function settleOrAbort<T>(promise: Promise<T>, signal: AbortSignal | undefined, fallback: T): Promise<T> {\n if (!signal) return promise;\n if (signal.aborted) return Promise.resolve(fallback);\n return new Promise<T>(resolve => {\n const onAbort = () => resolve(fallback);\n signal.addEventListener('abort', onAbort, { once: true });\n promise.then(\n value => {\n signal.removeEventListener('abort', onAbort);\n resolve(value);\n },\n () => {\n signal.removeEventListener('abort', onAbort);\n resolve(fallback);\n },\n );\n });\n}\n"],"mappings":";;;;;;AAKA,SAAgB,cAAiB,SAAqB,QAAiC,UAAyB;CAC9G,IAAI,CAAC,QAAQ,OAAO;CACpB,IAAI,OAAO,SAAS,OAAO,QAAQ,QAAQ,QAAQ;CACnD,OAAO,IAAI,SAAW,YAAW;EAC/B,MAAM,gBAAgB,QAAQ,QAAQ;EACtC,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;EACxD,QAAQ,MACN,UAAS;GACP,OAAO,oBAAoB,SAAS,OAAO;GAC3C,QAAQ,KAAK;EACf,SACM;GACJ,OAAO,oBAAoB,SAAS,OAAO;GAC3C,QAAQ,QAAQ;EAClB,CACF;CACF,CAAC;AACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"event-worker.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/github/event-worker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG1D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+CAA+C,CAAC;AAE9F,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAC9E,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,kCAAkC,CAAC;AACxF,OAAO,KAAK,EAAE,2BAA2B,EAAuB,MAAM,uBAAuB,CAAC;AAE9F,OAAO,EAAE,qBAAqB,EAA6C,MAAM,yBAAyB,CAAC;AAC3G,OAAO,KAAK,EACV,gCAAgC,EAEhC,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAiC1D,KAAK,WAAW,GAAG;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC;AACzE,KAAK,iCAAiC,GAAG;IACvC,OAAO,EAAE,CAAC,CAAC;IACX,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,wBAAwB,CAC/D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvB,iCAAiC,EACjC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACxB,CAAC;AAWF,MAAM,MAAM,sCAAsC,GAAG,gCAAgC,CAAC;AAEtF,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,iBAAiB,CAAC;IAC1B,UAAU,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAC5C,MAAM,EAAE,sCAAsC,CAAC;IAC/C,OAAO,EAAE,0BAA0B,CAAC;IACpC;;;;;;OAMG;IACH,aAAa,EAAE,+BAA+B,CAAC;IAC/C,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,qBAAqB,CAAC,EAAE,2BAA2B,CAAC;IACpD,2BAA2B,CAAC,EAAE,qBAAqB,CAAC;IACpD,6FAA6F;IAC7F,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,qBAAqB,CAAC;CACzC;AAED,qBAAa,yBAA0B,SAAQ,YAAY;;IACzD,QAAQ,CAAC,IAAI,4BAA4B;gBA8B7B,MAAM,EAAE,+BAA+B;IAgC7C,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IActB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAa3B,IAAI,SAAS,IAAI,OAAO,CAEvB;CA6VF"}
1
+ {"version":3,"file":"event-worker.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/github/event-worker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG1D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+CAA+C,CAAC;AAE9F,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAC9E,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,kCAAkC,CAAC;AACxF,OAAO,KAAK,EAAE,2BAA2B,EAAuB,MAAM,uBAAuB,CAAC;AAE9F,OAAO,EAAE,qBAAqB,EAA6C,MAAM,yBAAyB,CAAC;AAC3G,OAAO,KAAK,EACV,gCAAgC,EAEhC,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAmC1D,KAAK,WAAW,GAAG;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC;AACzE,KAAK,iCAAiC,GAAG;IACvC,OAAO,EAAE,CAAC,CAAC;IACX,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,wBAAwB,CAC/D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvB,iCAAiC,EACjC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACxB,CAAC;AAWF,MAAM,MAAM,sCAAsC,GAAG,gCAAgC,CAAC;AAEtF,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,iBAAiB,CAAC;IAC1B,UAAU,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAC5C,MAAM,EAAE,sCAAsC,CAAC;IAC/C,OAAO,EAAE,0BAA0B,CAAC;IACpC;;;;;;OAMG;IACH,aAAa,EAAE,+BAA+B,CAAC;IAC/C,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,qBAAqB,CAAC,EAAE,2BAA2B,CAAC;IACpD,2BAA2B,CAAC,EAAE,qBAAqB,CAAC;IACpD,6FAA6F;IAC7F,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,qBAAqB,CAAC;CACzC;AAED,qBAAa,yBAA0B,SAAQ,YAAY;;IACzD,QAAQ,CAAC,IAAI,4BAA4B;gBA+B7B,MAAM,EAAE,+BAA+B;IAgC7C,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IActB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAa3B,IAAI,SAAS,IAAI,OAAO,CAEvB;CA6VF"}
@@ -7,7 +7,7 @@ import { MastraWorker } from "@mastra/core/worker";
7
7
  //#region src/integrations/platform/github/event-worker.ts
8
8
  const API_PREFIX = "/v1/server/github-app";
9
9
  const DEFAULT_POLL_INTERVAL_MS = 2e4;
10
- const DEFAULT_RECONCILE_INTERVAL_MS = 5 * 6e4;
10
+ const DEFAULT_RECONCILE_INTERVAL_MS = 60 * 6e4;
11
11
  const EVENT_PAGE_SIZE = 500;
12
12
  const MIN_LEASE_TTL_MS = 3e4;
13
13
  const CURSOR_ORG_ID = "__platform_github_event_worker__";
@@ -59,8 +59,8 @@ var PlatformGithubEventWorker = class extends MastraWorker {
59
59
  #leaseTtlMs;
60
60
  #hasLease = false;
61
61
  #startedAt = 0;
62
- #lastPullRequestReconcileAt = 0;
63
- #lastIssueReconcileAt = 0;
62
+ #lastPullRequestReconcileAt = Number.NEGATIVE_INFINITY;
63
+ #lastIssueReconcileAt = Number.NEGATIVE_INFINITY;
64
64
  #settings = {
65
65
  version: 1,
66
66
  repositories: {}
@@ -1 +1 @@
1
- {"version":3,"file":"event-worker.js","names":["#client","#controller","#github","#storage","#ingestFactoryEvent","#reconcileFactoryState","#reconcileIssuesFactoryState","#pollEventsEnabled","#pullRequestReconcileIntervalMs","#issueReconcileIntervalMs","#intervalMs","#now","#dispatch","#sourceControl","#leaseOwner","#leaseTtlMs","#leaseProvider","#running","#startedAt","#settings","#schedule","#timer","#stopLeaseRenewal","#inFlight","#hasLease","#leaseKey","#tick","#ensureLease","#poll","#startLeaseRenewal","#leaseRenewalTimer","#discoverRepositories","#reconcileCadence","#pollRepository","#maybeReconcile","#lastPullRequestReconcileAt","#lastIssueReconcileAt","#saveSettings","#isAuthorizedSender"],"sources":["../../../../src/integrations/platform/github/event-worker.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { MountedMastraCode } from '@mastra/code-sdk';\nimport { isLeaseProvider, NoopLeaseProvider } from '@mastra/core/events';\nimport type { LeaseProvider, PubSub } from '@mastra/core/events';\nimport { MastraWorker } from '@mastra/core/worker';\nimport type { WorkerDeps } from '@mastra/core/worker';\n\nimport type { IntegrationStorageHandle } from '../../../storage/domains/integrations/base.js';\nimport type { GithubRepositoryPermission } from '../../github/integration.js';\nimport type { GithubIssueReconciler } from '../../github/issue-reconciler.js';\nimport type { GithubReconcileRepositorySource } from '../../github/reconcile-worker.js';\nimport type { GithubPullRequestReconciler, ReconcileRepository } from '../../github/rules.js';\nimport { listPullRequestSubscriptionsForWebhook, retirePullRequestSubscription } from '../../github/subscriptions.js';\nimport { dispatchGithubWebhook, isFactoryAppSender, resolveAuthorizedBots } from '../../github/webhook.js';\nimport type {\n GithubWebhookDispatchIntegration,\n GithubWebhookNotification,\n ParsedGithubWebhook,\n} from '../../github/webhook.js';\nimport type { PlatformApiClient } from '../api-client.js';\nimport { PlatformApiError } from '../api-client.js';\n\nconst API_PREFIX = '/v1/server/github-app';\nconst DEFAULT_POLL_INTERVAL_MS = 20_000;\nconst DEFAULT_RECONCILE_INTERVAL_MS = 5 * 60_000;\nconst EVENT_PAGE_SIZE = 500;\nconst MIN_LEASE_TTL_MS = 30_000;\nconst CURSOR_ORG_ID = '__platform_github_event_worker__';\nconst CURSOR_USER_ID = 'worker';\nconst SUPPORTED_EVENTS = new Set([\n 'issues',\n 'issue_comment',\n 'pull_request',\n 'pull_request_review',\n 'pull_request_review_comment',\n // Pushes never reach the subscription dispatcher; they feed the factory\n // ingest path so base checkpoints rebuild on default-branch updates.\n 'push',\n]);\n// Kinds emitted by `classifyGithubWebhook` that carry untrusted sender-authored\n// content and must pass the sender authorization gate below.\nconst AUTHOR_GATED_KINDS = new Set([\n 'issue-comment-created',\n 'review-comment-created',\n 'review-approved',\n 'review-changes-requested',\n 'review-submitted',\n 'review-dismissed',\n]);\nconst AUTHORIZED_PERMISSIONS = new Set<GithubRepositoryPermission>(['admin', 'maintain', 'write']);\nconst PERMISSION_CHECK_TIMEOUT_MS = 5_000;\n\ntype EventCursor = { afterEventId: string } | { afterTimestamp: number };\ntype PlatformGithubEventWorkerSettings = {\n version: 1;\n repositories: Record<string, EventCursor>;\n};\n\nexport type PlatformGithubEventStorage = IntegrationStorageHandle<\n Record<string, unknown>,\n PlatformGithubEventWorkerSettings,\n Record<string, unknown>\n>;\n\ntype EventLogEntry = {\n id: string;\n deliveryId: string;\n event: string;\n payload: unknown;\n};\n\ntype Repository = { id: number; fullName?: string; installationId: number };\n\nexport type PlatformGithubEventDispatchIntegration = GithubWebhookDispatchIntegration;\n\nexport interface PlatformGithubEventWorkerConfig {\n client: PlatformApiClient;\n controller: MountedMastraCode['controller'];\n github: PlatformGithubEventDispatchIntegration;\n storage: PlatformGithubEventStorage;\n /**\n * Source-control storage used to resolve the set of repositories the worker\n * should poll. The worker restricts itself to `(installation, repository)`\n * pairs linked to a factory project — the same set the reconciler uses —\n * so polling scales with Factory usage, not with the size of the underlying\n * GitHub org.\n */\n sourceControl: GithubReconcileRepositorySource;\n ingestFactoryEvent?: (event: ParsedGithubWebhook) => Promise<unknown>;\n reconcileFactoryState?: GithubPullRequestReconciler;\n reconcileIssuesFactoryState?: GithubIssueReconciler;\n /** When false the worker skips event tailing and only runs enabled reconciliation sweeps. */\n pollEventsEnabled?: boolean;\n intervalMs?: number;\n /** Legacy shared reconciliation cadence. */\n reconcileIntervalMs?: number;\n pullRequestReconcileIntervalMs?: number;\n issueReconcileIntervalMs?: number;\n now?: () => number;\n dispatch?: typeof dispatchGithubWebhook;\n}\n\nexport class PlatformGithubEventWorker extends MastraWorker {\n readonly name = 'platform-github-events';\n\n readonly #client: PlatformApiClient;\n readonly #controller: MountedMastraCode['controller'];\n readonly #github: PlatformGithubEventDispatchIntegration;\n readonly #storage: PlatformGithubEventStorage;\n readonly #ingestFactoryEvent: ((event: ParsedGithubWebhook) => Promise<unknown>) | undefined;\n readonly #reconcileFactoryState: GithubPullRequestReconciler | undefined;\n readonly #reconcileIssuesFactoryState: GithubIssueReconciler | undefined;\n readonly #pollEventsEnabled: boolean;\n readonly #pullRequestReconcileIntervalMs: number;\n readonly #issueReconcileIntervalMs: number;\n readonly #intervalMs: number;\n readonly #now: () => number;\n readonly #dispatch: typeof dispatchGithubWebhook;\n readonly #sourceControl: GithubReconcileRepositorySource;\n readonly #leaseOwner = randomUUID();\n\n #running = false;\n #timer: ReturnType<typeof setTimeout> | undefined;\n #leaseRenewalTimer: ReturnType<typeof setInterval> | undefined;\n #inFlight: Promise<void> | undefined;\n #leaseProvider: LeaseProvider = NoopLeaseProvider;\n #leaseTtlMs: number;\n #hasLease = false;\n #startedAt = 0;\n #lastPullRequestReconcileAt = 0;\n #lastIssueReconcileAt = 0;\n #settings: PlatformGithubEventWorkerSettings = { version: 1, repositories: {} };\n\n constructor(config: PlatformGithubEventWorkerConfig) {\n super();\n this.#client = config.client;\n this.#controller = config.controller;\n this.#github = config.github;\n this.#storage = config.storage;\n this.#ingestFactoryEvent = config.ingestFactoryEvent;\n this.#reconcileFactoryState = config.reconcileFactoryState;\n this.#reconcileIssuesFactoryState = config.reconcileIssuesFactoryState;\n this.#pollEventsEnabled = config.pollEventsEnabled ?? true;\n const legacyReconcileIntervalMs = config.reconcileIntervalMs ?? DEFAULT_RECONCILE_INTERVAL_MS;\n this.#pullRequestReconcileIntervalMs = config.pullRequestReconcileIntervalMs ?? legacyReconcileIntervalMs;\n this.#issueReconcileIntervalMs = config.issueReconcileIntervalMs ?? legacyReconcileIntervalMs;\n this.#intervalMs = config.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) {\n throw new Error('Platform GitHub event polling interval must be a positive number.');\n }\n if (!Number.isFinite(this.#pullRequestReconcileIntervalMs) || this.#pullRequestReconcileIntervalMs <= 0) {\n throw new Error('Platform GitHub pull request reconcile interval must be a positive number.');\n }\n if (!Number.isFinite(this.#issueReconcileIntervalMs) || this.#issueReconcileIntervalMs <= 0) {\n throw new Error('Platform GitHub issue reconcile interval must be a positive number.');\n }\n this.#leaseTtlMs = Math.max(\n MIN_LEASE_TTL_MS,\n Math.min(this.#intervalMs, this.#pullRequestReconcileIntervalMs, this.#issueReconcileIntervalMs) * 3,\n );\n this.#now = config.now ?? Date.now;\n this.#dispatch = config.dispatch ?? dispatchGithubWebhook;\n this.#sourceControl = config.sourceControl;\n }\n\n async init(deps: WorkerDeps): Promise<void> {\n await super.init(deps);\n this.#leaseProvider = getLeaseProvider(deps.pubsub);\n }\n\n async start(): Promise<void> {\n if (this.#running) return;\n if (!this.deps) throw new Error('PlatformGithubEventWorker: call init() before start()');\n\n this.#startedAt = this.#now() - 1;\n this.#settings = normalizeSettings(await this.#storage.settings.get(CURSOR_ORG_ID, CURSOR_USER_ID));\n this.#running = true;\n this.deps.logger.info('Platform GitHub event polling started', {\n intervalMs: this.#intervalMs,\n leaseTtlMs: this.#leaseTtlMs,\n });\n this.#schedule(0);\n }\n\n async stop(): Promise<void> {\n if (!this.#running) return;\n this.#running = false;\n if (this.#timer) clearTimeout(this.#timer);\n this.#timer = undefined;\n this.#stopLeaseRenewal();\n await this.#inFlight;\n if (this.#hasLease) {\n await this.#leaseProvider.releaseLease(this.#leaseKey(), this.#leaseOwner).catch(() => undefined);\n this.#hasLease = false;\n }\n }\n\n get isRunning(): boolean {\n return this.#running;\n }\n\n #schedule(delayMs: number): void {\n if (!this.#running) return;\n this.#timer = setTimeout(() => {\n this.#timer = undefined;\n const run = this.#tick();\n this.#inFlight = run;\n void run.finally(() => {\n if (this.#inFlight === run) this.#inFlight = undefined;\n });\n }, delayMs);\n this.#timer.unref?.();\n }\n\n async #tick(): Promise<void> {\n let nextDelay = this.#intervalMs;\n try {\n if (!(await this.#ensureLease())) return;\n nextDelay = await this.#poll();\n } catch (error) {\n nextDelay = retryDelay(error, this.#intervalMs);\n this.deps?.logger.error('Platform GitHub event polling cycle failed', {\n error: error instanceof Error ? error.message : String(error),\n retryInMs: nextDelay,\n });\n } finally {\n this.#schedule(nextDelay);\n }\n }\n\n async #ensureLease(): Promise<boolean> {\n if (this.#hasLease) return true;\n const result = await this.#leaseProvider.acquireLease(this.#leaseKey(), this.#leaseOwner, this.#leaseTtlMs);\n this.#hasLease = result.acquired;\n if (this.#hasLease) this.#startLeaseRenewal();\n return this.#hasLease;\n }\n\n #startLeaseRenewal(): void {\n if (this.#leaseRenewalTimer) return;\n this.#leaseRenewalTimer = setInterval(\n () => {\n void this.#leaseProvider\n .renewLease(this.#leaseKey(), this.#leaseOwner, this.#leaseTtlMs)\n .then(renewed => {\n if (!renewed) {\n this.#hasLease = false;\n this.#stopLeaseRenewal();\n }\n })\n .catch(error => {\n this.#hasLease = false;\n this.#stopLeaseRenewal();\n this.deps?.logger.warn('Platform GitHub event polling lease renewal failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n },\n Math.floor(this.#leaseTtlMs / 3),\n );\n this.#leaseRenewalTimer.unref?.();\n }\n\n #stopLeaseRenewal(): void {\n if (this.#leaseRenewalTimer) clearInterval(this.#leaseRenewalTimer);\n this.#leaseRenewalTimer = undefined;\n }\n\n async #poll(): Promise<number> {\n const repositories = await this.#discoverRepositories();\n let retryInMs = this.#pollEventsEnabled ? this.#intervalMs : this.#reconcileCadence();\n\n if (this.#pollEventsEnabled) {\n for (const repository of repositories) {\n if (!this.#running || !this.#hasLease) break;\n try {\n await this.#pollRepository(repository.id);\n } catch (error) {\n const delay = retryDelay(error, this.#intervalMs);\n retryInMs = Math.max(retryInMs, delay);\n this.deps?.logger.error('Platform GitHub repository event polling failed', {\n repositoryId: repository.id,\n error: error instanceof Error ? error.message : String(error),\n retryInMs: delay,\n });\n if (error instanceof PlatformApiError && error.status === 429) break;\n }\n }\n }\n\n await this.#maybeReconcile(repositories);\n return retryInMs;\n }\n\n #reconcileCadence(): number {\n const cadences = [\n this.#reconcileFactoryState ? this.#pullRequestReconcileIntervalMs : undefined,\n this.#reconcileIssuesFactoryState ? this.#issueReconcileIntervalMs : undefined,\n ].filter((interval): interval is number => interval !== undefined);\n return cadences.length > 0 ? Math.min(...cadences) : this.#intervalMs;\n }\n\n async #maybeReconcile(repositories: Repository[]): Promise<void> {\n if (!this.#running || !this.#hasLease) return;\n const now = this.#now();\n const reconcilePullRequests = Boolean(\n this.#reconcileFactoryState && now - this.#lastPullRequestReconcileAt >= this.#pullRequestReconcileIntervalMs,\n );\n const reconcileIssues = Boolean(\n this.#reconcileIssuesFactoryState && now - this.#lastIssueReconcileAt >= this.#issueReconcileIntervalMs,\n );\n if (!reconcilePullRequests && !reconcileIssues) return;\n if (reconcilePullRequests) this.#lastPullRequestReconcileAt = now;\n if (reconcileIssues) this.#lastIssueReconcileAt = now;\n\n const targets: ReconcileRepository[] = repositories.flatMap(repository =>\n repository.fullName\n ? [{ id: repository.id, fullName: repository.fullName, installationId: repository.installationId }]\n : [],\n );\n if (targets.length === 0) {\n this.deps?.logger.debug('Platform GitHub reconciliation skipped: no named repositories');\n return;\n }\n\n if (reconcilePullRequests && this.#reconcileFactoryState) {\n const startedAt = Date.now();\n try {\n const { errors, ...counts } = await this.#reconcileFactoryState(targets);\n const context = { ...counts, candidateRepositories: targets.length, durationMs: Date.now() - startedAt };\n if (counts.failed > 0) {\n this.deps?.logger.warn('Platform GitHub pull request reconcile sweep completed with failures', {\n ...context,\n errors,\n });\n } else if (counts.merged > 0 || counts.closed > 0) {\n this.deps?.logger.info('Platform GitHub pull request reconcile replayed missed merges/closes', context);\n } else {\n this.deps?.logger.info('Platform GitHub pull request reconcile sweep completed', context);\n }\n } catch (error) {\n this.deps?.logger.error('Platform GitHub pull request reconcile failed', {\n repositories: targets.length,\n durationMs: Date.now() - startedAt,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n if (reconcileIssues && this.#reconcileIssuesFactoryState && this.#hasLease) {\n const startedAt = Date.now();\n try {\n const { errors, ...counts } = await this.#reconcileIssuesFactoryState(targets);\n const context = { ...counts, candidateRepositories: targets.length, durationMs: Date.now() - startedAt };\n if (counts.failed > 0) {\n this.deps?.logger.warn('Platform GitHub issue reconcile sweep completed with failures', { ...context, errors });\n } else if (counts.closed > 0) {\n this.deps?.logger.info('Platform GitHub issue reconcile replayed closed work items', context);\n } else if (counts.updated > 0) {\n this.deps?.logger.info('Platform GitHub issue reconcile patched stale metadata', context);\n } else {\n this.deps?.logger.debug('Platform GitHub issue reconcile sweep completed', context);\n }\n } catch (error) {\n this.deps?.logger.error('Platform GitHub issue reconcile failed', {\n repositories: targets.length,\n durationMs: Date.now() - startedAt,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n }\n\n /**\n * Configured `(installation, repository)` pairs — the same set the\n * reconciler sweeps — resolved to the numeric ids and slug the poll\n * addresses GitHub with. Repositories not linked to any factory project\n * cannot produce work for Factory, so polling and reconciling them is\n * wasted `/events` bandwidth and platform load.\n */\n async #discoverRepositories(): Promise<Repository[]> {\n const keys = await this.#sourceControl.projectRepositories.listConfiguredExternalKeys();\n const repositories = new Map<number, Repository>();\n for (const key of keys) {\n const installationId = Number(key.installationExternalId);\n const repositoryId = Number(key.repositoryExternalId);\n if (\n !Number.isSafeInteger(installationId) ||\n installationId <= 0 ||\n !Number.isSafeInteger(repositoryId) ||\n repositoryId <= 0\n ) {\n continue;\n }\n if (repositories.has(repositoryId)) continue;\n // A configured key exists per project link, so `listByExternalRepository`\n // always yields at least one row; the first row's orgId is enough to\n // look up the repository row for its slug.\n const projects = await this.#sourceControl.projectRepositories.listByExternalRepository(key);\n const orgId = projects[0]?.orgId;\n const repository = orgId\n ? await this.#sourceControl.repositories.findByExternalId({ orgId, externalId: key.repositoryExternalId })\n : null;\n repositories.set(repositoryId, {\n id: repositoryId,\n installationId,\n fullName: repository?.slug,\n });\n }\n return [...repositories.values()];\n }\n\n async #pollRepository(repositoryId: number): Promise<void> {\n const key = String(repositoryId);\n if (!this.#settings.repositories[key]) {\n this.#settings.repositories[key] = { afterTimestamp: this.#startedAt };\n await this.#saveSettings();\n }\n\n while (this.#running && this.#hasLease) {\n const cursor: EventCursor = this.#settings.repositories[key]!;\n const query = new URLSearchParams({ limit: String(EVENT_PAGE_SIZE) });\n if ('afterEventId' in cursor) query.set('afterEventId', cursor.afterEventId);\n else query.set('afterTimestamp', String(cursor.afterTimestamp));\n\n const pollStartedAt = performance.now();\n const page = await this.#client.request<{ events: EventLogEntry[]; nextCursor: string | null }>(\n 'GET',\n `${API_PREFIX}/repositories/${repositoryId}/events?${query}`,\n );\n this.deps?.logger.debug('Platform GitHub repository event poll completed', {\n repositoryId,\n eventCount: page.events.length,\n latencyMs: Math.round(performance.now() - pollStartedAt),\n });\n if (page.events.length === 0 || !page.nextCursor) return;\n\n for (const event of page.events) {\n if (!this.#running || !this.#hasLease) return;\n const parsed = parseEvent(event);\n if (!parsed) {\n this.deps?.logger.warn('Platform GitHub event log returned a malformed event', {\n repositoryId,\n eventId: event.id,\n });\n continue;\n }\n if (isFactoryIngestedEvent(parsed)) {\n await this.#ingestFactoryEvent?.(parsed);\n }\n const result = await this.#dispatch(parsed, {\n controller: this.#controller,\n listSubscriptions: (target, options) =>\n listPullRequestSubscriptionsForWebhook(target, options, this.#github.integrationStorage),\n retireSubscription: (id, status) =>\n retirePullRequestSubscription(id, status, this.#github.integrationStorage),\n github: this.#github,\n isAuthorizedSender: notification => this.#isAuthorizedSender(notification),\n onTargetSkipped: subscription => {\n // Routine when a subscription's thread belongs to another\n // deployment, so this stays at debug rather than warning on a loop.\n this.deps?.logger.debug('Platform GitHub event skipped: thread is not held here', {\n deliveryId: event.deliveryId,\n subscriptionId: subscription.id,\n threadId: subscription.threadId,\n });\n },\n onSenderRejected: notification => {\n this.deps?.logger.debug('Platform GitHub event dropped: sender not authorized', {\n deliveryId: event.deliveryId,\n repository: notification.metadata.repository,\n sender: notification.metadata.sender,\n kind: notification.kind,\n });\n },\n onTargetError: (subscription, error) => {\n this.deps?.logger.error('Platform GitHub event delivery failed for a subscription', {\n deliveryId: event.deliveryId,\n subscriptionId: subscription.id,\n resourceId: subscription.resourceId,\n threadId: subscription.threadId,\n error: error instanceof Error ? error.message : String(error),\n });\n },\n });\n if (result.failed > 0) {\n this.deps?.logger.warn('Platform GitHub event completed with failed subscription deliveries', {\n repositoryId,\n deliveryId: event.deliveryId,\n delivered: result.delivered,\n failed: result.failed,\n });\n }\n }\n\n if (page.nextCursor === ('afterEventId' in cursor ? cursor.afterEventId : undefined)) return;\n this.#settings.repositories[key] = { afterEventId: page.nextCursor };\n await this.#saveSettings();\n }\n }\n\n async #isAuthorizedSender(notification: GithubWebhookNotification): Promise<boolean> {\n if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;\n const sender = notification.metadata.sender;\n const repository = notification.metadata.repository;\n if (!sender || !repository) return false;\n // Factory's own app has to clear the gate before the bot rules below, which\n // fail closed for every bot that is not explicitly allowlisted. GitHub\n // forbids an app from reviewing its own pull request, so `factory-review`\n // posts its verdict as a comment under this login and that comment is the\n // handoff the authoring agent wakes on.\n if (this.#github.identity?.matches(sender)) return true;\n if (isFactoryAppSender(sender, this.#github.slug)) return true;\n const normalizedSender = sender.toLowerCase();\n const authorizedBots = resolveAuthorizedBots(this.#github.authorizedBots);\n if (authorizedBots.has(normalizedSender)) return true;\n // Any other bot is gated purely by the allowlist: a GitHub App never holds a\n // collaborator permission under its sender login, so falling through to the\n // permission lookup would only fail closed after a wasted API call.\n if (notification.metadata.senderType?.toLowerCase() === 'bot' || normalizedSender.endsWith('[bot]')) {\n return false;\n }\n\n const abortController = new AbortController();\n const timeout = setTimeout(() => abortController.abort(), PERMISSION_CHECK_TIMEOUT_MS);\n try {\n const permission = await this.#github.getRepositoryCollaboratorPermission(\n notification.metadata.installationId,\n repository,\n sender,\n abortController.signal,\n );\n return permission !== undefined && AUTHORIZED_PERMISSIONS.has(permission);\n } catch {\n return false;\n } finally {\n clearTimeout(timeout);\n }\n }\n\n async #saveSettings(): Promise<void> {\n await this.#storage.settings.save(CURSOR_ORG_ID, CURSOR_USER_ID, this.#settings);\n }\n\n #leaseKey(): string {\n return `${this.name}:${this.#storage.integrationId}`;\n }\n}\n\nfunction getLeaseProvider(pubsub: PubSub): LeaseProvider {\n const getProvider = (pubsub as PubSub & { getLeaseProvider?: () => LeaseProvider | undefined }).getLeaseProvider;\n if (typeof getProvider === 'function') return getProvider.call(pubsub) ?? NoopLeaseProvider;\n return isLeaseProvider(pubsub) ? pubsub : NoopLeaseProvider;\n}\n\nfunction normalizeSettings(value: PlatformGithubEventWorkerSettings | null): PlatformGithubEventWorkerSettings {\n if (!value || value.version !== 1 || !value.repositories || typeof value.repositories !== 'object') {\n return { version: 1, repositories: {} };\n }\n return { version: 1, repositories: { ...value.repositories } };\n}\n\n// Events the polling worker forwards to the factory rules engine. Closures\n// let the reconciler finalize cards; `synchronize` and `review_requested` on a\n// pull request are the triggers the review board's re-review path listens for;\n// submitted reviews and pull request comments are how review feedback reaches\n// the agent that authored the branch. Direct-webhook consumers ingest every\n// parsed event; the platform path gates because the remaining events (issue\n// edits, comment edits and deletions) only interest the subscription\n// dispatcher, not the factory rules.\nfunction isFactoryIngestedEvent(event: ParsedGithubWebhook): boolean {\n if ((event.event === 'issues' || event.event === 'pull_request') && event.payload.action === 'closed') {\n return true;\n }\n if (event.event === 'pull_request') {\n const action = event.payload.action;\n // `opened` is what mints the Review card for a pull request. Without it a\n // factory-authored PR never gets reviewed on the polling path, which is the\n // only path a local deployment has.\n if (action === 'opened' || action === 'synchronize' || action === 'review_requested') return true;\n }\n if (event.event === 'pull_request_review' && event.payload.action === 'submitted') return true;\n if (event.event === 'issue_comment' && event.payload.action === 'created') return true;\n // Default-branch pushes drive base-checkpoint rebuilds\n // (`withBaseCheckpointWebhookTrigger` wraps the ingest callback).\n if (event.event === 'push') return true;\n return false;\n}\n\nfunction parseEvent(event: EventLogEntry): ParsedGithubWebhook | null {\n if (\n !event.id ||\n !event.deliveryId ||\n !SUPPORTED_EVENTS.has(event.event) ||\n !event.payload ||\n typeof event.payload !== 'object' ||\n Array.isArray(event.payload)\n ) {\n return null;\n }\n return {\n event: event.event,\n deliveryId: event.deliveryId,\n payload: event.payload as Record<string, unknown>,\n };\n}\n\nfunction retryDelay(error: unknown, fallbackMs: number): number {\n if (error instanceof PlatformApiError && error.status === 429 && error.retryAfterSeconds !== null) {\n return Math.max(fallbackMs, error.retryAfterSeconds * 1_000);\n }\n return fallbackMs;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"event-worker.js","names":["#client","#controller","#github","#storage","#ingestFactoryEvent","#reconcileFactoryState","#reconcileIssuesFactoryState","#pollEventsEnabled","#pullRequestReconcileIntervalMs","#issueReconcileIntervalMs","#intervalMs","#now","#dispatch","#sourceControl","#leaseOwner","#leaseTtlMs","#leaseProvider","#running","#startedAt","#settings","#schedule","#timer","#stopLeaseRenewal","#inFlight","#hasLease","#leaseKey","#tick","#ensureLease","#poll","#startLeaseRenewal","#leaseRenewalTimer","#discoverRepositories","#reconcileCadence","#pollRepository","#maybeReconcile","#lastPullRequestReconcileAt","#lastIssueReconcileAt","#saveSettings","#isAuthorizedSender"],"sources":["../../../../src/integrations/platform/github/event-worker.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type { MountedMastraCode } from '@mastra/code-sdk';\nimport { isLeaseProvider, NoopLeaseProvider } from '@mastra/core/events';\nimport type { LeaseProvider, PubSub } from '@mastra/core/events';\nimport { MastraWorker } from '@mastra/core/worker';\nimport type { WorkerDeps } from '@mastra/core/worker';\n\nimport type { IntegrationStorageHandle } from '../../../storage/domains/integrations/base.js';\nimport type { GithubRepositoryPermission } from '../../github/integration.js';\nimport type { GithubIssueReconciler } from '../../github/issue-reconciler.js';\nimport type { GithubReconcileRepositorySource } from '../../github/reconcile-worker.js';\nimport type { GithubPullRequestReconciler, ReconcileRepository } from '../../github/rules.js';\nimport { listPullRequestSubscriptionsForWebhook, retirePullRequestSubscription } from '../../github/subscriptions.js';\nimport { dispatchGithubWebhook, isFactoryAppSender, resolveAuthorizedBots } from '../../github/webhook.js';\nimport type {\n GithubWebhookDispatchIntegration,\n GithubWebhookNotification,\n ParsedGithubWebhook,\n} from '../../github/webhook.js';\nimport type { PlatformApiClient } from '../api-client.js';\nimport { PlatformApiError } from '../api-client.js';\n\nconst API_PREFIX = '/v1/server/github-app';\nconst DEFAULT_POLL_INTERVAL_MS = 20_000;\n// Event polling is the primary sync; the sweeps only catch drift (missed\n// events, cards ingested before polling was linked), so they run hourly.\nconst DEFAULT_RECONCILE_INTERVAL_MS = 60 * 60_000;\nconst EVENT_PAGE_SIZE = 500;\nconst MIN_LEASE_TTL_MS = 30_000;\nconst CURSOR_ORG_ID = '__platform_github_event_worker__';\nconst CURSOR_USER_ID = 'worker';\nconst SUPPORTED_EVENTS = new Set([\n 'issues',\n 'issue_comment',\n 'pull_request',\n 'pull_request_review',\n 'pull_request_review_comment',\n // Pushes never reach the subscription dispatcher; they feed the factory\n // ingest path so base checkpoints rebuild on default-branch updates.\n 'push',\n]);\n// Kinds emitted by `classifyGithubWebhook` that carry untrusted sender-authored\n// content and must pass the sender authorization gate below.\nconst AUTHOR_GATED_KINDS = new Set([\n 'issue-comment-created',\n 'review-comment-created',\n 'review-approved',\n 'review-changes-requested',\n 'review-submitted',\n 'review-dismissed',\n]);\nconst AUTHORIZED_PERMISSIONS = new Set<GithubRepositoryPermission>(['admin', 'maintain', 'write']);\nconst PERMISSION_CHECK_TIMEOUT_MS = 5_000;\n\ntype EventCursor = { afterEventId: string } | { afterTimestamp: number };\ntype PlatformGithubEventWorkerSettings = {\n version: 1;\n repositories: Record<string, EventCursor>;\n};\n\nexport type PlatformGithubEventStorage = IntegrationStorageHandle<\n Record<string, unknown>,\n PlatformGithubEventWorkerSettings,\n Record<string, unknown>\n>;\n\ntype EventLogEntry = {\n id: string;\n deliveryId: string;\n event: string;\n payload: unknown;\n};\n\ntype Repository = { id: number; fullName?: string; installationId: number };\n\nexport type PlatformGithubEventDispatchIntegration = GithubWebhookDispatchIntegration;\n\nexport interface PlatformGithubEventWorkerConfig {\n client: PlatformApiClient;\n controller: MountedMastraCode['controller'];\n github: PlatformGithubEventDispatchIntegration;\n storage: PlatformGithubEventStorage;\n /**\n * Source-control storage used to resolve the set of repositories the worker\n * should poll. The worker restricts itself to `(installation, repository)`\n * pairs linked to a factory project — the same set the reconciler uses —\n * so polling scales with Factory usage, not with the size of the underlying\n * GitHub org.\n */\n sourceControl: GithubReconcileRepositorySource;\n ingestFactoryEvent?: (event: ParsedGithubWebhook) => Promise<unknown>;\n reconcileFactoryState?: GithubPullRequestReconciler;\n reconcileIssuesFactoryState?: GithubIssueReconciler;\n /** When false the worker skips event tailing and only runs enabled reconciliation sweeps. */\n pollEventsEnabled?: boolean;\n intervalMs?: number;\n /** Legacy shared reconciliation cadence. */\n reconcileIntervalMs?: number;\n pullRequestReconcileIntervalMs?: number;\n issueReconcileIntervalMs?: number;\n now?: () => number;\n dispatch?: typeof dispatchGithubWebhook;\n}\n\nexport class PlatformGithubEventWorker extends MastraWorker {\n readonly name = 'platform-github-events';\n\n readonly #client: PlatformApiClient;\n readonly #controller: MountedMastraCode['controller'];\n readonly #github: PlatformGithubEventDispatchIntegration;\n readonly #storage: PlatformGithubEventStorage;\n readonly #ingestFactoryEvent: ((event: ParsedGithubWebhook) => Promise<unknown>) | undefined;\n readonly #reconcileFactoryState: GithubPullRequestReconciler | undefined;\n readonly #reconcileIssuesFactoryState: GithubIssueReconciler | undefined;\n readonly #pollEventsEnabled: boolean;\n readonly #pullRequestReconcileIntervalMs: number;\n readonly #issueReconcileIntervalMs: number;\n readonly #intervalMs: number;\n readonly #now: () => number;\n readonly #dispatch: typeof dispatchGithubWebhook;\n readonly #sourceControl: GithubReconcileRepositorySource;\n readonly #leaseOwner = randomUUID();\n\n #running = false;\n #timer: ReturnType<typeof setTimeout> | undefined;\n #leaseRenewalTimer: ReturnType<typeof setInterval> | undefined;\n #inFlight: Promise<void> | undefined;\n #leaseProvider: LeaseProvider = NoopLeaseProvider;\n #leaseTtlMs: number;\n #hasLease = false;\n #startedAt = 0;\n // Negative infinity so the first tick always sweeps, whatever the clock reads.\n #lastPullRequestReconcileAt = Number.NEGATIVE_INFINITY;\n #lastIssueReconcileAt = Number.NEGATIVE_INFINITY;\n #settings: PlatformGithubEventWorkerSettings = { version: 1, repositories: {} };\n\n constructor(config: PlatformGithubEventWorkerConfig) {\n super();\n this.#client = config.client;\n this.#controller = config.controller;\n this.#github = config.github;\n this.#storage = config.storage;\n this.#ingestFactoryEvent = config.ingestFactoryEvent;\n this.#reconcileFactoryState = config.reconcileFactoryState;\n this.#reconcileIssuesFactoryState = config.reconcileIssuesFactoryState;\n this.#pollEventsEnabled = config.pollEventsEnabled ?? true;\n const legacyReconcileIntervalMs = config.reconcileIntervalMs ?? DEFAULT_RECONCILE_INTERVAL_MS;\n this.#pullRequestReconcileIntervalMs = config.pullRequestReconcileIntervalMs ?? legacyReconcileIntervalMs;\n this.#issueReconcileIntervalMs = config.issueReconcileIntervalMs ?? legacyReconcileIntervalMs;\n this.#intervalMs = config.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n if (!Number.isFinite(this.#intervalMs) || this.#intervalMs <= 0) {\n throw new Error('Platform GitHub event polling interval must be a positive number.');\n }\n if (!Number.isFinite(this.#pullRequestReconcileIntervalMs) || this.#pullRequestReconcileIntervalMs <= 0) {\n throw new Error('Platform GitHub pull request reconcile interval must be a positive number.');\n }\n if (!Number.isFinite(this.#issueReconcileIntervalMs) || this.#issueReconcileIntervalMs <= 0) {\n throw new Error('Platform GitHub issue reconcile interval must be a positive number.');\n }\n this.#leaseTtlMs = Math.max(\n MIN_LEASE_TTL_MS,\n Math.min(this.#intervalMs, this.#pullRequestReconcileIntervalMs, this.#issueReconcileIntervalMs) * 3,\n );\n this.#now = config.now ?? Date.now;\n this.#dispatch = config.dispatch ?? dispatchGithubWebhook;\n this.#sourceControl = config.sourceControl;\n }\n\n async init(deps: WorkerDeps): Promise<void> {\n await super.init(deps);\n this.#leaseProvider = getLeaseProvider(deps.pubsub);\n }\n\n async start(): Promise<void> {\n if (this.#running) return;\n if (!this.deps) throw new Error('PlatformGithubEventWorker: call init() before start()');\n\n this.#startedAt = this.#now() - 1;\n this.#settings = normalizeSettings(await this.#storage.settings.get(CURSOR_ORG_ID, CURSOR_USER_ID));\n this.#running = true;\n this.deps.logger.info('Platform GitHub event polling started', {\n intervalMs: this.#intervalMs,\n leaseTtlMs: this.#leaseTtlMs,\n });\n this.#schedule(0);\n }\n\n async stop(): Promise<void> {\n if (!this.#running) return;\n this.#running = false;\n if (this.#timer) clearTimeout(this.#timer);\n this.#timer = undefined;\n this.#stopLeaseRenewal();\n await this.#inFlight;\n if (this.#hasLease) {\n await this.#leaseProvider.releaseLease(this.#leaseKey(), this.#leaseOwner).catch(() => undefined);\n this.#hasLease = false;\n }\n }\n\n get isRunning(): boolean {\n return this.#running;\n }\n\n #schedule(delayMs: number): void {\n if (!this.#running) return;\n this.#timer = setTimeout(() => {\n this.#timer = undefined;\n const run = this.#tick();\n this.#inFlight = run;\n void run.finally(() => {\n if (this.#inFlight === run) this.#inFlight = undefined;\n });\n }, delayMs);\n this.#timer.unref?.();\n }\n\n async #tick(): Promise<void> {\n let nextDelay = this.#intervalMs;\n try {\n if (!(await this.#ensureLease())) return;\n nextDelay = await this.#poll();\n } catch (error) {\n nextDelay = retryDelay(error, this.#intervalMs);\n this.deps?.logger.error('Platform GitHub event polling cycle failed', {\n error: error instanceof Error ? error.message : String(error),\n retryInMs: nextDelay,\n });\n } finally {\n this.#schedule(nextDelay);\n }\n }\n\n async #ensureLease(): Promise<boolean> {\n if (this.#hasLease) return true;\n const result = await this.#leaseProvider.acquireLease(this.#leaseKey(), this.#leaseOwner, this.#leaseTtlMs);\n this.#hasLease = result.acquired;\n if (this.#hasLease) this.#startLeaseRenewal();\n return this.#hasLease;\n }\n\n #startLeaseRenewal(): void {\n if (this.#leaseRenewalTimer) return;\n this.#leaseRenewalTimer = setInterval(\n () => {\n void this.#leaseProvider\n .renewLease(this.#leaseKey(), this.#leaseOwner, this.#leaseTtlMs)\n .then(renewed => {\n if (!renewed) {\n this.#hasLease = false;\n this.#stopLeaseRenewal();\n }\n })\n .catch(error => {\n this.#hasLease = false;\n this.#stopLeaseRenewal();\n this.deps?.logger.warn('Platform GitHub event polling lease renewal failed', {\n error: error instanceof Error ? error.message : String(error),\n });\n });\n },\n Math.floor(this.#leaseTtlMs / 3),\n );\n this.#leaseRenewalTimer.unref?.();\n }\n\n #stopLeaseRenewal(): void {\n if (this.#leaseRenewalTimer) clearInterval(this.#leaseRenewalTimer);\n this.#leaseRenewalTimer = undefined;\n }\n\n async #poll(): Promise<number> {\n const repositories = await this.#discoverRepositories();\n let retryInMs = this.#pollEventsEnabled ? this.#intervalMs : this.#reconcileCadence();\n\n if (this.#pollEventsEnabled) {\n for (const repository of repositories) {\n if (!this.#running || !this.#hasLease) break;\n try {\n await this.#pollRepository(repository.id);\n } catch (error) {\n const delay = retryDelay(error, this.#intervalMs);\n retryInMs = Math.max(retryInMs, delay);\n this.deps?.logger.error('Platform GitHub repository event polling failed', {\n repositoryId: repository.id,\n error: error instanceof Error ? error.message : String(error),\n retryInMs: delay,\n });\n if (error instanceof PlatformApiError && error.status === 429) break;\n }\n }\n }\n\n await this.#maybeReconcile(repositories);\n return retryInMs;\n }\n\n #reconcileCadence(): number {\n const cadences = [\n this.#reconcileFactoryState ? this.#pullRequestReconcileIntervalMs : undefined,\n this.#reconcileIssuesFactoryState ? this.#issueReconcileIntervalMs : undefined,\n ].filter((interval): interval is number => interval !== undefined);\n return cadences.length > 0 ? Math.min(...cadences) : this.#intervalMs;\n }\n\n async #maybeReconcile(repositories: Repository[]): Promise<void> {\n if (!this.#running || !this.#hasLease) return;\n const now = this.#now();\n const reconcilePullRequests = Boolean(\n this.#reconcileFactoryState && now - this.#lastPullRequestReconcileAt >= this.#pullRequestReconcileIntervalMs,\n );\n const reconcileIssues = Boolean(\n this.#reconcileIssuesFactoryState && now - this.#lastIssueReconcileAt >= this.#issueReconcileIntervalMs,\n );\n if (!reconcilePullRequests && !reconcileIssues) return;\n if (reconcilePullRequests) this.#lastPullRequestReconcileAt = now;\n if (reconcileIssues) this.#lastIssueReconcileAt = now;\n\n const targets: ReconcileRepository[] = repositories.flatMap(repository =>\n repository.fullName\n ? [{ id: repository.id, fullName: repository.fullName, installationId: repository.installationId }]\n : [],\n );\n if (targets.length === 0) {\n this.deps?.logger.debug('Platform GitHub reconciliation skipped: no named repositories');\n return;\n }\n\n if (reconcilePullRequests && this.#reconcileFactoryState) {\n const startedAt = Date.now();\n try {\n const { errors, ...counts } = await this.#reconcileFactoryState(targets);\n const context = { ...counts, candidateRepositories: targets.length, durationMs: Date.now() - startedAt };\n if (counts.failed > 0) {\n this.deps?.logger.warn('Platform GitHub pull request reconcile sweep completed with failures', {\n ...context,\n errors,\n });\n } else if (counts.merged > 0 || counts.closed > 0) {\n this.deps?.logger.info('Platform GitHub pull request reconcile replayed missed merges/closes', context);\n } else {\n this.deps?.logger.info('Platform GitHub pull request reconcile sweep completed', context);\n }\n } catch (error) {\n this.deps?.logger.error('Platform GitHub pull request reconcile failed', {\n repositories: targets.length,\n durationMs: Date.now() - startedAt,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n if (reconcileIssues && this.#reconcileIssuesFactoryState && this.#hasLease) {\n const startedAt = Date.now();\n try {\n const { errors, ...counts } = await this.#reconcileIssuesFactoryState(targets);\n const context = { ...counts, candidateRepositories: targets.length, durationMs: Date.now() - startedAt };\n if (counts.failed > 0) {\n this.deps?.logger.warn('Platform GitHub issue reconcile sweep completed with failures', { ...context, errors });\n } else if (counts.closed > 0) {\n this.deps?.logger.info('Platform GitHub issue reconcile replayed closed work items', context);\n } else if (counts.updated > 0) {\n this.deps?.logger.info('Platform GitHub issue reconcile patched stale metadata', context);\n } else {\n this.deps?.logger.debug('Platform GitHub issue reconcile sweep completed', context);\n }\n } catch (error) {\n this.deps?.logger.error('Platform GitHub issue reconcile failed', {\n repositories: targets.length,\n durationMs: Date.now() - startedAt,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n }\n\n /**\n * Configured `(installation, repository)` pairs — the same set the\n * reconciler sweeps — resolved to the numeric ids and slug the poll\n * addresses GitHub with. Repositories not linked to any factory project\n * cannot produce work for Factory, so polling and reconciling them is\n * wasted `/events` bandwidth and platform load.\n */\n async #discoverRepositories(): Promise<Repository[]> {\n const keys = await this.#sourceControl.projectRepositories.listConfiguredExternalKeys();\n const repositories = new Map<number, Repository>();\n for (const key of keys) {\n const installationId = Number(key.installationExternalId);\n const repositoryId = Number(key.repositoryExternalId);\n if (\n !Number.isSafeInteger(installationId) ||\n installationId <= 0 ||\n !Number.isSafeInteger(repositoryId) ||\n repositoryId <= 0\n ) {\n continue;\n }\n if (repositories.has(repositoryId)) continue;\n // A configured key exists per project link, so `listByExternalRepository`\n // always yields at least one row; the first row's orgId is enough to\n // look up the repository row for its slug.\n const projects = await this.#sourceControl.projectRepositories.listByExternalRepository(key);\n const orgId = projects[0]?.orgId;\n const repository = orgId\n ? await this.#sourceControl.repositories.findByExternalId({ orgId, externalId: key.repositoryExternalId })\n : null;\n repositories.set(repositoryId, {\n id: repositoryId,\n installationId,\n fullName: repository?.slug,\n });\n }\n return [...repositories.values()];\n }\n\n async #pollRepository(repositoryId: number): Promise<void> {\n const key = String(repositoryId);\n if (!this.#settings.repositories[key]) {\n this.#settings.repositories[key] = { afterTimestamp: this.#startedAt };\n await this.#saveSettings();\n }\n\n while (this.#running && this.#hasLease) {\n const cursor: EventCursor = this.#settings.repositories[key]!;\n const query = new URLSearchParams({ limit: String(EVENT_PAGE_SIZE) });\n if ('afterEventId' in cursor) query.set('afterEventId', cursor.afterEventId);\n else query.set('afterTimestamp', String(cursor.afterTimestamp));\n\n const pollStartedAt = performance.now();\n const page = await this.#client.request<{ events: EventLogEntry[]; nextCursor: string | null }>(\n 'GET',\n `${API_PREFIX}/repositories/${repositoryId}/events?${query}`,\n );\n this.deps?.logger.debug('Platform GitHub repository event poll completed', {\n repositoryId,\n eventCount: page.events.length,\n latencyMs: Math.round(performance.now() - pollStartedAt),\n });\n if (page.events.length === 0 || !page.nextCursor) return;\n\n for (const event of page.events) {\n if (!this.#running || !this.#hasLease) return;\n const parsed = parseEvent(event);\n if (!parsed) {\n this.deps?.logger.warn('Platform GitHub event log returned a malformed event', {\n repositoryId,\n eventId: event.id,\n });\n continue;\n }\n if (isFactoryIngestedEvent(parsed)) {\n await this.#ingestFactoryEvent?.(parsed);\n }\n const result = await this.#dispatch(parsed, {\n controller: this.#controller,\n listSubscriptions: (target, options) =>\n listPullRequestSubscriptionsForWebhook(target, options, this.#github.integrationStorage),\n retireSubscription: (id, status) =>\n retirePullRequestSubscription(id, status, this.#github.integrationStorage),\n github: this.#github,\n isAuthorizedSender: notification => this.#isAuthorizedSender(notification),\n onTargetSkipped: subscription => {\n // Routine when a subscription's thread belongs to another\n // deployment, so this stays at debug rather than warning on a loop.\n this.deps?.logger.debug('Platform GitHub event skipped: thread is not held here', {\n deliveryId: event.deliveryId,\n subscriptionId: subscription.id,\n threadId: subscription.threadId,\n });\n },\n onSenderRejected: notification => {\n this.deps?.logger.debug('Platform GitHub event dropped: sender not authorized', {\n deliveryId: event.deliveryId,\n repository: notification.metadata.repository,\n sender: notification.metadata.sender,\n kind: notification.kind,\n });\n },\n onTargetError: (subscription, error) => {\n this.deps?.logger.error('Platform GitHub event delivery failed for a subscription', {\n deliveryId: event.deliveryId,\n subscriptionId: subscription.id,\n resourceId: subscription.resourceId,\n threadId: subscription.threadId,\n error: error instanceof Error ? error.message : String(error),\n });\n },\n });\n if (result.failed > 0) {\n this.deps?.logger.warn('Platform GitHub event completed with failed subscription deliveries', {\n repositoryId,\n deliveryId: event.deliveryId,\n delivered: result.delivered,\n failed: result.failed,\n });\n }\n }\n\n if (page.nextCursor === ('afterEventId' in cursor ? cursor.afterEventId : undefined)) return;\n this.#settings.repositories[key] = { afterEventId: page.nextCursor };\n await this.#saveSettings();\n }\n }\n\n async #isAuthorizedSender(notification: GithubWebhookNotification): Promise<boolean> {\n if (!AUTHOR_GATED_KINDS.has(notification.kind)) return true;\n const sender = notification.metadata.sender;\n const repository = notification.metadata.repository;\n if (!sender || !repository) return false;\n // Factory's own app has to clear the gate before the bot rules below, which\n // fail closed for every bot that is not explicitly allowlisted. GitHub\n // forbids an app from reviewing its own pull request, so `factory-review`\n // posts its verdict as a comment under this login and that comment is the\n // handoff the authoring agent wakes on.\n if (this.#github.identity?.matches(sender)) return true;\n if (isFactoryAppSender(sender, this.#github.slug)) return true;\n const normalizedSender = sender.toLowerCase();\n const authorizedBots = resolveAuthorizedBots(this.#github.authorizedBots);\n if (authorizedBots.has(normalizedSender)) return true;\n // Any other bot is gated purely by the allowlist: a GitHub App never holds a\n // collaborator permission under its sender login, so falling through to the\n // permission lookup would only fail closed after a wasted API call.\n if (notification.metadata.senderType?.toLowerCase() === 'bot' || normalizedSender.endsWith('[bot]')) {\n return false;\n }\n\n const abortController = new AbortController();\n const timeout = setTimeout(() => abortController.abort(), PERMISSION_CHECK_TIMEOUT_MS);\n try {\n const permission = await this.#github.getRepositoryCollaboratorPermission(\n notification.metadata.installationId,\n repository,\n sender,\n abortController.signal,\n );\n return permission !== undefined && AUTHORIZED_PERMISSIONS.has(permission);\n } catch {\n return false;\n } finally {\n clearTimeout(timeout);\n }\n }\n\n async #saveSettings(): Promise<void> {\n await this.#storage.settings.save(CURSOR_ORG_ID, CURSOR_USER_ID, this.#settings);\n }\n\n #leaseKey(): string {\n return `${this.name}:${this.#storage.integrationId}`;\n }\n}\n\nfunction getLeaseProvider(pubsub: PubSub): LeaseProvider {\n const getProvider = (pubsub as PubSub & { getLeaseProvider?: () => LeaseProvider | undefined }).getLeaseProvider;\n if (typeof getProvider === 'function') return getProvider.call(pubsub) ?? NoopLeaseProvider;\n return isLeaseProvider(pubsub) ? pubsub : NoopLeaseProvider;\n}\n\nfunction normalizeSettings(value: PlatformGithubEventWorkerSettings | null): PlatformGithubEventWorkerSettings {\n if (!value || value.version !== 1 || !value.repositories || typeof value.repositories !== 'object') {\n return { version: 1, repositories: {} };\n }\n return { version: 1, repositories: { ...value.repositories } };\n}\n\n// Events the polling worker forwards to the factory rules engine. Closures\n// let the reconciler finalize cards; `synchronize` and `review_requested` on a\n// pull request are the triggers the review board's re-review path listens for;\n// submitted reviews and pull request comments are how review feedback reaches\n// the agent that authored the branch. Direct-webhook consumers ingest every\n// parsed event; the platform path gates because the remaining events (issue\n// edits, comment edits and deletions) only interest the subscription\n// dispatcher, not the factory rules.\nfunction isFactoryIngestedEvent(event: ParsedGithubWebhook): boolean {\n if ((event.event === 'issues' || event.event === 'pull_request') && event.payload.action === 'closed') {\n return true;\n }\n if (event.event === 'pull_request') {\n const action = event.payload.action;\n // `opened` is what mints the Review card for a pull request. Without it a\n // factory-authored PR never gets reviewed on the polling path, which is the\n // only path a local deployment has.\n if (action === 'opened' || action === 'synchronize' || action === 'review_requested') return true;\n }\n if (event.event === 'pull_request_review' && event.payload.action === 'submitted') return true;\n if (event.event === 'issue_comment' && event.payload.action === 'created') return true;\n // Default-branch pushes drive base-checkpoint rebuilds\n // (`withBaseCheckpointWebhookTrigger` wraps the ingest callback).\n if (event.event === 'push') return true;\n return false;\n}\n\nfunction parseEvent(event: EventLogEntry): ParsedGithubWebhook | null {\n if (\n !event.id ||\n !event.deliveryId ||\n !SUPPORTED_EVENTS.has(event.event) ||\n !event.payload ||\n typeof event.payload !== 'object' ||\n Array.isArray(event.payload)\n ) {\n return null;\n }\n return {\n event: event.event,\n deliveryId: event.deliveryId,\n payload: event.payload as Record<string, unknown>,\n };\n}\n\nfunction retryDelay(error: unknown, fallbackMs: number): number {\n if (error instanceof PlatformApiError && error.status === 429 && error.retryAfterSeconds !== null) {\n return Math.max(fallbackMs, error.retryAfterSeconds * 1_000);\n }\n return fallbackMs;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/github/integration.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/integrations/platform/github/integration.ts"],"names":[],"mappings":"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"}