Document that package.json aliases may target only direct subfolders of src #21
+5
-10
@@ -60,15 +60,6 @@ export async function retryInvalidToken<T extends { status: { success: boolean }
|
||||
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> {
|
||||
const stat = await lstat(path);
|
||||
if (!stat.isFile() || stat.isSymbolicLink()) {
|
||||
@@ -84,11 +75,15 @@ async function readCodexAuth(path: string): Promise<string> {
|
||||
} 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");
|
||||
}
|
||||
|
||||
+5
-11
@@ -49,16 +49,6 @@ async function profileProcessIds(uid: number): Promise<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.
|
||||
export async function killProfileProcesses(uid: number, attempt = 0): Promise<void> {
|
||||
if (attempt === 100) {
|
||||
@@ -69,7 +59,11 @@ export async function killProfileProcesses(uid: number, attempt = 0): Promise<vo
|
||||
return;
|
||||
}
|
||||
for (const pid of ids) {
|
||||
killProcess(pid);
|
||||
try {
|
||||
process.kill(pid, "SIGKILL");
|
||||
} catch (error) {
|
||||
if (!isNotFound(error)) throw error;
|
||||
}
|
||||
}
|
||||
await sleep(10);
|
||||
await killProfileProcesses(uid, attempt + 1);
|
||||
|
||||
+6
-2
@@ -5,8 +5,8 @@ import process from "node:process";
|
||||
|
||||
import { isNotFound } from "./errors.ts";
|
||||
import { isObject } from "./json.ts";
|
||||
import { failureFooter, isFailureReason, isProfileType, withProfile } from "./profiles.ts";
|
||||
import type { ProfileFailure } from "./profiles.ts";
|
||||
import { failureFooter, withProfile } from "./profiles.ts";
|
||||
import type { FailureReason, ProfileFailure } from "./profiles.ts";
|
||||
import { readRuntimeManifest } from "./runtime.ts";
|
||||
import type { RuntimeManifest, RuntimeOutcome } from "./runtime.ts";
|
||||
|
||||
@@ -30,6 +30,10 @@ const VERDICTS: Record<string, [event: string, mark: string]> = {
|
||||
Approved: ["APPROVED", "✅"],
|
||||
"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 =>
|
||||
`AI bot failed.\n\n${failureFooter(failures)}`;
|
||||
|
||||
|
||||
+1
-14
@@ -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<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 {
|
||||
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 function isFailureReason(value: unknown): value is FailureReason {
|
||||
return FAILURE_REASONS.has(value);
|
||||
}
|
||||
|
||||
export type ProfileFailure = Pick<BotProfile, "type" | "model"> & {
|
||||
reason: FailureReason;
|
||||
};
|
||||
|
||||
+23
-22
@@ -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<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(
|
||||
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<Promise<RuntimeProfile[]>>(
|
||||
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([]),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user