Restore helper layout in auth, isolation, runtime, and profiles from main
This commit is contained in:
+5
-10
@@ -60,15 +60,6 @@ export async function retryInvalidToken<T extends { status: { success: boolean }
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasCodexTokens(tokens: unknown): boolean {
|
|
||||||
return (
|
|
||||||
isObject(tokens) &&
|
|
||||||
["access_token", "refresh_token", "id_token", "account_id"].every(
|
|
||||||
(name) => typeof tokens[name] === "string" && tokens[name] !== "",
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readCodexAuth(path: string): Promise<string> {
|
async function readCodexAuth(path: string): Promise<string> {
|
||||||
const stat = await lstat(path);
|
const stat = await lstat(path);
|
||||||
if (!stat.isFile() || stat.isSymbolicLink()) {
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
||||||
@@ -84,11 +75,15 @@ async function readCodexAuth(path: string): Promise<string> {
|
|||||||
} catch {
|
} catch {
|
||||||
throw new Error("invalid Codex login");
|
throw new Error("invalid Codex login");
|
||||||
}
|
}
|
||||||
|
const tokens = isObject(value) ? value.tokens : undefined;
|
||||||
if (
|
if (
|
||||||
!isObject(value) ||
|
!isObject(value) ||
|
||||||
typeof value.auth_mode !== "string" ||
|
typeof value.auth_mode !== "string" ||
|
||||||
value.auth_mode === "" ||
|
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");
|
throw new Error("invalid Codex login");
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-11
@@ -49,16 +49,6 @@ async function profileProcessIds(uid: number): Promise<number[]> {
|
|||||||
.map(Number);
|
.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.
|
// 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<void> {
|
export async function killProfileProcesses(uid: number, attempt = 0): Promise<void> {
|
||||||
if (attempt === 100) {
|
if (attempt === 100) {
|
||||||
@@ -69,7 +59,11 @@ export async function killProfileProcesses(uid: number, attempt = 0): Promise<vo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const pid of ids) {
|
for (const pid of ids) {
|
||||||
killProcess(pid);
|
try {
|
||||||
|
process.kill(pid, "SIGKILL");
|
||||||
|
} catch (error) {
|
||||||
|
if (!isNotFound(error)) throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await sleep(10);
|
await sleep(10);
|
||||||
await killProfileProcesses(uid, attempt + 1);
|
await killProfileProcesses(uid, attempt + 1);
|
||||||
|
|||||||
+6
-2
@@ -5,8 +5,8 @@ import process from "node:process";
|
|||||||
|
|
||||||
import { isNotFound } from "./errors.ts";
|
import { isNotFound } from "./errors.ts";
|
||||||
import { isObject } from "./json.ts";
|
import { isObject } from "./json.ts";
|
||||||
import { failureFooter, isFailureReason, isProfileType, withProfile } from "./profiles.ts";
|
import { failureFooter, withProfile } from "./profiles.ts";
|
||||||
import type { ProfileFailure } from "./profiles.ts";
|
import type { FailureReason, ProfileFailure } from "./profiles.ts";
|
||||||
import { readRuntimeManifest } from "./runtime.ts";
|
import { readRuntimeManifest } from "./runtime.ts";
|
||||||
import type { RuntimeManifest, RuntimeOutcome } from "./runtime.ts";
|
import type { RuntimeManifest, RuntimeOutcome } from "./runtime.ts";
|
||||||
|
|
||||||
@@ -30,6 +30,10 @@ const VERDICTS: Record<string, [event: string, mark: string]> = {
|
|||||||
Approved: ["APPROVED", "✅"],
|
Approved: ["APPROVED", "✅"],
|
||||||
"Changes requested": ["REQUEST_CHANGES", "🛑"],
|
"Changes requested": ["REQUEST_CHANGES", "🛑"],
|
||||||
};
|
};
|
||||||
|
const PROFILE_TYPES: Set<unknown> = new Set(["claude", "codex", "opencode"]);
|
||||||
|
const FAILURE_REASONS: Set<unknown> = 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 =>
|
const failureComment = (failures: ProfileFailure[]): string =>
|
||||||
`AI bot failed.\n\n${failureFooter(failures)}`;
|
`AI bot failed.\n\n${failureFooter(failures)}`;
|
||||||
|
|
||||||
|
|||||||
+1
-14
@@ -1,18 +1,9 @@
|
|||||||
export type ProfileType = "claude" | "codex" | "opencode";
|
|
||||||
|
|
||||||
export interface BotProfile {
|
export interface BotProfile {
|
||||||
type: ProfileType;
|
type: "claude" | "codex" | "opencode";
|
||||||
model: string;
|
model: string;
|
||||||
token?: string;
|
token?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PROFILE_TYPES: Set<unknown> = new Set(["claude", "codex", "opencode"]);
|
|
||||||
const FAILURE_REASONS: Set<unknown> = 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<BotProfile, "type" | "model">): string {
|
export function profileFooter(profile: Pick<BotProfile, "type" | "model">): string {
|
||||||
return `> Profile: \`${profile.type}\` · \`${profile.model}\``;
|
return `> Profile: \`${profile.type}\` · \`${profile.model}\``;
|
||||||
}
|
}
|
||||||
@@ -23,10 +14,6 @@ export function withProfile(body: string, profile: Pick<BotProfile, "type" | "mo
|
|||||||
|
|
||||||
export type FailureReason = "sign-in failed" | "usage exhausted" | "failed";
|
export type FailureReason = "sign-in failed" | "usage exhausted" | "failed";
|
||||||
|
|
||||||
export function isFailureReason(value: unknown): value is FailureReason {
|
|
||||||
return FAILURE_REASONS.has(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProfileFailure = Pick<BotProfile, "type" | "model"> & {
|
export type ProfileFailure = Pick<BotProfile, "type" | "model"> & {
|
||||||
reason: FailureReason;
|
reason: FailureReason;
|
||||||
};
|
};
|
||||||
|
|||||||
+23
-22
@@ -39,24 +39,6 @@ async function writePrivate(path: string, value: string, uid: number, gid: numbe
|
|||||||
await chown(path, uid, gid);
|
await chown(path, uid, gid);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function prepareProfile(
|
|
||||||
runtimeDir: string,
|
|
||||||
profile: BotProfile,
|
|
||||||
index: number,
|
|
||||||
): Promise<RuntimeProfile> {
|
|
||||||
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(
|
export async function prepareRuntime(
|
||||||
actionPath: string,
|
actionPath: string,
|
||||||
runtimeRoot: string,
|
runtimeRoot: string,
|
||||||
@@ -74,10 +56,29 @@ export async function prepareRuntime(
|
|||||||
|
|
||||||
// One profile at a time, in order, as each claims the next UID.
|
// One profile at a time, in order, as each claims the next UID.
|
||||||
const profiles = await credentials.profiles.reduce<Promise<RuntimeProfile[]>>(
|
const profiles = await credentials.profiles.reduce<Promise<RuntimeProfile[]>>(
|
||||||
async (previous, profile, index) => [
|
async (previous, profile, index) => {
|
||||||
...(await previous),
|
const prepared = await previous;
|
||||||
await prepareProfile(runtimeDir, profile, index),
|
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([]),
|
Promise.resolve([]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user