From 702e969deba2e1012864a3233c4dd9d640aad062 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Tue, 19 May 2026 02:04:32 +0800 Subject: [PATCH] fix: stop kiro auto-run on proxy error pages --- background.js | 9 + background/auto-run-controller.js | 26 +++ background/logging-status.js | 6 + content/kiro/desktop-authorize-page.js | 27 ++- content/kiro/register-page.js | 35 +++- tests/auto-run-kiro-flow-selection.test.js | 174 ++++++++++++++++++ .../background-logging-status-module.test.js | 12 ++ tests/kiro-register-page-fatal-state.test.js | 79 ++++++++ ...idepanel-sub2api-priority-settings.test.js | 2 +- 9 files changed, 361 insertions(+), 9 deletions(-) create mode 100644 tests/kiro-register-page-fatal-state.test.js diff --git a/background.js b/background.js index 130c69b..1e9d277 100644 --- a/background.js +++ b/background.js @@ -8847,6 +8847,14 @@ function isSignupUserAlreadyExistsFailure(error) { return /SIGNUP_USER_ALREADY_EXISTS::|user_already_exists/i.test(message); } +function isKiroProxyFailure(error) { + if (typeof loggingStatus !== 'undefined' && loggingStatus?.isKiroProxyFailure) { + return loggingStatus.isKiroProxyFailure(error); + } + const message = getErrorMessage(error); + return /Kiro\s*(?:注册页|桌面授权页).*(?:CloudFront\s*拒绝请求|AWS\s*请求异常)|(?:当前代理\s*IP|出口区域异常).*(?:切换代理|更换代理)|AWS\s*风控.*(?:切换代理|更换代理)/i.test(message); +} + function isStep4Route405RecoveryLimitFailure(error) { const message = getErrorMessage(error); return /STEP4_405_RECOVERY_LIMIT::|步骤\s*4:检测到\s*405\s*错误页面,已连续点击“重试”恢复/i.test(message); @@ -11752,6 +11760,7 @@ const autoRunController = self.MultiPageBackgroundAutoRunController?.createAutoR isPhoneSmsPlatformRateLimitFailure, isPlusCheckoutNonFreeTrialFailure, isGpcTaskEndedFailure, + isKiroProxyFailure, isRestartCurrentAttemptError, isStep4Route405RecoveryLimitFailure, isSignupUserAlreadyExistsFailure, diff --git a/background/auto-run-controller.js b/background/auto-run-controller.js index f21310d..2247757 100644 --- a/background/auto-run-controller.js +++ b/background/auto-run-controller.js @@ -25,6 +25,7 @@ hasSavedNodeProgress, isAddPhoneAuthFailure, isGpcTaskEndedFailure, + isKiroProxyFailure, isPhoneSmsPlatformRateLimitFailure, isPlusCheckoutNonFreeTrialFailure, isRestartCurrentAttemptError, @@ -704,11 +705,15 @@ && isSignupUserAlreadyExistsFailure(err); const blockedByStep4Route405 = typeof isStep4Route405RecoveryLimitFailure === 'function' && isStep4Route405RecoveryLimitFailure(err); + const blockedByKiroProxy = typeof isKiroProxyFailure === 'function' + && isKiroProxyFailure(err); const canRetry = !blockedByAddPhone && !blockedByPhoneNoSupply && !blockedByPlusNonFreeTrial && !blockedByGpcTaskEnded && !blockedBySignupUserAlreadyExists + && !blockedByStep4Route405 + && !blockedByKiroProxy && autoRunSkipFailures && attemptRun < maxAttemptsForRound; @@ -926,6 +931,27 @@ break; } + if (blockedByKiroProxy) { + roundSummary.status = 'failed'; + roundSummary.finalFailureReason = reason; + await setState({ + autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries), + }); + await appendRoundRecordIfNeeded('failed', reason, err); + cancelPendingCommands('当前轮检测到 Kiro 代理异常页,已停止自动运行,等待用户切换代理。'); + await broadcastStopToContentScripts(); + await addLog(`第 ${targetRun}/${totalRuns} 轮检测到 Kiro 代理异常页:${reason}`, 'error'); + await addLog('当前代理可能不可用,请先切换代理后再继续。自动运行已停止。', 'warn'); + stoppedEarly = true; + await broadcastAutoRunStatus('stopped', { + currentRun: targetRun, + totalRuns, + attemptRun, + sessionId: 0, + }); + break; + } + if (canRetry) { const retryIndex = attemptRun; if (isRestartCurrentAttemptError(err)) { diff --git a/background/logging-status.js b/background/logging-status.js index e0c41cc..d26ad26 100644 --- a/background/logging-status.js +++ b/background/logging-status.js @@ -143,6 +143,11 @@ return /SIGNUP_USER_ALREADY_EXISTS::|user_already_exists/i.test(message); } + function isKiroProxyFailure(error) { + const message = getErrorMessage(error); + return /Kiro\s*(?:注册页|桌面授权页).*(?:CloudFront\s*拒绝请求|AWS\s*请求异常)|(?:当前代理\s*IP|出口区域异常).*(?:切换代理|更换代理)|AWS\s*风控.*(?:切换代理|更换代理)/i.test(message); + } + function isStep9RecoverableAuthError(error) { const message = String(typeof error === 'string' ? error : error?.message || ''); return /STEP9_OAUTH_RETRY::/i.test(message) @@ -236,6 +241,7 @@ getSourceLabel, hasSavedNodeProgress, hasSavedProgress, + isKiroProxyFailure, isLegacyStep9RecoverableAuthError, isRestartCurrentAttemptError, isSignupUserAlreadyExistsFailure, diff --git a/content/kiro/desktop-authorize-page.js b/content/kiro/desktop-authorize-page.js index 918bac4..6a0d679 100644 --- a/content/kiro/desktop-authorize-page.js +++ b/content/kiro/desktop-authorize-page.js @@ -4,6 +4,7 @@ const KIRO_DESKTOP_AUTHORIZE_LISTENER_SENTINEL = 'data-multipage-kiro-desktop-au const KIRO_DESKTOP_ALLOW_TEXT_PATTERN = /allow access|authorize|continue|allow|允许访问|授权|继续/i; const KIRO_DESKTOP_LOADING_TEXT_PATTERN = /loading|redirecting|please wait|加载中|跳转中|请稍候/i; const KIRO_DESKTOP_CLOUDFRONT_403_TEXT_PATTERN = /403 error|the request could not be satisfied|generated by cloudfront/i; +const KIRO_DESKTOP_AWS_REQUEST_ERROR_TEXT_PATTERN = /sorry,\s*there was an error processing your request\.?\s*please try again\.?|抱歉,处理您的请求时出错。请重试。?/i; const KIRO_DESKTOP_EMAIL_INPUT_SELECTOR = [ 'input[placeholder="username@example.com"]', @@ -94,8 +95,30 @@ function findDesktopActionButton(options = {}) { return pool[0] || null; } +function isKiroDesktopAuthHost(hostname = '') { + const normalized = String(hostname || '').trim().toLowerCase(); + return normalized === 'view.awsapps.com' + || normalized === 'login.awsapps.com' + || normalized === 'profile.aws.amazon.com' + || normalized === 'profile.aws' + || normalized.endsWith('.profile.aws.amazon.com') + || normalized.endsWith('.profile.aws') + || normalized === 'signin.aws.amazon.com' + || normalized === 'signin.aws' + || normalized.endsWith('.signin.aws.amazon.com') + || normalized.endsWith('.signin.aws'); +} + function detectDesktopFatalState(pageText = '', currentUrl = '', pageTitle = '') { const combinedText = `${pageTitle} ${pageText}`.replace(/\s+/g, ' ').trim(); + const isKiroAuthHost = isKiroDesktopAuthHost(location.hostname || ''); + if (isKiroAuthHost && KIRO_DESKTOP_AWS_REQUEST_ERROR_TEXT_PATTERN.test(combinedText)) { + return { + state: 'proxy_error_page', + url: currentUrl, + fatalMessage: 'Kiro 桌面授权页出现 AWS 请求异常,通常是当前代理 IP 或出口区域异常,请先切换代理后再重试。', + }; + } if (KIRO_DESKTOP_CLOUDFRONT_403_TEXT_PATTERN.test(combinedText)) { return { state: 'cloudfront_403_page', @@ -204,8 +227,8 @@ function detectKiroDesktopAuthorizeState() { async function getCurrentDesktopAuthorizeState() { const detected = detectKiroDesktopAuthorizeState(); - if (detected.state === 'cloudfront_403_page') { - throw new Error(detected.fatalMessage || 'Kiro 桌面授权页出现 403。'); + if (detected.state === 'cloudfront_403_page' || detected.state === 'proxy_error_page') { + throw new Error(detected.fatalMessage || 'Kiro 桌面授权页出现代理异常。'); } if (detected.state === 'callback_error') { throw new Error(`Kiro 桌面授权回调失败:${detected.error || 'unknown_error'}`); diff --git a/content/kiro/register-page.js b/content/kiro/register-page.js index 02e06f2..7e24020 100644 --- a/content/kiro/register-page.js +++ b/content/kiro/register-page.js @@ -7,6 +7,7 @@ const KIRO_CONFIRM_CONTINUE_TEXT_PATTERN = /confirm and continue|确认并继续 const KIRO_ALLOW_ACCESS_TEXT_PATTERN = /allow access|允许访问/i; const KIRO_SUCCESS_TEXT_PATTERN = /authorization successful|you may now close this window|you are now signed in|授权成功|可以关闭此窗口|已登录/i; const KIRO_CLOUDFRONT_403_TEXT_PATTERN = /403 error|the request could not be satisfied|generated by cloudfront/i; +const KIRO_AWS_REQUEST_ERROR_TEXT_PATTERN = /sorry,\s*there was an error processing your request\.?\s*please try again\.?|抱歉,处理您的请求时出错。请重试。?/i; const KIRO_EMAIL_INPUT_SELECTOR = [ 'input[placeholder="username@example.com"]', @@ -193,20 +194,39 @@ function findAuthorizationActionButton() { }); } +function isKiroAwsAuthHost(hostname = '') { + const normalized = String(hostname || '').trim().toLowerCase(); + return normalized === 'view.awsapps.com' + || normalized === 'login.awsapps.com' + || normalized === 'profile.aws.amazon.com' + || normalized === 'profile.aws' + || normalized.endsWith('.profile.aws.amazon.com') + || normalized.endsWith('.profile.aws') + || normalized === 'signin.aws.amazon.com' + || normalized === 'signin.aws' + || normalized.endsWith('.signin.aws.amazon.com') + || normalized.endsWith('.signin.aws'); +} + function detectKiroFatalPageState(pageText = '', currentUrl = '', pageTitle = '') { const combinedText = `${pageTitle} ${pageText}`.replace(/\s+/g, ' ').trim(); const normalizedHost = String(location.hostname || '').trim().toLowerCase(); - const isAwsProfileHost = normalizedHost === 'profile.aws.amazon.com' - || normalizedHost === 'profile.aws' - || normalizedHost.endsWith('.profile.aws.amazon.com') - || normalizedHost.endsWith('.profile.aws'); + const isKiroAuthHost = isKiroAwsAuthHost(normalizedHost); const matchedSignals = [ /403 error/i.test(combinedText), /the request could not be satisfied/i.test(combinedText), /generated by cloudfront/i.test(combinedText), ].filter(Boolean).length; - if (matchedSignals >= 2 || (isAwsProfileHost && matchedSignals >= 1 && KIRO_CLOUDFRONT_403_TEXT_PATTERN.test(combinedText))) { + if (isKiroAuthHost && KIRO_AWS_REQUEST_ERROR_TEXT_PATTERN.test(combinedText)) { + return { + state: 'proxy_error_page', + url: currentUrl, + fatalMessage: 'Kiro 注册页出现 AWS 请求异常,通常是当前代理 IP 或出口区域异常,请先切换代理后再重试。', + }; + } + + if (matchedSignals >= 2 || (isKiroAuthHost && matchedSignals >= 1 && KIRO_CLOUDFRONT_403_TEXT_PATTERN.test(combinedText))) { return { state: 'cloudfront_403_page', url: currentUrl, @@ -218,7 +238,8 @@ function detectKiroFatalPageState(pageText = '', currentUrl = '', pageTitle = '' } function isKiroFatalState(state = '') { - return state === 'cloudfront_403_page'; + return state === 'cloudfront_403_page' + || state === 'proxy_error_page'; } function getKiroFatalStateMessage(snapshot = {}) { @@ -228,6 +249,8 @@ function getKiroFatalStateMessage(snapshot = {}) { switch (snapshot?.state) { case 'cloudfront_403_page': return 'Kiro 注册页返回 403(CloudFront 拒绝请求),通常是当前代理 IP 或区域触发了 AWS 风控,请更换代理后重试。'; + case 'proxy_error_page': + return 'Kiro 注册页出现 AWS 请求异常,通常是当前代理 IP 或出口区域异常,请先切换代理后再重试。'; default: return `Kiro 页面出现异常状态:${snapshot?.state || 'unknown'}`; } diff --git a/tests/auto-run-kiro-flow-selection.test.js b/tests/auto-run-kiro-flow-selection.test.js index 1de7939..0c9c5fa 100644 --- a/tests/auto-run-kiro-flow-selection.test.js +++ b/tests/auto-run-kiro-flow-selection.test.js @@ -244,3 +244,177 @@ test('auto-run controller preserves kiro flow across fresh reset and starts from assert.deepStrictEqual(executedNodeIds, ['kiro-open-register-page']); assert.equal(helperCalls, 1); }); + +test('auto-run controller stops immediately on kiro proxy failures even when skip-failures is enabled', async () => { + const source = fs.readFileSync('background/auto-run-controller.js', 'utf8'); + const globalScope = {}; + const controllerApi = new Function('self', `${source}; return self.MultiPageBackgroundAutoRunController;`)(globalScope); + + const kiroNodeIds = [ + 'kiro-open-register-page', + 'kiro-submit-email', + 'kiro-submit-name', + 'kiro-submit-verification-code', + 'kiro-submit-password', + 'kiro-complete-register-consent', + 'kiro-start-desktop-authorize', + 'kiro-complete-desktop-authorize', + 'kiro-upload-credential', + ]; + const events = { + logs: [], + phases: [], + runCalls: 0, + cancelReasons: [], + records: [], + }; + let currentState = { + activeFlowId: 'kiro', + flowId: 'kiro', + autoRunSkipFailures: true, + autoRunFallbackThreadIntervalMinutes: 0, + autoRunDelayEnabled: false, + autoRunDelayMinutes: 30, + autoStepDelaySeconds: null, + nodeStatuses: { + 'kiro-open-register-page': 'pending', + 'kiro-submit-email': 'pending', + 'kiro-submit-name': 'pending', + 'kiro-submit-verification-code': 'pending', + 'kiro-submit-password': 'pending', + 'kiro-complete-register-consent': 'pending', + 'kiro-start-desktop-authorize': 'pending', + 'kiro-complete-desktop-authorize': 'pending', + 'kiro-upload-credential': 'pending', + }, + tabRegistry: {}, + sourceLastUrls: {}, + autoRunRoundSummaries: [], + }; + let sessionSeed = 900; + const runtime = { + state: { + autoRunActive: false, + autoRunCurrentRun: 0, + autoRunTotalRuns: 1, + autoRunAttemptRun: 0, + autoRunSessionId: 0, + }, + get() { + return { ...this.state }; + }, + set(updates = {}) { + this.state = { ...this.state, ...updates }; + }, + }; + + const controller = controllerApi.createAutoRunController({ + addLog: async (message, level = 'info') => { + events.logs.push({ message, level }); + }, + appendAccountRunRecord: async (status, _state, reason) => { + events.records.push({ status, reason }); + return { status, reason }; + }, + AUTO_RUN_MAX_RETRIES_PER_ROUND: 3, + AUTO_RUN_RETRY_DELAY_MS: 3000, + AUTO_RUN_TIMER_KIND_BEFORE_RETRY: 'before_retry', + AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS: 'between_rounds', + broadcastAutoRunStatus: async (phase, payload = {}) => { + events.phases.push({ phase, ...payload }); + currentState = { + ...currentState, + autoRunning: ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying', 'waiting_interval'].includes(phase), + autoRunPhase: phase, + autoRunCurrentRun: payload.currentRun ?? currentState.autoRunCurrentRun ?? 0, + autoRunTotalRuns: payload.totalRuns ?? currentState.autoRunTotalRuns ?? 1, + autoRunAttemptRun: payload.attemptRun ?? currentState.autoRunAttemptRun ?? 0, + }; + }, + broadcastStopToContentScripts: async () => {}, + cancelPendingCommands: (reason) => { + events.cancelReasons.push(reason); + }, + clearStopRequest: () => {}, + createAutoRunSessionId: () => { + sessionSeed += 1; + return sessionSeed; + }, + ensureHotmailMailboxReadyForAutoRunRound: async () => {}, + getAutoRunStatusPayload: (phase, payload = {}) => ({ + autoRunning: ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying', 'waiting_interval'].includes(phase), + autoRunPhase: phase, + autoRunCurrentRun: payload.currentRun ?? 0, + autoRunTotalRuns: payload.totalRuns ?? 1, + autoRunAttemptRun: payload.attemptRun ?? 0, + autoRunSessionId: payload.sessionId ?? 0, + }), + getErrorMessage: (error) => error?.message || String(error || ''), + getFirstUnfinishedNodeId: (statuses = {}) => { + for (const nodeId of kiroNodeIds) { + const status = String(statuses?.[nodeId] || 'pending').trim().toLowerCase(); + if (!['completed', 'manual_completed', 'skipped'].includes(status)) { + return nodeId; + } + } + return ''; + }, + getPendingAutoRunTimerPlan: () => null, + getRunningNodeIds: () => [], + getState: async () => ({ + ...currentState, + nodeStatuses: { ...(currentState.nodeStatuses || {}) }, + tabRegistry: { ...(currentState.tabRegistry || {}) }, + sourceLastUrls: { ...(currentState.sourceLastUrls || {}) }, + }), + getStopRequested: () => false, + hasSavedNodeProgress: () => false, + isAddPhoneAuthFailure: () => false, + isGpcTaskEndedFailure: () => false, + isKiroProxyFailure: (error) => /Kiro 注册页出现 AWS 请求异常|Kiro 注册页返回 403|切换代理|更换代理/i.test(error?.message || String(error || '')), + isPhoneSmsPlatformRateLimitFailure: () => false, + isPlusCheckoutNonFreeTrialFailure: () => false, + isRestartCurrentAttemptError: () => false, + isStep4Route405RecoveryLimitFailure: () => false, + isSignupUserAlreadyExistsFailure: () => false, + isStopError: () => false, + launchAutoRunTimerPlan: async () => false, + normalizeAutoRunFallbackThreadIntervalMinutes: (value) => Math.max(0, Number(value) || 0), + persistAutoRunTimerPlan: async () => {}, + resetState: async () => {}, + runAutoSequenceFromNode: async (nodeId) => { + events.runCalls += 1; + assert.equal(nodeId, 'kiro-open-register-page'); + throw new Error('Kiro 注册页出现 AWS 请求异常,通常是当前代理 IP 或出口区域异常,请先切换代理后再重试。'); + }, + runtime, + setState: async (updates = {}) => { + currentState = { + ...currentState, + ...updates, + nodeStatuses: updates.nodeStatuses ? { ...updates.nodeStatuses } : currentState.nodeStatuses, + tabRegistry: updates.tabRegistry ? { ...updates.tabRegistry } : currentState.tabRegistry, + sourceLastUrls: updates.sourceLastUrls ? { ...updates.sourceLastUrls } : currentState.sourceLastUrls, + }; + }, + sleepWithStop: async () => {}, + throwIfAutoRunSessionStopped: () => {}, + waitForRunningNodesToFinish: async () => currentState, + chrome: { + runtime: { + sendMessage: () => Promise.resolve(), + }, + }, + }); + + await controller.autoRunLoop(2, { autoRunSkipFailures: true, mode: 'restart' }); + + assert.equal(events.runCalls, 1); + assert.equal(events.phases[0]?.phase, 'running'); + assert.equal(events.phases.at(-1)?.phase, 'stopped'); + assert.equal(events.phases.some((entry) => entry.phase === 'retrying'), false); + assert.equal(events.cancelReasons.length, 1); + assert.match(events.cancelReasons[0], /Kiro 代理异常页/); + assert.ok(events.logs.some((entry) => /切换代理/.test(entry.message))); + assert.ok(events.records.some((entry) => /failed$/i.test(entry.status))); +}); diff --git a/tests/background-logging-status-module.test.js b/tests/background-logging-status-module.test.js index e7e5bc5..ed0cbcc 100644 --- a/tests/background-logging-status-module.test.js +++ b/tests/background-logging-status-module.test.js @@ -50,4 +50,16 @@ test('logging/status add-phone detection ignores step 2 phone-entry switch failu loggingStatus.getErrorMessage(new Error('GPC_TASK_ENDED::GPC OTP 超时,请重新创建任务')), 'GPC OTP 超时,请重新创建任务' ); + assert.equal( + loggingStatus.isKiroProxyFailure('Kiro 注册页出现 AWS 请求异常,通常是当前代理 IP 或出口区域异常,请先切换代理后再重试。'), + true + ); + assert.equal( + loggingStatus.isKiroProxyFailure('Kiro 注册页返回 403(CloudFront 拒绝请求),通常是当前代理 IP 或区域触发了 AWS 风控,请更换代理后重试。'), + true + ); + assert.equal( + loggingStatus.isKiroProxyFailure('步骤 2:邮箱已提交,当前已进入姓名页。'), + false + ); }); diff --git a/tests/kiro-register-page-fatal-state.test.js b/tests/kiro-register-page-fatal-state.test.js new file mode 100644 index 0000000..d989b84 --- /dev/null +++ b/tests/kiro-register-page-fatal-state.test.js @@ -0,0 +1,79 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const vm = require('node:vm'); + +const source = fs.readFileSync('content/kiro/register-page.js', 'utf8'); + +function createHarness({ href, hostname, title = '', bodyText = '' }) { + const context = { + console: { log() {}, warn() {}, error() {}, info() {} }, + location: { href, hostname }, + document: { + title, + body: { + textContent: bodyText, + }, + documentElement: { + getAttribute() { + return '1'; + }, + setAttribute() {}, + }, + querySelector() { + return null; + }, + querySelectorAll() { + return []; + }, + }, + window: {}, + globalThis: null, + resetStopState() {}, + isStopError() { + return false; + }, + throwIfStopped() {}, + sleep() { + return Promise.resolve(); + }, + fillInput() {}, + MouseEvent: class {}, + PointerEvent: class {}, + KeyboardEvent: class {}, + }; + context.window = context; + context.globalThis = context; + context.window.getComputedStyle = () => ({ display: 'block', visibility: 'visible' }); + + vm.createContext(context); + vm.runInContext(source, context); + return context; +} + +test('kiro register content detects aws request error page as a proxy failure', () => { + const harness = createHarness({ + href: 'https://profile.aws.amazon.com/signup', + hostname: 'profile.aws.amazon.com', + title: '错误', + bodyText: '抱歉,处理您的请求时出错。请重试。', + }); + + const detected = harness.detectKiroRegisterPageState(); + + assert.equal(detected.state, 'proxy_error_page'); + assert.match(detected.fatalMessage, /切换代理/); +}); + +test('kiro register content does not misclassify the normal name page as a proxy failure', () => { + const harness = createHarness({ + href: 'https://profile.aws.amazon.com/signup', + hostname: 'profile.aws.amazon.com', + title: 'Enter your name', + bodyText: '输入您的姓名 继续', + }); + + const fatal = harness.detectKiroFatalPageState('输入您的姓名 继续', harness.location.href, harness.document.title); + + assert.equal(fatal, null); +}); diff --git a/tests/sidepanel-sub2api-priority-settings.test.js b/tests/sidepanel-sub2api-priority-settings.test.js index f665a7e..2b48386 100644 --- a/tests/sidepanel-sub2api-priority-settings.test.js +++ b/tests/sidepanel-sub2api-priority-settings.test.js @@ -41,7 +41,7 @@ test('sidepanel persists and locks SUB2API account priority setting', () => { /inputSub2ApiAccountPriority\.value = String\(normalizeSub2ApiAccountPriorityValue\(state\?\.sub2apiAccountPriority\)\);/ ); assert.match(source, /applyFlowSettingsGroupVisibility\(visibleGroupIds\);/); - assert.match(flowRegistrySource, /'openai-source-sub2api': \{[\s\S]*'row-sub2api-account-priority'/); + assert.match(flowRegistrySource, /'openai-target-sub2api': \{[\s\S]*'row-sub2api-account-priority'/); assert.match(source, /inputSub2ApiAccountPriority\.disabled = locked;/); assert.match( source,