diff --git a/flows/openai/background/steps/oauth-login.js b/flows/openai/background/steps/oauth-login.js index 88bc6d0..2241721 100644 --- a/flows/openai/background/steps/oauth-login.js +++ b/flows/openai/background/steps/oauth-login.js @@ -208,9 +208,9 @@ throw new Error(`步骤 ${completionStepForState(currentState)}:邮箱注册模式 OAuth 登录不应进入添加邮箱页。URL: ${result?.url || ''}`.trim()); } if (isStep7AddPhoneResult(result) || isStep7PhoneVerificationResult(result)) { - payload.skipLoginVerificationStep = true; payload.addPhonePage = isStep7AddPhoneResult(result); payload.phoneVerificationPage = isStep7PhoneVerificationResult(result); + payload.directOAuthConsentPage = false; return payload; } if (isStep7PlainVerificationResult(result)) { @@ -233,8 +233,8 @@ } await completeNodeFromBackground(state?.nodeId || 'oauth-login', { loginVerificationRequestedAt: null, - skipLoginVerificationStep: true, addPhonePage: true, + phoneVerificationPage: false, directOAuthConsentPage: false, }); } diff --git a/phone-sms/providers/sms-bower.js b/phone-sms/providers/sms-bower.js index 7bfea2b..5736a45 100644 --- a/phone-sms/providers/sms-bower.js +++ b/phone-sms/providers/sms-bower.js @@ -365,7 +365,11 @@ await (String(options?.releaseAction || '').trim().toLowerCase() === 'ban' ? banActivation(state, activation, deps) : cancelActivation(state, activation, deps)); - return { currentTicketId: String(activation?.activationId || activation?.id || ''), nextActivation: null }; + const nextActivation = await requestActivation(state, { + ...options, + skipPreferredActivation: true, + }, deps); + return { currentTicketId: String(activation?.activationId || activation?.id || ''), nextActivation }; } function extractVerificationCode(raw = '') { diff --git a/tests/background-step6-retry-limit.test.js b/tests/background-step6-retry-limit.test.js index a073a19..e6ffe82 100644 --- a/tests/background-step6-retry-limit.test.js +++ b/tests/background-step6-retry-limit.test.js @@ -176,7 +176,7 @@ test('step 7 preserves visible step when refreshing OAuth after retry', async () assert.deepStrictEqual(events.refreshSteps, [9, 9, 9]); }); -test('step 7 hands add-phone to the dedicated post-login phone node without internal retry', async () => { +test('step 7 hands add-phone to the dedicated post-login phone node without internal retry and keeps login code node active', async () => { const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8'); const globalScope = {}; const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope); @@ -222,12 +222,16 @@ test('step 7 hands add-phone to the dedicated post-login phone node without inte step: 'oauth-login', payload: { loginVerificationRequestedAt: null, - skipLoginVerificationStep: true, addPhonePage: true, + phoneVerificationPage: false, directOAuthConsentPage: false, }, }, ]); + assert.ok( + !Object.prototype.hasOwnProperty.call(events.completions[0].payload, 'skipLoginVerificationStep'), + 'add-phone handoff must keep the login-code node active so step 9 phone verification can run next' + ); assert.ok( !events.logs.some(({ message }) => /准备重试/.test(message)), 'add-phone failure should not be logged as an internal retryable attempt' @@ -291,8 +295,8 @@ test('step 7 no longer runs shared phone verification inside oauth-login', async step: 'oauth-login', payload: { loginVerificationRequestedAt: null, - skipLoginVerificationStep: true, addPhonePage: true, + phoneVerificationPage: false, directOAuthConsentPage: false, }, }, diff --git a/tests/sms-bower-provider.test.js b/tests/sms-bower-provider.test.js index f6cb128..edc639b 100644 --- a/tests/sms-bower-provider.test.js +++ b/tests/sms-bower-provider.test.js @@ -142,3 +142,54 @@ test('SMS Bower provider acquires number with getNumberV2, polls code, and updat ] ); }); + + +test('SMS Bower rotate cancels rejected number then immediately acquires the next number', async () => { + const requests = []; + const provider = api.createProvider({ + fetchImpl: async (url, options = {}) => { + const parsed = new URL(url); + const action = parsed.searchParams.get('action'); + requests.push({ url: parsed, options }); + if (action === 'setStatus') { + return createTextResponse('ACCESS_CANCEL'); + } + if (action === 'getNumberV2') { + return createTextResponse({ activationId: 'next-activation', phoneNumber: '447700900456', activationCost: 0.22, countryCode: 16 }); + } + throw new Error(`unexpected ${parsed.toString()}`); + }, + sleepWithStop: async () => {}, + throwIfStopped: () => {}, + }); + + const state = { + smsBowerApiKey: 'demo-key', + smsBowerServiceCode: 'dr', + smsBowerCountryId: 16, + smsBowerCountryLabel: 'United Kingdom', + smsBowerMinPrice: '0.1', + smsBowerMaxPrice: '0.3', + }; + + const rotated = await provider.rotateActivation( + state, + { activationId: 'old-activation', phoneNumber: '447700900123', provider: 'sms-bower', countryId: 16 }, + { releaseAction: 'cancel', blockedCountryIds: [] } + ); + + assert.equal(rotated.currentTicketId, 'old-activation'); + assert.equal(rotated.nextActivation.activationId, 'next-activation'); + assert.equal(rotated.nextActivation.phoneNumber, '447700900456'); + assert.deepStrictEqual( + requests.map((entry) => [ + entry.url.searchParams.get('action'), + entry.url.searchParams.get('id'), + entry.url.searchParams.get('status'), + ]), + [ + ['setStatus', 'old-activation', '8'], + ['getNumberV2', null, null], + ] + ); +});