import type { RpaErrorCode } from "@/modules/rpa/errors"; type PageTextRule = { patterns: RegExp[]; code: RpaErrorCode; }; const PAGE_TEXT_RULES: PageTextRule[] = [ { patterns: [ /no carriers available/i, /no capacity/i, /unable to find a carrier/i, /无可用承运商/, /暂无运力/, ], code: "CARRIER_NO_CAPACITY", }, { patterns: [ /address not supported/i, /zip code is not supported/i, /we don't service this area/i, /不支持该邮编/, /地址不在服务范围/, ], code: "ADDRESS_NOT_SUPPORTED", }, ]; export function hasMothershipCredentials(): boolean { return Boolean( process.env.MOTHERSHIP_EMAIL?.trim() && process.env.MOTHERSHIP_PASSWORD?.trim(), ); } /** Mothership 页面文案 → 业务 error_code(task-110 / task-128) */ export function mapPageTextToErrorCode(pageText: string): RpaErrorCode | null { const normalized = pageText.trim(); if (!normalized) { return null; } for (const rule of PAGE_TEXT_RULES) { if (rule.patterns.some((pattern) => pattern.test(normalized))) { return rule.code; } } return null; } /** * URL → 错误分类(v0.6) * login 无凭据由 QuotePageAdapter.isLoginWithoutCredentials 处理为 STRUCT_CHANGE */ export function mapUrlToErrorCode(url: string): RpaErrorCode | null { const lower = url.toLowerCase(); if (lower.includes("captcha") || lower.includes("challenge")) { return "RPA_CAPTCHA"; } if ( lower.includes("/login") || lower.includes("/sign-in") || lower.includes("/signin") ) { if (hasMothershipCredentials()) { return "SESSION_EXPIRED"; } return null; } return null; } /** 入口类错误:不重试、不消耗 job 重试配额(task-128/129) */ export function isEntryErrorCode(code: RpaErrorCode): boolean { return ( code === "QUOTE_ENTRY_UNAVAILABLE" || code === "ADDRESS_SUGGESTION_NOT_FOUND" ); }