Turn off no-await-in-loop, consistent-return and switch-exhaustiveness-check, turn on no-array-reduce, and restore the plain loops

This commit is contained in:
Danny Kim
2026-10-19 19:41:06 +09:00
parent bd1ddb9406
commit a65bf353ab
4 changed files with 50 additions and 71 deletions
+6 -2
View File
@@ -247,8 +247,6 @@ const rules: DummyRuleMap = {
// Collides with explicit-types `no-undefined-type`: a `let` that `try` // Collides with explicit-types `no-undefined-type`: a `let` that `try`
// assigns would need `T | undefined`. // assigns would need `T | undefined`.
"init-declarations": "off", "init-declarations": "off",
// Collides with `no-await-in-loop`: sequential steps chain through `reduce`.
"unicorn/no-array-reduce": "off",
// Collides with literal Gitea Actions `${{ }}` expressions in workflow strings. // Collides with literal Gitea Actions `${{ }}` expressions in workflow strings.
"no-template-curly-in-string": "off", "no-template-curly-in-string": "off",
// Node code, everywhere: this action runs on Node. // Node code, everywhere: this action runs on Node.
@@ -264,6 +262,12 @@ const rules: DummyRuleMap = {
// PENDING USER: a fitting `name` needs a constructor and changes logged // PENDING USER: a fitting `name` needs a constructor and changes logged
// error names. // error names.
"unicorn/custom-error-definition": "off", "unicorn/custom-error-definition": "off",
// Sequential awaits are intentional; workarounds cost more than they save.
"no-await-in-loop": "off",
// Not type-aware; TypeScript's `noImplicitReturns` covers it.
"consistent-return": "off",
// TypeScript's type checking covers exhaustive switches.
"typescript/switch-exhaustiveness-check": "off",
// Off, too much churn: // Off, too much churn:
// Literal numbers read better than a constant per use. // Literal numbers read better than a constant per use.
"no-magic-numbers": "off", "no-magic-numbers": "off",
+16 -27
View File
@@ -53,37 +53,26 @@ export function failureFooter(failures: ProfileFailure[]): string {
.join(", ")}`; .join(", ")}`;
} }
// Tries the profiles from `index` on, moving on only after a sign-in failure // Tries the profiles in order, moving on only after a sign-in failure or
// or exhausted usage, and records every profile it tried. // exhausted usage, and records every profile it tried.
async function attemptFrom<T, P extends ProfileIdentity>( export async function runProfiles<T, P extends ProfileIdentity>(
profiles: P[],
attempt: (profile: P) => Promise<T>,
failures: ProfileFailure[],
index: number,
): Promise<T> {
const profile = profiles[index];
if (profile === undefined) {
throw new Error("profiles must contain at least one profile");
}
try {
return await attempt(profile);
} catch (error) {
const reason = failureReason(error);
failures.push({ type: profile.type, model: profile.model, reason });
if (reason === "failed" || index === profiles.length - 1) {
throw error;
}
console.error(`${profile.type} ${reason}; trying next profile`);
return attemptFrom(profiles, attempt, failures, index + 1);
}
}
export function runProfiles<T, P extends ProfileIdentity>(
profiles: P[], profiles: P[],
attempt: (profile: P) => Promise<T>, attempt: (profile: P) => Promise<T>,
failures: ProfileFailure[] = [], failures: ProfileFailure[] = [],
): Promise<T> { ): Promise<T> {
return attemptFrom(profiles, attempt, failures, 0); for (const [index, profile] of profiles.entries()) {
try {
return await attempt(profile);
} catch (error) {
const reason = failureReason(error);
failures.push({ type: profile.type, model: profile.model, reason });
if (reason === "failed" || index === profiles.length - 1) {
throw error;
}
console.error(`${profile.type} ${reason}; trying next profile`);
}
}
throw new Error("profiles must contain at least one profile");
} }
// Environments are maps from variable name to value, converted to the object // Environments are maps from variable name to value, converted to the object
+6 -16
View File
@@ -283,21 +283,6 @@ async function runClaude(profile: RuntimeProfile, token: string, prompt: string)
return result.result; return result.result;
} }
// Polls every 100 ms until the login prints something, failing if it exits first.
async function pollForInstructions(
shownText: () => string,
status: Promise<number>,
): Promise<void> {
if (shownText().trim() !== "") {
return;
}
const exited = await Promise.race([status.then(() => true), sleep(100, false)]);
if (exited) {
throw new AgentAuthError("AI bot login produced no instructions");
}
await pollForInstructions(shownText, status);
}
async function codexDeviceLogin( async function codexDeviceLogin(
profile: RuntimeProfile, profile: RuntimeProfile,
environment: ReadonlyMap<string, string>, environment: ReadonlyMap<string, string>,
@@ -319,7 +304,12 @@ async function codexDeviceLogin(
}; };
const drained = Promise.all([collect(login.stdout), collect(login.stderr)]); const drained = Promise.all([collect(login.stdout), collect(login.stderr)]);
await sleep(3000); await sleep(3000);
await pollForInstructions(() => shown, status); while (shown.trim() === "") {
const exited = await Promise.race([status.then(() => true), sleep(100, false)]);
if (exited) {
throw new AgentAuthError("AI bot login produced no instructions");
}
}
await postAuthorComment(withProfile(shown, profile)); await postAuthorComment(withProfile(shown, profile));
const [code] = await Promise.all([status, drained]); const [code] = await Promise.all([status, drained]);
if (code !== 0) { if (code !== 0) {
+22 -26
View File
@@ -61,32 +61,28 @@ export async function prepareRuntime(
await writePrivate(reviewerFile, credentials.reviewerToken, 0, 0); await writePrivate(reviewerFile, credentials.reviewerToken, 0, 0);
// 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: RuntimeProfile[] = [];
async (previous, profile, index) => { for (const [index, profile] of credentials.profiles.entries()) {
const prepared = await previous; const uid = FIRST_PROFILE_UID + index;
const uid = FIRST_PROFILE_UID + index; const home = join(runtimeDir, `profile-${index}`);
const home = join(runtimeDir, `profile-${index}`); await mkdir(home, { mode: 0o700 });
await mkdir(home, { mode: 0o700 }); await chmod(home, 0o700);
await chmod(home, 0o700); await chown(home, uid, uid);
await chown(home, uid, uid); const tokenFile =
const tokenFile = profile.token === undefined || profile.token === "" ? null : join(home, "token");
profile.token === undefined || profile.token === "" ? null : join(home, "token"); if (tokenFile !== null && profile.token !== undefined) {
if (tokenFile !== null && profile.token !== undefined) { await writePrivate(tokenFile, profile.token, uid, uid);
await writePrivate(tokenFile, profile.token, uid, uid); }
} profiles.push({
prepared.push({ index,
index, type: profile.type,
type: profile.type, model: profile.model,
model: profile.model, uid,
uid, gid: uid,
gid: uid, home,
home, tokenFile,
tokenFile, });
}); }
return prepared;
},
Promise.resolve([]),
);
const manifestPath = join(runtimeDir, "manifest.json"); const manifestPath = join(runtimeDir, "manifest.json");
const manifest: RuntimeManifest = { const manifest: RuntimeManifest = {