// The agent phase's environment and the helpers every agent runner shares. It // is read once, when the runner starts, as the single-file runner did. import { chmod, chown, writeFile } from "node:fs/promises"; import process from "node:process"; import { agentCommand } from "./isolation.ts"; import { agentEnvironment, sanitizedEnvironment } from "./profiles.ts"; import { readRuntimeManifest } from "./runtime.ts"; import type { RuntimeProfile } from "./runtime.ts"; export const env = (name: string): string => { const value = process.env[name]; if (value === undefined) { throw new Error(`${name} is not set`); } return value; }; function definedEnvironment(): Record { const result: Record = {}; for (const [name, value] of Object.entries(process.env)) { if (value !== undefined) { result[name] = value; } } return result; } export const API = env("GITEA_API_URL"); export const REPO = env("GITEA_REPOSITORY"); export const INDEX = env("ISSUE_INDEX"); export const EVENT = env("EVENT_NAME"); const AUTHOR_TOKEN = env("GITEA_TOKEN"); export const WORKSPACE = env("GITHUB_WORKSPACE"); export const MANIFEST = await readRuntimeManifest(env("BOT_AGENTS_MANIFEST")); export const PROCESS_ENV = sanitizedEnvironment(definedEnvironment()); export async function gitea(path: string, body?: unknown): Promise { const response = await fetch(`${API}/${path}`, { method: body === undefined ? "GET" : "POST", headers: { Authorization: `token ${AUTHOR_TOKEN}`, "Content-Type": "application/json", }, body: body === undefined ? null : JSON.stringify(body), }); if (!response.ok) { throw new Error(`${path}: ${response.status} ${await response.text()}`); } return response; } export async function postAuthorComment(body: string): Promise { await gitea(`repos/${REPO}/issues/${INDEX}/comments`, { body }); } export async function runAgentCommand( profile: RuntimeProfile, command: string, args: string[], token?: string, ): Promise { const { success, code } = await agentCommand(profile, command, args, { cwd: WORKSPACE, env: agentEnvironment(PROCESS_ENV, profile, token), clearEnv: true, }).output(); if (!success) { throw new Error(`${command} ${args[0]} exited with ${code}`); } } export async function makeProfileFile(profile: RuntimeProfile, name: string): Promise { const path = `${profile.home}/${name}`; await writeFile(path, "", { mode: 0o600 }); await chmod(path, 0o600); await chown(path, profile.uid, profile.gid); return path; }