From 2ed4d445f736c5fc343206939647c911fa8fcceb Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Wed, 26 Nov 2025 23:03:09 -0800 Subject: [PATCH] make codex yolo --- agents/codex.ts | 3 +- entry | 765 ++++++++++++++++++++++++++---------------------- 2 files changed, 421 insertions(+), 347 deletions(-) diff --git a/agents/codex.ts b/agents/codex.ts index 83938fa..b2386eb 100644 --- a/agents/codex.ts +++ b/agents/codex.ts @@ -42,7 +42,8 @@ export const codex = agent({ const codex = new Codex(codexOptions); const thread = codex.startThread({ approvalPolicy: "never", - sandboxMode: "workspace-write", + // use danger-full-access to allow git operations (workspace-write blocks .git directory writes) + sandboxMode: "danger-full-access", networkAccessEnabled: true, }); diff --git a/entry b/entry index 5dc8835..099c90b 100755 --- a/entry +++ b/entry @@ -18153,7 +18153,7 @@ var require_summary = __commonJS({ exports.summary = exports.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0; var os_1 = __require("os"); var fs_1 = __require("fs"); - var { access, appendFile, writeFile: writeFile2 } = fs_1.promises; + var { access, appendFile, writeFile } = fs_1.promises; exports.SUMMARY_ENV_VAR = "GITHUB_STEP_SUMMARY"; exports.SUMMARY_DOCS_URL = "https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary"; var Summary = class { @@ -18211,7 +18211,7 @@ var require_summary = __commonJS({ return __awaiter(this, void 0, void 0, function* () { const overwrite = !!(options === null || options === void 0 ? void 0 : options.overwrite); const filePath = yield this.filePath(); - const writeFunc = overwrite ? writeFile2 : appendFile; + const writeFunc = overwrite ? writeFile : appendFile; yield writeFunc(filePath, this._buffer, { encoding: "utf8" }); return this.emptyBuffer(); }); @@ -47775,7 +47775,7 @@ var require_snapshot_utils = __commonJS({ var require_snapshot_recorder = __commonJS({ "../node_modules/.pnpm/undici@7.16.0/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) { "use strict"; - var { writeFile: writeFile2, readFile, mkdir } = __require("node:fs/promises"); + var { writeFile, readFile, mkdir } = __require("node:fs/promises"); var { dirname: dirname2, resolve } = __require("node:path"); var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers"); var { InvalidArgumentError, UndiciError } = require_errors2(); @@ -48005,7 +48005,7 @@ var require_snapshot_recorder = __commonJS({ hash, snapshot: snapshot2 })); - await writeFile2(resolvedPath, JSON.stringify(data, null, 2), { flush: true }); + await writeFile(resolvedPath, JSON.stringify(data, null, 2), { flush: true }); } /** * Clears all recorded snapshots @@ -83934,8 +83934,6 @@ var package_default = { // utils/cli.ts var core = __toESM(require_core(), 1); var import_table = __toESM(require_src(), 1); -import { appendFileSync as appendFileSync2, existsSync as existsSync2 } from "node:fs"; -import { join as join4 } from "node:path"; var isGitHubActions = !!process.env.GITHUB_ACTIONS; var isDebugEnabled = () => process.env.LOG_LEVEL === "debug"; function startGroup2(name) { @@ -84179,31 +84177,8 @@ var log = { output += formatIndentedField("input", inputFormatted); } log.info(output.trimEnd()); - }, - /** - * Log MCP tool call information to mcpLog.txt in the temp directory - */ - toolCallToFile: ({ - toolName, - request: request2, - result, - error: error41 - }) => { - const logPath = getMcpLogPath(); - const params = { toolName, request: request2 }; - if (error41) { - params.error = error41; - } else if (result) { - params.result = result; - } - const logEntry = formatToolCall(params); - appendFileSync2(logPath, logEntry, "utf-8"); } }; -function getMcpLogPath() { - const tempDir = process.env.PULLFROG_TEMP_DIR; - return join4(tempDir, "mcpLog.txt"); -} function formatJsonValue(value2) { const compact = JSON.stringify(value2); return compact.length > 80 || compact.includes("\n") ? JSON.stringify(value2, null, 2) : compact; @@ -84222,39 +84197,6 @@ function formatIndentedField(label, content) { } return formatted; } -function formatToolInput(request2) { - const requestFormatted = formatJsonValue(request2); - if (requestFormatted === "{}") { - return ""; - } - return formatIndentedField("input", requestFormatted); -} -function formatToolResult(result) { - try { - const parsed2 = JSON.parse(result); - const formatted = formatJsonValue(parsed2); - return formatIndentedField("result", formatted); - } catch { - return formatIndentedField("result", result); - } -} -function formatToolCall({ - toolName, - request: request2, - result, - error: error41 -}) { - let logEntry = `\u2192 ${toolName} -`; - logEntry += formatToolInput(request2); - if (error41) { - logEntry += formatIndentedField("error", error41); - } else if (result) { - logEntry += formatToolResult(result); - } - logEntry += "\n"; - return logEntry; -} // ../node_modules/.pnpm/@toon-format+toon@1.0.0/node_modules/@toon-format/toon/dist/index.js var LIST_ITEM_MARKER = "-"; @@ -92415,11 +92357,216 @@ ${encode(payload.event)}`; // agents/shared.ts import { spawnSync } from "node:child_process"; -import { chmodSync, createWriteStream as createWriteStream2, existsSync as existsSync3 } from "node:fs"; +import { chmodSync, createWriteStream as createWriteStream2, existsSync as existsSync2 } from "node:fs"; import { mkdtemp } from "node:fs/promises"; import { tmpdir } from "node:os"; -import { join as join5 } from "node:path"; +import { join as join4 } from "node:path"; import { pipeline } from "node:stream/promises"; + +// utils/github.ts +var core2 = __toESM(require_core(), 1); +import { createSign } from "node:crypto"; +function isGitHubActionsEnvironment() { + return Boolean(process.env.GITHUB_ACTIONS); +} +async function acquireTokenViaOIDC() { + log.info("Generating OIDC token..."); + const oidcToken = await core2.getIDToken("pullfrog-api"); + log.info("OIDC token generated successfully"); + const apiUrl = process.env.API_URL || "https://pullfrog.ai"; + log.info("Exchanging OIDC token for installation token..."); + const timeoutMs = 5e3; + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeoutMs); + try { + const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, { + method: "POST", + headers: { + Authorization: `Bearer ${oidcToken}`, + "Content-Type": "application/json" + }, + signal: controller.signal + }); + clearTimeout(timeoutId); + if (!tokenResponse.ok) { + throw new Error(`Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText}`); + } + const tokenData = await tokenResponse.json(); + log.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`); + return tokenData.token; + } catch (error41) { + clearTimeout(timeoutId); + if (error41 instanceof Error && error41.name === "AbortError") { + throw new Error(`Token exchange timed out after ${timeoutMs}ms`); + } + throw error41; + } +} +var base64UrlEncode = (str) => { + return Buffer.from(str).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); +}; +var generateJWT = (appId, privateKey) => { + const now = Math.floor(Date.now() / 1e3); + const payload = { + iat: now - 60, + exp: now + 5 * 60, + iss: appId + }; + const header = { + alg: "RS256", + typ: "JWT" + }; + const encodedHeader = base64UrlEncode(JSON.stringify(header)); + const encodedPayload = base64UrlEncode(JSON.stringify(payload)); + const signaturePart = `${encodedHeader}.${encodedPayload}`; + const signature = createSign("RSA-SHA256").update(signaturePart).sign(privateKey, "base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); + return `${signaturePart}.${signature}`; +}; +var githubRequest = async (path3, options = {}) => { + const { method = "GET", headers = {}, body } = options; + const url2 = `https://api.github.com${path3}`; + const requestHeaders = { + Accept: "application/vnd.github.v3+json", + "User-Agent": "Pullfrog-Installation-Token-Generator/1.0", + ...headers + }; + const response = await fetch(url2, { + method, + headers: requestHeaders, + ...body && { body } + }); + if (!response.ok) { + const errorText = await response.text(); + throw new Error( + `GitHub API request failed: ${response.status} ${response.statusText} +${errorText}` + ); + } + return response.json(); +}; +var checkRepositoryAccess = async (token, repoOwner, repoName) => { + try { + const response = await githubRequest("/installation/repositories", { + headers: { Authorization: `token ${token}` } + }); + return response.repositories.some( + (repo) => repo.owner.login === repoOwner && repo.name === repoName + ); + } catch { + return false; + } +}; +var createInstallationToken = async (jwt, installationId) => { + const response = await githubRequest( + `/app/installations/${installationId}/access_tokens`, + { + method: "POST", + headers: { Authorization: `Bearer ${jwt}` } + } + ); + return response.token; +}; +var findInstallationId = async (jwt, repoOwner, repoName) => { + const installations = await githubRequest("/app/installations", { + headers: { Authorization: `Bearer ${jwt}` } + }); + for (const installation of installations) { + try { + const tempToken = await createInstallationToken(jwt, installation.id); + const hasAccess = await checkRepositoryAccess(tempToken, repoOwner, repoName); + if (hasAccess) { + return installation.id; + } + } catch { + } + } + throw new Error( + `No installation found with access to ${repoOwner}/${repoName}. Ensure the GitHub App is installed on the target repository.` + ); +}; +async function acquireTokenViaGitHubApp() { + const repoContext = parseRepoContext(); + const config2 = { + appId: process.env.GITHUB_APP_ID, + privateKey: process.env.GITHUB_PRIVATE_KEY?.replace(/\\n/g, "\n"), + repoOwner: repoContext.owner, + repoName: repoContext.name + }; + const jwt = generateJWT(config2.appId, config2.privateKey); + const installationId = await findInstallationId(jwt, config2.repoOwner, config2.repoName); + const token = await createInstallationToken(jwt, installationId); + return token; +} +async function acquireNewToken() { + if (isGitHubActionsEnvironment()) { + return await acquireTokenViaOIDC(); + } else { + return await acquireTokenViaGitHubApp(); + } +} +var githubInstallationToken; +async function setupGitHubInstallationToken() { + const acquiredToken = await acquireNewToken(); + core2.setSecret(acquiredToken); + githubInstallationToken = acquiredToken; + return acquiredToken; +} +function getGitHubInstallationToken() { + if (!githubInstallationToken) { + throw new Error("GitHub installation token not set. Call setupGitHubInstallationToken first."); + } + return githubInstallationToken; +} +async function revokeGitHubInstallationToken() { + if (!githubInstallationToken) { + return; + } + const token = githubInstallationToken; + githubInstallationToken = void 0; + const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com"; + try { + await fetch(`${apiUrl}/installation/token`, { + method: "DELETE", + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${token}`, + "X-GitHub-Api-Version": "2022-11-28" + } + }); + log.info("Installation token revoked"); + } catch (error41) { + log.warning( + `Failed to revoke installation token: ${error41 instanceof Error ? error41.message : String(error41)}` + ); + } +} +function parseRepoContext() { + const githubRepo = process.env.GITHUB_REPOSITORY; + if (!githubRepo) { + throw new Error("GITHUB_REPOSITORY environment variable is required"); + } + const [owner, name] = githubRepo.split("/"); + if (!owner || !name) { + throw new Error(`Invalid GITHUB_REPOSITORY format: ${githubRepo}. Expected 'owner/repo'`); + } + return { owner, name }; +} + +// agents/shared.ts +function createAgentEnv(agentSpecificVars) { + return { + PATH: process.env.PATH, + HOME: process.env.HOME, + LOG_LEVEL: process.env.LOG_LEVEL, + NODE_ENV: process.env.NODE_ENV, + GITHUB_TOKEN: getGitHubInstallationToken(), + ...agentSpecificVars + // values could be undefined but will be ignored + }; +} +function setupProcessAgentEnv(agentSpecificVars) { + Object.assign(process.env, createAgentEnv(agentSpecificVars)); +} async function installFromNpmTarball({ packageName, version: version2, @@ -92447,7 +92594,7 @@ async function installFromNpmTarball({ } log.info(`\u{1F4E6} Installing ${packageName}@${resolvedVersion}...`); const tempDir = process.env.PULLFROG_TEMP_DIR; - const tarballPath = join5(tempDir, "package.tgz"); + const tarballPath = join4(tempDir, "package.tgz"); const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org"; let tarballUrl; if (packageName.startsWith("@")) { @@ -92476,9 +92623,9 @@ async function installFromNpmTarball({ `Failed to extract tarball: ${extractResult.stderr || extractResult.stdout || "Unknown error"}` ); } - const extractedDir = join5(tempDir, "package"); - const cliPath = join5(extractedDir, executablePath); - if (!existsSync3(cliPath)) { + const extractedDir = join4(tempDir, "package"); + const cliPath = join4(extractedDir, executablePath); + if (!existsSync2(cliPath)) { throw new Error(`Executable not found in extracted package at ${cliPath}`); } if (installDependencies) { @@ -92524,10 +92671,10 @@ async function installFromGithub({ const assetUrl = asset.browser_download_url; log.info(`Downloading asset from ${assetUrl}...`); const tempDirPrefix = `${owner}-${repo}-github-`; - const tempDir = await mkdtemp(join5(tmpdir(), tempDirPrefix)); + const tempDir = await mkdtemp(join4(tmpdir(), tempDirPrefix)); const urlPath = new URL(assetUrl).pathname; const fileName2 = urlPath.split("/").pop() || "asset"; - const downloadPath = join5(tempDir, fileName2); + const downloadPath = join4(tempDir, fileName2); const assetResponse = await fetch(assetUrl); if (!assetResponse.ok) { throw new Error( @@ -92540,11 +92687,11 @@ async function installFromGithub({ log.info(`Downloaded asset to ${downloadPath}`); let cliPath; if (executablePath) { - cliPath = join5(tempDir, executablePath); + cliPath = join4(tempDir, executablePath); } else { cliPath = downloadPath; } - if (!existsSync3(cliPath)) { + if (!existsSync2(cliPath)) { throw new Error(`Executable not found at ${cliPath}`); } chmodSync(cliPath, 493); @@ -92557,7 +92704,7 @@ async function installFromCurl({ }) { log.info(`\u{1F4E6} Installing ${executableName}...`); const tempDir = process.env.PULLFROG_TEMP_DIR; - const installScriptPath = join5(tempDir, "install.sh"); + const installScriptPath = join4(tempDir, "install.sh"); log.info(`Downloading install script from ${installUrl}...`); const installScriptResponse = await fetch(installUrl); if (!installScriptResponse.ok) { @@ -92572,12 +92719,11 @@ async function installFromCurl({ const installResult = spawnSync("bash", [installScriptPath], { cwd: tempDir, env: { + // Run the install script with HOME set to temp directory + // ensuring a fresh install for each run HOME: tempDir, - // Cursor install script uses HOME for installation path - PATH: process.env.PATH || "", - SHELL: process.env.SHELL || "/bin/bash", - USER: process.env.USER || "", - TMPDIR: process.env.TMPDIR || "/tmp" + SHELL: process.env.SHELL, + USER: process.env.USER }, stdio: "pipe", encoding: "utf-8" @@ -92588,8 +92734,8 @@ async function installFromCurl({ `Failed to install ${executableName}. Install script exited with code ${installResult.status}. Output: ${errorOutput}` ); } - const cliPath = join5(tempDir, ".local", "bin", executableName); - if (!existsSync3(cliPath)) { + const cliPath = join4(tempDir, ".local", "bin", executableName); + if (!existsSync2(cliPath)) { throw new Error(`Executable not found at ${cliPath}`); } chmodSync(cliPath, 493); @@ -92612,7 +92758,7 @@ var claude = agent({ }); }, run: async ({ payload, mcpServers, apiKey, cliPath }) => { - process.env.ANTHROPIC_API_KEY = apiKey; + setupProcessAgentEnv({ ANTHROPIC_API_KEY: apiKey }); const prompt = addInstructions(payload); console.log(prompt); const queryInstance = query({ @@ -92711,7 +92857,7 @@ var messageHandlers = { // agents/codex.ts import { spawnSync as spawnSync2 } from "node:child_process"; import { mkdirSync as mkdirSync2 } from "node:fs"; -import { join as join6 } from "node:path"; +import { join as join5 } from "node:path"; // ../node_modules/.pnpm/@openai+codex-sdk@0.58.0/node_modules/@openai/codex-sdk/dist/index.js import { promises as fs2 } from "fs"; @@ -93046,13 +93192,14 @@ var codex = agent({ executablePath: "bin/codex.js" }); }, - run: async ({ payload, mcpServers, apiKey, cliPath, githubInstallationToken }) => { - process.env.OPENAI_API_KEY = apiKey; - process.env.GITHUB_INSTALLATION_TOKEN = githubInstallationToken; + run: async ({ payload, mcpServers, apiKey, cliPath }) => { const tempHome = process.env.PULLFROG_TEMP_DIR; - const configDir = join6(tempHome, ".config", "codex"); + const configDir = join5(tempHome, ".config", "codex"); mkdirSync2(configDir, { recursive: true }); - process.env.HOME = tempHome; + setupProcessAgentEnv({ + OPENAI_API_KEY: apiKey, + HOME: tempHome + }); configureCodexMcpServers({ mcpServers, cliPath }); const codexOptions = { apiKey, @@ -93061,7 +93208,8 @@ var codex = agent({ const codex2 = new Codex(codexOptions); const thread = codex2.startThread({ approvalPolicy: "never", - sandboxMode: "workspace-write", + // use danger-full-access to allow git operations (workspace-write blocks .git directory writes) + sandboxMode: "danger-full-access", networkAccessEnabled: true }); try { @@ -93198,7 +93346,7 @@ function configureCodexMcpServers({ mcpServers, cliPath }) { import { spawn as spawn3 } from "node:child_process"; import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "node:fs"; import { homedir as homedir2 } from "node:os"; -import { join as join7 } from "node:path"; +import { join as join6 } from "node:path"; var messageHandlers3 = { system: (_event) => { }, @@ -93251,9 +93399,7 @@ var cursor = agent({ executableName: "cursor-agent" }); }, - run: async ({ payload, apiKey, cliPath, githubInstallationToken, mcpServers }) => { - process.env.CURSOR_API_KEY = apiKey; - process.env.GITHUB_INSTALLATION_TOKEN = githubInstallationToken; + run: async ({ payload, apiKey, cliPath, mcpServers }) => { configureCursorMcpServers({ mcpServers, cliPath }); try { const fullPrompt = addInstructions(payload); @@ -93273,16 +93419,9 @@ var cursor = agent({ ], { cwd: process.cwd(), - env: { - CURSOR_API_KEY: apiKey, - GITHUB_INSTALLATION_TOKEN: githubInstallationToken, - LOG_LEVEL: process.env.LOG_LEVEL, - NODE_ENV: process.env.NODE_ENV, - HOME: process.env.HOME, - PATH: process.env.PATH - // Don't override HOME - Cursor CLI needs access to macOS keychain - // MCP config is written to tempDir/.cursor/mcp.json which Cursor will find - }, + env: createAgentEnv({ + CURSOR_API_KEY: apiKey + }), stdio: ["ignore", "pipe", "pipe"] // Ignore stdin, pipe stdout/stderr } @@ -93356,8 +93495,8 @@ var cursor = agent({ }); function configureCursorMcpServers({ mcpServers }) { const realHome = homedir2(); - const cursorConfigDir = join7(realHome, ".cursor"); - const mcpConfigPath = join7(cursorConfigDir, "mcp.json"); + const cursorConfigDir = join6(realHome, ".cursor"); + const mcpConfigPath = join6(cursorConfigDir, "mcp.json"); mkdirSync3(cursorConfigDir, { recursive: true }); const cursorMcpServers = {}; for (const [serverName, serverConfig] of Object.entries(mcpServers)) { @@ -93537,13 +93676,11 @@ var gemini = agent({ assetName: "gemini.js" }); }, - run: async ({ payload, apiKey, mcpServers, githubInstallationToken, cliPath }) => { + run: async ({ payload, apiKey, mcpServers, cliPath }) => { configureGeminiMcpServers({ mcpServers, cliPath }); if (!apiKey) { throw new Error("google_api_key or gemini_api_key is required for gemini agent"); } - process.env.GEMINI_API_KEY = apiKey; - process.env.GITHUB_INSTALLATION_TOKEN = githubInstallationToken; const sessionPrompt = addInstructions(payload); log.info(`Starting Gemini CLI with prompt: ${payload.prompt.substring(0, 100)}...`); let finalOutput = ""; @@ -93551,15 +93688,9 @@ var gemini = agent({ const result = await spawn4({ cmd: "node", args: [cliPath, "--yolo", "--output-format=stream-json", "-p", sessionPrompt], - env: { - PATH: process.env.PATH || "", - HOME: process.env.HOME || "", - TMPDIR: process.env.TMPDIR || "/tmp", - GEMINI_API_KEY: apiKey, - GITHUB_INSTALLATION_TOKEN: githubInstallationToken, - LOG_LEVEL: process.env.LOG_LEVEL, - NODE_ENV: process.env.NODE_ENV - }, + env: createAgentEnv({ + GEMINI_API_KEY: apiKey + }), timeout: 6e5, // 10 minutes onStdout: async (chunk) => { @@ -93648,10 +93779,9 @@ var agents = { }; // main.ts -import { existsSync as existsSync4, readFileSync as readFileSync2 } from "node:fs"; -import { mkdtemp as mkdtemp2, writeFile } from "node:fs/promises"; +import { mkdtemp as mkdtemp2 } from "node:fs/promises"; import { tmpdir as tmpdir2 } from "node:os"; -import { join as join8 } from "node:path"; +import { join as join7 } from "node:path"; // mcp/config.ts function createMcpConfigs(mcpServerUrl) { @@ -121360,183 +121490,6 @@ var Octokit2 = Octokit.plugin(requestLog, legacyRestEndpointMethods, paginateRes } ); -// utils/github.ts -var core2 = __toESM(require_core(), 1); -import { createSign } from "node:crypto"; -function isGitHubActionsEnvironment() { - return Boolean(process.env.GITHUB_ACTIONS); -} -async function acquireTokenViaOIDC() { - log.info("Generating OIDC token..."); - const oidcToken = await core2.getIDToken("pullfrog-api"); - log.info("OIDC token generated successfully"); - const apiUrl = process.env.API_URL || "https://pullfrog.ai"; - log.info("Exchanging OIDC token for installation token..."); - const timeoutMs = 5e3; - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), timeoutMs); - try { - const tokenResponse = await fetch(`${apiUrl}/api/github/installation-token`, { - method: "POST", - headers: { - Authorization: `Bearer ${oidcToken}`, - "Content-Type": "application/json" - }, - signal: controller.signal - }); - clearTimeout(timeoutId); - if (!tokenResponse.ok) { - throw new Error(`Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText}`); - } - const tokenData = await tokenResponse.json(); - log.info(`Installation token obtained for ${tokenData.repository || "all repositories"}`); - return tokenData.token; - } catch (error41) { - clearTimeout(timeoutId); - if (error41 instanceof Error && error41.name === "AbortError") { - throw new Error(`Token exchange timed out after ${timeoutMs}ms`); - } - throw error41; - } -} -var base64UrlEncode = (str) => { - return Buffer.from(str).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); -}; -var generateJWT = (appId, privateKey) => { - const now = Math.floor(Date.now() / 1e3); - const payload = { - iat: now - 60, - exp: now + 5 * 60, - iss: appId - }; - const header = { - alg: "RS256", - typ: "JWT" - }; - const encodedHeader = base64UrlEncode(JSON.stringify(header)); - const encodedPayload = base64UrlEncode(JSON.stringify(payload)); - const signaturePart = `${encodedHeader}.${encodedPayload}`; - const signature = createSign("RSA-SHA256").update(signaturePart).sign(privateKey, "base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); - return `${signaturePart}.${signature}`; -}; -var githubRequest = async (path3, options = {}) => { - const { method = "GET", headers = {}, body } = options; - const url2 = `https://api.github.com${path3}`; - const requestHeaders = { - Accept: "application/vnd.github.v3+json", - "User-Agent": "Pullfrog-Installation-Token-Generator/1.0", - ...headers - }; - const response = await fetch(url2, { - method, - headers: requestHeaders, - ...body && { body } - }); - if (!response.ok) { - const errorText = await response.text(); - throw new Error( - `GitHub API request failed: ${response.status} ${response.statusText} -${errorText}` - ); - } - return response.json(); -}; -var checkRepositoryAccess = async (token, repoOwner, repoName) => { - try { - const response = await githubRequest("/installation/repositories", { - headers: { Authorization: `token ${token}` } - }); - return response.repositories.some( - (repo) => repo.owner.login === repoOwner && repo.name === repoName - ); - } catch { - return false; - } -}; -var createInstallationToken = async (jwt, installationId) => { - const response = await githubRequest( - `/app/installations/${installationId}/access_tokens`, - { - method: "POST", - headers: { Authorization: `Bearer ${jwt}` } - } - ); - return response.token; -}; -var findInstallationId = async (jwt, repoOwner, repoName) => { - const installations = await githubRequest("/app/installations", { - headers: { Authorization: `Bearer ${jwt}` } - }); - for (const installation of installations) { - try { - const tempToken = await createInstallationToken(jwt, installation.id); - const hasAccess = await checkRepositoryAccess(tempToken, repoOwner, repoName); - if (hasAccess) { - return installation.id; - } - } catch { - } - } - throw new Error( - `No installation found with access to ${repoOwner}/${repoName}. Ensure the GitHub App is installed on the target repository.` - ); -}; -async function acquireTokenViaGitHubApp() { - const repoContext = parseRepoContext(); - const config2 = { - appId: process.env.GITHUB_APP_ID, - privateKey: process.env.GITHUB_PRIVATE_KEY?.replace(/\\n/g, "\n"), - repoOwner: repoContext.owner, - repoName: repoContext.name - }; - const jwt = generateJWT(config2.appId, config2.privateKey); - const installationId = await findInstallationId(jwt, config2.repoOwner, config2.repoName); - const token = await createInstallationToken(jwt, installationId); - return token; -} -async function acquireNewToken() { - if (isGitHubActionsEnvironment()) { - return await acquireTokenViaOIDC(); - } else { - return await acquireTokenViaGitHubApp(); - } -} -async function setupGitHubInstallationToken() { - const acquiredToken = await acquireNewToken(); - core2.setSecret(acquiredToken); - process.env.GITHUB_INSTALLATION_TOKEN = acquiredToken; - return acquiredToken; -} -async function revokeInstallationToken(token) { - const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com"; - try { - await fetch(`${apiUrl}/installation/token`, { - method: "DELETE", - headers: { - Accept: "application/vnd.github+json", - Authorization: `Bearer ${token}`, - "X-GitHub-Api-Version": "2022-11-28" - } - }); - log.info("Installation token revoked"); - } catch (error41) { - log.warning( - `Failed to revoke installation token: ${error41 instanceof Error ? error41.message : String(error41)}` - ); - } -} -function parseRepoContext() { - const githubRepo = process.env.GITHUB_REPOSITORY; - if (!githubRepo) { - throw new Error("GITHUB_REPOSITORY environment variable is required"); - } - const [owner, name] = githubRepo.split("/"); - if (!owner || !name) { - throw new Error(`Invalid GITHUB_REPOSITORY format: ${githubRepo}. Expected 'owner/repo'`); - } - return { owner, name }; -} - // mcp/shared.ts function getPayload() { const payloadEnv = process.env.PULLFROG_PAYLOAD; @@ -121552,14 +121505,10 @@ function getPayload() { } } function getMcpContext() { - const githubInstallationToken = process.env.GITHUB_INSTALLATION_TOKEN; - if (!githubInstallationToken) { - throw new Error("GITHUB_INSTALLATION_TOKEN environment variable is required"); - } return { ...parseRepoContext(), octokit: new Octokit2({ - auth: githubInstallationToken + auth: getGitHubInstallationToken() }), payload: getPayload() }; @@ -121571,14 +121520,16 @@ var addTools = (server, tools) => { } return server; }; -var contextualize = (executor) => async (params) => { - try { - const ctx = getMcpContext(); - const result = await executor(params, ctx); - return handleToolSuccess(result); - } catch (error41) { - return handleToolError(error41); - } +var contextualize = (executor) => { + return async (params) => { + try { + const ctx = getMcpContext(); + const result = await executor(params, ctx); + return handleToolSuccess(result); + } catch (error41) { + return handleToolError(error41); + } + }; }; var handleToolSuccess = (data) => { return { @@ -121933,6 +121884,154 @@ var IssueTool = tool({ }) }); +// mcp/issueComments.ts +var GetIssueComments = type({ + issue_number: type.number.describe("The issue number to get comments for") +}); +var GetIssueCommentsTool = tool({ + name: "get_issue_comments", + description: "Get all comments for a GitHub issue. Returns all comments including the issue body and all subsequent discussion comments.", + parameters: GetIssueComments, + execute: contextualize(async ({ issue_number }, ctx) => { + const comments = await ctx.octokit.paginate(ctx.octokit.rest.issues.listComments, { + owner: ctx.owner, + repo: ctx.name, + issue_number + }); + return { + issue_number, + comments: comments.map((comment) => ({ + id: comment.id, + body: comment.body, + user: comment.user?.login, + created_at: comment.created_at, + updated_at: comment.updated_at, + html_url: comment.html_url, + author_association: comment.author_association, + reactions: comment.reactions + })), + count: comments.length + }; + }) +}); + +// mcp/issueEvents.ts +var GetIssueEvents = type({ + issue_number: type.number.describe("The issue number to get events for") +}); +var GetIssueEventsTool = tool({ + name: "get_issue_events", + description: "Get timeline events for a GitHub issue that aren't reflected in the current state. Returns cross-references to other issues/PRs and commit references. Note: current labels, assignees, state, and milestone are already available via get_issue.", + parameters: GetIssueEvents, + execute: contextualize(async ({ issue_number }, ctx) => { + const events = await ctx.octokit.paginate(ctx.octokit.rest.issues.listEventsForTimeline, { + owner: ctx.owner, + repo: ctx.name, + issue_number + }); + const relevantEventTypes = /* @__PURE__ */ new Set(["cross_referenced", "referenced"]); + const parsedEvents = events.flatMap((event) => { + if (!("event" in event) || !relevantEventTypes.has(event.event)) { + return []; + } + const baseEvent = { + event: event.event + }; + if ("id" in event) { + baseEvent.id = event.id; + } + if ("actor" in event && event.actor) { + baseEvent.actor = event.actor.login; + } else if ("user" in event && event.user) { + baseEvent.actor = event.user.login; + } + if ("created_at" in event) { + baseEvent.created_at = event.created_at; + } + if (event.event === "cross_referenced") { + if ("source" in event && event.source) { + const source = event.source; + baseEvent.source = { + type: source.type, + issue: source.issue ? { + number: source.issue.number, + title: source.issue.title, + html_url: source.issue.html_url + } : null, + pull_request: source.pull_request ? { + number: source.pull_request.number, + title: source.pull_request.title, + html_url: source.pull_request.html_url + } : null + }; + } + } + if (event.event === "referenced") { + if ("commit_id" in event) { + baseEvent.commit_id = event.commit_id; + } + if ("commit_url" in event) { + baseEvent.commit_url = event.commit_url; + } + } + return [baseEvent]; + }); + return { + issue_number, + events: parsedEvents, + count: parsedEvents.length + }; + }) +}); + +// mcp/issueInfo.ts +var IssueInfo = type({ + issue_number: type.number.describe("The issue number to fetch") +}); +var IssueInfoTool = tool({ + name: "get_issue", + description: "Retrieve GitHub issue information by issue number", + parameters: IssueInfo, + execute: contextualize(async ({ issue_number }, ctx) => { + const issue2 = await ctx.octokit.rest.issues.get({ + owner: ctx.owner, + repo: ctx.name, + issue_number + }); + const data = issue2.data; + const hints = []; + if (data.comments > 0) { + hints.push("use get_issue_comments to retrieve all comments for this issue"); + } + hints.push( + "use get_issue_events to retrieve cross-references and commit references (relationships not reflected in current state)" + ); + return { + number: data.number, + url: data.html_url, + title: data.title, + body: data.body, + state: data.state, + locked: data.locked, + labels: data.labels?.map((label) => typeof label === "string" ? label : label.name), + assignees: data.assignees?.map((assignee) => assignee.login), + user: data.user?.login, + created_at: data.created_at, + updated_at: data.updated_at, + closed_at: data.closed_at, + comments: data.comments, + milestone: data.milestone?.title, + pull_request: data.pull_request ? { + url: data.pull_request.url, + html_url: data.pull_request.html_url, + diff_url: data.pull_request.diff_url, + patch_url: data.pull_request.patch_url + } : null, + hints + }; + }) +}); + // mcp/pr.ts var PullRequest = type({ title: type.string.describe("the title of the pull request"), @@ -122222,6 +122321,9 @@ async function startMcpHttpServer() { CreateWorkingCommentTool, UpdateWorkingCommentTool, IssueTool, + IssueInfoTool, + GetIssueCommentsTool, + GetIssueEventsTool, PullRequestTool, ReviewTool, PullRequestInfoTool, @@ -122374,21 +122476,16 @@ var Inputs = type({ ...keyInputDefs }); async function main(inputs) { - let pollInterval = null; let mcpServerClose; - let githubInstallationToken; try { const payload = parsePayload(inputs); const partialCtx = await initializeContext(inputs, payload); const ctx = partialCtx; - githubInstallationToken = ctx.githubInstallationToken; setupGitAuth({ githubInstallationToken: ctx.githubInstallationToken, repoContext: ctx.repoContext }); await setupTempDirectory(ctx); - setupMcpLogPolling(ctx); - pollInterval = ctx.pollInterval; setupGitBranch(ctx.payload); await startMcpServer(ctx); mcpServerClose = ctx.mcpServerClose; @@ -122406,15 +122503,10 @@ async function main(inputs) { error: errorMessage }; } finally { - if (pollInterval) { - clearInterval(pollInterval); - } if (mcpServerClose) { await mcpServerClose(); } - if (githubInstallationToken) { - await revokeInstallationToken(githubInstallationToken); - } + await revokeGitHubInstallationToken(); } } function getAvailableAgents(inputs) { @@ -122462,30 +122554,28 @@ async function initializeContext(inputs, payload) { log.info(`\u{1F438} Running pullfrog/action@${package_default.version}...`); Inputs.assert(inputs); setupGitConfig(); - const githubInstallationToken = await setupGitHubInstallationToken(); + const githubInstallationToken2 = await setupGitHubInstallationToken(); const repoContext = parseRepoContext(); const { agentName, agent: agent2 } = await resolveAgent( inputs, payload, - githubInstallationToken, + githubInstallationToken2, repoContext ); const resolvedPayload = { ...payload, agent: agentName }; return { inputs, - githubInstallationToken, + githubInstallationToken: githubInstallationToken2, repoContext, agentName, agent: agent2, payload: resolvedPayload, - sharedTempDir: "", - mcpLogPath: "", - pollInterval: null + sharedTempDir: "" }; } -async function resolveAgent(inputs, payload, githubInstallationToken, repoContext) { +async function resolveAgent(inputs, payload, githubInstallationToken2, repoContext) { const repoSettings = await fetchRepoSettings({ - token: githubInstallationToken, + token: githubInstallationToken2, repoContext }); const agentOverride = process.env.AGENT_OVERRIDE; @@ -122515,25 +122605,10 @@ async function resolveAgent(inputs, payload, githubInstallationToken, repoContex return { agentName, agent: agent2 }; } async function setupTempDirectory(ctx) { - ctx.sharedTempDir = await mkdtemp2(join8(tmpdir2(), "pullfrog-")); + ctx.sharedTempDir = await mkdtemp2(join7(tmpdir2(), "pullfrog-")); process.env.PULLFROG_TEMP_DIR = ctx.sharedTempDir; - ctx.mcpLogPath = join8(ctx.sharedTempDir, "mcpLog.txt"); - await writeFile(ctx.mcpLogPath, "", "utf-8"); log.info(`\u{1F4C2} PULLFROG_TEMP_DIR has been created at ${ctx.sharedTempDir}`); } -function setupMcpLogPolling(ctx) { - let lastSize = 0; - ctx.pollInterval = setInterval(() => { - if (existsSync4(ctx.mcpLogPath)) { - const content = readFileSync2(ctx.mcpLogPath, "utf-8"); - if (content.length > lastSize) { - const newContent = content.slice(lastSize); - process.stdout.write(newContent); - lastSize = content.length; - } - } - }, 100); -} function parsePayload(inputs) { try { const parsedPrompt = JSON.parse(inputs.prompt); @@ -122557,7 +122632,6 @@ async function startMcpServer(ctx) { const repoContext = parseRepoContext(); const githubRepository = `${repoContext.owner}/${repoContext.name}`; const allModes = [...modes, ...ctx.payload.modes || []]; - process.env.GITHUB_INSTALLATION_TOKEN = ctx.githubInstallationToken; process.env.GITHUB_REPOSITORY = githubRepository; process.env.PULLFROG_MODES = JSON.stringify(allModes); process.env.PULLFROG_PAYLOAD = JSON.stringify(ctx.payload); @@ -122594,7 +122668,6 @@ ${encode(eventWithoutContext)}`; return ctx.agent.run({ payload: ctx.payload, mcpServers: ctx.mcpServers, - githubInstallationToken: ctx.githubInstallationToken, apiKey: ctx.apiKey, cliPath: ctx.cliPath });