mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 12:22:14 +03:00
* fix(antigravity): wrap executeOnce in try/catch for Pro fallback chain When a Pro-tier candidate times out or throws a network error, the exception now continues to the next candidate instead of aborting the entire chain. Includes diagnostic logging and unit tests. Signed-off-by: Minxi Hou <houminxi@gmail.com> * fix(antigravity): propagate abort signal in Pro fallback catch block Re-throw AbortError and signal.aborted immediately instead of retrying the next candidate. Prevents wasted upstream requests after client disconnect. Signed-off-by: Minxi Hou <houminxi@gmail.com> * fix(mitm): skip DNS modification when sudo unavailable (container) In containers (USER node, no sudo, not root) provisionDnsEntries() now detects the condition up-front and logs a clear message instead of attempting sudo and silently swallowing the error. Adds canElevate() to the injectable deps interface for testability, and supports SKIP_ANTIGRAVITY_DNS=true for explicit opt-out. * fix(antigravity): improve abort detection and fallback error handling Check Error.name === 'AbortError' for non-DOMException environments (polyfills, test harnesses). Capture first 400 from any candidate (not just i===0) so mixed paths surface the 400 instead of a generic error. Return firstResult when last candidate throws, consistent with the all-400 case. * test(mitm): add coverage for container-skip DNS provisioning * test(mitm): harden container-skip DNS test assertions The SKIP_ANTIGRAVITY_DNS=true and canElevate()=false tests used empty agentStates/customHosts, so they could not distinguish 'all steps skipped' from 'only the default step skipped'. Provide non-empty mocks and assert addHostsDns was NOT called. Also add a SKIP_ANTIGRAVITY_DNS=false boundary test confirming the strict === "true" comparison does not block normal provisioning, and verify sudoPassword passthrough in the canElevate=true happy-path test. * refactor(mitm): split provisionDnsEntries below complexity gate provisionDnsEntries() (complexity ~18, this PR's try/catch/log additions pushed it over check-complexity.mjs's threshold of 15) and execute()'s Pro-fallback loop (complexity 27, from wrapping executeOnce() in try/catch for timeout resilience) were both over the gate. Decomposed each into small named helpers, no behavior change: - provision.ts: split into provisionDefaultDns/provisionAgentDns/ provisionCustomHostsDns, each wrapping one best-effort DNS step. - antigravity.ts: extracted the fallback-chain catch/400-handling decisions (handleAntigravityFallbackChainError, isAntigravityAbortError, handleAntigravityFallback400) into a new antigravity/proFallbackChain.ts submodule (pure, no executor instance state), mirroring the existing antigravity/sseCollect.ts submodule pattern. Also fixes the antigravity.ts file-size cap (was pushed to 1854 lines > 1813 frozen ceiling by this PR's own try/catch addition; now 1771). execute/provisionDnsEntries no longer appear with ruleId complexity or max-lines-per-function in the check-complexity.mjs report. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(antigravity): drop redundant loop continue (cognitive-complexity gate) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(antigravity): fold fallback outcome dispatch into switch (cognitive gate) Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Signed-off-by: Minxi Hou <houminxi@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: HouMinXi <19586012+HouMinXi@users.noreply.github.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
105 lines
3.8 KiB
TypeScript
105 lines
3.8 KiB
TypeScript
// Pure Pro-family fallback-chain decision helpers for the Antigravity executor (#7290):
|
|
// decide what execute()'s per-candidate loop does after executeOnce() throws or
|
|
// returns a 400, without depending on executor instance state (no `this`).
|
|
// Extracted from antigravity.ts (file-size cap) -- mirrors the existing
|
|
// antigravity/sseCollect.ts submodule pattern.
|
|
import type { ExecuteInput } from "../base.ts";
|
|
|
|
/** Shape of one execute()/executeOnce() result (kept local to avoid importing the class). */
|
|
export type AntigravityExecuteResult = {
|
|
response: Response;
|
|
url: string;
|
|
headers: Record<string, string>;
|
|
transformedBody: unknown;
|
|
};
|
|
|
|
/** True for an aborted request (caller disconnect) — never retried across candidates. */
|
|
export function isAntigravityAbortError(input: ExecuteInput, error: unknown): boolean {
|
|
return Boolean(
|
|
input.signal?.aborted ||
|
|
(error instanceof DOMException && error.name === "AbortError") ||
|
|
(error instanceof Error && error.name === "AbortError")
|
|
);
|
|
}
|
|
|
|
export type AntigravityFallbackChainErrorOutcome =
|
|
| { action: "throw"; error: unknown }
|
|
| { action: "return"; result: AntigravityExecuteResult }
|
|
| { action: "continue" };
|
|
|
|
/**
|
|
* Decide what execute()'s Pro-fallback loop does after executeOnce() THROWS for one
|
|
* candidate: propagate an abort immediately, retry the next candidate, surface the
|
|
* first 400 if the chain is exhausted, or throw a chain-exhausted error.
|
|
*/
|
|
export function handleAntigravityFallbackChainError(
|
|
input: ExecuteInput,
|
|
error: unknown,
|
|
candidate: string,
|
|
i: number,
|
|
chain: readonly string[],
|
|
firstResult: AntigravityExecuteResult | null,
|
|
resolvedUpstreamId: string
|
|
): AntigravityFallbackChainErrorOutcome {
|
|
// Abort signal (user disconnect) — propagate immediately, do not retry.
|
|
if (isAntigravityAbortError(input, error)) {
|
|
return { action: "throw", error };
|
|
}
|
|
if (i < chain.length - 1) {
|
|
input.log?.debug?.(
|
|
"AG_PRO_FALLBACK",
|
|
`Exception on "${candidate}" (${error instanceof Error ? error.message : String(error)}) -- retrying with next Pro candidate "${chain[i + 1]}"`
|
|
);
|
|
return { action: "continue" };
|
|
}
|
|
// Last candidate also threw -- return original 400 if available, otherwise throw.
|
|
if (firstResult) {
|
|
input.log?.warn?.(
|
|
"AG_PRO_FALLBACK",
|
|
`Pro fallback chain exhausted (last candidate threw, but first candidate returned 400) for "${resolvedUpstreamId}". Returning original 400.`
|
|
);
|
|
return { action: "return", result: firstResult };
|
|
}
|
|
return {
|
|
action: "throw",
|
|
error: new Error(
|
|
`Pro fallback chain exhausted (all ${chain.length} candidates failed). Last error: ${error instanceof Error ? error.message : String(error)}`
|
|
),
|
|
};
|
|
}
|
|
|
|
export type AntigravityFallback400Outcome =
|
|
| { action: "return"; result: AntigravityExecuteResult }
|
|
| { action: "continue" };
|
|
|
|
/**
|
|
* Decide what execute()'s Pro-fallback loop does after one candidate returns a 400:
|
|
* retry the next candidate, or (chain exhausted) surface the first candidate's
|
|
* sanitized 400.
|
|
*/
|
|
export function handleAntigravityFallback400(
|
|
input: ExecuteInput,
|
|
result: AntigravityExecuteResult,
|
|
firstResult: AntigravityExecuteResult | null,
|
|
candidate: string,
|
|
i: number,
|
|
chain: readonly string[],
|
|
resolvedUpstreamId: string
|
|
): AntigravityFallback400Outcome {
|
|
const isLast = i === chain.length - 1;
|
|
if (!isLast) {
|
|
input.log?.debug?.(
|
|
"AG_PRO_FALLBACK",
|
|
`400 on "${candidate}" — retrying with next Pro candidate "${chain[i + 1]}"`
|
|
);
|
|
return { action: "continue" };
|
|
}
|
|
|
|
// Chain exhausted: surface the FIRST candidate's sanitized 400.
|
|
input.log?.warn?.(
|
|
"AG_PRO_FALLBACK",
|
|
`Pro fallback chain exhausted (all ${chain.length} candidates 400'd) for "${resolvedUpstreamId}"`
|
|
);
|
|
return { action: "return", result: firstResult ?? result };
|
|
}
|