147 lines
4.8 KiB
TypeScript
147 lines
4.8 KiB
TypeScript
// changes to tool permissions should be reflected in wiki/granular-tools.md
|
|
import { ensureProgressCommentUpdated } from "./mcp/comment.ts";
|
|
import { initToolState, startMcpHttpServer } from "./mcp/server.ts";
|
|
import { computeModes } from "./modes.ts";
|
|
import { resolveAgent } from "./utils/agent.ts";
|
|
import { validateApiKey } from "./utils/apiKeys.ts";
|
|
import { resolveBody } from "./utils/body.ts";
|
|
import { log, writeSummary } from "./utils/cli.ts";
|
|
import { reportErrorToComment } from "./utils/errorReport.ts";
|
|
import { createOctokit } from "./utils/github.ts";
|
|
import { resolveInstructions } from "./utils/instructions.ts";
|
|
import { normalizeEnv } from "./utils/normalizeEnv.ts";
|
|
import { resolvePayload } from "./utils/payload.ts";
|
|
import { resolveRepoData } from "./utils/repoData.ts";
|
|
import { handleAgentResult } from "./utils/run.ts";
|
|
import { createTempDirectory, setupGit } from "./utils/setup.ts";
|
|
import { Timer } from "./utils/timer.ts";
|
|
import { resolveInstallationToken } from "./utils/token.ts";
|
|
import { resolveRun } from "./utils/workflow.ts";
|
|
|
|
export { Inputs } from "./utils/payload.ts";
|
|
|
|
export interface MainResult {
|
|
success: boolean;
|
|
output?: string | undefined;
|
|
error?: string | undefined;
|
|
}
|
|
|
|
export async function main(): Promise<MainResult> {
|
|
// normalize env var names to uppercase (handles case-insensitive workflow files)
|
|
normalizeEnv();
|
|
|
|
// store original GITHUB_TOKEN
|
|
process.env.ORIGINAL_GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
|
|
|
const timer = new Timer();
|
|
await using tokenRef = await resolveInstallationToken();
|
|
process.env.GITHUB_TOKEN = tokenRef.token;
|
|
|
|
const octokit = createOctokit(tokenRef.token);
|
|
const runInfo = await resolveRun({ octokit });
|
|
const toolState = initToolState({ runInfo });
|
|
|
|
try {
|
|
const repo = await resolveRepoData({ octokit, token: tokenRef.token });
|
|
timer.checkpoint("repoData");
|
|
|
|
// resolve payload after repoData so permissions can use DB settings
|
|
// precedence: action inputs > json payload > repoSettings > fallbacks
|
|
const payload = resolvePayload(repo.repoSettings);
|
|
if (payload.cwd && process.cwd() !== payload.cwd) {
|
|
process.chdir(payload.cwd);
|
|
}
|
|
|
|
// resolve body - fetches body_html and converts to markdown if images present
|
|
// this ensures agents receive markdown with working signed image URLs
|
|
const originalBody = payload.event.body;
|
|
const resolvedBody = await resolveBody({ event: payload.event, octokit, repo });
|
|
if (resolvedBody !== originalBody) {
|
|
payload.event.body = resolvedBody;
|
|
// also update prompt if original body was included there
|
|
if (originalBody && payload.prompt.includes(originalBody)) {
|
|
payload.prompt = payload.prompt.replace(originalBody, resolvedBody ?? "");
|
|
}
|
|
}
|
|
|
|
const tmpdir = createTempDirectory();
|
|
|
|
const agent = resolveAgent({ payload, repoSettings: repo.repoSettings });
|
|
|
|
validateApiKey({
|
|
agent,
|
|
owner: repo.owner,
|
|
name: repo.name,
|
|
});
|
|
|
|
await setupGit({
|
|
token: tokenRef.token,
|
|
originalToken: process.env.ORIGINAL_GITHUB_TOKEN,
|
|
bashPermission: payload.bash,
|
|
owner: repo.owner,
|
|
name: repo.name,
|
|
event: payload.event,
|
|
octokit,
|
|
toolState,
|
|
});
|
|
timer.checkpoint("git");
|
|
|
|
const modes = [...computeModes(), ...repo.repoSettings.modes];
|
|
|
|
await using mcpHttpServer = await startMcpHttpServer({
|
|
repo,
|
|
payload,
|
|
octokit,
|
|
githubInstallationToken: tokenRef.token,
|
|
agent,
|
|
modes,
|
|
toolState,
|
|
runId: runInfo.runId,
|
|
jobId: runInfo.jobId,
|
|
});
|
|
log.info(`» MCP server started at ${mcpHttpServer.url}`);
|
|
timer.checkpoint("mcpServer");
|
|
|
|
const instructions = resolveInstructions({
|
|
payload,
|
|
repoData: repo,
|
|
modes,
|
|
});
|
|
|
|
const result = await agent.run({
|
|
payload,
|
|
mcpServerUrl: mcpHttpServer.url,
|
|
tmpdir,
|
|
instructions,
|
|
});
|
|
|
|
// write last progress body to job summary
|
|
if (toolState.lastProgressBody) {
|
|
await writeSummary(toolState.lastProgressBody);
|
|
}
|
|
|
|
const mainResult = await handleAgentResult(result);
|
|
return mainResult;
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
log.error(errorMessage);
|
|
try {
|
|
await reportErrorToComment({ toolState, error: errorMessage });
|
|
} catch {
|
|
// error reporting failed, but don't let it mask the original error
|
|
}
|
|
return {
|
|
success: false,
|
|
error: errorMessage,
|
|
};
|
|
} finally {
|
|
// ensure progress comment is updated if it was never updated during execution
|
|
// do this before revoking the token so we can still make API calls
|
|
try {
|
|
await ensureProgressCommentUpdated(toolState);
|
|
} catch {
|
|
// error updating comment, but don't let it mask the original error
|
|
}
|
|
}
|
|
}
|