diff --git a/src/auth.ts b/src/auth.ts index b57c645..148f8a7 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -60,15 +60,6 @@ export async function retryInvalidToken typeof tokens[name] === "string" && tokens[name] !== "", - ) - ); -} - async function readCodexAuth(path: string): Promise { const stat = await lstat(path); if (!stat.isFile() || stat.isSymbolicLink()) { @@ -84,11 +75,15 @@ async function readCodexAuth(path: string): Promise { } catch { throw new Error("invalid Codex login"); } + const tokens = isObject(value) ? value.tokens : undefined; if ( !isObject(value) || typeof value.auth_mode !== "string" || value.auth_mode === "" || - !hasCodexTokens(value.tokens) + !isObject(tokens) || + !["access_token", "refresh_token", "id_token", "account_id"].every( + (name) => typeof tokens[name] === "string" && tokens[name] !== "", + ) ) { throw new Error("invalid Codex login"); } diff --git a/src/isolation.ts b/src/isolation.ts index 4df8701..0563a93 100644 --- a/src/isolation.ts +++ b/src/isolation.ts @@ -49,16 +49,6 @@ async function profileProcessIds(uid: number): Promise { .map(Number); } -function killProcess(pid: number): void { - try { - process.kill(pid, "SIGKILL"); - } catch (error) { - if (!isNotFound(error)) { - throw error; - } - } -} - // Kills the UID's processes every 10 ms until none is left, for up to 100 rounds. export async function killProfileProcesses(uid: number, attempt = 0): Promise { if (attempt === 100) { @@ -69,7 +59,11 @@ export async function killProfileProcesses(uid: number, attempt = 0): Promise = { Approved: ["APPROVED", "โœ…"], "Changes requested": ["REQUEST_CHANGES", "๐Ÿ›‘"], }; +const PROFILE_TYPES: Set = new Set(["claude", "codex", "opencode"]); +const FAILURE_REASONS: Set = new Set(["sign-in failed", "usage exhausted", "failed"]); +const isProfileType = (value: unknown): value is ProfileFailure["type"] => PROFILE_TYPES.has(value); +const isFailureReason = (value: unknown): value is FailureReason => FAILURE_REASONS.has(value); const failureComment = (failures: ProfileFailure[]): string => `AI bot failed.\n\n${failureFooter(failures)}`; diff --git a/src/profiles.ts b/src/profiles.ts index 79a1b15..c936c83 100644 --- a/src/profiles.ts +++ b/src/profiles.ts @@ -1,18 +1,9 @@ -export type ProfileType = "claude" | "codex" | "opencode"; - export interface BotProfile { - type: ProfileType; + type: "claude" | "codex" | "opencode"; model: string; token?: string; } -const PROFILE_TYPES: Set = new Set(["claude", "codex", "opencode"]); -const FAILURE_REASONS: Set = new Set(["sign-in failed", "usage exhausted", "failed"]); - -export function isProfileType(value: unknown): value is ProfileType { - return PROFILE_TYPES.has(value); -} - export function profileFooter(profile: Pick): string { return `> Profile: \`${profile.type}\` ยท \`${profile.model}\``; } @@ -23,10 +14,6 @@ export function withProfile(body: string, profile: Pick & { reason: FailureReason; }; diff --git a/src/runtime.ts b/src/runtime.ts index 15bc9ab..926c0b3 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -39,24 +39,6 @@ async function writePrivate(path: string, value: string, uid: number, gid: numbe await chown(path, uid, gid); } -async function prepareProfile( - runtimeDir: string, - profile: BotProfile, - index: number, -): Promise { - const uid = FIRST_PROFILE_UID + index; - const home = join(runtimeDir, `profile-${index}`); - await mkdir(home, { mode: 0o700 }); - await chmod(home, 0o700); - await chown(home, uid, uid); - const tokenFile = - profile.token === undefined || profile.token === "" ? null : join(home, "token"); - if (tokenFile !== null && profile.token !== undefined) { - await writePrivate(tokenFile, profile.token, uid, uid); - } - return { index, type: profile.type, model: profile.model, uid, gid: uid, home, tokenFile }; -} - export async function prepareRuntime( actionPath: string, runtimeRoot: string, @@ -74,10 +56,29 @@ export async function prepareRuntime( // One profile at a time, in order, as each claims the next UID. const profiles = await credentials.profiles.reduce>( - async (previous, profile, index) => [ - ...(await previous), - await prepareProfile(runtimeDir, profile, index), - ], + async (previous, profile, index) => { + const prepared = await previous; + const uid = FIRST_PROFILE_UID + index; + const home = join(runtimeDir, `profile-${index}`); + await mkdir(home, { mode: 0o700 }); + await chmod(home, 0o700); + await chown(home, uid, uid); + const tokenFile = + profile.token === undefined || profile.token === "" ? null : join(home, "token"); + if (tokenFile !== null && profile.token !== undefined) { + await writePrivate(tokenFile, profile.token, uid, uid); + } + prepared.push({ + index, + type: profile.type, + model: profile.model, + uid, + gid: uid, + home, + tokenFile, + }); + return prepared; + }, Promise.resolve([]), );