fix: stop kiro auto-run on proxy error pages
This commit is contained in:
@@ -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)));
|
||||
});
|
||||
|
||||
@@ -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
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user