Files
bot-agents/src/runtime.ts
T
Danny Kim 3565988249
Agents / agent (pull_request) Has been cancelled
Check / deno (pull_request) Failing after 35s
Recognize sign-in failures for every agent and fall through
Codex re-runs device login on revoked, expired, or reused sessions and
token_revoked/401 errors, not only invalid_refresh_token. Claude and
OpenCode credential rejections are classified too. Sign-in failures fall
through to the next profile, and the failure comment names each tried
profile with a fixed reason label.
2026-10-12 11:38:21 +09:00

105 lines
2.7 KiB
TypeScript

import { join } from "jsr:@std/path@1";
import { parseCredentials } from "./credentials.ts";
import type { BotProfile, ProfileFailure } from "./profiles.ts";
const FIRST_PROFILE_UID = 20_000;
export type RuntimeProfile = Pick<BotProfile, "type" | "model"> & {
index: number;
uid: number;
gid: number;
home: string;
tokenFile: string | null;
};
export type RuntimeManifest = {
runtimeDir: string;
manifestPath: string;
reviewerFile: string;
outcomeFile: string;
profiles: RuntimeProfile[];
};
export type RuntimeOutcome =
| {
ok: true;
body: string;
reviewPath: string;
anchorsPath: string;
profile: Pick<RuntimeProfile, "type" | "model">;
}
| { ok: false; failures: ProfileFailure[] };
async function writePrivate(
path: string,
value: string,
uid: number,
gid: number,
): Promise<void> {
await Deno.writeTextFile(path, value, { mode: 0o600 });
await Deno.chmod(path, 0o600);
await Deno.chown(path, uid, gid);
}
export async function prepareRuntime(
actionPath: string,
runtimeRoot: string,
masterKey: string,
): Promise<RuntimeManifest> {
const credentials = await parseCredentials(
await Deno.readTextFile(join(actionPath, "profiles.yml")),
masterKey,
);
const runtimeDir = await Deno.makeTempDir({
dir: runtimeRoot,
prefix: "bot-agents-",
});
try {
await Deno.chmod(runtimeDir, 0o711);
const reviewerFile = join(runtimeDir, "reviewer-token");
await writePrivate(reviewerFile, credentials.reviewerToken, 0, 0);
const profiles: RuntimeProfile[] = [];
for (const [index, profile] of credentials.profiles.entries()) {
const uid = FIRST_PROFILE_UID + index;
const home = join(runtimeDir, `profile-${index}`);
await Deno.mkdir(home, { mode: 0o700 });
await Deno.chmod(home, 0o700);
await Deno.chown(home, uid, uid);
const tokenFile = profile.token ? join(home, "token") : null;
if (tokenFile !== null) {
await writePrivate(tokenFile, profile.token!, uid, uid);
}
profiles.push({
index,
type: profile.type,
model: profile.model,
uid,
gid: uid,
home,
tokenFile,
});
}
const manifestPath = join(runtimeDir, "manifest.json");
const manifest: RuntimeManifest = {
runtimeDir,
manifestPath,
reviewerFile,
outcomeFile: join(runtimeDir, "outcome.json"),
profiles,
};
await writePrivate(manifestPath, JSON.stringify(manifest), 0, 0);
return manifest;
} catch (error) {
await Deno.remove(runtimeDir, { recursive: true }).catch(() => undefined);
throw error;
}
}
export async function readRuntimeManifest(
path: string,
): Promise<RuntimeManifest> {
return JSON.parse(await Deno.readTextFile(path));
}