refactor flows into canonical runtime architecture

This commit is contained in:
QLHazyCoder
2026-05-21 07:21:34 +08:00
parent e2485d2e64
commit a7b35ee11a
167 changed files with 9034 additions and 3032 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+2 -2
View File
@@ -2,8 +2,8 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { createAuthPageRecovery } = require('../content/auth-page-recovery.js');
const source = fs.readFileSync('content/auth-page-recovery.js', 'utf8');
const { createAuthPageRecovery } = require('../flows/openai/content/auth-page-recovery.js');
const source = fs.readFileSync('flows/openai/content/auth-page-recovery.js', 'utf8');
function extractFunction(sourceText, name) {
const start = sourceText.indexOf(`function ${name}(`);
+2 -2
View File
@@ -311,10 +311,10 @@ async function runAutoSequenceFromStep() {
10: 'completed',
}),
tabRegistry: {
'signup-page': { tabId: 88, ready: true },
'openai-auth': { tabId: 88, ready: true },
},
sourceLastUrls: {
'signup-page': 'https://auth.openai.com/authorize',
'openai-auth': 'https://auth.openai.com/authorize',
},
};
}
@@ -285,7 +285,7 @@ return {
assert.equal(api.normalizePersistentSettingValue('signupMethod', 'unknown'), 'email');
assert.equal(api.normalizePersistentSettingValue('activeFlowId', 'codex'), 'openai');
assert.equal(api.normalizePersistentSettingValue('activeFlowId', 'kiro'), 'kiro');
assert.equal(api.normalizePersistentSettingValue('kiroTargetId', 'unknown'), 'kiro-rs');
assert.equal(api.normalizePersistentSettingValue('targetId', 'sub2api'), 'sub2api');
assert.equal(api.normalizePersistentSettingValue('kiroRsUrl', ''), '');
assert.equal(api.normalizePersistentSettingValue('kiroRsKey', ' key-1 '), 'key-1');
assert.equal(api.normalizePersistentSettingValue('phoneSmsProvider', '5SIM'), '5sim');
+10 -10
View File
@@ -120,7 +120,7 @@ const self = {
},
},
};
const DEFAULT_STATE = { panelMode: 'cpa' };
const DEFAULT_STATE = { targetId: 'cpa' };
const CONTRIBUTION_RUNTIME_DEFAULTS = {
accountContributionEnabled: false,
accountContributionExpected: false,
@@ -150,7 +150,7 @@ return { buildAccountContributionState };
`)();
const enabledState = api.buildAccountContributionState(true, {
panelMode: 'sub2api',
targetId: 'sub2api',
customPassword: 'Secret123!',
accountRunHistoryTextEnabled: true,
}, {
@@ -166,12 +166,12 @@ return { buildAccountContributionState };
openai: { enabled: true, adapterId: 'openai-oauth' },
});
assert.equal(enabledState.contributionSessionId, 'session-001');
assert.equal(enabledState.panelMode, 'sub2api');
assert.equal(enabledState.targetId, 'sub2api');
assert.equal(enabledState.customPassword, '');
assert.equal(enabledState.accountRunHistoryTextEnabled, false);
const disabledState = api.buildAccountContributionState(false, {
panelMode: 'sub2api',
targetId: 'sub2api',
customPassword: 'Secret123!',
accountRunHistoryTextEnabled: true,
}, {
@@ -184,17 +184,17 @@ return { buildAccountContributionState };
assert.equal(disabledState.contributionAdapterId, '');
assert.deepStrictEqual(disabledState.flowContributionRuntime, {});
assert.equal(disabledState.contributionSessionId, '');
assert.equal(disabledState.panelMode, 'sub2api');
assert.equal(disabledState.targetId, 'sub2api');
assert.equal(disabledState.customPassword, 'Secret123!');
const plusContributionState = api.buildAccountContributionState(true, {
panelMode: 'cpa',
targetId: 'cpa',
plusModeEnabled: true,
customPassword: 'Secret123!',
accountRunHistoryTextEnabled: true,
}, {});
assert.equal(plusContributionState.contributionTargetGroupName, 'openai-plus');
assert.equal(plusContributionState.panelMode, 'sub2api');
assert.equal(plusContributionState.targetId, 'sub2api');
});
test('resetState preserves contribution runtime across reset', () => {
@@ -289,7 +289,7 @@ test('message router handles contribution mode, start flow, and status polling m
calls.push({ type: 'toggle', enabled });
return {
accountContributionEnabled: Boolean(enabled),
panelMode: 'cpa',
targetId: 'cpa',
};
},
startFlowContribution: async (options) => {
@@ -383,7 +383,7 @@ test('message router blocks AUTO_RUN and SCHEDULE_AUTO_RUN when shared auto-run
getPendingAutoRunTimerPlan: () => null,
getState: async () => ({
activeFlowId: 'site-a',
panelMode: 'cpa',
targetId: 'cpa',
signupMethod: 'phone',
stepStatuses: {},
}),
@@ -947,7 +947,7 @@ return { refreshOAuthUrlBeforeStep6 };
const oauthUrl = await api.refreshOAuthUrlBeforeStep6({
accountContributionEnabled: false,
panelMode: 'sub2api',
targetId: 'sub2api',
email: 'user@example.com',
});
+2 -2
View File
@@ -3,7 +3,7 @@ const fs = require('node:fs');
const test = require('node:test');
function loadCpaSessionImportModule() {
const source = fs.readFileSync('background/steps/cpa-session-import.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/cpa-session-import.js', 'utf8');
return new Function('self', `${source}; return self.MultiPageBackgroundCpaSessionImport;`)({});
}
@@ -78,7 +78,7 @@ test('CPA session import step reads current ChatGPT session and completes node',
assert.deepStrictEqual(ensureCalls[0].options.inject, [
'content/utils.js',
'content/operation-delay.js',
'content/plus-checkout.js',
'flows/openai/content/plus-checkout.js',
]);
assert.deepStrictEqual(sentMessages, [{
tabId: 91,
@@ -120,20 +120,20 @@ function isPlusModeState(state = {}) {
return Boolean(state?.plusModeEnabled);
}
function resolveCurrentFlowCapabilities(state = {}, options = {}) {
const normalizedPanelMode = String(options.panelMode || '').trim().toLowerCase();
const normalizedTargetId = String(options.targetId || '').trim().toLowerCase();
const requestedStrategy = normalizePlusAccountAccessStrategy(state.plusAccountAccessStrategy);
const effectiveStrategy = normalizedPanelMode === 'sub2api'
const effectiveStrategy = normalizedTargetId === 'sub2api'
? (requestedStrategy === 'sub2api_codex_session' ? 'sub2api_codex_session' : 'oauth')
: (normalizedPanelMode === 'cpa'
: (normalizedTargetId === 'cpa'
? (requestedStrategy === 'cpa_codex_session' ? 'cpa_codex_session' : 'oauth')
: 'oauth');
return {
effectivePanelMode: options.panelMode,
effectiveTargetId: options.targetId,
effectivePlusAccountAccessStrategy: effectiveStrategy,
effectiveSignupMethod: 'email',
stepDefinitionOptions: {
activeFlowId: 'openai',
panelMode: options.panelMode,
targetId: options.targetId,
plusModeEnabled: Boolean(state.plusModeEnabled),
plusPaymentMethod: normalizePlusPaymentMethod(state.plusPaymentMethod),
plusAccountAccessStrategy: effectiveStrategy,
@@ -158,7 +158,7 @@ test('background step resolution keeps SUB2API session tail only when the effect
const state = {
activeFlowId: 'openai',
flowId: 'openai',
panelMode: 'sub2api',
targetId: 'sub2api',
plusModeEnabled: true,
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'sub2api_codex_session',
@@ -184,7 +184,7 @@ test('background step resolution keeps CPA session tail when the effective Plus
const state = {
activeFlowId: 'openai',
flowId: 'openai',
panelMode: 'cpa',
targetId: 'cpa',
plusModeEnabled: true,
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'cpa_codex_session',
@@ -210,7 +210,7 @@ test('background step resolution falls back to OAuth tail when the requested ses
const state = {
activeFlowId: 'openai',
flowId: 'openai',
panelMode: 'cpa',
targetId: 'cpa',
plusModeEnabled: true,
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'sub2api_codex_session',
@@ -3,34 +3,42 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadDesktopAuthorizeRunnerApi() {
const stateSource = fs.readFileSync('background/kiro/state.js', 'utf8');
const clientSource = fs.readFileSync('background/kiro/desktop-client.js', 'utf8');
const runnerSource = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
const stateSource = fs.readFileSync('flows/kiro/background/state.js', 'utf8');
const clientSource = fs.readFileSync('flows/kiro/background/desktop-client.js', 'utf8');
const runnerSource = fs.readFileSync('flows/kiro/background/desktop-authorize-runner.js', 'utf8');
const globalScope = {};
new Function('self', `${stateSource}; ${clientSource}; ${runnerSource}; return self;`)(globalScope);
return globalScope.MultiPageBackgroundKiroDesktopAuthorizeRunner;
}
function getKiroRuntime(state = {}) {
return state?.runtimeState?.flowState?.kiro || {};
}
function createDesktopAuthorizeState(overrides = {}) {
return {
kiroRuntime: {
session: {
desktopTabId: 91,
runtimeState: {
flowState: {
kiro: {
session: {
desktopTabId: 91,
},
register: {
email: 'kiro-user@example.com',
},
desktopAuth: {
region: 'us-east-1',
clientId: 'desktop-client-id',
clientSecret: 'desktop-client-secret',
state: 'desktop-state-001',
codeVerifier: 'desktop-code-verifier',
redirectUri: 'http://127.0.0.1:43121/oauth/callback',
redirectPort: 43121,
authorizeUrl: 'https://example.com/authorize',
},
upload: {},
},
},
register: {
email: 'kiro-user@example.com',
},
desktopAuth: {
region: 'us-east-1',
clientId: 'desktop-client-id',
clientSecret: 'desktop-client-secret',
state: 'desktop-state-001',
codeVerifier: 'desktop-code-verifier',
redirectUri: 'http://127.0.0.1:43121/oauth/callback',
redirectPort: 43121,
authorizeUrl: 'https://example.com/authorize',
},
upload: {},
},
...overrides,
};
@@ -73,7 +81,7 @@ test('parseDesktopCallbackUrl validates state and redirect port', () => {
});
test('kiro desktop authorize runner uses a shared 3-minute page-load timeout budget', () => {
const source = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
const source = fs.readFileSync('flows/kiro/background/desktop-authorize-runner.js', 'utf8');
assert.match(source, /DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS/);
assert.match(source, /createTimeoutBudget/);
assert.match(source, /resolveTimeoutBudget/);
@@ -95,14 +103,14 @@ test('background wires the Kiro register injector into desktop authorization run
});
test('kiro desktop authorization opens account page when no web session tab is tracked', () => {
const source = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
const source = fs.readFileSync('flows/kiro/background/desktop-authorize-runner.js', 'utf8');
assert.match(source, /KIRO_WEB_ACCOUNT_URL = 'https:\/\/app\.kiro\.dev\/settings\/account'/);
assert.match(source, /chrome\.tabs\.create\(\{\s*url: KIRO_WEB_ACCOUNT_URL,\s*active: true,/);
assert.match(source, /未能从已打开页面确认 Kiro Web 登录态,正在打开 Kiro 账号页重新确认/);
});
test('kiro desktop authorization is gated by completed Kiro Web sign-in', () => {
const source = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
const source = fs.readFileSync('flows/kiro/background/desktop-authorize-runner.js', 'utf8');
assert.match(source, /restoreKiroWebSessionFromOpenTabs/);
assert.match(source, /GET_KIRO_REGISTER_PAGE_STATE/);
assert.match(source, /Kiro Web 登录态尚未建立/);
@@ -112,12 +120,16 @@ test('kiro desktop authorization is gated by completed Kiro Web sign-in', () =>
test('executeKiroStartDesktopAuthorize restores existing Kiro Web session before desktop auth', async () => {
const api = loadDesktopAuthorizeRunnerApi();
let currentState = {
kiroRuntime: {
session: {},
register: {},
webAuth: {},
desktopAuth: {},
upload: {},
runtimeState: {
flowState: {
kiro: {
session: {},
register: {},
webAuth: {},
desktopAuth: {},
upload: {},
},
},
},
};
const setStateCalls = [];
@@ -178,7 +190,7 @@ test('executeKiroStartDesktopAuthorize restores existing Kiro Web session before
getState: async () => currentState,
getTabId: async () => null,
isTabAlive: async () => false,
KIRO_REGISTER_INJECT_FILES: ['content/kiro/register-page.js'],
KIRO_REGISTER_INJECT_FILES: ['flows/kiro/content/register-page.js'],
registerTab: async (source, tabId) => {
if (source === 'kiro-desktop-authorize') {
desktopRegisteredTabId = tabId;
@@ -213,10 +225,10 @@ test('executeKiroStartDesktopAuthorize restores existing Kiro Web session before
assert.equal(desktopRegisteredTabId, 88);
assert.match(openedAuthorizeUrl, /client_id=restored-client-id/);
assert.equal(currentState.email, 'restored@duck.com');
assert.equal(currentState.kiroRuntime.register.email, 'restored@duck.com');
assert.equal(currentState.kiroRuntime.register.status, 'completed');
assert.equal(currentState.kiroRuntime.webAuth.status, 'signed_in');
assert.equal(completedPayload?.kiroRuntime?.desktopAuth?.clientId, 'restored-client-id');
assert.equal(getKiroRuntime(currentState).register.email, 'restored@duck.com');
assert.equal(getKiroRuntime(currentState).register.status, 'completed');
assert.equal(getKiroRuntime(currentState).webAuth.status, 'signed_in');
assert.equal(getKiroRuntime(completedPayload).desktopAuth?.clientId, 'restored-client-id');
assert.equal(
logs.some(({ message }) => message.includes('检测到已有 Kiro Web 登录态,已恢复账号 restored@duck.com')),
true
@@ -227,12 +239,16 @@ test('executeKiroStartDesktopAuthorize restores existing Kiro Web session before
test('executeKiroStartDesktopAuthorize opens Kiro account page to restore login state', async () => {
const api = loadDesktopAuthorizeRunnerApi();
let currentState = {
kiroRuntime: {
session: {},
register: {},
webAuth: {},
desktopAuth: {},
upload: {},
runtimeState: {
flowState: {
kiro: {
session: {},
register: {},
webAuth: {},
desktopAuth: {},
upload: {},
},
},
},
};
const logs = [];
@@ -278,7 +294,7 @@ test('executeKiroStartDesktopAuthorize opens Kiro account page to restore login
ensureContentScriptReadyOnTab: async (source, tabId, options = {}) => {
assert.equal(source, 'kiro-register-page');
assert.equal(tabId, 91);
assert.deepEqual(options.inject, ['content/kiro/register-page.js']);
assert.deepEqual(options.inject, ['flows/kiro/content/register-page.js']);
assert.equal(options.injectSource, 'kiro-register-page');
},
fetchImpl: async () => ({
@@ -291,7 +307,7 @@ test('executeKiroStartDesktopAuthorize opens Kiro account page to restore login
getState: async () => currentState,
getTabId: async () => null,
isTabAlive: async () => false,
KIRO_REGISTER_INJECT_FILES: ['content/kiro/register-page.js'],
KIRO_REGISTER_INJECT_FILES: ['flows/kiro/content/register-page.js'],
registerTab: async (source, tabId) => {
registeredTabs.push({ source, tabId });
},
@@ -320,8 +336,8 @@ test('executeKiroStartDesktopAuthorize opens Kiro account page to restore login
await runner.executeKiroStartDesktopAuthorize(currentState);
assert.equal(createdTabUrl, 'https://app.kiro.dev/settings/account');
assert.equal(currentState.kiroRuntime.register.email, 'opened@duck.com');
assert.equal(completedPayload?.kiroRuntime?.desktopAuth?.clientId, 'opened-client-id');
assert.equal(getKiroRuntime(currentState).register.email, 'opened@duck.com');
assert.equal(getKiroRuntime(completedPayload).desktopAuth?.clientId, 'opened-client-id');
assert.deepEqual(registeredTabs, [
{ source: 'kiro-register-page', tabId: 91 },
{ source: 'kiro-desktop-authorize', tabId: 92 },
@@ -391,31 +407,35 @@ test('executeKiroCompleteDesktopAuthorize finishes from callback page without wa
await runner.executeKiroCompleteDesktopAuthorize(currentState);
assert.equal(completedPayload?.kiroRuntime?.desktopAuth?.authorizationCode, 'auth-code-001');
assert.equal(completedPayload?.kiroRuntime?.desktopAuth?.refreshToken, 'refresh-token-001');
assert.equal(getKiroRuntime(completedPayload).desktopAuth?.authorizationCode, 'auth-code-001');
assert.equal(getKiroRuntime(completedPayload).desktopAuth?.refreshToken, 'refresh-token-001');
});
test('executeKiroCompleteDesktopAuthorize waits for callback after consent even if original tab disappears', async () => {
const api = loadDesktopAuthorizeRunnerApi();
let currentState = createDesktopAuthorizeState({
kiroRuntime: {
session: {
desktopTabId: 91,
runtimeState: {
flowState: {
kiro: {
session: {
desktopTabId: 91,
},
register: {
email: 'kiro-user@example.com',
},
desktopAuth: {
region: 'us-east-1',
clientId: 'desktop-client-id',
clientSecret: 'desktop-client-secret',
state: 'desktop-state-002',
codeVerifier: 'desktop-code-verifier',
redirectUri: 'http://127.0.0.1:43121/oauth/callback',
redirectPort: 43121,
authorizeUrl: 'https://example.com/authorize',
},
upload: {},
},
},
register: {
email: 'kiro-user@example.com',
},
desktopAuth: {
region: 'us-east-1',
clientId: 'desktop-client-id',
clientSecret: 'desktop-client-secret',
state: 'desktop-state-002',
codeVerifier: 'desktop-code-verifier',
redirectUri: 'http://127.0.0.1:43121/oauth/callback',
redirectPort: 43121,
authorizeUrl: 'https://example.com/authorize',
},
upload: {},
},
});
let completedPayload = null;
@@ -505,6 +525,6 @@ test('executeKiroCompleteDesktopAuthorize waits for callback after consent even
await runner.executeKiroCompleteDesktopAuthorize(currentState);
assert.equal(completedPayload?.kiroRuntime?.desktopAuth?.authorizationCode, 'auth-code-002');
assert.equal(completedPayload?.kiroRuntime?.desktopAuth?.refreshToken, 'refresh-token-002');
assert.equal(getKiroRuntime(completedPayload).desktopAuth?.authorizationCode, 'auth-code-002');
assert.equal(getKiroRuntime(completedPayload).desktopAuth?.refreshToken, 'refresh-token-002');
});
@@ -3,13 +3,17 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadPublisherApi() {
const stateSource = fs.readFileSync('background/kiro/state.js', 'utf8');
const publisherSource = fs.readFileSync('background/kiro/publisher-kiro-rs.js', 'utf8');
const stateSource = fs.readFileSync('flows/kiro/background/state.js', 'utf8');
const publisherSource = fs.readFileSync('flows/kiro/background/publisher-kiro-rs.js', 'utf8');
const globalScope = {};
new Function('self', `${stateSource}; ${publisherSource}; return self;`)(globalScope);
return globalScope.MultiPageBackgroundKiroPublisherKiroRs;
}
function getKiroRuntime(state = {}) {
return state?.runtimeState?.flowState?.kiro || {};
}
test('kiro publisher exposes a factory and upload payload helpers', () => {
const api = loadPublisherApi();
assert.equal(typeof api?.createKiroRsPublisher, 'function');
@@ -20,7 +24,7 @@ test('kiro publisher exposes a factory and upload payload helpers', () => {
test('kiro publisher builds kiro.rs payload from desktop auth runtime with BuilderId profileArn', async () => {
const api = loadPublisherApi();
const payload = api.buildKiroRsPayload({
kiroTargetId: 'kiro-rs',
targetId: 'kiro-rs',
kiroRsUrl: 'https://kiro.example.com/admin',
kiroRsKey: 'demo-key',
ipProxyEnabled: true,
@@ -29,18 +33,22 @@ test('kiro publisher builds kiro.rs payload from desktop auth runtime with Build
ipProxyProtocol: 'http',
ipProxyUsername: 'proxy-user',
ipProxyPassword: 'proxy-pass',
kiroRuntime: {
register: {
email: 'aws-user@example.com',
},
desktopAuth: {
region: 'us-east-1',
clientId: 'client-001',
clientSecret: 'secret-001',
refreshToken: 'refresh-token-001',
},
upload: {
targetId: 'kiro-rs',
runtimeState: {
flowState: {
kiro: {
register: {
email: 'aws-user@example.com',
},
desktopAuth: {
region: 'us-east-1',
clientId: 'client-001',
clientSecret: 'secret-001',
refreshToken: 'refresh-token-001',
},
upload: {
targetId: 'kiro-rs',
},
},
},
},
});
@@ -69,22 +77,26 @@ test('kiro publisher reads latest kiro.rs key from background state instead of s
const api = loadPublisherApi();
const requests = [];
let liveState = {
kiroTargetId: 'kiro-rs',
targetId: 'kiro-rs',
kiroRsUrl: 'https://kiro.example.com/admin',
kiroRsKey: 'live-key',
email: 'aws-user@example.com',
kiroRuntime: {
register: {
email: 'aws-user@example.com',
},
desktopAuth: {
region: 'us-east-1',
clientId: 'client-001',
clientSecret: 'secret-001',
refreshToken: 'refresh-token-001',
},
upload: {
targetId: 'kiro-rs',
runtimeState: {
flowState: {
kiro: {
register: {
email: 'aws-user@example.com',
},
desktopAuth: {
region: 'us-east-1',
clientId: 'client-001',
clientSecret: 'secret-001',
refreshToken: 'refresh-token-001',
},
upload: {
targetId: 'kiro-rs',
},
},
},
},
settingsState: {
@@ -180,19 +192,23 @@ test('kiro publisher routes step 9 through public contribution upload when contr
flowId: 'kiro',
accountContributionEnabled: true,
contributionAdapterId: 'kiro-builder-id',
kiroTargetId: 'kiro-rs',
kiroRuntime: {
register: {
email: 'aws-user@example.com',
},
desktopAuth: {
region: 'us-east-1',
clientId: 'client-001',
clientSecret: 'secret-001',
refreshToken: 'refresh-token-001',
},
upload: {
targetId: 'kiro-rs',
targetId: 'kiro-rs',
runtimeState: {
flowState: {
kiro: {
register: {
email: 'aws-user@example.com',
},
desktopAuth: {
region: 'us-east-1',
clientId: 'client-001',
clientSecret: 'secret-001',
refreshToken: 'refresh-token-001',
},
upload: {
targetId: 'kiro-rs',
},
},
},
},
};
@@ -224,10 +240,10 @@ test('kiro publisher routes step 9 through public contribution upload when contr
assert.equal(requests.length, 0);
assert.equal(completed.length, 1);
assert.equal(completed[0].nodeId, 'kiro-upload-credential');
assert.equal(completed[0].payload.kiroRuntime.upload.targetId, 'contribution');
assert.equal(completed[0].payload.kiroRuntime.upload.status, 'uploaded');
assert.equal(completed[0].payload.kiroRuntime.upload.credentialId, 'kiro-contribution-009');
assert.equal(completed[0].payload.kiroRuntime.upload.lastMessage, '贡献链路成功:kiro-step-9');
assert.equal(getKiroRuntime(completed[0].payload).upload.targetId, 'contribution');
assert.equal(getKiroRuntime(completed[0].payload).upload.status, 'uploaded');
assert.equal(getKiroRuntime(completed[0].payload).upload.credentialId, 'kiro-contribution-009');
assert.equal(getKiroRuntime(completed[0].payload).upload.lastMessage, '贡献链路成功:kiro-step-9');
});
test('kiro publisher trims api key and includes fallback Authorization header during connection check', async () => {
@@ -3,13 +3,17 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadRegisterRunnerApi() {
const stateSource = fs.readFileSync('background/kiro/state.js', 'utf8');
const runnerSource = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
const stateSource = fs.readFileSync('flows/kiro/background/state.js', 'utf8');
const runnerSource = fs.readFileSync('flows/kiro/background/register-runner.js', 'utf8');
const globalScope = {};
new Function('self', `${stateSource}; ${runnerSource}; return self;`)(globalScope);
return globalScope.MultiPageBackgroundKiroRegisterRunner;
}
function getKiroRuntime(state = {}) {
return state?.runtimeState?.flowState?.kiro || {};
}
test('kiro register runner module exposes a factory and Kiro official sign-in entry', () => {
const api = loadRegisterRunnerApi();
assert.equal(typeof api?.createKiroRegisterRunner, 'function');
@@ -17,7 +21,7 @@ test('kiro register runner module exposes a factory and Kiro official sign-in en
});
test('kiro register runner removed the old AWS device authorization bootstrap', () => {
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
const source = fs.readFileSync('flows/kiro/background/register-runner.js', 'utf8');
assert.doesNotMatch(source, /startBuilderIdDeviceLogin/);
assert.doesNotMatch(source, /device_authorization/);
assert.doesNotMatch(source, /verificationUriComplete/);
@@ -25,7 +29,7 @@ test('kiro register runner removed the old AWS device authorization bootstrap',
});
test('kiro register runner uses a shared 3-minute page-load timeout budget', () => {
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
const source = fs.readFileSync('flows/kiro/background/register-runner.js', 'utf8');
assert.match(source, /DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS/);
assert.match(source, /createTimeoutBudget/);
assert.match(source, /resolveTimeoutBudget/);
@@ -34,7 +38,7 @@ test('kiro register runner uses a shared 3-minute page-load timeout budget', ()
});
test('kiro register consent step treats Kiro Web signed-in page as completion', () => {
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
const source = fs.readFileSync('flows/kiro/background/register-runner.js', 'utf8');
assert.match(source, /readKiroRegisterPageState\(tabId, \{/);
assert.match(source, /\['authorization_page', 'success_page', 'kiro_web_signed_in'\]\.includes\(landingResult\?\.state\)/);
assert.match(source, /landingResult\?\.state === 'authorization_page'/);
@@ -42,7 +46,7 @@ test('kiro register consent step treats Kiro Web signed-in page as completion',
});
test('kiro register runner uses registration-only page states instead of shared OpenAI names', () => {
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
const source = fs.readFileSync('flows/kiro/background/register-runner.js', 'utf8');
assert.match(source, /KIRO_REGISTER_PAGE_STATES/);
assert.match(source, /'register_otp_page'/);
assert.match(source, /'create_password_page'/);
@@ -54,7 +58,7 @@ test('kiro register runner uses registration-only page states instead of shared
});
test('kiro register runner fails existing-account login branches during registration', () => {
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
const source = fs.readFileSync('flows/kiro/background/register-runner.js', 'utf8');
assert.match(source, /KIRO_REGISTER_EXISTING_ACCOUNT_STATES/);
assert.match(source, /assertKiroRegistrationOnlyState\(landingResult, currentState, 2, resolvedEmail\)/);
assert.match(source, /邮箱.*已进入 AWS Builder ID 登录页/);
@@ -64,12 +68,16 @@ test('kiro register runner fails existing-account login branches during registra
test('kiro submit-email stops immediately when AWS routes the email to login', async () => {
const api = loadRegisterRunnerApi();
const currentState = {
kiroRuntime: {
session: {
registerTabId: 101,
},
register: {
loginUrl: 'https://app.kiro.dev/signin',
runtimeState: {
flowState: {
kiro: {
session: {
registerTabId: 101,
},
register: {
loginUrl: 'https://app.kiro.dev/signin',
},
},
},
},
};
@@ -119,18 +127,22 @@ test('kiro submit-email stops immediately when AWS routes the email to login', a
assert.equal(completed, false);
assert.equal(sentMessages.some((message) => message.type === 'EXECUTE_NODE'), true);
assert.equal(statePatches.some((patch) => /已进入 AWS Builder ID 登录页/.test(patch.kiroRuntime?.session?.lastError || '')), true);
assert.equal(statePatches.some((patch) => /已进入 AWS Builder ID 登录页/.test(getKiroRuntime(patch).session?.lastError || '')), true);
});
test('kiro submit-email can adopt an already-open registration OTP page without allocating a new mailbox', async () => {
const api = loadRegisterRunnerApi();
const currentState = {
kiroRuntime: {
session: {
registerTabId: 102,
},
register: {
loginUrl: 'https://app.kiro.dev/signin',
runtimeState: {
flowState: {
kiro: {
session: {
registerTabId: 102,
},
register: {
loginUrl: 'https://app.kiro.dev/signin',
},
},
},
},
};
@@ -163,8 +175,8 @@ test('kiro submit-email can adopt an already-open registration OTP page without
await runner.executeKiroSubmitEmail({ nodeId: 'kiro-submit-email', ...currentState });
assert.equal(completedPayload?.kiroRuntime?.register?.email, 'manual-user@duck.com');
assert.equal(completedPayload?.kiroRuntime?.register?.status, 'waiting_otp');
assert.equal(completedPayload?.kiroRuntime?.register?.verificationRequestedAt, 0);
assert.equal(getKiroRuntime(completedPayload).register?.email, 'manual-user@duck.com');
assert.equal(getKiroRuntime(completedPayload).register?.status, 'waiting_otp');
assert.equal(getKiroRuntime(completedPayload).register?.verificationRequestedAt, 0);
assert.equal(sentMessages.some((message) => message.type === 'EXECUTE_NODE'), false);
});
+109 -85
View File
@@ -3,109 +3,128 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadKiroStateApi() {
const source = fs.readFileSync('background/kiro/state.js', 'utf8');
const source = fs.readFileSync('flows/kiro/background/state.js', 'utf8');
const globalScope = {};
return new Function('self', `${source}; return self.MultiPageBackgroundKiroState;`)(globalScope);
}
test('background imports kiro state module and routes Kiro runtime through dedicated helpers', () => {
function getKiroRuntime(state = {}) {
return state?.runtimeState?.flowState?.kiro || {};
}
test('background imports kiro state module for Kiro runtime projection and reset helpers', () => {
const source = fs.readFileSync('background.js', 'utf8');
assert.match(source, /background\/kiro\/state\.js/);
assert.match(source, /flows\/kiro\/background\/state\.js/);
assert.match(source, /const kiroStateHelpers = self\.MultiPageBackgroundKiroState/);
assert.match(source, /kiroStateHelpers\?\.buildStateView/);
assert.match(source, /kiroStateHelpers\?\.buildSessionStatePatch/);
assert.match(source, /kiroStateHelpers\?\.buildDownstreamResetPatch/);
assert.match(source, /kiroStateHelpers\?\.applyNodeCompletionPayload/);
assert.doesNotMatch(source, /migrateLegacyKiroRuntimeState/);
assert.equal(/kiroStateHelpers\?\.buildSessionStatePatch/.test(source), false);
assert.equal(/kiroRuntime:\s*kiroStateHelpers\?\.buildDefaultRuntimeState/.test(source), false);
});
test('kiro state module exposes canonical nested kiroRuntime view', () => {
test('kiro state module exposes canonical nested runtimeState view', () => {
const api = loadKiroStateApi();
const view = api.buildStateView({
kiroTargetId: 'kiro-rs',
kiroRuntime: {
session: {
currentStage: 'desktop-authorize',
registerTabId: 88,
pageState: 'name_entry',
pageUrl: 'https://view.awsapps.com/start',
},
register: {
email: 'aws-user@example.com',
fullName: 'Ada Lovelace',
loginUrl: 'https://app.kiro.dev/signin',
status: 'waiting_name',
},
webAuth: {
status: 'signin_started',
hasAccessToken: false,
hasSessionToken: false,
},
desktopAuth: {
clientId: 'client-001',
clientSecret: 'secret-001',
refreshToken: 'refresh-001',
status: 'authorized',
},
upload: {
targetId: 'kiro-rs',
status: 'ready_to_upload',
credentialId: 321,
},
const runtimeState = api.buildRuntimeStatePatch({}, {
session: {
currentStage: 'desktop-authorize',
registerTabId: 88,
pageState: 'name_entry',
pageUrl: 'https://view.awsapps.com/start',
},
register: {
email: 'aws-user@example.com',
fullName: 'Ada Lovelace',
loginUrl: 'https://app.kiro.dev/signin',
status: 'waiting_name',
},
webAuth: {
status: 'signin_started',
hasAccessToken: false,
hasSessionToken: false,
},
desktopAuth: {
clientId: 'client-001',
clientSecret: 'secret-001',
refreshToken: 'refresh-001',
status: 'authorized',
},
upload: {
targetId: 'kiro-rs',
status: 'ready_to_upload',
credentialId: 321,
},
}).runtimeState;
const view = api.buildStateView({
targetId: 'kiro-rs',
runtimeState,
});
assert.equal(view.kiroTargetId, 'kiro-rs');
assert.equal(view.kiroRuntime.session.currentStage, 'desktop-authorize');
assert.equal(view.kiroRuntime.session.registerTabId, 88);
assert.equal(view.kiroRuntime.register.email, 'aws-user@example.com');
assert.equal(view.kiroRuntime.register.loginUrl, 'https://app.kiro.dev/signin');
assert.equal(view.kiroRuntime.webAuth.status, 'signin_started');
assert.equal(view.kiroRuntime.desktopAuth.clientId, 'client-001');
assert.equal(view.kiroRuntime.desktopAuth.refreshToken, 'refresh-001');
assert.equal(view.kiroRuntime.upload.status, 'ready_to_upload');
assert.equal(view.kiroRuntime.upload.credentialId, 321);
assert.equal(view.targetId, 'kiro-rs');
assert.equal(Object.prototype.hasOwnProperty.call(view, 'kiroRuntime'), false);
assert.equal(getKiroRuntime(view).session.currentStage, 'desktop-authorize');
assert.equal(getKiroRuntime(view).session.registerTabId, 88);
assert.equal(getKiroRuntime(view).register.email, 'aws-user@example.com');
assert.equal(getKiroRuntime(view).register.loginUrl, 'https://app.kiro.dev/signin');
assert.equal(getKiroRuntime(view).webAuth.status, 'signin_started');
assert.equal(getKiroRuntime(view).desktopAuth.clientId, 'client-001');
assert.equal(getKiroRuntime(view).desktopAuth.refreshToken, 'refresh-001');
assert.equal(getKiroRuntime(view).upload.status, 'ready_to_upload');
assert.equal(getKiroRuntime(view).upload.credentialId, 321);
assert.equal(view.runtimeState.flowState.kiro.session.currentStage, 'desktop-authorize');
assert.equal(view.runtimeState.flowState.kiro.register.email, 'aws-user@example.com');
assert.equal(view.runtimeState.flowState.kiro.upload.credentialId, 321);
});
test('kiro state session patch accepts canonical nested runtime updates', () => {
const api = loadKiroStateApi();
const patch = api.buildSessionStatePatch({
kiroRuntime: api.buildDefaultRuntimeState(),
runtimeState: api.buildRuntimeStatePatch({}, api.buildDefaultRuntimeState()).runtimeState,
}, {
kiroRuntime: {
session: {
currentStage: 'register',
pageState: 'register_otp_page',
pageUrl: 'https://signin.aws/register',
},
register: {
email: 'aws-user@example.com',
fullName: 'Ada Lovelace',
verificationRequestedAt: 1700000000000,
},
desktopAuth: {
status: 'waiting_callback',
},
upload: {
status: 'waiting_register',
runtimeState: {
flowState: {
kiro: {
session: {
currentStage: 'register',
pageState: 'register_otp_page',
pageUrl: 'https://signin.aws/register',
},
register: {
email: 'aws-user@example.com',
fullName: 'Ada Lovelace',
verificationRequestedAt: 1700000000000,
},
desktopAuth: {
status: 'waiting_callback',
},
upload: {
status: 'waiting_register',
},
},
},
},
});
assert.equal(patch.kiroRuntime.session.currentStage, 'register');
assert.equal(patch.kiroRuntime.session.pageState, 'register_otp_page');
assert.equal(patch.kiroRuntime.session.pageUrl, 'https://signin.aws/register');
assert.equal(patch.kiroRuntime.register.email, 'aws-user@example.com');
assert.equal(patch.kiroRuntime.register.fullName, 'Ada Lovelace');
assert.equal(patch.kiroRuntime.register.verificationRequestedAt, 1700000000000);
assert.equal(patch.kiroRuntime.desktopAuth.status, 'waiting_callback');
assert.equal(patch.kiroRuntime.upload.status, 'waiting_register');
assert.equal(Object.prototype.hasOwnProperty.call(patch, 'kiroRuntime'), false);
assert.equal(getKiroRuntime(patch).session.currentStage, 'register');
assert.equal(getKiroRuntime(patch).session.pageState, 'register_otp_page');
assert.equal(getKiroRuntime(patch).session.pageUrl, 'https://signin.aws/register');
assert.equal(getKiroRuntime(patch).register.email, 'aws-user@example.com');
assert.equal(getKiroRuntime(patch).register.fullName, 'Ada Lovelace');
assert.equal(getKiroRuntime(patch).register.verificationRequestedAt, 1700000000000);
assert.equal(getKiroRuntime(patch).desktopAuth.status, 'waiting_callback');
assert.equal(getKiroRuntime(patch).upload.status, 'waiting_register');
assert.equal(patch.runtimeState.flowState.kiro.session.currentStage, 'register');
assert.equal(patch.runtimeState.flowState.kiro.register.email, 'aws-user@example.com');
assert.equal(patch.runtimeState.flowState.kiro.desktopAuth.status, 'waiting_callback');
});
test('kiro state reset helpers clear downstream runtime and fresh keep-state preserves only target selection', () => {
const api = loadKiroStateApi();
const currentState = {
kiroTargetId: 'kiro-rs',
kiroRuntime: {
targetId: 'kiro-rs',
runtimeState: api.buildRuntimeStatePatch({}, {
session: {
currentStage: 'upload',
registerTabId: 88,
@@ -126,21 +145,26 @@ test('kiro state reset helpers clear downstream runtime and fresh keep-state pre
status: 'uploaded',
credentialId: 321,
},
},
}).runtimeState,
};
const resetPatch = api.buildDownstreamResetPatch('kiro-submit-email', currentState);
assert.equal(resetPatch.kiroRuntime.session.currentStage, 'register');
assert.equal(resetPatch.kiroRuntime.register.email, '');
assert.equal(resetPatch.kiroRuntime.register.fullName, '');
assert.equal(resetPatch.kiroRuntime.desktopAuth.refreshToken, '');
assert.equal(resetPatch.kiroRuntime.upload.status, '');
assert.equal(resetPatch.kiroRuntime.upload.credentialId, null);
assert.equal(Object.prototype.hasOwnProperty.call(resetPatch, 'kiroRuntime'), false);
assert.equal(getKiroRuntime(resetPatch).session.currentStage, 'register');
assert.equal(getKiroRuntime(resetPatch).register.email, '');
assert.equal(getKiroRuntime(resetPatch).register.fullName, '');
assert.equal(getKiroRuntime(resetPatch).desktopAuth.refreshToken, '');
assert.equal(getKiroRuntime(resetPatch).upload.status, '');
assert.equal(getKiroRuntime(resetPatch).upload.credentialId, null);
assert.equal(resetPatch.runtimeState.flowState.kiro.register.email, '');
assert.equal(resetPatch.runtimeState.flowState.kiro.desktopAuth.refreshToken, '');
const keepState = api.buildFreshKeepState(currentState);
assert.equal(keepState.kiroTargetId, 'kiro-rs');
assert.equal(keepState.kiroRuntime.register.email, '');
assert.equal(keepState.kiroRuntime.desktopAuth.refreshToken, '');
assert.equal(keepState.kiroRuntime.upload.status, '');
assert.equal(keepState.kiroRuntime.upload.targetId, 'kiro-rs');
assert.equal(keepState.targetId, 'kiro-rs');
assert.equal(Object.prototype.hasOwnProperty.call(keepState, 'kiroRuntime'), false);
assert.equal(getKiroRuntime(keepState).register.email, '');
assert.equal(getKiroRuntime(keepState).desktopAuth.refreshToken, '');
assert.equal(getKiroRuntime(keepState).upload.status, '');
assert.equal(getKiroRuntime(keepState).upload.targetId, 'kiro-rs');
assert.equal(keepState.runtimeState.flowState.kiro.upload.targetId, 'kiro-rs');
});
@@ -4,11 +4,11 @@ const fs = require('node:fs');
test('background imports logging/status module', () => {
const source = fs.readFileSync('background.js', 'utf8');
assert.match(source, /background\/logging-status\.js/);
assert.match(source, /core\/flow-kernel\/logging-status\.js/);
});
test('logging/status module exposes a factory', () => {
const source = fs.readFileSync('background/logging-status.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/logging-status.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundLoggingStatus;`)(globalScope);
@@ -17,7 +17,7 @@ test('logging/status module exposes a factory', () => {
});
test('logging/status add-phone detection ignores step 2 phone-entry switch failures', () => {
const source = fs.readFileSync('background/logging-status.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/logging-status.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundLoggingStatus;`)(globalScope);
@@ -39,7 +39,7 @@ test('signup flow helper allocates mail2925 account before generating alias emai
calls.setEmail.push(email);
},
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
waitForTabUrlMatch: async () => null,
});
@@ -87,7 +87,7 @@ test('signup flow helper skips mail2925 account allocation when account pool swi
sendToContentScriptResilient: async () => ({}),
setEmailState: async () => {},
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
waitForTabUrlMatch: async () => null,
});
+23 -23
View File
@@ -459,7 +459,7 @@ test('SAVE_SETTING rebuilds Plus node statuses when panel mode forces the effect
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
const broadcasts = [];
let state = {
panelMode: 'sub2api',
targetId: 'sub2api',
plusModeEnabled: true,
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'sub2api_codex_session',
@@ -486,12 +486,12 @@ test('SAVE_SETTING rebuilds Plus node statuses when panel mode forces the effect
const router = api.createMessageRouter({
addLog: async () => {},
buildLuckmailSessionSettingsPayload: () => ({}),
buildPersistentSettingsPayload: (input = {}) => Object.prototype.hasOwnProperty.call(input, 'panelMode')
? { panelMode: input.panelMode }
buildPersistentSettingsPayload: (input = {}) => Object.prototype.hasOwnProperty.call(input, 'targetId')
? { targetId: input.targetId }
: {},
broadcastDataUpdate: (payload) => broadcasts.push(payload),
getNodeIdsForState: (nextState = {}) => (
String(nextState.panelMode || '').trim() === 'sub2api'
String(nextState.targetId || '').trim() === 'sub2api'
&& String(nextState.plusAccountAccessStrategy || '').trim() === 'sub2api_codex_session'
? [
'open-chatgpt',
@@ -525,12 +525,12 @@ test('SAVE_SETTING rebuilds Plus node statuses when panel mode forces the effect
const response = await router.handleMessage({
type: 'SAVE_SETTING',
payload: {
panelMode: 'cpa',
targetId: 'cpa',
},
});
assert.equal(response.ok, true);
assert.equal(state.panelMode, 'cpa');
assert.equal(state.targetId, 'cpa');
assert.equal(state.plusAccountAccessStrategy, 'sub2api_codex_session');
assert.equal(state.currentNodeId, '');
assert.equal(state.oauthUrl, null);
@@ -552,7 +552,7 @@ test('SAVE_SETTING rebuilds Plus node statuses when panel mode forces the effect
});
assert.equal(Object.prototype.hasOwnProperty.call(state.nodeStatuses, 'sub2api-session-import'), false);
assert.deepStrictEqual(broadcasts.at(-1), {
panelMode: 'cpa',
targetId: 'cpa',
signupMethod: 'email',
oauthUrl: null,
localhostUrl: null,
@@ -595,7 +595,7 @@ test('SAVE_SETTING mirrors activeFlowId into flowId when switching to kiro flow'
const globalScope = { console };
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
const broadcasts = [];
let state = { activeFlowId: 'openai', flowId: 'openai', panelMode: 'cpa', plusModeEnabled: false, plusPaymentMethod: 'paypal' };
let state = { activeFlowId: 'openai', flowId: 'openai', targetId: 'cpa', plusModeEnabled: false, plusPaymentMethod: 'paypal' };
const router = api.createMessageRouter({
addLog: async () => {},
@@ -683,7 +683,7 @@ test('SAVE_SETTING syncs canonical kiro settingsState back into session state',
let state = {
activeFlowId: 'kiro',
flowId: 'kiro',
kiroTargetId: 'kiro-rs',
targetId: 'kiro-rs',
kiroRsUrl: 'https://kiro.example.com/admin',
kiroRsKey: '',
settingsSchemaVersion: 4,
@@ -718,7 +718,7 @@ test('SAVE_SETTING syncs canonical kiro settingsState back into session state',
setPersistentSettings: async () => ({
activeFlowId: 'kiro',
flowId: 'kiro',
kiroTargetId: 'kiro-rs',
targetId: 'kiro-rs',
kiroRsUrl: 'https://kiro.example.com/admin',
kiroRsKey: 'live-key',
settingsSchemaVersion: 4,
@@ -751,7 +751,7 @@ test('CHECK_KIRO_RS_CONNECTION prefers current sidepanel payload over stale save
getState: async () => ({
activeFlowId: 'kiro',
flowId: 'kiro',
kiroTargetId: 'kiro-rs',
targetId: 'kiro-rs',
kiroRsUrl: 'https://old.example.com/admin',
kiroRsKey: 'old-key',
settingsState: {
@@ -808,7 +808,7 @@ test('AUTO_RUN applies current flow selection from payload before starting loop'
let state = {
activeFlowId: 'openai',
flowId: 'openai',
panelMode: 'cpa',
targetId: 'cpa',
plusModeEnabled: false,
plusPaymentMethod: 'paypal',
};
@@ -829,7 +829,7 @@ test('AUTO_RUN applies current flow selection from payload before starting loop'
validations.push({
activeFlowId: validationState?.activeFlowId,
flowId: validationState?.flowId,
kiroTargetId: validationState?.kiroTargetId,
targetId: validationState?.targetId,
optionActiveFlowId: options?.activeFlowId,
});
return { ok: true, errors: [] };
@@ -848,14 +848,14 @@ test('AUTO_RUN applies current flow selection from payload before starting loop'
assert.equal(response.ok, true);
assert.equal(state.activeFlowId, 'kiro');
assert.equal(state.flowId, 'kiro');
assert.equal(state.kiroTargetId, 'kiro-rs');
assert.equal(state.targetId, 'kiro-rs');
assert.deepStrictEqual(calls, [
{
type: 'setState',
updates: {
activeFlowId: 'kiro',
flowId: 'kiro',
kiroTargetId: 'kiro-rs',
targetId: 'kiro-rs',
},
},
{
@@ -877,7 +877,7 @@ test('AUTO_RUN applies current flow selection from payload before starting loop'
{
activeFlowId: 'kiro',
flowId: 'kiro',
kiroTargetId: 'kiro-rs',
targetId: 'kiro-rs',
optionActiveFlowId: 'kiro',
},
]);
@@ -891,18 +891,18 @@ test('SAVE_SETTING re-resolves signup method when panel mode changes', async ()
signupMethod: 'phone',
phoneVerificationEnabled: true,
plusModeEnabled: false,
panelMode: 'sub2api',
targetId: 'sub2api',
};
const router = api.createMessageRouter({
addLog: async () => {},
buildLuckmailSessionSettingsPayload: () => ({}),
buildPersistentSettingsPayload: (input = {}) => Object.prototype.hasOwnProperty.call(input, 'panelMode')
? { panelMode: input.panelMode }
buildPersistentSettingsPayload: (input = {}) => Object.prototype.hasOwnProperty.call(input, 'targetId')
? { targetId: input.targetId }
: {},
broadcastDataUpdate: () => {},
getState: async () => ({ ...state }),
resolveSignupMethod: (nextState = {}) => nextState.panelMode === 'cpa' ? 'email' : 'phone',
resolveSignupMethod: (nextState = {}) => nextState.targetId === 'cpa' ? 'email' : 'phone',
setPersistentSettings: async (updates) => ({ ...updates }),
setState: async (updates) => {
state = { ...state, ...updates };
@@ -911,11 +911,11 @@ test('SAVE_SETTING re-resolves signup method when panel mode changes', async ()
const response = await router.handleMessage({
type: 'SAVE_SETTING',
payload: { panelMode: 'cpa' },
payload: { targetId: 'cpa' },
});
assert.equal(response.ok, true);
assert.equal(state.panelMode, 'cpa');
assert.equal(state.targetId, 'cpa');
assert.equal(state.signupMethod, 'email');
});
@@ -929,7 +929,7 @@ test('SAVE_SETTING applies shared mode-switch normalization before persisting in
signupMethod: 'email',
phoneVerificationEnabled: false,
plusModeEnabled: false,
panelMode: 'cpa',
targetId: 'cpa',
};
const router = api.createMessageRouter({
@@ -426,7 +426,7 @@ test('message router finalizes step 3 before marking it completed', async () =>
const response = await router.handleMessage({
type: 'NODE_COMPLETE',
nodeId: 'fill-password',
source: 'signup-page',
source: 'openai-auth',
payload: {
nodeId: 'fill-password',
email: 'user@example.com',
@@ -548,7 +548,7 @@ test('message router marks step 3 failed when post-submit finalize fails', async
const response = await router.handleMessage({
type: 'NODE_COMPLETE',
nodeId: 'fill-password',
source: 'signup-page',
source: 'openai-auth',
payload: {
nodeId: 'fill-password',
email: 'user@example.com',
@@ -581,7 +581,7 @@ test('message router does not duplicate step 3 mismatch failure log after finali
const response = await router.handleMessage({
type: 'NODE_ERROR',
nodeId: 'fill-password',
source: 'signup-page',
source: 'openai-auth',
payload: { nodeId: 'fill-password' },
error: mismatchError,
}, {});
@@ -604,7 +604,7 @@ test('message router stops the flow and surfaces cloudflare security block error
const response = await router.handleMessage({
type: 'NODE_ERROR',
nodeId: 'oauth-login',
source: 'signup-page',
source: 'openai-auth',
payload: { nodeId: 'oauth-login' },
error: 'CF_SECURITY_BLOCKED::您已触发Cloudflare 安全防护系统',
}, {});
@@ -68,7 +68,7 @@ test('navigation utils support codex2api mode and url normalization', () => {
utils.normalizeCodex2ApiUrl('https://codex-admin.example.com/'),
'https://codex-admin.example.com/admin/accounts'
);
assert.equal(utils.getPanelMode({ panelMode: 'codex2api' }), 'codex2api');
assert.equal(utils.getPanelMode({ targetId: 'codex2api' }), 'codex2api');
assert.equal(utils.getPanelModeLabel('codex2api'), 'Codex2API');
});
@@ -4,10 +4,10 @@ const fs = require('node:fs');
test('confirm-oauth and platform-verify stay free of operation delay gate calls', () => {
for (const file of [
'background/steps/confirm-oauth.js',
'background/steps/platform-verify.js',
'flows/openai/background/steps/confirm-oauth.js',
'flows/openai/background/steps/platform-verify.js',
'background/panel-bridge.js',
'content/sub2api-panel.js',
'flows/openai/content/sub2api-panel.js',
]) {
const source = fs.readFileSync(file, 'utf8');
assert.doesNotMatch(source, /performOperationWithDelay\(/, `${file} must not call the operation delay gate`);
@@ -3,7 +3,7 @@ const fs = require('node:fs');
const test = require('node:test');
test('platform verify module supports codex2api protocol callback exchange', async () => {
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url, options = {}) => {
assert.equal(url, 'http://localhost:8080/api/admin/oauth/exchange-code');
@@ -81,7 +81,7 @@ test('platform verify module supports codex2api protocol callback exchange', asy
});
test('platform verify retries transient SUB2API oauth/token exchange errors before succeeding', async () => {
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
const logs = [];
@@ -150,7 +150,7 @@ test('platform verify retries transient SUB2API oauth/token exchange errors befo
});
test('platform verify retries transient SUB2API token_exchange_user_error before succeeding', async () => {
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
const logs = [];
@@ -44,7 +44,7 @@ function createDeps(overrides = {}) {
function loadStep10WithSub2Api() {
const sub2apiSource = fs.readFileSync('background/sub2api-api.js', 'utf8');
const step10Source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const step10Source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
return new Function('self', `${sub2apiSource}\n${step10Source}; return self.MultiPageBackgroundStep10;`)({});
}
@@ -57,7 +57,7 @@ function createSub2ApiResponse(payload, status = 200) {
}
test('platform verify module submits CPA callback via management API first', async () => {
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
let uiCalled = false;
globalThis.fetch = async (url, options = {}) => {
@@ -114,7 +114,7 @@ test('platform verify module submits CPA callback via management API first', asy
});
test('platform verify module prefers cpaManagementOrigin when provided', async () => {
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url, options = {}) => {
assert.equal(url, 'http://localhost:9999/v0/management/oauth-callback');
@@ -149,7 +149,7 @@ test('platform verify module prefers cpaManagementOrigin when provided', async (
});
test('platform verify module fails fast when CPA API submit fails', async () => {
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => ({
ok: false,
@@ -185,7 +185,7 @@ test('platform verify module fails fast when CPA API submit fails', async () =>
});
test('platform verify module requires management key for CPA API-only flow', async () => {
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
let fetchCalled = false;
globalThis.fetch = async () => {
@@ -221,7 +221,7 @@ test('platform verify module requires management key for CPA API-only flow', asy
});
test('platform verify module rejects callback when cpa oauth state mismatches', async () => {
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
let fetchCalled = false;
globalThis.fetch = async () => {
+56 -6
View File
@@ -3,14 +3,14 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadRuntimeStateApi() {
const source = fs.readFileSync('background/runtime-state.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/runtime-state.js', 'utf8');
const globalScope = {};
return new Function('self', `${source}; return self.MultiPageBackgroundRuntimeState;`)(globalScope);
}
test('background imports runtime-state module and wires state view helpers', () => {
const source = fs.readFileSync('background.js', 'utf8');
assert.match(source, /background\/runtime-state\.js/);
assert.match(source, /core\/flow-kernel\/runtime-state\.js/);
assert.match(source, /createRuntimeStateHelpers/);
assert.match(source, /buildStateViewWithRuntimeState/);
assert.match(source, /buildStatePatchWithRuntimeState/);
@@ -46,10 +46,10 @@ test('runtime-state view preserves canonical flow metadata from node state', ()
phoneNumber: '+447700900123',
},
tabRegistry: {
'signup-page': { tabId: 12 },
'openai-auth': { tabId: 12 },
},
sourceLastUrls: {
'signup-page': 'https://auth.example.com/start',
'openai-auth': 'https://auth.example.com/start',
},
flowStartTime: 12345,
});
@@ -72,10 +72,10 @@ test('runtime-state view preserves canonical flow metadata from node state', ()
});
assert.deepStrictEqual(view.sharedState, {
tabRegistry: {
'signup-page': { tabId: 12 },
'openai-auth': { tabId: 12 },
},
sourceLastUrls: {
'signup-page': 'https://auth.example.com/start',
'openai-auth': 'https://auth.example.com/start',
},
flowStartTime: 12345,
});
@@ -171,3 +171,53 @@ test('runtime-state patch prefers explicit activeFlowId over stale legacy flowId
'kiro-open-register-page': 'running',
});
});
test('runtime-state patch preserves canonical nested kiro flow state without projecting a top-level alias', () => {
const api = loadRuntimeStateApi();
const helpers = api.createRuntimeStateHelpers({
DEFAULT_ACTIVE_FLOW_ID: 'openai',
defaultNodeStatuses: {
'open-chatgpt': 'pending',
'kiro-open-register-page': 'pending',
},
});
const patch = helpers.buildSessionStatePatch({
runtimeState: {
activeFlowId: 'kiro',
flowState: {
kiro: {
session: {
currentStage: 'register',
},
register: {
email: 'old-user@example.com',
},
},
},
},
}, {
runtimeState: {
flowState: {
kiro: {
register: {
email: 'aws-user@example.com',
},
upload: {
status: 'uploaded',
},
},
},
},
});
assert.equal(Object.prototype.hasOwnProperty.call(patch, 'kiroRuntime'), false);
assert.equal(patch.runtimeState.flowState.kiro.session.currentStage, 'register');
assert.equal(patch.runtimeState.flowState.kiro.register.email, 'aws-user@example.com');
assert.equal(patch.runtimeState.flowState.kiro.upload.status, 'uploaded');
const view = helpers.buildStateView(patch);
assert.equal(Object.prototype.hasOwnProperty.call(view, 'kiroRuntime'), false);
assert.equal(view.flowState.kiro.register.email, 'aws-user@example.com');
assert.equal(view.flowState.kiro.upload.status, 'uploaded');
});
@@ -52,12 +52,24 @@ test('importSettingsBundle normalizes unsupported capability flags before persis
const api = new Function(`
const SETTINGS_EXPORT_SCHEMA_VERSION = 1;
const DEFAULT_REGISTRATION_EMAIL_STATE = { emailHistory: [] };
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
const self = {
MultiPageLegacySettingsImporter: {
createSettingsImporter() {
return {
importSettings(settings = {}) {
return { ...settings };
},
};
},
},
};
let persistedUpdates = null;
let stateUpdates = null;
let broadcastPayload = null;
let currentState = {
activeFlowId: 'site-a',
panelMode: 'sub2api',
targetId: 'sub2api',
signupMethod: 'phone',
plusModeEnabled: false,
phoneVerificationEnabled: false,
@@ -74,7 +86,7 @@ function validateModeSwitchState() {
ok: false,
errors: [{ code: 'panel_mode_unsupported', message: '当前 flow 不支持 SUB2API 面板模式。' }],
normalizedUpdates: {
panelMode: 'cpa',
targetId: 'cpa',
plusModeEnabled: false,
phoneVerificationEnabled: false,
signupMethod: 'email',
@@ -84,6 +96,9 @@ function validateModeSwitchState() {
function resolveSignupMethod(state = {}) {
return String(state?.signupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email';
}
function getSettingsSchemaApi() {
return null;
}
async function setPersistentSettings(updates) {
persistedUpdates = { ...updates };
}
@@ -109,7 +124,7 @@ return {
const result = await api.importSettingsBundle({
schemaVersion: 1,
settings: {
panelMode: 'sub2api',
targetId: 'sub2api',
plusModeEnabled: true,
phoneVerificationEnabled: true,
signupMethod: 'phone',
@@ -117,16 +132,164 @@ return {
});
assert.deepEqual(api.getPersistedUpdates(), {
panelMode: 'cpa',
targetId: 'cpa',
plusModeEnabled: false,
phoneVerificationEnabled: false,
signupMethod: 'email',
});
assert.equal(api.getStateUpdates().panelMode, 'cpa');
assert.equal(api.getStateUpdates().targetId, 'cpa');
assert.equal(api.getStateUpdates().plusModeEnabled, false);
assert.equal(api.getStateUpdates().phoneVerificationEnabled, false);
assert.equal(api.getStateUpdates().signupMethod, 'email');
assert.equal(api.getBroadcastPayload().panelMode, 'cpa');
assert.equal(api.getBroadcastPayload().targetId, 'cpa');
assert.equal(api.getBroadcastPayload().signupMethod, 'email');
assert.equal(result.signupMethod, 'email');
});
test('importSettingsBundle routes legacy settings through the legacy importer before persisting', async () => {
const api = new Function(`
const SETTINGS_EXPORT_SCHEMA_VERSION = 1;
const DEFAULT_REGISTRATION_EMAIL_STATE = { emailHistory: [] };
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
let importerInput = null;
let persistedUpdates = null;
let currentState = {
activeFlowId: 'openai',
nodeStatuses: {},
};
const self = {
MultiPageFlowRegistry: {
DEFAULT_FLOW_ID: 'openai',
},
MultiPageLegacySettingsImporter: {
createSettingsImporter() {
return {
importSettings(settings = {}) {
importerInput = JSON.parse(JSON.stringify(settings));
return {
settingsSchemaVersion: 5,
settingsState: {
schemaVersion: 5,
activeFlowId: 'kiro',
services: {
account: { customPassword: '' },
email: { provider: '163' },
proxy: { enabled: false, provider: '711proxy', mode: 'account' },
},
flows: {
openai: {
selectedTargetId: 'cpa',
targets: {
cpa: { vpsUrl: '', vpsPassword: '', localCpaStep9Mode: 'submit' },
sub2api: {
sub2apiUrl: '',
sub2apiEmail: '',
sub2apiPassword: '',
sub2apiGroupName: 'codex',
sub2apiGroupNames: ['codex', 'openai-plus'],
sub2apiAccountPriority: 1,
sub2apiDefaultProxyName: '',
},
codex2api: { codex2apiUrl: '', codex2apiAdminKey: '' },
},
signup: {
signupMethod: 'email',
phoneVerificationEnabled: false,
phoneSignupReloginAfterBindEmailEnabled: false,
},
plus: {
plusModeEnabled: false,
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'oauth',
},
autoRun: {
stepExecutionRange: { enabled: false, fromStep: 1, toStep: 11 },
},
},
kiro: {
selectedTargetId: 'kiro-rs',
targets: {
'kiro-rs': {
baseUrl: 'https://kiro.example.com/admin',
apiKey: 'imported-key',
},
},
autoRun: {
stepExecutionRange: { enabled: false, fromStep: 1, toStep: 9 },
},
},
},
},
};
},
};
},
},
};
async function ensureManualInteractionAllowed() {
return currentState;
}
function buildPersistentSettingsPayload(settings = {}) {
return {
activeFlowId: settings.settingsState.activeFlowId,
targetId: 'cpa',
signupMethod: 'email',
targetId: 'kiro-rs',
kiroRsUrl: settings.settingsState.flows.kiro.targets['kiro-rs'].baseUrl,
kiroRsKey: settings.settingsState.flows.kiro.targets['kiro-rs'].apiKey,
settingsSchemaVersion: settings.settingsSchemaVersion,
settingsState: settings.settingsState,
};
}
function validateModeSwitchState() {
return { normalizedUpdates: {} };
}
function resolveSignupMethod() {
return 'email';
}
function getSettingsSchemaApi() {
return null;
}
async function setPersistentSettings(updates) {
persistedUpdates = { ...updates };
return updates;
}
async function setState(updates) {
currentState = { ...currentState, ...updates };
}
function broadcastDataUpdate() {}
async function getState() {
return currentState;
}
${extractFunction('importSettingsBundle')}
return {
importSettingsBundle,
getImporterInput: () => importerInput,
getPersistedUpdates: () => persistedUpdates,
};
`)();
await api.importSettingsBundle({
schemaVersion: 1,
settings: {
targetId: 'sub2api',
kiroRuntime: {
upload: {
status: 'uploaded',
},
},
},
});
assert.deepEqual(api.getImporterInput(), {
targetId: 'sub2api',
kiroRuntime: {
upload: {
status: 'uploaded',
},
},
});
assert.equal(api.getPersistedUpdates().activeFlowId, 'kiro');
assert.equal(api.getPersistedUpdates().settingsSchemaVersion, 5);
assert.equal(api.getPersistedUpdates().settingsState.flows.kiro.targets['kiro-rs'].apiKey, 'imported-key');
});
@@ -1,9 +1,10 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { readFlowRegistryBundle } = require('./helpers/script-bundles.js');
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
const settingsSchemaSource = fs.readFileSync('shared/settings-schema.js', 'utf8');
const flowRegistrySource = readFlowRegistryBundle();
const settingsSchemaSource = fs.readFileSync('core/flow-kernel/settings-schema.js', 'utf8');
const backgroundSource = fs.readFileSync('background.js', 'utf8');
function extractFunction(name) {
@@ -59,9 +60,7 @@ const DEFAULT_ACTIVE_FLOW_ID = 'openai';
const DEFAULT_SUB2API_GROUP_NAMES = ['codex', 'openai-plus'];
const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([
'activeFlowId',
'openaiIntegrationTargetId',
'panelMode',
'kiroTargetId',
'targetId',
'vpsUrl',
'vpsPassword',
'localCpaStep9Mode',
@@ -92,7 +91,7 @@ const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([
const SETTINGS_SCHEMA_VIEW_KEY_SET = new Set(SETTINGS_SCHEMA_VIEW_KEYS);
const PERSISTED_SETTING_DEFAULTS = {
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
panelMode: 'cpa',
targetId: 'cpa',
signupMethod: 'email',
plusModeEnabled: false,
plusPaymentMethod: 'paypal',
@@ -102,7 +101,6 @@ const PERSISTED_SETTING_DEFAULTS = {
ipProxyEnabled: false,
ipProxyService: '711proxy',
ipProxyMode: 'account',
kiroTargetId: 'kiro-rs',
kiroRsUrl: '',
kiroRsKey: '',
stepExecutionRangeByFlow: {},
@@ -203,13 +201,13 @@ test('buildPersistentSettingsPayload writes canonical settings schema into persi
}, { fillDefaults: true });
assert.equal(payload.activeFlowId, 'kiro');
assert.equal(payload.kiroTargetId, 'kiro-rs');
assert.equal(payload.targetId, 'kiro-rs');
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(payload.kiroRsKey, 'secret-key');
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
assert.equal(payload.settingsSchemaVersion, 4);
assert.equal(payload.settingsSchemaVersion, 5);
assert.equal(payload.settingsState.activeFlowId, 'kiro');
assert.equal(payload.settingsState.flows.kiro.targetId, 'kiro-rs');
assert.equal(payload.settingsState.flows.kiro.selectedTargetId, 'kiro-rs');
assert.equal(
payload.settingsState.flows.kiro.targets['kiro-rs'].baseUrl,
'https://kiro.example.com/admin'
@@ -220,7 +218,7 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
const api = buildHarness();
const payload = api.buildPersistentSettingsPayload({
settingsSchemaVersion: 4,
settingsSchemaVersion: 5,
settingsState: {
activeFlowId: 'kiro',
services: {
@@ -230,8 +228,8 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
},
flows: {
openai: {
integrationTargetId: 'cpa',
integrationTargets: {
selectedTargetId: 'cpa',
targets: {
cpa: {
vpsUrl: '',
vpsPassword: '',
@@ -266,7 +264,7 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
},
},
kiro: {
targetId: 'kiro-rs',
selectedTargetId: 'kiro-rs',
targets: {
'kiro-rs': {
baseUrl: 'https://kiro.example.com/admin',
@@ -282,11 +280,11 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
}, { requireKnownKeys: true });
assert.equal(payload.activeFlowId, 'kiro');
assert.equal(payload.kiroTargetId, 'kiro-rs');
assert.equal(payload.targetId, 'kiro-rs');
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(payload.kiroRsKey, 'schema-only-key');
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
assert.equal(payload.settingsSchemaVersion, 4);
assert.equal(payload.settingsSchemaVersion, 5);
assert.equal(payload.settingsState.flows.openai.plus.plusAccountAccessStrategy, 'oauth');
});
@@ -313,7 +311,7 @@ function getRequestedKeys() {
assert.ok(api.getRequestedKeys().includes('settingsSchemaVersion'));
assert.ok(api.getRequestedKeys().includes('settingsState'));
assert.ok(api.getRequestedKeys().includes('plusAccountAccessStrategy'));
assert.equal(state.settingsSchemaVersion, 4);
assert.equal(state.settingsSchemaVersion, 5);
assert.equal(state.settingsState.activeFlowId, 'openai');
});
@@ -324,7 +322,7 @@ const chrome = {
local: {
async get() {
return {
settingsSchemaVersion: 4,
settingsSchemaVersion: 5,
settingsState: {
activeFlowId: 'kiro',
services: {
@@ -334,8 +332,8 @@ const chrome = {
},
flows: {
openai: {
integrationTargetId: 'sub2api',
integrationTargets: {
selectedTargetId: 'sub2api',
targets: {
cpa: {
vpsUrl: '',
vpsPassword: '',
@@ -370,7 +368,7 @@ const chrome = {
},
},
kiro: {
targetId: 'kiro-rs',
selectedTargetId: 'kiro-rs',
targets: {
'kiro-rs': {
baseUrl: 'https://kiro.example.com/admin',
@@ -393,7 +391,8 @@ const chrome = {
const state = await api.getPersistedSettings();
assert.equal(state.activeFlowId, 'kiro');
assert.equal(state.panelMode, 'sub2api');
assert.equal(state.targetId, 'kiro-rs');
assert.equal(state.settingsState.flows.openai.selectedTargetId, 'sub2api');
assert.equal(state.mailProvider, 'hotmail');
assert.equal(state.ipProxyEnabled, true);
assert.equal(state.kiroRsUrl, 'https://kiro.example.com/admin');
@@ -435,7 +434,7 @@ function getRemovedKeys() {
`);
const persisted = await api.setPersistentSettings({
settingsSchemaVersion: 4,
settingsSchemaVersion: 5,
settingsState: {
activeFlowId: 'kiro',
services: {
@@ -445,8 +444,8 @@ function getRemovedKeys() {
},
flows: {
openai: {
integrationTargetId: 'cpa',
integrationTargets: {
selectedTargetId: 'cpa',
targets: {
cpa: {
vpsUrl: '',
vpsPassword: '',
@@ -481,7 +480,7 @@ function getRemovedKeys() {
},
},
kiro: {
targetId: 'kiro-rs',
selectedTargetId: 'kiro-rs',
targets: {
'kiro-rs': {
baseUrl: 'https://kiro.example.com/admin',
@@ -499,21 +498,20 @@ function getRemovedKeys() {
const write = api.getPersistedWrites().at(-1);
assert.equal(persisted.activeFlowId, 'kiro');
assert.equal(persisted.kiroTargetId, 'kiro-rs');
assert.equal(persisted.targetId, 'kiro-rs');
assert.equal(persisted.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(persisted.kiroRsKey, 'nested-only-key');
assert.equal(persisted.plusAccountAccessStrategy, 'sub2api_codex_session');
assert.equal(Object.prototype.hasOwnProperty.call(persisted, 'kiroRegion'), false);
assert.equal(persisted.settingsSchemaVersion, 4);
assert.equal(persisted.settingsSchemaVersion, 5);
assert.equal(Object.prototype.hasOwnProperty.call(write, 'activeFlowId'), false);
assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRsUrl'), false);
assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRsKey'), false);
assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRegion'), false);
assert.equal(write.settingsSchemaVersion, 4);
assert.equal(write.settingsSchemaVersion, 5);
assert.equal(write.settingsState.activeFlowId, 'kiro');
assert.equal(write.settingsState.flows.openai.plus.plusAccountAccessStrategy, 'sub2api_codex_session');
assert.equal(write.settingsState.flows.kiro.targetId, 'kiro-rs');
assert.ok(api.getRemovedKeys().includes('panelMode'));
assert.equal(write.settingsState.flows.kiro.selectedTargetId, 'kiro-rs');
assert.ok(api.getRemovedKeys().includes('kiroRsUrl'));
});
@@ -526,7 +524,7 @@ const chrome = {
local: {
async get() {
return {
settingsSchemaVersion: 4,
settingsSchemaVersion: 5,
settingsState: {
activeFlowId: 'openai',
services: {
@@ -575,7 +573,7 @@ const chrome = {
local: {
async get() {
return {
settingsSchemaVersion: 4,
settingsSchemaVersion: 5,
settingsState: {
activeFlowId: 'openai',
services: {
@@ -585,8 +583,8 @@ const chrome = {
},
flows: {
openai: {
integrationTargetId: 'cpa',
integrationTargets: {
selectedTargetId: 'cpa',
targets: {
cpa: {
vpsUrl: 'https://old-cpa.example.com',
vpsPassword: 'old-vps-password',
@@ -621,7 +619,7 @@ const chrome = {
},
},
kiro: {
targetId: 'kiro-rs',
selectedTargetId: 'kiro-rs',
targets: {
'kiro-rs': {
baseUrl: 'https://kiro.example.com/admin',
@@ -654,7 +652,7 @@ function getRemovedKeys() {
`);
const persisted = await api.setPersistentSettings({
panelMode: 'sub2api',
targetId: 'sub2api',
mailProvider: 'cloudflare-temp-email',
ipProxyEnabled: true,
ipProxyMode: 'api',
@@ -664,7 +662,7 @@ function getRemovedKeys() {
});
const write = api.getPersistedWrites().at(-1);
assert.equal(persisted.panelMode, 'sub2api');
assert.equal(persisted.targetId, 'sub2api');
assert.equal(persisted.mailProvider, 'cloudflare-temp-email');
assert.equal(persisted.ipProxyEnabled, true);
assert.equal(persisted.ipProxyMode, 'api');
@@ -673,7 +671,7 @@ function getRemovedKeys() {
fromStep: 2,
toStep: 4,
});
assert.equal(write.settingsState.flows.openai.integrationTargetId, 'sub2api');
assert.equal(write.settingsState.flows.openai.selectedTargetId, 'sub2api');
assert.equal(write.settingsState.services.email.provider, 'cloudflare-temp-email');
assert.equal(write.settingsState.services.proxy.enabled, true);
assert.equal(write.settingsState.services.proxy.mode, 'api');
@@ -682,8 +680,8 @@ function getRemovedKeys() {
fromStep: 2,
toStep: 4,
});
assert.equal(write.settingsState.flows.openai.integrationTargets.sub2api.sub2apiUrl, 'https://sub2api.example.com');
assert.equal(write.settingsState.flows.openai.integrationTargets.sub2api.sub2apiEmail, 'owner@example.com');
assert.equal(write.settingsState.flows.openai.targets.sub2api.sub2apiUrl, 'https://sub2api.example.com');
assert.equal(write.settingsState.flows.openai.targets.sub2api.sub2apiEmail, 'owner@example.com');
assert.equal(write.settingsState.flows.kiro.targets['kiro-rs'].apiKey, 'kiro-key');
assert.equal(Object.prototype.hasOwnProperty.call(write, 'mailProvider'), false);
assert.equal(Object.prototype.hasOwnProperty.call(write, 'panelMode'), false);
+18 -18
View File
@@ -2,7 +2,7 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const step2Source = fs.readFileSync('background/steps/submit-signup-email.js', 'utf8');
const step2Source = fs.readFileSync('flows/openai/background/steps/submit-signup-email.js', 'utf8');
const step2GlobalScope = {};
const step2Api = new Function('self', `${step2Source}; return self.MultiPageBackgroundStep2;`)(step2GlobalScope);
@@ -33,7 +33,7 @@ test('step 2 completes with password step skipped when landing on email verifica
isTabAlive: async () => true,
resolveSignupEmailForFlow: async () => 'user@example.com',
sendToContentScriptResilient: async () => ({ submitted: true }),
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
});
await executor.executeStep2({ email: 'user@example.com' });
@@ -72,7 +72,7 @@ test('step 2 keeps password flow when landing on password page', async () => {
isTabAlive: async () => true,
resolveSignupEmailForFlow: async () => 'user@example.com',
sendToContentScriptResilient: async () => ({ submitted: true }),
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
});
await executor.executeStep2({ email: 'user@example.com' });
@@ -150,7 +150,7 @@ test('step 2 uses phone activation when resolved signup method is phone', async
sentPayloads.push(message.payload);
return { submitted: true };
},
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
});
await executor.executeStep2({ signupMethod: 'phone' });
@@ -233,7 +233,7 @@ test('step 2 reuses existing signup phone activation without acquiring a new num
sentPayloads.push(message.payload);
return { submitted: true };
},
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
});
await executor.executeStep2({
@@ -290,7 +290,7 @@ test('step 2 submits manual signup phone without acquiring a number', async () =
sentPayloads.push(message.payload);
return { submitted: true };
},
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
});
await executor.executeStep2({
@@ -354,7 +354,7 @@ test('step 2 stops with an explicit error instead of silently skipping 3/4/5 on
isTabAlive: async () => true,
resolveSignupEmailForFlow: async () => 'user@example.com',
sendToContentScriptResilient: async () => ({ submitted: true }),
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
});
await assert.rejects(
@@ -405,7 +405,7 @@ test('step 2 does not force auth-entry retry on logged-out chatgpt home when con
sentPayloads.push(message.payload);
return { submitted: true };
},
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
});
await executor.executeStep2({ email: 'user@example.com' });
@@ -472,7 +472,7 @@ test('step 2 waits for the existing signup tab to settle before probing the entr
}
return { submitted: true };
},
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
waitForTabStableComplete: async (_tabId, options) => {
events.push({ type: 'wait-stable', options });
return { id: 17, url: 'https://chatgpt.com/', status: 'complete' };
@@ -543,7 +543,7 @@ test('signup flow helper recognizes email verification page as post-email landin
},
setEmailState: async () => {},
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
waitForTabUrlMatch: async () => ({
id: 21,
url: 'https://auth.openai.com/email-verification',
@@ -600,7 +600,7 @@ test('signup flow helper waits for the signup entry tab to settle for step 2 bef
},
setEmailState: async () => {},
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
waitForTabStableComplete: async (_tabId, options) => {
events.push({ type: 'wait-stable', options });
return { id: 23, url: 'https://chatgpt.com/', status: 'complete' };
@@ -680,7 +680,7 @@ test('signup flow helper accepts phone signup landing on login password page', a
},
setEmailState: async () => {},
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
waitForTabUrlMatch: async (_tabId, predicate) => {
const url = 'https://auth.openai.com/log-in/password';
return predicate(url) ? { id: 22, url } : null;
@@ -724,7 +724,7 @@ test('signup flow helper reuses existing managed alias email when it is still co
setEmailCalls += 1;
},
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
waitForTabUrlMatch: async () => null,
});
@@ -767,7 +767,7 @@ test('signup flow helper can generate an email on demand when add-email starts f
setStateCalls.push(updates);
},
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
waitForTabUrlMatch: async () => null,
});
@@ -836,7 +836,7 @@ test('signup flow helper delegates preserved phone identity email sync to the sh
setStateCalls += 1;
},
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
waitForTabUrlMatch: async () => null,
});
@@ -901,7 +901,7 @@ test('signup flow helper finalizes step 3 submit by reusing signup verification
},
setEmailState: async () => {},
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
SIGNUP_PAGE_INJECT_FILES: ['content/utils.js', 'content/signup-page.js'],
OPENAI_AUTH_INJECT_FILES: ['content/utils.js', 'flows/openai/content/openai-auth.js'],
waitForTabUrlMatch: async () => null,
});
@@ -943,11 +943,11 @@ test('signup flow helper rewrites retryable step 3 finalize transport timeout in
isSignupPasswordPageUrl: () => true,
reuseOrCreateTab: async () => 31,
sendToContentScriptResilient: async () => {
throw new Error('Content script on signup-page did not respond in 45s. Try refreshing the tab and retry.');
throw new Error('Content script on openai-auth did not respond in 45s. Try refreshing the tab and retry.');
},
setEmailState: async () => {},
SIGNUP_ENTRY_URL: 'https://chatgpt.com/',
SIGNUP_PAGE_INJECT_FILES: ['content/utils.js', 'content/signup-page.js'],
OPENAI_AUTH_INJECT_FILES: ['content/utils.js', 'flows/openai/content/openai-auth.js'],
waitForTabUrlMatch: async () => null,
});
+15 -15
View File
@@ -6,22 +6,22 @@ test('background imports workflow step modules including rebuilt Kiro modules',
const source = fs.readFileSync('background.js', 'utf8');
[
'background/steps/open-chatgpt.js',
'background/steps/submit-signup-email.js',
'background/steps/fill-password.js',
'background/steps/fetch-signup-code.js',
'background/steps/fill-profile.js',
'background/steps/wait-registration-success.js',
'background/steps/oauth-login.js',
'background/steps/fetch-login-code.js',
'background/steps/confirm-oauth.js',
'background/steps/platform-verify.js',
'flows/openai/background/steps/open-chatgpt.js',
'flows/openai/background/steps/submit-signup-email.js',
'flows/openai/background/steps/fill-password.js',
'flows/openai/background/steps/fetch-signup-code.js',
'flows/openai/background/steps/fill-profile.js',
'flows/openai/background/steps/wait-registration-success.js',
'flows/openai/background/steps/oauth-login.js',
'flows/openai/background/steps/fetch-login-code.js',
'flows/openai/background/steps/confirm-oauth.js',
'flows/openai/background/steps/platform-verify.js',
'shared/kiro-timeouts.js',
'background/kiro/state.js',
'background/kiro/register-runner.js',
'background/kiro/desktop-client.js',
'background/kiro/desktop-authorize-runner.js',
'background/kiro/publisher-kiro-rs.js',
'flows/kiro/background/state.js',
'flows/kiro/background/register-runner.js',
'flows/kiro/background/desktop-client.js',
'flows/kiro/background/desktop-authorize-runner.js',
'flows/kiro/background/publisher-kiro-rs.js',
].forEach((path) => {
assert.match(source, new RegExp(path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
});
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
test('background node registry preserves node metadata even before an executor is registered', () => {
const source = fs.readFileSync('background/steps/registry.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/step-registry.js', 'utf8');
const api = new Function('self', `${source}; return self.MultiPageBackgroundStepRegistry;`)({});
const registry = api.createNodeRegistry([
{
@@ -27,7 +27,7 @@ test('background node registry preserves node metadata even before an executor i
});
test('background node registry executes registered nodes in display order', async () => {
const source = fs.readFileSync('background/steps/registry.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/step-registry.js', 'utf8');
const api = new Function('self', `${source}; return self.MultiPageBackgroundStepRegistry;`)({});
const events = [];
const registry = api.createNodeRegistry([
+6 -6
View File
@@ -4,19 +4,19 @@ const fs = require('node:fs');
test('background imports node registry and wires the rebuilt Kiro executors', () => {
const source = fs.readFileSync('background.js', 'utf8');
assert.match(source, /background\/steps\/registry\.js/);
assert.match(source, /core\/flow-kernel\/step-registry\.js/);
assert.match(source, /data\/step-definitions\.js/);
assert.match(source, /background\/workflow-engine\.js/);
assert.match(source, /core\/flow-kernel\/workflow-engine\.js/);
assert.match(source, /MultiPageStepDefinitions\?\.getNodes/);
assert.match(source, /buildNodeRegistry\(definitions/);
assert.match(source, /const stepRegistryCache = new Map\(\);/);
assert.match(source, /const definitions = getNodeDefinitionsForState\(state\);/);
assert.match(source, /stepRegistryCache\.set\(cacheKey, buildStepRegistry\(definitions\)\)/);
assert.match(source, /background\/kiro\/register-runner\.js/);
assert.match(source, /background\/kiro\/desktop-client\.js/);
assert.match(source, /background\/kiro\/desktop-authorize-runner\.js/);
assert.match(source, /background\/kiro\/publisher-kiro-rs\.js/);
assert.match(source, /flows\/kiro\/background\/register-runner\.js/);
assert.match(source, /flows\/kiro\/background\/desktop-client\.js/);
assert.match(source, /flows\/kiro\/background\/desktop-authorize-runner\.js/);
assert.match(source, /flows\/kiro\/background\/publisher-kiro-rs\.js/);
assert.doesNotMatch(source, /background\/steps\/kiro-device-auth\.js/);
assert.match(source, /const kiroRegisterRunner = self\.MultiPageBackgroundKiroRegisterRunner\?\.createKiroRegisterRunner\(/);
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadStep1Module() {
const source = fs.readFileSync('background/steps/open-chatgpt.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/open-chatgpt.js', 'utf8');
const globalScope = {};
return new Function('self', `${source}; return self.MultiPageBackgroundStep1;`)(globalScope);
}
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('background/steps/fill-password.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/fill-password.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep3;`)(globalScope);
@@ -26,7 +26,7 @@ test('step 3 reuses existing generated password when rerunning the same email fl
events.passwordStates.push(password);
},
setState: async () => {},
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
});
await executor.executeStep3({
@@ -80,7 +80,7 @@ test('step 3 supports phone-only signup identity when password page is present',
setState: async (updates) => {
events.stateUpdates.push(updates);
},
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
});
await executor.executeStep3({
@@ -149,7 +149,7 @@ test('step 3 phone signup intent does not fall back to a stale email identity',
setState: async (updates) => {
events.stateUpdates.push(updates);
},
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
});
await assert.rejects(
@@ -188,7 +188,7 @@ test('step 3 respects resolved email fallback when phone signup is unavailable',
},
setPasswordState: async () => {},
setState: async () => {},
SIGNUP_PAGE_INJECT_FILES: [],
OPENAI_AUTH_INJECT_FILES: [],
});
await executor.executeStep3({
+2 -2
View File
@@ -2,7 +2,7 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('background/steps/fetch-signup-code.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/fetch-signup-code.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep4;`)(globalScope);
@@ -427,7 +427,7 @@ test('step 4 prepare retries transport by recovering retry page without replayin
}
sendToContentScriptCalls += 1;
if (sendToContentScriptCalls === 1) {
throw new Error('Content script on signup-page did not respond in 30s. Try refreshing the tab and retry.');
throw new Error('Content script on openai-auth did not respond in 30s. Try refreshing the tab and retry.');
}
return { ready: true };
},
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('background/steps/fill-profile.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/fill-profile.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep5;`)(globalScope);
@@ -28,7 +28,7 @@ test('step 5 forwards generated profile data and relies on completion signal flo
assert.deepStrictEqual(events.messages, [
{
source: 'signup-page',
source: 'openai-auth',
message: {
type: 'EXECUTE_NODE',
nodeId: 'fill-profile',
+18 -18
View File
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
test('step 6 waits for registration success and completes from background', async () => {
const source = fs.readFileSync('background/steps/wait-registration-success.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/wait-registration-success.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep6;`)(globalScope);
@@ -33,7 +33,7 @@ test('step 6 waits for registration success and completes from background', asyn
});
test('step 6 only clears cookies when cleanup switch is enabled', async () => {
const source = fs.readFileSync('background/steps/wait-registration-success.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/wait-registration-success.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep6;`)(globalScope);
@@ -90,7 +90,7 @@ test('step 6 only clears cookies when cleanup switch is enabled', async () => {
});
test('step 7 retries up to configured limit and then fails', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
@@ -138,7 +138,7 @@ test('step 7 retries up to configured limit and then fails', async () => {
});
test('step 7 hands add-phone to the dedicated post-login phone node without internal retry', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
@@ -196,7 +196,7 @@ test('step 7 hands add-phone to the dedicated post-login phone node without inte
});
test('step 7 no longer runs shared phone verification inside oauth-login', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
@@ -218,7 +218,7 @@ test('step 7 no longer runs shared phone verification inside oauth-login', async
password: 'secret',
phoneVerificationEnabled: true,
}),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 91 : 0),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 91 : 0),
isStep6RecoverableResult: (result) => result?.step6Outcome === 'recoverable',
isStep6SuccessResult: (result) => result?.step6Outcome === 'success',
phoneVerificationHelpers: {
@@ -261,7 +261,7 @@ test('step 7 no longer runs shared phone verification inside oauth-login', async
});
test('step 7 add-phone handoff does not depend on phone verification being enabled', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
@@ -302,7 +302,7 @@ test('step 7 add-phone handoff does not depend on phone verification being enabl
});
test('step 7 ignores obsolete shared add-phone verifier during handoff', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
@@ -343,7 +343,7 @@ test('step 7 ignores obsolete shared add-phone verifier during handoff', async (
});
test('step 7 starts a new oauth timeout window for each refreshed oauth url', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
@@ -396,7 +396,7 @@ test('step 7 starts a new oauth timeout window for each refreshed oauth url', as
});
test('step 7 forwards direct OAuth consent skip metadata when completing', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
@@ -445,7 +445,7 @@ test('step 7 forwards direct OAuth consent skip metadata when completing', async
});
test('step 7 forwards phone login identity payload when account identifier is phone', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
@@ -535,7 +535,7 @@ test('step 7 forwards phone login identity payload when account identifier is ph
});
test('step 7 keeps Plus email login even when phone sms runtime exists', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
@@ -589,7 +589,7 @@ test('step 7 keeps Plus email login even when phone sms runtime exists', async (
});
test('step 7 keeps relogin-bound-email as the active node id', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
@@ -655,7 +655,7 @@ test('step 7 keeps relogin-bound-email as the active node id', async () => {
});
test('step 7 keeps phone login after step 8 stores an unbound email for phone signup', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
@@ -711,7 +711,7 @@ test('step 7 keeps phone login after step 8 stores an unbound email for phone si
});
test('step 7 can infer phone login from an available phone signup configuration before step 2 finishes', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
@@ -757,7 +757,7 @@ test('step 7 can infer phone login from an available phone signup configuration
});
test('step 7 can start from a manually filled signup phone without completed step 2 or step 3 state', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
@@ -822,7 +822,7 @@ test('step 7 can start from a manually filled signup phone without completed ste
});
test('step 7 stops immediately when management secret is missing', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
@@ -867,7 +867,7 @@ test('step 7 stops immediately when management secret is missing', async () => {
});
test('step 7 stops immediately when management secret is invalid', async () => {
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/oauth-login.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
+15 -15
View File
@@ -2,7 +2,7 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('background/steps/fetch-login-code.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/fetch-login-code.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep8;`)(globalScope);
@@ -44,7 +44,7 @@ test('step 8 submits login verification directly without replaying step 7', asyn
navigateOnReuse: false,
}),
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: () => false,
@@ -447,7 +447,7 @@ test('step 8 defers add-email page to the dedicated bind-email node in phone mod
password: 'secret',
oauthUrl: 'https://oauth.example/latest',
}),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: () => false,
@@ -991,7 +991,7 @@ test('step 8 does not submit or recover add-email inside fetch-login-code', asyn
navigateOnReuse: false,
}),
getState: async () => ({ ...runtimeState }),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: () => false,
@@ -1102,7 +1102,7 @@ test('step 8 rejects add-email page in email login mode', async () => {
navigateOnReuse: false,
}),
getState: async () => ({ ...runtimeState }),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: () => false,
@@ -1290,7 +1290,7 @@ test('Plus login-code step reuses step 8 verification logic but completes visibl
navigateOnReuse: false,
}),
getState: async () => ({ email: 'user@example.com', password: 'secret', plusModeEnabled: true }),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: () => false,
@@ -1371,7 +1371,7 @@ test('bound-email relogin code step points recovery to the relogin step in Plus
signupMethod: 'phone',
phoneSignupReloginAfterBindEmailEnabled: true,
}),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: () => false,
@@ -1449,7 +1449,7 @@ test('step 8 completes directly when auth page is already on OAuth consent page'
navigateOnReuse: false,
}),
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: () => false,
@@ -1533,7 +1533,7 @@ test('step 8 retries in-place when polling fails but auth page still stays on ve
navigateOnReuse: false,
}),
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: (error) => /页面通信异常|did not respond/i.test(String(error?.message || error || '')),
@@ -1595,7 +1595,7 @@ test('step 8 keeps resend cooldown timestamp across in-place retries to avoid re
navigateOnReuse: false,
}),
getState: async () => ({ email: 'user@example.com', password: 'secret', loginVerificationRequestedAt: null }),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: (error) => /页面通信异常|did not respond/i.test(String(error?.message || error || '')),
@@ -1677,7 +1677,7 @@ test('step 8 completes when polling fails but recovery probe shows oauth consent
navigateOnReuse: false,
}),
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: (error) => /页面通信异常|did not respond/i.test(String(error?.message || error || '')),
@@ -1759,7 +1759,7 @@ test('step 8 uses a fixed 10-minute lookback window and plans 2925 polling as 2/
{ id: 'acc-1', email: 'pool-user@2925.com' },
],
}),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: () => false,
@@ -1839,7 +1839,7 @@ test('step 8 falls back to the run email when the verification page does not exp
navigateOnReuse: false,
}),
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: () => false,
@@ -1896,7 +1896,7 @@ test('step 8 does not rerun step 7 when verification submit lands on add-phone',
navigateOnReuse: false,
}),
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: () => false,
@@ -1953,7 +1953,7 @@ test('step 8 checks iCloud session before polling iCloud mailbox', async () => {
navigateOnReuse: true,
}),
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
getTabId: async (sourceName) => (sourceName === 'openai-auth' ? 1 : 2),
HOTMAIL_PROVIDER: 'hotmail-api',
isTabAlive: async () => true,
isVerificationMailPollingError: () => false,
@@ -16,7 +16,7 @@ function loadSub2ApiApiModule() {
}
function loadSub2ApiSessionImportModule() {
const source = fs.readFileSync('background/steps/sub2api-session-import.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/sub2api-session-import.js', 'utf8');
return new Function('self', `${source}; return self.MultiPageBackgroundSub2ApiSessionImport;`)({});
}
@@ -331,7 +331,7 @@ test('session import step reads current ChatGPT session and completes node', asy
assert.deepStrictEqual(ensureCalls[0].options.inject, [
'content/utils.js',
'content/operation-delay.js',
'content/plus-checkout.js',
'flows/openai/content/plus-checkout.js',
]);
assert.deepStrictEqual(sentMessages, [{
tabId: 91,
+27 -25
View File
@@ -1,14 +1,17 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { readSourceRegistryBundle } = require('./helpers/script-bundles.js');
const registrySource = readSourceRegistryBundle();
test('background imports tab runtime module', () => {
const source = fs.readFileSync('background.js', 'utf8');
assert.match(source, /background\/tab-runtime\.js/);
assert.match(source, /core\/flow-kernel\/tab-runtime\.js/);
});
test('tab runtime module exposes a factory', () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
@@ -16,9 +19,8 @@ test('tab runtime module exposes a factory', () => {
assert.equal(typeof api?.createTabRuntime, 'function');
});
test('tab runtime accepts canonical openai-auth readiness for queued signup-page commands', async () => {
const runtimeSource = fs.readFileSync('background/tab-runtime.js', 'utf8');
const registrySource = fs.readFileSync('shared/source-registry.js', 'utf8');
test('tab runtime accepts canonical openai-auth readiness for queued openai-auth commands', async () => {
const runtimeSource = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
const runtimeApi = new Function('self', `${runtimeSource}; return self.MultiPageBackgroundTabRuntime;`)({});
const registryApi = new Function('self', `${registrySource}; return self.MultiPageSourceRegistry;`)({});
const sourceRegistry = registryApi.createSourceRegistry();
@@ -26,10 +28,10 @@ test('tab runtime accepts canonical openai-auth readiness for queued signup-page
const sentMessages = [];
let currentState = {
tabRegistry: {
'signup-page': { tabId: 91, ready: true },
'openai-auth': { tabId: 91, ready: true },
},
sourceLastUrls: {
'signup-page': 'https://auth.openai.com/authorize',
'openai-auth': 'https://auth.openai.com/authorize',
},
};
@@ -73,21 +75,21 @@ test('tab runtime accepts canonical openai-auth readiness for queued signup-page
sourceLastUrls: {},
};
const queued = runtime.queueCommand('signup-page', { type: 'STEP2_TEST' }, 1000);
const queued = runtime.queueCommand('openai-auth', { type: 'STEP2_TEST' }, 1000);
runtime.flushCommand('openai-auth', 55);
await assert.doesNotReject(queued);
assert.deepEqual(sentMessages, [{ tabId: 55, message: { type: 'STEP2_TEST' } }]);
await runtime.ensureContentScriptReadyOnTab('signup-page', 77, {
await runtime.ensureContentScriptReadyOnTab('openai-auth', 77, {
timeoutMs: 100,
});
assert.deepEqual(currentState.tabRegistry['openai-auth'], { tabId: 77, ready: true, windowId: 1 });
assert.equal(Object.prototype.hasOwnProperty.call(currentState.tabRegistry, 'signup-page'), false);
assert.deepEqual(Object.keys(currentState.tabRegistry), ['openai-auth']);
});
test('tab runtime caps per-attempt response timeout to the remaining resilient timeout budget', () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
@@ -122,7 +124,7 @@ test('tab runtime caps per-attempt response timeout to the remaining resilient t
});
test('tab runtime gives step 5 profile submit enough response time for slow page transitions', () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
@@ -153,7 +155,7 @@ test('tab runtime gives step 5 profile submit enough response time for slow page
});
test('tab runtime replays retryable transport recovery hook and surfaces a localized timeout error', async () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
@@ -207,7 +209,7 @@ test('tab runtime replays retryable transport recovery hook and surfaces a local
});
test('tab runtime waitForTabComplete waits until tab status becomes complete', async () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
@@ -250,7 +252,7 @@ test('tab runtime waitForTabComplete waits until tab status becomes complete', a
});
test('tab runtime waitForTabComplete aborts promptly when stop is requested', async () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
@@ -290,7 +292,7 @@ test('tab runtime waitForTabComplete aborts promptly when stop is requested', as
});
test('tab runtime waitForTabStableComplete waits through a late navigation after an initial complete state', async () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
@@ -345,7 +347,7 @@ test('tab runtime waitForTabStableComplete waits through a late navigation after
});
test('tab runtime opens new automation tabs in the locked window', async () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
const created = [];
@@ -377,14 +379,14 @@ test('tab runtime opens new automation tabs in the locked window', async () => {
throwIfStopped: () => {},
});
await runtime.reuseOrCreateTab('signup-page', 'https://example.com');
await runtime.reuseOrCreateTab('openai-auth', 'https://example.com');
assert.equal(created.length, 1);
assert.equal(created[0].windowId, 100);
});
test('tab runtime force-new opens replacement before removing the active stale source tab', async () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
const events = [];
@@ -413,18 +415,18 @@ test('tab runtime force-new opens replacement before removing the active stale s
getSourceLabel: (sourceName) => sourceName || 'unknown',
getState: async () => ({
automationWindowId: 100,
sourceLastUrls: { 'signup-page': 'https://chatgpt.com/' },
sourceLastUrls: { 'openai-auth': 'https://chatgpt.com/' },
tabRegistry: {},
}),
matchesSourceUrlFamily: (sourceName, candidateUrl) => (
sourceName === 'signup-page'
sourceName === 'openai-auth'
&& /chatgpt\.com|auth\.openai\.com/.test(String(candidateUrl || ''))
),
setState: async () => {},
throwIfStopped: () => {},
});
const tabId = await runtime.reuseOrCreateTab('signup-page', 'https://auth.openai.com/authorize', {
const tabId = await runtime.reuseOrCreateTab('openai-auth', 'https://auth.openai.com/authorize', {
forceNew: true,
});
@@ -439,7 +441,7 @@ test('tab runtime force-new opens replacement before removing the active stale s
});
test('tab runtime scopes tab queries to the locked automation window', async () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
const queries = [];
@@ -472,7 +474,7 @@ test('tab runtime scopes tab queries to the locked automation window', async ()
});
test('tab runtime does not create tabs outside an unavailable locked window', async () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
const created = [];
@@ -512,7 +514,7 @@ test('tab runtime does not create tabs outside an unavailable locked window', as
});
test('tab runtime does not query all windows when the locked window is unavailable', async () => {
const source = fs.readFileSync('background/tab-runtime.js', 'utf8');
const source = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundTabRuntime;`)(globalScope);
const queries = [];
+2 -1
View File
@@ -1,8 +1,9 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { readFlowRegistryBundle } = require('./helpers/script-bundles.js');
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
const flowRegistrySource = readFlowRegistryBundle();
const contributionRegistrySource = fs.readFileSync('shared/contribution-registry.js', 'utf8');
function loadApi() {
+18 -27
View File
@@ -1,18 +1,12 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { readFlowCapabilitiesBundle } = require('./helpers/script-bundles.js');
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
const contributionRegistrySource = fs.readFileSync('shared/contribution-registry.js', 'utf8');
const settingsSchemaSource = fs.readFileSync('shared/settings-schema.js', 'utf8');
const source = fs.readFileSync('shared/flow-capabilities.js', 'utf8');
const source = readFlowCapabilitiesBundle();
function loadApi() {
const scope = {};
return new Function(
'self',
`${flowRegistrySource}; ${contributionRegistrySource}; ${settingsSchemaSource}; ${source}; return self.MultiPageFlowCapabilities;`
)(scope);
return new Function('self', `${source}; return self.MultiPageFlowCapabilities;`)(scope);
}
test('flow capability registry keeps OpenAI phone signup available only when runtime locks allow it', () => {
@@ -22,7 +16,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
const enabledState = registry.resolveSidepanelCapabilities({
state: {
activeFlowId: 'openai',
openaiIntegrationTargetId: 'cpa',
targetId: 'cpa',
phoneVerificationEnabled: true,
plusModeEnabled: false,
accountContributionEnabled: false,
@@ -38,7 +32,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
const plusLockedState = registry.resolveSidepanelCapabilities({
state: {
activeFlowId: 'openai',
openaiIntegrationTargetId: 'sub2api',
targetId: 'sub2api',
phoneVerificationEnabled: true,
plusModeEnabled: true,
accountContributionEnabled: false,
@@ -59,7 +53,7 @@ test('flow capability registry defaults unknown flows to minimal non-phone capab
const capabilityState = registry.resolveSidepanelCapabilities({
state: {
activeFlowId: 'site-a',
openaiIntegrationTargetId: 'codex2api',
targetId: 'codex2api',
phoneVerificationEnabled: true,
plusModeEnabled: true,
accountContributionEnabled: true,
@@ -73,7 +67,7 @@ test('flow capability registry defaults unknown flows to minimal non-phone capab
assert.equal(capabilityState.canShowLuckmail, false);
assert.equal(capabilityState.canUsePhoneSignup, false);
assert.equal(capabilityState.effectiveSignupMethod, 'email');
assert.equal(capabilityState.panelMode, 'codex2api');
assert.equal(capabilityState.effectiveTargetId, 'codex2api');
assert.deepEqual(capabilityState.supportedTargetIds, []);
});
@@ -84,8 +78,7 @@ test('flow capability registry exposes Kiro as an independent flow with its own
const capabilityState = registry.resolveSidepanelCapabilities({
state: {
activeFlowId: 'kiro',
kiroTargetId: 'kiro-rs',
openaiIntegrationTargetId: 'sub2api',
targetId: 'kiro-rs',
signupMethod: 'phone',
plusModeEnabled: true,
phoneVerificationEnabled: true,
@@ -120,7 +113,7 @@ test('flow capability registry exposes shared auto-run validation for phone lock
const plusLockedResult = registry.validateAutoRunStart({
state: {
activeFlowId: 'openai',
openaiIntegrationTargetId: 'cpa',
targetId: 'cpa',
signupMethod: 'phone',
phoneVerificationEnabled: true,
plusModeEnabled: true,
@@ -134,7 +127,7 @@ test('flow capability registry exposes shared auto-run validation for phone lock
const unsupportedPanelResult = registry.validateAutoRunStart({
state: {
activeFlowId: 'site-a',
openaiIntegrationTargetId: 'sub2api',
targetId: 'sub2api',
signupMethod: 'email',
},
});
@@ -158,14 +151,14 @@ test('flow capability registry normalizes unsupported mode switches back to the
const validation = registry.validateModeSwitch({
state: {
activeFlowId: 'site-a',
openaiIntegrationTargetId: 'sub2api',
targetId: 'sub2api',
signupMethod: 'phone',
phoneVerificationEnabled: true,
plusModeEnabled: true,
accountContributionEnabled: true,
},
changedKeys: [
'openaiIntegrationTargetId',
'targetId',
'signupMethod',
'phoneVerificationEnabled',
'plusModeEnabled',
@@ -175,9 +168,7 @@ test('flow capability registry normalizes unsupported mode switches back to the
assert.equal(validation.ok, false);
assert.deepEqual(validation.normalizedUpdates, {
panelMode: 'cpa',
openaiIntegrationTargetId: 'cpa',
kiroTargetId: 'cpa',
targetId: 'cpa',
signupMethod: 'email',
phoneVerificationEnabled: false,
plusModeEnabled: false,
@@ -203,7 +194,7 @@ test('flow capability registry exposes editable Plus account access strategies f
const capabilityState = registry.resolveSidepanelCapabilities({
state: {
activeFlowId: 'openai',
openaiIntegrationTargetId: 'sub2api',
targetId: 'sub2api',
signupMethod: 'email',
plusModeEnabled: true,
plusAccountAccessStrategy: 'sub2api_codex_session',
@@ -227,7 +218,7 @@ test('flow capability registry maps session import to the current source target'
const capabilityState = registry.resolveSidepanelCapabilities({
state: {
activeFlowId: 'openai',
openaiIntegrationTargetId: 'sub2api',
targetId: 'sub2api',
signupMethod: 'email',
plusModeEnabled: true,
plusAccountAccessStrategy: 'cpa_codex_session',
@@ -250,7 +241,7 @@ test('flow capability registry exposes editable Plus account access strategies f
const capabilityState = registry.resolveSidepanelCapabilities({
state: {
activeFlowId: 'openai',
openaiIntegrationTargetId: 'cpa',
targetId: 'cpa',
signupMethod: 'email',
plusModeEnabled: true,
plusAccountAccessStrategy: 'cpa_codex_session',
@@ -274,7 +265,7 @@ test('flow capability registry falls back to OAuth when the current source canno
const capabilityState = registry.resolveSidepanelCapabilities({
state: {
activeFlowId: 'openai',
openaiIntegrationTargetId: 'codex2api',
targetId: 'codex2api',
signupMethod: 'email',
plusModeEnabled: true,
plusAccountAccessStrategy: 'cpa_codex_session',
@@ -298,7 +289,7 @@ test('flow capability registry forces SUB2API session import only for contributi
const capabilityState = registry.resolveSidepanelCapabilities({
state: {
activeFlowId: 'openai',
openaiIntegrationTargetId: 'cpa',
targetId: 'cpa',
signupMethod: 'email',
plusModeEnabled: true,
accountContributionEnabled: true,
+9 -11
View File
@@ -1,9 +1,9 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { readFlowRegistryBundle, readBundle } = require('./helpers/script-bundles.js');
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
const settingsSchemaSource = fs.readFileSync('shared/settings-schema.js', 'utf8');
const flowRegistrySource = readFlowRegistryBundle();
const settingsSchemaSource = readBundle(['core/flow-kernel/settings-schema.js']);
function loadApis() {
const scope = {};
@@ -57,7 +57,7 @@ test('settings schema normalizes view input into canonical nested namespaces', (
const normalized = schema.normalizeSettingsState({
activeFlowId: 'kiro',
panelMode: 'sub2api',
targetId: 'kiro-rs',
mailProvider: 'hotmail',
ipProxyEnabled: true,
ipProxyService: '711proxy',
@@ -75,9 +75,9 @@ test('settings schema normalizes view input into canonical nested namespaces', (
assert.equal(normalized.services.email.provider, 'hotmail');
assert.equal(normalized.services.proxy.enabled, true);
assert.equal(normalized.services.account.customPassword, 'SharedSecret123!');
assert.equal(normalized.flows.openai.integrationTargetId, 'sub2api');
assert.equal(normalized.flows.openai.selectedTargetId, 'cpa');
assert.equal(normalized.flows.openai.plus.plusAccountAccessStrategy, 'sub2api_codex_session');
assert.equal(normalized.flows.kiro.targetId, 'kiro-rs');
assert.equal(normalized.flows.kiro.selectedTargetId, 'kiro-rs');
assert.equal(normalized.flows.kiro.targets['kiro-rs'].baseUrl, 'https://kiro.example.com/admin');
assert.equal(normalized.flows.kiro.targets['kiro-rs'].apiKey, 'secret-key');
assert.deepEqual(normalized.flows.kiro.autoRun.stepExecutionRange, {
@@ -116,7 +116,7 @@ test('settings schema can project canonical state into a read view without legac
const schema = settingsSchema.createSettingsSchema();
const normalized = schema.normalizeSettingsState({
activeFlowId: 'kiro',
kiroTargetId: 'kiro-rs',
targetId: 'kiro-rs',
kiroRsUrl: 'https://kiro.example.com/admin',
kiroRsKey: 'key-123',
plusAccountAccessStrategy: 'sub2api_codex_session',
@@ -124,13 +124,11 @@ test('settings schema can project canonical state into a read view without legac
const view = schema.buildSettingsView(normalized);
assert.equal(view.activeFlowId, 'kiro');
assert.equal(view.openaiIntegrationTargetId, 'cpa');
assert.equal(view.kiroTargetId, 'kiro-rs');
assert.equal(view.panelMode, 'cpa');
assert.equal(view.targetId, 'kiro-rs');
assert.equal(view.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(view.kiroRsKey, 'key-123');
assert.equal(view.plusAccountAccessStrategy, 'sub2api_codex_session');
assert.equal(view.settingsSchemaVersion, 4);
assert.equal(view.settingsSchemaVersion, 5);
assert.equal(view.settingsState.activeFlowId, 'kiro');
});
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('background/steps/gopay-approve.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/gopay-approve.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
const vm = require('node:vm');
const source = fs.readFileSync('content/gopay-flow.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/gopay-flow.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('background/steps/gopay-manual-confirm.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/gopay-manual-confirm.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundGoPayManualConfirm;`)(globalScope);
+65
View File
@@ -0,0 +1,65 @@
const fs = require('node:fs');
const FLOW_DEFINITION_FILES = Object.freeze([
'flows/openai/index.js',
'flows/kiro/index.js',
'flows/index.js',
]);
const FLOW_WORKFLOW_FILES = Object.freeze([
'flows/openai/workflow.js',
'flows/kiro/workflow.js',
]);
function readBundle(files = []) {
return files
.map((file) => fs.readFileSync(file, 'utf8'))
.join(';\n');
}
function readFlowRegistryBundle(extraFiles = []) {
return readBundle([
...FLOW_DEFINITION_FILES,
'core/flow-kernel/flow-registry.js',
...extraFiles,
]);
}
function readSourceRegistryBundle(extraFiles = []) {
return readBundle([
...FLOW_DEFINITION_FILES,
'core/flow-kernel/flow-registry.js',
'core/flow-kernel/source-registry.js',
...extraFiles,
]);
}
function readStepDefinitionsBundle(extraFiles = []) {
return readBundle([
...FLOW_DEFINITION_FILES,
...FLOW_WORKFLOW_FILES,
'data/step-definitions.js',
...extraFiles,
]);
}
function readFlowCapabilitiesBundle(extraFiles = []) {
return readBundle([
...FLOW_DEFINITION_FILES,
'core/flow-kernel/flow-registry.js',
'shared/contribution-registry.js',
'core/flow-kernel/settings-schema.js',
'core/flow-kernel/flow-capabilities.js',
...extraFiles,
]);
}
module.exports = {
FLOW_DEFINITION_FILES,
FLOW_WORKFLOW_FILES,
readBundle,
readFlowCapabilitiesBundle,
readFlowRegistryBundle,
readSourceRegistryBundle,
readStepDefinitionsBundle,
};
+69 -41
View File
@@ -3,14 +3,18 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadKiroContributionModules() {
const stateSource = fs.readFileSync('background/kiro/state.js', 'utf8');
const artifactSource = fs.readFileSync('background/kiro/credential-artifact.js', 'utf8');
const stateSource = fs.readFileSync('flows/kiro/background/state.js', 'utf8');
const artifactSource = fs.readFileSync('flows/kiro/background/credential-artifact.js', 'utf8');
const adapterSource = fs.readFileSync('background/contribution/adapters/kiro-builder-id.js', 'utf8');
const globalScope = {};
new Function('self', `${stateSource}; ${artifactSource}; ${adapterSource}; return self;`)(globalScope);
return globalScope;
}
function getKiroRuntime(state = {}) {
return state?.runtimeState?.flowState?.kiro || {};
}
function buildAuthorizedKiroState(overrides = {}) {
return {
activeFlowId: 'kiro',
@@ -19,20 +23,24 @@ function buildAuthorizedKiroState(overrides = {}) {
contributionAdapterId: 'kiro-builder-id',
contributionNickname: '贡献者',
contributionQq: '123456',
kiroRuntime: {
register: {
email: 'kiro-user@example.com',
},
desktopAuth: {
region: 'us-east-1',
clientId: 'client-id-001',
clientSecret: 'client-secret-super-long',
refreshToken: 'refresh-token-super-secret',
tokenSource: 'desktop_authorization_code_pkce',
authorizedAt: 1760000000000,
},
upload: {
targetId: 'kiro-rs',
runtimeState: {
flowState: {
kiro: {
register: {
email: 'kiro-user@example.com',
},
desktopAuth: {
region: 'us-east-1',
clientId: 'client-id-001',
clientSecret: 'client-secret-super-long',
refreshToken: 'refresh-token-super-secret',
tokenSource: 'desktop_authorization_code_pkce',
authorizedAt: 1760000000000,
},
upload: {
targetId: 'kiro-rs',
},
},
},
},
...overrides,
@@ -65,11 +73,15 @@ test('Kiro Builder ID artifact builder rejects missing required fields', () => {
assert.throws(
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
kiroRuntime: {
...buildAuthorizedKiroState().kiroRuntime,
desktopAuth: {
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
refreshToken: '',
runtimeState: {
flowState: {
kiro: {
...getKiroRuntime(buildAuthorizedKiroState()),
desktopAuth: {
...getKiroRuntime(buildAuthorizedKiroState()).desktopAuth,
refreshToken: '',
},
},
},
},
})),
@@ -77,11 +89,15 @@ test('Kiro Builder ID artifact builder rejects missing required fields', () => {
);
assert.throws(
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
kiroRuntime: {
...buildAuthorizedKiroState().kiroRuntime,
desktopAuth: {
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
clientId: '',
runtimeState: {
flowState: {
kiro: {
...getKiroRuntime(buildAuthorizedKiroState()),
desktopAuth: {
...getKiroRuntime(buildAuthorizedKiroState()).desktopAuth,
clientId: '',
},
},
},
},
})),
@@ -89,11 +105,15 @@ test('Kiro Builder ID artifact builder rejects missing required fields', () => {
);
assert.throws(
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
kiroRuntime: {
...buildAuthorizedKiroState().kiroRuntime,
desktopAuth: {
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
clientSecret: '',
runtimeState: {
flowState: {
kiro: {
...getKiroRuntime(buildAuthorizedKiroState()),
desktopAuth: {
...getKiroRuntime(buildAuthorizedKiroState()).desktopAuth,
clientSecret: '',
},
},
},
},
})),
@@ -101,9 +121,13 @@ test('Kiro Builder ID artifact builder rejects missing required fields', () => {
);
assert.throws(
() => api.buildKiroBuilderIdArtifact(buildAuthorizedKiroState({
kiroRuntime: {
...buildAuthorizedKiroState().kiroRuntime,
register: { email: '' },
runtimeState: {
flowState: {
kiro: {
...getKiroRuntime(buildAuthorizedKiroState()),
register: { email: '' },
},
},
},
email: '',
accountIdentifier: '',
@@ -167,11 +191,15 @@ test('Kiro contribution adapter skips invalid artifacts without sending secrets'
});
const result = await adapter.maybeSubmitFlowContribution(buildAuthorizedKiroState({
kiroRuntime: {
...buildAuthorizedKiroState().kiroRuntime,
desktopAuth: {
...buildAuthorizedKiroState().kiroRuntime.desktopAuth,
refreshToken: '',
runtimeState: {
flowState: {
kiro: {
...getKiroRuntime(buildAuthorizedKiroState()),
desktopAuth: {
...getKiroRuntime(buildAuthorizedKiroState()).desktopAuth,
refreshToken: '',
},
},
},
},
}));
@@ -211,8 +239,8 @@ test('Kiro contribution adapter redacts server errors that echo submitted secret
});
test('Kiro public contribution is triggered from step 9 instead of step 8', () => {
const desktopSource = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
const publisherSource = fs.readFileSync('background/kiro/publisher-kiro-rs.js', 'utf8');
const desktopSource = fs.readFileSync('flows/kiro/background/desktop-authorize-runner.js', 'utf8');
const publisherSource = fs.readFileSync('flows/kiro/background/publisher-kiro-rs.js', 'utf8');
assert.doesNotMatch(desktopSource, /trigger:\s*'kiro-step-8'/);
assert.match(publisherSource, /maybeSubmitFlowContribution/);
assert.match(publisherSource, /trigger:\s*'kiro-step-9'/);
+1 -1
View File
@@ -3,7 +3,7 @@ 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');
const source = fs.readFileSync('flows/kiro/content/register-page.js', 'utf8');
function createTextNode(textContent = '') {
return { textContent };
@@ -0,0 +1,68 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const accountRunHistorySource = fs.readFileSync('background/account-run-history.js', 'utf8');
const accountRecordsImporterSource = fs.readFileSync('imports/legacy/account-records-importer.js', 'utf8');
function loadImporterApi() {
const scope = {};
return new Function('self', `
${accountRunHistorySource}
${accountRecordsImporterSource}
return self.MultiPageLegacyAccountRecordsImporter;
`)(scope);
}
test('legacy account records importer normalizes old history records into canonical record shape', () => {
const importer = loadImporterApi();
const importerApi = importer.createAccountRecordsImporter({
getNodeIdByStepForState: () => '',
});
const records = importerApi.importAccountRecords([
{
email: 'LegacyUser@example.com',
password: 'secret',
status: 'success',
recordedAt: '2026-04-17T00:12:00.000Z',
plusModeEnabled: 1,
},
{
phone: '+6612345',
finalStatus: 'stopped',
reason: '用户停止',
failedStep: 7,
flowId: 'kiro',
},
]);
assert.equal(records.length, 2);
assert.deepEqual(records[0], {
recordId: 'legacyuser@example.com',
flowId: '',
runId: '',
accountIdentifierType: 'email',
accountIdentifier: 'legacyuser@example.com',
email: 'legacyuser@example.com',
phoneNumber: '',
password: 'secret',
finalStatus: 'success',
finishedAt: '2026-04-17T00:12:00.000Z',
retryCount: 0,
failureLabel: '流程完成',
failureDetail: '',
failedNodeId: '',
failedStep: null,
source: 'manual',
autoRunContext: null,
plusModeEnabled: true,
accountContributionEnabled: false,
});
assert.equal(records[1].recordId, 'phone:+6612345');
assert.equal(records[1].flowId, 'kiro');
assert.equal(records[1].accountIdentifierType, 'phone');
assert.equal(records[1].phoneNumber, '+6612345');
assert.equal(records[1].finalStatus, 'stopped');
assert.equal(records[1].failedStep, 7);
});
+86
View File
@@ -0,0 +1,86 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { readFlowRegistryBundle } = require('./helpers/script-bundles.js');
const flowRegistrySource = readFlowRegistryBundle();
const settingsSchemaSource = fs.readFileSync('core/flow-kernel/settings-schema.js', 'utf8');
const settingsImporterSource = fs.readFileSync('imports/legacy/settings-importer.js', 'utf8');
function loadImporterApi() {
const scope = {};
return new Function('self', `
${flowRegistrySource}
${settingsSchemaSource}
${settingsImporterSource}
return {
flowRegistry: self.MultiPageFlowRegistry,
settingsSchema: self.MultiPageSettingsSchema,
importer: self.MultiPageLegacySettingsImporter,
};
`)(scope);
}
test('legacy settings importer converts flat legacy keys into canonical settingsState', () => {
const { flowRegistry, settingsSchema, importer } = loadImporterApi();
const importerApi = importer.createSettingsImporter({
flowRegistry,
settingsSchemaApi: settingsSchema.createSettingsSchema({ flowRegistry }),
});
const imported = importerApi.importSettings({
activeFlowId: 'kiro',
panelMode: 'sub2api',
kiroTargetId: 'kiro-rs',
kiroRsUrl: 'https://kiro.example.com/admin',
kiroRsKey: 'secret-key',
mailProvider: 'hotmail',
stepExecutionRangeByFlow: {
kiro: { enabled: true, fromStep: 2, toStep: 9 },
},
kiroRuntime: {
upload: {
status: 'uploaded',
},
},
});
assert.equal(imported.settingsSchemaVersion, 5);
assert.equal(imported.settingsState.activeFlowId, 'kiro');
assert.equal(imported.settingsState.flows.openai.selectedTargetId, 'sub2api');
assert.equal(imported.settingsState.flows.kiro.selectedTargetId, 'kiro-rs');
assert.equal(imported.settingsState.flows.kiro.targets['kiro-rs'].baseUrl, 'https://kiro.example.com/admin');
assert.equal(imported.settingsState.flows.kiro.targets['kiro-rs'].apiKey, 'secret-key');
assert.deepEqual(imported.settingsState.flows.kiro.autoRun.stepExecutionRange, {
enabled: true,
fromStep: 2,
toStep: 9,
});
assert.equal(Object.prototype.hasOwnProperty.call(imported, 'kiroRuntime'), false);
assert.deepEqual(imported.legacyFieldHits.sort(), [
'kiroRuntime',
'kiroTargetId',
'panelMode',
'stepExecutionRangeByFlow',
]);
});
test('legacy settings importer preserves canonical settingsState without reintroducing old fields', () => {
const { flowRegistry, settingsSchema, importer } = loadImporterApi();
const schema = settingsSchema.createSettingsSchema({ flowRegistry });
const importerApi = importer.createSettingsImporter({
flowRegistry,
settingsSchemaApi: schema,
});
const canonicalState = schema.normalizeSettingsState({
settingsState: schema.buildDefaultSettingsState(),
});
const imported = importerApi.importSettings({
settingsSchemaVersion: 5,
settingsState: canonicalState,
});
assert.deepEqual(imported.settingsState, canonicalState);
assert.deepEqual(imported.legacyFieldHits, []);
});
@@ -78,6 +78,6 @@ test('mail polling bundles do not load operation delay module', () => {
});
test('WhatsApp code reader remains polling-only and delay-free', () => {
const source = fs.readFileSync('content/whatsapp-flow.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/whatsapp-flow.js', 'utf8');
assert.doesNotMatch(source, /performOperationWithDelay\(/);
});
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
@@ -2,7 +2,7 @@ const assert = require('assert');
const fs = require('fs');
const helperSource = fs.readFileSync('background.js', 'utf8');
const tabRuntimeSource = fs.readFileSync('background/tab-runtime.js', 'utf8');
const tabRuntimeSource = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
function extractFunction(source, name) {
const markers = [`async function ${name}(`, `function ${name}(`];
@@ -145,14 +145,14 @@ return {
(async () => {
assert.strictEqual(
api.matchesSourceUrlFamily('signup-page', 'https://chatgpt.com/', 'https://chatgpt.com/'),
api.matchesSourceUrlFamily('openai-auth', 'https://chatgpt.com/', 'https://chatgpt.com/'),
true,
'signup-page family should include chatgpt.com'
'openai-auth family should include chatgpt.com'
);
assert.strictEqual(
api.matchesSourceUrlFamily('signup-page', 'https://chat.openai.com/', 'https://auth.openai.com/authorize'),
api.matchesSourceUrlFamily('openai-auth', 'https://chat.openai.com/', 'https://auth.openai.com/authorize'),
true,
'signup-page family should include legacy chat.openai.com'
'openai-auth family should include legacy chat.openai.com'
);
assert.strictEqual(
api.matchesSourceUrlFamily('mail-163', 'https://mail.126.com/js6/main.jsp', 'https://mail.163.com/js6/main.jsp'),
@@ -169,15 +169,15 @@ return {
],
state: {
sourceLastUrls: {
'signup-page': 'https://chatgpt.com/',
'openai-auth': 'https://chatgpt.com/',
},
tabRegistry: {
'signup-page': { tabId: 3, ready: true },
'openai-auth': { tabId: 3, ready: true },
},
},
});
await api.closeConflictingTabsForSource('signup-page', 'https://auth.openai.com/authorize', {
await api.closeConflictingTabsForSource('openai-auth', 'https://auth.openai.com/authorize', {
excludeTabIds: [3],
});
@@ -195,19 +195,19 @@ return {
],
state: {
sourceLastUrls: {
'signup-page': 'https://auth.openai.com/authorize?client_id=test',
'openai-auth': 'https://auth.openai.com/authorize?client_id=test',
},
tabRegistry: {
'signup-page': { tabId: 11, ready: true },
'openai-auth': { tabId: 11, ready: true },
},
},
});
await api.closeConflictingTabsForSource('signup-page', 'https://chatgpt.com/');
await api.closeConflictingTabsForSource('openai-auth', 'https://chatgpt.com/');
snapshot = api.snapshot();
assert.deepStrictEqual(snapshot.removedBatches, [[11, 12]]);
assert.strictEqual(snapshot.currentState.tabRegistry['signup-page'], null);
assert.strictEqual(snapshot.currentState.tabRegistry['openai-auth'], null);
api.reset({
tabs: [
@@ -216,19 +216,19 @@ return {
],
state: {
sourceLastUrls: {
'signup-page': 'https://auth.openai.com/authorize?client_id=test',
'openai-auth': 'https://auth.openai.com/authorize?client_id=test',
},
tabRegistry: {},
},
});
await api.closeConflictingTabsForSource('signup-page', 'https://chatgpt.com/');
await api.closeConflictingTabsForSource('openai-auth', 'https://chatgpt.com/');
snapshot = api.snapshot();
assert.deepStrictEqual(snapshot.removedBatches, [[21, 22]]);
assert.deepStrictEqual(snapshot.currentTabs, []);
console.log('signup page tab cleanup tests passed');
console.log('openai auth tab cleanup tests passed');
})().catch((error) => {
console.error(error);
process.exit(1);
+11 -11
View File
@@ -10,10 +10,10 @@ function assertOrdered(list, before, after) {
test('manifest loads operation delay after utils only for covered auth/provider bundles', () => {
const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8'));
const authBundle = manifest.content_scripts.find((entry) => entry.js.includes('content/signup-page.js')).js;
const authBundle = manifest.content_scripts.find((entry) => entry.js.includes('flows/openai/content/openai-auth.js')).js;
assertOrdered(authBundle, 'content/utils.js', 'content/operation-delay.js');
assertOrdered(authBundle, 'content/operation-delay.js', 'content/auth-page-recovery.js');
assertOrdered(authBundle, 'content/operation-delay.js', 'content/signup-page.js');
assertOrdered(authBundle, 'content/operation-delay.js', 'flows/openai/content/auth-page-recovery.js');
assertOrdered(authBundle, 'content/operation-delay.js', 'flows/openai/content/openai-auth.js');
const duckBundle = manifest.content_scripts.find((entry) => entry.js.includes('content/duck-mail.js')).js;
assertOrdered(duckBundle, 'content/utils.js', 'content/operation-delay.js');
@@ -27,11 +27,11 @@ test('manifest loads operation delay after utils only for covered auth/provider
test('dynamic covered injections load operation delay after utils', () => {
const expectations = [
['background.js', 'SIGNUP_PAGE_INJECT_FILES'],
['background/steps/create-plus-checkout.js', 'PLUS_CHECKOUT_INJECT_FILES'],
['background/steps/fill-plus-checkout.js', 'PLUS_CHECKOUT_INJECT_FILES'],
['background/steps/paypal-approve.js', 'PAYPAL_INJECT_FILES'],
['background/steps/gopay-approve.js', 'GOPAY_INJECT_FILES'],
['background.js', 'OPENAI_AUTH_INJECT_FILES'],
['flows/openai/background/steps/create-plus-checkout.js', 'PLUS_CHECKOUT_INJECT_FILES'],
['flows/openai/background/steps/fill-plus-checkout.js', 'PLUS_CHECKOUT_INJECT_FILES'],
['flows/openai/background/steps/paypal-approve.js', 'PAYPAL_INJECT_FILES'],
['flows/openai/background/steps/gopay-approve.js', 'GOPAY_INJECT_FILES'],
['background/mail-2925-session.js', 'MAIL2925_INJECT'],
];
for (const [file, constantName] of expectations) {
@@ -40,8 +40,8 @@ test('dynamic covered injections load operation delay after utils', () => {
assert.ok(match, `missing ${constantName} in ${file}`);
const block = match[1];
assert.match(block, /'content\/utils\.js'[\s\S]*'content\/operation-delay\.js'/, `${file} must inject operation delay after utils`);
if (constantName === 'SIGNUP_PAGE_INJECT_FILES') {
assert.match(block, /'content\/operation-delay\.js'[\s\S]*'content\/auth-page-recovery\.js'/, 'auth recovery must load after operation delay');
if (constantName === 'OPENAI_AUTH_INJECT_FILES') {
assert.match(block, /'content\/operation-delay\.js'[\s\S]*'flows\/openai\/content\/auth-page-recovery\.js'/, 'auth recovery must load after operation delay');
}
}
});
@@ -56,7 +56,7 @@ test('2925 provider reuse path also injects operation delay', () => {
});
test('excluded platform verification paths do not load operation delay', () => {
for (const file of ['background/steps/platform-verify.js', 'background/panel-bridge.js']) {
for (const file of ['flows/openai/background/steps/platform-verify.js', 'background/panel-bridge.js']) {
const source = fs.readFileSync(file, 'utf8');
assert.doesNotMatch(source, /content\/operation-delay\.js/);
}
+2 -2
View File
@@ -3,8 +3,8 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
const vm = require('node:vm');
const source = fs.readFileSync('background/steps/paypal-approve.js', 'utf8');
const paypalContentSource = fs.readFileSync('content/paypal-flow.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/paypal-approve.js', 'utf8');
const paypalContentSource = fs.readFileSync('flows/openai/content/paypal-flow.js', 'utf8');
function loadModule() {
const self = {};
+1 -1
View File
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
const vm = require('node:vm');
const source = fs.readFileSync('content/paypal-flow.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/paypal-flow.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/phone-auth.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/phone-auth.js', 'utf8');
const globalScope = { navigator: { language: 'zh-CN' } };
const api = new Function('self', `${source}; return self.MultiPagePhoneAuth;`)(globalScope);
+8 -8
View File
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadPhoneCountryUtils() {
const source = fs.readFileSync('content/phone-country-utils.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/phone-country-utils.js', 'utf8');
const root = {};
return new Function('self', `${source}; return self.MultiPagePhoneCountryUtils;`)(root);
}
@@ -43,21 +43,21 @@ test('phone country utils is loaded before phone auth content scripts', () => {
));
assert.ok(authScript, 'missing auth content script');
assert.ok(authScript.js.includes('content/phone-country-utils.js'));
assert.ok(authScript.js.includes('flows/openai/content/phone-country-utils.js'));
assert.ok(
authScript.js.indexOf('content/phone-country-utils.js') < authScript.js.indexOf('content/phone-auth.js'),
authScript.js.indexOf('flows/openai/content/phone-country-utils.js') < authScript.js.indexOf('flows/openai/content/phone-auth.js'),
'phone-country-utils.js must load before phone-auth.js'
);
assert.ok(
authScript.js.indexOf('content/phone-country-utils.js') < authScript.js.indexOf('content/signup-page.js'),
'phone-country-utils.js must load before signup-page.js'
authScript.js.indexOf('flows/openai/content/phone-country-utils.js') < authScript.js.indexOf('flows/openai/content/openai-auth.js'),
'phone-country-utils.js must load before openai-auth.js'
);
const background = fs.readFileSync('background.js', 'utf8');
const injectLine = background.match(/const\s+SIGNUP_PAGE_INJECT_FILES\s*=\s*\[[^\n]+\]/)?.[0] || '';
assert.ok(injectLine.includes("'content/phone-country-utils.js'"));
const injectLine = background.match(/const\s+OPENAI_AUTH_INJECT_FILES\s*=\s*\[[^\n]+\]/)?.[0] || '';
assert.ok(injectLine.includes("'flows/openai/content/phone-country-utils.js'"));
assert.ok(
injectLine.indexOf("'content/phone-country-utils.js'") < injectLine.indexOf("'content/phone-auth.js'"),
injectLine.indexOf("'flows/openai/content/phone-country-utils.js'") < injectLine.indexOf("'flows/openai/content/phone-auth.js'"),
'dynamic signup injection must load phone-country-utils.js before phone-auth.js'
);
});
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/platform-verify.js', 'utf8');
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
function createExecutor(overrides = {}) {
+7 -7
View File
@@ -82,7 +82,7 @@ function isGpcAutoModePermissionDenied() {
return false;
}
function getSelectedPanelMode() {
return latestState?.panelMode || 'cpa';
return latestState?.targetId || 'cpa';
}
function resolveCurrentSidepanelCapabilities() {
return ${capabilityStateSource};
@@ -141,7 +141,7 @@ test('sidepanel keeps requested Plus account strategy while contribution mode fo
}`,
`{
activeFlowId: 'openai',
panelMode: 'cpa',
targetId: 'cpa',
plusPaymentMethod: 'paypal',
accountContributionEnabled: true,
plusAccountAccessStrategy: 'cpa_codex_session',
@@ -168,7 +168,7 @@ test('sidepanel maps generic session import to SUB2API when the current source i
}`,
`{
activeFlowId: 'openai',
panelMode: 'sub2api',
targetId: 'sub2api',
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'sub2api_codex_session',
}`
@@ -190,12 +190,12 @@ test('sidepanel maps generic session import to CPA when the current source is CP
runtimeLocks: { plusModeEnabled: true },
canEditPlusAccountAccessStrategy: true,
availablePlusAccountAccessStrategies: ['oauth', 'cpa_codex_session'],
effectivePanelMode: 'cpa',
effectiveTargetId: 'cpa',
effectivePlusAccountAccessStrategy: 'cpa_codex_session',
}`,
`{
activeFlowId: 'openai',
panelMode: 'cpa',
targetId: 'cpa',
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'cpa_codex_session',
}`
@@ -217,12 +217,12 @@ test('sidepanel falls back to OAuth when the current source cannot import sessio
runtimeLocks: { plusModeEnabled: true },
canEditPlusAccountAccessStrategy: false,
availablePlusAccountAccessStrategies: ['oauth'],
effectivePanelMode: 'codex2api',
effectiveTargetId: 'codex2api',
effectivePlusAccountAccessStrategy: 'oauth',
}`,
`{
activeFlowId: 'openai',
panelMode: 'codex2api',
targetId: 'codex2api',
plusPaymentMethod: 'paypal',
plusAccountAccessStrategy: 'cpa_codex_session',
}`
+1 -1
View File
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
const vm = require('node:vm');
const source = fs.readFileSync('content/plus-checkout.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/plus-checkout.js', 'utf8');
test('plus checkout content script can be injected repeatedly on the same page', () => {
const attrs = new Map();
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
function loadPlusCheckoutBillingModule() {
const source = fs.readFileSync('background/steps/fill-plus-checkout.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/fill-plus-checkout.js', 'utf8');
const globalScope = {};
return new Function('self', `${source}; return self.MultiPageBackgroundPlusCheckoutBilling;`)(globalScope);
}
+2 -2
View File
@@ -3,8 +3,8 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
const vm = require('node:vm');
const source = fs.readFileSync('background/steps/create-plus-checkout.js', 'utf8');
const plusCheckoutSource = fs.readFileSync('content/plus-checkout.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/create-plus-checkout.js', 'utf8');
const plusCheckoutSource = fs.readFileSync('flows/openai/content/plus-checkout.js', 'utf8');
const gopayUtilsSource = fs.readFileSync('gopay-utils.js', 'utf8');
const globalScope = {};
new Function('self', `${gopayUtilsSource};`)(globalScope);
+1 -1
View File
@@ -2,7 +2,7 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('background/steps/plus-return-confirm.js', 'utf8');
const source = fs.readFileSync('flows/openai/background/steps/plus-return-confirm.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundPlusReturnConfirm;`)(globalScope);
@@ -67,8 +67,7 @@ const latestState = {
accountContributionEnabled: false,
activeFlowId: 'openai',
flowId: 'openai',
panelMode: 'cpa',
kiroTargetId: 'kiro-rs',
targetId: 'cpa',
};
const inputAutoSkipFailures = { checked: false };
const inputContributionNickname = { value: 'tester' };
@@ -79,7 +78,7 @@ const inputAutoDelayMinutes = { value: '30' };
const btnAutoRun = { disabled: false, innerHTML: '' };
const inputRunCount = { disabled: false };
const selectFlow = { value: latestState.activeFlowId };
const selectPanelMode = { value: latestState.panelMode };
const selectPanelMode = { value: latestState.targetId };
let runCountValue = ${Math.max(1, Number(runCount) || 1)};
let pendingAutoRunStartTotalRuns = 0;
let pendingAutoRunStartExpiresAt = 0;
@@ -121,8 +120,8 @@ function getSelectedFlowId(state = latestState) {
function getSelectedTargetId(flowId = getSelectedFlowId()) {
return String(
flowId === 'kiro'
? (selectPanelMode.value || latestState.kiroTargetId || 'kiro-rs')
: normalizePanelMode(selectPanelMode.value || latestState.panelMode || 'cpa')
? (selectPanelMode.value || latestState.targetId || 'kiro-rs')
: normalizePanelMode(selectPanelMode.value || latestState.targetId || 'cpa')
).trim().toLowerCase() || (flowId === 'kiro' ? 'kiro-rs' : 'cpa');
}
function shouldOfferAutoModeChoice() { return false; }
@@ -239,7 +238,7 @@ test('startAutoRunFromCurrentSettings sends current flow selection with auto run
selectPanelMode.value = 'kiro-rs';
latestState.activeFlowId = 'openai';
latestState.flowId = 'openai';
latestState.kiroTargetId = 'kiro-rs';
latestState.targetId = 'kiro-rs';
events.push({ type: 'flow-switch-race' });
}`,
});
@@ -264,7 +263,7 @@ test('startAutoRunFromCurrentSettings blocks when shared flow capability validat
const events = [];
const latestState = {
activeFlowId: 'site-a',
panelMode: 'cpa',
targetId: 'cpa',
signupMethod: 'phone',
accountContributionEnabled: false,
phoneVerificationEnabled: true,
@@ -2,9 +2,10 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const vm = require('node:vm');
const { readFlowRegistryBundle } = require('./helpers/script-bundles.js');
const source = fs.readFileSync('sidepanel/contribution-mode.js', 'utf8');
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
const flowRegistrySource = readFlowRegistryBundle();
const contributionRegistrySource = fs.readFileSync('shared/contribution-registry.js', 'utf8');
function createElement() {
+2 -1
View File
@@ -339,7 +339,8 @@ return {
api.setLatestState({ accountContributionEnabled: false });
const normalPayload = api.collectSettingsPayload();
assert.equal(normalPayload.panelMode, 'cpa');
assert.equal('panelMode' in normalPayload, false);
assert.equal(normalPayload.targetId, 'cpa');
assert.equal(normalPayload.customPassword, 'Secret123!');
assert.equal(normalPayload.accountRunHistoryTextEnabled, true);
assert.equal(normalPayload.accountRunHistoryHelperBaseUrl, 'http://127.0.0.1:17373');
+3 -3
View File
@@ -170,9 +170,11 @@ return {
activeFlowId: 'kiro',
flowId: 'kiro',
nodeStatuses: { 'open-chatgpt': 'completed' },
targetId: 'kiro-rs',
});
assert.equal(api.getCalls()[0].activeFlowId, 'kiro');
assert.equal(api.getCalls()[0].flowId, 'kiro');
assert.equal(api.getCalls()[0].targetId, 'kiro-rs');
});
test('updatePanelModeUI reapplies dynamic Plus and phone visibility after flow group visibility', () => {
@@ -185,7 +187,7 @@ const calls = [];
let latestState = {
activeFlowId: 'openai',
flowId: 'openai',
panelMode: 'cpa',
targetId: 'cpa',
};
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
const selectFlow = { value: '' };
@@ -220,8 +222,6 @@ function updatePhoneVerificationSettingsUI() {
function resolveCurrentSidepanelCapabilities() {
return {
visibleGroupIds: ['service-account', 'openai-plus', 'openai-phone'],
effectivePanelMode: 'cpa',
panelMode: 'cpa',
effectiveTargetId: 'cpa',
};
}
@@ -254,11 +254,11 @@ test('sidepanel source wires runtime signup phone field to background sync messa
test('sidepanel warns once before using phone signup with CPA source', async () => {
assert.match(
sidepanelSource,
/signupMethodButtons\.forEach\(\(button\) => \{[\s\S]*await confirmCpaPhoneSignupIfNeeded\(\{[\s\S]*signupMethod: nextSignupMethod,[\s\S]*panelMode: getSelectedPanelMode\(\),/
/signupMethodButtons\.forEach\(\(button\) => \{[\s\S]*await confirmCpaPhoneSignupIfNeeded\(\{[\s\S]*signupMethod: nextSignupMethod,[\s\S]*targetId: getSelectedPanelMode\(latestState\),/
);
assert.match(
sidepanelSource,
/selectPanelMode\.addEventListener\('change', async \(\) => \{[\s\S]*await confirmCpaPhoneSignupIfNeeded\(\{[\s\S]*signupMethod: getSelectedSignupMethod\(\),[\s\S]*panelMode: nextPanelMode,/
/selectPanelMode\.addEventListener\('change', async \(\) => \{[\s\S]*await confirmCpaPhoneSignupIfNeeded\(\{[\s\S]*signupMethod: getSelectedSignupMethod\(\),[\s\S]*targetId: nextPanelMode,/
);
const bundle = [
@@ -277,6 +277,7 @@ test('sidepanel warns once before using phone signup with CPA source', async ()
const SIGNUP_METHOD_PHONE = 'phone';
const SIGNUP_METHOD_EMAIL = 'email';
const DEFAULT_SIGNUP_METHOD = SIGNUP_METHOD_EMAIL;
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
const CPA_PHONE_SIGNUP_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-cpa-phone-signup-prompt-dismissed';
const CPA_PHONE_SIGNUP_WARNING_MESSAGE = '请确保打开手机接码设置中的“绑定后重登”开关,不然可能无法使用(有些版本无需开启)';
const storage = new Map();
@@ -291,6 +292,11 @@ const localStorage = {
storage.delete(key);
},
};
let latestState = {
activeFlowId: 'openai',
flowId: 'openai',
targetId: 'cpa',
};
let selectedSignupMethod = 'phone';
let selectedPanelMode = 'cpa';
let capturedOptions = null;
@@ -301,6 +307,12 @@ function getSelectedSignupMethod() {
function getSelectedPanelMode() {
return selectedPanelMode;
}
function getSelectedTargetId() {
return selectedPanelMode;
}
function getSelectedTargetIdForState() {
return selectedPanelMode;
}
async function openConfirmModalWithOption(options) {
capturedOptions = options;
return modalResult;
@@ -326,7 +338,7 @@ return {
assert.equal(api.shouldWarnCpaPhoneSignup('phone', 'sub2api'), false);
assert.equal(api.shouldWarnCpaPhoneSignup('phone', 'codex2api'), false);
const firstResult = await api.confirmCpaPhoneSignupIfNeeded({ signupMethod: 'phone', panelMode: 'cpa' });
const firstResult = await api.confirmCpaPhoneSignupIfNeeded({ signupMethod: 'phone', targetId: 'cpa' });
assert.equal(firstResult, true);
assert.equal(api.getCapturedOptions().title, 'CPA 手机号注册提醒');
assert.equal(api.getCapturedOptions().message, '请确保打开手机接码设置中的“绑定后重登”开关,不然可能无法使用(有些版本无需开启)');
@@ -335,7 +347,7 @@ return {
assert.equal(api.getDismissed(), null);
api.setModalResult({ confirmed: false, optionChecked: true });
const secondResult = await api.confirmCpaPhoneSignupIfNeeded({ signupMethod: 'phone', panelMode: 'cpa' });
const secondResult = await api.confirmCpaPhoneSignupIfNeeded({ signupMethod: 'phone', targetId: 'cpa' });
assert.equal(secondResult, false);
assert.equal(api.getDismissed(), '1');
assert.equal(api.shouldWarnCpaPhoneSignup('phone', 'cpa'), false);
@@ -354,11 +366,11 @@ const window = {
MultiPageFlowCapabilities: {
createFlowCapabilityRegistry() {
return {
resolveSidepanelCapabilities({ state = {}, panelMode = 'cpa', signupMethod = 'email' } = {}) {
resolveSidepanelCapabilities({ state = {}, targetId = 'cpa', signupMethod = 'email' } = {}) {
const phoneAllowed = String(state?.activeFlowId || '').trim().toLowerCase() === 'openai';
return {
canSelectPhoneSignup: phoneAllowed,
shouldWarnCpaPhoneSignup: phoneAllowed && signupMethod === 'phone' && panelMode === 'cpa',
shouldWarnCpaPhoneSignup: phoneAllowed && signupMethod === 'phone' && targetId === 'cpa',
};
},
};
@@ -368,11 +380,14 @@ const window = {
let latestState = {
activeFlowId: 'site-a',
accountContributionEnabled: false,
panelMode: 'cpa',
targetId: 'cpa',
};
const inputPhoneVerificationEnabled = { checked: true };
const inputPlusModeEnabled = { checked: false };
function getSelectedFlowId() { return latestState.activeFlowId; }
function getSelectedPanelMode() { return 'cpa'; }
function getSelectedTargetId() { return 'cpa'; }
function getSelectedTargetIdForState() { return 'cpa'; }
function getSelectedSignupMethod() { return 'phone'; }
function isCpaPhoneSignupPromptDismissed() { return false; }
${bundle}
@@ -1059,6 +1074,7 @@ function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
function normalizeAutoStepDelaySeconds(value) { return value === '' ? null : Number(value); }
function normalizeVerificationResendCount(value, fallback) { return Number(value) || fallback; }
function normalizePlusAccountAccessStrategy(value = '') { return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION : PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; }
function resolvePlusAccountAccessStrategyForTarget(value = '') { return normalizePlusAccountAccessStrategy(value); }
${extractFunction('normalizePhoneSmsProvider')}
${extractFunction('normalizePhoneSmsProviderValue')}
${extractFunction('normalizeFiveSimCountryCode')}
@@ -1,10 +1,11 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { readFlowRegistryBundle } = require('./helpers/script-bundles.js');
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
const source = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
const flowRegistrySource = readFlowRegistryBundle();
test('sidepanel exposes SUB2API account priority below group setting', () => {
assert.match(html, /id="row-sub2api-account-priority"/);
@@ -25,6 +26,10 @@ test('sidepanel exposes SUB2API account priority below group setting', () => {
});
test('sidepanel persists and locks SUB2API account priority setting', () => {
const flowRegistryApi = new Function(
'self',
`${flowRegistrySource}; return self.MultiPageFlowRegistry;`
)({});
assert.match(
source,
/const rowSub2ApiAccountPriority = document\.getElementById\('row-sub2api-account-priority'\);/
@@ -41,7 +46,10 @@ test('sidepanel persists and locks SUB2API account priority setting', () => {
/inputSub2ApiAccountPriority\.value = String\(normalizeSub2ApiAccountPriorityValue\(state\?\.sub2apiAccountPriority\)\);/
);
assert.match(source, /applyFlowSettingsGroupVisibility\(visibleGroupIds\);/);
assert.match(flowRegistrySource, /'openai-target-sub2api': \{[\s\S]*'row-sub2api-account-priority'/);
assert.deepStrictEqual(
flowRegistryApi.getSettingsGroupDefinition('openai-target-sub2api')?.rowIds?.includes('row-sub2api-account-priority'),
true
);
assert.match(source, /inputSub2ApiAccountPriority\.disabled = locked;/);
assert.match(
source,
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+20 -20
View File
@@ -1,20 +1,19 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { readSourceRegistryBundle } = require('./helpers/script-bundles.js');
function loadSourceRegistry() {
const flowRegistrySource = fs.readFileSync('shared/flow-registry.js', 'utf8');
const sourceRegistrySource = fs.readFileSync('shared/source-registry.js', 'utf8');
const globalScope = {};
new Function('self', `${flowRegistrySource}; ${sourceRegistrySource}; return self;`)(globalScope);
new Function('self', `${readSourceRegistryBundle()}; return self;`)(globalScope);
return globalScope.MultiPageSourceRegistry.createSourceRegistry();
}
test('background imports shared source registry module', () => {
const source = fs.readFileSync('background.js', 'utf8');
assert.match(source, /shared\/flow-registry\.js/);
assert.match(source, /shared\/settings-schema\.js/);
assert.match(source, /shared\/source-registry\.js/);
assert.match(source, /core\/flow-kernel\/flow-registry\.js/);
assert.match(source, /core\/flow-kernel\/settings-schema\.js/);
assert.match(source, /core\/flow-kernel\/source-registry\.js/);
assert.match(source, /shared\/kiro-timeouts\.js/);
});
@@ -23,10 +22,10 @@ test('manifest loads shared source registry before content utils in static bundl
for (const entry of manifest.content_scripts || []) {
const scripts = Array.isArray(entry.js) ? entry.js : [];
if (!scripts.includes('content/utils.js')) continue;
assert.ok(scripts.includes('shared/source-registry.js'));
assert.ok(scripts.includes('core/flow-kernel/source-registry.js'));
assert.ok(
scripts.indexOf('shared/source-registry.js') < scripts.indexOf('content/utils.js'),
'shared/source-registry.js must load before content/utils.js'
scripts.indexOf('core/flow-kernel/source-registry.js') < scripts.indexOf('content/utils.js'),
'core/flow-kernel/source-registry.js must load before content/utils.js'
);
}
});
@@ -35,8 +34,8 @@ test('manifest no longer ships a static Kiro content bundle', () => {
const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8'));
const hasStaticKiroBundle = (manifest.content_scripts || []).some((entry) => {
const scripts = Array.isArray(entry.js) ? entry.js : [];
return scripts.includes('content/kiro/register-page.js')
|| scripts.includes('content/kiro/desktop-authorize-page.js');
return scripts.includes('flows/kiro/content/register-page.js')
|| scripts.includes('flows/kiro/content/desktop-authorize-page.js');
});
assert.equal(hasStaticKiroBundle, false);
@@ -46,19 +45,20 @@ test('background injects shared Kiro timeout module before Kiro content scripts'
const source = fs.readFileSync('background.js', 'utf8');
assert.match(
source,
/const KIRO_REGISTER_INJECT_FILES = \['shared\/source-registry\.js', 'shared\/kiro-timeouts\.js', 'content\/utils\.js', 'content\/kiro\/register-page\.js'\];/
/const KIRO_REGISTER_INJECT_FILES = \['flows\/openai\/index\.js', 'flows\/kiro\/index\.js', 'flows\/index\.js', 'core\/flow-kernel\/flow-registry\.js', 'core\/flow-kernel\/source-registry\.js', 'shared\/kiro-timeouts\.js', 'content\/utils\.js', 'flows\/kiro\/content\/register-page\.js'\];/
);
assert.match(
source,
/const KIRO_DESKTOP_AUTHORIZE_INJECT_FILES = \['shared\/source-registry\.js', 'shared\/kiro-timeouts\.js', 'content\/utils\.js', 'content\/kiro\/desktop-authorize-page\.js'\];/
/const KIRO_DESKTOP_AUTHORIZE_INJECT_FILES = \['flows\/openai\/index\.js', 'flows\/kiro\/index\.js', 'flows\/index\.js', 'core\/flow-kernel\/flow-registry\.js', 'core\/flow-kernel\/source-registry\.js', 'shared\/kiro-timeouts\.js', 'content\/utils\.js', 'flows\/kiro\/content\/desktop-authorize-page\.js'\];/
);
});
test('shared source registry exposes canonical Kiro sources and drivers', () => {
const registry = loadSourceRegistry();
assert.equal(registry.resolveCanonicalSource('signup-page'), 'openai-auth');
assert.deepEqual(registry.getSourceKeys('signup-page'), ['openai-auth', 'signup-page']);
assert.equal(registry.resolveCanonicalSource('openai-auth'), 'openai-auth');
assert.deepEqual(registry.getSourceKeys('openai-auth'), ['openai-auth']);
assert.equal(registry.resolveCanonicalSource('signup-page'), 'signup-page');
assert.equal(registry.getSourceLabel('openai-auth'), '认证页');
assert.equal(
@@ -132,9 +132,9 @@ test('shared source registry exposes canonical Kiro sources and drivers', () =>
assert.equal(registry.driverAcceptsCommand('openai-auth', 'submit-signup-email'), true);
assert.equal(registry.driverAcceptsCommand('content/platform-panel', 'platform-verify'), true);
assert.equal(registry.driverAcceptsCommand('openai-auth', 'platform-verify'), false);
assert.equal(registry.driverAcceptsCommand('content/kiro/register-page', 'kiro-submit-password'), true);
assert.equal(registry.driverAcceptsCommand('content/kiro/desktop-authorize-page', 'kiro-complete-desktop-authorize'), true);
assert.equal(registry.driverAcceptsCommand('background/kiro-register', 'kiro-open-register-page'), true);
assert.equal(registry.driverAcceptsCommand('background/kiro-desktop-authorize', 'kiro-start-desktop-authorize'), true);
assert.equal(registry.driverAcceptsCommand('background/kiro-publisher-kiro-rs', 'kiro-upload-credential'), true);
assert.equal(registry.driverAcceptsCommand('flows/kiro/content/register-page', 'kiro-submit-password'), true);
assert.equal(registry.driverAcceptsCommand('flows/kiro/content/desktop-authorize-page', 'kiro-complete-desktop-authorize'), true);
assert.equal(registry.driverAcceptsCommand('flows/kiro/background/register-runner', 'kiro-open-register-page'), true);
assert.equal(registry.driverAcceptsCommand('flows/kiro/background/desktop-authorize-runner', 'kiro-start-desktop-authorize'), true);
assert.equal(registry.driverAcceptsCommand('flows/kiro/background/publisher-kiro-rs', 'kiro-upload-credential'), true);
});
+6 -10
View File
@@ -1,12 +1,11 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { readStepDefinitionsBundle } = require('./helpers/script-bundles.js');
test('step definitions module exposes ordered normal and Plus step metadata', () => {
const source = fs.readFileSync('data/step-definitions.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageStepDefinitions;`)(globalScope);
const api = new Function('self', `${readStepDefinitionsBundle()}; return self.MultiPageStepDefinitions;`)(globalScope);
const steps = api.getSteps();
const phoneSteps = api.getSteps({ signupMethod: 'phone' });
const phoneReloginSteps = api.getSteps({
@@ -180,7 +179,7 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
]
);
assert.equal(kiroSteps.every((step) => step.flowId === 'kiro'), true);
assert.equal(kiroSteps[0].driverId, 'background/kiro-register');
assert.equal(kiroSteps[0].driverId, 'flows/kiro/background/register-runner');
assert.equal(kiroSteps[8].sourceId, 'kiro-rs-admin');
assert.equal(kiroSteps[0].title, '打开注册页');
assert.equal(kiroSteps[1].title, '获取邮箱并继续');
@@ -286,9 +285,8 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
});
test('Plus session strategy swaps the OAuth tail for a single SUB2API import node', () => {
const source = fs.readFileSync('data/step-definitions.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageStepDefinitions;`)(globalScope);
const api = new Function('self', `${readStepDefinitionsBundle()}; return self.MultiPageStepDefinitions;`)(globalScope);
const forbiddenTailKeys = [
'oauth-login',
'fetch-login-code',
@@ -360,9 +358,8 @@ test('Plus session strategy swaps the OAuth tail for a single SUB2API import nod
});
test('Plus phone signup never switches to SUB2API session tail even if the requested strategy is session import', () => {
const source = fs.readFileSync('data/step-definitions.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageStepDefinitions;`)(globalScope);
const api = new Function('self', `${readStepDefinitionsBundle()}; return self.MultiPageStepDefinitions;`)(globalScope);
const steps = api.getSteps({
plusModeEnabled: true,
plusPaymentMethod: 'paypal',
@@ -377,9 +374,8 @@ test('Plus phone signup never switches to SUB2API session tail even if the reque
});
test('Plus session strategy swaps the OAuth tail for a single CPA import node', () => {
const source = fs.readFileSync('data/step-definitions.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageStepDefinitions;`)(globalScope);
const api = new Function('self', `${readStepDefinitionsBundle()}; return self.MultiPageStepDefinitions;`)(globalScope);
const forbiddenTailKeys = [
'oauth-login',
'fetch-login-code',
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -1,7 +1,7 @@
const assert = require('assert');
const fs = require('fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+2 -2
View File
@@ -2,8 +2,8 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const phoneAuthSource = fs.readFileSync('content/phone-auth.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
const phoneAuthSource = fs.readFileSync('flows/openai/content/phone-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -3,7 +3,7 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
const backgroundSource = fs.readFileSync('background.js', 'utf8');
const step8Source = fs.readFileSync('background/steps/fetch-login-code.js', 'utf8');
const step8Source = fs.readFileSync('flows/openai/background/steps/fetch-login-code.js', 'utf8');
const step8GlobalScope = {};
const step8Api = new Function('self', `${step8Source}; return self.MultiPageBackgroundStep8;`)(step8GlobalScope);
+2 -2
View File
@@ -34,13 +34,13 @@ const bundle = [
const api = new Function(`${bundle}; return { isRetryableContentScriptTransportError };`)();
assert.strictEqual(
api.isRetryableContentScriptTransportError(new Error('Content script on signup-page did not respond in 2s. Try refreshing the tab and retry.')),
api.isRetryableContentScriptTransportError(new Error('Content script on openai-auth did not respond in 2s. Try refreshing the tab and retry.')),
true,
'Step 8 状态探测短超时应被视为可重试错误'
);
assert.strictEqual(
api.isRetryableContentScriptTransportError(new Error('Content script on signup-page did not respond in 30s. Try refreshing the tab and retry.')),
api.isRetryableContentScriptTransportError(new Error('Content script on openai-auth did not respond in 30s. Try refreshing the tab and retry.')),
true,
'普通内容脚本超时也应沿用可重试分支'
);
+3 -3
View File
@@ -2,7 +2,7 @@
const fs = require('fs');
const helperSource = fs.readFileSync('background.js', 'utf8');
const step8ModuleSource = fs.readFileSync('background/steps/confirm-oauth.js', 'utf8');
const step8ModuleSource = fs.readFileSync('flows/openai/background/steps/confirm-oauth.js', 'utf8');
function extractFunction(source, name) {
const markers = [`async function ${name}(`, `function ${name}(`];
@@ -178,11 +178,11 @@ async function isTabAlive() {
}
async function ensureStep8SignupPageReady() {}
async function prepareStep8DebuggerClick() {
sentMessages.push({ source: 'signup-page', type: 'STEP8_FIND_AND_CLICK' });
sentMessages.push({ source: 'openai-auth', type: 'STEP8_FIND_AND_CLICK' });
return { rect: { centerX: 10, centerY: 20 } };
}
async function triggerStep8ContentStrategy() {
sentMessages.push({ source: 'signup-page', type: 'STEP8_TRIGGER_CONTINUE' });
sentMessages.push({ source: 'openai-auth', type: 'STEP8_TRIGGER_CONTINUE' });
}
async function waitForStep8ClickEffect() {
return { progressed: false, reason: 'no_effect' };
+4 -4
View File
@@ -3,7 +3,7 @@ const fs = require('fs');
const helperSource = fs.readFileSync('background.js', 'utf8');
const messageRouterSource = fs.readFileSync('background/message-router.js', 'utf8');
const tabRuntimeSource = fs.readFileSync('background/tab-runtime.js', 'utf8');
const tabRuntimeSource = fs.readFileSync('core/flow-kernel/tab-runtime.js', 'utf8');
function extractFunction(source, name) {
const markers = [`async function ${name}(`, `function ${name}(`];
@@ -68,7 +68,7 @@ const MAIL_2925_MODE_RECEIVE = 'receive';
const DEFAULT_MAIL_2925_MODE = MAIL_2925_MODE_PROVIDE;
let currentState = {
tabRegistry: {
'signup-page': { tabId: 1, ready: true },
'openai-auth': { tabId: 1, ready: true },
'vps-panel': { tabId: 99, ready: true },
},
};
@@ -205,7 +205,7 @@ return {
{ id: 4, url: authCallbackUrl },
],
tabRegistry: {
'signup-page': { tabId: 1, ready: true },
'openai-auth': { tabId: 1, ready: true },
'vps-panel': { tabId: 99, ready: true },
},
});
@@ -213,7 +213,7 @@ return {
await api.handleStepData(10, { localhostUrl: codexCallbackUrl });
let snapshot = api.snapshot();
assert.deepStrictEqual(snapshot.removedBatches, [[1], [2]]);
assert.strictEqual(snapshot.currentState.tabRegistry['signup-page'], null);
assert.strictEqual(snapshot.currentState.tabRegistry['openai-auth'], null);
assert.deepStrictEqual(snapshot.currentState.tabRegistry['vps-panel'], { tabId: 99, ready: true });
api.reset({
+1 -1
View File
@@ -2,7 +2,7 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const step9ModuleSource = fs.readFileSync('background/steps/confirm-oauth.js', 'utf8');
const step9ModuleSource = fs.readFileSync('flows/openai/background/steps/confirm-oauth.js', 'utf8');
test('step9 observes disabled oauth timeout while waiting for localhost callback', async () => {
const api = new Function('step9ModuleSource', `
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/signup-page.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/openai-auth.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -2,7 +2,7 @@ const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('content/vps-panel.js', 'utf8');
const source = fs.readFileSync('flows/openai/content/vps-panel.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
+1 -1
View File
@@ -1,7 +1,7 @@
const assert = require('assert');
const fs = require('fs');
const step9ModuleSource = fs.readFileSync('background/steps/confirm-oauth.js', 'utf8');
const step9ModuleSource = fs.readFileSync('flows/openai/background/steps/confirm-oauth.js', 'utf8');
const api = new Function('step9ModuleSource', `
const self = {};
+1 -1
View File
@@ -131,7 +131,7 @@ function createSub2ApiPanelContext(fetchCalls = []) {
};
vm.createContext(context);
vm.runInContext(fs.readFileSync('content/sub2api-panel.js', 'utf8'), context);
vm.runInContext(fs.readFileSync('flows/openai/content/sub2api-panel.js', 'utf8'), context);
return context;
}
+1 -1
View File
@@ -1569,7 +1569,7 @@ test('verification flow notifies onResendRequestedAt when resend is triggered',
);
});
test('verification flow uses resilient signup-page transport when submitting verification code', async () => {
test('verification flow uses resilient openai-auth transport when submitting verification code', async () => {
const resilientCalls = [];
const helpers = api.createVerificationFlowHelpers({