diff --git a/background.js b/background.js index 82021f6..2dec324 100644 --- a/background.js +++ b/background.js @@ -1,11 +1,17 @@ -// background.js — Service Worker: orchestration, state, tab management, message routing +// background.js — Service Worker: orchestration, state, tab management, message routing importScripts( - 'shared/flow-registry.js', + 'flows/openai/index.js', + 'flows/openai/workflow.js', + 'flows/kiro/index.js', + 'flows/kiro/workflow.js', + 'flows/index.js', + 'core/flow-kernel/flow-registry.js', 'shared/contribution-registry.js', - 'shared/settings-schema.js', - 'shared/source-registry.js', - 'shared/flow-capabilities.js', + 'core/flow-kernel/settings-schema.js', + 'imports/legacy/settings-importer.js', + 'core/flow-kernel/source-registry.js', + 'core/flow-kernel/flow-capabilities.js', 'shared/kiro-timeouts.js', 'managed-alias-utils.js', 'mail2925-utils.js', @@ -25,15 +31,15 @@ importScripts( 'background/cpa-api.js', 'background/panel-bridge.js', 'background/registration-email-state.js', - 'background/workflow-engine.js', - 'background/runtime-state.js', - 'background/kiro/state.js', - 'background/kiro/credential-artifact.js', + 'core/flow-kernel/workflow-engine.js', + 'core/flow-kernel/runtime-state.js', + 'flows/kiro/background/state.js', + 'flows/kiro/background/credential-artifact.js', 'background/contribution/adapters/kiro-builder-id.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/register-runner.js', + 'flows/kiro/background/desktop-client.js', + 'flows/kiro/background/desktop-authorize-runner.js', + 'flows/kiro/background/publisher-kiro-rs.js', 'background/generated-email-helpers.js', 'background/signup-flow-helpers.js', 'background/mail-rule-registry.js', @@ -41,30 +47,30 @@ importScripts( 'background/message-router.js', 'background/verification-flow.js', 'background/auto-run-controller.js', - 'background/tab-runtime.js', + 'core/flow-kernel/tab-runtime.js', 'background/navigation-utils.js', - 'background/logging-status.js', - 'background/steps/registry.js', + 'core/flow-kernel/logging-status.js', + 'core/flow-kernel/step-registry.js', 'data/step-definitions.js', 'data/address-sources.js', - '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/create-plus-checkout.js', - 'background/steps/fill-plus-checkout.js', - 'background/steps/gopay-manual-confirm.js', - 'background/steps/paypal-approve.js', - 'background/steps/gopay-approve.js', - 'background/steps/plus-return-confirm.js', - 'background/steps/sub2api-session-import.js', - 'background/steps/cpa-session-import.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/create-plus-checkout.js', + 'flows/openai/background/steps/fill-plus-checkout.js', + 'flows/openai/background/steps/gopay-manual-confirm.js', + 'flows/openai/background/steps/paypal-approve.js', + 'flows/openai/background/steps/gopay-approve.js', + 'flows/openai/background/steps/plus-return-confirm.js', + 'flows/openai/background/steps/sub2api-session-import.js', + 'flows/openai/background/steps/cpa-session-import.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', 'data/names.js', 'hotmail-utils.js', 'microsoft-email.js', @@ -498,15 +504,6 @@ function buildStatePatchWithRuntimeState(currentState = {}, updates = {}) { if (runtimeStateHelpers?.buildSessionStatePatch) { nextPatch = runtimeStateHelpers.buildSessionStatePatch(currentState, nextPatch); } - if (kiroStateHelpers?.buildSessionStatePatch) { - const kiroPatch = kiroStateHelpers.buildSessionStatePatch(currentState, updates); - if (kiroPatch && Object.keys(kiroPatch).length > 0) { - nextPatch = { - ...nextPatch, - ...kiroPatch, - }; - } - } return nextPatch; } @@ -894,7 +891,7 @@ function buildResolvedStepDefinitionState(state = {}) { signupMethod: requestedSignupMethod, }, { activeFlowId: requestedActiveFlowId, - panelMode: state?.panelMode, + targetId: state?.targetId, signupMethod: requestedSignupMethod, }) : null; @@ -910,7 +907,6 @@ function buildResolvedStepDefinitionState(state = {}) { ...state, activeFlowId: resolvedActiveFlowId, flowId: resolvedActiveFlowId, - panelMode: stepDefinitionOptions.panelMode || capabilityState?.effectivePanelMode || state?.panelMode, targetId: stepDefinitionOptions.targetId || capabilityState?.effectiveTargetId || state?.targetId, plusModeEnabled: stepDefinitionOptions.plusModeEnabled === undefined ? plusModeEnabled @@ -1178,9 +1174,8 @@ function setupDeclarativeNetRequestRules() { // ============================================================ const PERSISTED_SETTING_DEFAULTS = { - panelMode: 'cpa', + targetId: 'cpa', activeFlowId: DEFAULT_ACTIVE_FLOW_ID, - kiroTargetId: 'kiro-rs', kiroRsUrl: String(self.MultiPageFlowRegistry?.DEFAULT_KIRO_RS_URL || '').trim(), kiroRsKey: '', vpsUrl: '', @@ -1371,9 +1366,7 @@ const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS); const PERSISTED_SETTINGS_SCHEMA_KEYS = ['settingsSchemaVersion', 'settingsState']; const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([ 'activeFlowId', - 'openaiIntegrationTargetId', - 'panelMode', - 'kiroTargetId', + 'targetId', 'vpsUrl', 'vpsPassword', 'localCpaStep9Mode', @@ -1417,7 +1410,6 @@ const DEFAULT_STATE = { currentNodeId: '', nodeStatuses: { ...DEFAULT_NODE_STATUSES }, runtimeState: runtimeStateHelpers?.buildDefaultRuntimeState?.() || null, - kiroRuntime: kiroStateHelpers?.buildDefaultRuntimeState?.() || null, ...CONTRIBUTION_RUNTIME_DEFAULTS, accounts: [], // 已生成账号记录:{ email, password, createdAt }。 accountRunHistory: [], // 账号运行历史快照,实际持久化在 chrome.storage.local。 @@ -1819,7 +1811,7 @@ function resolveCurrentFlowCapabilities(state = {}, options = {}) { } return registry.resolveSidepanelCapabilities({ activeFlowId: options?.activeFlowId ?? state?.activeFlowId, - panelMode: options?.panelMode ?? state?.panelMode, + targetId: options?.targetId ?? state?.targetId, signupMethod: options?.signupMethod ?? state?.signupMethod, state, }); @@ -1832,7 +1824,7 @@ function validateAutoRunStartState(state = {}, options = {}) { } return registry.validateAutoRunStart({ activeFlowId: options?.activeFlowId ?? state?.activeFlowId, - panelMode: options?.panelMode ?? state?.panelMode, + targetId: options?.targetId ?? state?.targetId, signupMethod: options?.signupMethod ?? state?.signupMethod, state, }); @@ -1851,7 +1843,7 @@ function validateModeSwitchState(state = {}, options = {}) { return registry.validateModeSwitch({ activeFlowId: options?.activeFlowId ?? state?.activeFlowId, changedKeys: options?.changedKeys, - panelMode: options?.panelMode ?? state?.panelMode, + targetId: options?.targetId ?? state?.targetId, signupMethod: options?.signupMethod ?? state?.signupMethod, state, }); @@ -1868,7 +1860,7 @@ function canUsePhoneSignup(state = {}) { return registry?.resolveSidepanelCapabilities ? registry.resolveSidepanelCapabilities({ activeFlowId: state?.activeFlowId, - panelMode: state?.panelMode, + targetId: state?.targetId, signupMethod: state?.signupMethod, state, }) @@ -1898,7 +1890,7 @@ function resolveSignupMethod(state = {}) { return registry?.resolveSidepanelCapabilities ? registry.resolveSidepanelCapabilities({ activeFlowId: state?.activeFlowId, - panelMode: state?.panelMode, + targetId: state?.targetId, signupMethod: method, state, }) @@ -3049,18 +3041,13 @@ function normalizeStepExecutionRangeByFlow(value = {}) { function normalizePersistentSettingValue(key, value) { switch (key) { - case 'panelMode': - return normalizePanelMode(value); + case 'targetId': + return String(value || '').trim().toLowerCase(); case 'activeFlowId': if (typeof self.MultiPageFlowRegistry?.normalizeFlowId === 'function') { return self.MultiPageFlowRegistry.normalizeFlowId(value, DEFAULT_ACTIVE_FLOW_ID); } return String(value || '').trim().toLowerCase() === 'kiro' ? 'kiro' : DEFAULT_ACTIVE_FLOW_ID; - case 'kiroTargetId': - if (typeof self.MultiPageFlowRegistry?.normalizeTargetId === 'function') { - return self.MultiPageFlowRegistry.normalizeTargetId('kiro', value, 'kiro-rs'); - } - return String(value || '').trim().toLowerCase() === 'kiro-rs' ? 'kiro-rs' : 'kiro-rs'; case 'kiroRsUrl': return String(value || '').trim(); case 'kiroRsKey': @@ -3575,7 +3562,7 @@ function buildPersistentSettingsPayload(input = {}, options = {}) { if (Object.prototype.hasOwnProperty.call(payload, 'phoneVerificationEnabled') || Object.prototype.hasOwnProperty.call(payload, 'plusModeEnabled') || Object.prototype.hasOwnProperty.call(payload, 'signupMethod') - || Object.prototype.hasOwnProperty.call(payload, 'panelMode') + || Object.prototype.hasOwnProperty.call(payload, 'targetId') || Object.prototype.hasOwnProperty.call(payload, 'activeFlowId')) { payload.signupMethod = resolveSignupMethod(nextSignupConstraintState); } @@ -3689,6 +3676,13 @@ function mergeSettingsStatePatch(baseValue = {}, patchValue = {}) { function buildSettingsStatePatchFromFlatUpdates(updates = {}) { const patch = {}; const hasUpdate = (key) => Object.prototype.hasOwnProperty.call(updates, key); + const normalizePatchFlowId = (value = '', fallback = DEFAULT_ACTIVE_FLOW_ID) => { + if (typeof self.MultiPageFlowRegistry?.normalizeFlowId === 'function') { + return self.MultiPageFlowRegistry.normalizeFlowId(value, fallback); + } + const normalized = String(value || '').trim().toLowerCase(); + return normalized || String(fallback || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID; + }; const assignIfUpdated = (key, path) => { if (hasUpdate(key)) { setSettingsStatePatchValue(patch, path, updates[key]); @@ -3696,26 +3690,29 @@ function buildSettingsStatePatchFromFlatUpdates(updates = {}) { }; assignIfUpdated('activeFlowId', ['activeFlowId']); - if (hasUpdate('openaiIntegrationTargetId') || hasUpdate('panelMode')) { + if (hasUpdate('selectedTargetId') || hasUpdate('targetId')) { + const flowId = normalizePatchFlowId( + updates.activeFlowId ?? updates.flowId, + DEFAULT_ACTIVE_FLOW_ID + ); setSettingsStatePatchValue( patch, - ['flows', 'openai', 'integrationTargetId'], - hasUpdate('openaiIntegrationTargetId') ? updates.openaiIntegrationTargetId : updates.panelMode + ['flows', flowId, 'selectedTargetId'], + hasUpdate('selectedTargetId') ? updates.selectedTargetId : updates.targetId ); } - assignIfUpdated('kiroTargetId', ['flows', 'kiro', 'targetId']); - assignIfUpdated('vpsUrl', ['flows', 'openai', 'integrationTargets', 'cpa', 'vpsUrl']); - assignIfUpdated('vpsPassword', ['flows', 'openai', 'integrationTargets', 'cpa', 'vpsPassword']); - assignIfUpdated('localCpaStep9Mode', ['flows', 'openai', 'integrationTargets', 'cpa', 'localCpaStep9Mode']); - assignIfUpdated('sub2apiUrl', ['flows', 'openai', 'integrationTargets', 'sub2api', 'sub2apiUrl']); - assignIfUpdated('sub2apiEmail', ['flows', 'openai', 'integrationTargets', 'sub2api', 'sub2apiEmail']); - assignIfUpdated('sub2apiPassword', ['flows', 'openai', 'integrationTargets', 'sub2api', 'sub2apiPassword']); - assignIfUpdated('sub2apiGroupName', ['flows', 'openai', 'integrationTargets', 'sub2api', 'sub2apiGroupName']); - assignIfUpdated('sub2apiGroupNames', ['flows', 'openai', 'integrationTargets', 'sub2api', 'sub2apiGroupNames']); - assignIfUpdated('sub2apiAccountPriority', ['flows', 'openai', 'integrationTargets', 'sub2api', 'sub2apiAccountPriority']); - assignIfUpdated('sub2apiDefaultProxyName', ['flows', 'openai', 'integrationTargets', 'sub2api', 'sub2apiDefaultProxyName']); - assignIfUpdated('codex2apiUrl', ['flows', 'openai', 'integrationTargets', 'codex2api', 'codex2apiUrl']); - assignIfUpdated('codex2apiAdminKey', ['flows', 'openai', 'integrationTargets', 'codex2api', 'codex2apiAdminKey']); + assignIfUpdated('vpsUrl', ['flows', 'openai', 'targets', 'cpa', 'vpsUrl']); + assignIfUpdated('vpsPassword', ['flows', 'openai', 'targets', 'cpa', 'vpsPassword']); + assignIfUpdated('localCpaStep9Mode', ['flows', 'openai', 'targets', 'cpa', 'localCpaStep9Mode']); + assignIfUpdated('sub2apiUrl', ['flows', 'openai', 'targets', 'sub2api', 'sub2apiUrl']); + assignIfUpdated('sub2apiEmail', ['flows', 'openai', 'targets', 'sub2api', 'sub2apiEmail']); + assignIfUpdated('sub2apiPassword', ['flows', 'openai', 'targets', 'sub2api', 'sub2apiPassword']); + assignIfUpdated('sub2apiGroupName', ['flows', 'openai', 'targets', 'sub2api', 'sub2apiGroupName']); + assignIfUpdated('sub2apiGroupNames', ['flows', 'openai', 'targets', 'sub2api', 'sub2apiGroupNames']); + assignIfUpdated('sub2apiAccountPriority', ['flows', 'openai', 'targets', 'sub2api', 'sub2apiAccountPriority']); + assignIfUpdated('sub2apiDefaultProxyName', ['flows', 'openai', 'targets', 'sub2api', 'sub2apiDefaultProxyName']); + assignIfUpdated('codex2apiUrl', ['flows', 'openai', 'targets', 'codex2api', 'codex2apiUrl']); + assignIfUpdated('codex2apiAdminKey', ['flows', 'openai', 'targets', 'codex2api', 'codex2apiAdminKey']); assignIfUpdated('customPassword', ['services', 'account', 'customPassword']); assignIfUpdated('signupMethod', ['flows', 'openai', 'signup', 'signupMethod']); assignIfUpdated('phoneVerificationEnabled', ['flows', 'openai', 'signup', 'phoneVerificationEnabled']); @@ -3850,6 +3847,18 @@ function buildAutoRunFreshResetSettingsState(prevState = {}, activeFlowId = DEFA const currentSettingsState = isPlainObjectValue(prevState?.settingsState) ? prevState.settingsState : {}; + const settingsSchemaApi = typeof getSettingsSchemaApi === 'function' + ? getSettingsSchemaApi() + : null; + const normalizedCurrentSettingsState = settingsSchemaApi?.normalizeSettingsState + ? settingsSchemaApi.normalizeSettingsState({ + ...prevState, + activeFlowId, + settingsState: currentSettingsState, + }, { + activeFlowId, + }) + : currentSettingsState; const normalizedStepExecutionRangeByFlow = normalizeStepExecutionRangeByFlow(prevState?.stepExecutionRangeByFlow || {}); const nextSettingsStatePatch = { activeFlowId, @@ -3868,7 +3877,9 @@ function buildAutoRunFreshResetSettingsState(prevState = {}, activeFlowId = DEFA }, flows: { openai: { - integrationTargetId: prevState?.openaiIntegrationTargetId || prevState?.panelMode, + selectedTargetId: settingsSchemaApi?.getSelectedTargetId + ? settingsSchemaApi.getSelectedTargetId(normalizedCurrentSettingsState, 'openai') + : undefined, autoRun: normalizedStepExecutionRangeByFlow.openai ? { stepExecutionRange: normalizedStepExecutionRangeByFlow.openai, @@ -3876,7 +3887,9 @@ function buildAutoRunFreshResetSettingsState(prevState = {}, activeFlowId = DEFA : undefined, }, kiro: { - targetId: prevState?.kiroTargetId, + selectedTargetId: settingsSchemaApi?.getSelectedTargetId + ? settingsSchemaApi.getSelectedTargetId(normalizedCurrentSettingsState, 'kiro') + : undefined, autoRun: normalizedStepExecutionRangeByFlow.kiro ? { stepExecutionRange: normalizedStepExecutionRangeByFlow.kiro, @@ -3918,15 +3931,14 @@ function buildFreshAutoRunKeepState(prevState = {}) { keepState.activeFlowId = activeFlowId; keepState.flowId = activeFlowId; - if (Object.prototype.hasOwnProperty.call(sourceState, 'panelMode')) { - keepState.panelMode = normalizePanelMode(sourceState.panelMode); + const settingsSchemaApi = typeof getSettingsSchemaApi === 'function' + ? getSettingsSchemaApi() + : null; + if (settingsSchemaApi?.getSelectedTargetId) { + keepState.targetId = settingsSchemaApi.getSelectedTargetId(settingsState, activeFlowId); } if (typeof kiroStateHelpers?.buildFreshKeepState === 'function') { Object.assign(keepState, kiroStateHelpers.buildFreshKeepState(sourceState)); - } else if (Object.prototype.hasOwnProperty.call(sourceState, 'kiroTargetId')) { - keepState.kiroTargetId = self.MultiPageFlowRegistry?.normalizeTargetId - ? self.MultiPageFlowRegistry.normalizeTargetId('kiro', sourceState.kiroTargetId, 'kiro-rs') - : String(sourceState.kiroTargetId || 'kiro-rs').trim().toLowerCase(); } if (Object.prototype.hasOwnProperty.call(sourceState, 'settingsSchemaVersion')) { keepState.settingsSchemaVersion = Number(sourceState.settingsSchemaVersion) || 0; @@ -4172,7 +4184,15 @@ async function importSettingsBundle(configBundle) { throw new Error('\u914d\u7f6e\u6587\u4ef6\u7f3a\u5c11 settings \u914d\u7f6e\u6bb5\u3002'); } - const importedSettings = buildPersistentSettingsPayload(configBundle.settings, { + const settingsImporter = self.MultiPageLegacySettingsImporter?.createSettingsImporter?.({ + flowRegistry: self.MultiPageFlowRegistry, + settingsSchemaApi: typeof getSettingsSchemaApi === 'function' ? getSettingsSchemaApi() : null, + defaultFlowId: DEFAULT_ACTIVE_FLOW_ID, + }) || null; + const importedSettingsSource = typeof settingsImporter?.importSettings === 'function' + ? settingsImporter.importSettings(configBundle.settings) + : configBundle.settings; + const importedSettings = buildPersistentSettingsPayload(importedSettingsSource, { fillDefaults: true, requireKnownKeys: true, }); @@ -4190,7 +4210,7 @@ async function importSettingsBundle(configBundle) { Object.prototype.hasOwnProperty.call(importedSettings, 'phoneVerificationEnabled') || Object.prototype.hasOwnProperty.call(importedSettings, 'plusModeEnabled') || Object.prototype.hasOwnProperty.call(importedSettings, 'signupMethod') - || Object.prototype.hasOwnProperty.call(importedSettings, 'panelMode') + || Object.prototype.hasOwnProperty.call(importedSettings, 'targetId') || Object.prototype.hasOwnProperty.call(importedSettings, 'activeFlowId') || Object.prototype.hasOwnProperty.call(importedSettings, 'accountContributionEnabled') ) { @@ -4436,7 +4456,7 @@ function buildAccountContributionState(enabled, persistedSettings = {}, currentS ...(routing ? { contributionSource: routing.source, contributionTargetGroupName: routing.targetGroupName, - panelMode: routing.source, + targetId: routing.source, } : {}), customPassword: '', accountRunHistoryTextEnabled: false, @@ -4449,7 +4469,7 @@ function buildAccountContributionState(enabled, persistedSettings = {}, currentS accountContributionExpected: false, contributionAdapterId: '', flowContributionRuntime: {}, - panelMode: persistedSettings.panelMode || DEFAULT_STATE.panelMode, + targetId: persistedSettings.targetId || DEFAULT_STATE.targetId, customPassword: persistedSettings.customPassword || '', accountRunHistoryTextEnabled: Boolean(persistedSettings.accountRunHistoryTextEnabled), }; @@ -4472,7 +4492,7 @@ async function setAccountContributionMode(enabled, options = {}) { } broadcastDataUpdate({ ...contributionBroadcast, - panelMode: nextState.panelMode, + targetId: nextState.targetId, customPassword: nextState.customPassword, accountRunHistoryTextEnabled: nextState.accountRunHistoryTextEnabled, accountRunHistoryHelperBaseUrl: nextState.accountRunHistoryHelperBaseUrl, @@ -8542,10 +8562,10 @@ function getPanelMode(state = {}) { if (typeof navigationUtils !== 'undefined' && navigationUtils?.getPanelMode) { return navigationUtils.getPanelMode(state); } - if (state.panelMode === 'sub2api') { + if (state.targetId === 'sub2api') { return 'sub2api'; } - if (state.panelMode === 'codex2api') { + if (state.targetId === 'codex2api') { return 'codex2api'; } return 'cpa'; @@ -8661,7 +8681,6 @@ function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) { const reference = parseUrlSafely(referenceUrl); switch (source) { case 'openai-auth': - case 'signup-page': return isSignupPageHost(candidate.hostname) || isSignupEntryHost(candidate.hostname); case 'duck-mail': return candidate.hostname === 'duckduckgo.com' && candidate.pathname.startsWith('/email/'); @@ -8975,7 +8994,6 @@ function getSourceLabel(source) { 'openai-auth': '认证页', 'gmail-mail': 'Gmail 邮箱', 'sidepanel': '侧边栏', - 'signup-page': '认证页', 'vps-panel': 'CPA 面板', 'sub2api-panel': 'SUB2API 后台', 'codex2api-panel': 'Codex2API 后台', @@ -11544,7 +11562,7 @@ async function executeNodeAndWait(nodeId, delayAfter = 2000) { } if (normalizedNodeId === 'fill-profile') { - const signupTabId = await getTabId('signup-page'); + const signupTabId = await getTabId('openai-auth'); if (signupTabId) { await addLog('自动运行:填写资料节点已收到完成信号,正在等待当前页面完成加载并稳定...', 'info'); await waitForTabStableComplete(signupTabId, { @@ -12953,7 +12971,7 @@ async function runAutoSequenceFromNodeGraph(startNodeId, context = {}) { continue; } - const signupTabId = await getTabId('signup-page'); + const signupTabId = await getTabId('openai-auth'); if (signupTabId) { await chrome.tabs.update(signupTabId, { active: true }); } @@ -13236,9 +13254,9 @@ async function resumeAutoRun() { // ============================================================ const SIGNUP_ENTRY_URL = 'https://chatgpt.com/'; -const SIGNUP_PAGE_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/auth-page-recovery.js', 'content/phone-country-utils.js', 'content/phone-auth.js', 'content/signup-page.js']; -const KIRO_REGISTER_INJECT_FILES = ['shared/source-registry.js', 'shared/kiro-timeouts.js', 'content/utils.js', 'content/kiro/register-page.js']; -const KIRO_DESKTOP_AUTHORIZE_INJECT_FILES = ['shared/source-registry.js', 'shared/kiro-timeouts.js', 'content/utils.js', 'content/kiro/desktop-authorize-page.js']; +const OPENAI_AUTH_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'flows/openai/content/auth-page-recovery.js', 'flows/openai/content/phone-country-utils.js', 'flows/openai/content/phone-auth.js', 'flows/openai/content/openai-auth.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']; +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']; const panelBridge = self.MultiPageBackgroundPanelBridge?.createPanelBridge({ chrome, addLog, @@ -13287,7 +13305,7 @@ const signupFlowHelpers = self.MultiPageSignupFlowHelpers?.createSignupFlowHelpe setEmailState, setState, SIGNUP_ENTRY_URL, - SIGNUP_PAGE_INJECT_FILES, + OPENAI_AUTH_INJECT_FILES, waitForTabStableComplete, waitForTabUrlMatch, }); @@ -13418,7 +13436,7 @@ const step2Executor = self.MultiPageBackgroundStep2?.createStep2Executor({ resolveSignupMethod, resolveSignupEmailForFlow, sendToContentScriptResilient, - SIGNUP_PAGE_INJECT_FILES, + OPENAI_AUTH_INJECT_FILES, waitForTabStableComplete, }); const step3Executor = self.MultiPageBackgroundStep3?.createStep3Executor({ @@ -13432,7 +13450,7 @@ const step3Executor = self.MultiPageBackgroundStep3?.createStep3Executor({ sendToContentScript, setPasswordState, setState, - SIGNUP_PAGE_INJECT_FILES, + OPENAI_AUTH_INJECT_FILES, }); async function ensureIcloudMailSessionForVerification(options = {}) { @@ -13917,7 +13935,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter }, finalizeStep3Completion: async () => { const currentState = await getState(); - const signupTabId = await getTabId('signup-page'); + const signupTabId = await getTabId('openai-auth'); return signupFlowHelpers.finalizeSignupPasswordSubmitInTab( signupTabId, currentState.password || currentState.customPassword || '', @@ -14143,7 +14161,7 @@ async function executeStep2(state) { } // ============================================================ -// Step 3: Fill Password (via signup-page.js) +// Step 3: Fill Password (via openai-auth.js) // ============================================================ async function executeStep3(state) { @@ -14151,7 +14169,7 @@ async function executeStep3(state) { } // ============================================================ -// Step 4: Get Signup Verification Code (qq-mail.js polls, then fills in signup-page.js) +// Step 4: Get Signup Verification Code (qq-mail.js polls, then fills in openai-auth.js) // ============================================================ function getMailConfig(state) { @@ -14305,7 +14323,7 @@ async function executeStep4(state) { } // ============================================================ -// Step 5: Fill Name & Birthday (via signup-page.js) +// Step 5: Fill Name & Birthday (via openai-auth.js) // ============================================================ async function executeStep5(state) { @@ -14659,7 +14677,7 @@ async function getLoginAuthStateFromContent(options = {}) { const logStep = visibleStep > 0 ? visibleStep : null; const { logMessage = '认证页正在切换,等待页面重新就绪后继续确认验证码页状态...' } = options; const result = await sendToContentScriptResilient( - 'signup-page', + 'openai-auth', { type: 'GET_LOGIN_AUTH_STATE', source: 'background', @@ -14684,7 +14702,7 @@ async function getLoginAuthStateFromContent(options = {}) { async function getStep5SubmitStateFromContent(options = {}) { const result = await sendToContentScriptResilient( - 'signup-page', + 'openai-auth', { type: 'GET_STEP5_SUBMIT_STATE', source: 'background', @@ -14709,7 +14727,7 @@ async function getStep5SubmitStateFromContent(options = {}) { async function recoverStep5SubmitRetryPageOnTab(options = {}) { const result = await sendToContentScriptResilient( - 'signup-page', + 'openai-auth', { type: 'RECOVER_STEP5_SUBMIT_RETRY_PAGE', source: 'background', @@ -14849,7 +14867,7 @@ async function ensureStep8VerificationPageReady(options = {}) { const recoverTimeoutMs = 15000; if (typeof sendToContentScriptResilient === 'function') { recoverResult = await sendToContentScriptResilient( - 'signup-page', + 'openai-auth', recoverMessage, { timeoutMs: recoverTimeoutMs, @@ -14861,7 +14879,7 @@ async function ensureStep8VerificationPageReady(options = {}) { } ); } else if (typeof sendToContentScript === 'function') { - recoverResult = await sendToContentScript('signup-page', recoverMessage, { + recoverResult = await sendToContentScript('openai-auth', recoverMessage, { responseTimeoutMs: recoverTimeoutMs, }); } @@ -15098,9 +15116,9 @@ async function shouldDeferStep9CallbackTimeout(details = {}) { async function ensureStep8SignupPageReady(tabId, options = {}) { const visibleStep = Math.floor(Number(options.visibleStep || options.logStep || options.step) || 0); - await ensureContentScriptReadyOnTab('signup-page', tabId, { - inject: SIGNUP_PAGE_INJECT_FILES, - injectSource: 'signup-page', + await ensureContentScriptReadyOnTab('openai-auth', tabId, { + inject: OPENAI_AUTH_INJECT_FILES, + injectSource: 'openai-auth', timeoutMs: options.timeoutMs ?? 15000, retryDelayMs: options.retryDelayMs ?? 600, logMessage: options.logMessage || '', @@ -15142,7 +15160,7 @@ async function readAuthTabSnapshot(tabId) { async function getStep8PageState(tabId, responseTimeoutMs = 1500, visibleStep = 9) { try { - const result = await sendTabMessageWithTimeout(tabId, 'signup-page', { + const result = await sendTabMessageWithTimeout(tabId, 'openai-auth', { type: 'STEP8_GET_STATE', source: 'background', payload: { visibleStep }, @@ -15219,7 +15237,7 @@ async function prepareStep8DebuggerClick(tabId, options = {}) { logStepKey: 'confirm-oauth', logMessage: '认证页内容脚本已失联,正在恢复后继续定位按钮...', }); - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'STEP8_FIND_AND_CLICK', source: 'background', payload: { visibleStep, nodeId: 'confirm-oauth' }, @@ -15249,7 +15267,7 @@ async function triggerStep8ContentStrategy(tabId, strategy, options = {}) { logStepKey: 'confirm-oauth', logMessage: '认证页内容脚本已失联,正在恢复后继续点击“继续”按钮...', }); - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'STEP8_TRIGGER_CONTINUE', source: 'background', payload: { @@ -15287,7 +15305,7 @@ async function recoverAuthRetryPageOnTab(tabId, payload = {}, options = {}) { logStepKey: 'confirm-oauth', logMessage: options.readyLogMessage || '认证页内容脚本已失联,正在恢复后继续处理重试页...', }); - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'RECOVER_AUTH_RETRY_PAGE', source: 'background', payload: { nodeId: 'confirm-oauth', ...(payload || {}) }, diff --git a/background/auto-run-controller.js b/background/auto-run-controller.js index 2247757..976185f 100644 --- a/background/auto-run-controller.js +++ b/background/auto-run-controller.js @@ -106,8 +106,7 @@ return { activeFlowId: state.activeFlowId, flowId: state.flowId || state.activeFlowId, - panelMode: state.panelMode, - kiroTargetId: state.kiroTargetId, + targetId: state.targetId, vpsUrl: state.vpsUrl, vpsPassword: state.vpsPassword, customPassword: state.customPassword, diff --git a/background/logging-status.js b/background/logging-status.js index d26ad26..64bd29f 100644 --- a/background/logging-status.js +++ b/background/logging-status.js @@ -22,7 +22,6 @@ 'openai-auth': '认证页', 'gmail-mail': 'Gmail 邮箱', 'sidepanel': '侧边栏', - 'signup-page': '认证页', 'vps-panel': 'CPA 面板', 'sub2api-panel': 'SUB2API 后台', 'codex2api-panel': 'Codex2API 后台', diff --git a/background/message-router.js b/background/message-router.js index c1b3d79..bc3b576 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -91,7 +91,7 @@ } return capabilityRegistry.validateAutoRunStart({ activeFlowId: options?.activeFlowId ?? validationState?.activeFlowId, - panelMode: options?.panelMode ?? validationState?.panelMode, + targetId: options?.targetId ?? validationState?.targetId, signupMethod: options?.signupMethod ?? validationState?.signupMethod, state: validationState, }); @@ -113,7 +113,7 @@ return capabilityRegistry.validateModeSwitch({ activeFlowId: options?.activeFlowId ?? validationState?.activeFlowId, changedKeys: options?.changedKeys, - panelMode: options?.panelMode ?? validationState?.panelMode, + targetId: options?.targetId ?? validationState?.targetId, signupMethod: options?.signupMethod ?? validationState?.signupMethod, state: validationState, }); @@ -217,10 +217,6 @@ return String(targetId || fallbackSourceId).trim().toLowerCase() || fallbackSourceId; } - function mapAutoRunTargetIdToPanelMode(targetId = '', fallback = 'cpa') { - return String(targetId || fallback || 'cpa').trim().toLowerCase() || 'cpa'; - } - function buildAutoRunFlowStateUpdates(payload = {}) { const hasActiveFlowId = Object.prototype.hasOwnProperty.call(payload, 'activeFlowId'); const hasTargetId = Object.prototype.hasOwnProperty.call(payload, 'targetId'); @@ -233,11 +229,11 @@ flowId: activeFlowId, }; if (hasTargetId) { - if (activeFlowId === 'kiro') { - updates.kiroTargetId = normalizeMessageTargetId('kiro', payload.targetId, 'kiro-rs'); - } else { - updates.panelMode = mapAutoRunTargetIdToPanelMode(payload.targetId, 'cpa'); - } + updates.targetId = normalizeMessageTargetId( + activeFlowId, + payload.targetId, + activeFlowId === 'kiro' ? 'kiro-rs' : 'cpa' + ); } return updates; } @@ -297,10 +293,10 @@ } const signupTabId = typeof getTabId === 'function' - ? await getTabId('signup-page') + ? await getTabId('openai-auth') : null; const signupTabAlive = signupTabId && typeof isTabAlive === 'function' - ? await isTabAlive('signup-page') + ? await isTabAlive('openai-auth') : Boolean(signupTabId); if (!signupTabId || !signupTabAlive) { @@ -1309,7 +1305,7 @@ const state = await getState(); const autoRunStartValidation = validateAutoRunStart(state, { activeFlowId: autoRunFlowStateUpdates.activeFlowId ?? state?.activeFlowId, - panelMode: autoRunFlowStateUpdates.panelMode ?? state?.panelMode, + targetId: autoRunFlowStateUpdates.targetId ?? state?.targetId, state, }); if (autoRunStartValidation?.ok === false) { @@ -1352,7 +1348,7 @@ const state = await getState(); const autoRunStartValidation = validateAutoRunStart(state, { activeFlowId: autoRunFlowStateUpdates.activeFlowId ?? state?.activeFlowId, - panelMode: autoRunFlowStateUpdates.panelMode ?? state?.panelMode, + targetId: autoRunFlowStateUpdates.targetId ?? state?.targetId, state, }); if (autoRunStartValidation?.ok === false) { @@ -1451,7 +1447,7 @@ Object.prototype.hasOwnProperty.call(updates, 'phoneVerificationEnabled') || Object.prototype.hasOwnProperty.call(updates, 'plusModeEnabled') || Object.prototype.hasOwnProperty.call(updates, 'signupMethod') - || Object.prototype.hasOwnProperty.call(updates, 'panelMode') + || Object.prototype.hasOwnProperty.call(updates, 'targetId') || Object.prototype.hasOwnProperty.call(updates, 'activeFlowId') || Object.prototype.hasOwnProperty.call(updates, 'accountContributionEnabled') ) { @@ -1600,10 +1596,8 @@ ); const selectedPlusAccountAccessStrategy = getPlusAccountAccessStrategyLabel( stateUpdates.plusAccountAccessStrategy ?? currentState?.plusAccountAccessStrategy ?? 'oauth', - stateUpdates.panelMode - ?? currentState?.panelMode - ?? stateUpdates.openaiIntegrationTargetId - ?? currentState?.openaiIntegrationTargetId + stateUpdates.targetId + ?? currentState?.targetId ?? 'cpa' ); await addLog( @@ -1620,10 +1614,8 @@ } else if (plusAccountAccessStrategyChanged && nextPlusModeEnabled) { const selectedPlusAccountAccessStrategy = getPlusAccountAccessStrategyLabel( stateUpdates.plusAccountAccessStrategy ?? currentState?.plusAccountAccessStrategy ?? 'oauth', - stateUpdates.panelMode - ?? currentState?.panelMode - ?? stateUpdates.openaiIntegrationTargetId - ?? currentState?.openaiIntegrationTargetId + stateUpdates.targetId + ?? currentState?.targetId ?? 'cpa' ); await addLog(`Plus 账号接入策略已切换为 ${selectedPlusAccountAccessStrategy},已更新对应的 Plus 尾链。`, 'info'); @@ -1661,7 +1653,7 @@ ); const targetId = normalizeMessageTargetId( activeFlowId, - message.payload?.targetId || currentState?.kiroTargetId || 'kiro-rs', + message.payload?.targetId || currentState?.targetId || 'kiro-rs', 'kiro-rs' ); const nestedTargetConfig = currentState?.settingsState?.flows?.kiro?.targets?.[targetId] diff --git a/background/navigation-utils.js b/background/navigation-utils.js index 2353973..c377207 100644 --- a/background/navigation-utils.js +++ b/background/navigation-utils.js @@ -42,10 +42,10 @@ } function getPanelMode(state = {}) { - if (state.panelMode === 'sub2api') { + if (state.targetId === 'sub2api') { return 'sub2api'; } - if (state.panelMode === 'codex2api') { + if (state.targetId === 'codex2api') { return 'codex2api'; } return 'cpa'; @@ -128,7 +128,6 @@ switch (source) { case 'openai-auth': - case 'signup-page': return isSignupPageHost(candidate.hostname) || isSignupEntryHost(candidate.hostname); case 'duck-mail': return candidate.hostname === 'duckduckgo.com' && candidate.pathname.startsWith('/email/'); diff --git a/background/phone-verification-flow.js b/background/phone-verification-flow.js index 11783d4..08141ef 100644 --- a/background/phone-verification-flow.js +++ b/background/phone-verification-flow.js @@ -4585,7 +4585,7 @@ visibleStep, logStepKey: 'phone-verification', }); - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'STEP8_GET_STATE', source: 'background', payload: { visibleStep }, @@ -4676,7 +4676,7 @@ const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(30000, { step: visibleStep, actionLabel: '提交添加手机号' }) : 30000; - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'SUBMIT_PHONE_NUMBER', source: 'background', payload: { @@ -4723,7 +4723,7 @@ const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: '提交手机验证码' }) : 45000; - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'SUBMIT_PHONE_VERIFICATION_CODE', source: 'background', payload: { @@ -4756,7 +4756,7 @@ const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(65000, { step: visibleStep, actionLabel: 'resend phone verification code' }) : 65000; - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'RESEND_PHONE_VERIFICATION_CODE', source: 'background', payload: options || {}, @@ -4780,7 +4780,7 @@ const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: '提交注册手机验证码' }) : 45000; - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'SUBMIT_PHONE_VERIFICATION_CODE', step: visibleStep, source: 'background', @@ -4809,7 +4809,7 @@ const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(65000, { step: visibleStep, actionLabel: '重新发送注册手机验证码' }) : 65000; - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'RESEND_VERIFICATION_CODE', step: visibleStep, source: 'background', @@ -4834,7 +4834,7 @@ const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(30000, { step: visibleStep, actionLabel: 'return to add-phone page' }) : 30000; - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'RETURN_TO_ADD_PHONE', source: 'background', payload: {}, @@ -4863,7 +4863,7 @@ } const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9; try { - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'CHECK_PHONE_RESEND_ERROR', source: 'background', payload: { visibleStep }, @@ -6135,7 +6135,7 @@ const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: '提交登录手机验证码' }) : 45000; - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'SUBMIT_PHONE_VERIFICATION_CODE', step: visibleStep, source: 'background', @@ -6164,7 +6164,7 @@ const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function' ? await getOAuthFlowStepTimeoutMs(65000, { step: visibleStep, actionLabel: '重新发送登录手机验证码' }) : 65000; - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'RESEND_VERIFICATION_CODE', step: visibleStep, source: 'background', diff --git a/background/signup-flow-helpers.js b/background/signup-flow-helpers.js index 35f372e..75d2070 100644 --- a/background/signup-flow-helpers.js +++ b/background/signup-flow-helpers.js @@ -26,7 +26,7 @@ setEmailState, setState, SIGNUP_ENTRY_URL, - SIGNUP_PAGE_INJECT_FILES, + OPENAI_AUTH_INJECT_FILES, waitForTabStableComplete = null, waitForTabUrlMatch, } = deps; @@ -57,16 +57,16 @@ } async function openSignupEntryTab(step = 1) { - const tabId = await reuseOrCreateTab('signup-page', SIGNUP_ENTRY_URL, { - inject: SIGNUP_PAGE_INJECT_FILES, - injectSource: 'signup-page', + const tabId = await reuseOrCreateTab('openai-auth', SIGNUP_ENTRY_URL, { + inject: OPENAI_AUTH_INJECT_FILES, + injectSource: 'openai-auth', }); await waitForSignupEntryTabToSettle(tabId, step); - await ensureContentScriptReadyOnTab('signup-page', tabId, { - inject: SIGNUP_PAGE_INJECT_FILES, - injectSource: 'signup-page', + await ensureContentScriptReadyOnTab('openai-auth', tabId, { + inject: OPENAI_AUTH_INJECT_FILES, + injectSource: 'openai-auth', timeoutMs: 45000, retryDelayMs: 900, logMessage: `步骤 ${step}:ChatGPT 官网仍在加载,正在重试连接内容脚本...`, @@ -77,7 +77,7 @@ async function ensureSignupEntryPageReady(step = 1) { const tabId = await openSignupEntryTab(step); - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'ENSURE_SIGNUP_ENTRY_READY', step, source: 'background', @@ -186,9 +186,9 @@ } } - await ensureContentScriptReadyOnTab('signup-page', tabId, { - inject: SIGNUP_PAGE_INJECT_FILES, - injectSource: 'signup-page', + await ensureContentScriptReadyOnTab('openai-auth', tabId, { + inject: OPENAI_AUTH_INJECT_FILES, + injectSource: 'openai-auth', timeoutMs: 45000, retryDelayMs: 900, logMessage: landingState === 'password_page' @@ -204,7 +204,7 @@ }; } - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'ENSURE_SIGNUP_PASSWORD_PAGE_READY', step, source: 'background', @@ -244,9 +244,9 @@ throw new Error(`认证页面标签页已关闭,无法完成步骤 ${step} 的提交后确认。`); } - await ensureContentScriptReadyOnTab('signup-page', tabId, { - inject: SIGNUP_PAGE_INJECT_FILES, - injectSource: 'signup-page', + await ensureContentScriptReadyOnTab('openai-auth', tabId, { + inject: OPENAI_AUTH_INJECT_FILES, + injectSource: 'openai-auth', timeoutMs: 45000, retryDelayMs: 900, logMessage: `步骤 ${step}:认证页仍在切换,正在等待页面恢复后继续确认提交流程...`, @@ -254,7 +254,7 @@ let result; try { - result = await sendToContentScriptResilient('signup-page', { + result = await sendToContentScriptResilient('openai-auth', { type: 'PREPARE_SIGNUP_VERIFICATION', step, source: 'background', diff --git a/background/verification-flow.js b/background/verification-flow.js index 27221f5..c417724 100644 --- a/background/verification-flow.js +++ b/background/verification-flow.js @@ -242,7 +242,7 @@ const requestTimeoutMs = Math.max(1200, Math.min(5000, timeoutMs)); const result = typeof sendToContentScriptResilient === 'function' ? await sendToContentScriptResilient( - 'signup-page', + 'openai-auth', request, { timeoutMs: requestTimeoutMs, @@ -251,7 +251,7 @@ logMessage: `步骤 ${step}:验证码提交后页面正在切换,等待页面恢复并确认授权状态...`, } ) - : await sendToContentScript('signup-page', request, { + : await sendToContentScript('openai-auth', request, { responseTimeoutMs: requestTimeoutMs, }); @@ -516,7 +516,7 @@ async function requestVerificationCodeResend(step, options = {}) { throwIfStopped(); - const signupTabId = await getTabId('signup-page'); + const signupTabId = await getTabId('openai-auth'); if (!signupTabId) { throw new Error('认证页面标签页已关闭,无法重新请求验证码。'); } @@ -525,7 +525,7 @@ await chrome.tabs.update(signupTabId, { active: true }); throwIfStopped(); - const result = await sendToContentScript('signup-page', { + const result = await sendToContentScript('openai-auth', { type: 'RESEND_VERIFICATION_CODE', step, source: 'background', @@ -1111,7 +1111,7 @@ async function submitVerificationCode(step, code, options = {}) { const completionStep = getCompletionStep(step, options); const authLoginStep = completionStep >= 11 ? 10 : 7; - const signupTabId = await getTabId('signup-page'); + const signupTabId = await getTabId('openai-auth'); if (!signupTabId) { throw new Error('认证页面标签页已关闭,无法填写验证码。'); } @@ -1143,7 +1143,7 @@ const shouldAvoidReplaySubmit = step === 8; if (typeof sendToContentScriptResilient === 'function' && !shouldAvoidReplaySubmit) { try { - result = await sendToContentScriptResilient('signup-page', message, { + result = await sendToContentScriptResilient('openai-auth', message, { timeoutMs: Math.max(baseResponseTimeoutMs + 15000, 30000), retryDelayMs: 700, responseTimeoutMs: baseResponseTimeoutMs, @@ -1206,7 +1206,7 @@ } } else if (shouldAvoidReplaySubmit) { try { - result = await sendToContentScript('signup-page', message, { + result = await sendToContentScript('openai-auth', message, { responseTimeoutMs: baseResponseTimeoutMs, }); } catch (err) { @@ -1255,7 +1255,7 @@ throw err; } } else { - result = await sendToContentScript('signup-page', message, { + result = await sendToContentScript('openai-auth', message, { responseTimeoutMs: baseResponseTimeoutMs, }); } diff --git a/shared/flow-capabilities.js b/core/flow-kernel/flow-capabilities.js similarity index 87% rename from shared/flow-capabilities.js rename to core/flow-kernel/flow-capabilities.js index 7927ec8..03969ed 100644 --- a/shared/flow-capabilities.js +++ b/core/flow-kernel/flow-capabilities.js @@ -12,9 +12,11 @@ const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth'; const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session'; const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session'; - const VALID_OPENAI_TARGET_IDS = Array.isArray(flowRegistryApi.OPENAI_TARGET_IDS) - ? flowRegistryApi.OPENAI_TARGET_IDS.slice() - : ['cpa', 'sub2api', 'codex2api']; + const VALID_OPENAI_TARGET_IDS = typeof flowRegistryApi.getTargetDefinitions === 'function' + ? Object.keys(flowRegistryApi.getTargetDefinitions('openai') || {}) + : (Array.isArray(flowRegistryApi.OPENAI_TARGET_IDS) + ? flowRegistryApi.OPENAI_TARGET_IDS.slice() + : ['cpa', 'sub2api', 'codex2api']); const REGISTERED_FLOW_IDS = Array.isArray(flowRegistryApi.getRegisteredFlowIds?.()) ? flowRegistryApi.getRegisteredFlowIds().map((flowId) => String(flowId || '').trim().toLowerCase()).filter(Boolean) : [DEFAULT_FLOW_ID]; @@ -63,38 +65,18 @@ const MODE_SWITCH_RELEVANT_KEYS = Object.freeze([ 'activeFlowId', 'accountContributionEnabled', - 'panelMode', 'phoneVerificationEnabled', 'plusModeEnabled', 'signupMethod', 'plusAccountAccessStrategy', - 'openaiIntegrationTargetId', - 'kiroTargetId', + 'targetId', ]); - const OPENAI_TARGET_CAPABILITIES = Object.freeze({ - cpa: Object.freeze({ - supportsPhoneSignup: true, - requiresPhoneSignupWarning: true, - supportedPlusAccountAccessStrategies: Object.freeze([ - PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH, - PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION, - ]), - }), - sub2api: Object.freeze({ - supportsPhoneSignup: true, - requiresPhoneSignupWarning: false, - supportedPlusAccountAccessStrategies: Object.freeze([ - PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH, - PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION, - ]), - }), - codex2api: Object.freeze({ - supportsPhoneSignup: true, - requiresPhoneSignupWarning: false, - supportedPlusAccountAccessStrategies: Object.freeze([PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH]), - }), - }); + const OPENAI_TARGET_CAPABILITIES = Object.freeze( + typeof flowRegistryApi.getTargetCapabilityDefinitions === 'function' + ? (flowRegistryApi.getTargetCapabilityDefinitions('openai') || {}) + : {} + ); function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) { if (typeof flowRegistryApi.normalizeFlowId === 'function') { @@ -254,33 +236,18 @@ } function normalizeRequestedTargetId(activeFlowId, state = {}, options = {}) { - if (activeFlowId === 'openai') { - return normalizeOpenAiTargetId( - options?.targetId - ?? options?.integrationTargetId - ?? options?.panelMode - ?? state?.openaiIntegrationTargetId - ?? state?.panelMode, - DEFAULT_OPENAI_TARGET_ID - ); - } - - const rawTargetId = activeFlowId === 'kiro' - ? ( - options?.targetId - ?? state?.kiroTargetId - ?? flowRegistryApi.getDefaultTargetId?.(activeFlowId) - ?? '' - ) - : ( - options?.targetId - ?? state?.targetId - ?? state?.openaiIntegrationTargetId - ?? state?.panelMode - ?? state?.kiroTargetId - ?? flowRegistryApi.getDefaultTargetId?.(activeFlowId) - ?? '' - ); + const schemaTargetId = settingsSchema?.getSelectedTargetId + ? settingsSchema.getSelectedTargetId({ + ...state, + activeFlowId, + }, activeFlowId) + : ''; + const rawTargetId = options?.targetId + ?? options?.selectedTargetId + ?? state?.targetId + ?? schemaTargetId + ?? flowRegistryApi.getDefaultTargetId?.(activeFlowId) + ?? ''; if ( isRegisteredFlowId(activeFlowId) @@ -312,32 +279,9 @@ } function resolveEffectiveTargetId(activeFlowId, state = {}, requestedTargetId = DEFAULT_OPENAI_TARGET_ID) { - if (!isRegisteredFlowId(activeFlowId)) { - return normalizeRequestedTargetId(activeFlowId, state, { - targetId: requestedTargetId, - }); - } - if (settingsSchema?.getSelectedTargetId) { - const targetId = settingsSchema.getSelectedTargetId({ - ...state, - activeFlowId, - }, activeFlowId); - if (targetId) { - return targetId; - } - } - if (typeof flowRegistryApi.normalizeTargetId === 'function') { - return flowRegistryApi.normalizeTargetId( - activeFlowId, - activeFlowId === 'openai' - ? (state?.openaiIntegrationTargetId || state?.panelMode || requestedTargetId) - : (state?.kiroTargetId || requestedTargetId), - flowRegistryApi.getDefaultTargetId?.(activeFlowId) - ); - } - return activeFlowId === 'openai' - ? normalizeOpenAiTargetId(requestedTargetId) - : String(requestedTargetId || '').trim().toLowerCase(); + return normalizeRequestedTargetId(activeFlowId, state, { + targetId: requestedTargetId, + }); } function resolveSidepanelCapabilities(options = {}) { @@ -444,13 +388,11 @@ canUsePhoneSignup: canSelectPhoneSignup, canUseSelectedTarget: targetSupported, effectivePlusAccountAccessStrategy, - effectivePanelMode: effectiveTargetId, effectiveSignupMethod, effectiveSignupMethods, effectiveTargetId, flowCapabilities: flowState, panelCapabilities: targetState, - panelMode: effectiveTargetId, requestedPlusAccountAccessStrategy, requestedSignupMethod, requestedTargetId, @@ -460,8 +402,6 @@ && Boolean(targetState.requiresPhoneSignupWarning), stepDefinitionOptions: { activeFlowId, - integrationTargetId: effectiveTargetId, - panelMode: effectiveTargetId, targetId: effectiveTargetId, plusAccountAccessStrategy: effectivePlusAccountAccessStrategy, plusModeEnabled: runtimeLocks.plusModeEnabled, @@ -576,14 +516,12 @@ const shouldReconcileSignupMethod = MODE_SWITCH_RELEVANT_KEYS.some((key) => changedKeySet.has(key)); if ( - (changedKeySet.has('panelMode') || changedKeySet.has('openaiIntegrationTargetId') || changedKeySet.has('kiroTargetId')) + changedKeySet.has('targetId') && Array.isArray(capabilityState.supportedTargetIds) && capabilityState.supportedTargetIds.length > 0 && capabilityState.canUseSelectedTarget === false ) { - normalizedUpdates.panelMode = capabilityState.effectiveTargetId; - normalizedUpdates.openaiIntegrationTargetId = capabilityState.effectiveTargetId; - normalizedUpdates.kiroTargetId = capabilityState.effectiveTargetId; + normalizedUpdates.targetId = capabilityState.effectiveTargetId; errors.push({ code: 'panel_mode_unsupported', message: `当前 flow 不支持 ${getTargetLabel(capabilityState.activeFlowId, capabilityState.requestedTargetId)} 来源。`, diff --git a/core/flow-kernel/flow-registry.js b/core/flow-kernel/flow-registry.js new file mode 100644 index 0000000..eff14fe --- /dev/null +++ b/core/flow-kernel/flow-registry.js @@ -0,0 +1,300 @@ +(function attachMultiPageFlowRegistry(root, factory) { + root.MultiPageFlowRegistry = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createFlowRegistryModule() { + const rootScope = typeof self !== 'undefined' ? self : globalThis; + const flowsIndexApi = rootScope.MultiPageFlowsIndex || {}; + + const DEFAULT_FLOW_ID = 'openai'; + const SHARED_SERVICE_IDS = Object.freeze(['account', 'email', 'proxy']); + + const DEFAULT_FLOW_CAPABILITIES = Object.freeze({ + supportsEmailSignup: true, + supportsPhoneSignup: false, + supportsPhoneVerificationSettings: false, + supportsPlusMode: false, + supportsContributionMode: false, + supportsAccountContribution: false, + supportsOpenAiOAuthContribution: false, + contributionAdapterIds: [], + supportedTargetIds: [], + supportsLuckmail: false, + supportsOauthTimeoutBudget: false, + canSwitchFlow: true, + stepDefinitionMode: 'default', + targetSelectorLabel: '\u6765\u6e90', + }); + + function freezeDeep(value) { + if (!value || typeof value !== 'object' || Object.isFrozen(value)) { + return value; + } + Object.getOwnPropertyNames(value).forEach((key) => { + freezeDeep(value[key]); + }); + return Object.freeze(value); + } + + const SHARED_SETTINGS_GROUP_DEFINITIONS = freezeDeep({ + 'service-account': { + id: 'service-account', + label: '\u8d26\u6237', + rowIds: ['row-custom-password'], + }, + 'service-email': { + id: 'service-email', + label: '\u90ae\u7bb1\u670d\u52a1', + }, + 'service-proxy': { + id: 'service-proxy', + label: 'IP \u4ee3\u7406', + sectionIds: ['ip-proxy-section'], + }, + }); + + function buildFlowDefinitions() { + if (typeof flowsIndexApi.getFlowDefinitions !== 'function') { + return {}; + } + return flowsIndexApi.getFlowDefinitions() || {}; + } + + const FLOW_DEFINITIONS = freezeDeep(buildFlowDefinitions()); + const REGISTERED_FLOW_IDS = Object.freeze(Object.keys(FLOW_DEFINITIONS)); + + function buildSettingsGroupDefinitions() { + const next = { + ...SHARED_SETTINGS_GROUP_DEFINITIONS, + }; + Object.values(FLOW_DEFINITIONS).forEach((flowDefinition) => { + Object.assign(next, flowDefinition?.settingsGroups || {}); + }); + return next; + } + + const SETTINGS_GROUP_DEFINITIONS = freezeDeep(buildSettingsGroupDefinitions()); + + function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) { + const normalized = String(value || '').trim().toLowerCase(); + if (normalized && Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, normalized)) { + return normalized; + } + const fallbackValue = String(fallback || '').trim().toLowerCase(); + if (fallbackValue && Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, fallbackValue)) { + return fallbackValue; + } + if (Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, DEFAULT_FLOW_ID)) { + return DEFAULT_FLOW_ID; + } + return REGISTERED_FLOW_IDS[0] || DEFAULT_FLOW_ID; + } + + function getRegisteredFlowIds() { + return REGISTERED_FLOW_IDS.slice(); + } + + function getFlowDefinition(flowId) { + const normalizedFlowId = normalizeFlowId(flowId); + return FLOW_DEFINITIONS[normalizedFlowId] || null; + } + + function getFlowLabel(flowId) { + return getFlowDefinition(flowId)?.label || normalizeFlowId(flowId); + } + + function getDefaultTargetId(flowId) { + const flowDefinition = getFlowDefinition(flowId); + return String(flowDefinition?.defaultTargetId || '').trim().toLowerCase(); + } + + function normalizeTargetId(flowId, targetId = '', fallback = undefined) { + const normalizedFlowId = normalizeFlowId(flowId); + const targetDefinitions = getFlowDefinition(normalizedFlowId)?.targets || {}; + const targetKeys = Object.keys(targetDefinitions); + if (!targetKeys.length) { + return String(targetId || fallback || '').trim().toLowerCase(); + } + + const normalizedTargetId = String(targetId || '').trim().toLowerCase(); + if (normalizedTargetId && Object.prototype.hasOwnProperty.call(targetDefinitions, normalizedTargetId)) { + return normalizedTargetId; + } + + const fallbackValue = String(fallback || '').trim().toLowerCase(); + if (fallbackValue && Object.prototype.hasOwnProperty.call(targetDefinitions, fallbackValue)) { + return fallbackValue; + } + + const defaultTargetId = getDefaultTargetId(normalizedFlowId); + if (defaultTargetId && Object.prototype.hasOwnProperty.call(targetDefinitions, defaultTargetId)) { + return defaultTargetId; + } + + return targetKeys[0]; + } + + function normalizeOpenAiTargetId(value = '', fallback = undefined) { + return normalizeTargetId('openai', value, fallback); + } + + function normalizeKiroTargetId(value = '', fallback = undefined) { + return normalizeTargetId('kiro', value, fallback); + } + + function getTargetDefinitions(flowId) { + return getFlowDefinition(flowId)?.targets || {}; + } + + function getTargetDefinition(flowId, targetId) { + const normalizedFlowId = normalizeFlowId(flowId); + const normalizedTargetId = normalizeTargetId( + normalizedFlowId, + targetId, + getDefaultTargetId(normalizedFlowId) + ); + return getTargetDefinitions(normalizedFlowId)[normalizedTargetId] || null; + } + + function getTargetOptions(flowId) { + return Object.values(getTargetDefinitions(flowId)); + } + + function getTargetLabel(flowId, targetId) { + return getTargetDefinition(flowId, targetId)?.label + || normalizeTargetId(flowId, targetId); + } + + function getPublicationTargetDefinitions(flowId) { + return getFlowDefinition(flowId)?.publicationTargets || {}; + } + + function getPublicationTargetDefinition(flowId, publicationTargetId) { + const normalizedFlowId = normalizeFlowId(flowId); + const flowDefinition = getFlowDefinition(normalizedFlowId); + const normalizedPublicationTargetId = String( + publicationTargetId || flowDefinition?.defaultPublicationTargetId || '' + ).trim().toLowerCase(); + return getPublicationTargetDefinitions(normalizedFlowId)[normalizedPublicationTargetId] || null; + } + + function getFlowCapabilities(flowId) { + return { + ...DEFAULT_FLOW_CAPABILITIES, + ...(getFlowDefinition(flowId)?.capabilities || {}), + }; + } + + function getTargetCapabilityDefinitions(flowId) { + return getFlowDefinition(flowId)?.targetCapabilities || {}; + } + + function getTargetCapabilities(flowId, targetId) { + const normalizedFlowId = normalizeFlowId(flowId); + const normalizedTargetId = normalizeTargetId( + normalizedFlowId, + targetId, + getDefaultTargetId(normalizedFlowId) + ); + return getTargetCapabilityDefinitions(normalizedFlowId)[normalizedTargetId] || null; + } + + function getVisibleGroupIds(flowId, targetId, options = {}) { + const normalizedFlowId = normalizeFlowId(flowId); + const flowDefinition = getFlowDefinition(normalizedFlowId); + const normalizedTargetId = normalizeTargetId( + normalizedFlowId, + targetId, + getDefaultTargetId(normalizedFlowId) + ); + const targetDefinition = getTargetDefinition(normalizedFlowId, normalizedTargetId); + const includeSharedServices = options?.includeSharedServices !== false; + const serviceGroups = includeSharedServices + ? (Array.isArray(flowDefinition?.services) + ? flowDefinition.services.map((serviceId) => `service-${serviceId}`) + : []) + : []; + return Array.from(new Set([ + ...(Array.isArray(flowDefinition?.baseGroups) ? flowDefinition.baseGroups : []), + ...(Array.isArray(targetDefinition?.groups) ? targetDefinition.groups : []), + ...serviceGroups, + ])); + } + + function getSettingsGroupDefinition(groupId) { + const normalizedGroupId = String(groupId || '').trim(); + return SETTINGS_GROUP_DEFINITIONS[normalizedGroupId] || null; + } + + function getSettingsGroupDefinitions() { + return SETTINGS_GROUP_DEFINITIONS; + } + + function getRuntimeSourceDefinitions() { + const next = {}; + Object.values(FLOW_DEFINITIONS).forEach((flowDefinition) => { + Object.assign(next, flowDefinition?.runtimeSources || {}); + }); + return next; + } + + function getDriverDefinitions() { + const next = {}; + Object.values(FLOW_DEFINITIONS).forEach((flowDefinition) => { + Object.assign(next, flowDefinition?.driverDefinitions || {}); + }); + return next; + } + + function getSourceAliases() { + const next = {}; + Object.values(FLOW_DEFINITIONS).forEach((flowDefinition) => { + Object.assign(next, flowDefinition?.sourceAliases || {}); + }); + return next; + } + + const OPENAI_TARGET_IDS = Object.freeze(Object.keys(getTargetDefinitions('openai'))); + const DEFAULT_OPENAI_TARGET_ID = String(getDefaultTargetId('openai') || OPENAI_TARGET_IDS[0] || 'cpa'); + const DEFAULT_KIRO_TARGET_ID = String(getDefaultTargetId('kiro') || 'kiro-rs'); + const DEFAULT_KIRO_PUBLICATION_TARGET_ID = String( + getFlowDefinition('kiro')?.defaultPublicationTargetId || DEFAULT_KIRO_TARGET_ID + ); + const DEFAULT_KIRO_RS_URL = String( + getFlowDefinition('kiro')?.defaultTargetState?.baseUrl || '' + ).trim(); + + return { + DEFAULT_FLOW_CAPABILITIES, + DEFAULT_FLOW_ID, + DEFAULT_KIRO_PUBLICATION_TARGET_ID, + DEFAULT_KIRO_RS_URL, + DEFAULT_KIRO_TARGET_ID, + DEFAULT_OPENAI_TARGET_ID, + FLOW_DEFINITIONS, + OPENAI_TARGET_IDS, + SETTINGS_GROUP_DEFINITIONS, + SHARED_SERVICE_IDS, + getDriverDefinitions, + getDefaultTargetId, + getFlowCapabilities, + getFlowDefinition, + getFlowLabel, + getPublicationTargetDefinition, + getPublicationTargetDefinitions, + getRegisteredFlowIds, + getRuntimeSourceDefinitions, + getSettingsGroupDefinition, + getSettingsGroupDefinitions, + getSourceAliases, + getTargetCapabilities, + getTargetCapabilityDefinitions, + getTargetDefinition, + getTargetDefinitions, + getTargetLabel, + getTargetOptions, + getVisibleGroupIds, + normalizeFlowId, + normalizeKiroTargetId, + normalizeOpenAiTargetId, + normalizeTargetId, + }; +}); diff --git a/core/flow-kernel/logging-status.js b/core/flow-kernel/logging-status.js new file mode 100644 index 0000000..64bd29f --- /dev/null +++ b/core/flow-kernel/logging-status.js @@ -0,0 +1,257 @@ +(function attachBackgroundLoggingStatus(root, factory) { + root.MultiPageBackgroundLoggingStatus = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundLoggingStatusModule() { + function createLoggingStatus(deps = {}) { + const { + chrome, + DEFAULT_STATE, + getStepIdByNodeIdForState, + getState, + isRecoverableStep9AuthFailure, + LOG_PREFIX, + setState, + sourceRegistry = null, + STOP_ERROR_MESSAGE, + } = deps; + + function getSourceLabel(source) { + if (sourceRegistry?.getSourceLabel) { + return sourceRegistry.getSourceLabel(source); + } + const labels = { + 'openai-auth': '认证页', + 'gmail-mail': 'Gmail 邮箱', + 'sidepanel': '侧边栏', + 'vps-panel': 'CPA 面板', + 'sub2api-panel': 'SUB2API 后台', + 'codex2api-panel': 'Codex2API 后台', + 'qq-mail': 'QQ 邮箱', + 'mail-163': '163 邮箱', + 'mail-2925': '2925 邮箱', + 'inbucket-mail': 'Inbucket 邮箱', + 'duck-mail': 'Duck 邮箱', + 'hotmail-api': 'Hotmail(API对接/本地助手)', + 'luckmail-api': 'LuckMail(API 购邮)', + 'cloudflare-temp-email': 'Cloudflare Temp Email', + 'cloudmail': 'Cloud Mail', + 'plus-checkout': 'Plus Checkout', + 'paypal-flow': 'PayPal 授权页', + 'gopay-flow': 'GoPay 授权页', + 'unknown-source': '未知来源', + }; + return labels[source] || source || '未知来源'; + } + + function normalizeLogStep(value) { + const step = Math.floor(Number(value) || 0); + return step > 0 ? step : null; + } + + function buildLogEntry(message, level = 'info', options = {}) { + const normalizedOptions = options && typeof options === 'object' ? options : {}; + const step = normalizeLogStep(normalizedOptions.step); + const stepKey = String(normalizedOptions.stepKey || '').trim(); + const nodeId = String(normalizedOptions.nodeId || normalizedOptions.nodeKey || stepKey || '').trim(); + return { + message: String(message || ''), + level, + timestamp: Date.now(), + step, + stepKey, + nodeId, + }; + } + + async function addLog(message, level = 'info', options = {}) { + const state = await getState(); + const logs = state.logs || []; + const entry = buildLogEntry(message, level, options); + logs.push(entry); + if (logs.length > 500) logs.splice(0, logs.length - 500); + await setState({ logs }); + chrome.runtime.sendMessage({ type: 'LOG_ENTRY', payload: entry }).catch(() => { }); + } + + async function setNodeStatus(nodeId, status) { + const normalizedNodeId = String(nodeId || '').trim(); + if (!normalizedNodeId) { + throw new Error('setNodeStatus 缺少 nodeId。'); + } + const state = await getState(); + const nodeStatuses = { ...(state.nodeStatuses || {}) }; + nodeStatuses[normalizedNodeId] = status; + await setState({ + nodeStatuses, + currentNodeId: normalizedNodeId, + }); + chrome.runtime.sendMessage({ + type: 'NODE_STATUS_CHANGED', + payload: { nodeId: normalizedNodeId, status }, + }).catch(() => { }); + } + + function getErrorMessage(error) { + return String(typeof error === 'string' ? error : error?.message || '') + .replace(/^GPC_TASK_ENDED::/i, '') + .replace(/^AUTO_RUN_STEP_IDLE_RESTART::/i, ''); + } + + function isVerificationMailPollingError(error) { + const message = getErrorMessage(error); + return /未在 .*邮箱中找到新的匹配邮件|未在 Hotmail 收件箱中找到新的匹配验证码|邮箱轮询结束,但未获取到验证码|无法获取新的(?:注册|登录)验证码|页面未能重新就绪|页面通信异常|内容脚本\s+\d+(?:\.\d+)?\s*秒内未响应|did not respond in \d+s|405\s+method\s+not\s+allowed|route\s+error.*405|did\s+not\s+provide\s+an?\s+[`'"]?action|post\s+request\s+to\s+["']?\/(?:email|phone)-verification/i.test(message); + } + + function isAddPhoneAuthFailure(error) { + const message = getErrorMessage(error); + if (/\u624b\u673a\u53f7\u8f93\u5165\u6a21\u5f0f|phone\s+entry/i.test(message)) { + return false; + } + return /https:\/\/auth\.openai\.com\/(?:add-phone|phone-verification)(?:[/?#]|$)|\badd-phone\b|phone-verification|\u6dfb\u52a0\u624b\u673a\u53f7|\u624b\u673a\u53f7\u7801|\u624b\u673a\u9a8c\u8bc1\u7801\u9875|\u624b\u673a\u9a8c\u8bc1\u9875|\u8fdb\u5165\u624b\u673a\u53f7\u9875\u9762|\u624b\u673a\u53f7\u9875|\u624b\u673a\u53f7\u9875\u9762|phone\s+number|telephone/i.test(message); + } + + function getLoginAuthStateLabel(state) { + switch (state) { + case 'verification_page': + return '登录验证码页'; + case 'password_page': + return '密码页'; + case 'email_page': + return '邮箱输入页'; + case 'login_timeout_error_page': + return '登录超时报错页'; + case 'oauth_consent_page': + return 'OAuth 授权页'; + case 'add_phone_page': + return '手机号页'; + case 'add_email_page': + return '添加邮箱页'; + case 'phone_verification_page': + return '手机验证码页'; + default: + return '未知页面'; + } + } + + function isRestartCurrentAttemptError(error) { + const message = String(typeof error === 'string' ? error : error?.message || ''); + return /当前邮箱已存在,需要重新开始新一轮|SIGNUP_PHONE_PASSWORD_MISMATCH::/i.test(message); + } + + function isSignupUserAlreadyExistsFailure(error) { + const message = getErrorMessage(error); + return /SIGNUP_USER_ALREADY_EXISTS::|user_already_exists/i.test(message); + } + + function isKiroProxyFailure(error) { + const message = getErrorMessage(error); + return /Kiro\s*(?:注册页|桌面授权页).*(?:CloudFront\s*拒绝请求|AWS\s*请求异常)|(?:当前代理\s*IP|出口区域异常).*(?:切换代理|更换代理)|AWS\s*风控.*(?:切换代理|更换代理)/i.test(message); + } + + function isStep9RecoverableAuthError(error) { + const message = String(typeof error === 'string' ? error : error?.message || ''); + return /STEP9_OAUTH_RETRY::/i.test(message) + || isRecoverableStep9AuthFailure(message); + } + + function isLegacyStep9RecoverableAuthError(error) { + const message = String(typeof error === 'string' ? error : error?.message || ''); + return /STEP9_OAUTH_TIMEOUT::|认证失败:\s*(?:Timeout waiting for OAuth callback|timeout of \d+ms exceeded)/i.test(message); + } + + function isStepDoneStatus(status) { + return status === 'completed' || status === 'manual_completed' || status === 'skipped'; + } + + function getFirstUnfinishedStep(statuses = {}) { + const nodeStatuses = statuses && typeof statuses === 'object' ? statuses : {}; + const nodeIds = Object.keys(DEFAULT_STATE.nodeStatuses || {}); + for (const nodeId of nodeIds) { + if (!isStepDoneStatus(nodeStatuses[nodeId] || 'pending')) { + return typeof getStepIdByNodeIdForState === 'function' + ? getStepIdByNodeIdForState(nodeId, {}) + : null; + } + } + return null; + } + + function hasSavedProgress(statuses = {}) { + return Object.values({ ...DEFAULT_STATE.nodeStatuses, ...statuses }).some((status) => status !== 'pending'); + } + + function getRunningSteps(statuses = {}) { + return Object.entries({ ...DEFAULT_STATE.nodeStatuses, ...statuses }) + .filter(([, status]) => status === 'running') + .map(([nodeId]) => (typeof getStepIdByNodeIdForState === 'function' ? getStepIdByNodeIdForState(nodeId, {}) : null)) + .filter((step) => Number.isInteger(step) && step > 0) + .sort((a, b) => a - b); + } + + function getFirstUnfinishedNode(statuses = {}) { + const nodeIds = Object.keys(DEFAULT_STATE.nodeStatuses || {}); + for (const nodeId of nodeIds) { + if (!isStepDoneStatus(statuses[nodeId] || 'pending')) { + return nodeId; + } + } + return ''; + } + + function hasSavedNodeProgress(statuses = {}) { + return Object.values({ ...DEFAULT_STATE.nodeStatuses, ...statuses }).some((status) => status !== 'pending'); + } + + function getRunningNodes(statuses = {}) { + return Object.entries({ ...DEFAULT_STATE.nodeStatuses, ...statuses }) + .filter(([, status]) => status === 'running') + .map(([nodeId]) => nodeId); + } + + function getAutoRunStatusPayload(phase, payload = {}) { + return { + autoRunning: phase === 'scheduled' + || phase === 'running' + || phase === 'waiting_step' + || phase === 'waiting_email' + || phase === 'retrying' + || phase === 'waiting_interval', + autoRunPhase: phase, + autoRunCurrentRun: payload.currentRun ?? 0, + autoRunTotalRuns: payload.totalRuns ?? 1, + autoRunAttemptRun: payload.attemptRun ?? 0, + autoRunSessionId: Math.max(0, Math.floor(Number(payload.sessionId ?? payload.autoRunSessionId) || 0)), + scheduledAutoRunAt: Number.isFinite(Number(payload.scheduledAt)) ? Number(payload.scheduledAt) : null, + autoRunCountdownAt: Number.isFinite(Number(payload.countdownAt)) ? Number(payload.countdownAt) : null, + autoRunCountdownTitle: payload.countdownTitle === undefined ? '' : String(payload.countdownTitle || ''), + autoRunCountdownNote: payload.countdownNote === undefined ? '' : String(payload.countdownNote || ''), + }; + } + + return { + addLog, + getAutoRunStatusPayload, + getFirstUnfinishedNode, + isAddPhoneAuthFailure, + getErrorMessage, + getFirstUnfinishedStep, + getLoginAuthStateLabel, + getRunningNodes, + getRunningSteps, + getSourceLabel, + hasSavedNodeProgress, + hasSavedProgress, + isKiroProxyFailure, + isLegacyStep9RecoverableAuthError, + isRestartCurrentAttemptError, + isSignupUserAlreadyExistsFailure, + isStep9RecoverableAuthError, + isStepDoneStatus, + isVerificationMailPollingError, + setNodeStatus, + }; + } + + return { + createLoggingStatus, + }; +}); diff --git a/background/runtime-state.js b/core/flow-kernel/runtime-state.js similarity index 84% rename from background/runtime-state.js rename to core/flow-kernel/runtime-state.js index a902eed..4dd4faf 100644 --- a/background/runtime-state.js +++ b/core/flow-kernel/runtime-state.js @@ -140,6 +140,24 @@ return value; } + function deepMerge(baseValue, patchValue) { + if (Array.isArray(patchValue)) { + return patchValue.map((item) => cloneValue(item)); + } + if (!isPlainObject(patchValue)) { + return patchValue === undefined ? cloneValue(baseValue) : patchValue; + } + + const baseObject = isPlainObject(baseValue) ? baseValue : {}; + const next = { + ...cloneValue(baseObject), + }; + Object.entries(patchValue).forEach(([key, value]) => { + next[key] = deepMerge(baseObject[key], value); + }); + return next; + } + function normalizePlainObject(value) { return isPlainObject(value) ? value : {}; } @@ -277,6 +295,7 @@ 'activeRunId', 'currentNodeId', 'nodeStatuses', + 'kiroRuntime', ...RUNTIME_TOP_LEVEL_FIELDS, ]); @@ -358,6 +377,43 @@ return next; } + function buildRuntimeStateSeed(currentRuntimeState = {}, updates = {}) { + const normalizedUpdates = normalizePlainObject(updates); + let nextRuntimeState = deepMerge(currentRuntimeState, normalizePlainObject(normalizedUpdates.runtimeState)); + + if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'sharedState')) { + nextRuntimeState = deepMerge(nextRuntimeState, { + sharedState: normalizePlainObject(normalizedUpdates.sharedState), + }); + } + if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'shared')) { + nextRuntimeState = deepMerge(nextRuntimeState, { + sharedState: normalizePlainObject(normalizedUpdates.shared), + }); + } + if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'serviceState')) { + nextRuntimeState = deepMerge(nextRuntimeState, { + serviceState: normalizePlainObject(normalizedUpdates.serviceState), + }); + } + if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'services')) { + nextRuntimeState = deepMerge(nextRuntimeState, { + serviceState: normalizePlainObject(normalizedUpdates.services), + }); + } + if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'flowState')) { + nextRuntimeState = deepMerge(nextRuntimeState, { + flowState: normalizePlainObject(normalizedUpdates.flowState), + }); + } + if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'flows')) { + nextRuntimeState = deepMerge(nextRuntimeState, { + flowState: normalizePlainObject(normalizedUpdates.flows), + }); + } + return nextRuntimeState; + } + function buildRuntimeStateDefault() { return { flowId: DEFAULT_ACTIVE_FLOW_ID, @@ -388,6 +444,19 @@ ...buildRuntimeStateDefault(), ...cloneValue(normalizePlainObject(state.runtimeState)), }; + const projectedKiroRuntime = isPlainObject(normalizePlainObject(state.flowState).kiro) + ? normalizePlainObject(state.flowState.kiro) + : {}; + const canonicalKiroRuntime = deepMerge( + normalizePlainObject(normalizePlainObject(baseRuntimeState.flowState).kiro), + projectedKiroRuntime + ); + if (Object.keys(canonicalKiroRuntime).length > 0) { + baseRuntimeState.flowState = { + ...cloneValue(normalizePlainObject(baseRuntimeState.flowState)), + kiro: canonicalKiroRuntime, + }; + } const activeFlowId = normalizeFlowId( Object.prototype.hasOwnProperty.call(state, 'activeFlowId') ? state.activeFlowId @@ -472,9 +541,10 @@ function buildSessionStatePatch(currentState = {}, updates = {}) { const currentRuntimeState = ensureRuntimeState(currentState); + const runtimeStateSeed = buildRuntimeStateSeed(currentRuntimeState, updates); const runtimeState = ensureRuntimeState({ - runtimeState: currentRuntimeState, - ...projectRuntimeViewFields(currentRuntimeState), + runtimeState: runtimeStateSeed, + ...projectRuntimeViewFields(runtimeStateSeed), ...buildRuntimeInputFromPatch(updates), }); diff --git a/shared/settings-schema.js b/core/flow-kernel/settings-schema.js similarity index 75% rename from shared/settings-schema.js rename to core/flow-kernel/settings-schema.js index f200001..8e8afad 100644 --- a/shared/settings-schema.js +++ b/core/flow-kernel/settings-schema.js @@ -60,7 +60,7 @@ function buildDefaultSettingsState() { return { - schemaVersion: 4, + schemaVersion: 5, activeFlowId: defaultFlowId, services: { account: { @@ -77,8 +77,8 @@ }, flows: { openai: { - integrationTargetId: defaultOpenAiTargetId, - integrationTargets: { + selectedTargetId: defaultOpenAiTargetId, + targets: { cpa: { vpsUrl: '', vpsPassword: '', @@ -120,7 +120,7 @@ }, }, kiro: { - targetId: defaultKiroTargetId, + selectedTargetId: defaultKiroTargetId, targets: { 'kiro-rs': { baseUrl: defaultKiroRsUrl, @@ -139,8 +139,12 @@ }; } - function getIntegrationTargetValue(settingsState, pathGetter, fallback = {}) { - return cloneValue(pathGetter(isPlainObject(settingsState) ? settingsState : {}) || fallback); + function getTargetValue(settingsState, pathGetter, legacyPathGetter = null, fallback = {}) { + const sourceState = isPlainObject(settingsState) ? settingsState : {}; + const resolvedValue = pathGetter(sourceState) + || (typeof legacyPathGetter === 'function' ? legacyPathGetter(sourceState) : null) + || fallback; + return cloneValue(resolvedValue); } function normalizeSettingsState(input = {}, options = {}) { @@ -155,20 +159,25 @@ ?? defaults.activeFlowId, defaults.activeFlowId ); - const openaiIntegrationTargetId = normalizeTargetId( + const openaiSelectedTargetId = normalizeTargetId( 'openai', - nested?.flows?.openai?.integrationTargetId - ?? input?.openaiIntegrationTargetId - ?? input?.panelMode - ?? defaults.flows.openai.integrationTargetId, - defaults.flows.openai.integrationTargetId + nested?.flows?.openai?.selectedTargetId + ?? nested?.flows?.openai?.integrationTargetId + ?? (activeFlowId === 'openai' + ? (input?.selectedTargetId ?? input?.targetId) + : undefined) + ?? defaults.flows.openai.selectedTargetId, + defaults.flows.openai.selectedTargetId ); - const kiroTargetId = normalizeTargetId( + const kiroSelectedTargetId = normalizeTargetId( 'kiro', - nested?.flows?.kiro?.targetId - ?? input?.kiroTargetId - ?? defaults.flows.kiro.targetId, - defaults.flows.kiro.targetId + nested?.flows?.kiro?.selectedTargetId + ?? nested?.flows?.kiro?.targetId + ?? (activeFlowId === 'kiro' + ? (input?.selectedTargetId ?? input?.targetId) + : undefined) + ?? defaults.flows.kiro.selectedTargetId, + defaults.flows.kiro.selectedTargetId ); const stepExecutionRangeByFlow = isPlainObject(input?.stepExecutionRangeByFlow) ? input.stepExecutionRangeByFlow @@ -212,76 +221,101 @@ }, flows: { openai: { - integrationTargetId: openaiIntegrationTargetId, - integrationTargets: { + selectedTargetId: openaiSelectedTargetId, + targets: { cpa: { - ...defaults.flows.openai.integrationTargets.cpa, - ...getIntegrationTargetValue(nested, (state) => state.flows?.openai?.integrationTargets?.cpa), + ...defaults.flows.openai.targets.cpa, + ...getTargetValue( + nested, + (state) => state.flows?.openai?.targets?.cpa, + (state) => state.flows?.openai?.integrationTargets?.cpa + ), vpsUrl: String( input?.vpsUrl + ?? nested?.flows?.openai?.targets?.cpa?.vpsUrl ?? nested?.flows?.openai?.integrationTargets?.cpa?.vpsUrl ?? '' ).trim(), vpsPassword: String( input?.vpsPassword + ?? nested?.flows?.openai?.targets?.cpa?.vpsPassword ?? nested?.flows?.openai?.integrationTargets?.cpa?.vpsPassword ?? '' ), localCpaStep9Mode: String( input?.localCpaStep9Mode + ?? nested?.flows?.openai?.targets?.cpa?.localCpaStep9Mode ?? nested?.flows?.openai?.integrationTargets?.cpa?.localCpaStep9Mode - ?? defaults.flows.openai.integrationTargets.cpa.localCpaStep9Mode - ).trim() || defaults.flows.openai.integrationTargets.cpa.localCpaStep9Mode, + ?? defaults.flows.openai.targets.cpa.localCpaStep9Mode + ).trim() || defaults.flows.openai.targets.cpa.localCpaStep9Mode, }, sub2api: { - ...defaults.flows.openai.integrationTargets.sub2api, - ...getIntegrationTargetValue(nested, (state) => state.flows?.openai?.integrationTargets?.sub2api), + ...defaults.flows.openai.targets.sub2api, + ...getTargetValue( + nested, + (state) => state.flows?.openai?.targets?.sub2api, + (state) => state.flows?.openai?.integrationTargets?.sub2api + ), sub2apiUrl: String( input?.sub2apiUrl + ?? nested?.flows?.openai?.targets?.sub2api?.sub2apiUrl ?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiUrl ?? '' ).trim(), sub2apiEmail: String( input?.sub2apiEmail + ?? nested?.flows?.openai?.targets?.sub2api?.sub2apiEmail ?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiEmail ?? '' ).trim(), sub2apiPassword: String( input?.sub2apiPassword + ?? nested?.flows?.openai?.targets?.sub2api?.sub2apiPassword ?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiPassword ?? '' ), sub2apiGroupName: String( input?.sub2apiGroupName + ?? nested?.flows?.openai?.targets?.sub2api?.sub2apiGroupName ?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiGroupName - ?? defaults.flows.openai.integrationTargets.sub2api.sub2apiGroupName - ).trim() || defaults.flows.openai.integrationTargets.sub2api.sub2apiGroupName, + ?? defaults.flows.openai.targets.sub2api.sub2apiGroupName + ).trim() || defaults.flows.openai.targets.sub2api.sub2apiGroupName, sub2apiGroupNames: Array.isArray(input?.sub2apiGroupNames) ? input.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean) - : (Array.isArray(nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiGroupNames) - ? nested.flows.openai.integrationTargets.sub2api.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean) - : [...defaults.flows.openai.integrationTargets.sub2api.sub2apiGroupNames]), + : (Array.isArray(nested?.flows?.openai?.targets?.sub2api?.sub2apiGroupNames) + ? nested.flows.openai.targets.sub2api.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean) + : (Array.isArray(nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiGroupNames) + ? nested.flows.openai.integrationTargets.sub2api.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean) + : [...defaults.flows.openai.targets.sub2api.sub2apiGroupNames])), sub2apiAccountPriority: Math.max(1, Number( input?.sub2apiAccountPriority + ?? nested?.flows?.openai?.targets?.sub2api?.sub2apiAccountPriority ?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiAccountPriority - ?? defaults.flows.openai.integrationTargets.sub2api.sub2apiAccountPriority - ) || defaults.flows.openai.integrationTargets.sub2api.sub2apiAccountPriority), + ?? defaults.flows.openai.targets.sub2api.sub2apiAccountPriority + ) || defaults.flows.openai.targets.sub2api.sub2apiAccountPriority), sub2apiDefaultProxyName: String( input?.sub2apiDefaultProxyName + ?? nested?.flows?.openai?.targets?.sub2api?.sub2apiDefaultProxyName ?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiDefaultProxyName ?? '' ).trim(), }, codex2api: { - ...defaults.flows.openai.integrationTargets.codex2api, - ...getIntegrationTargetValue(nested, (state) => state.flows?.openai?.integrationTargets?.codex2api), + ...defaults.flows.openai.targets.codex2api, + ...getTargetValue( + nested, + (state) => state.flows?.openai?.targets?.codex2api, + (state) => state.flows?.openai?.integrationTargets?.codex2api + ), codex2apiUrl: String( input?.codex2apiUrl + ?? nested?.flows?.openai?.targets?.codex2api?.codex2apiUrl ?? nested?.flows?.openai?.integrationTargets?.codex2api?.codex2apiUrl ?? '' ).trim(), codex2apiAdminKey: String( input?.codex2apiAdminKey + ?? nested?.flows?.openai?.targets?.codex2api?.codex2apiAdminKey ?? nested?.flows?.openai?.integrationTargets?.codex2api?.codex2apiAdminKey ?? '' ).trim(), @@ -349,11 +383,14 @@ }, }, kiro: { - targetId: kiroTargetId, + selectedTargetId: kiroSelectedTargetId, targets: { 'kiro-rs': { ...defaults.flows.kiro.targets['kiro-rs'], - ...getIntegrationTargetValue(nested, (state) => state.flows?.kiro?.targets?.['kiro-rs']), + ...getTargetValue( + nested, + (state) => state.flows?.kiro?.targets?.['kiro-rs'] + ), baseUrl: String( input?.kiroRsUrl ?? input?.kiroRsBaseUrl @@ -419,20 +456,24 @@ const normalizedState = normalizeSettingsState(settingsState); const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId); const flowSettings = normalizedState?.flows?.[normalizedFlowId] || {}; - if (normalizedFlowId === 'kiro') { - return normalizeTargetId( - normalizedFlowId, - flowSettings?.targetId, - defaultKiroTargetId - ); - } return normalizeTargetId( normalizedFlowId, - flowSettings?.integrationTargetId, - defaultOpenAiTargetId + flowSettings?.selectedTargetId, + normalizedFlowId === 'kiro' ? defaultKiroTargetId : defaultOpenAiTargetId ); } + function getTargetSettings(settingsState = {}, flowId, targetId = '') { + const normalizedState = normalizeSettingsState(settingsState); + const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId); + const resolvedTargetId = normalizeTargetId( + normalizedFlowId, + targetId || getSelectedTargetId(normalizedState, normalizedFlowId), + getSelectedTargetId(normalizedState, normalizedFlowId) + ); + return cloneValue(normalizedState?.flows?.[normalizedFlowId]?.targets?.[resolvedTargetId] || {}); + } + function buildStepExecutionRangeByFlow(settingsState = {}) { const normalizedState = normalizeSettingsState(settingsState); return { @@ -455,21 +496,19 @@ const openaiState = normalizedState.flows.openai; const kiroState = normalizedState.flows.kiro; next.activeFlowId = normalizedState.activeFlowId; - next.openaiIntegrationTargetId = getSelectedTargetId(normalizedState, 'openai'); - next.kiroTargetId = getSelectedTargetId(normalizedState, 'kiro'); - next.panelMode = next.openaiIntegrationTargetId; - next.vpsUrl = openaiState.integrationTargets.cpa.vpsUrl; - next.vpsPassword = openaiState.integrationTargets.cpa.vpsPassword; - next.localCpaStep9Mode = openaiState.integrationTargets.cpa.localCpaStep9Mode; - next.sub2apiUrl = openaiState.integrationTargets.sub2api.sub2apiUrl; - next.sub2apiEmail = openaiState.integrationTargets.sub2api.sub2apiEmail; - next.sub2apiPassword = openaiState.integrationTargets.sub2api.sub2apiPassword; - next.sub2apiGroupName = openaiState.integrationTargets.sub2api.sub2apiGroupName; - next.sub2apiGroupNames = cloneValue(openaiState.integrationTargets.sub2api.sub2apiGroupNames); - next.sub2apiAccountPriority = openaiState.integrationTargets.sub2api.sub2apiAccountPriority; - next.sub2apiDefaultProxyName = openaiState.integrationTargets.sub2api.sub2apiDefaultProxyName; - next.codex2apiUrl = openaiState.integrationTargets.codex2api.codex2apiUrl; - next.codex2apiAdminKey = openaiState.integrationTargets.codex2api.codex2apiAdminKey; + next.targetId = getSelectedTargetId(normalizedState, normalizedState.activeFlowId); + next.vpsUrl = openaiState.targets.cpa.vpsUrl; + next.vpsPassword = openaiState.targets.cpa.vpsPassword; + next.localCpaStep9Mode = openaiState.targets.cpa.localCpaStep9Mode; + next.sub2apiUrl = openaiState.targets.sub2api.sub2apiUrl; + next.sub2apiEmail = openaiState.targets.sub2api.sub2apiEmail; + next.sub2apiPassword = openaiState.targets.sub2api.sub2apiPassword; + next.sub2apiGroupName = openaiState.targets.sub2api.sub2apiGroupName; + next.sub2apiGroupNames = cloneValue(openaiState.targets.sub2api.sub2apiGroupNames); + next.sub2apiAccountPriority = openaiState.targets.sub2api.sub2apiAccountPriority; + next.sub2apiDefaultProxyName = openaiState.targets.sub2api.sub2apiDefaultProxyName; + next.codex2apiUrl = openaiState.targets.codex2api.codex2apiUrl; + next.codex2apiAdminKey = openaiState.targets.codex2api.codex2apiAdminKey; next.customPassword = normalizedState.services.account.customPassword; next.signupMethod = openaiState.signup.signupMethod; next.phoneVerificationEnabled = openaiState.signup.phoneVerificationEnabled; @@ -497,11 +536,12 @@ const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId); const targetId = getSelectedTargetId(normalizedState, normalizedFlowId); if (normalizedFlowId === 'kiro') { + const targetSettings = getTargetSettings(normalizedState, normalizedFlowId, targetId); return { activeFlowId: normalizedFlowId, targetId, - kiroRsUrl: normalizedState.flows.kiro.targets['kiro-rs'].baseUrl, - kiroRsKey: normalizedState.flows.kiro.targets['kiro-rs'].apiKey, + kiroRsUrl: targetSettings.baseUrl || '', + kiroRsKey: targetSettings.apiKey || '', }; } return { @@ -517,6 +557,7 @@ getFlowInputState, getFlowSettings, getSelectedTargetId, + getTargetSettings, mergeSettingsState, normalizeSettingsState, }; diff --git a/core/flow-kernel/source-registry.js b/core/flow-kernel/source-registry.js new file mode 100644 index 0000000..4b0299c --- /dev/null +++ b/core/flow-kernel/source-registry.js @@ -0,0 +1,486 @@ +(function attachMultiPageSourceRegistry(root, factory) { + root.MultiPageSourceRegistry = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createSourceRegistryModule() { + const rootScope = typeof self !== 'undefined' ? self : globalThis; + const flowRegistryApi = rootScope.MultiPageFlowRegistry || {}; + + const SHARED_SOURCE_DEFINITIONS = Object.freeze({ + 'qq-mail': { + flowId: null, + kind: 'mail-provider', + label: 'QQ \u90ae\u7bb1', + readyPolicy: 'top-frame-only', + family: 'qq-mail-family', + driverId: 'content/qq-mail', + cleanupScopes: [], + detectionMatchers: [ + { hostnames: ['mail.qq.com', 'wx.mail.qq.com'] }, + ], + familyMatchers: [ + { hostnames: ['mail.qq.com', 'wx.mail.qq.com'] }, + ], + }, + 'mail-163': { + flowId: null, + kind: 'mail-provider', + label: '163 \u90ae\u7bb1', + readyPolicy: 'top-frame-only', + family: 'mail-163-family', + driverId: 'content/mail-163', + cleanupScopes: [], + detectionMatchers: [ + { + hostnames: ['mail.163.com', 'mail.126.com', 'webmail.vip.163.com'], + hostnameEndsWith: ['.mail.163.com', '.mail.126.com'], + matchMode: 'any', + }, + ], + familyMatchers: [ + { + hostnames: ['mail.163.com', 'mail.126.com', 'webmail.vip.163.com'], + hostnameEndsWith: ['.mail.163.com', '.mail.126.com'], + matchMode: 'any', + }, + ], + }, + 'gmail-mail': { + flowId: null, + kind: 'mail-provider', + label: 'Gmail \u90ae\u7bb1', + readyPolicy: 'top-frame-only', + family: 'gmail-mail-family', + driverId: 'content/gmail-mail', + cleanupScopes: [], + detectionMatchers: [ + { hostnames: ['mail.google.com'] }, + ], + familyMatchers: [ + { hostnames: ['mail.google.com'] }, + ], + }, + 'icloud-mail': { + flowId: null, + kind: 'mail-provider', + label: 'iCloud \u90ae\u7bb1', + readyPolicy: 'allow-child-frame', + family: 'icloud-mail-family', + driverId: 'content/icloud-mail', + cleanupScopes: [], + detectionMatchers: [ + { hostnames: ['www.icloud.com', 'www.icloud.com.cn'] }, + ], + familyMatchers: [ + { hostnames: ['www.icloud.com', 'www.icloud.com.cn'] }, + ], + }, + 'inbucket-mail': { + flowId: null, + kind: 'mail-provider', + label: 'Inbucket \u90ae\u7bb1', + readyPolicy: 'top-frame-only', + family: 'inbucket-mail-family', + driverId: 'content/inbucket-mail', + cleanupScopes: [], + familyMatchers: [ + { originEqualsReference: true, pathPrefixes: ['/m/'] }, + ], + }, + 'mail-2925': { + flowId: null, + kind: 'mail-provider', + label: '2925 \u90ae\u7bb1', + readyPolicy: 'top-frame-only', + family: 'mail-2925-family', + driverId: 'content/mail-2925', + cleanupScopes: [], + detectionMatchers: [ + { hostnames: ['2925.com', 'www.2925.com'] }, + ], + familyMatchers: [ + { hostnames: ['2925.com', 'www.2925.com'] }, + ], + }, + 'duck-mail': { + flowId: null, + kind: 'mail-provider', + label: 'Duck \u90ae\u7bb1', + readyPolicy: 'allow-child-frame', + family: 'duck-mail-family', + driverId: 'content/duck-mail', + cleanupScopes: [], + detectionMatchers: [ + { urlIncludes: 'duckduckgo.com/email/settings/autofill' }, + ], + familyMatchers: [ + { hostnames: ['duckduckgo.com'], pathPrefixes: ['/email/'] }, + ], + }, + 'unknown-source': { + flowId: null, + kind: 'unknown', + label: '\u672a\u77e5\u6765\u6e90', + readyPolicy: 'disabled', + family: 'unknown-family', + driverId: null, + cleanupScopes: [], + }, + }); + + const SHARED_DRIVER_DEFINITIONS = Object.freeze({ + 'content/qq-mail': { + sourceId: 'qq-mail', + commands: ['POLL_EMAIL'], + }, + 'content/mail-163': { + sourceId: 'mail-163', + commands: ['POLL_EMAIL'], + }, + 'content/gmail-mail': { + sourceId: 'gmail-mail', + commands: ['POLL_EMAIL'], + }, + 'content/icloud-mail': { + sourceId: 'icloud-mail', + commands: ['POLL_EMAIL'], + }, + 'content/mail-2925': { + sourceId: 'mail-2925', + commands: ['POLL_EMAIL'], + }, + 'content/duck-mail': { + sourceId: 'duck-mail', + commands: ['FETCH_ALIAS_EMAIL'], + }, + }); + + function normalizeHostname(hostname = '') { + return String(hostname || '').trim().toLowerCase(); + } + + function matchesNamedHostFamily(hostname = '', family = '') { + const normalizedHost = normalizeHostname(hostname); + const normalizedFamily = normalizeHostname(family); + if (!normalizedHost || !normalizedFamily) { + return false; + } + return normalizedHost === normalizedFamily + || normalizedHost.endsWith(`.${normalizedFamily}`) + || normalizedHost.startsWith(`${normalizedFamily}.`) + || normalizedHost.includes(`.${normalizedFamily}.`); + } + + function parseUrlSafely(rawUrl) { + if (!rawUrl) { + return null; + } + try { + return new URL(rawUrl); + } catch { + return null; + } + } + + function buildCandidateUrl(url = '', hostname = '') { + const candidate = parseUrlSafely(url); + if (candidate) { + return candidate; + } + const normalizedHostname = normalizeHostname(hostname); + if (!normalizedHostname) { + return null; + } + return parseUrlSafely(`https://${normalizedHostname}/`); + } + + function normalizeSourceId(source) { + return String(source || '').trim(); + } + + function normalizeStringArray(values = []) { + if (!Array.isArray(values)) { + return []; + } + return values + .map((value) => String(value || '').trim()) + .filter(Boolean); + } + + function hostnameMatchesSuffix(hostname = '', suffix = '') { + const normalizedHost = normalizeHostname(hostname); + const normalizedSuffix = normalizeHostname(suffix); + if (!normalizedHost || !normalizedSuffix) { + return false; + } + if (normalizedHost === normalizedSuffix) { + return true; + } + const dottedSuffix = normalizedSuffix.startsWith('.') + ? normalizedSuffix + : `.${normalizedSuffix}`; + return normalizedHost.endsWith(dottedSuffix); + } + + function getRuntimeSourceDefinitions() { + return { + ...(typeof flowRegistryApi.getRuntimeSourceDefinitions === 'function' + ? flowRegistryApi.getRuntimeSourceDefinitions() + : {}), + ...SHARED_SOURCE_DEFINITIONS, + }; + } + + function getDriverDefinitions() { + return { + ...(typeof flowRegistryApi.getDriverDefinitions === 'function' + ? flowRegistryApi.getDriverDefinitions() + : {}), + ...SHARED_DRIVER_DEFINITIONS, + }; + } + + function getSourceAliases() { + return { + ...(typeof flowRegistryApi.getSourceAliases === 'function' + ? flowRegistryApi.getSourceAliases() + : {}), + }; + } + + function createSourceRegistry() { + const SOURCE_DEFINITIONS = getRuntimeSourceDefinitions(); + const DRIVER_DEFINITIONS = getDriverDefinitions(); + const SOURCE_ALIASES = getSourceAliases(); + + function resolveCanonicalSource(source) { + const normalized = normalizeSourceId(source); + if (!normalized) { + return ''; + } + return SOURCE_ALIASES[normalized] || normalized; + } + + function getAliasKeysForCanonicalSource(source) { + const canonical = resolveCanonicalSource(source); + return Object.keys(SOURCE_ALIASES).filter((alias) => SOURCE_ALIASES[alias] === canonical); + } + + function getSourceKeys(source) { + const normalized = normalizeSourceId(source); + const canonical = resolveCanonicalSource(normalized); + return Array.from(new Set([ + canonical, + ...getAliasKeysForCanonicalSource(canonical), + normalized, + ].filter(Boolean))); + } + + function getSourceMeta(source) { + const canonical = resolveCanonicalSource(source); + const definition = SOURCE_DEFINITIONS[canonical]; + if (!definition) { + return null; + } + return { + id: canonical, + aliases: getAliasKeysForCanonicalSource(canonical), + ...definition, + }; + } + + function getSourceLabel(source) { + return getSourceMeta(source)?.label || normalizeSourceId(source) || '\u672a\u77e5\u6765\u6e90'; + } + + function getDriverIdForSource(source) { + return getSourceMeta(source)?.driverId || null; + } + + function getDriverMeta(sourceOrDriverId) { + const directDriverId = normalizeSourceId(sourceOrDriverId); + const driverId = Object.prototype.hasOwnProperty.call(DRIVER_DEFINITIONS, directDriverId) + ? directDriverId + : getDriverIdForSource(sourceOrDriverId); + if (!driverId || !Object.prototype.hasOwnProperty.call(DRIVER_DEFINITIONS, driverId)) { + return null; + } + return { + id: driverId, + ...DRIVER_DEFINITIONS[driverId], + }; + } + + function driverAcceptsCommand(sourceOrDriverId, command) { + const normalizedCommand = normalizeSourceId(command); + if (!normalizedCommand) { + return false; + } + const driver = getDriverMeta(sourceOrDriverId); + return Array.isArray(driver?.commands) && driver.commands.includes(normalizedCommand); + } + + function matchesUrlRule(rule = {}, candidate, reference) { + if (!candidate) { + return false; + } + const matchMode = String(rule?.matchMode || 'all').trim().toLowerCase() === 'any' + ? 'any' + : 'all'; + const conditions = []; + + const hostnames = normalizeStringArray(rule.hostnames); + if (hostnames.length) { + conditions.push(hostnames.includes(candidate.hostname)); + } + + const hostnameFamilies = normalizeStringArray(rule.hostnameFamilies); + if (hostnameFamilies.length) { + conditions.push(hostnameFamilies.some((family) => matchesNamedHostFamily(candidate.hostname, family))); + } + + const hostnameEndsWith = normalizeStringArray(rule.hostnameEndsWith); + if (hostnameEndsWith.length) { + conditions.push(hostnameEndsWith.some((suffix) => hostnameMatchesSuffix(candidate.hostname, suffix))); + } + + if (rule.hostnameRegex) { + conditions.push(new RegExp(rule.hostnameRegex, rule.hostnameRegexFlags || '').test(candidate.hostname)); + } + + if (rule.urlIncludes) { + conditions.push(String(candidate.href || '').includes(String(rule.urlIncludes))); + } + + const pathPrefixes = normalizeStringArray(rule.pathPrefixes); + if (pathPrefixes.length) { + conditions.push(pathPrefixes.some((prefix) => candidate.pathname.startsWith(prefix))); + } + + if (rule.pathEquals) { + conditions.push(candidate.pathname === String(rule.pathEquals)); + } + + const pathEqualsOneOf = normalizeStringArray(rule.pathEqualsOneOf); + if (pathEqualsOneOf.length) { + conditions.push(pathEqualsOneOf.includes(candidate.pathname)); + } + + if (rule.originEqualsReference) { + conditions.push(Boolean(reference) && candidate.origin === reference.origin); + } + + if (rule.pathEqualsReference) { + conditions.push(Boolean(reference) && candidate.pathname === reference.pathname); + } + + if (!conditions.length) { + return false; + } + + return matchMode === 'any' + ? conditions.some(Boolean) + : conditions.every(Boolean); + } + + function matchesSourceDetection(source, url = '', hostname = '') { + const candidate = buildCandidateUrl(url, hostname); + if (!candidate) { + return false; + } + const canonical = resolveCanonicalSource(source); + const detectionMatchers = getSourceMeta(canonical)?.detectionMatchers || []; + return detectionMatchers.some((rule) => matchesUrlRule(rule, candidate, null)); + } + + function isSignupPageHost(hostname = '') { + return matchesSourceDetection('openai-auth', '', hostname); + } + + function isSignupEntryHost(hostname = '') { + return matchesSourceDetection('chatgpt', '', hostname); + } + + function is163MailHost(hostname = '') { + return matchesSourceDetection('mail-163', '', hostname); + } + + function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) { + const candidate = parseUrlSafely(candidateUrl); + if (!candidate) { + return false; + } + const canonical = resolveCanonicalSource(source); + const familyMatchers = getSourceMeta(canonical)?.familyMatchers || []; + const reference = parseUrlSafely(referenceUrl); + return familyMatchers.some((rule) => matchesUrlRule(rule, candidate, reference)); + } + + function detectSourceFromLocation({ + injectedSource, + url = '', + hostname = '', + } = {}) { + if (injectedSource) { + return resolveCanonicalSource(injectedSource); + } + + const candidate = buildCandidateUrl(url, hostname); + if (!candidate) { + return 'unknown-source'; + } + + const detectionSourceIds = Object.keys(SOURCE_DEFINITIONS).filter((sourceId) => sourceId !== 'unknown-source'); + for (const sourceId of detectionSourceIds) { + if (matchesSourceDetection(sourceId, candidate.href, candidate.hostname)) { + return sourceId; + } + } + + return 'unknown-source'; + } + + function shouldReportReadyForFrame(source, isChildFrame) { + const canonical = resolveCanonicalSource(source); + const readyPolicy = getSourceMeta(canonical)?.readyPolicy || 'allow-child-frame'; + if (readyPolicy === 'disabled') { + return false; + } + if (!isChildFrame) { + return true; + } + return readyPolicy === 'allow-child-frame'; + } + + function getCleanupOwnerSource(cleanupScope) { + const normalizedCleanupScope = String(cleanupScope || '').trim(); + const owner = Object.keys(SOURCE_DEFINITIONS).find((sourceId) => { + const cleanupScopes = Array.isArray(SOURCE_DEFINITIONS[sourceId]?.cleanupScopes) + ? SOURCE_DEFINITIONS[sourceId].cleanupScopes + : []; + return cleanupScopes.includes(normalizedCleanupScope); + }); + return resolveCanonicalSource(owner || ''); + } + + return { + detectSourceFromLocation, + driverAcceptsCommand, + getCleanupOwnerSource, + getDriverIdForSource, + getDriverMeta, + getSourceKeys, + getSourceLabel, + getSourceMeta, + is163MailHost, + isSignupEntryHost, + isSignupPageHost, + matchesSourceUrlFamily, + parseUrlSafely, + resolveCanonicalSource, + shouldReportReadyForFrame, + }; + } + + return { + createSourceRegistry, + }; +}); diff --git a/background/steps/registry.js b/core/flow-kernel/step-registry.js similarity index 100% rename from background/steps/registry.js rename to core/flow-kernel/step-registry.js diff --git a/background/tab-runtime.js b/core/flow-kernel/tab-runtime.js similarity index 99% rename from background/tab-runtime.js rename to core/flow-kernel/tab-runtime.js index 0844096..cba669b 100644 --- a/background/tab-runtime.js +++ b/core/flow-kernel/tab-runtime.js @@ -76,7 +76,7 @@ if (sourceRegistry?.getCleanupOwnerSource) { return sourceRegistry.getCleanupOwnerSource(cleanupScope); } - return cleanupScope === 'oauth-localhost-callback' ? 'signup-page' : ''; + return cleanupScope === 'oauth-localhost-callback' ? 'openai-auth' : ''; } function normalizeAutomationWindowId(value) { diff --git a/background/workflow-engine.js b/core/flow-kernel/workflow-engine.js similarity index 100% rename from background/workflow-engine.js rename to core/flow-kernel/workflow-engine.js diff --git a/data/step-definitions.js b/data/step-definitions.js index b6ea992..e610744 100644 --- a/data/step-definitions.js +++ b/data/step-definitions.js @@ -1,589 +1,80 @@ (function attachStepDefinitions(root, factory) { root.MultiPageStepDefinitions = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createStepDefinitionsModule() { + const rootScope = typeof self !== 'undefined' ? self : globalThis; + const flowsIndexApi = rootScope.MultiPageFlowsIndex || {}; + const DEFAULT_ACTIVE_FLOW_ID = 'openai'; - const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal'; - const PLUS_PAYMENT_METHOD_PAYPAL_HOSTED = 'paypal-hosted'; - const PLUS_PAYMENT_METHOD_GOPAY = 'gopay'; - const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper'; - const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth'; - const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session'; - const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session'; - const PLUS_PAYMENT_STEP_KEY = 'paypal-approve'; const SIGNUP_METHOD_EMAIL = 'email'; const SIGNUP_METHOD_PHONE = 'phone'; - const NORMAL_PREFIX_STEP_DEFINITIONS = [ - { id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' }, - { id: 2, order: 20, key: 'submit-signup-email', title: '注册并输入邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-signup-email' }, - { id: 3, order: 30, key: 'fill-password', title: '填写密码并继续', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-password' }, - { id: 4, order: 40, key: 'fetch-signup-code', title: '获取注册验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-signup-code' }, - { id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-profile' }, - { id: 6, order: 60, key: 'wait-registration-success', title: '等待注册成功', sourceId: 'chatgpt', driverId: null, command: 'wait-registration-success' }, - ]; - - const PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS = [ - { id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' }, - { id: 2, order: 20, key: 'submit-signup-email', title: '注册并输入邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-signup-email' }, - { id: 3, order: 30, key: 'fill-password', title: '填写密码并继续', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-password' }, - { id: 4, order: 40, key: 'fetch-signup-code', title: '获取注册验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-signup-code' }, - { id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-profile' }, - { id: 6, order: 60, key: 'plus-checkout-create', title: '创建 Plus Checkout', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-create' }, - { id: 7, order: 70, key: 'plus-checkout-billing', title: '填写账单并提交订单', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-billing' }, - { id: 8, order: 80, key: 'paypal-approve', title: 'PayPal 登录与授权', sourceId: 'paypal-flow', driverId: 'content/paypal-flow', command: 'paypal-approve' }, - { id: 9, order: 90, key: 'plus-checkout-return', title: '订阅回跳确认', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-return' }, - ]; - const PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS = [ - ...PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS.slice(0, 6), - { id: 7, order: 70, key: 'paypal-hosted-email', title: '无卡直绑填写 PayPal 邮箱', sourceId: 'paypal-flow', driverId: 'content/paypal-flow', command: 'paypal-hosted-email' }, - { id: 8, order: 80, key: 'paypal-hosted-card', title: '无卡直绑填写 PayPal 资料', sourceId: 'paypal-flow', driverId: 'content/paypal-flow', command: 'paypal-hosted-card' }, - { id: 9, order: 90, key: 'paypal-hosted-create-account', title: '无卡直绑确认创建 PayPal', sourceId: 'paypal-flow', driverId: 'content/paypal-flow', command: 'paypal-hosted-create-account' }, - { id: 10, order: 100, key: 'paypal-hosted-review', title: '无卡直绑完成 PayPal 授权', sourceId: 'paypal-flow', driverId: 'content/paypal-flow', command: 'paypal-hosted-review' }, - ]; - - const PLUS_GOPAY_PREFIX_STEP_DEFINITIONS = [ - { id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' }, - { id: 2, order: 20, key: 'submit-signup-email', title: '注册并输入邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-signup-email' }, - { id: 3, order: 30, key: 'fill-password', title: '填写密码并继续', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-password' }, - { id: 4, order: 40, key: 'fetch-signup-code', title: '获取注册验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-signup-code' }, - { id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-profile' }, - { id: 6, order: 60, key: 'plus-checkout-create', title: '打开 GoPay 订阅页', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-create' }, - { id: 7, order: 70, key: 'gopay-subscription-confirm', title: '等待 GoPay 订阅确认', sourceId: 'gopay-flow', driverId: 'content/gopay-flow', command: 'gopay-subscription-confirm' }, - ]; - - const PLUS_GPC_PREFIX_STEP_DEFINITIONS = [ - { id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' }, - { id: 2, order: 20, key: 'submit-signup-email', title: '注册并输入邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-signup-email' }, - { id: 3, order: 30, key: 'fill-password', title: '填写密码并继续', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-password' }, - { id: 4, order: 40, key: 'fetch-signup-code', title: '获取注册验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-signup-code' }, - { id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-profile' }, - { id: 6, order: 60, key: 'plus-checkout-create', title: '创建 GPC 订单', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-create' }, - { id: 7, order: 70, key: 'plus-checkout-billing', title: '等待 GPC 任务完成', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-billing' }, - ]; - - function isPhoneSignupReloginAfterBindEmailEnabled(options = {}) { - return Boolean(options?.phoneSignupReloginAfterBindEmailEnabled); - } - - function createOpenAiAuthTail(startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL, options = {}) { - const id = Number(startId) || 7; - const order = Number(startOrder) || id * 10; - const commonStart = [ - { id, order, key: 'oauth-login', title: '刷新 OAuth 并登录', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'oauth-login' }, - { id: id + 1, order: order + 10, key: 'fetch-login-code', title: '获取登录验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-login-code' }, - ]; - - if (signupMethod === SIGNUP_METHOD_PHONE) { - if (isPhoneSignupReloginAfterBindEmailEnabled(options)) { - return [ - ...commonStart, - { id: id + 2, order: order + 20, key: 'bind-email', title: '绑定邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'bind-email' }, - { id: id + 3, order: order + 30, key: 'fetch-bind-email-code', title: '获取绑定邮箱验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fetch-bind-email-code', mailRuleId: 'openai-login-code' }, - { id: id + 4, order: order + 40, key: 'relogin-bound-email', title: '绑定邮箱后刷新 OAuth 并登录(邮箱)', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'oauth-login' }, - { id: id + 5, order: order + 50, key: 'fetch-bound-email-login-code', title: '获取登录验证码(邮箱)', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-login-code' }, - { id: id + 6, order: order + 60, key: 'post-bound-email-phone-verification', title: '手机号验证', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'post-login-phone-verification' }, - { id: id + 7, order: order + 70, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' }, - { id: id + 8, order: order + 80, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' }, - ]; - } - return [ - ...commonStart, - { id: id + 2, order: order + 20, key: 'bind-email', title: '绑定邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'bind-email' }, - { id: id + 3, order: order + 30, key: 'fetch-bind-email-code', title: '获取绑定邮箱验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fetch-bind-email-code', mailRuleId: 'openai-login-code' }, - { id: id + 4, order: order + 40, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' }, - { id: id + 5, order: order + 50, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' }, - ]; + function normalizeRegisteredFlowIds(values = []) { + if (!Array.isArray(values)) { + return []; } - - return [ - ...commonStart, - { id: id + 2, order: order + 20, key: 'post-login-phone-verification', title: '手机号验证', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'post-login-phone-verification' }, - { id: id + 3, order: order + 30, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' }, - { id: id + 4, order: order + 40, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' }, - ]; + const seen = new Set(); + return values + .map((value) => String(value || '').trim().toLowerCase()) + .filter((value) => { + if (!value || seen.has(value)) { + return false; + } + seen.add(value); + return true; + }); } - function createSub2ApiSessionImportTail(startId, startOrder) { - const id = Number(startId) || 10; - const order = Number(startOrder) || id * 10; - return [ - { - id, - order, - key: 'sub2api-session-import', - title: '导入当前 ChatGPT 会话到 SUB2API', - sourceId: 'sub2api-panel', - driverId: 'background/sub2api-session-import', - command: 'sub2api-session-import', - }, - ]; + function getRegisteredFlowIds() { + return normalizeRegisteredFlowIds( + typeof flowsIndexApi.getRegisteredFlowIds === 'function' + ? flowsIndexApi.getRegisteredFlowIds() + : [DEFAULT_ACTIVE_FLOW_ID] + ).filter((flowId) => Boolean(getFlowWorkflow(flowId))); } - function createCpaSessionImportTail(startId, startOrder) { - const id = Number(startId) || 10; - const order = Number(startOrder) || id * 10; - return [ - { - id, - order, - key: 'cpa-session-import', - title: '导入当前 ChatGPT 会话到 CPA', - sourceId: 'vps-panel', - driverId: 'background/cpa-session-import', - command: 'cpa-session-import', - }, - ]; + function getFlowWorkflow(flowId) { + return typeof flowsIndexApi.getFlowWorkflow === 'function' + ? flowsIndexApi.getFlowWorkflow(flowId) + : null; } - function normalizePlusAccountAccessStrategy(value = '') { - const normalized = String(value || '').trim().toLowerCase(); - if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) { - return PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION; - } - if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) { - return PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION; - } - return PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; - } - - function resolvePlusSessionImportTail(options = {}, signupMethod = SIGNUP_METHOD_EMAIL) { - if (signupMethod !== SIGNUP_METHOD_EMAIL) { - return null; - } - const strategy = normalizePlusAccountAccessStrategy(options?.plusAccountAccessStrategy); - if (strategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) { - return createSub2ApiSessionImportTail; - } - if (strategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) { - return createCpaSessionImportTail; - } - return null; - } - - function createOpenAiSteps(prefixSteps, startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL, options = {}) { - const sessionTailFactory = resolvePlusSessionImportTail(options, signupMethod); - const tailSteps = sessionTailFactory - ? sessionTailFactory(startId, startOrder) - : createOpenAiAuthTail(startId, startOrder, signupMethod, options); - return [ - ...prefixSteps, - ...tailSteps, - ]; - } - - function createHostedCheckoutAuthTail(startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL, options = {}) { - const id = Number(startId) || 7; - const order = Number(startOrder) || id * 10; - const commonStart = [ - { id, order, key: 'oauth-login', title: '刷新 OAuth 并登录', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'oauth-login' }, - { id: id + 1, order: order + 10, key: 'fetch-login-code', title: '获取登录验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-login-code' }, - ]; - - if (signupMethod === SIGNUP_METHOD_PHONE) { - if (isPhoneSignupReloginAfterBindEmailEnabled(options)) { - return [ - ...commonStart, - { id: id + 2, order: order + 20, key: 'bind-email', title: '绑定邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'bind-email' }, - { id: id + 3, order: order + 30, key: 'fetch-bind-email-code', title: '获取绑定邮箱验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fetch-bind-email-code', mailRuleId: 'openai-login-code' }, - { id: id + 4, order: order + 40, key: 'relogin-bound-email', title: '绑定邮箱后刷新 OAuth 并登录(邮箱)', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'oauth-login' }, - { id: id + 5, order: order + 50, key: 'fetch-bound-email-login-code', title: '获取登录验证码(邮箱)', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-login-code' }, - { id: id + 6, order: order + 60, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' }, - { id: id + 7, order: order + 70, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' }, - ]; - } - return [ - ...commonStart, - { id: id + 2, order: order + 20, key: 'bind-email', title: '绑定邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'bind-email' }, - { id: id + 3, order: order + 30, key: 'fetch-bind-email-code', title: '获取绑定邮箱验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fetch-bind-email-code', mailRuleId: 'openai-login-code' }, - { id: id + 4, order: order + 40, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' }, - { id: id + 5, order: order + 50, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' }, - ]; - } - - return [ - ...commonStart, - { id: id + 2, order: order + 20, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' }, - { id: id + 3, order: order + 30, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' }, - ]; - } - - function createHostedCheckoutSteps(prefixSteps, startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL, options = {}) { - const sessionTailFactory = resolvePlusSessionImportTail(options, signupMethod); - const tailSteps = sessionTailFactory - ? sessionTailFactory(startId, startOrder) - : createHostedCheckoutAuthTail(startId, startOrder, signupMethod, options); - return [ - ...prefixSteps, - ...tailSteps, - ]; - } - - const NORMAL_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_EMAIL); - const NORMAL_PHONE_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_PHONE); - const NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true }); - const PLUS_PAYPAL_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL); - const PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS = createOpenAiSteps( - PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, - 10, - 100, - SIGNUP_METHOD_EMAIL, - { plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION } - ); - const PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS = createOpenAiSteps( - PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, - 10, - 100, - SIGNUP_METHOD_EMAIL, - { plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION } - ); - const PLUS_PAYPAL_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE); - const PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true }); - const PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS = createHostedCheckoutSteps(PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS, 11, 110, SIGNUP_METHOD_EMAIL); - const PLUS_PAYPAL_HOSTED_CHECKOUT_SUB2API_SESSION_STEP_DEFINITIONS = createHostedCheckoutSteps( - PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS, - 11, - 110, - SIGNUP_METHOD_EMAIL, - { plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION } - ); - const PLUS_PAYPAL_HOSTED_CHECKOUT_CPA_SESSION_STEP_DEFINITIONS = createHostedCheckoutSteps( - PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS, - 11, - 110, - SIGNUP_METHOD_EMAIL, - { plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION } - ); - const PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_STEP_DEFINITIONS = createHostedCheckoutSteps(PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS, 11, 110, SIGNUP_METHOD_PHONE); - const PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createHostedCheckoutSteps(PLUS_PAYPAL_HOSTED_CHECKOUT_PREFIX_STEP_DEFINITIONS, 11, 110, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true }); - const PLUS_GOPAY_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL); - const PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS = createOpenAiSteps( - PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, - 10, - 100, - SIGNUP_METHOD_EMAIL, - { plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION } - ); - const PLUS_GOPAY_CPA_SESSION_STEP_DEFINITIONS = createOpenAiSteps( - PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, - 10, - 100, - SIGNUP_METHOD_EMAIL, - { plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION } - ); - const PLUS_GOPAY_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE); - const PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true }); - const PLUS_GPC_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL); - const PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS = createOpenAiSteps( - PLUS_GPC_PREFIX_STEP_DEFINITIONS, - 10, - 100, - SIGNUP_METHOD_EMAIL, - { plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION } - ); - const PLUS_GPC_CPA_SESSION_STEP_DEFINITIONS = createOpenAiSteps( - PLUS_GPC_PREFIX_STEP_DEFINITIONS, - 10, - 100, - SIGNUP_METHOD_EMAIL, - { plusAccountAccessStrategy: PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION } - ); - const PLUS_GPC_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE); - const PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE, { phoneSignupReloginAfterBindEmailEnabled: true }); - const KIRO_STEP_DEFINITIONS = [ - { - id: 1, - order: 10, - key: 'kiro-open-register-page', - title: '打开注册页', - sourceId: 'kiro-register-page', - driverId: 'background/kiro-register', - command: 'kiro-open-register-page', - }, - { - id: 2, - order: 20, - key: 'kiro-submit-email', - title: '获取邮箱并继续', - sourceId: 'kiro-register-page', - driverId: 'background/kiro-register', - command: 'kiro-submit-email', - }, - { - id: 3, - order: 30, - key: 'kiro-submit-name', - title: '填写姓名并继续', - sourceId: 'kiro-register-page', - driverId: 'background/kiro-register', - command: 'kiro-submit-name', - }, - { - id: 4, - order: 40, - key: 'kiro-submit-verification-code', - title: '获取验证码并继续', - sourceId: 'kiro-register-page', - driverId: 'background/kiro-register', - command: 'kiro-submit-verification-code', - }, - { - id: 5, - order: 50, - key: 'kiro-submit-password', - title: '设置密码并继续', - sourceId: 'kiro-register-page', - driverId: 'background/kiro-register', - command: 'kiro-submit-password', - }, - { - id: 6, - order: 60, - key: 'kiro-complete-register-consent', - title: '完成注册授权', - sourceId: 'kiro-register-page', - driverId: 'background/kiro-register', - command: 'kiro-complete-register-consent', - }, - { - id: 7, - order: 70, - key: 'kiro-start-desktop-authorize', - title: '启动桌面授权', - sourceId: 'kiro-desktop-authorize', - driverId: 'background/kiro-desktop-authorize', - command: 'kiro-start-desktop-authorize', - }, - { - id: 8, - order: 80, - key: 'kiro-complete-desktop-authorize', - title: '完成桌面授权', - sourceId: 'kiro-desktop-authorize', - driverId: 'background/kiro-desktop-authorize', - command: 'kiro-complete-desktop-authorize', - }, - { - id: 9, - order: 90, - key: 'kiro-upload-credential', - title: '上传凭据到 kiro.rs', - sourceId: 'kiro-rs-admin', - driverId: 'background/kiro-publisher-kiro-rs', - command: 'kiro-upload-credential', - }, - ]; - - const PHONE_SIGNUP_TITLE_OVERRIDES = Object.freeze({ - 'submit-signup-email': '注册并输入手机号', - 'fetch-signup-code': '获取手机验证码', - }); - const KIRO_CONTRIBUTION_STEP_TITLE = '贡献上传'; - - function isPlusModeEnabled(options = {}) { - return Boolean(options?.plusModeEnabled || options?.plusMode); - } - - function normalizePlusPaymentMethod(value = '') { - const normalized = String(value || '').trim().toLowerCase(); - if (normalized === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED || normalized === 'paypal_direct' || normalized === 'paypal-direct') { - return PLUS_PAYMENT_METHOD_PAYPAL_HOSTED; - } - if (normalized === PLUS_PAYMENT_METHOD_GPC_HELPER) { - return PLUS_PAYMENT_METHOD_GPC_HELPER; - } - return normalized === PLUS_PAYMENT_METHOD_GOPAY ? PLUS_PAYMENT_METHOD_GOPAY : PLUS_PAYMENT_METHOD_PAYPAL; - } - - function normalizeSignupMethod(value = '') { - return String(value || '').trim().toLowerCase() === SIGNUP_METHOD_PHONE - ? SIGNUP_METHOD_PHONE - : SIGNUP_METHOD_EMAIL; + function hasFlow(flowId) { + return Boolean(getFlowWorkflow(flowId)); } function normalizeActiveFlowId(value = '', fallback = DEFAULT_ACTIVE_FLOW_ID) { const normalized = String(value || '').trim().toLowerCase(); - if (normalized) { + if (hasFlow(normalized)) { return normalized; } const fallbackValue = String(fallback || '').trim().toLowerCase(); - return fallbackValue || DEFAULT_ACTIVE_FLOW_ID; + if (hasFlow(fallbackValue)) { + return fallbackValue; + } + return getRegisteredFlowIds()[0] || DEFAULT_ACTIVE_FLOW_ID; } - function getResolvedSignupMethod(options = {}) { - return normalizeSignupMethod(options?.resolvedSignupMethod || options?.signupMethod); - } - - function getOpenAiModeStepDefinitions(options = {}) { - const signupMethod = getResolvedSignupMethod(options); - const reloginAfterBindEmail = signupMethod === SIGNUP_METHOD_PHONE - && isPhoneSignupReloginAfterBindEmailEnabled(options); - if (!isPlusModeEnabled(options)) { - if (signupMethod === SIGNUP_METHOD_PHONE) { - return reloginAfterBindEmail - ? NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS - : NORMAL_PHONE_STEP_DEFINITIONS; - } - return NORMAL_STEP_DEFINITIONS; - } - const paymentMethod = normalizePlusPaymentMethod(options?.plusPaymentMethod || options?.paymentMethod); - const plusAccountAccessStrategy = normalizePlusAccountAccessStrategy(options?.plusAccountAccessStrategy); - if (paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED) { - if (signupMethod === SIGNUP_METHOD_PHONE) { - return reloginAfterBindEmail - ? PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS - : PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_STEP_DEFINITIONS; - } - if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) { - return PLUS_PAYPAL_HOSTED_CHECKOUT_SUB2API_SESSION_STEP_DEFINITIONS; - } - if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) { - return PLUS_PAYPAL_HOSTED_CHECKOUT_CPA_SESSION_STEP_DEFINITIONS; - } - return PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS; - } - if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) { - if (signupMethod === SIGNUP_METHOD_PHONE) { - return reloginAfterBindEmail - ? PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS - : PLUS_GPC_PHONE_STEP_DEFINITIONS; - } - if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) { - return PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS; - } - if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) { - return PLUS_GPC_CPA_SESSION_STEP_DEFINITIONS; - } - return PLUS_GPC_STEP_DEFINITIONS; - } - if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY) { - if (signupMethod === SIGNUP_METHOD_PHONE) { - return reloginAfterBindEmail - ? PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS - : PLUS_GOPAY_PHONE_STEP_DEFINITIONS; - } - if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) { - return PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS; - } - if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) { - return PLUS_GOPAY_CPA_SESSION_STEP_DEFINITIONS; - } - return PLUS_GOPAY_STEP_DEFINITIONS; - } - if (signupMethod === SIGNUP_METHOD_PHONE) { - return reloginAfterBindEmail - ? PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS - : PLUS_PAYPAL_PHONE_STEP_DEFINITIONS; - } - if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) { - return PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS; - } - if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) { - return PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS; - } - return PLUS_PAYPAL_STEP_DEFINITIONS; - } - - function getOpenAiPlusPaymentStepTitle(options = {}) { - if (!isPlusModeEnabled(options)) { - return ''; - } - const paymentStep = getOpenAiModeStepDefinitions({ - ...options, - plusModeEnabled: true, - }).find((step) => step.key === PLUS_PAYMENT_STEP_KEY); - return paymentStep?.title || ''; - } - - function getOpenAiResolvedStepTitle(step = {}, options = {}) { - if (isPlusModeEnabled(options) && step.key === PLUS_PAYMENT_STEP_KEY) { - return getOpenAiPlusPaymentStepTitle(options) || step.title; - } - const signupMethod = getResolvedSignupMethod(options); - if (signupMethod === SIGNUP_METHOD_PHONE && PHONE_SIGNUP_TITLE_OVERRIDES[step.key]) { - return PHONE_SIGNUP_TITLE_OVERRIDES[step.key]; - } - return step.title; - } - - function isKiroContributionModeEnabled(options = {}) { - return Boolean(options?.accountContributionEnabled || options?.state?.accountContributionEnabled); - } - - const FLOW_DEFINITION_BUILDERS = Object.freeze({ - openai: { - getAllSteps() { - const keyed = new Map(); - for (const step of [ - ...NORMAL_STEP_DEFINITIONS, - ...NORMAL_PHONE_STEP_DEFINITIONS, - ...NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, - ...PLUS_PAYPAL_STEP_DEFINITIONS, - ...PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS, - ...PLUS_PAYPAL_CPA_SESSION_STEP_DEFINITIONS, - ...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS, - ...PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, - ...PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS, - ...PLUS_PAYPAL_HOSTED_CHECKOUT_SUB2API_SESSION_STEP_DEFINITIONS, - ...PLUS_PAYPAL_HOSTED_CHECKOUT_CPA_SESSION_STEP_DEFINITIONS, - ...PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_STEP_DEFINITIONS, - ...PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, - ...PLUS_GOPAY_STEP_DEFINITIONS, - ...PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS, - ...PLUS_GOPAY_CPA_SESSION_STEP_DEFINITIONS, - ...PLUS_GOPAY_PHONE_STEP_DEFINITIONS, - ...PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, - ...PLUS_GPC_STEP_DEFINITIONS, - ...PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS, - ...PLUS_GPC_CPA_SESSION_STEP_DEFINITIONS, - ...PLUS_GPC_PHONE_STEP_DEFINITIONS, - ...PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, - ]) { - keyed.set(`${step.id}:${step.key}`, step); - } - return Array.from(keyed.values()).sort((left, right) => { - const leftOrder = Number.isFinite(left.order) ? left.order : left.id; - const rightOrder = Number.isFinite(right.order) ? right.order : right.id; - if (leftOrder !== rightOrder) return leftOrder - rightOrder; - return left.id - right.id; - }); - }, - getModeStepDefinitions: getOpenAiModeStepDefinitions, - getPlusPaymentStepTitle: getOpenAiPlusPaymentStepTitle, - resolveStepTitle: getOpenAiResolvedStepTitle, - }, - kiro: { - getAllSteps() { - return KIRO_STEP_DEFINITIONS; - }, - getModeStepDefinitions() { - return KIRO_STEP_DEFINITIONS; - }, - getPlusPaymentStepTitle() { - return ''; - }, - resolveStepTitle(step, options = {}) { - if (step?.key === 'kiro-upload-credential' && isKiroContributionModeEnabled(options)) { - return KIRO_CONTRIBUTION_STEP_TITLE; - } - return step?.title || ''; - }, - }, - }); - - function hasFlow(flowId) { - const normalizedFlowId = normalizeActiveFlowId(flowId, ''); - return Boolean(normalizedFlowId && FLOW_DEFINITION_BUILDERS[normalizedFlowId]); - } - - function getRegisteredFlowIds() { - return Object.keys(FLOW_DEFINITION_BUILDERS); + function hasExplicitActiveFlowId(options = {}) { + return Boolean( + options + && typeof options === 'object' + && Object.prototype.hasOwnProperty.call(options, 'activeFlowId') + && String(options.activeFlowId || '').trim() + ); } function getFlowDefinitionBuilder(options = {}) { - const flowId = normalizeActiveFlowId(options?.activeFlowId, DEFAULT_ACTIVE_FLOW_ID); + const rawFlowId = String(options?.activeFlowId || '').trim().toLowerCase(); + if (hasExplicitActiveFlowId(options) && rawFlowId && !hasFlow(rawFlowId)) { + return { + flowId: rawFlowId, + builder: null, + }; + } + const flowId = normalizeActiveFlowId(rawFlowId, DEFAULT_ACTIVE_FLOW_ID); return { flowId, - builder: FLOW_DEFINITION_BUILDERS[flowId] || null, + builder: getFlowWorkflow(flowId), }; } @@ -656,7 +147,9 @@ } return cloneNodes(builder.getAllSteps(options), options, flowId) .sort((left, right) => { - if (left.displayOrder !== right.displayOrder) return left.displayOrder - right.displayOrder; + if (left.displayOrder !== right.displayOrder) { + return left.displayOrder - right.displayOrder; + } return left.nodeId.localeCompare(right.nodeId); }); } @@ -712,7 +205,7 @@ } function getWorkflow(options = {}) { - const flowId = normalizeActiveFlowId(options?.activeFlowId, DEFAULT_ACTIVE_FLOW_ID); + const { flowId } = getFlowDefinitionBuilder(options); const nodes = getNodes(options); return { flowId, @@ -722,34 +215,32 @@ }; } + const defaultWorkflowBuilder = getFlowWorkflow(DEFAULT_ACTIVE_FLOW_ID); + const STEP_DEFINITIONS = cloneSteps( + defaultWorkflowBuilder?.getVariantStepDefinitions + ? defaultWorkflowBuilder.getVariantStepDefinitions('normal') + : (defaultWorkflowBuilder?.getModeStepDefinitions?.({ activeFlowId: DEFAULT_ACTIVE_FLOW_ID }) || []), + {}, + DEFAULT_ACTIVE_FLOW_ID + ); + const NORMAL_STEP_DEFINITIONS = STEP_DEFINITIONS; + const PLUS_STEP_DEFINITIONS = cloneSteps( + defaultWorkflowBuilder?.getVariantStepDefinitions + ? defaultWorkflowBuilder.getVariantStepDefinitions('plusPaypal') + : [], + { plusModeEnabled: true }, + DEFAULT_ACTIVE_FLOW_ID + ); + return { DEFAULT_ACTIVE_FLOW_ID, - STEP_DEFINITIONS: NORMAL_STEP_DEFINITIONS, NORMAL_STEP_DEFINITIONS, - NORMAL_PHONE_STEP_DEFINITIONS, - NORMAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, - PLUS_STEP_DEFINITIONS: PLUS_PAYPAL_STEP_DEFINITIONS, - PLUS_PAYPAL_STEP_DEFINITIONS, - PLUS_PAYPAL_SUB2API_SESSION_STEP_DEFINITIONS, - PLUS_PAYPAL_PHONE_STEP_DEFINITIONS, - PLUS_PAYPAL_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, - PLUS_PAYPAL_HOSTED_CHECKOUT_STEP_DEFINITIONS, - PLUS_PAYPAL_HOSTED_CHECKOUT_SUB2API_SESSION_STEP_DEFINITIONS, - PLUS_PAYPAL_HOSTED_CHECKOUT_CPA_SESSION_STEP_DEFINITIONS, - PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_STEP_DEFINITIONS, - PLUS_PAYPAL_HOSTED_CHECKOUT_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, - PLUS_GOPAY_STEP_DEFINITIONS, - PLUS_GOPAY_SUB2API_SESSION_STEP_DEFINITIONS, - PLUS_GOPAY_PHONE_STEP_DEFINITIONS, - PLUS_GOPAY_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, - PLUS_GPC_STEP_DEFINITIONS, - PLUS_GPC_SUB2API_SESSION_STEP_DEFINITIONS, - PLUS_GPC_PHONE_STEP_DEFINITIONS, - PLUS_GPC_PHONE_BOUND_EMAIL_RELOGIN_STEP_DEFINITIONS, + PLUS_STEP_DEFINITIONS, SIGNUP_METHOD_EMAIL, SIGNUP_METHOD_PHONE, - getAllSteps, + STEP_DEFINITIONS, getAllNodes, + getAllSteps, getLastStepId, getNodeByDisplayOrder, getNodeById, @@ -762,9 +253,6 @@ getSteps, getWorkflow, hasFlow, - isPlusModeEnabled, normalizeActiveFlowId, - normalizePlusPaymentMethod, - normalizeSignupMethod, }; }); diff --git a/docs/md/步骤与Flow节点化重构开发方案.md b/docs/md/步骤与Flow节点化重构开发方案.md index 4edf830..1286260 100644 --- a/docs/md/步骤与Flow节点化重构开发方案.md +++ b/docs/md/步骤与Flow节点化重构开发方案.md @@ -1,4 +1,4 @@ -# 步骤与Flow节点化重构开发方案 +# 步骤与Flow节点化重构开发方案 ## 1. 文档目标 @@ -19,17 +19,17 @@ | --- | --- | --- | | 状态 | `background.js` 里仍有 `currentStep`、`stepStatuses` | 数字步骤同时承担状态、顺序和恢复锚点 | | 协议 | `EXECUTE_STEP`、`STEP_COMPLETE`、`STEP_ERROR`、`SKIP_STEP` | 消息协议仍以数字 step 为核心 | -| 定义 | `data/step-definitions.js`、`background/steps/registry.js` | 只有 OpenAI 真正注册,且执行仍按数字 id | +| 定义 | `data/step-definitions.js`、`core/flow-kernel/step-registry.js` | 只有 OpenAI 真正注册,且执行仍按数字 id | | UI | `sidepanel/sidepanel.js` 按 step 渲染、发送、跳过 | 侧边栏本质上还是数字步骤面板 | -| 内容脚本 | `content/signup-page.js` 按 `message.step` 分支 | 页面驱动逻辑被 step number 绑死 | -| 运行态 | `background/runtime-state.js` 已有 `currentNodeId`、`nodeStatuses` | 但仍通过 legacy step 视图回写,双模型并存 | -| 外围能力 | `shared/source-registry.js`、`shared/flow-capabilities.js`、`background/mail-rule-registry.js` | 已有抽象,但都还没成为唯一事实来源 | +| 内容脚本 | `flows/openai/content/openai-auth.js` 按 `message.step` 分支 | 页面驱动逻辑被 step number 绑死 | +| 运行态 | `core/flow-kernel/runtime-state.js` 已有 `currentNodeId`、`nodeStatuses` | 但仍通过 legacy step 视图回写,双模型并存 | +| 外围能力 | `core/flow-kernel/source-registry.js`、`core/flow-kernel/flow-capabilities.js`、`background/mail-rule-registry.js` | 已有抽象,但都还没成为唯一事实来源 | 重点文件里最能说明问题的几个点: - `background.js`:`DEFAULT_STATE`、`setStepStatus()`、`skipStep()`、`runAutoSequenceFromStep()`、`getStepRegistryForState()` 仍把数字 step 当主流程。 - `background/message-router.js`:`STEP_COMPLETE` / `STEP_ERROR` / `EXECUTE_STEP` / `SKIP_STEP` 还在消息层面锁死 step。 -- `background/runtime-state.js`:已经有 node 相关字段,但还在做 step 兼容派生。 +- `core/flow-kernel/runtime-state.js`:已经有 node 相关字段,但还在做 step 兼容派生。 - `sidepanel/sidepanel.js`:步骤列表、状态展示、手动执行、跳过、自动运行都还围着 step。 - `flows/openai/mail-rules.js`、`background/mail-rule-registry.js`:已经说明“规则可以 flow 化”,但目前只有 OpenAI 在用。 @@ -164,7 +164,7 @@ node = { type: 'task', order: 20, // 仅用于展示,不参与身份 sourceId: 'openai-auth', - driverId: 'content/signup-page', + driverId: 'flows/openai/content/openai-auth', next: ['fill-password'], retryPolicy: { maxAttempts: 3 }, recoveryPolicy: { onFailure: 'restart-node' }, @@ -241,11 +241,11 @@ flows// ### 6.3 content scripts -`content/signup-page.js` 这类脚本要从“按 step 分支”改成“按 node action / command 分支”。 +`flows/openai/content/openai-auth.js` 这类脚本要从“按 step 分支”改成“按 node action / command 分支”。 ### 6.4 source / driver -`shared/source-registry.js` 只负责页面来源、标签页生命周期和注入范围。 +`core/flow-kernel/source-registry.js` 只负责页面来源、标签页生命周期和注入范围。 `driverRegistry` 只负责“这个 source 能接什么命令”。 @@ -378,3 +378,4 @@ flows// - 邮件规则是否只从当前 flow definition 派生。 - sidepanel 是否能仅通过 workflow nodes 渲染,不新增全局步骤 switch。 - 自动运行、历史和日志是否都能用 `flowId/runId/nodeId` 定位。 + diff --git a/docs/多Flow根治性统一重构方案.md b/docs/多Flow根治性统一重构方案.md new file mode 100644 index 0000000..7bbca11 --- /dev/null +++ b/docs/多Flow根治性统一重构方案.md @@ -0,0 +1,1197 @@ +# 多Flow根治性统一重构方案 + +## 1. 背景 + +当前项目已经完成过多轮从单一 OpenAI 注册链路向多 flow 架构的重构,但现在的状态仍然属于“骨架已多 flow 化,核心 contract 仍保留 `openai + kiro` 双分支”。 + +这会带来三个持续性问题: + +- 每新增一个 flow,都需要继续修改 core 语义,而不是只新增 flow 自己的模块。 +- 文件命名、状态命名、配置命名仍带有明显历史负担,读代码和改代码都累。 +- 每次重构都只能修表层,无法保证第三个 flow 接入时不再次爆炸。 + +本方案的目标不是“再修一轮兼容”,而是一次性把多 flow 架构收口成稳定合同,后续新增 flow 时不再改 core 语义。 + +## 2. 总目标 + +本次重构完成后,项目必须达到以下目标: + +- 新增一个 flow 时,只新增该 flow 目录和注册项,不再修改 core 的配置合同、运行态合同和侧栏核心语义。 +- 所有 flow 都使用同一套配置结构、同一套运行态结构、同一套 source/driver 注册模型。 +- OpenAI 与 Kiro 的代码组织、文件命名、步骤命名、source 命名、driver 命名全部统一到同一规范。 +- 删除长期存在的历史别名和双轨字段,避免后续维护继续被旧命名拖住。 +- 旧配置不再作为运行时输入;旧数据只允许通过导入转换器映射到新 canonical 结构后进入系统。 + +## 3. 当前根问题 + +### 3.1 配置模型仍然是双轨 + +当前 `settings-schema` 只内建 `flows.openai` 和 `flows.kiro` 两套显式结构,同时继续派生: + +- `openaiIntegrationTargetId` +- `kiroTargetId` +- `panelMode` + +这不是多 flow 通用合同,而是两个 flow 的并排硬编码。第三个 flow 接入时,只能继续增加第三套特判。 + +### 3.2 运行态模型是裂开的 + +当前运行态存在两条体系: + +- OpenAI 使用 `runtimeState.flowState.openai` +- Kiro 使用独立 `kiroRuntime` + +这意味着“flow 私有运行态放哪里”没有统一答案。后续新增 flow 时,要么复制第三套运行态入口,要么继续在 core 写例外分支。 + +### 3.3 source / driver 仍未彻底注册化 + +虽然已经有 `flow-registry` 与 `source-registry`,但 core 里仍直接识别: + +- OpenAI host +- Kiro host +- OpenAI / Kiro source family + +这说明 source 系统还不是“flow 自带定义,core 只消费注册表”,而是“core 知道两个 flow 的页面事实”。 + +### 3.4 sidepanel 还是默认 flow 分支思维 + +当前 sidepanel 很多判断仍然是: + +- 默认 flow 视为 OpenAI +- 非默认 flow 基本按 Kiro 处理 + +例如 target 读取、target 持久化、贡献页 target 解析、flow 切换后的字段同步,都带着明显的 `openai / kiro` 条件分支。 + +### 3.5 “按 flow 执行范围”表面通用,内部仍是双 flow 特判 + +`stepExecutionRangeByFlow` 在 UI 侧已经接近通用,但写回持久配置时仍只处理: + +- `openai` +- `kiro` + +这类设计最危险,因为它会制造“表面支持多 flow,实际只有两个 flow 真能落盘”的假通用。 + +### 3.6 文件和命名仍带历史语义 + +当前仍大量存在下列混合命名: + +- `signup-page` +- `open-chatgpt` +- `platform-verify` +- `plus-checkout` +- `kiro-*` +- `vps-panel` + +这些名字有的看起来像通用名,实际是 OpenAI 专属;有的则直接带品牌。继续保留会让后续架构再度滑回历史分支。 + +## 4. 重构原则 + +### 4.1 不做旧配置兼容,只做导入转换 + +本次不保留“先兼容着跑”的中间态。新系统上线后,运行时、持久化读写、sidepanel 消息、background 内部状态全部只认新合同。 + +原则是: + +- 旧配置、旧账号记录、旧 flow 产物只能通过导入器转换 +- 导入器是单向转换器,不是长期桥接层 +- 导入完成后的数据必须完全符合新合同 +- 除导入器外,任何模块都不再直接读写 `panelMode`、`openaiIntegrationTargetId`、`kiroTargetId`、`kiroRuntime` 等旧字段 + +### 4.2 通用层只负责“怎么跑一个 flow” + +core 只解决这些问题: + +- flow 注册 +- source 注册 +- driver 注册 +- workflow 运行 +- 状态存储 +- sidepanel 动态渲染 +- 通用服务接入 + +core 不再知道任何 OpenAI 或 Kiro 业务步骤语义。 + +### 4.3 flow 私有能力留在 flow 内部 + +以下能力继续保留为 flow 私有: + +- OpenAI 的 OAuth、Plus、接码、平台绑定 +- Kiro 的 Builder ID、桌面授权、`kiro.rs` 上传 +- 任意新 flow 的站点特有页面状态机、验证码规则、成功判定和产物结构 + +### 4.4 命名必须先统一,再谈长期可维护性 + +如果 contract 抽象了,但目录和文件名还保留历史语义,后续阅读成本依然很高。本次重构必须把命名一并统一。 + +## 5. 第一层功能边界:公共能力与私有能力 + +这一层只回答一个问题:哪些能力应该是扩展级公共能力,哪些能力必须收回到各 flow 自己内部。 + +本层结论确定后,后续所有 contract、目录结构和配置模型都必须服从这条边界,不允许再反向把 flow 私有语义塞回公共层。 + +### 5.1 扩展级公共能力 + +当前项目真正应保留为全局公共能力的只有四块: + +- 项目壳层 +- 邮箱服务 +- 账户密码 +- IP 代理 + +其中: + +- `项目壳层` 只包括顶部容器、flow 切换、开始/停止/重试、日志与运行态展示这些外层承载能力。 +- `邮箱服务` 是跨 flow 的身份与收件基础设施。 +- `账户密码` 指侧栏中的“账户密码”文本框,它的语义是“注册目标账号时使用的共享密码”。 +- `IP 代理` 是扩展级环境能力,不属于任何单独 flow。 + +### 5.2 邮箱服务需要继续重构统一 + +邮箱服务值得保留为公共层,但必须从历史 OpenAI 语义中继续剥离。 + +它在功能上只应该负责三类事情: + +- 生成注册邮箱 +- 提供收件与轮询能力 +- 维护当前轮次的邮箱身份 + +邮箱服务不应该继续承接这些内容: + +- 某个 flow 的验证码命中规则 +- 某个 flow 的页面跳转语义 +- 某个 flow 的身份保留分支 +- 某个 flow 的注册成功或失败判断 + +因此邮箱层应拆成两部分: + +- `公共邮箱服务` + 负责邮箱生成、收件、轮询、当前邮箱身份维护 +- `flow 私有邮件规则` + 负责某个 flow 在某一步认哪封邮件、提取什么内容、何时算命中 + +结论: + +- 邮箱服务是公共能力 +- 邮件规则不是公共能力 + +### 5.3 “账户密码”是共享注册密码,不是公共凭据服务 + +当前侧栏的“账户密码”文本框,其真实产品语义已经确认: + +- 它不是平台后台登录密码 +- 它不是某个管理接口凭据 +- 它不是统一的凭据中心配置 +- 它只是“注册出来的目标账号要设置成什么密码” + +因此这里不应再抽象出“公共凭据服务”。 + +正确做法是: + +- 保留一个全局公共的 `共享注册密码` +- 各 flow 自己决定是否使用这份密码 +- 各 flow 自己决定在哪一步使用 +- 如果某个 flow 没有设置密码步骤,则直接忽略 + +这意味着: + +- `账户密码` 是公共输入能力 +- `凭据结构` 不是公共能力 +- `凭据产物字段` 不是公共能力 + +### 5.4 凭据与账号产物必须保持 flow 私有 + +虽然不做公共凭据服务,但每个 flow 仍然会生成自己的账号产物。 + +例如: + +- OpenAI 可能产出邮箱、密码、session、OAuth 结果、平台接入结果 +- Kiro 可能产出邮箱、密码、refresh token、clientId、clientSecret、上传结果 + +这里允许公共的只有“外层展示容器”或“记录壳层”,不允许公共化的内容包括: + +- 主身份字段 +- token 字段 +- 上传字段 +- 绑定字段 +- 各类平台回调字段 + +结论: + +- 账号产物展示容器可以公共 +- 账号产物字段结构必须 flow 私有 + +### 5.5 IP 代理属于扩展级环境能力 + +IP 代理应直接定义为: + +- 扩展级公共能力 +- 与 flow 无关 +- 由 flow 声明“可用/必需/禁用”,但不拥有它 + +flow 最多只能声明: + +- 当前 flow 是否允许代理 +- 当前 flow 的哪些页面需要代理约束 +- 当前 flow 是否需要代理检测或 fail-close 策略 + +但代理配置、切换、应用、检测、诊断,都不属于 flow 私有模型。 + +### 5.6 第一层最终边界 + +本次重构在功能边界上最终固定为: + +#### 全局公共能力 + +- 项目壳层 +- 邮箱服务 +- 共享注册密码 +- IP 代理 + +#### 仅允许公共容器,不允许公共字段语义 + +- 账号记录容器 +- 产物展示容器 + +#### flow 私有能力 + +- 账号主身份模型 +- 邮件命中规则 +- 验证码规则 +- 注册步骤 +- 登录步骤 +- OAuth / callback +- 支付 +- 上传 +- flow 最终凭据与产物字段结构 + +后续所有重构如果与这条边界冲突,以这条边界为准,不允许再为了省事把 flow 私有字段上提为公共模型。 + +## 6. 第二层产品模型:flow 与 target + +这一层只回答两个问题: + +- flow 在产品上到底是什么 +- target 在产品上到底是什么 + +如果这一层不先定死,后面配置模型、侧栏模型和运行态模型就会继续在 `panelMode / integrationTarget / targetId` 之间摇摆。 + +### 6.1 flow 的产品定义 + +每个 flow 都应被定义为一条独立的“账号生产链路”。 + +它至少具备以下特征: + +- 有独立入口 +- 有独立步骤顺序 +- 有独立成功判定 +- 有独立最终产物 +- 只按需消费公共能力,例如邮箱、共享注册密码、IP 代理 + +因此,flow 不是“几步流程的组合”,而是一个完整产品单元。 + +结论: + +- `openai` 是一个完整 flow +- `kiro` 是一个完整 flow +- 未来新站点也必须是完整 flow +- 不允许再把新站点业务塞进现有 flow 里做条件分支 + +### 6.2 target 的产品定义 + +target 不应继续被理解为历史意义上的 `panelMode`。 + +正确的产品语义应该是: + +- target 是这个 flow 最终产物的去向 +- target 是这个 flow 最终服务的目标系统 + +例如: + +- OpenAI flow 的 target 可以是 `CPA / SUB2API / Codex2API` +- Kiro flow 的 target 可以是 `kiro-rs` + +因此: + +- `flow` 回答“怎么产出账号或产物” +- `target` 回答“产出后要交给谁使用” + +这两个概念必须严格分层,不允许再混用。 + +### 6.3 每个 flow 都必须自带 target 模型 + +以后不再允许: + +- 只有 OpenAI flow 有 target +- Kiro flow 走另一套特殊 target 字段 +- 默认 flow 使用 `panelMode`,其他 flow 使用 `targetId` + +统一要求如下: + +- 每个 flow 都必须有 `selectedTarget` +- 每个 flow 都必须有 `targets` +- 单 target flow 只是“只有一个 target”,不是“没有 target 概念” + +即使某个 flow 当前只有一个 target,模型里也必须保留 target 层,只是在 UI 上可以隐藏选择器。 + +这样做的原因是: + +- 未来该 flow 如果新增第二个 target,不需要再次改核心模型 +- sidepanel 和 settings 不需要区分“哪个 flow 才有 target” + +### 6.4 flow 的产品结构 + +从功能角度,每个 flow 都应至少包含以下部分: + +- flow 基本信息 +- flow 对公共能力的依赖声明 +- flow 私有配置 +- flow targets +- flow 产物定义 + +进一步展开: + +- `flow 基本信息` + - 名称 + - 说明 + - 是否可自动化 +- `公共能力依赖声明` + - 是否使用邮箱 + - 是否使用共享注册密码 + - 是否允许代理 +- `flow 私有配置` + - 仅该 flow 自己理解的设置 +- `flow targets` + - 一个或多个目标系统 +- `flow 产物定义` + - 该 flow 最终生成什么结果 + +### 6.5 第二层最终结论 + +本次重构在产品模型上最终固定为: + +- `flow = 独立账号生产产品线` +- `target = flow 产物去向` +- 每个 flow 都必须天然支持自己的 target 模型 +- 全局只保留“当前选中的 flow” +- 当前 target 永远从当前 flow 自己内部读取 + +由此带来的直接结果是: + +- `panelMode` 只允许存在于导入器输入映射里 +- `openaiIntegrationTargetId / kiroTargetId` 不再是长期 canonical 配置 +- sidepanel 不再需要围绕“默认 flow 是 OpenAI”建立逻辑 + +后续所有设计如果与这层产品模型冲突,以这层定义为准。 + +## 7. 第三层运行模型:统一节点承载,不统一阶段语义 + +这一层只回答一个问题: + +- 多 flow 体系下,到底应该统一什么样的运行流程模型 + +本层最终结论是: + +- 统一节点承载方式 +- 不统一跨 flow 的阶段语义 + +也就是说,core 只负责“如何承载一个 flow 的内部流程”,不负责替每个 flow 规定必须长成同一种产品流程形状。 + +### 7.1 不强制统一阶段 + +本次重构不再要求所有 flow 都映射到一套固定阶段,例如: + +- 准备 +- 进入 +- 验证 +- 成型 +- 接入 +- 交付 + +这种抽象在文档上容易显得整齐,但对真实多 flow 来说约束过强,后续会重新形成“表面通用、实际不好用”的壳层。 + +因此: + +- OpenAI 保持自己的内部流程形状 +- Kiro 保持自己的内部流程形状 +- 新 flow 也不需要被迫套进统一阶段模型 + +### 7.2 真正需要统一的是节点运行模型 + +虽然不统一阶段语义,但所有 flow 仍然必须共享同一套“节点承载方式”。 + +每个 flow 的内部流程都应被理解为: + +- 一组有顺序的节点 +- 节点之间存在前后关系 +- 每个节点都有清晰输入和结果 +- 节点可以被手动执行、自动执行、停止和重试 + +也就是说,core 只统一: + +- 节点是什么 +- 节点如何排序 +- 节点如何连接 +- 节点如何汇报状态 + +而不统一: + +- 节点一定属于哪个阶段 +- 某个数字步骤在所有 flow 里都代表什么 + +### 7.3 数字步骤只表示 flow 内部显示顺序 + +以后数字步骤只允许表达: + +- 当前 flow 内部的展示顺序 + +不再允许表达: + +- 跨 flow 的固定语义 +- 全局共享的产品阶段 +- 某个历史 OpenAI 10 步链路的编号意义 + +这意味着: + +- `Step 4` 不再天然等于“验证码步骤” +- `Step 7` 不再天然等于“OAuth” +- `Step 9` 不再天然等于“平台验证” + +这些语义都必须下沉为 flow 私有流程定义。 + +### 7.4 节点的最小公共合同 + +从产品功能角度,每个节点只需要满足以下最小公共合同: + +- 有名称 +- 有顺序 +- 有前后关系 +- 有明确成功结果 +- 有明确失败结果 +- 可以手动执行 +- 可以自动执行 +- 可以停止 +- 可以重试 + +节点不应该再承载这些“假通用”要求: + +- 必须属于固定阶段 +- 必须服从跨 flow 的相同命名 +- 必须让不同 flow 共享同一种页面语义 + +### 7.5 外层统一状态,不统一流程内容 + +扩展外层只需要理解统一运行状态,不需要理解每个 flow 的内部阶段。 + +建议外层只保留以下状态集合: + +- 未开始 +- 运行中 +- 等待中 +- 需人工处理 +- 已停止 +- 已失败 +- 已完成 + +这些状态只服务于: + +- 侧栏展示 +- 自动运行控制 +- 停止 / 重试入口 +- 日志与记录 + +它们不应反过来要求 flow 把内部步骤硬映射成统一业务阶段。 + +### 7.6 执行范围绑定当前 flow 的节点顺序 + +`执行范围` 这个能力未来仍然保留,但其产品定义应明确为: + +- 对当前 flow 可见节点顺序的执行限制 + +它不再代表: + +- 全局统一步骤范围 +- 跨 flow 的阶段范围 +- OpenAI 历史编号的范围 + +也就是说,执行范围始终只作用于“当前 flow 自己的节点序列”。 + +### 7.7 第三层最终结论 + +本层最终固定为: + +- 不统一所有 flow 的阶段语义 +- 不统一所有 flow 的产品流程形状 +- 每个 flow 自己定义内部流程 +- core 只统一节点承载方式 +- 数字步骤只代表当前 flow 的显示顺序 +- 扩展外层只理解统一运行状态 +- 执行范围只绑定当前 flow 的节点顺序 + +后续如果有设计要求把所有 flow 再次硬映射成一套跨 flow 固定阶段,以本层结论为准,默认不采纳。 + +## 8. 第四层界面分工:公共壳层与 flow 工作面 + +这一层只回答一个问题: + +- sidepanel 到底哪些界面必须永远公共,哪些界面必须彻底交给 flow 自己 + +如果这层不先定死,后续所有 flow 接入都会继续把 `openai / kiro / new-flow` 的条件分支堆进侧栏主文件。 + +### 8.1 只保留两层界面 + +本次重构后,sidepanel 只保留两层界面: + +- 公共壳层 +- flow 工作面 + +不再增加第三层“伪通用业务面板”。 + +原因很简单: + +- 公共壳层负责承载扩展级操作 +- flow 工作面负责承载 flow 私有配置与流程内容 + +这样结构最稳,也最不容易重新膨胀。 + +### 8.2 公共壳层放什么 + +公共壳层只允许放扩展级公共入口与全局控制入口,包括: + +- flow 选择 +- target 选择 +- 开始 / 停止 / 重试 / 自动 +- 当前运行状态 +- 日志区 +- 账号记录入口 +- 文档 / 引导 / 更新提示入口 +- 贡献 / 使用教程按钮 +- 公共能力入口 +- 邮箱服务 +- 共享注册密码 +- IP 代理 + +这里的“贡献 / 使用教程”按钮属于公共壳层入口,但其功能逻辑必须按当前 flow 适配。 + +也就是说: + +- 按钮入口位置是公共的 +- 点击后的目标页面、说明内容、贡献适配逻辑由当前 flow 决定 + +不允许再把它写成某一个默认 flow 的固定行为。 + +### 8.3 flow 工作面放什么 + +flow 工作面只允许承载该 flow 自己的内容,包括: + +- flow 私有配置 +- flow 私有步骤列表 +- flow 私有运行态展示 +- flow 私有人工补位入口 +- flow 私有产物展示字段 + +例如: + +- OpenAI 的 Plus、OAuth、平台接入、手机验证,只能放在 OpenAI 工作面 +- Kiro 的桌面授权、上传到 `kiro.rs`,只能放在 Kiro 工作面 + +未来新增 flow 也必须遵守同样规则。 + +### 8.4 步骤列表属于 flow 私有界面 + +步骤列表不属于公共界面。 + +公共壳层最多只负责: + +- 放置步骤列表容器 +- 提供统一骨架样式 +- 提供统一按钮状态和颜色规则 + +但以下内容都必须由 flow 自己决定: + +- 有哪些步骤 +- 步骤叫什么 +- 步骤显示什么说明 +- 哪些步骤可执行 +- 哪些步骤可跳过 +- 哪些步骤需要人工补位 + +### 8.5 账号记录采用“公共入口 + 私有内容” + +账号记录建议固定为: + +- 公共入口 +- flow 私有内容 + +也就是说: + +- 记录页或记录弹层入口是公共的 +- 分页、筛选、基础容器可以公共 +- 每条记录里显示哪些字段,按当前 flow 决定 + +这样既保留统一入口,也避免为了统一记录展示而把所有 flow 的产物字段重新揉成一个假通用结构。 + +### 8.6 公共能力入口的边界 + +公共区可以保留三类公共能力入口: + +- 邮箱服务 +- 共享注册密码 +- IP 代理 + +但这些公共入口只负责: + +- 配置 +- 当前状态展示 +- 基础校验 + +它们不负责: + +- 解释某个 flow 的业务步骤 +- 解释某个 flow 的页面语义 +- 承载某个 flow 的专属运行逻辑 + +这意味着: + +- OpenAI 专属邮箱模式说明,应回到 OpenAI 工作面 +- Kiro 专属密码说明,应回到 Kiro 工作面 +- 某个 flow 的代理特殊策略,也应由该 flow 自己声明和解释 + +### 8.7 target 选择器仍属于公共壳层 + +target 虽然属于当前 flow 的内部模型,但从用户交互角度,它属于“开跑前的全局决策”。 + +因此: + +- target 选择器放在公共壳层 +- target 的可选项由当前 flow 决定 +- target 的说明文案也由当前 flow 决定 + +这样用户能在统一入口完成“跑哪个 flow、产物交给谁”的全局决策。 + +### 8.8 第四层最终结论 + +本层最终固定为: + +- sidepanel 只保留“公共壳层 + flow 工作面”两层 +- 公共壳层承载扩展级入口和全局控制 +- flow 工作面承载 flow 私有配置、步骤、运行态和产物 +- 步骤列表属于 flow 私有界面 +- 账号记录采用“公共入口 + 私有内容” +- 贡献 / 使用教程按钮属于公共壳层入口,但功能逻辑必须按当前 flow 适配 +- 不允许再把任何 OpenAI / Kiro / 新 flow 的专属业务配置继续堆进公共区 + +## 9. 目标架构 + +建议收口为以下目录形态: + +```txt +core/ + flow-kernel/ + flow-registry.js + source-registry.js + driver-registry.js + settings-schema.js + runtime-state.js + workflow-engine.js + step-registry.js + tab-runtime.js + logging-status.js + +flows/ + index.js + + openai/ + flow.js + settings.js + state.js + sources.js + drivers.js + steps/ + content/ + mail/ + rules.js + contribution/ + + kiro/ + flow.js + settings.js + state.js + sources.js + drivers.js + steps/ + content/ + contribution/ + +imports/ + legacy/ + settings-importer.js + account-records-importer.js + flow-artifacts/ + openai.js + kiro.js +``` + +第一阶段可以不强制先物理移动到 `core/flow-kernel/`,但逻辑上必须先收成这套边界。 + +补充要求: + +- `imports/legacy/` 是全仓库唯一允许理解旧字段、旧结构、旧命名的目录。 +- `core/` 与 `flows/` 不再保留任何旧字段回写逻辑。 + +## 10. 统一合同 + +### 10.1 配置合同 + +唯一 canonical 配置结构: + +```js +settingsState = { + schemaVersion, + activeFlowId, + services: { + account, + email, + proxy, + }, + flows: { + [flowId]: { + selectedTargetId, + targets: { + [targetId]: {} + }, + autoRun: { + stepExecutionRange, + }, + ui: {}, + features: {}, + } + } +} +``` + +要求: + +- `selectedTargetId + targets[targetId]` 是唯一 target 入口。 +- 不再以 `openaiIntegrationTargetId`、`kiroTargetId` 作为 canonical 字段。 +- `panelMode` 不再作为内部真实配置语义,也不再出现在 sidepanel payload、background 持久化写入路径和运行时配置读取路径中。 +- 每个 flow 都必须使用 `selectedTargetId + targets[targetId]` 组合。 +- target 专属字段只能挂在对应 `targets[targetId]` 下,不再允许提升到 flow 顶层或全局顶层。 +- 旧配置文件不能被直接加载运行,只能先经过导入转换器。 + +### 10.2 运行态合同 + +唯一 canonical 运行态结构: + +```js +runtimeState = { + activeFlowId, + activeRunId, + currentNodeId, + nodeStatuses, + sharedState: {}, + serviceState: { + account: {}, + email: {}, + proxy: {}, + }, + flowState: { + [flowId]: { + session: {}, + nodes: {}, + artifacts: {}, + ui: {}, + } + } +} +``` + +要求: + +- 删除独立 `kiroRuntime` 作为长期入口。 +- OpenAI、Kiro、未来新 flow 的私有运行态全部进入 `runtimeState.flowState[flowId]`。 +- 顶层不再持久化任何 flow 私有扁平字段。 +- 运行态不做旧快照导入;导入只处理持久配置与持久产物,导入完成后从新运行态起跑。 +- 如需便捷读取视图,只能从 canonical `flowState` 派生,不能再反向写出独立运行态入口。 + +### 10.3 flow 定义合同 + +每个 flow 统一导出: + +```js +{ + id, + label, + services, + capabilities, + settingsGroups, + targets, + settingsShape, + runtimeStateShape, + sources, + drivers, + workflow, + artifactSchema, + importers, + contributionAdapters, +} +``` + +要求: + +- `flow-registry` 只合并 flow 定义。 +- `settings-schema`、`source-registry`、`driver-registry`、`step-registry` 都只消费 flow 导出的定义。 +- flow 私有导入转换规则也由 flow 自己提供,core 只负责分发到对应 importer。 +- core 不再直接声明某个 flow 的 target、source、driver。 + +### 10.4 source 合同 + +每个 source 至少声明: + +- `id` +- `flowId` +- `kind` +- `label` +- `hostPatterns` +- `familyMatcher` +- `injectFiles` +- `readyPolicy` +- `cleanupScopes` +- `tabReusePolicy` + +要求: + +- host 判断由 flow source 定义驱动。 +- `unknown-source` 仅做诊断,不自动回退到任意业务 source。 +- callback cleanup 必须由 `cleanupScope -> ownerSource` 注册表驱动。 + +### 10.5 driver 合同 + +每个 driver 至少声明: + +- `id` +- `sourceId` +- `commands` +- `injectFiles` + +要求: + +- driver 负责“页面能做什么” +- source 负责“页面是什么、如何识别、如何复用、如何清理” + +### 10.6 step 合同 + +每个 step 统一使用: + +- `id` +- `order` +- `key` +- `title` +- `sourceId` +- `driverId` +- `command` +- `mailRuleId` + +要求: + +- `step key` 只表达 flow 内语义,不再混入历史平台词和伪通用词。 +- `background/steps/*.js` 这种共享目录最终要消失,改由各 flow 自己维护 `steps/`。 + +## 11. 命名统一方案 + +### 11.1 flow 目录命名 + +- `flows/openai/` +- `flows/kiro/` + +未来新增 flow 一律使用 `flows//` + +### 11.2 source 命名 + +统一格式: + +```txt +- +mail- +panel- +``` + +例如: + +- `openai-auth` +- `openai-entry` +- `openai-checkout` +- `openai-paypal` +- `openai-gopay` +- `kiro-register` +- `kiro-desktop-authorize` +- `panel-sub2api` +- `panel-cpa` +- `mail-gmail` + +### 11.3 driver 命名 + +统一格式: + +```txt +flows//content/.js +flows//steps/.js +``` + +不再使用历史伪通用名作为全局 driver 语义。 + +### 11.4 OpenAI 文件重命名建议 + +以下文件建议统一迁移: + +- `flows/openai/content/openai-auth.js` -> `flows/openai/content/openai-auth.js` +- `flows/openai/content/plus-checkout.js` -> `flows/openai/content/checkout-page.js` +- `flows/openai/content/paypal-flow.js` -> `flows/openai/content/paypal-page.js` +- `flows/openai/content/gopay-flow.js` -> `flows/openai/content/gopay-page.js` +- `flows/openai/content/sub2api-panel.js` -> `flows/openai/content/sub2api-panel-page.js` +- `flows/openai/content/vps-panel.js` -> `flows/openai/content/cpa-panel-page.js` + +- `flows/openai/background/steps/open-chatgpt.js` -> `flows/openai/steps/open-entry.js` +- `flows/openai/background/steps/submit-signup-email.js` -> `flows/openai/steps/submit-identifier.js` +- `flows/openai/background/steps/fill-password.js` -> `flows/openai/steps/submit-password.js` +- `flows/openai/background/steps/fetch-signup-code.js` -> `flows/openai/steps/submit-signup-code.js` +- `flows/openai/background/steps/fill-profile.js` -> `flows/openai/steps/submit-profile.js` +- `flows/openai/background/steps/wait-registration-success.js` -> `flows/openai/steps/wait-register-complete.js` +- `flows/openai/background/steps/oauth-login.js` -> `flows/openai/steps/start-oauth-login.js` +- `flows/openai/background/steps/fetch-login-code.js` -> `flows/openai/steps/submit-login-code.js` +- `flows/openai/background/steps/confirm-oauth.js` -> `flows/openai/steps/confirm-oauth-consent.js` +- `flows/openai/background/steps/platform-verify.js` -> `flows/openai/steps/complete-platform-bind.js` + +### 11.5 Kiro 文件重命名建议 + +以下文件建议统一迁移: + +- `flows/kiro/background/register-runner.js` -> `flows/kiro/steps/register-flow.js` +- `flows/kiro/background/desktop-authorize-runner.js` -> `flows/kiro/steps/desktop-authorize-flow.js` +- `flows/kiro/background/publisher-kiro-rs.js` -> `flows/kiro/steps/upload-credential.js` +- `flows/kiro/background/state.js` -> `flows/kiro/state.js` +- `flows/kiro/content/register-page.js` -> `flows/kiro/content/register-page.js` +- `flows/kiro/content/desktop-authorize-page.js` -> `flows/kiro/content/desktop-authorize-page.js` + +## 12. 替换式升级与导入转换策略 + +### 阶段 1:先定 canonical contract 与最终目录边界 + +目标: + +- 定义统一的 settings、runtime、flow、source、driver、step 合同 +- 定义 `core/`、`flows/`、`imports/legacy/` 三层边界 +- 明确旧字段只允许出现在导入器中 + +产出: + +- 更新设计文档 +- 明确迁移边界 + +完成标准: + +- 不再允许出现“新 flow 以后再看放哪”这种模糊点 + +### 阶段 2:统一配置模型 + +目标: + +- 让所有 flow 都走 `settingsState.flows[flowId]` +- 停止所有新代码对旧配置字段的直接读写 + +动作: + +- 重写 `core/flow-kernel/settings-schema.js` +- 把 `selectedTargetId` 作为统一 target 入口 +- 改写 `background.js` 中 `buildSettingsStatePatchFromFlatUpdates`、`buildAutoRunFreshResetSettingsState`、`buildFreshAutoRunKeepState` +- 改写 `background/message-router.js` 与 `sidepanel/sidepanel.js` 的设置写入路径,只提交 canonical patch +- 将 `panelMode / openaiIntegrationTargetId / kiroTargetId` 从运行时和持久化写入路径移除 + +完成标准: + +- 任意 flow 都可通过相同 API 读取当前 target 和 flow 设置 +- 正常保存、导出、恢复流程里不再出现旧 target 字段 + +### 阶段 3:统一运行态模型 + +目标: + +- 把 `kiroRuntime` 并入 `runtimeState.flowState.kiro` +- 让 OpenAI 与 Kiro 都按同一种 flow 私有运行态承载方式工作 + +动作: + +- 重写 `core/flow-kernel/runtime-state.js` +- 把 `flows/kiro/background/state.js` 合并或迁入 `flows/kiro/state.js` +- 给 OpenAI 与 Kiro 都定义 flow 私有状态 shape 与 reset 规则 +- 删除独立 Kiro 运行态主入口 +- 停止持久化所有 flow 私有顶层扁平字段 + +完成标准: + +- core 不再区分“OpenAI 私有状态”与“Kiro 私有状态”的存储方式 +- 自动运行、fresh reset、日志回写都只消费统一运行态结构 + +### 阶段 4:建立单向导入转换器 + +目标: + +- 让旧数据只能通过导入器进入新系统 +- 明确“导入”与“运行时兼容”是两件不同的事 + +动作: + +- 新建 `imports/legacy/settings-importer.js` +- 新建 `imports/legacy/account-records-importer.js` +- 按需新增 `flows/openai/importers/*` 与 `flows/kiro/importers/*` 用于旧产物字段映射 +- 导入器负责识别 `panelMode`、`openaiIntegrationTargetId`、`kiroTargetId`、`stepExecutionRangeByFlow`、`kiroRuntime` 以及旧账号记录结构 +- 导入结果统一输出为新 `settingsState`、新账号记录结构、新 flow 产物结构 +- 明确不导入旧运行态快照 + +完成标准: + +- 旧配置文件只能通过“导入”按钮进入系统,不能被直接加载运行 +- 导入完成后,系统内部不再保留旧字段副本 + +### 阶段 5:统一 flow 注册与 source/driver 注册 + +目标: + +- 让 flow 目录自带 source、driver、workflow、state、settings 定义 + +动作: + +- 新建 `flows/index.js` +- 把 flow 相关定义从 `core/flow-kernel/flow-registry.js`、`core/flow-kernel/source-registry.js`、`core/flow-kernel/flow-capabilities.js`、`data/step-definitions.js`、`core/flow-kernel/step-registry.js` 拆到各 flow 内 +- `source-registry` 改为只做合并与查询 +- `workflow-engine` 与 step registry 改为只消费 flow 导出定义 + +完成标准: + +- 新增 flow 时只新增 flow 定义,不改 core host 判断语义 + +### 阶段 6:统一 sidepanel 驱动方式 + +目标: + +- sidepanel 完全按 registry 驱动,而不是按 `openai / kiro` 条件分支 + +动作: + +- 重写 `sidepanel/sidepanel.js` +- 调整 `sidepanel/contribution-mode.js`、`sidepanel/account-records-manager.js`、相关 flow 私有展示逻辑 +- flow selector、target selector、visible groups、capability 全部从 registry 派生 +- `stepExecutionRange` 改成真正按当前 flow 通用读写 +- 账号记录区改成“公共列表壳层 + flow 私有渲染器” + +完成标准: + +- sidepanel 不再依赖默认 flow 是 OpenAI 的假设 +- sidepanel 不再直接处理 `panelMode` 与 `kiroTargetId` 两套并列写法 + +### 阶段 7:统一目录与文件命名 + +目标: + +- 把 OpenAI 与 Kiro 都迁入 `flows//` + +动作: + +- 重命名文件 +- 更新所有 import / `importScripts` / 注入列表 / 测试引用 +- 删除历史伪通用文件名 +- 将 `content/*.js`、`background/steps/*.js`、`background/kiro/*` 分别迁入对应 flow 目录 + +完成标准: + +- 新人看目录时能立即分清 core 与 flow 私有实现 + +### 阶段 8:删除旧兼容层与旧字段路径 + +目标: + +- 删除所有只为迁移存在的旧字段、旧别名、旧路径和旧命名 + +动作: + +- 删除 `signup-page -> openai-auth` 长期别名 +- 删除 `kiroRuntime` 独立入口 +- 删除 `panelMode` 作为内部真实 target 语义 +- 删除 `background.js`、`background/message-router.js`、`background/navigation-utils.js`、`background/auto-run-controller.js` 里所有核心旧字段桥接逻辑 +- 删除所有 `activeFlowId === 'kiro'` / `activeFlowId === DEFAULT_ACTIVE_FLOW_ID` 的核心结构分支 +- 用新的 canonical contract 测试替换旧兼容写回测试;旧字段只保留导入器测试 + +完成标准: + +- 仓库里只有 `imports/legacy/` 与其测试还认识旧字段 +- core 只理解“某个已注册 flow”,不理解“OpenAI 还是 Kiro” + +## 13. 实施顺序要求 + +为了避免第三次半重构,本次必须按顺序推进: + +1. 先收口 contract +2. 再统一 settings +3. 再统一 runtime +4. 再建立导入转换器 +5. 再拆 flow registry/source/driver +6. 再统一 sidepanel +7. 再迁目录和文件名 +8. 最后删旧字段与旧桥接逻辑 + +禁止的做法: + +- 先新增第三个 flow,再回头修 core +- 先重命名文件,但 contract 仍旧双轨 +- 为了快,继续在 sidepanel 或 background 增加新的 `openai / kiro / new-flow` 三分支 +- 一边写 canonical 结构,一边继续保留旧字段双写 +- 让旧配置在启动时被直接读取并“尽量凑合可用” + +## 14. 完成判定 + +本次重构只有在满足以下条件时才算完成: + +- 新增一个新 flow 时,不需要修改 `settings-schema` 核心结构。 +- 新增一个新 flow 时,不需要修改 `runtime-state` 核心结构。 +- 新增一个新 flow 时,不需要在 sidepanel 主逻辑中增加 flow 名称判断分支。 +- 新增一个新 flow 时,不需要在 `source-registry` core 里写新 host 判断。 +- OpenAI 与 Kiro 都已经迁入统一命名体系。 +- `background.js`、`background/message-router.js`、`sidepanel/sidepanel.js` 的正常运行路径不再读写 `panelMode`、`openaiIntegrationTargetId`、`kiroTargetId`、`kiroRuntime`。 +- 旧配置文件不能直接运行,只能通过导入转换器进入新结构。 +- 旧运行态快照不再支持恢复。 +- 历史旧字段只允许存在于 `imports/legacy/` 及其测试中。 + +## 15. 非目标 + +本方案当前不解决: + +- 新增具体第三个 flow 的业务实现 +- OpenAI 私有流程本身的业务优化 +- Kiro 私有流程本身的业务优化 +- 所有公共工具立即物理迁移到 `core/` 目录 +- 旧配置的自动无感兼容启动 +- 旧运行态快照的兼容恢复 + +本方案只解决“以后再加 flow 时,不要再从根上出结构问题”。 + +## 16. 最终结论 + +这次重构必须被当成一次“彻底替换式升级”,不是普通功能开发。 + +如果继续在现有 `openai + kiro` 双轨基础上补第三个 flow,后续仍会再次进入: + +- core 继续膨胀 +- 命名继续污染 +- 兼容层继续叠加 +- 每次改动都更累 + +因此本次必须一次性完成: + +- 统一 contract +- 统一命名 +- 统一目录边界 +- 删除长期双轨 +- 取消旧配置运行时兼容 +- 只保留单向导入转换器 + +完成后,后续 flow 才能真正变成“新增模块”,而不是“继续改核心”;旧世界也只能通过导入器进入新世界,而不能再反向定义新系统的结构。 + diff --git a/docs/多Flow资源入口与贡献体系升级开发方案.md b/docs/多Flow资源入口与贡献体系升级开发方案.md index 104a99e..b4cd636 100644 --- a/docs/多Flow资源入口与贡献体系升级开发方案.md +++ b/docs/多Flow资源入口与贡献体系升级开发方案.md @@ -94,9 +94,9 @@ 文件: -1. `background/kiro/state.js` -2. `background/kiro/desktop-authorize-runner.js` -3. `background/kiro/publisher-kiro-rs.js` +1. `flows/kiro/background/state.js` +2. `flows/kiro/background/desktop-authorize-runner.js` +3. `flows/kiro/background/publisher-kiro-rs.js` 4. `data/step-definitions.js` 当前事实: @@ -434,7 +434,7 @@ const FLOW_CONTRIBUTION_ADAPTERS = { }; ``` -### 4.2 修改 `shared/flow-registry.js` +### 4.2 修改 `core/flow-kernel/flow-registry.js` 新增 capability: @@ -449,7 +449,7 @@ supportsAccountContribution: true 3. `supportsContributionMode` 仅作为迁移字段映射到 `supportsAccountContribution`。 4. 新增 flow 只有在注册 adapter 后才能设置 `supportsAccountContribution: true`。 -### 4.3 修改 `shared/flow-capabilities.js` +### 4.3 修改 `core/flow-kernel/flow-capabilities.js` 处理规则: @@ -499,7 +499,7 @@ flowContributionRuntime: { 新增文件: ```text -background/kiro/credential-artifact.js +flows/kiro/background/credential-artifact.js ``` 职责: @@ -515,8 +515,8 @@ background/kiro/credential-artifact.js 修改位置: -1. `background/kiro/desktop-authorize-runner.js` -2. `background/kiro/publisher-kiro-rs.js` +1. `flows/kiro/background/desktop-authorize-runner.js` +2. `flows/kiro/background/publisher-kiro-rs.js` 3. Kiro runner 调度处 处理规则: @@ -782,7 +782,7 @@ POLL_FLOW_CONTRIBUTION_STATUS 开发内容: -1. 新增 `background/kiro/credential-artifact.js`。 +1. 新增 `flows/kiro/background/credential-artifact.js`。 2. 新增 `background/contribution/adapters/kiro-builder-id.js`。 3. 复用 Kiro runtime 产物构建 artifact。 4. 增加本地校验。 diff --git a/docs/多注册流程来源与驱动注册设计.md b/docs/多注册流程来源与驱动注册设计.md index 82a5f4c..5965739 100644 --- a/docs/多注册流程来源与驱动注册设计.md +++ b/docs/多注册流程来源与驱动注册设计.md @@ -1,4 +1,4 @@ -# 多注册流程来源与驱动注册设计 +# 多注册流程来源与驱动注册设计 本文解决的是另一个会在第二个 flow 接入时立刻爆炸的问题:当前项目虽然有 `tab-runtime`,但“来源 source 是谁、该注入什么脚本、URL family 怎么判定、localhost callback 应该清谁”仍然带着明显的 OpenAI 单流程假设。 @@ -10,7 +10,7 @@ - `content/utils.js` 的 `detectScriptSource()` - `background/navigation-utils.js` 的 `matchesSourceUrlFamily()` -- `background/tab-runtime.js` 的注册表、冲突清理、callback 清理 +- `core/flow-kernel/tab-runtime.js` 的注册表、冲突清理、callback 清理 这说明当前的 source 体系不是“可扩展的来源注册表”,而是“OpenAI 时代遗留的 source 命名”。 @@ -58,10 +58,10 @@ sourceRegistry = { 'content/activation-utils.js', 'content/utils.js', 'content/operation-delay.js', - 'content/auth-page-recovery.js', - 'content/phone-country-utils.js', - 'content/phone-auth.js', - 'content/signup-page.js', + 'flows/openai/content/auth-page-recovery.js', + 'flows/openai/content/phone-country-utils.js', + 'flows/openai/content/phone-auth.js', + 'flows/openai/content/openai-auth.js', ], cleanupScopes: ['oauth-localhost-callback'], }, @@ -81,7 +81,7 @@ sourceRegistry = { label: 'SUB2API 后台', dynamicOnly: true, familyMatcher: 'sub2api-panel-family', - injectFiles: ['content/utils.js', 'content/sub2api-panel.js'], + injectFiles: ['content/utils.js', 'flows/openai/content/sub2api-panel.js'], }, }; ``` @@ -125,7 +125,7 @@ sourceRegistry = { driverRegistry = { 'openai-auth': { sourceId: 'openai-auth', - driverId: 'content/signup-page', + driverId: 'flows/openai/content/openai-auth', commands: [ 'OPEN_SIGNUP', 'SUBMIT_SIGNUP_IDENTIFIER', @@ -244,3 +244,4 @@ callbackRegistry = { - manifest 与动态注入没有统一入口 - `content/utils.js` 的默认 source 回退会污染未来 flow + diff --git a/docs/多注册流程架构边界.md b/docs/多注册流程架构边界.md index 894b606..2c0a545 100644 --- a/docs/多注册流程架构边界.md +++ b/docs/多注册流程架构边界.md @@ -77,8 +77,8 @@ flows/ - `phone-sms/providers/hero-sms.js` - `phone-sms/providers/five-sim.js` - `phone-sms/providers/registry.js` -- `content/phone-auth.js` -- `content/phone-country-utils.js` +- `flows/openai/content/phone-auth.js` +- `flows/openai/content/phone-country-utils.js` - sidepanel 中的接码配置区 - 相关测试:`tests/phone-verification-flow.test.js`、`tests/five-sim-provider.test.js`、`tests/sidepanel-phone-verification-settings.test.js` diff --git a/docs/错误重试分层策略.md b/docs/错误重试分层策略.md index 46c7f59..66e157c 100644 --- a/docs/错误重试分层策略.md +++ b/docs/错误重试分层策略.md @@ -7,8 +7,8 @@ 定义:不离开当前步骤,在当前页面链路内恢复或替换资源。 典型位置: -- `background/steps/fetch-signup-code.js`(步骤 4 页面恢复) -- `background/steps/fetch-login-code.js` + `background/verification-flow.js`(步骤 8 邮箱轮询与重发) +- `flows/openai/background/steps/fetch-signup-code.js`(步骤 4 页面恢复) +- `flows/openai/background/steps/fetch-login-code.js` + `background/verification-flow.js`(步骤 8 邮箱轮询与重发) - `background/phone-verification-flow.js`(步骤 9 号码轮换、重发、回到 add-phone) 常见动作: diff --git a/flows/index.js b/flows/index.js new file mode 100644 index 0000000..90ff613 --- /dev/null +++ b/flows/index.js @@ -0,0 +1,68 @@ +(function attachMultiPageFlowsIndex(root, factory) { + root.MultiPageFlowsIndex = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createFlowsIndexModule() { + const rootScope = typeof self !== 'undefined' ? self : globalThis; + + const FLOW_ENTRY_DEFINITIONS = Object.freeze({ + openai: { + id: 'openai', + path: 'flows/openai/', + }, + kiro: { + id: 'kiro', + path: 'flows/kiro/', + }, + }); + + function normalizeFlowId(value = '') { + return String(value || '').trim().toLowerCase(); + } + + function getRegisteredFlowIds() { + return Object.keys(FLOW_ENTRY_DEFINITIONS); + } + + function getFlowEntry(flowId) { + const normalized = normalizeFlowId(flowId); + const baseEntry = FLOW_ENTRY_DEFINITIONS[normalized]; + if (!baseEntry) { + return null; + } + return { + ...baseEntry, + definition: normalized === 'openai' + ? (rootScope.MultiPageOpenAiFlowDefinition || null) + : (rootScope.MultiPageKiroFlowDefinition || null), + workflow: normalized === 'openai' + ? (rootScope.MultiPageOpenAiWorkflow || null) + : (rootScope.MultiPageKiroWorkflow || null), + }; + } + + function getFlowDefinition(flowId) { + return getFlowEntry(flowId)?.definition || null; + } + + function getFlowDefinitions() { + const next = {}; + getRegisteredFlowIds().forEach((flowId) => { + const definition = getFlowDefinition(flowId); + if (definition) { + next[flowId] = definition; + } + }); + return next; + } + + function getFlowWorkflow(flowId) { + return getFlowEntry(flowId)?.workflow || null; + } + + return { + getFlowEntry, + getFlowDefinition, + getFlowDefinitions, + getFlowWorkflow, + getRegisteredFlowIds, + }; +}); diff --git a/background/kiro/credential-artifact.js b/flows/kiro/background/credential-artifact.js similarity index 94% rename from background/kiro/credential-artifact.js rename to flows/kiro/background/credential-artifact.js index 11da827..73cbe65 100644 --- a/background/kiro/credential-artifact.js +++ b/flows/kiro/background/credential-artifact.js @@ -20,14 +20,15 @@ function readKiroRuntime(state = {}) { return kiroStateApi?.ensureRuntimeState ? kiroStateApi.ensureRuntimeState(state) - : (isPlainObject(state?.kiroRuntime) ? state.kiroRuntime : {}); + : (isPlainObject(state?.runtimeState?.flowState?.kiro) + ? state.runtimeState.flowState.kiro + : (isPlainObject(state?.flowState?.kiro) ? state.flowState.kiro : {})); } function resolveKiroTargetId(state = {}, runtimeState = readKiroRuntime(state)) { return cleanString( - state?.settingsState?.flows?.kiro?.targetId - || state?.flows?.kiro?.targetId - || state?.kiroTargetId + state?.settingsState?.flows?.kiro?.selectedTargetId + || state?.targetId || runtimeState?.upload?.targetId || DEFAULT_TARGET_ID ) || DEFAULT_TARGET_ID; @@ -46,7 +47,6 @@ return cleanString( runtimeState?.desktopAuth?.region || state?.settingsState?.flows?.kiro?.targets?.[targetId]?.region - || state?.flows?.kiro?.targets?.[targetId]?.region || DEFAULT_REGION ) || DEFAULT_REGION; } diff --git a/background/kiro/desktop-authorize-runner.js b/flows/kiro/background/desktop-authorize-runner.js similarity index 98% rename from background/kiro/desktop-authorize-runner.js rename to flows/kiro/background/desktop-authorize-runner.js index fb95aa9..0f1f15b 100644 --- a/background/kiro/desktop-authorize-runner.js +++ b/flows/kiro/background/desktop-authorize-runner.js @@ -97,16 +97,40 @@ typeof kiroStateApi?.buildDefaultRuntimeState === 'function' ? kiroStateApi.buildDefaultRuntimeState() : {}, - state?.kiroRuntime || {} + isPlainObject(state?.runtimeState?.flowState?.kiro) + ? state.runtimeState.flowState.kiro + : (isPlainObject(state?.flowState?.kiro) ? state.flowState.kiro : {}) ); } - function mergeRuntimePatch(currentState = {}, patch = {}) { + function buildCanonicalRuntimePatch(currentState = {}, nextRuntimeState = {}) { + if (typeof kiroStateApi?.buildRuntimeStatePatch === 'function') { + return kiroStateApi.buildRuntimeStatePatch(currentState, nextRuntimeState); + } + const baseRuntimeState = isPlainObject(currentState?.runtimeState) + ? cloneValue(currentState.runtimeState) + : {}; + const baseFlowState = isPlainObject(baseRuntimeState.flowState) + ? cloneValue(baseRuntimeState.flowState) + : {}; return { - kiroRuntime: deepMerge(readKiroRuntime(currentState), patch), + runtimeState: { + ...baseRuntimeState, + flowState: { + ...baseFlowState, + kiro: deepMerge(readKiroRuntime(currentState), nextRuntimeState), + }, + }, }; } + function mergeRuntimePatch(currentState = {}, patch = {}) { + return buildCanonicalRuntimePatch( + currentState, + deepMerge(readKiroRuntime(currentState), patch) + ); + } + function normalizePositiveInteger(value, fallback) { const numeric = Math.floor(Number(value)); if (Number.isInteger(numeric) && numeric > 0) { diff --git a/background/kiro/desktop-client.js b/flows/kiro/background/desktop-client.js similarity index 100% rename from background/kiro/desktop-client.js rename to flows/kiro/background/desktop-client.js diff --git a/background/kiro/publisher-kiro-rs.js b/flows/kiro/background/publisher-kiro-rs.js similarity index 93% rename from background/kiro/publisher-kiro-rs.js rename to flows/kiro/background/publisher-kiro-rs.js index edee9e5..5ea1002 100644 --- a/background/kiro/publisher-kiro-rs.js +++ b/flows/kiro/background/publisher-kiro-rs.js @@ -108,20 +108,43 @@ function readKiroRuntime(state = {}) { return kiroStateApi?.ensureRuntimeState ? kiroStateApi.ensureRuntimeState(state) - : (isPlainObject(state?.kiroRuntime) ? state.kiroRuntime : {}); + : (isPlainObject(state?.runtimeState?.flowState?.kiro) + ? state.runtimeState.flowState.kiro + : (isPlainObject(state?.flowState?.kiro) ? state.flowState.kiro : {})); + } + + function buildCanonicalRuntimePatch(currentState = {}, nextRuntimeState = {}) { + if (typeof kiroStateApi?.buildRuntimeStatePatch === 'function') { + return kiroStateApi.buildRuntimeStatePatch(currentState, nextRuntimeState); + } + const baseRuntimeState = isPlainObject(currentState?.runtimeState) + ? cloneValue(currentState.runtimeState) + : {}; + const baseFlowState = isPlainObject(baseRuntimeState.flowState) + ? cloneValue(baseRuntimeState.flowState) + : {}; + return { + runtimeState: { + ...baseRuntimeState, + flowState: { + ...baseFlowState, + kiro: deepMerge(readKiroRuntime(currentState), nextRuntimeState), + }, + }, + }; } function mergeRuntimePatch(currentState = {}, patch = {}) { - return { - kiroRuntime: deepMerge(readKiroRuntime(currentState), patch), - }; + return buildCanonicalRuntimePatch( + currentState, + deepMerge(readKiroRuntime(currentState), patch) + ); } function resolveKiroTargetId(state = {}) { return cleanString( - state?.settingsState?.flows?.kiro?.targetId - || state?.flows?.kiro?.targetId - || state?.kiroTargetId + state?.settingsState?.flows?.kiro?.selectedTargetId + || state?.targetId || readKiroRuntime(state).upload?.targetId || DEFAULT_TARGET_ID ) || DEFAULT_TARGET_ID; @@ -131,9 +154,7 @@ if (targetId !== DEFAULT_TARGET_ID) { throw new Error(`暂不支持 Kiro 发布目标:${targetId}`); } - const nestedConfig = state?.settingsState?.flows?.kiro?.targets?.[targetId] - || state?.flows?.kiro?.targets?.[targetId] - || {}; + const nestedConfig = state?.settingsState?.flows?.kiro?.targets?.[targetId] || {}; return { baseUrl: cleanString(nestedConfig.baseUrl || state?.kiroRsUrl), apiKey: normalizeKiroRsApiKey(nestedConfig.apiKey ?? state?.kiroRsKey ?? ''), @@ -188,7 +209,6 @@ const region = normalizeRegion( desktopAuth.region || state?.settingsState?.flows?.kiro?.targets?.[targetId]?.region - || state?.flows?.kiro?.targets?.[targetId]?.region || DEFAULT_REGION ); const email = cleanString(register.email || state?.email); diff --git a/background/kiro/register-runner.js b/flows/kiro/background/register-runner.js similarity index 98% rename from background/kiro/register-runner.js rename to flows/kiro/background/register-runner.js index c8cfb62..521f29c 100644 --- a/background/kiro/register-runner.js +++ b/flows/kiro/background/register-runner.js @@ -195,16 +195,40 @@ typeof kiroStateApi?.buildDefaultRuntimeState === 'function' ? kiroStateApi.buildDefaultRuntimeState() : {}, - state?.kiroRuntime || {} + isPlainObject(state?.runtimeState?.flowState?.kiro) + ? state.runtimeState.flowState.kiro + : (isPlainObject(state?.flowState?.kiro) ? state.flowState.kiro : {}) ); } - function mergeRuntimePatch(currentState = {}, patch = {}) { + function buildCanonicalRuntimePatch(currentState = {}, nextRuntimeState = {}) { + if (typeof kiroStateApi?.buildRuntimeStatePatch === 'function') { + return kiroStateApi.buildRuntimeStatePatch(currentState, nextRuntimeState); + } + const baseRuntimeState = isPlainObject(currentState?.runtimeState) + ? cloneValue(currentState.runtimeState) + : {}; + const baseFlowState = isPlainObject(baseRuntimeState.flowState) + ? cloneValue(baseRuntimeState.flowState) + : {}; return { - kiroRuntime: deepMerge(readKiroRuntime(currentState), patch), + runtimeState: { + ...baseRuntimeState, + flowState: { + ...baseFlowState, + kiro: deepMerge(readKiroRuntime(currentState), nextRuntimeState), + }, + }, }; } + function mergeRuntimePatch(currentState = {}, patch = {}) { + return buildCanonicalRuntimePatch( + currentState, + deepMerge(readKiroRuntime(currentState), patch) + ); + } + function getErrorMessage(error) { return error instanceof Error ? error.message : String(error ?? '未知错误'); } @@ -1076,7 +1100,7 @@ tokenSource: 'desktop_authorization_code_pkce', }, upload: { - targetId: cleanString(currentState?.kiroTargetId || readKiroRuntime(currentState).upload?.targetId) || DEFAULT_TARGET_ID, + targetId: cleanString(currentState?.targetId || readKiroRuntime(currentState).upload?.targetId) || DEFAULT_TARGET_ID, status: 'waiting_register', error: '', credentialId: null, @@ -1361,7 +1385,7 @@ const pollingState = { ...currentState, email: currentEmail, - kiroRuntime: deepMerge(readKiroRuntime(currentState), { + ...mergeRuntimePatch(currentState, { register: { email: currentEmail, }, diff --git a/background/kiro/state.js b/flows/kiro/background/state.js similarity index 71% rename from background/kiro/state.js rename to flows/kiro/background/state.js index b580f45..3fc3392 100644 --- a/background/kiro/state.js +++ b/flows/kiro/background/state.js @@ -58,6 +58,24 @@ return Number.isInteger(numeric) ? numeric : fallback; } + function normalizeNullableIdentifier(value, fallback = null) { + if (value === null || value === undefined || value === '') { + return fallback; + } + if (typeof value === 'number') { + const numeric = Math.floor(value); + return Number.isInteger(numeric) ? numeric : fallback; + } + const normalized = String(value ?? '').trim(); + if (!normalized) { + return fallback; + } + const numeric = Math.floor(Number(normalized)); + return Number.isInteger(numeric) && String(numeric) === normalized + ? numeric + : normalized; + } + function normalizeBoolean(value, fallback = false) { if (value === true || value === false) { return value; @@ -174,15 +192,63 @@ targetId: normalizeString(merged.upload?.targetId, DEFAULT_TARGET_ID), status: normalizeString(merged.upload?.status), error: normalizeString(merged.upload?.error), - credentialId: normalizeNullableInteger(merged.upload?.credentialId), + credentialId: normalizeNullableIdentifier(merged.upload?.credentialId), lastMessage: normalizeString(merged.upload?.lastMessage), lastUploadedAt: Math.max(0, normalizeInteger(merged.upload?.lastUploadedAt)), }, }; } + function buildCanonicalRuntimeStatePatch(state = {}, runtimeState = {}) { + const normalizedRuntimeState = normalizeRuntimeState(runtimeState); + const baseRuntimeState = isPlainObject(state?.runtimeState) + ? cloneValue(state.runtimeState) + : {}; + const baseFlowState = isPlainObject(baseRuntimeState.flowState) + ? cloneValue(baseRuntimeState.flowState) + : {}; + return { + ...baseRuntimeState, + flowState: { + ...baseFlowState, + kiro: normalizedRuntimeState, + }, + }; + } + + function buildRuntimeStateView(runtimeState = {}) { + const normalizedFlowState = isPlainObject(runtimeState?.flowState) + ? runtimeState.flowState + : {}; + return { + flowState: cloneValue(normalizedFlowState), + flows: cloneValue(normalizedFlowState), + }; + } + + function buildRuntimeStatePatch(currentState = {}, patch = {}) { + if (!isPlainObject(patch)) { + return {}; + } + const nextRuntimeState = normalizeRuntimeState( + deepMerge(ensureRuntimeState(currentState), patch) + ); + return { + runtimeState: buildCanonicalRuntimeStatePatch(currentState, nextRuntimeState), + }; + } + function ensureRuntimeState(state = {}) { - return normalizeRuntimeState(isPlainObject(state?.kiroRuntime) ? state.kiroRuntime : {}); + const runtimeFlowState = isPlainObject(state?.runtimeState?.flowState) + ? state.runtimeState.flowState + : {}; + if (isPlainObject(runtimeFlowState.kiro)) { + return normalizeRuntimeState(runtimeFlowState.kiro); + } + if (isPlainObject(state?.flowState?.kiro)) { + return normalizeRuntimeState(state.flowState.kiro); + } + return buildDefaultRuntimeState(); } function projectRuntimeFields() { @@ -190,31 +256,30 @@ } function buildStateView(state = {}) { + const nextRuntimeState = ensureRuntimeState(state); + const runtimeState = buildCanonicalRuntimeStatePatch(state, nextRuntimeState); return { ...state, - kiroRuntime: ensureRuntimeState(state), + runtimeState, + ...buildRuntimeStateView(runtimeState), }; } function buildSessionStatePatch(currentState = {}, updates = {}) { - if (!isPlainObject(updates?.kiroRuntime)) { + const runtimePatch = isPlainObject(updates?.runtimeState?.flowState?.kiro) + ? updates.runtimeState.flowState.kiro + : (isPlainObject(updates?.flowState?.kiro) + ? updates.flowState.kiro + : null); + if (!runtimePatch) { return {}; } - const nextRuntimeState = normalizeRuntimeState( - deepMerge(ensureRuntimeState(currentState), updates.kiroRuntime) - ); - return { - kiroRuntime: nextRuntimeState, - }; + return buildRuntimeStatePatch(currentState, runtimePatch); } function buildRuntimeResetPatch(currentState = {}, patch = {}) { - return { - kiroRuntime: normalizeRuntimeState( - deepMerge(ensureRuntimeState(currentState), patch) - ), - }; + return buildRuntimeStatePatch(currentState, patch); } function buildStartRegisterResetPatch(currentState = {}) { @@ -222,7 +287,7 @@ const nextRuntimeState = buildDefaultRuntimeState(); nextRuntimeState.upload.targetId = currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID; return { - kiroRuntime: nextRuntimeState, + runtimeState: buildCanonicalRuntimeStatePatch(currentState, nextRuntimeState), }; } @@ -251,43 +316,45 @@ }, }); return { - kiroRuntime: nextRuntimeState, + runtimeState: buildCanonicalRuntimeStatePatch(currentState, nextRuntimeState), }; } function buildDesktopResetPatch(currentState = {}) { const currentRuntimeState = ensureRuntimeState(currentState); + const nextRuntimeState = normalizeRuntimeState({ + ...currentRuntimeState, + session: { + ...currentRuntimeState.session, + currentStage: 'desktop-authorize', + desktopTabId: null, + pageState: '', + pageUrl: '', + lastError: '', + lastWarning: '', + }, + desktopAuth: buildDefaultRuntimeState().desktopAuth, + upload: { + ...buildDefaultRuntimeState().upload, + targetId: currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID, + }, + }); return { - kiroRuntime: normalizeRuntimeState({ - ...currentRuntimeState, - session: { - ...currentRuntimeState.session, - currentStage: 'desktop-authorize', - desktopTabId: null, - pageState: '', - pageUrl: '', - lastError: '', - lastWarning: '', - }, - desktopAuth: buildDefaultRuntimeState().desktopAuth, - upload: { - ...buildDefaultRuntimeState().upload, - targetId: currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID, - }, - }), + runtimeState: buildCanonicalRuntimeStatePatch(currentState, nextRuntimeState), }; } function buildUploadResetPatch(currentState = {}) { const currentRuntimeState = ensureRuntimeState(currentState); + const nextRuntimeState = normalizeRuntimeState({ + ...currentRuntimeState, + upload: { + ...buildDefaultRuntimeState().upload, + targetId: currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID, + }, + }); return { - kiroRuntime: normalizeRuntimeState({ - ...currentRuntimeState, - upload: { - ...buildDefaultRuntimeState().upload, - targetId: currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID, - }, - }), + runtimeState: buildCanonicalRuntimeStatePatch(currentState, nextRuntimeState), }; } @@ -332,9 +399,9 @@ const nextRuntimeState = buildDefaultRuntimeState(); nextRuntimeState.upload.targetId = currentRuntimeState.upload?.targetId || DEFAULT_TARGET_ID; return { - kiroRuntime: nextRuntimeState, - ...(Object.prototype.hasOwnProperty.call(currentState, 'kiroTargetId') - ? { kiroTargetId: normalizeString(currentState.kiroTargetId, DEFAULT_TARGET_ID).toLowerCase() } + runtimeState: buildCanonicalRuntimeStatePatch(currentState, nextRuntimeState), + ...(Object.prototype.hasOwnProperty.call(currentState, 'targetId') + ? { targetId: normalizeString(currentState.targetId, DEFAULT_TARGET_ID).toLowerCase() } : {}), }; } @@ -348,6 +415,7 @@ buildDefaultRuntimeState, buildDownstreamResetPatch, buildFreshKeepState, + buildRuntimeStatePatch, buildSessionStatePatch, buildStateView, ensureRuntimeState, diff --git a/content/kiro/desktop-authorize-page.js b/flows/kiro/content/desktop-authorize-page.js similarity index 100% rename from content/kiro/desktop-authorize-page.js rename to flows/kiro/content/desktop-authorize-page.js diff --git a/content/kiro/register-page.js b/flows/kiro/content/register-page.js similarity index 100% rename from content/kiro/register-page.js rename to flows/kiro/content/register-page.js diff --git a/flows/kiro/index.js b/flows/kiro/index.js new file mode 100644 index 0000000..df4b0b2 --- /dev/null +++ b/flows/kiro/index.js @@ -0,0 +1,226 @@ +(function attachMultiPageKiroFlowDefinition(root, factory) { + root.MultiPageKiroFlowDefinition = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createMultiPageKiroFlowDefinition() { + function freezeDeep(entry) { + if (!entry || typeof entry !== 'object' || Object.isFrozen(entry)) { + return entry; + } + Object.getOwnPropertyNames(entry).forEach((key) => { + freezeDeep(entry[key]); + }); + return Object.freeze(entry); + } + + const VALUE = freezeDeep({ + "id": "kiro", + "label": "Kiro", + "services": [ + "account", + "email", + "proxy" + ], + "capabilities": { + "supportsEmailSignup": true, + "supportsPhoneSignup": false, + "supportsPhoneVerificationSettings": false, + "supportsPlusMode": false, + "supportsContributionMode": false, + "supportsAccountContribution": true, + "supportsOpenAiOAuthContribution": false, + "contributionAdapterIds": [ + "kiro-builder-id" + ], + "supportedTargetIds": [ + "kiro-rs" + ], + "supportsLuckmail": false, + "supportsOauthTimeoutBudget": false, + "canSwitchFlow": true, + "stepDefinitionMode": "kiro", + "targetSelectorLabel": "来源" + }, + "baseGroups": [ + "kiro-runtime-status" + ], + "targets": { + "kiro-rs": { + "id": "kiro-rs", + "label": "kiro.rs", + "groups": [ + "kiro-target-kiro-rs" + ] + } + }, + "publicationTargets": { + "kiro-rs": { + "id": "kiro-rs", + "label": "kiro.rs" + } + }, + "runtimeSources": { + "kiro-register-page": { + "flowId": "kiro", + "kind": "flow-page", + "label": "Kiro 注册页", + "readyPolicy": "top-frame-only", + "family": "kiro-register-page-family", + "driverId": "flows/kiro/content/register-page", + "cleanupScopes": [], + "detectionMatchers": [ + { + "hostnames": [ + "app.kiro.dev", + "kiro.dev" + ] + }, + { + "hostnames": [ + "view.awsapps.com", + "login.awsapps.com", + "amazonaws.com" + ], + "hostnameFamilies": [ + "signin.aws", + "profile.aws" + ], + "hostnameEndsWith": [ + ".amazonaws.com" + ], + "matchMode": "any" + } + ], + "familyMatchers": [ + { + "hostnames": [ + "app.kiro.dev", + "kiro.dev" + ] + }, + { + "hostnames": [ + "view.awsapps.com", + "login.awsapps.com", + "amazonaws.com" + ], + "hostnameFamilies": [ + "signin.aws", + "profile.aws" + ], + "hostnameEndsWith": [ + ".amazonaws.com" + ], + "matchMode": "any" + } + ] + }, + "kiro-desktop-authorize": { + "flowId": "kiro", + "kind": "flow-page", + "label": "Kiro 桌面授权页", + "readyPolicy": "top-frame-only", + "family": "kiro-desktop-authorize-family", + "driverId": "flows/kiro/content/desktop-authorize-page", + "cleanupScopes": [], + "familyMatchers": [ + { + "hostnames": [ + "view.awsapps.com", + "login.awsapps.com", + "amazonaws.com" + ], + "hostnameFamilies": [ + "signin.aws", + "profile.aws" + ], + "hostnameEndsWith": [ + ".amazonaws.com" + ], + "matchMode": "any" + } + ] + }, + "kiro-rs-admin": { + "flowId": "kiro", + "kind": "virtual-page", + "label": "kiro.rs Admin", + "readyPolicy": "disabled", + "family": "kiro-rs-admin-family", + "driverId": null, + "cleanupScopes": [], + "familyMatchers": [] + } + }, + "driverDefinitions": { + "flows/kiro/content/register-page": { + "sourceId": "kiro-register-page", + "commands": [ + "kiro-open-register-page", + "kiro-submit-email", + "kiro-submit-name", + "kiro-submit-verification-code", + "kiro-submit-password", + "kiro-complete-register-consent" + ] + }, + "flows/kiro/content/desktop-authorize-page": { + "sourceId": "kiro-desktop-authorize", + "commands": [ + "kiro-complete-desktop-authorize" + ] + }, + "flows/kiro/background/register-runner": { + "sourceId": "kiro-register-page", + "commands": [ + "kiro-open-register-page", + "kiro-submit-email", + "kiro-submit-name", + "kiro-submit-verification-code", + "kiro-submit-password", + "kiro-complete-register-consent" + ] + }, + "flows/kiro/background/desktop-authorize-runner": { + "sourceId": "kiro-desktop-authorize", + "commands": [ + "kiro-start-desktop-authorize", + "kiro-complete-desktop-authorize" + ] + }, + "flows/kiro/background/publisher-kiro-rs": { + "sourceId": "kiro-rs-admin", + "commands": [ + "kiro-upload-credential" + ] + } + }, + "defaultTargetId": "kiro-rs", + "defaultPublicationTargetId": "kiro-rs", + "defaultTargetState": { + "baseUrl": "", + "apiKey": "" + }, + "settingsGroups": { + "kiro-target-kiro-rs": { + "id": "kiro-target-kiro-rs", + "label": "kiro.rs 配置", + "rowIds": [ + "row-kiro-rs-url", + "row-kiro-rs-key", + "row-kiro-rs-test-status" + ] + }, + "kiro-runtime-status": { + "id": "kiro-runtime-status", + "label": "Kiro 运行态", + "rowIds": [ + "row-kiro-web-status", + "row-kiro-login-url", + "row-kiro-upload-status" + ] + } + }, + "sourceAliases": {} +}); + + return VALUE; +}); diff --git a/flows/kiro/workflow.js b/flows/kiro/workflow.js new file mode 100644 index 0000000..145e0b5 --- /dev/null +++ b/flows/kiro/workflow.js @@ -0,0 +1,142 @@ +(function attachMultiPageKiroWorkflow(root, factory) { + root.MultiPageKiroWorkflow = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createMultiPageKiroWorkflow() { + const KIRO_CONTRIBUTION_STEP_TITLE = '\u8d21\u732e\u4e0a\u4f20'; + + function freezeDeep(entry) { + if (!entry || typeof entry !== 'object' || Object.isFrozen(entry)) { + return entry; + } + Object.getOwnPropertyNames(entry).forEach((key) => { + freezeDeep(entry[key]); + }); + return Object.freeze(entry); + } + + const STEP_VARIANTS = freezeDeep({ + "default": [ + { + "id": 1, + "order": 10, + "key": "kiro-open-register-page", + "title": "打开注册页", + "sourceId": "kiro-register-page", + "driverId": "flows/kiro/background/register-runner", + "command": "kiro-open-register-page", + "flowId": "kiro" + }, + { + "id": 2, + "order": 20, + "key": "kiro-submit-email", + "title": "获取邮箱并继续", + "sourceId": "kiro-register-page", + "driverId": "flows/kiro/background/register-runner", + "command": "kiro-submit-email", + "flowId": "kiro" + }, + { + "id": 3, + "order": 30, + "key": "kiro-submit-name", + "title": "填写姓名并继续", + "sourceId": "kiro-register-page", + "driverId": "flows/kiro/background/register-runner", + "command": "kiro-submit-name", + "flowId": "kiro" + }, + { + "id": 4, + "order": 40, + "key": "kiro-submit-verification-code", + "title": "获取验证码并继续", + "sourceId": "kiro-register-page", + "driverId": "flows/kiro/background/register-runner", + "command": "kiro-submit-verification-code", + "flowId": "kiro" + }, + { + "id": 5, + "order": 50, + "key": "kiro-submit-password", + "title": "设置密码并继续", + "sourceId": "kiro-register-page", + "driverId": "flows/kiro/background/register-runner", + "command": "kiro-submit-password", + "flowId": "kiro" + }, + { + "id": 6, + "order": 60, + "key": "kiro-complete-register-consent", + "title": "完成注册授权", + "sourceId": "kiro-register-page", + "driverId": "flows/kiro/background/register-runner", + "command": "kiro-complete-register-consent", + "flowId": "kiro" + }, + { + "id": 7, + "order": 70, + "key": "kiro-start-desktop-authorize", + "title": "启动桌面授权", + "sourceId": "kiro-desktop-authorize", + "driverId": "flows/kiro/background/desktop-authorize-runner", + "command": "kiro-start-desktop-authorize", + "flowId": "kiro" + }, + { + "id": 8, + "order": 80, + "key": "kiro-complete-desktop-authorize", + "title": "完成桌面授权", + "sourceId": "kiro-desktop-authorize", + "driverId": "flows/kiro/background/desktop-authorize-runner", + "command": "kiro-complete-desktop-authorize", + "flowId": "kiro" + }, + { + "id": 9, + "order": 90, + "key": "kiro-upload-credential", + "title": "上传凭据到 kiro.rs", + "sourceId": "kiro-rs-admin", + "driverId": "flows/kiro/background/publisher-kiro-rs", + "command": "kiro-upload-credential", + "flowId": "kiro" + } + ] +}); + + function getVariantStepDefinitions(variantKey = 'default') { + return Array.isArray(STEP_VARIANTS[variantKey]) ? STEP_VARIANTS[variantKey] : STEP_VARIANTS.default; + } + + function getModeStepDefinitions() { + return getVariantStepDefinitions('default'); + } + + function getAllSteps() { + return getVariantStepDefinitions('default'); + } + + function getPlusPaymentStepTitle() { + return ''; + } + + function resolveStepTitle(step = {}, options = {}) { + if (step?.key === 'kiro-upload-credential' && Boolean(options?.accountContributionEnabled || options?.state?.accountContributionEnabled)) { + return KIRO_CONTRIBUTION_STEP_TITLE; + } + return step?.title || ''; + } + + return { + flowId: 'kiro', + getAllSteps, + getModeStepDefinitions, + getPlusPaymentStepTitle, + getVariantStepDefinitions, + resolveStepTitle, + }; +}); diff --git a/background/steps/confirm-oauth.js b/flows/openai/background/steps/confirm-oauth.js similarity index 98% rename from background/steps/confirm-oauth.js rename to flows/openai/background/steps/confirm-oauth.js index 1c344ba..74abd5c 100644 --- a/background/steps/confirm-oauth.js +++ b/flows/openai/background/steps/confirm-oauth.js @@ -221,14 +221,14 @@ (async () => { try { throwIfStep8SettledOrStopped(resolved); - signupTabId = await getTabId('signup-page'); + signupTabId = await getTabId('openai-auth'); throwIfStep8SettledOrStopped(resolved); - if (signupTabId && await isTabAlive('signup-page')) { + if (signupTabId && await isTabAlive('openai-auth')) { await chrome.tabs.update(signupTabId, { active: true }); await addStepLog(visibleStep, '已切回认证页,正在准备调试器点击...'); } else { - signupTabId = await reuseOrCreateTab('signup-page', activeState.oauthUrl); + signupTabId = await reuseOrCreateTab('openai-auth', activeState.oauthUrl); await addStepLog(visibleStep, '已重新打开认证页,正在准备调试器点击...'); } diff --git a/background/steps/cpa-session-import.js b/flows/openai/background/steps/cpa-session-import.js similarity index 99% rename from background/steps/cpa-session-import.js rename to flows/openai/background/steps/cpa-session-import.js index 67891a9..d3fa6be 100644 --- a/background/steps/cpa-session-import.js +++ b/flows/openai/background/steps/cpa-session-import.js @@ -2,7 +2,7 @@ root.MultiPageBackgroundCpaSessionImport = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundCpaSessionImportModule() { const PLUS_CHECKOUT_SOURCE = 'plus-checkout'; - const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/plus-checkout.js']; + const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'flows/openai/content/plus-checkout.js']; function createCpaSessionImportExecutor(deps = {}) { const { diff --git a/background/steps/create-plus-checkout.js b/flows/openai/background/steps/create-plus-checkout.js similarity index 99% rename from background/steps/create-plus-checkout.js rename to flows/openai/background/steps/create-plus-checkout.js index d29b99f..a912624 100644 --- a/background/steps/create-plus-checkout.js +++ b/flows/openai/background/steps/create-plus-checkout.js @@ -4,8 +4,8 @@ const PLUS_CHECKOUT_SOURCE = 'plus-checkout'; const PAYPAL_SOURCE = 'paypal-flow'; const PLUS_CHECKOUT_ENTRY_URL = 'https://chatgpt.com/'; - const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/plus-checkout.js']; - const PAYPAL_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/paypal-flow.js']; + const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'flows/openai/content/plus-checkout.js']; + const PAYPAL_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'flows/openai/content/paypal-flow.js']; const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal'; const PLUS_PAYMENT_METHOD_PAYPAL_HOSTED = 'paypal-hosted'; const PLUS_PAYMENT_METHOD_GOPAY = 'gopay'; diff --git a/background/steps/fetch-login-code.js b/flows/openai/background/steps/fetch-login-code.js similarity index 99% rename from background/steps/fetch-login-code.js rename to flows/openai/background/steps/fetch-login-code.js index 8b95c47..3208846 100644 --- a/background/steps/fetch-login-code.js +++ b/flows/openai/background/steps/fetch-login-code.js @@ -158,7 +158,7 @@ } const timeoutMs = Math.max(1000, Number(options.timeoutMs) || 15000); const result = await sendToContentScriptResilient( - 'signup-page', + 'openai-auth', { type: 'GET_LOGIN_AUTH_STATE', source: 'background', @@ -208,7 +208,7 @@ }) : 60000; const result = await sendToContentScriptResilient( - 'signup-page', + 'openai-auth', { type: 'SUBMIT_ADD_EMAIL', source: 'background', @@ -439,7 +439,7 @@ } async function ensureAuthTabForPostLoginStep(state, visibleStep) { - const authTabId = await getTabId('signup-page'); + const authTabId = await getTabId('openai-auth'); if (authTabId) { await chrome.tabs.update(authTabId, { active: true }); return authTabId; @@ -447,7 +447,7 @@ if (!state?.oauthUrl) { throw new Error(`步骤 ${visibleStep}:缺少登录用 OAuth 链接,请先完成刷新 OAuth 并登录。`); } - return reuseOrCreateTab('signup-page', state.oauthUrl); + return reuseOrCreateTab('openai-auth', state.oauthUrl); } async function completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, options = {}) { @@ -708,7 +708,7 @@ activeFetchLoginCodeStep = visibleStep; activeFetchLoginCodeStepKey = 'fetch-bound-email-login-code'; const preparedState = buildBoundEmailLoginState(state, visibleStep); - const authTabId = await getTabId('signup-page'); + const authTabId = await getTabId('openai-auth'); if (authTabId) { await chrome.tabs.update(authTabId, { active: true }); @@ -716,7 +716,7 @@ if (!preparedState.oauthUrl) { throw new Error(`步骤 ${visibleStep}:缺少登录用 OAuth 链接,请先完成绑定邮箱后刷新 OAuth 并登录。`); } - await reuseOrCreateTab('signup-page', preparedState.oauthUrl); + await reuseOrCreateTab('openai-auth', preparedState.oauthUrl); } throwIfStopped(); @@ -765,7 +765,7 @@ const visibleStep = getVisibleStep(state, 8); activeFetchLoginCodeStep = visibleStep; activeFetchLoginCodeStepKey = 'fetch-login-code'; - const authTabId = await getTabId('signup-page'); + const authTabId = await getTabId('openai-auth'); if (authTabId) { await chrome.tabs.update(authTabId, { active: true }); @@ -773,7 +773,7 @@ if (!state.oauthUrl) { throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${getAuthLoginStepForVisibleStep(visibleStep)}。`); } - await reuseOrCreateTab('signup-page', state.oauthUrl); + await reuseOrCreateTab('openai-auth', state.oauthUrl); } throwIfStopped(); diff --git a/background/steps/fetch-signup-code.js b/flows/openai/background/steps/fetch-signup-code.js similarity index 98% rename from background/steps/fetch-signup-code.js rename to flows/openai/background/steps/fetch-signup-code.js index 102cc24..4701b8d 100644 --- a/background/steps/fetch-signup-code.js +++ b/flows/openai/background/steps/fetch-signup-code.js @@ -188,7 +188,7 @@ async function executeStep4(state) { const stepStartedAt = Date.now(); const verificationSessionKey = `4:${stepStartedAt}`; - const signupTabId = await getTabId('signup-page'); + const signupTabId = await getTabId('openai-auth'); if (!signupTabId) { throw new Error('认证页面标签页已关闭,无法继续步骤 4。请先执行步骤 1 或步骤 2,重新打开认证页后再试。'); @@ -228,10 +228,10 @@ try { prepareResult = typeof sendToContentScript === 'function' - ? await sendToContentScript('signup-page', prepareRequest, { + ? await sendToContentScript('openai-auth', prepareRequest, { responseTimeoutMs: prepareResponseTimeoutMs, }) - : await sendToContentScriptResilient('signup-page', prepareRequest, { + : await sendToContentScriptResilient('openai-auth', prepareRequest, { timeoutMs: Math.max(1000, prepareTimeoutMs - (Date.now() - prepareStartAt)), responseTimeoutMs: prepareResponseTimeoutMs, retryDelayMs: 700, @@ -248,7 +248,7 @@ throw error; } - const recoverResult = await sendToContentScriptResilient('signup-page', { + const recoverResult = await sendToContentScriptResilient('openai-auth', { type: 'RECOVER_AUTH_RETRY_PAGE', step: 4, source: 'background', diff --git a/background/steps/fill-password.js b/flows/openai/background/steps/fill-password.js similarity index 92% rename from background/steps/fill-password.js rename to flows/openai/background/steps/fill-password.js index 6c3807b..d2c47b6 100644 --- a/background/steps/fill-password.js +++ b/flows/openai/background/steps/fill-password.js @@ -13,7 +13,7 @@ sendToContentScript, setPasswordState, setState, - SIGNUP_PAGE_INJECT_FILES, + OPENAI_AUTH_INJECT_FILES, } = deps; function normalizeSignupMethod(value = '') { @@ -71,8 +71,8 @@ throw new Error('缺少注册账号,请先完成步骤 2。'); } - const signupTabId = await getTabId('signup-page'); - if (!signupTabId || !(await isTabAlive('signup-page'))) { + const signupTabId = await getTabId('openai-auth'); + if (!signupTabId || !(await isTabAlive('openai-auth'))) { throw new Error('认证页面标签页已关闭,请先重新完成步骤 2。'); } @@ -90,9 +90,9 @@ await setState({ accounts }); await chrome.tabs.update(signupTabId, { active: true }); - await ensureContentScriptReadyOnTab('signup-page', signupTabId, { - inject: SIGNUP_PAGE_INJECT_FILES, - injectSource: 'signup-page', + await ensureContentScriptReadyOnTab('openai-auth', signupTabId, { + inject: OPENAI_AUTH_INJECT_FILES, + injectSource: 'openai-auth', timeoutMs: 45000, retryDelayMs: 900, logMessage: '步骤 3:密码页内容脚本未就绪,正在等待页面恢复...', @@ -104,7 +104,7 @@ await addLog( `步骤 3:正在填写密码,${identityLabel},密码为${state.customPassword ? '自定义' : '自动生成'}(${password.length} 位)` ); - await sendToContentScript('signup-page', { + await sendToContentScript('openai-auth', { type: 'EXECUTE_NODE', nodeId: 'fill-password', step: 3, diff --git a/background/steps/fill-plus-checkout.js b/flows/openai/background/steps/fill-plus-checkout.js similarity index 99% rename from background/steps/fill-plus-checkout.js rename to flows/openai/background/steps/fill-plus-checkout.js index 6a4d813..cc5737f 100644 --- a/background/steps/fill-plus-checkout.js +++ b/flows/openai/background/steps/fill-plus-checkout.js @@ -2,7 +2,7 @@ root.MultiPageBackgroundPlusCheckoutBilling = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundPlusCheckoutBillingModule() { const PLUS_CHECKOUT_SOURCE = 'plus-checkout'; - const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/plus-checkout.js']; + const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'flows/openai/content/plus-checkout.js']; const PLUS_CHECKOUT_URL_PATTERN = /^https:\/\/chatgpt\.com\/checkout(?:\/|$)/i; const PLUS_CHECKOUT_FRAME_READY_DELAY_MS = 500; const PLUS_CHECKOUT_SUBMIT_MAX_ATTEMPTS = 5; diff --git a/background/steps/fill-profile.js b/flows/openai/background/steps/fill-profile.js similarity index 95% rename from background/steps/fill-profile.js rename to flows/openai/background/steps/fill-profile.js index 6aa5801..9bc1888 100644 --- a/background/steps/fill-profile.js +++ b/flows/openai/background/steps/fill-profile.js @@ -15,7 +15,7 @@ await addLog(`步骤 5:已生成姓名 ${firstName} ${lastName},生日 ${year}-${month}-${day}`); - await sendToContentScript('signup-page', { + await sendToContentScript('openai-auth', { type: 'EXECUTE_NODE', nodeId: 'fill-profile', step: 5, diff --git a/background/steps/gopay-approve.js b/flows/openai/background/steps/gopay-approve.js similarity index 99% rename from background/steps/gopay-approve.js rename to flows/openai/background/steps/gopay-approve.js index d47856d..d2d56a3 100644 --- a/background/steps/gopay-approve.js +++ b/flows/openai/background/steps/gopay-approve.js @@ -4,7 +4,7 @@ const GOPAY_SOURCE = 'gopay-flow'; const GOPAY_OTP_SOURCE = 'gopay-otp-flow'; const PLUS_CHECKOUT_SOURCE = 'plus-checkout'; - const GOPAY_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/gopay-flow.js']; + const GOPAY_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'flows/openai/content/gopay-flow.js']; const GOPAY_WAIT_TIMEOUT_MS = 120000; const GOPAY_POLL_INTERVAL_MS = 1000; const GOPAY_LINKING_RETRY_WAIT_MS = 15000; @@ -914,16 +914,16 @@ const target = Number.isInteger(frameId) ? await sendGoPayFrameCommand(tabId, frameId, targetMessageType, {}) : await sendGoPayCommand(tabId, targetMessageType, {}); - const rect = target?.rect || null; - if (!target?.found || !rect || !Number.isFinite(rect.centerX) || !Number.isFinite(rect.centerY)) { - return { clicked: false, reason: 'target_not_found', clickTarget: target?.target || '' }; - } - if (Number.isInteger(frameId)) { - return { clicked: false, reason: 'debugger_click_skipped_for_frame_target', clickTarget: target.target || '' }; - } - await clickWithDebugger(tabId, rect); - return { clicked: true, clickTarget: target.target || '' }; - } + const rect = target?.rect || null; + if (!target?.found || !rect || !Number.isFinite(rect.centerX) || !Number.isFinite(rect.centerY)) { + return { clicked: false, reason: 'target_not_found', clickTarget: target?.target || '' }; + } + if (Number.isInteger(frameId)) { + return { clicked: false, reason: 'debugger_click_skipped_for_frame_target', clickTarget: target.target || '' }; + } + await clickWithDebugger(tabId, rect); + return { clicked: true, clickTarget: target.target || '' }; + } async function clickGoPayContinueWithDebugger(tabId, frameId = null) { diff --git a/background/steps/gopay-manual-confirm.js b/flows/openai/background/steps/gopay-manual-confirm.js similarity index 100% rename from background/steps/gopay-manual-confirm.js rename to flows/openai/background/steps/gopay-manual-confirm.js diff --git a/background/steps/oauth-login.js b/flows/openai/background/steps/oauth-login.js similarity index 99% rename from background/steps/oauth-login.js rename to flows/openai/background/steps/oauth-login.js index e8dd1f9..63098dd 100644 --- a/background/steps/oauth-login.js +++ b/flows/openai/background/steps/oauth-login.js @@ -301,10 +301,10 @@ }); } - await reuseOrCreateTab('signup-page', oauthUrl, { forceNew: true }); + await reuseOrCreateTab('openai-auth', oauthUrl, { forceNew: true }); const result = await sendToContentScriptResilient( - 'signup-page', + 'openai-auth', { type: 'EXECUTE_NODE', nodeId: state?.nodeId || 'oauth-login', diff --git a/background/steps/open-chatgpt.js b/flows/openai/background/steps/open-chatgpt.js similarity index 100% rename from background/steps/open-chatgpt.js rename to flows/openai/background/steps/open-chatgpt.js diff --git a/background/steps/paypal-approve.js b/flows/openai/background/steps/paypal-approve.js similarity index 99% rename from background/steps/paypal-approve.js rename to flows/openai/background/steps/paypal-approve.js index bc1787f..e8c23c8 100644 --- a/background/steps/paypal-approve.js +++ b/flows/openai/background/steps/paypal-approve.js @@ -3,7 +3,7 @@ })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundPayPalApproveModule() { const PAYPAL_SOURCE = 'paypal-flow'; const PLUS_CHECKOUT_SOURCE = 'plus-checkout'; - const PAYPAL_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/paypal-flow.js']; + const PAYPAL_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'flows/openai/content/paypal-flow.js']; const PAYPAL_LOGIN_TRANSITION_TIMEOUT_MS = 30000; const PAYPAL_LOGIN_TRANSITION_POLL_MS = 500; diff --git a/background/steps/platform-verify.js b/flows/openai/background/steps/platform-verify.js similarity index 100% rename from background/steps/platform-verify.js rename to flows/openai/background/steps/platform-verify.js diff --git a/background/steps/plus-return-confirm.js b/flows/openai/background/steps/plus-return-confirm.js similarity index 100% rename from background/steps/plus-return-confirm.js rename to flows/openai/background/steps/plus-return-confirm.js diff --git a/background/steps/sub2api-session-import.js b/flows/openai/background/steps/sub2api-session-import.js similarity index 99% rename from background/steps/sub2api-session-import.js rename to flows/openai/background/steps/sub2api-session-import.js index 7d51654..a163022 100644 --- a/background/steps/sub2api-session-import.js +++ b/flows/openai/background/steps/sub2api-session-import.js @@ -2,7 +2,7 @@ root.MultiPageBackgroundSub2ApiSessionImport = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundSub2ApiSessionImportModule() { const PLUS_CHECKOUT_SOURCE = 'plus-checkout'; - const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'content/plus-checkout.js']; + const PLUS_CHECKOUT_INJECT_FILES = ['content/utils.js', 'content/operation-delay.js', 'flows/openai/content/plus-checkout.js']; function createSub2ApiSessionImportExecutor(deps = {}) { const { diff --git a/background/steps/submit-signup-email.js b/flows/openai/background/steps/submit-signup-email.js similarity index 95% rename from background/steps/submit-signup-email.js rename to flows/openai/background/steps/submit-signup-email.js index 0e4a690..e8942b7 100644 --- a/background/steps/submit-signup-email.js +++ b/flows/openai/background/steps/submit-signup-email.js @@ -17,7 +17,7 @@ resolveSignupMethod = () => 'email', resolveSignupEmailForFlow, sendToContentScriptResilient, - SIGNUP_PAGE_INJECT_FILES, + OPENAI_AUTH_INJECT_FILES, waitForTabStableComplete = null, } = deps; @@ -37,7 +37,7 @@ function isRetryableStep2TransportErrorMessage(errorLike) { const message = getErrorMessage(errorLike); - return /Content script on signup-page did not respond in \d+s|内容脚本\s+\d+(?:\.\d+)?\s*秒内未响应|Receiving end does not exist|message channel closed|A listener indicated an asynchronous response|port closed before a response was received|did not respond in \d+s/i.test(message); + return /Content script on [\w-]+ did not respond in \d+s|内容脚本\s+\d+(?:\.\d+)?\s*秒内未响应|Receiving end does not exist|message channel closed|A listener indicated an asynchronous response|port closed before a response was received|did not respond in \d+s/i.test(message); } function isLikelyLoggedInChatgptHomeUrl(rawUrl) { @@ -78,7 +78,7 @@ } try { - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'ENSURE_SIGNUP_ENTRY_READY', step: 2, source: 'background', @@ -151,7 +151,7 @@ } = options; try { - return await sendToContentScriptResilient('signup-page', { + return await sendToContentScriptResilient('openai-auth', { type: 'EXECUTE_NODE', nodeId: 'submit-signup-email', step: 2, @@ -198,7 +198,7 @@ throw new Error('步骤 2:未找到可用的注册页标签,无法切换到手机号注册入口。'); } - const result = await sendToContentScriptResilient('signup-page', { + const result = await sendToContentScriptResilient('openai-auth', { type: 'ENSURE_SIGNUP_PHONE_ENTRY_READY', step: 2, source: 'background', @@ -232,8 +232,8 @@ } async function ensureSignupTabForStep2() { - let signupTabId = await getTabId('signup-page'); - if (!signupTabId || !(await isTabAlive('signup-page'))) { + let signupTabId = await getTabId('openai-auth'); + if (!signupTabId || !(await isTabAlive('openai-auth'))) { await addLog('步骤 2:未发现可用的注册页标签,正在重新打开 ChatGPT 官网...', 'warn'); signupTabId = (await ensureSignupEntryPageReady(2)).tabId; } else { @@ -243,9 +243,9 @@ signupTabId, '步骤 2:已切换到注册页标签,正在等待页面加载完成并额外稳定 3 秒...' ); - await ensureContentScriptReadyOnTab('signup-page', signupTabId, { - inject: SIGNUP_PAGE_INJECT_FILES, - injectSource: 'signup-page', + await ensureContentScriptReadyOnTab('openai-auth', signupTabId, { + inject: OPENAI_AUTH_INJECT_FILES, + injectSource: 'openai-auth', timeoutMs: 45000, retryDelayMs: 900, logMessage: '步骤 2:注册入口页内容脚本未就绪,正在等待页面恢复...', diff --git a/background/steps/wait-registration-success.js b/flows/openai/background/steps/wait-registration-success.js similarity index 100% rename from background/steps/wait-registration-success.js rename to flows/openai/background/steps/wait-registration-success.js diff --git a/content/auth-page-recovery.js b/flows/openai/content/auth-page-recovery.js similarity index 100% rename from content/auth-page-recovery.js rename to flows/openai/content/auth-page-recovery.js diff --git a/content/gopay-flow.js b/flows/openai/content/gopay-flow.js similarity index 99% rename from content/gopay-flow.js rename to flows/openai/content/gopay-flow.js index 570e0b9..2edde4b 100644 --- a/content/gopay-flow.js +++ b/flows/openai/content/gopay-flow.js @@ -1,4 +1,4 @@ -// content/gopay-flow.js — GoPay authorization helper. +// flows/openai/content/gopay-flow.js — GoPay authorization helper. console.log('[MultiPage:gopay-flow] Content script loaded on', location.href); diff --git a/content/signup-page.js b/flows/openai/content/openai-auth.js similarity index 99% rename from content/signup-page.js rename to flows/openai/content/openai-auth.js index 11c9e95..f62fa29 100644 --- a/content/signup-page.js +++ b/flows/openai/content/openai-auth.js @@ -1,10 +1,10 @@ -// content/signup-page.js — Content script for ChatGPT signup entry + OpenAI auth pages +// flows/openai/content/openai-auth.js — Content script for ChatGPT signup entry + OpenAI auth pages // Injected on: auth0.openai.com, auth.openai.com, accounts.openai.com // Dynamically injected on: chatgpt.com -console.log('[MultiPage:signup-page] Content script loaded on', location.href); +console.log('[MultiPage:openai-auth] Content script loaded on', location.href); -const SIGNUP_PAGE_LISTENER_SENTINEL = 'data-multipage-signup-page-listener'; +const OPENAI_AUTH_LISTENER_SENTINEL = 'data-multipage-openai-auth-listener'; function getOperationDelayRunner() { const rootScope = typeof window !== 'undefined' ? window : globalThis; @@ -14,8 +14,8 @@ function getOperationDelayRunner() { : async (_metadata, operation) => operation(); } -if (document.documentElement.getAttribute(SIGNUP_PAGE_LISTENER_SENTINEL) !== '1') { - document.documentElement.setAttribute(SIGNUP_PAGE_LISTENER_SENTINEL, '1'); +if (document.documentElement.getAttribute(OPENAI_AUTH_LISTENER_SENTINEL) !== '1') { + document.documentElement.setAttribute(OPENAI_AUTH_LISTENER_SENTINEL, '1'); // Listen for commands from Background chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { @@ -70,10 +70,10 @@ if (document.documentElement.getAttribute(SIGNUP_PAGE_LISTENER_SENTINEL) !== '1' } }); } else { - console.log('[MultiPage:signup-page] 消息监听已存在,跳过重复注册'); + console.log('[MultiPage:openai-auth] 消息监听已存在,跳过重复注册'); } -const SIGNUP_PAGE_NODE_HANDLERS = Object.freeze({ +const OPENAI_AUTH_NODE_HANDLERS = Object.freeze({ 'submit-signup-email': (payload) => step2_clickRegister(payload), 'fill-password': (payload) => step3_fillEmailPassword(payload), 'fill-profile': (payload) => step5_fillNameBirthday(payload), @@ -120,9 +120,9 @@ async function handleCommand(message) { switch (message.type) { case 'EXECUTE_NODE': { const nodeId = String(message.nodeId || message.payload?.nodeId || '').trim(); - const handler = SIGNUP_PAGE_NODE_HANDLERS[nodeId]; + const handler = OPENAI_AUTH_NODE_HANDLERS[nodeId]; if (!handler) { - throw new Error(`signup-page.js 不处理节点 ${nodeId}`); + throw new Error(`openai-auth.js 不处理节点 ${nodeId}`); } return await handler(message.payload || {}); } @@ -1085,7 +1085,7 @@ function logSignupPasswordDiagnostics(context, level = 'warn') { try { log(`${context}:密码页诊断快照:${JSON.stringify(getSignupPasswordDiagnostics())}`, level); } catch (error) { - console.warn('[MultiPage:signup-page] failed to build signup password diagnostics:', error?.message || error); + console.warn('[MultiPage:openai-auth] failed to build signup password diagnostics:', error?.message || error); } } @@ -1310,7 +1310,7 @@ async function fillSignupEmailAndContinue(email, step) { }); } catch (error) { if (!isStopError(error)) { - console.error('[MultiPage:signup-page] deferred signup email submit failed:', error?.message || error); + console.error('[MultiPage:openai-auth] deferred signup email submit failed:', error?.message || error); } } }, 120); @@ -2580,7 +2580,7 @@ async function submitSignupPhoneNumberAndContinue(payload = {}) { }); } catch (error) { if (!isStopError(error)) { - console.error('[MultiPage:signup-page] deferred signup phone submit failed:', error?.message || error); + console.error('[MultiPage:openai-auth] deferred signup phone submit failed:', error?.message || error); } } }, 120); @@ -2720,7 +2720,7 @@ async function step3_fillEmailPassword(payload) { log('步骤 3:表单已提交'); } catch (error) { if (!isStopError(error)) { - console.error('[MultiPage:signup-page] deferred step 3 submit failed:', error?.message || error); + console.error('[MultiPage:openai-auth] deferred step 3 submit failed:', error?.message || error); } } }, 120); diff --git a/content/paypal-flow.js b/flows/openai/content/paypal-flow.js similarity index 99% rename from content/paypal-flow.js rename to flows/openai/content/paypal-flow.js index 959245d..8f89943 100644 --- a/content/paypal-flow.js +++ b/flows/openai/content/paypal-flow.js @@ -1,4 +1,4 @@ -// content/paypal-flow.js — PayPal login and approval helper. +// flows/openai/content/paypal-flow.js — PayPal login and approval helper. console.log('[MultiPage:paypal-flow] Content script loaded on', location.href); diff --git a/content/phone-auth.js b/flows/openai/content/phone-auth.js similarity index 100% rename from content/phone-auth.js rename to flows/openai/content/phone-auth.js diff --git a/content/phone-country-utils.js b/flows/openai/content/phone-country-utils.js similarity index 100% rename from content/phone-country-utils.js rename to flows/openai/content/phone-country-utils.js diff --git a/content/plus-checkout.js b/flows/openai/content/plus-checkout.js similarity index 99% rename from content/plus-checkout.js rename to flows/openai/content/plus-checkout.js index d29360d..79e2bd9 100644 --- a/content/plus-checkout.js +++ b/flows/openai/content/plus-checkout.js @@ -1,4 +1,4 @@ -// content/plus-checkout.js — ChatGPT Plus checkout helper. +// flows/openai/content/plus-checkout.js — ChatGPT Plus checkout helper. (function attachPlusCheckoutContentScript() { console.log('[MultiPage:plus-checkout] Content script loaded on', location.href); diff --git a/content/sub2api-panel.js b/flows/openai/content/sub2api-panel.js similarity index 99% rename from content/sub2api-panel.js rename to flows/openai/content/sub2api-panel.js index 6d3d25b..a9ef9a5 100644 --- a/content/sub2api-panel.js +++ b/flows/openai/content/sub2api-panel.js @@ -1,4 +1,4 @@ -// content/sub2api-panel.js — 页内脚本:SUB2API 后台(OAuth 生成与回调提交) +// flows/openai/content/sub2api-panel.js — 页内脚本:SUB2API 后台(OAuth 生成与回调提交) console.log('[MultiPage:sub2api-panel] Content script loaded on', location.href); diff --git a/content/vps-panel.js b/flows/openai/content/vps-panel.js similarity index 99% rename from content/vps-panel.js rename to flows/openai/content/vps-panel.js index 4b4d341..0a1d9df 100644 --- a/content/vps-panel.js +++ b/flows/openai/content/vps-panel.js @@ -1,4 +1,4 @@ -// content/vps-panel.js — Content script for CPA panel (OAuth URL request / platform verification node) +// flows/openai/content/vps-panel.js — Content script for CPA panel (OAuth URL request / platform verification node) // Injected on: CPA panel (user-configured URL) // // Actual DOM structure (after login click): diff --git a/content/whatsapp-flow.js b/flows/openai/content/whatsapp-flow.js similarity index 98% rename from content/whatsapp-flow.js rename to flows/openai/content/whatsapp-flow.js index 99ef22b..064c0f2 100644 --- a/content/whatsapp-flow.js +++ b/flows/openai/content/whatsapp-flow.js @@ -1,4 +1,4 @@ -// content/whatsapp-flow.js — WhatsApp Web code reader for GoPay. +// flows/openai/content/whatsapp-flow.js — WhatsApp Web code reader for GoPay. console.log('[MultiPage:whatsapp-flow] Content script loaded on', location.href); diff --git a/flows/openai/index.js b/flows/openai/index.js new file mode 100644 index 0000000..d98c68e --- /dev/null +++ b/flows/openai/index.js @@ -0,0 +1,425 @@ +(function attachMultiPageOpenAiFlowDefinition(root, factory) { + root.MultiPageOpenAiFlowDefinition = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createMultiPageOpenAiFlowDefinition() { + function freezeDeep(entry) { + if (!entry || typeof entry !== 'object' || Object.isFrozen(entry)) { + return entry; + } + Object.getOwnPropertyNames(entry).forEach((key) => { + freezeDeep(entry[key]); + }); + return Object.freeze(entry); + } + + const VALUE = freezeDeep({ + "id": "openai", + "label": "Codex / OpenAI", + "services": [ + "account", + "email", + "proxy" + ], + "capabilities": { + "supportsEmailSignup": true, + "supportsPhoneSignup": true, + "supportsPhoneVerificationSettings": true, + "supportsPlusMode": true, + "supportsContributionMode": true, + "supportsAccountContribution": true, + "supportsOpenAiOAuthContribution": true, + "contributionAdapterIds": [ + "openai-oauth", + "openai-codex-file", + "openai-sub2api-file" + ], + "supportedTargetIds": [ + "cpa", + "sub2api", + "codex2api" + ], + "supportsLuckmail": true, + "supportsOauthTimeoutBudget": true, + "canSwitchFlow": true, + "stepDefinitionMode": "openai-dynamic", + "targetSelectorLabel": "来源" + }, + "baseGroups": [ + "openai-plus", + "openai-phone", + "openai-oauth", + "openai-step6" + ], + "targets": { + "cpa": { + "id": "cpa", + "label": "CPA 面板", + "groups": [ + "openai-target-cpa" + ] + }, + "sub2api": { + "id": "sub2api", + "label": "SUB2API", + "groups": [ + "openai-target-sub2api" + ] + }, + "codex2api": { + "id": "codex2api", + "label": "Codex2API", + "groups": [ + "openai-target-codex2api" + ] + } + }, + "runtimeSources": { + "openai-auth": { + "flowId": "openai", + "kind": "flow-page", + "label": "认证页", + "readyPolicy": "allow-child-frame", + "family": "openai-auth-family", + "driverId": "flows/openai/content/openai-auth", + "cleanupScopes": [ + "oauth-localhost-callback" + ], + "detectionMatchers": [ + { + "hostnames": [ + "auth0.openai.com", + "auth.openai.com", + "accounts.openai.com" + ] + } + ], + "familyMatchers": [ + { + "hostnames": [ + "auth0.openai.com", + "auth.openai.com", + "accounts.openai.com" + ] + }, + { + "hostnames": [ + "chatgpt.com", + "www.chatgpt.com", + "chat.openai.com" + ] + } + ] + }, + "chatgpt": { + "flowId": "openai", + "kind": "flow-entry", + "label": "ChatGPT 首页", + "readyPolicy": "allow-child-frame", + "family": "chatgpt-entry-family", + "driverId": null, + "cleanupScopes": [], + "detectionMatchers": [ + { + "hostnames": [ + "chatgpt.com", + "www.chatgpt.com", + "chat.openai.com" + ] + } + ], + "familyMatchers": [ + { + "hostnames": [ + "chatgpt.com", + "www.chatgpt.com", + "chat.openai.com" + ] + } + ] + }, + "vps-panel": { + "flowId": "openai", + "kind": "panel-page", + "label": "CPA 面板", + "readyPolicy": "allow-child-frame", + "family": "vps-panel-family", + "driverId": "flows/openai/content/vps-panel", + "cleanupScopes": [], + "familyMatchers": [ + { + "originEqualsReference": true, + "pathEqualsReference": true + } + ] + }, + "platform-panel": { + "flowId": "openai", + "kind": "virtual-page", + "label": "平台回调面板", + "readyPolicy": "disabled", + "family": "platform-panel-family", + "driverId": "content/platform-panel", + "cleanupScopes": [], + "familyMatchers": [] + }, + "sub2api-panel": { + "flowId": "openai", + "kind": "panel-page", + "label": "SUB2API 后台", + "readyPolicy": "allow-child-frame", + "family": "sub2api-panel-family", + "driverId": "flows/openai/content/sub2api-panel", + "cleanupScopes": [], + "familyMatchers": [ + { + "originEqualsReference": true, + "pathPrefixes": [ + "/admin/accounts" + ] + }, + { + "originEqualsReference": true, + "pathPrefixes": [ + "/login" + ] + }, + { + "originEqualsReference": true, + "pathEqualsOneOf": [ + "/" + ] + } + ] + }, + "codex2api-panel": { + "flowId": "openai", + "kind": "panel-page", + "label": "Codex2API 后台", + "readyPolicy": "allow-child-frame", + "family": "codex2api-panel-family", + "driverId": "flows/openai/content/sub2api-panel", + "cleanupScopes": [], + "familyMatchers": [ + { + "originEqualsReference": true, + "pathPrefixes": [ + "/admin/accounts" + ] + }, + { + "originEqualsReference": true, + "pathEqualsOneOf": [ + "/admin", + "/" + ] + } + ] + }, + "plus-checkout": { + "flowId": "openai", + "kind": "flow-page", + "label": "Plus Checkout", + "readyPolicy": "top-frame-only", + "family": "plus-checkout-family", + "driverId": "flows/openai/content/plus-checkout", + "cleanupScopes": [], + "familyMatchers": [ + { + "hostnames": [ + "chatgpt.com" + ], + "pathPrefixes": [ + "/checkout/" + ] + } + ] + }, + "paypal-flow": { + "flowId": "openai", + "kind": "flow-page", + "label": "PayPal 授权页", + "readyPolicy": "allow-child-frame", + "family": "paypal-flow-family", + "driverId": "flows/openai/content/paypal-flow", + "cleanupScopes": [], + "familyMatchers": [ + { + "hostnameEndsWith": [ + "paypal.com" + ] + } + ] + }, + "gopay-flow": { + "flowId": "openai", + "kind": "flow-page", + "label": "GoPay 授权页", + "readyPolicy": "allow-child-frame", + "family": "gopay-flow-family", + "driverId": "flows/openai/content/gopay-flow", + "cleanupScopes": [], + "familyMatchers": [ + { + "hostnameRegex": "gopay|gojek", + "hostnameRegexFlags": "i" + } + ] + } + }, + "driverDefinitions": { + "flows/openai/content/openai-auth": { + "sourceId": "openai-auth", + "commands": [ + "submit-signup-email", + "fill-password", + "fill-profile", + "oauth-login", + "submit-verification-code", + "post-login-phone-verification", + "bind-email", + "fetch-bind-email-code", + "confirm-oauth", + "detect-auth-state" + ] + }, + "flows/openai/content/sub2api-panel": { + "sourceId": "sub2api-panel", + "commands": [ + "open-panel", + "fetch-oauth-url", + "platform-verify" + ] + }, + "flows/openai/content/vps-panel": { + "sourceId": "vps-panel", + "commands": [ + "open-panel", + "fetch-oauth-url", + "platform-verify" + ] + }, + "content/platform-panel": { + "sourceId": "platform-panel", + "commands": [ + "platform-verify", + "fetch-oauth-url" + ] + }, + "flows/openai/content/plus-checkout": { + "sourceId": "plus-checkout", + "commands": [ + "plus-checkout-create", + "paypal-hosted-openai-checkout", + "plus-checkout-billing", + "plus-checkout-return" + ] + }, + "flows/openai/content/paypal-flow": { + "sourceId": "paypal-flow", + "commands": [ + "paypal-approve", + "paypal-hosted-email", + "paypal-hosted-card", + "paypal-hosted-create-account", + "paypal-hosted-review" + ] + }, + "flows/openai/content/gopay-flow": { + "sourceId": "gopay-flow", + "commands": [ + "gopay-subscription-confirm" + ] + } + }, + "defaultTargetId": "cpa", + "settingsGroups": { + "openai-target-cpa": { + "id": "openai-target-cpa", + "label": "CPA 来源", + "rowIds": [ + "row-vps-url", + "row-vps-password", + "row-local-cpa-step9-mode" + ] + }, + "openai-target-sub2api": { + "id": "openai-target-sub2api", + "label": "SUB2API 来源", + "rowIds": [ + "row-sub2api-url", + "row-sub2api-email", + "row-sub2api-password", + "row-sub2api-group", + "row-sub2api-account-priority", + "row-sub2api-default-proxy" + ] + }, + "openai-target-codex2api": { + "id": "openai-target-codex2api", + "label": "Codex2API 来源", + "rowIds": [ + "row-codex2api-url", + "row-codex2api-admin-key" + ] + }, + "openai-plus": { + "id": "openai-plus", + "label": "Plus", + "rowIds": [ + "row-plus-mode", + "row-plus-account-access-strategy", + "row-plus-payment-method" + ] + }, + "openai-phone": { + "id": "openai-phone", + "label": "接码设置", + "sectionIds": [ + "phone-verification-section" + ], + "rowIds": [] + }, + "openai-oauth": { + "id": "openai-oauth", + "label": "OAuth", + "rowIds": [ + "row-oauth-flow-timeout", + "row-oauth-display" + ] + }, + "openai-step6": { + "id": "openai-step6", + "label": "第六步", + "rowIds": [ + "row-step6-cookie-settings" + ] + } + }, + "targetCapabilities": { + "cpa": { + "supportsPhoneSignup": true, + "requiresPhoneSignupWarning": true, + "supportedPlusAccountAccessStrategies": [ + "oauth", + "cpa_codex_session" + ] + }, + "sub2api": { + "supportsPhoneSignup": true, + "requiresPhoneSignupWarning": false, + "supportedPlusAccountAccessStrategies": [ + "oauth", + "sub2api_codex_session" + ] + }, + "codex2api": { + "supportsPhoneSignup": true, + "requiresPhoneSignupWarning": false, + "supportedPlusAccountAccessStrategies": [ + "oauth" + ] + } + } +}); + + return VALUE; +}); diff --git a/flows/openai/workflow.js b/flows/openai/workflow.js new file mode 100644 index 0000000..59d0ad1 --- /dev/null +++ b/flows/openai/workflow.js @@ -0,0 +1,3166 @@ +(function attachMultiPageOpenAiWorkflow(root, factory) { + root.MultiPageOpenAiWorkflow = factory(); +})(typeof self !== 'undefined' ? self : globalThis, function createMultiPageOpenAiWorkflow() { + const SIGNUP_METHOD_EMAIL = 'email'; + const SIGNUP_METHOD_PHONE = 'phone'; + const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal'; + const PLUS_PAYMENT_METHOD_PAYPAL_HOSTED = 'paypal-hosted'; + const PLUS_PAYMENT_METHOD_GOPAY = 'gopay'; + const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper'; + const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth'; + const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session'; + const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session'; + const PLUS_PAYMENT_STEP_KEY = 'paypal-approve'; + + function freezeDeep(entry) { + if (!entry || typeof entry !== 'object' || Object.isFrozen(entry)) { + return entry; + } + Object.getOwnPropertyNames(entry).forEach((key) => { + freezeDeep(entry[key]); + }); + return Object.freeze(entry); + } + + const STEP_VARIANTS = freezeDeep({ + "normal": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取注册验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "wait-registration-success", + "title": "等待注册成功", + "sourceId": "chatgpt", + "driverId": null, + "command": "wait-registration-success", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 8, + "order": 80, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 9, + "order": 90, + "key": "post-login-phone-verification", + "title": "手机号验证", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "post-login-phone-verification", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ], + "normalPhone": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入手机号", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取手机验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "wait-registration-success", + "title": "等待注册成功", + "sourceId": "chatgpt", + "driverId": null, + "command": "wait-registration-success", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 8, + "order": 80, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 9, + "order": 90, + "key": "bind-email", + "title": "绑定邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "bind-email", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "fetch-bind-email-code", + "title": "获取绑定邮箱验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fetch-bind-email-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 12, + "order": 120, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ], + "normalPhoneRelogin": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入手机号", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取手机验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "wait-registration-success", + "title": "等待注册成功", + "sourceId": "chatgpt", + "driverId": null, + "command": "wait-registration-success", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 8, + "order": 80, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 9, + "order": 90, + "key": "bind-email", + "title": "绑定邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "bind-email", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "fetch-bind-email-code", + "title": "获取绑定邮箱验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fetch-bind-email-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "relogin-bound-email", + "title": "绑定邮箱后刷新 OAuth 并登录(邮箱)", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 12, + "order": 120, + "key": "fetch-bound-email-login-code", + "title": "获取登录验证码(邮箱)", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 13, + "order": 130, + "key": "post-bound-email-phone-verification", + "title": "手机号验证", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "post-login-phone-verification", + "flowId": "openai" + }, + { + "id": 14, + "order": 140, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 15, + "order": 150, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ], + "plusPaypal": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取注册验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 Plus Checkout", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "plus-checkout-billing", + "title": "填写账单并提交订单", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-billing", + "flowId": "openai" + }, + { + "id": 8, + "order": 80, + "key": "paypal-approve", + "title": "PayPal 登录与授权", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-approve", + "flowId": "openai" + }, + { + "id": 9, + "order": 90, + "key": "plus-checkout-return", + "title": "订阅回跳确认", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-return", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 12, + "order": 120, + "key": "post-login-phone-verification", + "title": "手机号验证", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "post-login-phone-verification", + "flowId": "openai" + }, + { + "id": 13, + "order": 130, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 14, + "order": 140, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ], + "plusPaypalSub2apiSession": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取注册验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 Plus Checkout", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "plus-checkout-billing", + "title": "填写账单并提交订单", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-billing", + "flowId": "openai" + }, + { + "id": 8, + "order": 80, + "key": "paypal-approve", + "title": "PayPal 登录与授权", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-approve", + "flowId": "openai" + }, + { + "id": 9, + "order": 90, + "key": "plus-checkout-return", + "title": "订阅回跳确认", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-return", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "sub2api-session-import", + "title": "导入当前 ChatGPT 会话到 SUB2API", + "sourceId": "sub2api-panel", + "driverId": "flows/openai/background/steps/sub2api-session-import", + "command": "sub2api-session-import", + "flowId": "openai" + } + ], + "plusPaypalCpaSession": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取注册验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 Plus Checkout", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "plus-checkout-billing", + "title": "填写账单并提交订单", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-billing", + "flowId": "openai" + }, + { + "id": 8, + "order": 80, + "key": "paypal-approve", + "title": "PayPal 登录与授权", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-approve", + "flowId": "openai" + }, + { + "id": 9, + "order": 90, + "key": "plus-checkout-return", + "title": "订阅回跳确认", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-return", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "cpa-session-import", + "title": "导入当前 ChatGPT 会话到 CPA", + "sourceId": "vps-panel", + "driverId": "flows/openai/background/steps/cpa-session-import", + "command": "cpa-session-import", + "flowId": "openai" + } + ], + "plusPaypalPhone": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入手机号", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取手机验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 Plus Checkout", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "plus-checkout-billing", + "title": "填写账单并提交订单", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-billing", + "flowId": "openai" + }, + { + "id": 8, + "order": 80, + "key": "paypal-approve", + "title": "PayPal 登录与授权", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-approve", + "flowId": "openai" + }, + { + "id": 9, + "order": 90, + "key": "plus-checkout-return", + "title": "订阅回跳确认", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-return", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 12, + "order": 120, + "key": "bind-email", + "title": "绑定邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "bind-email", + "flowId": "openai" + }, + { + "id": 13, + "order": 130, + "key": "fetch-bind-email-code", + "title": "获取绑定邮箱验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fetch-bind-email-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 14, + "order": 140, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 15, + "order": 150, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ], + "plusPaypalPhoneRelogin": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入手机号", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取手机验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 Plus Checkout", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "plus-checkout-billing", + "title": "填写账单并提交订单", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-billing", + "flowId": "openai" + }, + { + "id": 8, + "order": 80, + "key": "paypal-approve", + "title": "PayPal 登录与授权", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-approve", + "flowId": "openai" + }, + { + "id": 9, + "order": 90, + "key": "plus-checkout-return", + "title": "订阅回跳确认", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-return", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 12, + "order": 120, + "key": "bind-email", + "title": "绑定邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "bind-email", + "flowId": "openai" + }, + { + "id": 13, + "order": 130, + "key": "fetch-bind-email-code", + "title": "获取绑定邮箱验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fetch-bind-email-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 14, + "order": 140, + "key": "relogin-bound-email", + "title": "绑定邮箱后刷新 OAuth 并登录(邮箱)", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 15, + "order": 150, + "key": "fetch-bound-email-login-code", + "title": "获取登录验证码(邮箱)", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 16, + "order": 160, + "key": "post-bound-email-phone-verification", + "title": "手机号验证", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "post-login-phone-verification", + "flowId": "openai" + }, + { + "id": 17, + "order": 170, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 18, + "order": 180, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ], + "plusPaypalHosted": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取注册验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 Plus Checkout", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "paypal-hosted-email", + "title": "无卡直绑填写 PayPal 邮箱", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-email", + "flowId": "openai" + }, + { + "id": 8, + "order": 80, + "key": "paypal-hosted-card", + "title": "无卡直绑填写 PayPal 资料", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-card", + "flowId": "openai" + }, + { + "id": 9, + "order": 90, + "key": "paypal-hosted-create-account", + "title": "无卡直绑确认创建 PayPal", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-create-account", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "paypal-hosted-review", + "title": "无卡直绑完成 PayPal 授权", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-review", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 12, + "order": 120, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 13, + "order": 130, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 14, + "order": 140, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ], + "plusPaypalHostedSub2apiSession": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取注册验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 Plus Checkout", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "paypal-hosted-email", + "title": "无卡直绑填写 PayPal 邮箱", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-email", + "flowId": "openai" + }, + { + "id": 8, + "order": 80, + "key": "paypal-hosted-card", + "title": "无卡直绑填写 PayPal 资料", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-card", + "flowId": "openai" + }, + { + "id": 9, + "order": 90, + "key": "paypal-hosted-create-account", + "title": "无卡直绑确认创建 PayPal", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-create-account", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "paypal-hosted-review", + "title": "无卡直绑完成 PayPal 授权", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-review", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "sub2api-session-import", + "title": "导入当前 ChatGPT 会话到 SUB2API", + "sourceId": "sub2api-panel", + "driverId": "flows/openai/background/steps/sub2api-session-import", + "command": "sub2api-session-import", + "flowId": "openai" + } + ], + "plusPaypalHostedCpaSession": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取注册验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 Plus Checkout", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "paypal-hosted-email", + "title": "无卡直绑填写 PayPal 邮箱", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-email", + "flowId": "openai" + }, + { + "id": 8, + "order": 80, + "key": "paypal-hosted-card", + "title": "无卡直绑填写 PayPal 资料", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-card", + "flowId": "openai" + }, + { + "id": 9, + "order": 90, + "key": "paypal-hosted-create-account", + "title": "无卡直绑确认创建 PayPal", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-create-account", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "paypal-hosted-review", + "title": "无卡直绑完成 PayPal 授权", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-review", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "cpa-session-import", + "title": "导入当前 ChatGPT 会话到 CPA", + "sourceId": "vps-panel", + "driverId": "flows/openai/background/steps/cpa-session-import", + "command": "cpa-session-import", + "flowId": "openai" + } + ], + "plusPaypalHostedPhone": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入手机号", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取手机验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 Plus Checkout", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "paypal-hosted-email", + "title": "无卡直绑填写 PayPal 邮箱", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-email", + "flowId": "openai" + }, + { + "id": 8, + "order": 80, + "key": "paypal-hosted-card", + "title": "无卡直绑填写 PayPal 资料", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-card", + "flowId": "openai" + }, + { + "id": 9, + "order": 90, + "key": "paypal-hosted-create-account", + "title": "无卡直绑确认创建 PayPal", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-create-account", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "paypal-hosted-review", + "title": "无卡直绑完成 PayPal 授权", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-review", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 12, + "order": 120, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 13, + "order": 130, + "key": "bind-email", + "title": "绑定邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "bind-email", + "flowId": "openai" + }, + { + "id": 14, + "order": 140, + "key": "fetch-bind-email-code", + "title": "获取绑定邮箱验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fetch-bind-email-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 15, + "order": 150, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 16, + "order": 160, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ], + "plusPaypalHostedPhoneRelogin": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入手机号", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取手机验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 Plus Checkout", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "paypal-hosted-email", + "title": "无卡直绑填写 PayPal 邮箱", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-email", + "flowId": "openai" + }, + { + "id": 8, + "order": 80, + "key": "paypal-hosted-card", + "title": "无卡直绑填写 PayPal 资料", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-card", + "flowId": "openai" + }, + { + "id": 9, + "order": 90, + "key": "paypal-hosted-create-account", + "title": "无卡直绑确认创建 PayPal", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-create-account", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "paypal-hosted-review", + "title": "无卡直绑完成 PayPal 授权", + "sourceId": "paypal-flow", + "driverId": "flows/openai/content/paypal-flow", + "command": "paypal-hosted-review", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 12, + "order": 120, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 13, + "order": 130, + "key": "bind-email", + "title": "绑定邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "bind-email", + "flowId": "openai" + }, + { + "id": 14, + "order": 140, + "key": "fetch-bind-email-code", + "title": "获取绑定邮箱验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fetch-bind-email-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 15, + "order": 150, + "key": "relogin-bound-email", + "title": "绑定邮箱后刷新 OAuth 并登录(邮箱)", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 16, + "order": 160, + "key": "fetch-bound-email-login-code", + "title": "获取登录验证码(邮箱)", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 17, + "order": 170, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 18, + "order": 180, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ], + "plusGopay": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取注册验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "打开 GoPay 订阅页", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "gopay-subscription-confirm", + "title": "等待 GoPay 订阅确认", + "sourceId": "gopay-flow", + "driverId": "flows/openai/content/gopay-flow", + "command": "gopay-subscription-confirm", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 12, + "order": 120, + "key": "post-login-phone-verification", + "title": "手机号验证", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "post-login-phone-verification", + "flowId": "openai" + }, + { + "id": 13, + "order": 130, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 14, + "order": 140, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ], + "plusGopaySub2apiSession": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取注册验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "打开 GoPay 订阅页", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "gopay-subscription-confirm", + "title": "等待 GoPay 订阅确认", + "sourceId": "gopay-flow", + "driverId": "flows/openai/content/gopay-flow", + "command": "gopay-subscription-confirm", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "sub2api-session-import", + "title": "导入当前 ChatGPT 会话到 SUB2API", + "sourceId": "sub2api-panel", + "driverId": "flows/openai/background/steps/sub2api-session-import", + "command": "sub2api-session-import", + "flowId": "openai" + } + ], + "plusGopayCpaSession": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取注册验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "打开 GoPay 订阅页", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "gopay-subscription-confirm", + "title": "等待 GoPay 订阅确认", + "sourceId": "gopay-flow", + "driverId": "flows/openai/content/gopay-flow", + "command": "gopay-subscription-confirm", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "cpa-session-import", + "title": "导入当前 ChatGPT 会话到 CPA", + "sourceId": "vps-panel", + "driverId": "flows/openai/background/steps/cpa-session-import", + "command": "cpa-session-import", + "flowId": "openai" + } + ], + "plusGopayPhone": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入手机号", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取手机验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "打开 GoPay 订阅页", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "gopay-subscription-confirm", + "title": "等待 GoPay 订阅确认", + "sourceId": "gopay-flow", + "driverId": "flows/openai/content/gopay-flow", + "command": "gopay-subscription-confirm", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 12, + "order": 120, + "key": "bind-email", + "title": "绑定邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "bind-email", + "flowId": "openai" + }, + { + "id": 13, + "order": 130, + "key": "fetch-bind-email-code", + "title": "获取绑定邮箱验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fetch-bind-email-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 14, + "order": 140, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 15, + "order": 150, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ], + "plusGopayPhoneRelogin": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入手机号", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取手机验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "打开 GoPay 订阅页", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "gopay-subscription-confirm", + "title": "等待 GoPay 订阅确认", + "sourceId": "gopay-flow", + "driverId": "flows/openai/content/gopay-flow", + "command": "gopay-subscription-confirm", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 12, + "order": 120, + "key": "bind-email", + "title": "绑定邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "bind-email", + "flowId": "openai" + }, + { + "id": 13, + "order": 130, + "key": "fetch-bind-email-code", + "title": "获取绑定邮箱验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fetch-bind-email-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 14, + "order": 140, + "key": "relogin-bound-email", + "title": "绑定邮箱后刷新 OAuth 并登录(邮箱)", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 15, + "order": 150, + "key": "fetch-bound-email-login-code", + "title": "获取登录验证码(邮箱)", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 16, + "order": 160, + "key": "post-bound-email-phone-verification", + "title": "手机号验证", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "post-login-phone-verification", + "flowId": "openai" + }, + { + "id": 17, + "order": 170, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 18, + "order": 180, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ], + "plusGpc": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取注册验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 GPC 订单", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "plus-checkout-billing", + "title": "等待 GPC 任务完成", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-billing", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 12, + "order": 120, + "key": "post-login-phone-verification", + "title": "手机号验证", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "post-login-phone-verification", + "flowId": "openai" + }, + { + "id": 13, + "order": 130, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 14, + "order": 140, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ], + "plusGpcSub2apiSession": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取注册验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 GPC 订单", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "plus-checkout-billing", + "title": "等待 GPC 任务完成", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-billing", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "sub2api-session-import", + "title": "导入当前 ChatGPT 会话到 SUB2API", + "sourceId": "sub2api-panel", + "driverId": "flows/openai/background/steps/sub2api-session-import", + "command": "sub2api-session-import", + "flowId": "openai" + } + ], + "plusGpcCpaSession": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取注册验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 GPC 订单", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "plus-checkout-billing", + "title": "等待 GPC 任务完成", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-billing", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "cpa-session-import", + "title": "导入当前 ChatGPT 会话到 CPA", + "sourceId": "vps-panel", + "driverId": "flows/openai/background/steps/cpa-session-import", + "command": "cpa-session-import", + "flowId": "openai" + } + ], + "plusGpcPhone": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入手机号", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取手机验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 GPC 订单", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "plus-checkout-billing", + "title": "等待 GPC 任务完成", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-billing", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 12, + "order": 120, + "key": "bind-email", + "title": "绑定邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "bind-email", + "flowId": "openai" + }, + { + "id": 13, + "order": 130, + "key": "fetch-bind-email-code", + "title": "获取绑定邮箱验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fetch-bind-email-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 14, + "order": 140, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 15, + "order": 150, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ], + "plusGpcPhoneRelogin": [ + { + "id": 1, + "order": 10, + "key": "open-chatgpt", + "title": "打开 ChatGPT 官网", + "sourceId": "chatgpt", + "driverId": null, + "command": "open-chatgpt", + "flowId": "openai" + }, + { + "id": 2, + "order": 20, + "key": "submit-signup-email", + "title": "注册并输入手机号", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-signup-email", + "flowId": "openai" + }, + { + "id": 3, + "order": 30, + "key": "fill-password", + "title": "填写密码并继续", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-password", + "flowId": "openai" + }, + { + "id": 4, + "order": 40, + "key": "fetch-signup-code", + "title": "获取手机验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-signup-code", + "flowId": "openai" + }, + { + "id": 5, + "order": 50, + "key": "fill-profile", + "title": "填写姓名和生日", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fill-profile", + "flowId": "openai" + }, + { + "id": 6, + "order": 60, + "key": "plus-checkout-create", + "title": "创建 GPC 订单", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-create", + "flowId": "openai" + }, + { + "id": 7, + "order": 70, + "key": "plus-checkout-billing", + "title": "等待 GPC 任务完成", + "sourceId": "plus-checkout", + "driverId": "flows/openai/content/plus-checkout", + "command": "plus-checkout-billing", + "flowId": "openai" + }, + { + "id": 10, + "order": 100, + "key": "oauth-login", + "title": "刷新 OAuth 并登录", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 11, + "order": 110, + "key": "fetch-login-code", + "title": "获取登录验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 12, + "order": 120, + "key": "bind-email", + "title": "绑定邮箱", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "bind-email", + "flowId": "openai" + }, + { + "id": 13, + "order": 130, + "key": "fetch-bind-email-code", + "title": "获取绑定邮箱验证码", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "fetch-bind-email-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 14, + "order": 140, + "key": "relogin-bound-email", + "title": "绑定邮箱后刷新 OAuth 并登录(邮箱)", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "oauth-login", + "flowId": "openai" + }, + { + "id": 15, + "order": 150, + "key": "fetch-bound-email-login-code", + "title": "获取登录验证码(邮箱)", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "submit-verification-code", + "mailRuleId": "openai-login-code", + "flowId": "openai" + }, + { + "id": 16, + "order": 160, + "key": "post-bound-email-phone-verification", + "title": "手机号验证", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "post-login-phone-verification", + "flowId": "openai" + }, + { + "id": 17, + "order": 170, + "key": "confirm-oauth", + "title": "自动确认 OAuth", + "sourceId": "openai-auth", + "driverId": "flows/openai/content/openai-auth", + "command": "confirm-oauth", + "flowId": "openai" + }, + { + "id": 18, + "order": 180, + "key": "platform-verify", + "title": "平台回调验证", + "sourceId": "platform-panel", + "driverId": "content/platform-panel", + "command": "platform-verify", + "flowId": "openai" + } + ] +}); + + function isPlusModeEnabled(options = {}) { + return Boolean(options?.plusModeEnabled || options?.plusMode); + } + + function normalizePlusPaymentMethod(value = '') { + const normalized = String(value || '').trim().toLowerCase(); + if (normalized === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED || normalized === 'paypal_direct' || normalized === 'paypal-direct') { + return PLUS_PAYMENT_METHOD_PAYPAL_HOSTED; + } + if (normalized === PLUS_PAYMENT_METHOD_GPC_HELPER) { + return PLUS_PAYMENT_METHOD_GPC_HELPER; + } + return normalized === PLUS_PAYMENT_METHOD_GOPAY ? PLUS_PAYMENT_METHOD_GOPAY : PLUS_PAYMENT_METHOD_PAYPAL; + } + + function normalizeSignupMethod(value = '') { + return String(value || '').trim().toLowerCase() === SIGNUP_METHOD_PHONE + ? SIGNUP_METHOD_PHONE + : SIGNUP_METHOD_EMAIL; + } + + function isPhoneSignupReloginAfterBindEmailEnabled(options = {}) { + return Boolean(options?.phoneSignupReloginAfterBindEmailEnabled); + } + + function normalizePlusAccountAccessStrategy(value = '') { + const normalized = String(value || '').trim().toLowerCase(); + if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) { + return PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION; + } + if (normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) { + return PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION; + } + return PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; + } + + function resolveVariantKey(options = {}) { + const signupMethod = normalizeSignupMethod(options?.resolvedSignupMethod || options?.signupMethod); + const reloginAfterBindEmail = signupMethod === SIGNUP_METHOD_PHONE && isPhoneSignupReloginAfterBindEmailEnabled(options); + if (!isPlusModeEnabled(options)) { + if (signupMethod === SIGNUP_METHOD_PHONE) { + return reloginAfterBindEmail ? 'normalPhoneRelogin' : 'normalPhone'; + } + return 'normal'; + } + + const plusAccountAccessStrategy = normalizePlusAccountAccessStrategy(options?.plusAccountAccessStrategy); + const paymentMethod = normalizePlusPaymentMethod(options?.plusPaymentMethod || options?.paymentMethod); + if (paymentMethod === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED) { + if (signupMethod === SIGNUP_METHOD_PHONE) { + return reloginAfterBindEmail ? 'plusPaypalHostedPhoneRelogin' : 'plusPaypalHostedPhone'; + } + if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) { + return 'plusPaypalHostedSub2apiSession'; + } + if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) { + return 'plusPaypalHostedCpaSession'; + } + return 'plusPaypalHosted'; + } + + if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY) { + if (signupMethod === SIGNUP_METHOD_PHONE) { + return reloginAfterBindEmail ? 'plusGopayPhoneRelogin' : 'plusGopayPhone'; + } + if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) { + return 'plusGopaySub2apiSession'; + } + if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) { + return 'plusGopayCpaSession'; + } + return 'plusGopay'; + } + + if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) { + if (signupMethod === SIGNUP_METHOD_PHONE) { + return reloginAfterBindEmail ? 'plusGpcPhoneRelogin' : 'plusGpcPhone'; + } + if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) { + return 'plusGpcSub2apiSession'; + } + if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) { + return 'plusGpcCpaSession'; + } + return 'plusGpc'; + } + + if (signupMethod === SIGNUP_METHOD_PHONE) { + return reloginAfterBindEmail ? 'plusPaypalPhoneRelogin' : 'plusPaypalPhone'; + } + if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION) { + return 'plusPaypalSub2apiSession'; + } + if (plusAccountAccessStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION) { + return 'plusPaypalCpaSession'; + } + return 'plusPaypal'; + } + + function getVariantStepDefinitions(variantKey) { + return Array.isArray(STEP_VARIANTS[variantKey]) ? STEP_VARIANTS[variantKey] : STEP_VARIANTS.normal; + } + + function getModeStepDefinitions(options = {}) { + return getVariantStepDefinitions(resolveVariantKey(options)); + } + + function getAllSteps() { + const keyed = new Map(); + Object.values(STEP_VARIANTS).forEach((steps) => { + (Array.isArray(steps) ? steps : []).forEach((step) => { + keyed.set(`${step.id}:${step.key}`, step); + }); + }); + return Array.from(keyed.values()).sort((left, right) => { + const leftOrder = Number.isFinite(left.order) ? left.order : left.id; + const rightOrder = Number.isFinite(right.order) ? right.order : right.id; + if (leftOrder !== rightOrder) return leftOrder - rightOrder; + return left.id - right.id; + }); + } + + function getPlusPaymentStepTitle(options = {}) { + if (!isPlusModeEnabled(options)) { + return ''; + } + const paymentStep = getModeStepDefinitions({ + ...options, + plusModeEnabled: true, + }).find((step) => step.key === PLUS_PAYMENT_STEP_KEY); + return paymentStep?.title || ''; + } + + function resolveStepTitle(step = {}) { + return step?.title || ''; + } + + return { + flowId: 'openai', + getAllSteps, + getModeStepDefinitions, + getPlusPaymentStepTitle, + getVariantStepDefinitions, + isPlusModeEnabled, + normalizePlusPaymentMethod, + normalizePlusAccountAccessStrategy, + normalizeSignupMethod, + resolveStepTitle, + }; +}); diff --git a/imports/legacy/account-records-importer.js b/imports/legacy/account-records-importer.js new file mode 100644 index 0000000..ad159ab --- /dev/null +++ b/imports/legacy/account-records-importer.js @@ -0,0 +1,47 @@ +(function attachLegacyAccountRecordsImporter(root, factory) { + root.MultiPageLegacyAccountRecordsImporter = factory(root); +})(typeof self !== 'undefined' ? self : globalThis, function createLegacyAccountRecordsImporterModule(root) { + function createAccountRecordsImporter(deps = {}) { + const createAccountRunHistoryHelpers = deps.createAccountRunHistoryHelpers + || root.MultiPageBackgroundAccountRunHistory?.createAccountRunHistoryHelpers; + + function importAccountRecords(records = []) { + if (!Array.isArray(records)) { + throw new Error('账号记录导入内容必须是数组。'); + } + if (typeof createAccountRunHistoryHelpers !== 'function') { + return records + .filter((record) => Boolean(record) && typeof record === 'object' && !Array.isArray(record)) + .map((record) => ({ ...record })); + } + + const helpers = createAccountRunHistoryHelpers({ + chrome: deps.chrome || { + storage: { + local: { + get: async () => ({}), + set: async () => {}, + }, + }, + }, + getState: deps.getState || (async () => ({})), + addLog: deps.addLog || (async () => {}), + getNodeIdByStepForState: deps.getNodeIdByStepForState || (() => ''), + normalizeAccountRunHistoryHelperBaseUrl: deps.normalizeAccountRunHistoryHelperBaseUrl + || ((value) => String(value || '').trim()), + }); + if (typeof helpers?.normalizeAccountRunHistory !== 'function') { + return []; + } + return helpers.normalizeAccountRunHistory(records); + } + + return { + importAccountRecords, + }; + } + + return { + createAccountRecordsImporter, + }; +}); diff --git a/imports/legacy/settings-importer.js b/imports/legacy/settings-importer.js new file mode 100644 index 0000000..6a7047e --- /dev/null +++ b/imports/legacy/settings-importer.js @@ -0,0 +1,142 @@ +(function attachLegacySettingsImporter(root, factory) { + root.MultiPageLegacySettingsImporter = factory(root); +})(typeof self !== 'undefined' ? self : globalThis, function createLegacySettingsImporterModule(root) { + const LEGACY_TOP_LEVEL_KEYS = Object.freeze([ + 'panelMode', + 'openaiIntegrationTargetId', + 'kiroTargetId', + 'stepExecutionRangeByFlow', + 'kiroRuntime', + ]); + + function isPlainObject(value) { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value); + } + + function cloneValue(value) { + if (Array.isArray(value)) { + return value.map((entry) => cloneValue(entry)); + } + if (isPlainObject(value)) { + return Object.fromEntries( + Object.entries(value).map(([key, entryValue]) => [key, cloneValue(entryValue)]) + ); + } + return value; + } + + function collectLegacyFieldHits(input = {}) { + if (!isPlainObject(input)) { + return []; + } + + const hits = []; + LEGACY_TOP_LEVEL_KEYS.forEach((key) => { + if (Object.prototype.hasOwnProperty.call(input, key)) { + hits.push(key); + } + }); + + const settingsState = isPlainObject(input.settingsState) ? input.settingsState : {}; + const openaiSettings = isPlainObject(settingsState?.flows?.openai) ? settingsState.flows.openai : {}; + const kiroSettings = isPlainObject(settingsState?.flows?.kiro) ? settingsState.flows.kiro : {}; + if (Object.prototype.hasOwnProperty.call(openaiSettings, 'integrationTargetId')) { + hits.push('settingsState.flows.openai.integrationTargetId'); + } + if (Object.prototype.hasOwnProperty.call(openaiSettings, 'integrationTargets')) { + hits.push('settingsState.flows.openai.integrationTargets'); + } + if (Object.prototype.hasOwnProperty.call(kiroSettings, 'targetId')) { + hits.push('settingsState.flows.kiro.targetId'); + } + + return Array.from(new Set(hits)); + } + + function resolveSettingsSchemaApi(deps = {}) { + if (deps.settingsSchemaApi?.normalizeSettingsState && deps.settingsSchemaApi?.buildSettingsView) { + return deps.settingsSchemaApi; + } + const settingsSchemaModule = deps.settingsSchemaModule || root.MultiPageSettingsSchema || null; + if (typeof settingsSchemaModule?.createSettingsSchema !== 'function') { + return null; + } + return settingsSchemaModule.createSettingsSchema({ + flowRegistry: deps.flowRegistry || root.MultiPageFlowRegistry || null, + defaultFlowId: deps.defaultFlowId, + }); + } + + function buildImportInput(input = {}) { + const next = isPlainObject(input) ? cloneValue(input) : {}; + const settingsState = isPlainObject(next.settingsState) ? next.settingsState : {}; + const flows = isPlainObject(settingsState.flows) ? settingsState.flows : {}; + const openaiState = isPlainObject(flows.openai) ? flows.openai : {}; + const kiroState = isPlainObject(flows.kiro) ? flows.kiro : {}; + const legacyOpenAiTargetId = String( + input?.openaiIntegrationTargetId + ?? input?.panelMode + ?? '' + ).trim(); + const legacyKiroTargetId = String(input?.kiroTargetId ?? '').trim(); + const openaiHasCanonicalTarget = Object.prototype.hasOwnProperty.call(openaiState, 'selectedTargetId') + || Object.prototype.hasOwnProperty.call(openaiState, 'integrationTargetId'); + const kiroHasCanonicalTarget = Object.prototype.hasOwnProperty.call(kiroState, 'selectedTargetId') + || Object.prototype.hasOwnProperty.call(kiroState, 'targetId'); + + if (!openaiHasCanonicalTarget && legacyOpenAiTargetId) { + next.settingsState = settingsState; + next.settingsState.flows = flows; + next.settingsState.flows.openai = { + ...openaiState, + selectedTargetId: legacyOpenAiTargetId, + }; + } + + if (!kiroHasCanonicalTarget && legacyKiroTargetId) { + next.settingsState = next.settingsState || settingsState; + next.settingsState.flows = next.settingsState.flows || flows; + next.settingsState.flows.kiro = { + ...kiroState, + selectedTargetId: legacyKiroTargetId, + }; + } + + return next; + } + + function createSettingsImporter(deps = {}) { + const settingsSchemaApi = resolveSettingsSchemaApi(deps); + + function importSettings(input = {}) { + if (!isPlainObject(input)) { + throw new Error('配置文件中的 settings 内容无效。'); + } + if (!settingsSchemaApi?.normalizeSettingsState) { + throw new Error('设置导入器未完成初始化。'); + } + + const importInput = buildImportInput(input); + const normalizedState = settingsSchemaApi.normalizeSettingsState(importInput, { + activeFlowId: importInput.activeFlowId || importInput.flowId || deps.defaultFlowId, + }); + + return { + settingsSchemaVersion: Number(normalizedState.schemaVersion) || 0, + settingsState: cloneValue(normalizedState), + legacyFieldHits: collectLegacyFieldHits(input), + }; + } + + return { + collectLegacyFieldHits, + importSettings, + }; + } + + return { + LEGACY_TOP_LEVEL_KEYS, + collectLegacyFieldHits, + createSettingsImporter, + }; +}); diff --git a/manifest.json b/manifest.json index c10b7bc..09a5163 100644 --- a/manifest.json +++ b/manifest.json @@ -49,13 +49,17 @@ ], "js": [ "content/activation-utils.js", - "shared/source-registry.js", + "flows/openai/index.js", + "flows/kiro/index.js", + "flows/index.js", + "core/flow-kernel/flow-registry.js", + "core/flow-kernel/source-registry.js", "content/utils.js", "content/operation-delay.js", - "content/auth-page-recovery.js", - "content/phone-country-utils.js", - "content/phone-auth.js", - "content/signup-page.js" + "flows/openai/content/auth-page-recovery.js", + "flows/openai/content/phone-country-utils.js", + "flows/openai/content/phone-auth.js", + "flows/openai/content/openai-auth.js" ], "run_at": "document_idle" }, @@ -66,7 +70,11 @@ ], "js": [ "content/activation-utils.js", - "shared/source-registry.js", + "flows/openai/index.js", + "flows/kiro/index.js", + "flows/index.js", + "core/flow-kernel/flow-registry.js", + "core/flow-kernel/source-registry.js", "content/utils.js", "content/qq-mail.js" ], @@ -83,7 +91,11 @@ ], "js": [ "content/activation-utils.js", - "shared/source-registry.js", + "flows/openai/index.js", + "flows/kiro/index.js", + "flows/index.js", + "core/flow-kernel/flow-registry.js", + "core/flow-kernel/source-registry.js", "content/utils.js", "content/mail-163.js" ], @@ -97,7 +109,11 @@ ], "js": [ "content/activation-utils.js", - "shared/source-registry.js", + "flows/openai/index.js", + "flows/kiro/index.js", + "flows/index.js", + "core/flow-kernel/flow-registry.js", + "core/flow-kernel/source-registry.js", "content/utils.js", "content/icloud-mail.js" ], @@ -110,7 +126,11 @@ ], "js": [ "content/activation-utils.js", - "shared/source-registry.js", + "flows/openai/index.js", + "flows/kiro/index.js", + "flows/index.js", + "core/flow-kernel/flow-registry.js", + "core/flow-kernel/source-registry.js", "content/utils.js", "content/operation-delay.js", "content/duck-mail.js" diff --git a/md/plus模式-sub2api-session接入策略开发方案.md b/md/plus模式-sub2api-session接入策略开发方案.md index ee42cd1..e72b9a9 100644 --- a/md/plus模式-sub2api-session接入策略开发方案.md +++ b/md/plus模式-sub2api-session接入策略开发方案.md @@ -21,21 +21,21 @@ 主仓库 `codex注册扩展` 已重点阅读: - `data/step-definitions.js` -- `shared/flow-capabilities.js` -- `shared/flow-registry.js` -- `shared/settings-schema.js` +- `core/flow-kernel/flow-capabilities.js` +- `core/flow-kernel/flow-registry.js` +- `core/flow-kernel/settings-schema.js` - `sidepanel/sidepanel.html` - `sidepanel/sidepanel.js` - `background.js` -- `background/runtime-state.js` +- `core/flow-kernel/runtime-state.js` - `background/message-router.js` - `background/panel-bridge.js` - `background/sub2api-api.js` -- `background/steps/platform-verify.js` -- `background/steps/create-plus-checkout.js` -- `background/steps/fill-plus-checkout.js` -- `background/steps/plus-return-confirm.js` -- `content/plus-checkout.js` +- `flows/openai/background/steps/platform-verify.js` +- `flows/openai/background/steps/create-plus-checkout.js` +- `flows/openai/background/steps/fill-plus-checkout.js` +- `flows/openai/background/steps/plus-return-confirm.js` +- `flows/openai/content/plus-checkout.js` - `tests/step-definitions-module.test.js` - `tests/sidepanel-plus-payment-method.test.js` - `tests/flow-capabilities-module.test.js` @@ -125,7 +125,7 @@ 1. 通过 `background/panel-bridge.js` 请求 `background/sub2api-api.js` 生成 OAuth URL 2. 保存 `oauthUrl / sub2apiSessionId / sub2apiOAuthState` 3. 页面完成 OAuth 回调,拿到 `localhostUrl` -4. `background/steps/platform-verify.js` 调 `submitOpenAiCallback` +4. `flows/openai/background/steps/platform-verify.js` 调 `submitOpenAiCallback` 5. SUB2API 用 `exchange-code` 交换 OAuth code,再创建账号 这条链路是“完整 OAuth 账号创建”,不是导入现有 ChatGPT Web session。 @@ -140,8 +140,8 @@ 现成能力位置: -- `content/plus-checkout.js` -- `background/steps/create-plus-checkout.js` +- `flows/openai/content/plus-checkout.js` +- `flows/openai/background/steps/create-plus-checkout.js` 这意味着新功能不需要再发明“如何拿 session”,只需要把这份能力接到 Plus 尾链上。 @@ -258,7 +258,7 @@ - `select-plus-account-access-strategy` - `plus-account-access-strategy-caption` -`shared/flow-registry.js` 的 `openai-plus` 设置组应扩展为同时包含: +`core/flow-kernel/flow-registry.js` 的 `openai-plus` 设置组应扩展为同时包含: - `row-plus-mode` - `row-plus-payment-method` @@ -364,13 +364,13 @@ Session 导入策略生效时,替换锚点如下: 建议新增后台执行器文件: -- `background/steps/sub2api-session-import.js` +- `flows/openai/background/steps/sub2api-session-import.js` 执行器职责: 1. 定位当前 Plus 会话页对应 tab 2. 确认当前 tab 已回到 ChatGPT / OpenAI 域名页面,且可访问 `/api/auth/session` -3. 通过现有 `content/plus-checkout.js` 能力读取: +3. 通过现有 `flows/openai/content/plus-checkout.js` 能力读取: - `session` - `accessToken` 4. 用现有 `background/sub2api-api.js` 登录 SUB2API @@ -477,13 +477,13 @@ Session 导入策略生效时,替换锚点如下: 而不是去复用旧的: -- `content/sub2api-panel.js` +- `flows/openai/content/sub2api-panel.js` 原因: 1. 当前主链路已迁移到 `background/sub2api-api.js` 2. session 导入需要登录、查分组、查代理、发导入请求,这些都属于后台 API 编排 -3. 继续往 `content/sub2api-panel.js` 塞逻辑会造成双实现 +3. 继续往 `flows/openai/content/sub2api-panel.js` 塞逻辑会造成双实现 建议新增 API 方法: @@ -503,7 +503,7 @@ Session 导入策略生效时,替换锚点如下: - `background/panel-bridge.js` 继续只负责“跨 target 的 OAuth URL 申请桥接” - session 导入不要硬塞进 `requestOAuthUrlFromPanel` -- `background/steps/platform-verify.js` 继续只处理 callback 型平台验证 +- `flows/openai/background/steps/platform-verify.js` 继续只处理 callback 型平台验证 - `sub2api-session-import` 走独立后台执行器,不混进 `platform-verify` ## 5.7 后台总控与运行态设计 @@ -599,7 +599,7 @@ Session 导入策略生效时,替换锚点如下: ### 5.8.1 settings-schema -`shared/settings-schema.js` 需要在: +`core/flow-kernel/settings-schema.js` 需要在: - `flows.openai.plus` @@ -647,7 +647,7 @@ Session 导入策略生效时,替换锚点如下: ### 5.9.1 不要把策略可用性写死在 UI -应扩展 `shared/flow-capabilities.js`,让它输出: +应扩展 `core/flow-kernel/flow-capabilities.js`,让它输出: - `requestedPlusAccountAccessStrategy` - `effectivePlusAccountAccessStrategy` @@ -821,9 +821,9 @@ session strategy 下这些逻辑全部不适用。 ### 涉及文件 -- `shared/settings-schema.js` -- `shared/flow-capabilities.js` -- `shared/flow-registry.js` +- `core/flow-kernel/settings-schema.js` +- `core/flow-kernel/flow-capabilities.js` +- `core/flow-kernel/flow-registry.js` - `sidepanel/sidepanel.html` - `sidepanel/sidepanel.js` - `background.js` @@ -889,10 +889,10 @@ session strategy 下这些逻辑全部不适用。 ### 涉及文件 - `background/sub2api-api.js` -- `background/steps/sub2api-session-import.js` -- `background/steps/registry.js` 或后台节点注册处 -- `content/plus-checkout.js` -- `background/steps/create-plus-checkout.js` +- `flows/openai/background/steps/sub2api-session-import.js` +- `core/flow-kernel/step-registry.js` 或后台节点注册处 +- `flows/openai/content/plus-checkout.js` +- `flows/openai/background/steps/create-plus-checkout.js` ### 完成标准 @@ -920,9 +920,9 @@ session strategy 下这些逻辑全部不适用。 ### 涉及文件 - `background.js` -- `background/runtime-state.js` +- `core/flow-kernel/runtime-state.js` - `background/message-router.js` -- `background/steps/platform-verify.js` +- `flows/openai/background/steps/platform-verify.js` - `background/panel-bridge.js` - 自动运行 / auth-chain 相关测试 diff --git a/shared/flow-registry.js b/shared/flow-registry.js deleted file mode 100644 index 3f3123d..0000000 --- a/shared/flow-registry.js +++ /dev/null @@ -1,582 +0,0 @@ -(function attachMultiPageFlowRegistry(root, factory) { - root.MultiPageFlowRegistry = factory(); -})(typeof self !== 'undefined' ? self : globalThis, function createFlowRegistryModule() { - const DEFAULT_FLOW_ID = 'openai'; - const DEFAULT_OPENAI_TARGET_ID = 'cpa'; - const DEFAULT_KIRO_TARGET_ID = 'kiro-rs'; - const DEFAULT_KIRO_PUBLICATION_TARGET_ID = 'kiro-rs'; - const DEFAULT_KIRO_RS_URL = ''; - const OPENAI_TARGET_IDS = Object.freeze(['cpa', 'sub2api', 'codex2api']); - const SHARED_SERVICE_IDS = Object.freeze(['account', 'email', 'proxy']); - - const DEFAULT_FLOW_CAPABILITIES = Object.freeze({ - supportsEmailSignup: true, - supportsPhoneSignup: false, - supportsPhoneVerificationSettings: false, - supportsPlusMode: false, - supportsContributionMode: false, - supportsAccountContribution: false, - supportsOpenAiOAuthContribution: false, - contributionAdapterIds: [], - supportedTargetIds: [], - supportsLuckmail: false, - supportsOauthTimeoutBudget: false, - canSwitchFlow: true, - stepDefinitionMode: 'default', - targetSelectorLabel: '来源', - }); - - function freezeDeep(value) { - if (!value || typeof value !== 'object' || Object.isFrozen(value)) { - return value; - } - Object.getOwnPropertyNames(value).forEach((key) => { - freezeDeep(value[key]); - }); - return Object.freeze(value); - } - - const FLOW_DEFINITIONS = freezeDeep({ - openai: { - id: 'openai', - label: 'Codex / OpenAI', - services: ['account', 'email', 'proxy'], - capabilities: { - ...DEFAULT_FLOW_CAPABILITIES, - supportsPhoneSignup: true, - supportsPhoneVerificationSettings: true, - supportsPlusMode: true, - supportsContributionMode: true, - supportsAccountContribution: true, - supportsOpenAiOAuthContribution: true, - contributionAdapterIds: ['openai-oauth', 'openai-codex-file', 'openai-sub2api-file'], - supportedTargetIds: [...OPENAI_TARGET_IDS], - supportsLuckmail: true, - supportsOauthTimeoutBudget: true, - stepDefinitionMode: 'openai-dynamic', - }, - baseGroups: [ - 'openai-plus', - 'openai-phone', - 'openai-oauth', - 'openai-step6', - ], - targets: { - cpa: { - id: 'cpa', - label: 'CPA 面板', - groups: ['openai-target-cpa'], - }, - sub2api: { - id: 'sub2api', - label: 'SUB2API', - groups: ['openai-target-sub2api'], - }, - codex2api: { - id: 'codex2api', - label: 'Codex2API', - groups: ['openai-target-codex2api'], - }, - }, - runtimeSources: { - 'openai-auth': { - flowId: 'openai', - kind: 'flow-page', - label: '认证页', - readyPolicy: 'allow-child-frame', - family: 'openai-auth-family', - driverId: 'content/signup-page', - cleanupScopes: ['oauth-localhost-callback'], - }, - chatgpt: { - flowId: 'openai', - kind: 'flow-entry', - label: 'ChatGPT 首页', - readyPolicy: 'allow-child-frame', - family: 'chatgpt-entry-family', - driverId: null, - cleanupScopes: [], - }, - 'vps-panel': { - flowId: 'openai', - kind: 'panel-page', - label: 'CPA 面板', - readyPolicy: 'allow-child-frame', - family: 'vps-panel-family', - driverId: 'content/vps-panel', - cleanupScopes: [], - }, - 'platform-panel': { - flowId: 'openai', - kind: 'virtual-page', - label: '平台回调面板', - readyPolicy: 'disabled', - family: 'platform-panel-family', - driverId: 'content/platform-panel', - cleanupScopes: [], - }, - 'sub2api-panel': { - flowId: 'openai', - kind: 'panel-page', - label: 'SUB2API 后台', - readyPolicy: 'allow-child-frame', - family: 'sub2api-panel-family', - driverId: 'content/sub2api-panel', - cleanupScopes: [], - }, - 'codex2api-panel': { - flowId: 'openai', - kind: 'panel-page', - label: 'Codex2API 后台', - readyPolicy: 'allow-child-frame', - family: 'codex2api-panel-family', - driverId: 'content/sub2api-panel', - cleanupScopes: [], - }, - 'plus-checkout': { - flowId: 'openai', - kind: 'flow-page', - label: 'Plus Checkout', - readyPolicy: 'top-frame-only', - family: 'plus-checkout-family', - driverId: 'content/plus-checkout', - cleanupScopes: [], - }, - 'paypal-flow': { - flowId: 'openai', - kind: 'flow-page', - label: 'PayPal 授权页', - readyPolicy: 'allow-child-frame', - family: 'paypal-flow-family', - driverId: 'content/paypal-flow', - cleanupScopes: [], - }, - 'gopay-flow': { - flowId: 'openai', - kind: 'flow-page', - label: 'GoPay 授权页', - readyPolicy: 'allow-child-frame', - family: 'gopay-flow-family', - driverId: 'content/gopay-flow', - cleanupScopes: [], - }, - }, - driverDefinitions: { - 'content/signup-page': { - sourceId: 'openai-auth', - commands: [ - 'submit-signup-email', - 'fill-password', - 'fill-profile', - 'oauth-login', - 'submit-verification-code', - 'post-login-phone-verification', - 'bind-email', - 'fetch-bind-email-code', - 'confirm-oauth', - 'detect-auth-state', - ], - }, - 'content/sub2api-panel': { - sourceId: 'sub2api-panel', - commands: ['open-panel', 'fetch-oauth-url', 'platform-verify'], - }, - 'content/vps-panel': { - sourceId: 'vps-panel', - commands: ['open-panel', 'fetch-oauth-url', 'platform-verify'], - }, - 'content/platform-panel': { - sourceId: 'platform-panel', - commands: ['platform-verify', 'fetch-oauth-url'], - }, - 'content/plus-checkout': { - sourceId: 'plus-checkout', - commands: [ - 'plus-checkout-create', - 'paypal-hosted-openai-checkout', - 'plus-checkout-billing', - 'plus-checkout-return', - ], - }, - 'content/paypal-flow': { - sourceId: 'paypal-flow', - commands: [ - 'paypal-approve', - 'paypal-hosted-email', - 'paypal-hosted-card', - 'paypal-hosted-create-account', - 'paypal-hosted-review', - ], - }, - 'content/gopay-flow': { - sourceId: 'gopay-flow', - commands: ['gopay-subscription-confirm'], - }, - }, - }, - kiro: { - id: 'kiro', - label: 'Kiro', - services: ['account', 'email', 'proxy'], - capabilities: { - ...DEFAULT_FLOW_CAPABILITIES, - supportsAccountContribution: true, - contributionAdapterIds: ['kiro-builder-id'], - supportedTargetIds: [DEFAULT_KIRO_TARGET_ID], - stepDefinitionMode: 'kiro', - }, - baseGroups: [ - 'kiro-runtime-status', - ], - targets: { - 'kiro-rs': { - id: 'kiro-rs', - label: 'kiro.rs', - groups: ['kiro-target-kiro-rs'], - }, - }, - publicationTargets: { - 'kiro-rs': { - id: 'kiro-rs', - label: 'kiro.rs', - }, - }, - runtimeSources: { - 'kiro-register-page': { - flowId: 'kiro', - kind: 'flow-page', - label: 'Kiro 注册页', - readyPolicy: 'top-frame-only', - family: 'kiro-register-page-family', - driverId: 'content/kiro/register-page', - cleanupScopes: [], - }, - 'kiro-desktop-authorize': { - flowId: 'kiro', - kind: 'flow-page', - label: 'Kiro 桌面授权页', - readyPolicy: 'top-frame-only', - family: 'kiro-desktop-authorize-family', - driverId: 'content/kiro/desktop-authorize-page', - cleanupScopes: [], - }, - 'kiro-rs-admin': { - flowId: 'kiro', - kind: 'virtual-page', - label: 'kiro.rs Admin', - readyPolicy: 'disabled', - family: 'kiro-rs-admin-family', - driverId: null, - cleanupScopes: [], - }, - }, - driverDefinitions: { - 'content/kiro/register-page': { - sourceId: 'kiro-register-page', - commands: [ - 'kiro-open-register-page', - 'kiro-submit-email', - 'kiro-submit-name', - 'kiro-submit-verification-code', - 'kiro-submit-password', - 'kiro-complete-register-consent', - ], - }, - 'content/kiro/desktop-authorize-page': { - sourceId: 'kiro-desktop-authorize', - commands: [ - 'kiro-complete-desktop-authorize', - ], - }, - 'background/kiro-register': { - sourceId: 'kiro-register-page', - commands: [ - 'kiro-open-register-page', - 'kiro-submit-email', - 'kiro-submit-name', - 'kiro-submit-verification-code', - 'kiro-submit-password', - 'kiro-complete-register-consent', - ], - }, - 'background/kiro-desktop-authorize': { - sourceId: 'kiro-desktop-authorize', - commands: [ - 'kiro-start-desktop-authorize', - 'kiro-complete-desktop-authorize', - ], - }, - 'background/kiro-publisher-kiro-rs': { - sourceId: 'kiro-rs-admin', - commands: [ - 'kiro-upload-credential', - ], - }, - }, - }, - }); - - const SETTINGS_GROUP_DEFINITIONS = freezeDeep({ - 'service-account': { - id: 'service-account', - label: '账户', - rowIds: ['row-custom-password'], - }, - 'service-email': { - id: 'service-email', - label: '邮箱服务', - }, - 'service-proxy': { - id: 'service-proxy', - label: 'IP 代理', - sectionIds: ['ip-proxy-section'], - }, - 'openai-target-cpa': { - id: 'openai-target-cpa', - label: 'CPA 来源', - rowIds: ['row-vps-url', 'row-vps-password', 'row-local-cpa-step9-mode'], - }, - 'openai-target-sub2api': { - id: 'openai-target-sub2api', - label: 'SUB2API 来源', - rowIds: [ - 'row-sub2api-url', - 'row-sub2api-email', - 'row-sub2api-password', - 'row-sub2api-group', - 'row-sub2api-account-priority', - 'row-sub2api-default-proxy', - ], - }, - 'openai-target-codex2api': { - id: 'openai-target-codex2api', - label: 'Codex2API 来源', - rowIds: ['row-codex2api-url', 'row-codex2api-admin-key'], - }, - 'openai-plus': { - id: 'openai-plus', - label: 'Plus', - rowIds: ['row-plus-mode', 'row-plus-account-access-strategy', 'row-plus-payment-method'], - }, - 'openai-phone': { - id: 'openai-phone', - label: '接码设置', - sectionIds: ['phone-verification-section'], - rowIds: [], - }, - 'openai-oauth': { - id: 'openai-oauth', - label: 'OAuth', - rowIds: ['row-oauth-flow-timeout', 'row-oauth-display'], - }, - 'openai-step6': { - id: 'openai-step6', - label: '第六步', - rowIds: ['row-step6-cookie-settings'], - }, - 'kiro-target-kiro-rs': { - id: 'kiro-target-kiro-rs', - label: 'kiro.rs 配置', - rowIds: ['row-kiro-rs-url', 'row-kiro-rs-key', 'row-kiro-rs-test-status'], - }, - 'kiro-runtime-status': { - id: 'kiro-runtime-status', - label: 'Kiro 运行态', - rowIds: ['row-kiro-web-status', 'row-kiro-login-url', 'row-kiro-upload-status'], - }, - }); - - function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) { - const normalized = String(value || '').trim().toLowerCase(); - if (normalized && Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, normalized)) { - return normalized; - } - const fallbackValue = String(fallback || '').trim().toLowerCase(); - return Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, fallbackValue) - ? fallbackValue - : DEFAULT_FLOW_ID; - } - - function getRegisteredFlowIds() { - return Object.keys(FLOW_DEFINITIONS); - } - - function getFlowDefinition(flowId) { - return FLOW_DEFINITIONS[normalizeFlowId(flowId)] || FLOW_DEFINITIONS[DEFAULT_FLOW_ID]; - } - - function getFlowLabel(flowId) { - return getFlowDefinition(flowId)?.label || normalizeFlowId(flowId); - } - - function getDefaultTargetId(flowId) { - return normalizeFlowId(flowId) === 'kiro' - ? DEFAULT_KIRO_TARGET_ID - : DEFAULT_OPENAI_TARGET_ID; - } - - function normalizeOpenAiTargetId(value = '', fallback = DEFAULT_OPENAI_TARGET_ID) { - const normalized = String(value || '').trim().toLowerCase(); - if (OPENAI_TARGET_IDS.includes(normalized)) { - return normalized; - } - const fallbackValue = String(fallback || '').trim().toLowerCase(); - return OPENAI_TARGET_IDS.includes(fallbackValue) - ? fallbackValue - : DEFAULT_OPENAI_TARGET_ID; - } - - function normalizeKiroTargetId(value = '', fallback = DEFAULT_KIRO_TARGET_ID) { - const normalized = String(value || '').trim().toLowerCase(); - if (normalized === DEFAULT_KIRO_TARGET_ID) { - return normalized; - } - const fallbackValue = String(fallback || '').trim().toLowerCase(); - return fallbackValue === DEFAULT_KIRO_TARGET_ID - ? fallbackValue - : DEFAULT_KIRO_TARGET_ID; - } - - function normalizeTargetId(flowId, targetId = '', fallback = undefined) { - const normalizedFlowId = normalizeFlowId(flowId); - if (normalizedFlowId === 'kiro') { - return normalizeKiroTargetId( - targetId, - fallback || DEFAULT_KIRO_TARGET_ID - ); - } - return normalizeOpenAiTargetId( - targetId, - fallback || DEFAULT_OPENAI_TARGET_ID - ); - } - - function getTargetDefinitions(flowId) { - return getFlowDefinition(flowId)?.targets || {}; - } - - function getTargetDefinition(flowId, targetId) { - const normalizedFlowId = normalizeFlowId(flowId); - const normalizedTargetId = normalizeTargetId( - normalizedFlowId, - targetId, - getDefaultTargetId(normalizedFlowId) - ); - return getTargetDefinitions(normalizedFlowId)[normalizedTargetId] || null; - } - - function getTargetOptions(flowId) { - return Object.values(getTargetDefinitions(flowId)); - } - - function getTargetLabel(flowId, targetId) { - return getTargetDefinition(flowId, targetId)?.label - || normalizeTargetId(flowId, targetId); - } - - function getPublicationTargetDefinitions(flowId) { - return getFlowDefinition(flowId)?.publicationTargets || {}; - } - - function getPublicationTargetDefinition(flowId, publicationTargetId) { - const normalizedFlowId = normalizeFlowId(flowId); - const normalizedPublicationTargetId = String( - publicationTargetId || ( - normalizedFlowId === 'kiro' - ? DEFAULT_KIRO_PUBLICATION_TARGET_ID - : '' - ) - ).trim().toLowerCase(); - return getPublicationTargetDefinitions(normalizedFlowId)[normalizedPublicationTargetId] || null; - } - - function getFlowCapabilities(flowId) { - return { - ...DEFAULT_FLOW_CAPABILITIES, - ...(getFlowDefinition(flowId)?.capabilities || {}), - }; - } - - function getVisibleGroupIds(flowId, targetId, options = {}) { - const normalizedFlowId = normalizeFlowId(flowId); - const flowDefinition = getFlowDefinition(normalizedFlowId); - const normalizedTargetId = normalizeTargetId( - normalizedFlowId, - targetId, - getDefaultTargetId(normalizedFlowId) - ); - const targetDefinition = getTargetDefinition( - normalizedFlowId, - normalizedTargetId - ); - const includeSharedServices = options?.includeSharedServices !== false; - const serviceGroups = includeSharedServices - ? (Array.isArray(flowDefinition?.services) - ? flowDefinition.services.map((serviceId) => `service-${serviceId}`) - : []) - : []; - return Array.from(new Set([ - ...(Array.isArray(flowDefinition?.baseGroups) ? flowDefinition.baseGroups : []), - ...(Array.isArray(targetDefinition?.groups) ? targetDefinition.groups : []), - ...serviceGroups, - ])); - } - - function getSettingsGroupDefinition(groupId) { - const normalizedGroupId = String(groupId || '').trim(); - return SETTINGS_GROUP_DEFINITIONS[normalizedGroupId] || null; - } - - function getSettingsGroupDefinitions() { - return SETTINGS_GROUP_DEFINITIONS; - } - - function getRuntimeSourceDefinitions() { - const next = {}; - Object.values(FLOW_DEFINITIONS).forEach((flowDefinition) => { - Object.assign(next, flowDefinition.runtimeSources || {}); - }); - return next; - } - - function getDriverDefinitions() { - const next = {}; - Object.values(FLOW_DEFINITIONS).forEach((flowDefinition) => { - Object.assign(next, flowDefinition.driverDefinitions || {}); - }); - return next; - } - - return { - DEFAULT_FLOW_CAPABILITIES, - DEFAULT_FLOW_ID, - DEFAULT_KIRO_TARGET_ID, - DEFAULT_KIRO_PUBLICATION_TARGET_ID, - DEFAULT_KIRO_RS_URL, - DEFAULT_OPENAI_TARGET_ID, - FLOW_DEFINITIONS, - OPENAI_TARGET_IDS, - SETTINGS_GROUP_DEFINITIONS, - SHARED_SERVICE_IDS, - getDriverDefinitions, - getDefaultTargetId, - getFlowCapabilities, - getFlowDefinition, - getFlowLabel, - getPublicationTargetDefinition, - getPublicationTargetDefinitions, - getRegisteredFlowIds, - getRuntimeSourceDefinitions, - getSettingsGroupDefinition, - getSettingsGroupDefinitions, - getTargetDefinition, - getTargetDefinitions, - getTargetLabel, - getTargetOptions, - getVisibleGroupIds, - normalizeFlowId, - normalizeKiroTargetId, - normalizeOpenAiTargetId, - normalizeTargetId, - }; -}); diff --git a/shared/source-registry.js b/shared/source-registry.js deleted file mode 100644 index 587420f..0000000 --- a/shared/source-registry.js +++ /dev/null @@ -1,406 +0,0 @@ -(function attachMultiPageSourceRegistry(root, factory) { - root.MultiPageSourceRegistry = factory(); -})(typeof self !== 'undefined' ? self : globalThis, function createSourceRegistryModule() { - const rootScope = typeof self !== 'undefined' ? self : globalThis; - const flowRegistryApi = rootScope.MultiPageFlowRegistry || {}; - - const SOURCE_ALIASES = Object.freeze({ - 'signup-page': 'openai-auth', - }); - - const SHARED_SOURCE_DEFINITIONS = Object.freeze({ - 'qq-mail': { - flowId: null, - kind: 'mail-provider', - label: 'QQ 邮箱', - readyPolicy: 'top-frame-only', - family: 'qq-mail-family', - driverId: 'content/qq-mail', - cleanupScopes: [], - }, - 'mail-163': { - flowId: null, - kind: 'mail-provider', - label: '163 邮箱', - readyPolicy: 'top-frame-only', - family: 'mail-163-family', - driverId: 'content/mail-163', - cleanupScopes: [], - }, - 'gmail-mail': { - flowId: null, - kind: 'mail-provider', - label: 'Gmail 邮箱', - readyPolicy: 'top-frame-only', - family: 'gmail-mail-family', - driverId: 'content/gmail-mail', - cleanupScopes: [], - }, - 'icloud-mail': { - flowId: null, - kind: 'mail-provider', - label: 'iCloud 邮箱', - readyPolicy: 'allow-child-frame', - family: 'icloud-mail-family', - driverId: 'content/icloud-mail', - cleanupScopes: [], - }, - 'inbucket-mail': { - flowId: null, - kind: 'mail-provider', - label: 'Inbucket 邮箱', - readyPolicy: 'top-frame-only', - family: 'inbucket-mail-family', - driverId: 'content/inbucket-mail', - cleanupScopes: [], - }, - 'mail-2925': { - flowId: null, - kind: 'mail-provider', - label: '2925 邮箱', - readyPolicy: 'top-frame-only', - family: 'mail-2925-family', - driverId: 'content/mail-2925', - cleanupScopes: [], - }, - 'duck-mail': { - flowId: null, - kind: 'mail-provider', - label: 'Duck 邮箱', - readyPolicy: 'allow-child-frame', - family: 'duck-mail-family', - driverId: 'content/duck-mail', - cleanupScopes: [], - }, - 'unknown-source': { - flowId: null, - kind: 'unknown', - label: '未知来源', - readyPolicy: 'disabled', - family: 'unknown-family', - driverId: null, - cleanupScopes: [], - }, - }); - - const SHARED_DRIVER_DEFINITIONS = Object.freeze({ - 'content/qq-mail': { - sourceId: 'qq-mail', - commands: ['POLL_EMAIL'], - }, - 'content/mail-163': { - sourceId: 'mail-163', - commands: ['POLL_EMAIL'], - }, - 'content/gmail-mail': { - sourceId: 'gmail-mail', - commands: ['POLL_EMAIL'], - }, - 'content/icloud-mail': { - sourceId: 'icloud-mail', - commands: ['POLL_EMAIL'], - }, - 'content/mail-2925': { - sourceId: 'mail-2925', - commands: ['POLL_EMAIL'], - }, - 'content/duck-mail': { - sourceId: 'duck-mail', - commands: ['FETCH_ALIAS_EMAIL'], - }, - }); - - const CLEANUP_SCOPE_OWNERS = Object.freeze({ - 'oauth-localhost-callback': 'openai-auth', - }); - - const AUTH_PAGE_HOSTS = new Set(['auth0.openai.com', 'auth.openai.com', 'accounts.openai.com']); - const ENTRY_PAGE_HOSTS = new Set(['chatgpt.com', 'www.chatgpt.com', 'chat.openai.com']); - const CHILD_FRAME_BLOCKED_SOURCES = new Set([ - 'qq-mail', - 'mail-163', - 'gmail-mail', - 'mail-2925', - 'inbucket-mail', - 'plus-checkout', - 'kiro-register-page', - 'kiro-desktop-authorize', - ]); - - function normalizeHostname(hostname = '') { - return String(hostname || '').trim().toLowerCase(); - } - - function matchesNamedHostFamily(hostname = '', family = '') { - const normalizedHost = normalizeHostname(hostname); - const normalizedFamily = normalizeHostname(family); - if (!normalizedHost || !normalizedFamily) { - return false; - } - return normalizedHost === normalizedFamily - || normalizedHost.endsWith(`.${normalizedFamily}`) - || normalizedHost.startsWith(`${normalizedFamily}.`) - || normalizedHost.includes(`.${normalizedFamily}.`); - } - - function isKiroWebHost(hostname = '') { - const normalized = normalizeHostname(hostname); - return normalized === 'app.kiro.dev' - || normalized === 'kiro.dev'; - } - - function isKiroAwsAuthHost(hostname = '') { - const normalized = normalizeHostname(hostname); - return normalized === 'view.awsapps.com' - || normalized === 'login.awsapps.com' - || matchesNamedHostFamily(normalized, 'signin.aws') - || matchesNamedHostFamily(normalized, 'profile.aws') - || normalized === 'amazonaws.com' - || normalized.endsWith('.amazonaws.com'); - } - - function isKiroRegisterHost(hostname = '') { - return isKiroWebHost(hostname) || isKiroAwsAuthHost(hostname); - } - - function getRuntimeSourceDefinitions() { - return { - ...(typeof flowRegistryApi.getRuntimeSourceDefinitions === 'function' - ? flowRegistryApi.getRuntimeSourceDefinitions() - : {}), - ...SHARED_SOURCE_DEFINITIONS, - }; - } - - function getDriverDefinitions() { - return { - ...(typeof flowRegistryApi.getDriverDefinitions === 'function' - ? flowRegistryApi.getDriverDefinitions() - : {}), - ...SHARED_DRIVER_DEFINITIONS, - }; - } - - function createSourceRegistry() { - const SOURCE_DEFINITIONS = getRuntimeSourceDefinitions(); - const DRIVER_DEFINITIONS = getDriverDefinitions(); - - function parseUrlSafely(rawUrl) { - if (!rawUrl) return null; - try { - return new URL(rawUrl); - } catch { - return null; - } - } - - function normalizeSourceId(source) { - return String(source || '').trim(); - } - - function resolveCanonicalSource(source) { - const normalized = normalizeSourceId(source); - if (!normalized) return ''; - return SOURCE_ALIASES[normalized] || normalized; - } - - function getAliasKeysForCanonicalSource(source) { - const canonical = resolveCanonicalSource(source); - return Object.keys(SOURCE_ALIASES).filter((alias) => SOURCE_ALIASES[alias] === canonical); - } - - function getSourceKeys(source) { - const normalized = normalizeSourceId(source); - const canonical = resolveCanonicalSource(normalized); - return Array.from(new Set([ - canonical, - ...getAliasKeysForCanonicalSource(canonical), - normalized, - ].filter(Boolean))); - } - - function getSourceMeta(source) { - const canonical = resolveCanonicalSource(source); - const definition = SOURCE_DEFINITIONS[canonical]; - if (!definition) { - return null; - } - return { - id: canonical, - aliases: getAliasKeysForCanonicalSource(canonical), - ...definition, - }; - } - - function getSourceLabel(source) { - return getSourceMeta(source)?.label || normalizeSourceId(source) || '未知来源'; - } - - function getDriverIdForSource(source) { - return getSourceMeta(source)?.driverId || null; - } - - function getDriverMeta(sourceOrDriverId) { - const directDriverId = normalizeSourceId(sourceOrDriverId); - const driverId = Object.prototype.hasOwnProperty.call(DRIVER_DEFINITIONS, directDriverId) - ? directDriverId - : getDriverIdForSource(sourceOrDriverId); - if (!driverId || !Object.prototype.hasOwnProperty.call(DRIVER_DEFINITIONS, driverId)) { - return null; - } - return { - id: driverId, - ...DRIVER_DEFINITIONS[driverId], - }; - } - - function driverAcceptsCommand(sourceOrDriverId, command) { - const normalizedCommand = normalizeSourceId(command); - if (!normalizedCommand) { - return false; - } - const driver = getDriverMeta(sourceOrDriverId); - return Array.isArray(driver?.commands) && driver.commands.includes(normalizedCommand); - } - - function isSignupPageHost(hostname = '') { - return AUTH_PAGE_HOSTS.has(normalizeHostname(hostname)); - } - - function isSignupEntryHost(hostname = '') { - return ENTRY_PAGE_HOSTS.has(normalizeHostname(hostname)); - } - - function is163MailHost(hostname = '') { - const normalized = normalizeHostname(hostname); - return normalized === 'mail.163.com' - || normalized.endsWith('.mail.163.com') - || normalized === 'mail.126.com' - || normalized.endsWith('.mail.126.com') - || normalized === 'webmail.vip.163.com'; - } - - function matchesSourceUrlFamily(source, candidateUrl, referenceUrl) { - const candidate = parseUrlSafely(candidateUrl); - if (!candidate) return false; - - const canonical = resolveCanonicalSource(source); - const reference = parseUrlSafely(referenceUrl); - - switch (canonical) { - case 'openai-auth': - return isSignupPageHost(candidate.hostname) || isSignupEntryHost(candidate.hostname); - case 'chatgpt': - return isSignupEntryHost(candidate.hostname); - case 'duck-mail': - return candidate.hostname === 'duckduckgo.com' && candidate.pathname.startsWith('/email/'); - case 'qq-mail': - return candidate.hostname === 'mail.qq.com' || candidate.hostname === 'wx.mail.qq.com'; - case 'mail-163': - return is163MailHost(candidate.hostname); - case 'gmail-mail': - return candidate.hostname === 'mail.google.com'; - case 'icloud-mail': - return candidate.hostname === 'www.icloud.com' - || candidate.hostname === 'www.icloud.com.cn'; - case 'inbucket-mail': - return Boolean(reference) - && candidate.origin === reference.origin - && candidate.pathname.startsWith('/m/'); - case 'mail-2925': - return candidate.hostname === '2925.com' || candidate.hostname === 'www.2925.com'; - case 'vps-panel': - return Boolean(reference) - && candidate.origin === reference.origin - && candidate.pathname === reference.pathname; - case 'sub2api-panel': - return Boolean(reference) - && candidate.origin === reference.origin - && ( - candidate.pathname.startsWith('/admin/accounts') - || candidate.pathname.startsWith('/login') - || candidate.pathname === '/' - ); - case 'codex2api-panel': - return Boolean(reference) - && candidate.origin === reference.origin - && ( - candidate.pathname.startsWith('/admin/accounts') - || candidate.pathname === '/admin' - || candidate.pathname === '/' - ); - case 'plus-checkout': - return candidate.hostname === 'chatgpt.com' - && candidate.pathname.startsWith('/checkout/'); - case 'paypal-flow': - return candidate.hostname.endsWith('paypal.com'); - case 'gopay-flow': - return /gopay|gojek/i.test(candidate.hostname); - case 'kiro-register-page': - return isKiroRegisterHost(candidate.hostname); - case 'kiro-desktop-authorize': - return isKiroAwsAuthHost(candidate.hostname); - default: - return false; - } - } - - function detectSourceFromLocation({ - injectedSource, - url = '', - hostname = '', - } = {}) { - if (injectedSource) return resolveCanonicalSource(injectedSource); - - const normalizedHostname = String(hostname || '').toLowerCase(); - const normalizedUrl = String(url || ''); - - if (isSignupPageHost(normalizedHostname)) return 'openai-auth'; - if (normalizedHostname === 'mail.qq.com' || normalizedHostname === 'wx.mail.qq.com') return 'qq-mail'; - if (is163MailHost(normalizedHostname)) return 'mail-163'; - if (normalizedHostname === 'mail.google.com') return 'gmail-mail'; - if (normalizedHostname === 'www.icloud.com' || normalizedHostname === 'www.icloud.com.cn') return 'icloud-mail'; - if (normalizedUrl.includes('duckduckgo.com/email/settings/autofill')) return 'duck-mail'; - if (normalizedUrl.includes('2925.com')) return 'mail-2925'; - if (isKiroRegisterHost(normalizedHostname)) return 'kiro-register-page'; - if (isSignupEntryHost(normalizedHostname)) return 'chatgpt'; - return 'unknown-source'; - } - - function shouldReportReadyForFrame(source, isChildFrame) { - const canonical = resolveCanonicalSource(source); - const readyPolicy = getSourceMeta(canonical)?.readyPolicy || 'allow-child-frame'; - if (readyPolicy === 'disabled') return false; - if (!isChildFrame) return true; - if (readyPolicy === 'top-frame-only') return false; - if (CHILD_FRAME_BLOCKED_SOURCES.has(canonical)) return false; - return true; - } - - function getCleanupOwnerSource(cleanupScope) { - return resolveCanonicalSource(CLEANUP_SCOPE_OWNERS[String(cleanupScope || '').trim()] || ''); - } - - return { - detectSourceFromLocation, - driverAcceptsCommand, - getCleanupOwnerSource, - getDriverIdForSource, - getDriverMeta, - getSourceKeys, - getSourceLabel, - getSourceMeta, - is163MailHost, - isSignupEntryHost, - isSignupPageHost, - matchesSourceUrlFamily, - parseUrlSafely, - resolveCanonicalSource, - shouldReportReadyForFrame, - }; - } - - return { - createSourceRegistry, - }; -}); diff --git a/sidepanel/contribution-mode.js b/sidepanel/contribution-mode.js index 0ff8c42..d0c54e8 100644 --- a/sidepanel/contribution-mode.js +++ b/sidepanel/contribution-mode.js @@ -77,7 +77,7 @@ } function getContributionSource(currentState = getLatestState()) { - return normalizeContributionSource(currentState.contributionSource || currentState.panelMode); + return normalizeContributionSource(currentState.contributionSource || getActiveTargetId(currentState)); } function getContributionSourceLabel(currentState = getLatestState()) { @@ -107,10 +107,8 @@ if (selectedTargetId) { return selectedTargetId; } - if (activeFlowId === 'kiro') { - return normalizeString(currentState.kiroTargetId || currentState.targetId || 'kiro-rs').toLowerCase() || 'kiro-rs'; - } - return normalizeString(currentState.openaiIntegrationTargetId || currentState.panelMode || currentState.targetId || 'cpa').toLowerCase() || 'cpa'; + return normalizeString(currentState.targetId).toLowerCase() + || (activeFlowId === 'kiro' ? 'kiro-rs' : 'cpa'); } function applySelectedFlowToState(nextState = {}, flowId = 'openai', targetId = '') { @@ -122,14 +120,14 @@ ...baseState, activeFlowId: selectedFlowId, flowId: selectedFlowId, - panelMode: selectedTargetId || normalizeString(baseState.panelMode).toLowerCase() || 'cpa', + targetId: selectedTargetId || normalizeString(baseState.targetId).toLowerCase() || 'cpa', }; } return { ...baseState, activeFlowId: selectedFlowId, flowId: selectedFlowId, - kiroTargetId: selectedTargetId || normalizeString(baseState.kiroTargetId || baseState.targetId).toLowerCase() || 'kiro-rs', + targetId: selectedTargetId || normalizeString(baseState.targetId).toLowerCase() || 'kiro-rs', }; } @@ -164,7 +162,7 @@ if (registry?.resolveSidepanelCapabilities) { return Boolean(registry.resolveSidepanelCapabilities({ activeFlowId: getActiveFlowId(currentState), - panelMode: currentState?.panelMode, + targetId: getActiveTargetId(currentState), state: currentState, })?.canShowContributionMode); } diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 286d262..892e196 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -1,4 +1,4 @@ - + @@ -1876,10 +1876,15 @@ - + + + + + + - - + + diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 32f36fd..11abee8 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -2002,13 +2002,44 @@ async function openConfirmModalWithOption({ async function openPlusManualConfirmationDialog(options = {}) { const method = String(options.method || '').trim().toLowerCase(); const activeFlowId = String(latestState?.activeFlowId || latestState?.flowId || 'openai').trim().toLowerCase(); - const panelMode = String(latestState?.panelMode || latestState?.openaiIntegrationTargetId || '').trim().toLowerCase(); + const normalizeTargetIdForFlowSafe = typeof normalizeTargetIdForFlow === 'function' + ? normalizeTargetIdForFlow + : ((flowId, targetId = '', fallback = '') => { + const normalizedFlowId = String(flowId || '').trim().toLowerCase() || 'openai'; + if (normalizedFlowId === 'openai') { + const normalizedTargetId = String(targetId || fallback || '').trim().toLowerCase(); + return normalizedTargetId === 'sub2api' || normalizedTargetId === 'codex2api' ? normalizedTargetId : 'cpa'; + } + const normalizedTargetId = String(targetId || '').trim().toLowerCase(); + return normalizedTargetId || String(fallback || '').trim().toLowerCase() || 'kiro-rs'; + }); + const getDefaultTargetIdForFlowSafe = typeof getDefaultTargetIdForFlow === 'function' + ? getDefaultTargetIdForFlow + : ((flowId = 'openai') => (String(flowId || '').trim().toLowerCase() === 'openai' ? 'cpa' : 'kiro-rs')); + const normalizePlusStrategyTargetIdSafe = typeof normalizePlusStrategyTargetId === 'function' + ? normalizePlusStrategyTargetId + : ((value = '') => { + const normalized = String(value || '').trim().toLowerCase(); + if (normalized === 'sub2api' || normalized === 'codex2api') { + return normalized; + } + return 'cpa'; + }); + const targetId = normalizePlusStrategyTargetIdSafe( + typeof getSelectedTargetIdForState === 'function' + ? getSelectedTargetIdForState(latestState, activeFlowId) + : normalizeTargetIdForFlowSafe( + activeFlowId, + latestState?.targetId || '', + getDefaultTargetIdForFlowSafe(activeFlowId) + ) + ); const signupMethod = String(latestState?.resolvedSignupMethod || latestState?.signupMethod || 'email').trim().toLowerCase(); const plusModeEnabled = latestState?.plusModeEnabled === undefined ? true : Boolean(latestState.plusModeEnabled); const plusAccountAccessStrategy = String(latestState?.plusAccountAccessStrategy || 'oauth').trim().toLowerCase(); const useSub2ApiSessionImport = plusModeEnabled && activeFlowId === 'openai' - && panelMode === 'sub2api' + && targetId === 'sub2api' && signupMethod === 'email' && plusAccountAccessStrategy === 'sub2api_codex_session'; const continuationActionLabel = useSub2ApiSessionImport @@ -2042,13 +2073,44 @@ async function syncPlusManualConfirmationDialog() { const step = Number(latestState?.plusManualConfirmationStep) || 0; const method = String(latestState?.plusManualConfirmationMethod || '').trim().toLowerCase(); const activeFlowId = String(latestState?.activeFlowId || latestState?.flowId || 'openai').trim().toLowerCase(); - const panelMode = String(latestState?.panelMode || latestState?.openaiIntegrationTargetId || '').trim().toLowerCase(); + const normalizeTargetIdForFlowSafe = typeof normalizeTargetIdForFlow === 'function' + ? normalizeTargetIdForFlow + : ((flowId, targetId = '', fallback = '') => { + const normalizedFlowId = String(flowId || '').trim().toLowerCase() || 'openai'; + if (normalizedFlowId === 'openai') { + const normalizedTargetId = String(targetId || fallback || '').trim().toLowerCase(); + return normalizedTargetId === 'sub2api' || normalizedTargetId === 'codex2api' ? normalizedTargetId : 'cpa'; + } + const normalizedTargetId = String(targetId || '').trim().toLowerCase(); + return normalizedTargetId || String(fallback || '').trim().toLowerCase() || 'kiro-rs'; + }); + const getDefaultTargetIdForFlowSafe = typeof getDefaultTargetIdForFlow === 'function' + ? getDefaultTargetIdForFlow + : ((flowId = 'openai') => (String(flowId || '').trim().toLowerCase() === 'openai' ? 'cpa' : 'kiro-rs')); + const normalizePlusStrategyTargetIdSafe = typeof normalizePlusStrategyTargetId === 'function' + ? normalizePlusStrategyTargetId + : ((value = '') => { + const normalized = String(value || '').trim().toLowerCase(); + if (normalized === 'sub2api' || normalized === 'codex2api') { + return normalized; + } + return 'cpa'; + }); + const targetId = normalizePlusStrategyTargetIdSafe( + typeof getSelectedTargetIdForState === 'function' + ? getSelectedTargetIdForState(latestState, activeFlowId) + : normalizeTargetIdForFlowSafe( + activeFlowId, + latestState?.targetId || '', + getDefaultTargetIdForFlowSafe(activeFlowId) + ) + ); const signupMethod = String(latestState?.resolvedSignupMethod || latestState?.signupMethod || 'email').trim().toLowerCase(); const plusModeEnabled = latestState?.plusModeEnabled === undefined ? true : Boolean(latestState.plusModeEnabled); const plusAccountAccessStrategy = String(latestState?.plusAccountAccessStrategy || 'oauth').trim().toLowerCase(); const useSub2ApiSessionImport = plusModeEnabled && activeFlowId === 'openai' - && panelMode === 'sub2api' + && targetId === 'sub2api' && signupMethod === 'email' && plusAccountAccessStrategy === 'sub2api_codex_session'; const continuationActionLabel = useSub2ApiSessionImport @@ -2159,10 +2221,10 @@ function getContributionContentFlowId(state = latestState) { function getContributionContentTargetId(state = latestState) { const flowId = getContributionContentFlowId(state); - if (flowId === 'kiro') { - return String(state?.kiroTargetId || state?.targetId || 'kiro-rs').trim().toLowerCase() || 'kiro-rs'; + if (typeof getSelectedTargetIdForState === 'function') { + return getSelectedTargetIdForState(state, flowId); } - return String(state?.openaiIntegrationTargetId || state?.panelMode || state?.targetId || 'cpa').trim().toLowerCase() || 'cpa'; + return normalizeTargetIdForFlow(flowId, state?.targetId || '', getDefaultTargetIdForFlow(flowId)); } function openNewUserGuidePrompt() { @@ -2255,29 +2317,54 @@ function shouldWarnAutoRunFallbackRisk(totalRuns, autoRunSkipFailures) { return totalRuns >= AUTO_RUN_FALLBACK_RISK_WARNING_MIN_RUNS; } -function shouldWarnCpaPhoneSignup(signupMethod = null, panelMode = null) { +function shouldWarnCpaPhoneSignup(signupMethod = null, targetId = null) { + const defaultFlowId = typeof DEFAULT_ACTIVE_FLOW_ID === 'string' ? DEFAULT_ACTIVE_FLOW_ID : 'openai'; const resolvedSignupMethod = normalizeSignupMethod( signupMethod ?? ( typeof getSelectedSignupMethod === 'function' ? getSelectedSignupMethod() : DEFAULT_SIGNUP_METHOD - ) + ) ); - const resolvedPanelMode = normalizePanelMode( - panelMode ?? ( - typeof getSelectedPanelMode === 'function' - ? getSelectedPanelMode() - : 'cpa' - ) + const activeFlowId = typeof getSelectedFlowId === 'function' + ? getSelectedFlowId(latestState) + : ( + typeof normalizeFlowId === 'function' + ? normalizeFlowId(latestState?.activeFlowId || latestState?.flowId || defaultFlowId) + : (String(latestState?.activeFlowId || latestState?.flowId || defaultFlowId).trim().toLowerCase() || defaultFlowId) + ); + const normalizeTargetIdForFlowSafe = typeof normalizeTargetIdForFlow === 'function' + ? normalizeTargetIdForFlow + : ((flowId, value = '', fallback = '') => { + const normalizedFlowId = String(flowId || '').trim().toLowerCase() || defaultFlowId; + if (normalizedFlowId === defaultFlowId) { + return normalizePanelMode(value || fallback); + } + const normalizedValue = String(value || '').trim().toLowerCase(); + return normalizedValue || String(fallback || '').trim().toLowerCase() || 'kiro-rs'; + }); + const resolvedTargetId = normalizeTargetIdForFlowSafe( + activeFlowId, + targetId ?? ( + typeof getSelectedTargetId === 'function' + ? getSelectedTargetId(activeFlowId) + : getSelectedTargetIdForState(latestState, activeFlowId) + ), + typeof getDefaultTargetIdForFlow === 'function' + ? getDefaultTargetIdForFlow(activeFlowId) + : (activeFlowId === defaultFlowId ? 'cpa' : 'kiro-rs') ); const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' ? resolveCurrentSidepanelCapabilities({ - panelMode: resolvedPanelMode, + activeFlowId, + targetId: resolvedTargetId, signupMethod: resolvedSignupMethod, state: { ...(typeof latestState !== 'undefined' ? latestState : {}), - panelMode: resolvedPanelMode, + activeFlowId, + flowId: activeFlowId, + targetId: resolvedTargetId, signupMethod: resolvedSignupMethod, }, }) @@ -2288,12 +2375,14 @@ function shouldWarnCpaPhoneSignup(signupMethod = null, panelMode = null) { }) || null; return registry?.resolveSidepanelCapabilities ? registry.resolveSidepanelCapabilities({ - activeFlowId: typeof latestState !== 'undefined' ? latestState?.activeFlowId : '', - panelMode: resolvedPanelMode, + activeFlowId, + targetId: resolvedTargetId, signupMethod: resolvedSignupMethod, state: { ...(typeof latestState !== 'undefined' ? latestState : {}), - panelMode: resolvedPanelMode, + activeFlowId, + flowId: activeFlowId, + targetId: resolvedTargetId, signupMethod: resolvedSignupMethod, }, }) @@ -2305,7 +2394,7 @@ function shouldWarnCpaPhoneSignup(signupMethod = null, panelMode = null) { } return resolvedSignupMethod === SIGNUP_METHOD_PHONE - && resolvedPanelMode === 'cpa' + && resolvedTargetId === 'cpa' && !isCpaPhoneSignupPromptDismissed(); } @@ -2327,11 +2416,11 @@ async function confirmCpaPhoneSignupIfNeeded(options = {}) { const signupMethod = Object.prototype.hasOwnProperty.call(options, 'signupMethod') ? options.signupMethod : null; - const panelMode = Object.prototype.hasOwnProperty.call(options, 'panelMode') - ? options.panelMode + const targetId = Object.prototype.hasOwnProperty.call(options, 'targetId') + ? options.targetId : null; - if (!shouldWarnCpaPhoneSignup(signupMethod, panelMode)) { + if (!shouldWarnCpaPhoneSignup(signupMethod, targetId)) { return true; } @@ -2559,6 +2648,18 @@ function getKiroUploadStatusLabel(value = '') { } } +function getKiroRuntimeState(state = {}) { + const runtimeState = state?.runtimeState?.flowState?.kiro; + if (runtimeState && typeof runtimeState === 'object' && !Array.isArray(runtimeState)) { + return runtimeState; + } + const flowState = state?.flowState?.kiro; + if (flowState && typeof flowState === 'object' && !Array.isArray(flowState)) { + return flowState; + } + return {}; +} + function setKiroRsConnectionTestStatus(message = '') { const nextText = String(message || '').trim() || '未测试'; kiroRsConnectionTestStatusText = nextText; @@ -2873,6 +2974,18 @@ function syncLatestState(nextState) { nodeStatuses: mergedNodeStatuses, }; + const activeFlowId = typeof normalizeFlowId === 'function' + ? normalizeFlowId(latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID, DEFAULT_ACTIVE_FLOW_ID) + : (String(latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID); + const schema = typeof getSettingsSchema === 'function' ? getSettingsSchema() : null; + const selectedTargetId = schema?.getSelectedTargetId + ? schema.getSelectedTargetId(latestState, activeFlowId) + : (String(latestState?.targetId || '').trim().toLowerCase() + || (activeFlowId === 'kiro' ? 'kiro-rs' : 'cpa')); + latestState.targetId = typeof normalizeTargetIdForFlow === 'function' + ? normalizeTargetIdForFlow(activeFlowId, selectedTargetId) + : selectedTargetId; + renderAccountRecords(latestState); } @@ -3053,7 +3166,12 @@ function getRequestedPlusAccountAccessStrategy(state = latestState) { if (typeof selectPanelMode !== 'undefined' && selectPanelMode?.value) { return selectPanelMode.value; } - return state?.panelMode || state?.openaiIntegrationTargetId || 'cpa'; + return getSelectedTargetIdForState({ + ...(latestState || {}), + ...(state || {}), + activeFlowId: DEFAULT_ACTIVE_FLOW_ID, + flowId: DEFAULT_ACTIVE_FLOW_ID, + }, DEFAULT_ACTIVE_FLOW_ID); }; const resolveStrategyForTarget = typeof resolvePlusAccountAccessStrategyForTarget === 'function' ? resolvePlusAccountAccessStrategyForTarget @@ -3136,7 +3254,38 @@ function resolvePlusManualContinuationActionLabelFromState(state = latestState) const activeFlowId = String(state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase(); const signupMethod = normalizeSignupMethod(state?.resolvedSignupMethod || state?.signupMethod || DEFAULT_SIGNUP_METHOD); const plusModeEnabled = state?.plusModeEnabled === undefined ? true : Boolean(state?.plusModeEnabled); - const targetId = normalizePlusStrategyTargetId(state?.panelMode || state?.openaiIntegrationTargetId || 'cpa'); + const normalizeTargetIdForFlowSafe = typeof normalizeTargetIdForFlow === 'function' + ? normalizeTargetIdForFlow + : ((flowId, targetId = '', fallback = '') => { + const normalizedFlowId = String(flowId || '').trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID; + if (normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID) { + const normalizedTargetId = String(targetId || fallback || '').trim().toLowerCase(); + return normalizedTargetId === 'sub2api' || normalizedTargetId === 'codex2api' ? normalizedTargetId : 'cpa'; + } + const normalizedTargetId = String(targetId || '').trim().toLowerCase(); + return normalizedTargetId || String(fallback || '').trim().toLowerCase() || 'kiro-rs'; + }); + const getDefaultTargetIdForFlowSafe = typeof getDefaultTargetIdForFlow === 'function' + ? getDefaultTargetIdForFlow + : ((flowId = DEFAULT_ACTIVE_FLOW_ID) => (String(flowId || '').trim().toLowerCase() === DEFAULT_ACTIVE_FLOW_ID ? 'cpa' : 'kiro-rs')); + const normalizePlusStrategyTargetIdSafe = typeof normalizePlusStrategyTargetId === 'function' + ? normalizePlusStrategyTargetId + : ((value = '') => { + const normalized = String(value || '').trim().toLowerCase(); + if (normalized === 'sub2api' || normalized === 'codex2api') { + return normalized; + } + return 'cpa'; + }); + const targetId = normalizePlusStrategyTargetIdSafe( + typeof getSelectedTargetIdForState === 'function' + ? getSelectedTargetIdForState(state, activeFlowId) + : normalizeTargetIdForFlowSafe( + activeFlowId, + state?.targetId || '', + getDefaultTargetIdForFlowSafe(activeFlowId) + ) + ); const strategy = normalizePlusAccountAccessStrategy(state?.plusAccountAccessStrategy || DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY); const effectiveStrategy = plusModeEnabled && activeFlowId === DEFAULT_ACTIVE_FLOW_ID && signupMethod === SIGNUP_METHOD_EMAIL ? strategy @@ -4478,12 +4627,63 @@ function collectSettingsPayload() { const normalized = String(value || '').trim().toLowerCase(); return normalized === 'sub2api' || normalized === 'codex2api' ? normalized : 'cpa'; }); - const rawPanelMode = normalizePanelModeSafe(selectPanelMode?.value || latestState?.panelMode || 'cpa'); + const normalizeTargetIdForFlowSafe = typeof normalizeTargetIdForFlow === 'function' + ? normalizeTargetIdForFlow + : ((flowId, targetId = '', fallback = '') => { + const normalizedFlowId = String(flowId || '').trim().toLowerCase() || defaultFlowId; + if (normalizedFlowId === defaultFlowId) { + return normalizePanelModeSafe(targetId || fallback); + } + const normalizedTargetId = String(targetId || '').trim().toLowerCase(); + return normalizedTargetId || String(fallback || '').trim().toLowerCase() || 'kiro-rs'; + }); + const resolvePlusAccountAccessStrategyForTargetSafe = typeof resolvePlusAccountAccessStrategyForTarget === 'function' + ? resolvePlusAccountAccessStrategyForTarget + : ((value = '', targetId = '') => { + const oauthStrategyValue = typeof PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH !== 'undefined' + ? PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH + : 'oauth'; + const sub2apiSessionStrategyValue = typeof PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION !== 'undefined' + ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION + : 'sub2api_codex_session'; + const cpaSessionStrategyValue = typeof PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION !== 'undefined' + ? PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION + : 'cpa_codex_session'; + const sessionUiValue = typeof PLUS_ACCOUNT_ACCESS_STRATEGY_CODEX_SESSION_UI !== 'undefined' + ? PLUS_ACCOUNT_ACCESS_STRATEGY_CODEX_SESSION_UI + : 'codex_session'; + const normalizedValue = String(value || '').trim().toLowerCase(); + const isSessionImport = normalizedValue === sessionUiValue + || normalizedValue === sub2apiSessionStrategyValue + || normalizedValue === cpaSessionStrategyValue; + if (!isSessionImport) { + return oauthStrategyValue; + } + const normalizedTargetId = typeof normalizePlusStrategyTargetId === 'function' + ? normalizePlusStrategyTargetId(targetId) + : normalizePanelModeSafe(targetId || ''); + if (normalizedTargetId === 'sub2api') { + return sub2apiSessionStrategyValue; + } + if (normalizedTargetId === 'cpa') { + return cpaSessionStrategyValue; + } + return oauthStrategyValue; + }); const selectedTargetId = typeof getSelectedTargetId === 'function' ? getSelectedTargetId(activeFlowId) - : (activeFlowId === defaultFlowId - ? rawPanelMode - : String(selectPanelMode?.value || latestState?.kiroTargetId || 'kiro-rs').trim().toLowerCase() || 'kiro-rs'); + : normalizeTargetIdForFlowSafe( + activeFlowId, + selectPanelMode?.value || latestState?.targetId || '', + typeof getDefaultTargetIdForFlow === 'function' + ? getDefaultTargetIdForFlow(activeFlowId) + : (activeFlowId === defaultFlowId ? 'cpa' : 'kiro-rs') + ); + const openAiTargetId = normalizePanelModeSafe( + activeFlowId === defaultFlowId + ? selectedTargetId + : getSelectedPanelMode(latestState) + ); const rawPlusModeEnabled = typeof inputPlusModeEnabled !== 'undefined' && inputPlusModeEnabled ? Boolean(inputPlusModeEnabled.checked) : Boolean(latestState?.plusModeEnabled); @@ -4495,14 +4695,12 @@ function collectSettingsPayload() { ? resolveCurrentSidepanelCapabilities({ activeFlowId, targetId: selectedTargetId, - panelMode: rawPanelMode, signupMethod: selectedSignupMethod, state: { ...(latestState || {}), activeFlowId, - ...(activeFlowId === defaultFlowId - ? { panelMode: rawPanelMode } - : { kiroTargetId: selectedTargetId }), + flowId: activeFlowId, + targetId: selectedTargetId, plusModeEnabled: rawPlusModeEnabled, plusAccountAccessStrategy: requestedPlusAccountAccessStrategy, phoneVerificationEnabled: rawPhoneVerificationEnabled, @@ -4517,15 +4715,13 @@ function collectSettingsPayload() { return registry?.resolveSidepanelCapabilities ? registry.resolveSidepanelCapabilities({ activeFlowId, - panelMode: rawPanelMode, targetId: selectedTargetId, signupMethod: selectedSignupMethod, state: { ...(latestState || {}), activeFlowId, - ...(activeFlowId === defaultFlowId - ? { panelMode: rawPanelMode } - : { kiroTargetId: selectedTargetId }), + flowId: activeFlowId, + targetId: selectedTargetId, plusModeEnabled: rawPlusModeEnabled, plusAccountAccessStrategy: requestedPlusAccountAccessStrategy, phoneVerificationEnabled: rawPhoneVerificationEnabled, @@ -4534,8 +4730,12 @@ function collectSettingsPayload() { }) : null; })(); - const effectivePanelMode = capabilityState?.effectivePanelMode || capabilityState?.panelMode || rawPanelMode; const effectiveTargetId = capabilityState?.effectiveTargetId || selectedTargetId; + const effectiveOpenAiTargetId = normalizePanelModeSafe( + activeFlowId === defaultFlowId + ? effectiveTargetId + : openAiTargetId + ); const effectivePlusModeEnabled = capabilityState ? Boolean(capabilityState.runtimeLocks?.plusModeEnabled) : rawPlusModeEnabled; @@ -4623,16 +4823,7 @@ function collectSettingsPayload() { }); return { activeFlowId, - ...(accountContributionEnabled ? {} : { - ...(activeFlowId === defaultFlowId ? { panelMode: effectivePanelMode } : {}), - }), - kiroTargetId: normalizeKiroTargetIdSafe( - 'kiro', - activeFlowId === 'kiro' - ? effectiveTargetId - : (latestState?.kiroTargetId || 'kiro-rs'), - 'kiro-rs' - ), + targetId: effectiveTargetId, kiroRsUrl: currentKiroRsUrlValue !== null ? (currentKiroRsUrlValue || defaultKiroRsUrl) : (String(latestState?.kiroRsUrl || defaultKiroRsUrl).trim() || defaultKiroRsUrl), @@ -4677,7 +4868,9 @@ function collectSettingsPayload() { codex2apiAdminKey: inputCodex2ApiAdminKey.value.trim(), plusModeEnabled: effectivePlusModeEnabled, plusPaymentMethod, - plusAccountAccessStrategy: requestedPlusAccountAccessStrategy, + plusAccountAccessStrategy: activeFlowId === defaultFlowId + ? resolvePlusAccountAccessStrategyForTargetSafe(requestedPlusAccountAccessStrategy, effectiveOpenAiTargetId) + : requestedPlusAccountAccessStrategy, hostedCheckoutVerificationUrl: typeof inputHostedCheckoutVerificationUrl !== 'undefined' && inputHostedCheckoutVerificationUrl ? String(inputHostedCheckoutVerificationUrl.value || '').trim() : String(latestState?.hostedCheckoutVerificationUrl || '').trim(), @@ -8683,12 +8876,9 @@ function getSelectedTargetIdForState(state = latestState, flowId = getSelectedFl if (schema?.getSelectedTargetId) { return schema.getSelectedTargetId(state || {}, normalizedFlowId); } - if (normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID) { - return normalizePanelMode(state?.panelMode || getDefaultTargetIdForFlow(normalizedFlowId)); - } return normalizeTargetIdForFlow( normalizedFlowId, - state?.kiroTargetId || '', + state?.targetId || '', getDefaultTargetIdForFlow(normalizedFlowId) ); } @@ -8698,14 +8888,9 @@ function getSelectedTargetId(flowId = getSelectedFlowId()) { const selectedValue = typeof selectPanelMode !== 'undefined' && selectPanelMode ? selectPanelMode.value : ''; - if (normalizedFlowId === DEFAULT_ACTIVE_FLOW_ID) { - return normalizePanelMode( - selectedValue || latestState?.panelMode || getDefaultTargetIdForFlow(normalizedFlowId) - ); - } return normalizeTargetIdForFlow( normalizedFlowId, - selectedValue || latestState?.kiroTargetId || '', + selectedValue || latestState?.targetId || '', getDefaultTargetIdForFlow(normalizedFlowId) ); } @@ -8857,24 +9042,15 @@ function resolveCurrentSidepanelCapabilities(options = {}) { }; const targetId = options?.targetId !== undefined ? options.targetId - : (activeFlowId === DEFAULT_ACTIVE_FLOW_ID - ? (options?.panelMode ?? state?.panelMode) - : (options?.kiroTargetId ?? state?.kiroTargetId)); - if (activeFlowId === DEFAULT_ACTIVE_FLOW_ID) { - state.panelMode = normalizePanelMode( - targetId || state?.panelMode || getDefaultTargetIdForFlow(activeFlowId) - ); - } else { - state.kiroTargetId = normalizeTargetIdForFlow( - activeFlowId, - targetId || state?.kiroTargetId || '', - getDefaultTargetIdForFlow(activeFlowId) - ); - } + : (state?.targetId ?? getSelectedTargetIdForState(state, activeFlowId)); + state.targetId = normalizeTargetIdForFlow( + activeFlowId, + targetId || state?.targetId || '', + getDefaultTargetIdForFlow(activeFlowId) + ); return registry.resolveSidepanelCapabilities({ activeFlowId, - panelMode: state?.panelMode, - targetId: activeFlowId === DEFAULT_ACTIVE_FLOW_ID ? state?.panelMode : state?.kiroTargetId, + targetId: state.targetId, signupMethod: options?.signupMethod ?? state?.signupMethod, state, }); @@ -8887,7 +9063,7 @@ function resolveStepDefinitionCapabilityState(state = latestState, options = {}) }; const capabilityState = resolveCurrentSidepanelCapabilities({ activeFlowId: options?.activeFlowId ?? nextState?.activeFlowId, - panelMode: options?.panelMode ?? nextState?.panelMode, + targetId: options?.targetId ?? nextState?.targetId, signupMethod: options?.signupMethod ?? nextState?.signupMethod, state: nextState, }); @@ -8905,16 +9081,35 @@ function resolveStepDefinitionCapabilityState(state = latestState, options = {}) }; } -function getSelectedPanelMode() { +function getSelectedPanelMode(state = latestState) { const resolvedPanelMode = normalizePanelMode( - typeof selectPanelMode !== 'undefined' && selectPanelMode - ? selectPanelMode.value - : (typeof latestState !== 'undefined' ? latestState?.panelMode : '') + typeof getSelectedTargetIdForState === 'function' + ? getSelectedTargetIdForState({ + ...(typeof latestState !== 'undefined' ? latestState : {}), + ...(state || {}), + activeFlowId: DEFAULT_ACTIVE_FLOW_ID, + flowId: DEFAULT_ACTIVE_FLOW_ID, + }, DEFAULT_ACTIVE_FLOW_ID) + : ( + typeof selectPanelMode !== 'undefined' && selectPanelMode + ? selectPanelMode.value + : (typeof state !== 'undefined' ? state?.targetId : '') + ) ); const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' - ? resolveCurrentSidepanelCapabilities({ panelMode: resolvedPanelMode }) + ? resolveCurrentSidepanelCapabilities({ + activeFlowId: DEFAULT_ACTIVE_FLOW_ID, + targetId: resolvedPanelMode, + state: { + ...(typeof latestState !== 'undefined' ? latestState : {}), + ...(state || {}), + activeFlowId: DEFAULT_ACTIVE_FLOW_ID, + flowId: DEFAULT_ACTIVE_FLOW_ID, + targetId: resolvedPanelMode, + }, + }) : null; - return capabilityState?.effectivePanelMode || capabilityState?.panelMode || resolvedPanelMode; + return normalizePanelMode(capabilityState?.effectiveTargetId || resolvedPanelMode); } function getSelectedSignupMethod() { @@ -8943,7 +9138,9 @@ function canSelectPhoneSignupMethod() { : Boolean(latestState?.accountContributionEnabled); const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' ? resolveCurrentSidepanelCapabilities({ - panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode, + targetId: typeof getSelectedTargetId === 'function' + ? getSelectedTargetId(getSelectedFlowId(latestState)) + : latestState?.targetId, state: { ...(typeof latestState !== 'undefined' ? latestState : {}), phoneVerificationEnabled: phoneEnabled, @@ -8959,7 +9156,9 @@ function canSelectPhoneSignupMethod() { return registry?.resolveSidepanelCapabilities ? registry.resolveSidepanelCapabilities({ activeFlowId: typeof latestState !== 'undefined' ? latestState?.activeFlowId : '', - panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : (latestState?.panelMode || 'cpa'), + targetId: typeof getSelectedTargetId === 'function' + ? getSelectedTargetId(getSelectedFlowId(latestState)) + : latestState?.targetId, state: { ...(typeof latestState !== 'undefined' ? latestState : {}), phoneVerificationEnabled: phoneEnabled, @@ -9061,7 +9260,9 @@ function updatePhoneVerificationSettingsUI() { : Boolean(latestState?.plusModeEnabled); const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' ? resolveCurrentSidepanelCapabilities({ - panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode, + targetId: typeof getSelectedTargetId === 'function' + ? getSelectedTargetId(getSelectedFlowId(latestState)) + : latestState?.targetId, signupMethod: typeof getSelectedSignupMethod === 'function' ? getSelectedSignupMethod() : latestState?.signupMethod, state: { ...(latestState || {}), @@ -9077,7 +9278,9 @@ function updatePhoneVerificationSettingsUI() { return registry?.resolveSidepanelCapabilities ? registry.resolveSidepanelCapabilities({ activeFlowId: latestState?.activeFlowId, - panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : (latestState?.panelMode || 'cpa'), + targetId: typeof getSelectedTargetId === 'function' + ? getSelectedTargetId(getSelectedFlowId(latestState)) + : latestState?.targetId, signupMethod: typeof getSelectedSignupMethod === 'function' ? getSelectedSignupMethod() : latestState?.signupMethod, state: { ...(latestState || {}), @@ -9366,7 +9569,9 @@ function updatePlusModeUI() { : false; const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' ? resolveCurrentSidepanelCapabilities({ - panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode, + targetId: typeof getSelectedTargetId === 'function' + ? getSelectedTargetId(getSelectedFlowId(latestState)) + : latestState?.targetId, state: { ...(latestState || {}), plusModeEnabled: rawEnabled, @@ -9381,7 +9586,9 @@ function updatePlusModeUI() { return registry?.resolveSidepanelCapabilities ? registry.resolveSidepanelCapabilities({ activeFlowId: latestState?.activeFlowId, - panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : (latestState?.panelMode || 'cpa'), + targetId: typeof getSelectedTargetId === 'function' + ? getSelectedTargetId(getSelectedFlowId(latestState)) + : latestState?.targetId, state: { ...(latestState || {}), plusModeEnabled: rawEnabled, @@ -9403,10 +9610,8 @@ function updatePlusModeUI() { || requestedPlusAccountAccessStrategy || oauthStrategyValue; const effectiveTargetId = resolveStrategyTargetId( - capabilityState?.effectivePanelMode - || capabilityState?.effectiveTargetId - || capabilityState?.panelMode - || (typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode) + capabilityState?.effectiveTargetId + || (typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode(latestState) : latestState?.targetId) || 'cpa' ); const method = enabled ? getSelectedPlusPaymentMethod() : defaultMethod; @@ -9711,7 +9916,12 @@ function syncSignupPhoneInputFromState(state = latestState) { : (String(rawSignupMethod || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email'); const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' ? resolveCurrentSidepanelCapabilities({ - panelMode: state?.panelMode || latestState?.panelMode, + targetId: typeof getSelectedTargetIdForState === 'function' + ? getSelectedTargetIdForState({ + ...(latestState || {}), + ...(state || {}), + }, state?.activeFlowId || latestState?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID) + : (state?.targetId || latestState?.targetId), signupMethod: selectedMethod, state: { ...(latestState || {}), @@ -9727,7 +9937,12 @@ function syncSignupPhoneInputFromState(state = latestState) { return registry?.resolveSidepanelCapabilities ? registry.resolveSidepanelCapabilities({ activeFlowId: state?.activeFlowId || latestState?.activeFlowId, - panelMode: state?.panelMode || latestState?.panelMode, + targetId: typeof getSelectedTargetIdForState === 'function' + ? getSelectedTargetIdForState({ + ...(latestState || {}), + ...(state || {}), + }, state?.activeFlowId || latestState?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID) + : (state?.targetId || latestState?.targetId), signupMethod: selectedMethod, state: { ...(latestState || {}), @@ -9925,13 +10140,44 @@ async function openPlusManualConfirmationDialog(options = {}) { const method = String(options.method || '').trim().toLowerCase(); const gopayValue = typeof PLUS_PAYMENT_METHOD_GOPAY !== 'undefined' ? PLUS_PAYMENT_METHOD_GOPAY : 'gopay'; const activeFlowId = String(latestState?.activeFlowId || latestState?.flowId || 'openai').trim().toLowerCase(); - const panelMode = String(latestState?.panelMode || latestState?.openaiIntegrationTargetId || '').trim().toLowerCase(); + const normalizeTargetIdForFlowSafe = typeof normalizeTargetIdForFlow === 'function' + ? normalizeTargetIdForFlow + : ((flowId, targetId = '', fallback = '') => { + const normalizedFlowId = String(flowId || '').trim().toLowerCase() || 'openai'; + if (normalizedFlowId === 'openai') { + const normalizedTargetId = String(targetId || fallback || '').trim().toLowerCase(); + return normalizedTargetId === 'sub2api' || normalizedTargetId === 'codex2api' ? normalizedTargetId : 'cpa'; + } + const normalizedTargetId = String(targetId || '').trim().toLowerCase(); + return normalizedTargetId || String(fallback || '').trim().toLowerCase() || 'kiro-rs'; + }); + const getDefaultTargetIdForFlowSafe = typeof getDefaultTargetIdForFlow === 'function' + ? getDefaultTargetIdForFlow + : ((flowId = 'openai') => (String(flowId || '').trim().toLowerCase() === 'openai' ? 'cpa' : 'kiro-rs')); + const normalizePlusStrategyTargetIdSafe = typeof normalizePlusStrategyTargetId === 'function' + ? normalizePlusStrategyTargetId + : ((value = '') => { + const normalized = String(value || '').trim().toLowerCase(); + if (normalized === 'sub2api' || normalized === 'codex2api') { + return normalized; + } + return 'cpa'; + }); + const targetId = normalizePlusStrategyTargetIdSafe( + typeof getSelectedTargetIdForState === 'function' + ? getSelectedTargetIdForState(latestState, activeFlowId) + : normalizeTargetIdForFlowSafe( + activeFlowId, + latestState?.targetId || '', + getDefaultTargetIdForFlowSafe(activeFlowId) + ) + ); const signupMethod = String(latestState?.resolvedSignupMethod || latestState?.signupMethod || 'email').trim().toLowerCase(); const plusModeEnabled = latestState?.plusModeEnabled === undefined ? true : Boolean(latestState.plusModeEnabled); const plusAccountAccessStrategy = String(latestState?.plusAccountAccessStrategy || 'oauth').trim().toLowerCase(); const useSub2ApiSessionImport = plusModeEnabled && activeFlowId === 'openai' - && panelMode === 'sub2api' + && targetId === 'sub2api' && signupMethod === 'email' && plusAccountAccessStrategy === 'sub2api_codex_session'; const continuationActionLabel = useSub2ApiSessionImport @@ -9996,13 +10242,44 @@ async function syncPlusManualConfirmationDialog() { const step = Number(latestState?.plusManualConfirmationStep) || 0; const method = String(latestState?.plusManualConfirmationMethod || '').trim().toLowerCase(); const activeFlowId = String(latestState?.activeFlowId || latestState?.flowId || 'openai').trim().toLowerCase(); - const panelMode = String(latestState?.panelMode || latestState?.openaiIntegrationTargetId || '').trim().toLowerCase(); + const normalizeTargetIdForFlowSafe = typeof normalizeTargetIdForFlow === 'function' + ? normalizeTargetIdForFlow + : ((flowId, targetId = '', fallback = '') => { + const normalizedFlowId = String(flowId || '').trim().toLowerCase() || 'openai'; + if (normalizedFlowId === 'openai') { + const normalizedTargetId = String(targetId || fallback || '').trim().toLowerCase(); + return normalizedTargetId === 'sub2api' || normalizedTargetId === 'codex2api' ? normalizedTargetId : 'cpa'; + } + const normalizedTargetId = String(targetId || '').trim().toLowerCase(); + return normalizedTargetId || String(fallback || '').trim().toLowerCase() || 'kiro-rs'; + }); + const getDefaultTargetIdForFlowSafe = typeof getDefaultTargetIdForFlow === 'function' + ? getDefaultTargetIdForFlow + : ((flowId = 'openai') => (String(flowId || '').trim().toLowerCase() === 'openai' ? 'cpa' : 'kiro-rs')); + const normalizePlusStrategyTargetIdSafe = typeof normalizePlusStrategyTargetId === 'function' + ? normalizePlusStrategyTargetId + : ((value = '') => { + const normalized = String(value || '').trim().toLowerCase(); + if (normalized === 'sub2api' || normalized === 'codex2api') { + return normalized; + } + return 'cpa'; + }); + const targetId = normalizePlusStrategyTargetIdSafe( + typeof getSelectedTargetIdForState === 'function' + ? getSelectedTargetIdForState(latestState, activeFlowId) + : normalizeTargetIdForFlowSafe( + activeFlowId, + latestState?.targetId || '', + getDefaultTargetIdForFlowSafe(activeFlowId) + ) + ); const signupMethod = String(latestState?.resolvedSignupMethod || latestState?.signupMethod || 'email').trim().toLowerCase(); const plusModeEnabled = latestState?.plusModeEnabled === undefined ? true : Boolean(latestState.plusModeEnabled); const plusAccountAccessStrategy = String(latestState?.plusAccountAccessStrategy || 'oauth').trim().toLowerCase(); const useSub2ApiSessionImport = plusModeEnabled && activeFlowId === 'openai' - && panelMode === 'sub2api' + && targetId === 'sub2api' && signupMethod === 'email' && plusAccountAccessStrategy === 'sub2api_codex_session'; const continuationActionLabel = useSub2ApiSessionImport @@ -10523,7 +10800,7 @@ function syncStepDefinitionsFromUiState(stateOverrides = {}) { const stepDefinitionState = typeof resolveStepDefinitionCapabilityState === 'function' ? resolveStepDefinitionCapabilityState(nextState, { activeFlowId: nextState?.activeFlowId, - panelMode: nextState?.panelMode, + targetId: nextState?.targetId, signupMethod: nextState?.signupMethod, state: nextState, }) @@ -10616,7 +10893,7 @@ function applySettingsState(state) { ? syncFlowSelectorsFromState(state) : { activeFlowId: String(state?.activeFlowId || state?.flowId || defaultActiveFlowId).trim().toLowerCase() || defaultActiveFlowId, - targetId: String(state?.panelMode || 'cpa').trim().toLowerCase() || 'cpa', + targetId: String(state?.targetId || 'cpa').trim().toLowerCase() || 'cpa', }; if (typeof applyOperationDelayState === 'function') { applyOperationDelayState(state); @@ -10748,24 +11025,38 @@ function applySettingsState(state) { if (typeof displayKiroRsTestStatus !== 'undefined' && displayKiroRsTestStatus) { displayKiroRsTestStatus.textContent = kiroRsConnectionTestStatusText; } + const resolveKiroRuntimeState = typeof getKiroRuntimeState === 'function' + ? getKiroRuntimeState + : ((value = {}) => { + const runtimeState = value?.runtimeState?.flowState?.kiro; + if (runtimeState && typeof runtimeState === 'object' && !Array.isArray(runtimeState)) { + return runtimeState; + } + const flowState = value?.flowState?.kiro; + if (flowState && typeof flowState === 'object' && !Array.isArray(flowState)) { + return flowState; + } + return {}; + }); + const kiroRuntimeState = resolveKiroRuntimeState(state); if (typeof displayKiroWebStatus !== 'undefined' && displayKiroWebStatus) { const kiroWebStatus = String( - state?.kiroRuntime?.webAuth?.status - || state?.kiroRuntime?.register?.status + kiroRuntimeState?.webAuth?.status + || kiroRuntimeState?.register?.status || '' ).trim(); displayKiroWebStatus.textContent = kiroWebStatus || '未开始'; } if (typeof displayKiroLoginUrl !== 'undefined' && displayKiroLoginUrl) { const kiroLoginUrl = String( - state?.kiroRuntime?.register?.loginUrl + kiroRuntimeState?.register?.loginUrl || '' ).trim(); displayKiroLoginUrl.textContent = kiroLoginUrl || '未打开'; } if (typeof displayKiroUploadStatus !== 'undefined' && displayKiroUploadStatus) { const kiroUploadStatus = String( - state?.kiroRuntime?.upload?.status + kiroRuntimeState?.upload?.status || '' ).trim(); displayKiroUploadStatus.textContent = getKiroUploadStatusLabel(kiroUploadStatus); @@ -12404,7 +12695,9 @@ function updateMailProviderUI() { : latestState?.icloudHostPreference; const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' ? resolveCurrentSidepanelCapabilities({ - panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode, + targetId: typeof getSelectedTargetId === 'function' + ? getSelectedTargetId(getSelectedFlowId(latestState)) + : latestState?.targetId, state: latestState || {}, }) : null; @@ -13048,23 +13341,16 @@ function updatePanelModeUI() { : normalizeFlowId(latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID); const targetId = typeof getSelectedTargetId === 'function' ? getSelectedTargetId(activeFlowId) - : (activeFlowId === DEFAULT_ACTIVE_FLOW_ID - ? normalizePanelMode(selectPanelMode?.value || latestState?.panelMode || 'cpa') - : String(selectPanelMode?.value || latestState?.kiroTargetId || 'kiro-rs').trim().toLowerCase() || 'kiro-rs'); - const rawPanelMode = activeFlowId === DEFAULT_ACTIVE_FLOW_ID - ? normalizePanelMode(targetId || latestState?.panelMode || 'cpa') - : normalizePanelMode(latestState?.panelMode || 'cpa'); + : normalizeTargetIdForFlow(activeFlowId, selectPanelMode?.value || latestState?.targetId || '', getDefaultTargetIdForFlow(activeFlowId)); const capabilityState = typeof resolveCurrentSidepanelCapabilities === 'function' ? resolveCurrentSidepanelCapabilities({ activeFlowId, targetId, - panelMode: rawPanelMode, state: { ...(latestState || {}), activeFlowId, - ...(activeFlowId === DEFAULT_ACTIVE_FLOW_ID - ? { panelMode: rawPanelMode } - : { kiroTargetId: targetId }), + flowId: activeFlowId, + targetId, }, }) : null; @@ -13089,12 +13375,16 @@ function updatePanelModeUI() { if (typeof updatePhoneVerificationSettingsUI === 'function') { updatePhoneVerificationSettingsUI(); } - const panelMode = capabilityState?.effectivePanelMode || capabilityState?.panelMode || rawPanelMode; + const displayTargetId = normalizePanelMode( + activeFlowId === DEFAULT_ACTIVE_FLOW_ID + ? (capabilityState?.effectiveTargetId || targetId) + : getSelectedPanelMode(latestState) + ); - const useCodex2Api = panelMode === 'codex2api'; + const useCodex2Api = displayTargetId === 'codex2api'; const step9Btn = document.querySelector('.step-btn[data-step-key="platform-verify"]'); if (step9Btn && activeFlowId === DEFAULT_ACTIVE_FLOW_ID) { - step9Btn.textContent = panelMode === 'sub2api' + step9Btn.textContent = displayTargetId === 'sub2api' ? 'SUB2API 回调验证' : (useCodex2Api ? 'Codex2API 回调验证' : 'CPA 回调验证'); } @@ -14042,7 +14332,7 @@ const accountContributionManager = window.SidepanelContributionMode?.createContr : String(latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID), getSelectedTargetId: (flowId, state = latestState) => (typeof getSelectedTargetIdForState === 'function' ? getSelectedTargetIdForState(state, flowId) - : (String(state?.panelMode || state?.kiroTargetId || 'cpa').trim().toLowerCase() || 'cpa')), + : normalizeTargetIdForFlow(flowId, state?.targetId || '', getDefaultTargetIdForFlow(flowId))), isModeSwitchBlocked: isContributionModeSwitchBlocked, openConfirmModal, openExternalUrl, @@ -14570,7 +14860,9 @@ async function startAutoRunFromCurrentSettings() { } const validationState = { ...(latestState || {}), - panelMode: typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode, + targetId: typeof getSelectedTargetId === 'function' + ? getSelectedTargetId(getSelectedFlowId(latestState)) + : latestState?.targetId, signupMethod: typeof getSelectedSignupMethod === 'function' ? getSelectedSignupMethod() : latestState?.signupMethod, phoneVerificationEnabled: typeof inputPhoneVerificationEnabled !== 'undefined' && inputPhoneVerificationEnabled ? Boolean(inputPhoneVerificationEnabled.checked) @@ -14582,7 +14874,7 @@ async function startAutoRunFromCurrentSettings() { }; return registry.validateAutoRunStart({ activeFlowId: validationState.activeFlowId, - panelMode: validationState.panelMode, + targetId: validationState.targetId, signupMethod: validationState.signupMethod, state: validationState, }); @@ -14650,11 +14942,7 @@ async function startAutoRunFromCurrentSettings() { : (String(latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID); const targetId = typeof getSelectedTargetId === 'function' ? getSelectedTargetId(activeFlowId) - : ( - activeFlowId === DEFAULT_ACTIVE_FLOW_ID - ? normalizePanelMode(latestState?.panelMode || 'cpa') - : (String(latestState?.kiroTargetId || 'kiro-rs').trim().toLowerCase() || 'kiro-rs') - ); + : normalizeTargetIdForFlow(activeFlowId, latestState?.targetId || '', getDefaultTargetIdForFlow(activeFlowId)); inputAutoDelayMinutes.value = String(delayMinutes); btnAutoRun.innerHTML = delayEnabled ? ' 计划中...' @@ -15230,9 +15518,7 @@ selectPanelMode.addEventListener('change', async () => { : (activeFlowId === DEFAULT_ACTIVE_FLOW_ID ? 'cpa' : 'kiro-rs'); const previousTargetId = typeof getSelectedTargetIdForState === 'function' ? getSelectedTargetIdForState(latestState, activeFlowId) - : (activeFlowId === DEFAULT_ACTIVE_FLOW_ID - ? normalizePanelMode(latestState?.panelMode || defaultTargetId) - : String(latestState?.kiroTargetId || defaultTargetId).trim().toLowerCase() || defaultTargetId); + : normalizeTargetIdForFlow(activeFlowId, latestState?.targetId || '', defaultTargetId); let nextTargetId = typeof normalizeTargetIdForFlow === 'function' ? normalizeTargetIdForFlow(activeFlowId, selectPanelMode.value, defaultTargetId) : (activeFlowId === DEFAULT_ACTIVE_FLOW_ID @@ -15243,7 +15529,7 @@ selectPanelMode.addEventListener('change', async () => { selectPanelMode.value = nextPanelMode; const confirmed = await confirmCpaPhoneSignupIfNeeded({ signupMethod: getSelectedSignupMethod(), - panelMode: nextPanelMode, + targetId: nextPanelMode, }); if (!confirmed) { selectPanelMode.value = previousTargetId; @@ -15254,7 +15540,7 @@ selectPanelMode.addEventListener('change', async () => { syncLatestState({ activeFlowId, flowId: activeFlowId, - panelMode: nextPanelMode, + targetId: nextPanelMode, }); if ( typeof selectPlusAccountAccessStrategy !== 'undefined' @@ -15278,7 +15564,7 @@ selectPanelMode.addEventListener('change', async () => { syncLatestState({ activeFlowId, flowId: activeFlowId, - kiroTargetId: nextTargetId, + targetId: nextTargetId, }); } updatePanelModeUI(); @@ -15305,7 +15591,7 @@ selectPlusAccountAccessStrategy?.addEventListener('change', () => { selectPlusAccountAccessStrategy.value = nextUiValue; selectPlusAccountAccessStrategy.dataset.requestedValue = resolvePlusAccountAccessStrategyForTarget( nextUiValue, - typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode + typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode(latestState) : latestState?.targetId ); updatePlusModeUI(); markSettingsDirty(true); @@ -16100,19 +16386,13 @@ selectFlow?.addEventListener('change', () => { : (nextActiveFlowId === DEFAULT_ACTIVE_FLOW_ID ? 'cpa' : 'kiro-rs'); const nextTargetId = typeof getSelectedTargetIdForState === 'function' ? getSelectedTargetIdForState(nextStateBase, nextActiveFlowId) - : (nextActiveFlowId === DEFAULT_ACTIVE_FLOW_ID - ? normalizePanelMode(nextStateBase?.panelMode || defaultTargetId) - : String(nextStateBase?.kiroTargetId || defaultTargetId).trim().toLowerCase() || defaultTargetId); + : normalizeTargetIdForFlow(nextActiveFlowId, nextStateBase?.targetId || '', defaultTargetId); syncLatestState({ activeFlowId: nextActiveFlowId, flowId: nextActiveFlowId, - ...(nextActiveFlowId === DEFAULT_ACTIVE_FLOW_ID - ? { panelMode: normalizePanelMode(nextTargetId || defaultTargetId) } - : { - kiroTargetId: typeof normalizeTargetIdForFlow === 'function' - ? normalizeTargetIdForFlow(nextActiveFlowId, nextTargetId, defaultTargetId) - : (String(nextTargetId || defaultTargetId).trim().toLowerCase() || defaultTargetId), - }), + targetId: typeof normalizeTargetIdForFlow === 'function' + ? normalizeTargetIdForFlow(nextActiveFlowId, nextTargetId, defaultTargetId) + : (String(nextTargetId || defaultTargetId).trim().toLowerCase() || defaultTargetId), }); updatePanelModeUI(); if (typeof syncStepDefinitionsFromUiState === 'function') { @@ -16320,7 +16600,7 @@ signupMethodButtons.forEach((button) => { const nextSignupMethod = normalizeSignupMethod(button.dataset.signupMethod); const confirmed = await confirmCpaPhoneSignupIfNeeded({ signupMethod: nextSignupMethod, - panelMode: getSelectedPanelMode(), + targetId: getSelectedPanelMode(latestState), }); if (!confirmed) { updateSignupMethodUI(); @@ -17047,15 +17327,18 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { setLocalCpaStep9Mode(message.payload.localCpaStep9Mode); } if ( - message.payload.panelMode !== undefined + message.payload.targetId !== undefined || message.payload.activeFlowId !== undefined || message.payload.flowId !== undefined - || message.payload.kiroTargetId !== undefined ) { if (typeof syncFlowSelectorsFromState === 'function') { syncFlowSelectorsFromState(latestState); - } else if (message.payload.panelMode !== undefined) { - selectPanelMode.value = normalizePanelMode(message.payload.panelMode || 'cpa'); + } else if (message.payload.targetId !== undefined) { + selectPanelMode.value = normalizeTargetIdForFlow( + latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID, + message.payload.targetId || '', + getDefaultTargetIdForFlow(latestState?.activeFlowId || latestState?.flowId || DEFAULT_ACTIVE_FLOW_ID) + ); } updatePanelModeUI(); } diff --git a/tests/auth-japanese-localization.test.js b/tests/auth-japanese-localization.test.js index b7529e6..3aeb1d2 100644 --- a/tests/auth-japanese-localization.test.js +++ b/tests/auth-japanese-localization.test.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('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}(`]; diff --git a/tests/auth-page-recovery.test.js b/tests/auth-page-recovery.test.js index a6b87ad..24db8ee 100644 --- a/tests/auth-page-recovery.test.js +++ b/tests/auth-page-recovery.test.js @@ -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}(`); diff --git a/tests/auto-run-fresh-attempt-reset.test.js b/tests/auto-run-fresh-attempt-reset.test.js index f1a2354..4505ae8 100644 --- a/tests/auto-run-fresh-attempt-reset.test.js +++ b/tests/auto-run-fresh-attempt-reset.test.js @@ -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', }, }; } diff --git a/tests/background-account-history-settings.test.js b/tests/background-account-history-settings.test.js index 9acc2b8..21ed0f0 100644 --- a/tests/background-account-history-settings.test.js +++ b/tests/background-account-history-settings.test.js @@ -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'); diff --git a/tests/background-contribution-mode.test.js b/tests/background-contribution-mode.test.js index 591a440..fe100a4 100644 --- a/tests/background-contribution-mode.test.js +++ b/tests/background-contribution-mode.test.js @@ -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', }); diff --git a/tests/background-cpa-session-import.test.js b/tests/background-cpa-session-import.test.js index b73c658..6396de6 100644 --- a/tests/background-cpa-session-import.test.js +++ b/tests/background-cpa-session-import.test.js @@ -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, diff --git a/tests/background-effective-plus-account-access-strategy.test.js b/tests/background-effective-plus-account-access-strategy.test.js index aae5b48..5afc723 100644 --- a/tests/background-effective-plus-account-access-strategy.test.js +++ b/tests/background-effective-plus-account-access-strategy.test.js @@ -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', diff --git a/tests/background-kiro-desktop-authorize-runner-module.test.js b/tests/background-kiro-desktop-authorize-runner-module.test.js index efee324..9e811c1 100644 --- a/tests/background-kiro-desktop-authorize-runner-module.test.js +++ b/tests/background-kiro-desktop-authorize-runner-module.test.js @@ -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'); }); diff --git a/tests/background-kiro-publisher-kiro-rs-module.test.js b/tests/background-kiro-publisher-kiro-rs-module.test.js index e51d222..66d80d6 100644 --- a/tests/background-kiro-publisher-kiro-rs-module.test.js +++ b/tests/background-kiro-publisher-kiro-rs-module.test.js @@ -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 () => { diff --git a/tests/background-kiro-register-runner-module.test.js b/tests/background-kiro-register-runner-module.test.js index d54a1d7..a28ee9c 100644 --- a/tests/background-kiro-register-runner-module.test.js +++ b/tests/background-kiro-register-runner-module.test.js @@ -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); }); diff --git a/tests/background-kiro-state-module.test.js b/tests/background-kiro-state-module.test.js index 0e6ad43..04502fd 100644 --- a/tests/background-kiro-state-module.test.js +++ b/tests/background-kiro-state-module.test.js @@ -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'); }); diff --git a/tests/background-logging-status-module.test.js b/tests/background-logging-status-module.test.js index ed0cbcc..186e1bd 100644 --- a/tests/background-logging-status-module.test.js +++ b/tests/background-logging-status-module.test.js @@ -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); diff --git a/tests/background-mail2925-signup-flow.test.js b/tests/background-mail2925-signup-flow.test.js index 43c75fb..15f77d3 100644 --- a/tests/background-mail2925-signup-flow.test.js +++ b/tests/background-mail2925-signup-flow.test.js @@ -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, }); diff --git a/tests/background-message-router-module.test.js b/tests/background-message-router-module.test.js index cc0908e..bd284fc 100644 --- a/tests/background-message-router-module.test.js +++ b/tests/background-message-router-module.test.js @@ -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({ diff --git a/tests/background-message-router-step2-skip.test.js b/tests/background-message-router-step2-skip.test.js index d48e56f..bfa2b1d 100644 --- a/tests/background-message-router-step2-skip.test.js +++ b/tests/background-message-router-step2-skip.test.js @@ -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 安全防护系统', }, {}); diff --git a/tests/background-navigation-utils-module.test.js b/tests/background-navigation-utils-module.test.js index cce18ed..e6d62c4 100644 --- a/tests/background-navigation-utils-module.test.js +++ b/tests/background-navigation-utils-module.test.js @@ -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'); }); diff --git a/tests/background-operation-delay-exclusions.test.js b/tests/background-operation-delay-exclusions.test.js index 744964e..d73fb85 100644 --- a/tests/background-operation-delay-exclusions.test.js +++ b/tests/background-operation-delay-exclusions.test.js @@ -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`); diff --git a/tests/background-platform-verify-codex2api.test.js b/tests/background-platform-verify-codex2api.test.js index 1a3ba7a..90f1c17 100644 --- a/tests/background-platform-verify-codex2api.test.js +++ b/tests/background-platform-verify-codex2api.test.js @@ -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 = []; diff --git a/tests/background-platform-verify-cpa-api.test.js b/tests/background-platform-verify-cpa-api.test.js index 4aaf2fe..31d84ab 100644 --- a/tests/background-platform-verify-cpa-api.test.js +++ b/tests/background-platform-verify-cpa-api.test.js @@ -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 () => { diff --git a/tests/background-runtime-state-module.test.js b/tests/background-runtime-state-module.test.js index c7455c8..ddb8134 100644 --- a/tests/background-runtime-state-module.test.js +++ b/tests/background-runtime-state-module.test.js @@ -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'); +}); diff --git a/tests/background-settings-import-mode-validation.test.js b/tests/background-settings-import-mode-validation.test.js index 3348397..d509e81 100644 --- a/tests/background-settings-import-mode-validation.test.js +++ b/tests/background-settings-import-mode-validation.test.js @@ -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'); +}); diff --git a/tests/background-settings-schema-persistence.test.js b/tests/background-settings-schema-persistence.test.js index ff5df40..a5e5347 100644 --- a/tests/background-settings-schema-persistence.test.js +++ b/tests/background-settings-schema-persistence.test.js @@ -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); diff --git a/tests/background-signup-step2-branching.test.js b/tests/background-signup-step2-branching.test.js index 5b0d324..3090fbf 100644 --- a/tests/background-signup-step2-branching.test.js +++ b/tests/background-signup-step2-branching.test.js @@ -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, }); diff --git a/tests/background-step-modules.test.js b/tests/background-step-modules.test.js index 1651099..2ec5b7a 100644 --- a/tests/background-step-modules.test.js +++ b/tests/background-step-modules.test.js @@ -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, '\\$&'))); }); diff --git a/tests/background-step-node-registry-module.test.js b/tests/background-step-node-registry-module.test.js index bf9421f..e216a88 100644 --- a/tests/background-step-node-registry-module.test.js +++ b/tests/background-step-node-registry-module.test.js @@ -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([ diff --git a/tests/background-step-registry.test.js b/tests/background-step-registry.test.js index d29bb41..aca3ac2 100644 --- a/tests/background-step-registry.test.js +++ b/tests/background-step-registry.test.js @@ -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\(/); diff --git a/tests/background-step1-cookie-cleanup.test.js b/tests/background-step1-cookie-cleanup.test.js index 3dff752..2de2d06 100644 --- a/tests/background-step1-cookie-cleanup.test.js +++ b/tests/background-step1-cookie-cleanup.test.js @@ -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); } diff --git a/tests/background-step3-password-reuse.test.js b/tests/background-step3-password-reuse.test.js index f70fda1..822a3d5 100644 --- a/tests/background-step3-password-reuse.test.js +++ b/tests/background-step3-password-reuse.test.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/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({ diff --git a/tests/background-step4-filter-window.test.js b/tests/background-step4-filter-window.test.js index 8d51bab..e9e3a89 100644 --- a/tests/background-step4-filter-window.test.js +++ b/tests/background-step4-filter-window.test.js @@ -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 }; }, diff --git a/tests/background-step5-submit-short-circuit.test.js b/tests/background-step5-submit-short-circuit.test.js index 781ea81..f205428 100644 --- a/tests/background-step5-submit-short-circuit.test.js +++ b/tests/background-step5-submit-short-circuit.test.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/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', diff --git a/tests/background-step6-retry-limit.test.js b/tests/background-step6-retry-limit.test.js index 42f6ac1..c4d4913 100644 --- a/tests/background-step6-retry-limit.test.js +++ b/tests/background-step6-retry-limit.test.js @@ -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); diff --git a/tests/background-step7-recovery.test.js b/tests/background-step7-recovery.test.js index 1e6b8e0..46785b5 100644 --- a/tests/background-step7-recovery.test.js +++ b/tests/background-step7-recovery.test.js @@ -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, diff --git a/tests/background-sub2api-session-import.test.js b/tests/background-sub2api-session-import.test.js index 8c486b1..8d35264 100644 --- a/tests/background-sub2api-session-import.test.js +++ b/tests/background-sub2api-session-import.test.js @@ -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, diff --git a/tests/background-tab-runtime-module.test.js b/tests/background-tab-runtime-module.test.js index fe19522..533d5ff 100644 --- a/tests/background-tab-runtime-module.test.js +++ b/tests/background-tab-runtime-module.test.js @@ -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 = []; diff --git a/tests/contribution-registry.test.js b/tests/contribution-registry.test.js index af07481..dfed168 100644 --- a/tests/contribution-registry.test.js +++ b/tests/contribution-registry.test.js @@ -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() { diff --git a/tests/flow-capabilities-module.test.js b/tests/flow-capabilities-module.test.js index 547197a..fa73b35 100644 --- a/tests/flow-capabilities-module.test.js +++ b/tests/flow-capabilities-module.test.js @@ -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, diff --git a/tests/flow-registry-settings-schema.test.js b/tests/flow-registry-settings-schema.test.js index 329e18c..0d9809f 100644 --- a/tests/flow-registry-settings-schema.test.js +++ b/tests/flow-registry-settings-schema.test.js @@ -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'); }); diff --git a/tests/gopay-approve-source.test.js b/tests/gopay-approve-source.test.js index 8523496..c3e1b90 100644 --- a/tests/gopay-approve-source.test.js +++ b/tests/gopay-approve-source.test.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/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}(`]; diff --git a/tests/gopay-flow-content.test.js b/tests/gopay-flow-content.test.js index 2f88726..a1183a8 100644 --- a/tests/gopay-flow-content.test.js +++ b/tests/gopay-flow-content.test.js @@ -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}(`]; diff --git a/tests/gopay-manual-confirm.test.js b/tests/gopay-manual-confirm.test.js index b116083..03880f1 100644 --- a/tests/gopay-manual-confirm.test.js +++ b/tests/gopay-manual-confirm.test.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/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); diff --git a/tests/helpers/script-bundles.js b/tests/helpers/script-bundles.js new file mode 100644 index 0000000..632294e --- /dev/null +++ b/tests/helpers/script-bundles.js @@ -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, +}; diff --git a/tests/kiro-contribution-artifact.test.js b/tests/kiro-contribution-artifact.test.js index 390a55e..a1c999e 100644 --- a/tests/kiro-contribution-artifact.test.js +++ b/tests/kiro-contribution-artifact.test.js @@ -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'/); diff --git a/tests/kiro-register-page-fatal-state.test.js b/tests/kiro-register-page-fatal-state.test.js index 57b7412..9fcc077 100644 --- a/tests/kiro-register-page-fatal-state.test.js +++ b/tests/kiro-register-page-fatal-state.test.js @@ -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 }; diff --git a/tests/legacy-account-records-importer.test.js b/tests/legacy-account-records-importer.test.js new file mode 100644 index 0000000..20562dd --- /dev/null +++ b/tests/legacy-account-records-importer.test.js @@ -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); +}); diff --git a/tests/legacy-settings-importer.test.js b/tests/legacy-settings-importer.test.js new file mode 100644 index 0000000..4fa82c6 --- /dev/null +++ b/tests/legacy-settings-importer.test.js @@ -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, []); +}); diff --git a/tests/mail-polling-operation-delay-exclusion.test.js b/tests/mail-polling-operation-delay-exclusion.test.js index 0447824..1f09016 100644 --- a/tests/mail-polling-operation-delay-exclusion.test.js +++ b/tests/mail-polling-operation-delay-exclusion.test.js @@ -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\(/); }); diff --git a/tests/signup-page-node-resolution.test.js b/tests/openai-auth-node-resolution.test.js similarity index 96% rename from tests/signup-page-node-resolution.test.js rename to tests/openai-auth-node-resolution.test.js index 038a26e..7ac8338 100644 --- a/tests/signup-page-node-resolution.test.js +++ b/tests/openai-auth-node-resolution.test.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('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}(`]; diff --git a/tests/signup-page-tab-cleanup.test.js b/tests/openai-auth-tab-cleanup.test.js similarity index 84% rename from tests/signup-page-tab-cleanup.test.js rename to tests/openai-auth-tab-cleanup.test.js index 5b021f6..8388b34 100644 --- a/tests/signup-page-tab-cleanup.test.js +++ b/tests/openai-auth-tab-cleanup.test.js @@ -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); diff --git a/tests/operation-delay-injection.test.js b/tests/operation-delay-injection.test.js index 2c0a7fd..f6e7b4a 100644 --- a/tests/operation-delay-injection.test.js +++ b/tests/operation-delay-injection.test.js @@ -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/); } diff --git a/tests/paypal-approve-detection.test.js b/tests/paypal-approve-detection.test.js index 1597d42..31c26d8 100644 --- a/tests/paypal-approve-detection.test.js +++ b/tests/paypal-approve-detection.test.js @@ -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 = {}; diff --git a/tests/paypal-flow-content.test.js b/tests/paypal-flow-content.test.js index 0313208..db41f5c 100644 --- a/tests/paypal-flow-content.test.js +++ b/tests/paypal-flow-content.test.js @@ -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}(`]; diff --git a/tests/phone-auth-country-match.test.js b/tests/phone-auth-country-match.test.js index ef849d8..3a58ffd 100644 --- a/tests/phone-auth-country-match.test.js +++ b/tests/phone-auth-country-match.test.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('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); diff --git a/tests/phone-country-utils.test.js b/tests/phone-country-utils.test.js index c158d3f..d123791 100644 --- a/tests/phone-country-utils.test.js +++ b/tests/phone-country-utils.test.js @@ -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' ); }); diff --git a/tests/platform-verify-step-resolution.test.js b/tests/platform-verify-step-resolution.test.js index efe637b..c2e4f60 100644 --- a/tests/platform-verify-step-resolution.test.js +++ b/tests/platform-verify-step-resolution.test.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 = {}) { diff --git a/tests/plus-account-access-strategy.test.js b/tests/plus-account-access-strategy.test.js index 10f2010..6959779 100644 --- a/tests/plus-account-access-strategy.test.js +++ b/tests/plus-account-access-strategy.test.js @@ -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', }` diff --git a/tests/plus-checkout-address-input.test.js b/tests/plus-checkout-address-input.test.js index f46868b..06e8a32 100644 --- a/tests/plus-checkout-address-input.test.js +++ b/tests/plus-checkout-address-input.test.js @@ -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(); diff --git a/tests/plus-checkout-billing-tab-resolution.test.js b/tests/plus-checkout-billing-tab-resolution.test.js index 790f33d..91f9e0b 100644 --- a/tests/plus-checkout-billing-tab-resolution.test.js +++ b/tests/plus-checkout-billing-tab-resolution.test.js @@ -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); } diff --git a/tests/plus-checkout-create-wait.test.js b/tests/plus-checkout-create-wait.test.js index 8f8b846..06ae2f2 100644 --- a/tests/plus-checkout-create-wait.test.js +++ b/tests/plus-checkout-create-wait.test.js @@ -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); diff --git a/tests/plus-return-confirm-wait.test.js b/tests/plus-return-confirm-wait.test.js index 7c377a4..cb23da5 100644 --- a/tests/plus-return-confirm-wait.test.js +++ b/tests/plus-return-confirm-wait.test.js @@ -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); diff --git a/tests/sidepanel-auto-run-content-refresh.test.js b/tests/sidepanel-auto-run-content-refresh.test.js index 25b3734..9fcd644 100644 --- a/tests/sidepanel-auto-run-content-refresh.test.js +++ b/tests/sidepanel-auto-run-content-refresh.test.js @@ -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, diff --git a/tests/sidepanel-contribution-mode-flow-scope.test.js b/tests/sidepanel-contribution-mode-flow-scope.test.js index 2edd842..776e7ef 100644 --- a/tests/sidepanel-contribution-mode-flow-scope.test.js +++ b/tests/sidepanel-contribution-mode-flow-scope.test.js @@ -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() { diff --git a/tests/sidepanel-contribution-mode.test.js b/tests/sidepanel-contribution-mode.test.js index 1e8995b..e755884 100644 --- a/tests/sidepanel-contribution-mode.test.js +++ b/tests/sidepanel-contribution-mode.test.js @@ -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'); diff --git a/tests/sidepanel-flow-source-registry.test.js b/tests/sidepanel-flow-source-registry.test.js index 397d1ae..67cf0e6 100644 --- a/tests/sidepanel-flow-source-registry.test.js +++ b/tests/sidepanel-flow-source-registry.test.js @@ -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', }; } diff --git a/tests/sidepanel-phone-verification-settings.test.js b/tests/sidepanel-phone-verification-settings.test.js index 6ba1299..376ebca 100644 --- a/tests/sidepanel-phone-verification-settings.test.js +++ b/tests/sidepanel-phone-verification-settings.test.js @@ -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')} diff --git a/tests/sidepanel-sub2api-priority-settings.test.js b/tests/sidepanel-sub2api-priority-settings.test.js index 2b48386..7461136 100644 --- a/tests/sidepanel-sub2api-priority-settings.test.js +++ b/tests/sidepanel-sub2api-priority-settings.test.js @@ -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, diff --git a/tests/signup-entry-diagnostics.test.js b/tests/signup-entry-diagnostics.test.js index a0ff73b..5d880e6 100644 --- a/tests/signup-entry-diagnostics.test.js +++ b/tests/signup-entry-diagnostics.test.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('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}(`]; diff --git a/tests/signup-step2-email-switch.test.js b/tests/signup-step2-email-switch.test.js index 3c0e5ae..146e2c1 100644 --- a/tests/signup-step2-email-switch.test.js +++ b/tests/signup-step2-email-switch.test.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('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}(`]; diff --git a/tests/signup-verification-state-guard.test.js b/tests/signup-verification-state-guard.test.js index ec3f101..3f1c065 100644 --- a/tests/signup-verification-state-guard.test.js +++ b/tests/signup-verification-state-guard.test.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('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}(`]; diff --git a/tests/source-registry-module.test.js b/tests/source-registry-module.test.js index 36e90c8..7518426 100644 --- a/tests/source-registry-module.test.js +++ b/tests/source-registry-module.test.js @@ -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); }); diff --git a/tests/step-definitions-module.test.js b/tests/step-definitions-module.test.js index e72708e..b134f6c 100644 --- a/tests/step-definitions-module.test.js +++ b/tests/step-definitions-module.test.js @@ -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', diff --git a/tests/step3-direct-complete.test.js b/tests/step3-direct-complete.test.js index 08bb771..aa2290e 100644 --- a/tests/step3-direct-complete.test.js +++ b/tests/step3-direct-complete.test.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('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}(`]; diff --git a/tests/step4-split-code-submit.test.js b/tests/step4-split-code-submit.test.js index 384f15c..63f0638 100644 --- a/tests/step4-split-code-submit.test.js +++ b/tests/step4-split-code-submit.test.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('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}(`]; diff --git a/tests/step4-submit-retry-recovery.test.js b/tests/step4-submit-retry-recovery.test.js index c5d856f..0035e17 100644 --- a/tests/step4-submit-retry-recovery.test.js +++ b/tests/step4-submit-retry-recovery.test.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('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}(`]; diff --git a/tests/step5-age-consent.test.js b/tests/step5-age-consent.test.js index 82107b7..72fabe6 100644 --- a/tests/step5-age-consent.test.js +++ b/tests/step5-age-consent.test.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('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}(`]; diff --git a/tests/step5-direct-complete.test.js b/tests/step5-direct-complete.test.js index d561dcd..deafa0a 100644 --- a/tests/step5-direct-complete.test.js +++ b/tests/step5-direct-complete.test.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('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}(`]; diff --git a/tests/step6-login-state.test.js b/tests/step6-login-state.test.js index 2ed170d..b1d4630 100644 --- a/tests/step6-login-state.test.js +++ b/tests/step6-login-state.test.js @@ -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}(`]; diff --git a/tests/step6-oauth-consent-skip.test.js b/tests/step6-oauth-consent-skip.test.js index d8ad6bb..a7a1ce6 100644 --- a/tests/step6-oauth-consent-skip.test.js +++ b/tests/step6-oauth-consent-skip.test.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('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}(`]; diff --git a/tests/step6-passwordless-otp-login.test.js b/tests/step6-passwordless-otp-login.test.js index c28d8ad..4b84305 100644 --- a/tests/step6-passwordless-otp-login.test.js +++ b/tests/step6-passwordless-otp-login.test.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('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}(`]; diff --git a/tests/step6-timeout-recovery.test.js b/tests/step6-timeout-recovery.test.js index f6e5d01..08e7306 100644 --- a/tests/step6-timeout-recovery.test.js +++ b/tests/step6-timeout-recovery.test.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('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}(`]; diff --git a/tests/step7-phone-login-entry.test.js b/tests/step7-phone-login-entry.test.js index 6dad645..0455bbb 100644 --- a/tests/step7-phone-login-entry.test.js +++ b/tests/step7-phone-login-entry.test.js @@ -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}(`]; diff --git a/tests/step8-restart-step7-error.test.js b/tests/step8-restart-step7-error.test.js index bd0764c..17d7dcd 100644 --- a/tests/step8-restart-step7-error.test.js +++ b/tests/step8-restart-step7-error.test.js @@ -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); diff --git a/tests/step8-state-timeout-retry.test.js b/tests/step8-state-timeout-retry.test.js index 2d23789..5a8dcd4 100644 --- a/tests/step8-state-timeout-retry.test.js +++ b/tests/step8-state-timeout-retry.test.js @@ -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, '普通内容脚本超时也应沿用可重试分支' ); diff --git a/tests/step8-stop-cleanup.test.js b/tests/step8-stop-cleanup.test.js index 1aae758..44322c4 100644 --- a/tests/step8-stop-cleanup.test.js +++ b/tests/step8-stop-cleanup.test.js @@ -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' }; diff --git a/tests/step9-localhost-cleanup-scope.test.js b/tests/step9-localhost-cleanup-scope.test.js index 55d5bfd..ae9b0e4 100644 --- a/tests/step9-localhost-cleanup-scope.test.js +++ b/tests/step9-localhost-cleanup-scope.test.js @@ -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({ diff --git a/tests/step9-oauth-timeout-toggle.test.js b/tests/step9-oauth-timeout-toggle.test.js index 1a5ad6a..d5ce2ca 100644 --- a/tests/step9-oauth-timeout-toggle.test.js +++ b/tests/step9-oauth-timeout-toggle.test.js @@ -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', ` diff --git a/tests/step9-profile-after-phone-verification.test.js b/tests/step9-profile-after-phone-verification.test.js index 622cba2..a0fc1f4 100644 --- a/tests/step9-profile-after-phone-verification.test.js +++ b/tests/step9-profile-after-phone-verification.test.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('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}(`]; diff --git a/tests/step9-status-diagnostics.test.js b/tests/step9-status-diagnostics.test.js index f73d67e..8c39422 100644 --- a/tests/step9-status-diagnostics.test.js +++ b/tests/step9-status-diagnostics.test.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('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}(`]; diff --git a/tests/step9-timeout-recovery.test.js b/tests/step9-timeout-recovery.test.js index 6002e02..4d77463 100644 --- a/tests/step9-timeout-recovery.test.js +++ b/tests/step9-timeout-recovery.test.js @@ -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 = {}; diff --git a/tests/sub2api-panel-proxy.test.js b/tests/sub2api-panel-proxy.test.js index e215821..3db96e2 100644 --- a/tests/sub2api-panel-proxy.test.js +++ b/tests/sub2api-panel-proxy.test.js @@ -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; } diff --git a/tests/verification-flow-polling.test.js b/tests/verification-flow-polling.test.js index f7da2a8..8e9d21c 100644 --- a/tests/verification-flow-polling.test.js +++ b/tests/verification-flow-polling.test.js @@ -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({ diff --git a/项目完整链路说明.md b/项目完整链路说明.md index d6d2756..c12025e 100644 --- a/项目完整链路说明.md +++ b/项目完整链路说明.md @@ -1,4 +1,4 @@ -# 项目完整链路说明 +# 项目完整链路说明 本文档面向 AI 与开发者,目标是让阅读者在最短时间内理解“项目做什么、怎么跑、数据怎么流、功能链路怎么串”,从而在新增功能时不漏逻辑、不误改边界。 @@ -124,10 +124,10 @@ ### 3.3 Flow / Source 注册 -[shared/flow-registry.js](./shared/flow-registry.js) 定义 flow、来源、可见设置分组、runtime source 与 driver。 -[shared/source-registry.js](./shared/source-registry.js) 负责把 flow runtime source 与共享 mail source 合并成统一来源视图。 -[shared/flow-capabilities.js](./shared/flow-capabilities.js) 负责把当前 flow/source 解析成 sidepanel 的能力矩阵。 -[shared/settings-schema.js](./shared/settings-schema.js) 负责把旧平铺字段与新的 `flows.*` 配置结构互转。 +[core/flow-kernel/flow-registry.js](./core/flow-kernel/flow-registry.js) 定义 flow、来源、可见设置分组、runtime source 与 driver。 +[core/flow-kernel/source-registry.js](./core/flow-kernel/source-registry.js) 负责把 flow runtime source 与共享 mail source 合并成统一来源视图。 +[core/flow-kernel/flow-capabilities.js](./core/flow-kernel/flow-capabilities.js) 负责把当前 flow/source 解析成 sidepanel 的能力矩阵。 +[core/flow-kernel/settings-schema.js](./core/flow-kernel/settings-schema.js) 负责把旧平铺字段与新的 `flows.*` 配置结构互转。 这意味着: @@ -138,14 +138,14 @@ ### 3.3.1 Workflow 与运行态基础设施 -[background/workflow-engine.js](./background/workflow-engine.js) 负责把当前 flow 的 workflow 节点定义变成统一的执行视图,包括: +[core/flow-kernel/workflow-engine.js](./core/flow-kernel/workflow-engine.js) 负责把当前 flow 的 workflow 节点定义变成统一的执行视图,包括: - 节点默认状态 `pending` - 当前 workflow 的节点顺序与 `nodeId` - `next` 后继节点解析 - `getFirstUnfinishedNodeId / getRunningNodeIds / hasSavedProgress` 这类自动运行与恢复入口共享判断 -[background/runtime-state.js](./background/runtime-state.js) 则负责把 session 运行态整理成统一的结构化视图与 patch 入口: +[core/flow-kernel/runtime-state.js](./core/flow-kernel/runtime-state.js) 则负责把 session 运行态整理成统一的结构化视图与 patch 入口: - `runtimeState`:当前运行中的 node、nodeStatuses、activeRunId 等核心执行态 - `sharedState`:automation window、tab registry、最近来源 URL 这类跨 flow 共享运行态 @@ -157,7 +157,7 @@ ### 3.4 步骤注册 [data/step-definitions.js](./data/step-definitions.js) 提供共享步骤元数据,并按当前 `activeFlowId` 输出 flow-specific workflow。`openai` flow 会继续按 `plusPaymentMethod / signupMethod / plusAccountAccessStrategy` 动态解析步骤标题与节点集合:当 Plus 模式为邮箱注册且来源支持时,原本的 OAuth 尾链可以被替换成 `sub2api-session-import` 或 `cpa-session-import`;`kiro` flow 则输出固定的 9 节点注册/桌面授权/上传 workflow。 -[background/steps/registry.js](./background/steps/registry.js) 负责把“workflow node 元数据”映射到“步骤执行器”,同时保留 `flowId / nodeId / displayOrder / executeKey` 等稳定节点元数据。 +[core/flow-kernel/step-registry.js](./core/flow-kernel/step-registry.js) 负责把“workflow node 元数据”映射到“步骤执行器”,同时保留 `flowId / nodeId / displayOrder / executeKey` 等稳定节点元数据。 这意味着: @@ -244,7 +244,7 @@ 补充: -- `background/runtime-state.js` 会把会话运行态统一整理成 `runtimeState / sharedState / serviceState / flowState` 四层结构;当前顶层那些 `currentNodeId / nodeStatuses / automationWindowId / ipProxyApplied* / flowId / runId` 等兼容字段,都是这份结构化运行态的投影视图,不应再当成另一套平行状态机维护。 +- `core/flow-kernel/runtime-state.js` 会把会话运行态统一整理成 `runtimeState / sharedState / serviceState / flowState` 四层结构;当前顶层那些 `currentNodeId / nodeStatuses / automationWindowId / ipProxyApplied* / flowId / runId` 等兼容字段,都是这份结构化运行态的投影视图,不应再当成另一套平行状态机维护。 - `automationWindowId` 会同时存在于顶层兼容字段与 `runtimeState.sharedState` 视图中;真正目的是约束自动化只在当前 sidepanel 所在窗口里继续接管标签页,而不是把不同窗口里的同类页面串到一起。 - `content/mail-2925.js` 会把“当前验证码步骤启动会话”对应的已试验证码写入 `chrome.storage.session`,键为 `seen2925CodeState`;同一个 Step 4 / Step 8 会话里试过的验证码不会再次提交,新会话开始时会重置。 @@ -366,7 +366,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 ### 5.2 队列与重试 -[background/tab-runtime.js](./background/tab-runtime.js) 负责: +[core/flow-kernel/tab-runtime.js](./core/flow-kernel/tab-runtime.js) 负责: - `queueCommand` - `flushCommand` @@ -386,7 +386,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 当前接入方式: -- 它会在 `content/utils.js` 之后注入到需要真实页面交互的 bundle,例如 `signup-page`、`plus-checkout`、`paypal-flow`、`gopay-flow`,以及 `mail-2925` 这类仍有页面级点击/清理动作的链路。 +- 它会在 `content/utils.js` 之后注入到需要真实页面交互的 bundle,例如 `openai-auth`、`plus-checkout`、`paypal-flow`、`gopay-flow`,以及 `mail-2925` 这类仍有页面级点击/清理动作的链路。 - 它主要包装输入、选择、点击、提交与短等待这类页面内操作;当前默认节奏为 2 秒。 - `confirm-oauth`、`platform-verify` 这类后台节点不走这一层;邮箱 API 轮询、短信 API 轮询、WhatsApp 读码和其他纯后台请求也不应被硬塞进 2 秒延迟。 - `operationDelayEnabled` 这个持久字段现在仍会被内容脚本监听,但对外语义已经等价于“始终启用”,保留它主要是为了兼容旧配置与统一注入同步。 @@ -397,8 +397,8 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 文件: -- [background/steps/open-chatgpt.js](./background/steps/open-chatgpt.js) -- [content/signup-page.js](./content/signup-page.js) +- [flows/openai/background/steps/open-chatgpt.js](./flows/openai/background/steps/open-chatgpt.js) +- [flows/openai/content/openai-auth.js](./flows/openai/content/openai-auth.js) 流程: @@ -415,7 +415,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 文件: -- [background/steps/submit-signup-email.js](./background/steps/submit-signup-email.js) +- [flows/openai/background/steps/submit-signup-email.js](./flows/openai/background/steps/submit-signup-email.js) 流程: @@ -425,7 +425,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 4. 内容脚本识别到官网“免费注册 / Sign up / Register”入口后,会先等待 3 秒再点击;如果点击后仍停留在官网入口页、没有进入邮箱或手机号输入页,会再次等待 3 秒后重试,最多额外重试 5 次,且每次真正点击前都会检查 Stop 状态 5. 邮箱注册时,如果注册弹窗默认停留在手机号输入模式,会先尝试点击 `继续使用电子邮件地址登录 / Continue using email address` 一类按钮切回邮箱输入模式 6. 邮箱注册分支:提交邮箱;邮箱输入框识别同时兼容本地化占位与 `aria-label` -7. 手机号注册分支:先点击官网“免费注册”并切换到手机号注册入口,必要时展开更多选项;确认手机号输入页就绪后,按“已有 signup activation > 手动填写的运行态手机号 > 接码平台新申请号码”的顺序决定手机号来源,避免重复买号。内容脚本会通过 `content/phone-country-utils.js` 共享工具按号码最长区号和平台国家名切换手机号国家下拉框,兼容 `+(44)` / `(+44)` / `+44` 等页面文案;必须确认可视下拉按钮的区号已同步后才填写并提交,若仍显示旧国家则停止提交 +7. 手机号注册分支:先点击官网“免费注册”并切换到手机号注册入口,必要时展开更多选项;确认手机号输入页就绪后,按“已有 signup activation > 手动填写的运行态手机号 > 接码平台新申请号码”的顺序决定手机号来源,避免重复买号。内容脚本会通过 `flows/openai/content/phone-country-utils.js` 共享工具按号码最长区号和平台国家名切换手机号国家下拉框,兼容 `+(44)` / `(+44)` / `+44` 等页面文案;必须确认可视下拉按钮的区号已同步后才填写并提交,若仍显示旧国家则停止提交 8. 以当前统一账号标识先写入一条“停止(流程尚未完成)”的记录占位;邮箱账号占位邮箱,phone-only 账号占位手机号 9. 等待身份提交后的真实落地页 10. 如果进入密码页,则继续执行 Step 3 @@ -435,7 +435,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 文件: -- [background/steps/fill-password.js](./background/steps/fill-password.js) +- [flows/openai/background/steps/fill-password.js](./flows/openai/background/steps/fill-password.js) 流程: @@ -460,8 +460,8 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 文件: -- [background/steps/fetch-signup-code.js](./background/steps/fetch-signup-code.js) -- [background/steps/fetch-login-code.js](./background/steps/fetch-login-code.js) +- [flows/openai/background/steps/fetch-signup-code.js](./flows/openai/background/steps/fetch-signup-code.js) +- [flows/openai/background/steps/fetch-login-code.js](./flows/openai/background/steps/fetch-login-code.js) - [background/verification-flow.js](./background/verification-flow.js) 这两步共享验证码主流程: @@ -489,7 +489,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 - `2925` 内容脚本会把每一封实际打开检测的邮件立即删除;同一验证码步骤启动后,试过的验证码会按“步骤 ID + 启动时间”隔离缓存,不会在本次步骤里重复提交;如果再次遇到相同验证码,对应邮件也会在读取后立即删除,避免后续反复打开。 - `2925` 在 provide 模式下仍保持宽松匹配:只要邮件内容命中 ChatGPT / OpenAI 验证码过滤条件,就会尝试该邮件。 - `2925` 在 receive 模式下会恢复“弱目标邮箱匹配”:只有当邮件里显式出现了其他收件邮箱时才会跳过;如果邮件里没有明确写出邮箱,仍允许继续尝试该验证码。 -- 手动点击 Step 4 重新执行时,后台会先检查 `signup-page` 认证页标签是否仍然存在;如果步骤 1 / 2 打开的认证页已经关闭,就会直接提示“请先执行步骤 1 或步骤 2,确保认证页仍然打开并停留在验证码页”,不会先重置后续步骤再报技术错误。 +- 手动点击 Step 4 重新执行时,后台会先检查 `openai-auth` 认证页标签是否仍然存在;如果步骤 1 / 2 打开的认证页已经关闭,就会直接提示“请先执行步骤 1 或步骤 2,确保认证页仍然打开并停留在验证码页”,不会先重置后续步骤再报技术错误。 - 当验证码最终提交成功后,后台会异步向 2925 邮箱页发送 `DELETE_ALL_EMAILS`,执行“全选 + 删除”清理剩余邮件,不阻塞主流程。 - 如果 `2925` 邮箱页在轮询期间出现“子邮箱已达上限邮箱”,后台会记录当前时间,把当前 2925 账号禁用 24 小时,自动切到下一个可用账号并完成登录,然后直接报错结束当前尝试;如果 Auto 开启了自动重试,现有控制器会按原逻辑进入下一次尝试。 @@ -499,7 +499,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 文件: -- [background/steps/fill-profile.js](./background/steps/fill-profile.js) +- [flows/openai/background/steps/fill-profile.js](./flows/openai/background/steps/fill-profile.js) 流程: @@ -513,7 +513,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 文件: -- [background/steps/wait-registration-success.js](./background/steps/wait-registration-success.js) +- [flows/openai/background/steps/wait-registration-success.js](./flows/openai/background/steps/wait-registration-success.js) 流程: @@ -526,7 +526,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 文件: -- [background/steps/oauth-login.js](./background/steps/oauth-login.js) +- [flows/openai/background/steps/oauth-login.js](./flows/openai/background/steps/oauth-login.js) 流程: @@ -554,7 +554,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 文件: -- [background/steps/fetch-login-code.js](./background/steps/fetch-login-code.js) +- [flows/openai/background/steps/fetch-login-code.js](./flows/openai/background/steps/fetch-login-code.js) 流程: @@ -589,7 +589,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 文件: -- [background/steps/confirm-oauth.js](./background/steps/confirm-oauth.js) +- [flows/openai/background/steps/confirm-oauth.js](./flows/openai/background/steps/confirm-oauth.js) 流程: @@ -615,7 +615,7 @@ IP 代理模块在同步、切换、Change、出口探测和自动运行成功 文件: -- [background/steps/platform-verify.js](./background/steps/platform-verify.js) +- [flows/openai/background/steps/platform-verify.js](./flows/openai/background/steps/platform-verify.js) 说明: @@ -646,7 +646,7 @@ SUB2API 补充: - 步骤 7 直接调用 `POST /api/v1/auth/login` 登录管理接口,再调用 `/api/v1/admin/groups/all` 校验目标 openai 分组;如果配置了默认代理,会读取 `/api/v1/admin/proxies/all?with_count=true` 并按名称或 ID 选择代理。 - 步骤 7 调用 `POST /api/v1/admin/openai/generate-auth-url` 获取 `auth_url / session_id / state`,并把目标分组 ID、代理 ID、草稿账号名写入运行态。 - 步骤 10,以及 Plus 仍走 OAuth 尾链时的 `platform-verify` 节点,会直接用 localhost callback 中的 `code / state` 调用 `POST /api/v1/admin/openai/exchange-code`,再用返回的 OpenAI token 信息调用 `POST /api/v1/admin/accounts` 创建 SUB2API 账号。 -- SUB2API 主链路不再打开后台页面,也不再依赖 `content/sub2api-panel.js` 的 DOM 自动化;该内容脚本仅保留为旧后台页面路径的兼容能力。 +- SUB2API 主链路不再打开后台页面,也不再依赖 `flows/openai/content/sub2api-panel.js` 的 DOM 自动化;该内容脚本仅保留为旧后台页面路径的兼容能力。 贡献模式补充: @@ -1159,8 +1159,8 @@ Hide My Email 获取与管理链路: 1. [data/step-definitions.js](./data/step-definitions.js) 2. [background/steps](./background/steps) -3. [background/steps/registry.js](./background/steps/registry.js) -4. 如果步骤属于新 flow,还必须同步检查 [shared/flow-registry.js](./shared/flow-registry.js) 与 [shared/settings-schema.js](./shared/settings-schema.js) +3. [core/flow-kernel/step-registry.js](./core/flow-kernel/step-registry.js) +4. 如果步骤属于新 flow,还必须同步检查 [core/flow-kernel/flow-registry.js](./core/flow-kernel/flow-registry.js) 与 [core/flow-kernel/settings-schema.js](./core/flow-kernel/settings-schema.js) 4. 自动运行链路是否需要纳入 5. Step 状态传播和侧边栏展示是否需要适配 6. 测试是否要补 @@ -1204,7 +1204,7 @@ Hide My Email 获取与管理链路: 更新 [项目开发规范(AI协作).md](./项目开发规范(AI协作).md) ## 2026-04 链路补充:认证页共享恢复 -- 新增共享恢复层:`content/auth-page-recovery.js`。 +- 新增共享恢复层:`flows/openai/content/auth-page-recovery.js`。 - Step 4 在等待注册验证码页时,如果命中认证页 `Try again / 重试` 页,或 `/email-verification` 上的 `405 / Route Error` 重试页,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再继续回到密码页重提和验证码页确认流程。 - 但如果 Step 4 的认证重试页正文中出现 `user_already_exists`,则会直接视为“当前用户已存在”:不点击 `重试`,不再回到步骤 1 重开当前轮,而是立即结束当前轮;开启自动重试时直接进入下一轮。 - Step 7 在识别到登录超时报错页时,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复当前页面;恢复成功后会优先按当前页面状态继续当前登录流程,例如直接续跑邮箱页或密码页;只有仍未恢复到可继续状态时,才按原有可恢复失败逻辑重跑 Step 7。 @@ -1219,3 +1219,4 @@ Hide My Email 获取与管理链路: - `2925` 在 Step 4 / Step 8 现在会携带固定的步骤开始时间窗口,实际筛选下限为“步骤开始时间向前回看 10 分钟”。 - 为了保留这段固定时间窗内已经到达的验证码邮件,后台不再在轮询开始前预先清空 2925 邮箱。 - `2925` 验证码最终提交成功后,后台仍会异步发送 `DELETE_ALL_EMAILS` 做收尾清理。 + diff --git a/项目开发规范(AI协作).md b/项目开发规范(AI协作).md index e8da894..1b56777 100644 --- a/项目开发规范(AI协作).md +++ b/项目开发规范(AI协作).md @@ -93,7 +93,7 @@ - 步骤文件应优先使用语义化名称,不再使用 `stepX.js` 命名。 - 步骤顺序统一由: - [data/step-definitions.js](./data/step-definitions.js) - - [background/steps/registry.js](./background/steps/registry.js) + - [core/flow-kernel/step-registry.js](./core/flow-kernel/step-registry.js) 共同管理。 - 步骤显示和执行必须以 `key / nodeId` 为主,不得重新回到硬编码步骤号驱动流程的写法。 - 同一个可见步骤号在不同模式下可能代表不同业务位置;日志、完成信号、错误恢复和 UI 渲染必须按当前步骤定义解析。 @@ -230,7 +230,7 @@ - 如果新增来源本身已经提供稳定的后台协议接口,可以直接走协议分支接入: - 步骤 7 通过 `background/panel-bridge.js` 生成 `auth_url` - - 步骤 10 通过 `background/steps/platform-verify.js` 直接提交 localhost callback + - 步骤 10 通过 `flows/openai/background/steps/platform-verify.js` 直接提交 localhost callback - 这类来源优先复用现有 OpenAI 授权页与 localhost callback 主链,不要为了“看起来统一”再额外新增一套页面 DOM 自动点击内容脚本。 - 只有当目标来源没有可用协议接口、必须依赖后台页面按钮时,才新增对应的 panel content script。 @@ -304,10 +304,10 @@ - session import 节点必须直接完成目标平台接入,不得再经过 `platform-verify`,也不得混入普通 OAuth callback 状态机。 - 相关改动必须同步检查: - `data/step-definitions.js` - - `shared/flow-capabilities.js` - - `shared/settings-schema.js` + - `core/flow-kernel/flow-capabilities.js` + - `core/flow-kernel/settings-schema.js` - `background/message-router.js` - - 对应 session import executor 与 `background/steps/platform-verify.js` + - 对应 session import executor 与 `flows/openai/background/steps/platform-verify.js` - `sidepanel/sidepanel.html` 与 `sidepanel/sidepanel.js` - 手动跳过、自动运行、最终完成节点判断、日志 step 映射与测试 diff --git a/项目文件结构说明.md b/项目文件结构说明.md index 6b41410..6e32bd4 100644 --- a/项目文件结构说明.md +++ b/项目文件结构说明.md @@ -1,4 +1,4 @@ -# 项目文件结构说明 +# 项目文件结构说明 本文档列出当前仓库中所有“非忽略文件”,并说明每个文件的作用。 @@ -54,11 +54,11 @@ - `background/cloudmail-provider.js`:Cloud Mail / SkyMail 后台 provider 模块,负责 Token 获取、邮箱创建、邮件列表读取、验证码轮询和生成/转发收件目标邮箱选择;新生成的 Cloud Mail 地址会统一走共享注册邮箱状态持久化,既回写带来源标记的注册邮箱运行态,也能在 Step 8 `add-email` 的手机号身份链路中保留 `signupPhone* / accountIdentifier*`。 - `background/yyds-mail-provider.js`:YYDS Mail 后台 provider 模块,负责通过 `X-API-Key` 创建临时邮箱、保存返回的地址与临时 Bearer token、按 `/messages` 与 `/messages/{id}` 读取验证码邮件,并在成功收尾或 reset 时清空当前运行态邮箱。 - `background/generated-email-helpers.js`:生成邮箱辅助层,除了 Duck / Cloudflare / iCloud / Cloudflare Temp Email,也统一承接 Gmail / 2925 的别名邮箱生成入口与自定义邮箱池读取;当 provider 为 2925 且 `mail2925Mode = provide` 时,会先确保当前账号池里已有可用账号,再基于该账号生成别名邮箱;若 `mail2925Mode = receive`,则会回退到普通邮箱生成链路;当生成方式为 iCloud 且 `icloudFetchMode = always_new` 时,会强制跳过未用别名复用并新建别名;当生成方式为 `custom-pool` 时,会按当前目标轮次读取邮箱池中的对应邮箱;Duck 生成前会优先结合 `registrationEmailState` 与侧栏当前可见邮箱解析对比基线,避免重复拿到旧地址;所有生成结果都会统一走共享注册邮箱状态持久化,普通邮箱流仍切回邮箱身份,Step 8 `add-email` 的手机号身份流则保留当前手机号身份。 -- `background/kiro/state.js`:Kiro 独立运行态模型与状态工具,负责 `kiroRuntime.session / register / webAuth / desktopAuth / upload` 的默认值、归一化、节点完成回写、下游重置和 fresh-attempt keep-state 构建。 -- `background/kiro/register-runner.js`:Kiro 注册页执行器,负责步骤 1~6 的编排、Kiro / AWS Builder ID cookies 清理、打开 `https://app.kiro.dev/signin`、选择 Builder ID 入口、注册标签页管理、邮箱/姓名/验证码/密码/授权确认,以及 Kiro Web 登录态推进。 -- `background/kiro/desktop-client.js`:Kiro 桌面授权协议层,负责桌面 OIDC client 注册、PKCE 参数生成、授权地址组装、callback 参数校验和授权码换 token。 -- `background/kiro/desktop-authorize-runner.js`:Kiro 桌面授权执行器,负责步骤 7~8 的标签页打开、授权页轮询、localhost callback 捕获,以及桌面授权完成后的凭据落库。 -- `background/kiro/publisher-kiro-rs.js`:Kiro 发布器,负责 `kiro.rs` 地址归一化、连通性探测、BuilderId profileArn 固定映射、machineId 计算、上传 payload 构建与最终凭据上传。 +- `flows/kiro/background/state.js`:Kiro 独立运行态模型与状态工具,负责 `kiroRuntime.session / register / webAuth / desktopAuth / upload` 的默认值、归一化、节点完成回写、下游重置和 fresh-attempt keep-state 构建。 +- `flows/kiro/background/register-runner.js`:Kiro 注册页执行器,负责步骤 1~6 的编排、Kiro / AWS Builder ID cookies 清理、打开 `https://app.kiro.dev/signin`、选择 Builder ID 入口、注册标签页管理、邮箱/姓名/验证码/密码/授权确认,以及 Kiro Web 登录态推进。 +- `flows/kiro/background/desktop-client.js`:Kiro 桌面授权协议层,负责桌面 OIDC client 注册、PKCE 参数生成、授权地址组装、callback 参数校验和授权码换 token。 +- `flows/kiro/background/desktop-authorize-runner.js`:Kiro 桌面授权执行器,负责步骤 7~8 的标签页打开、授权页轮询、localhost callback 捕获,以及桌面授权完成后的凭据落库。 +- `flows/kiro/background/publisher-kiro-rs.js`:Kiro 发布器,负责 `kiro.rs` 地址归一化、连通性探测、BuilderId profileArn 固定映射、machineId 计算、上传 payload 构建与最终凭据上传。 - `background/ip-proxy-core.js`:IP 代理核心模块,负责代理条目解析、账号/接口代理池运行态、PAC 应用与清除、代理鉴权回填、出口探测、失败时的目标站点 fail-close 防漏规则,以及自动运行成功阈值后的代理切换支撑。 - `background/ip-proxy-provider-711proxy.js`:711Proxy provider 规则模块,负责从账号串中识别和写回 `region / session / sessTime` 等参数,并在固定账号模式下把侧栏配置转换为最终生效的代理账号。 - `background/logging-status.js`:后台日志、步骤状态、错误信息和若干状态判断的公共工具层;日志条目统一写入结构化 `step / stepKey`,sidepanel 只读取该元数据渲染步骤标签,不再从日志正文解析步骤号;当前额外承接 `add-phone / 手机号页` 这类认证 fatal 错误的共享判定,并会把 Step 2 的“手机号输入模式未切成功”与真正的 auth `add-phone` 页面区分开,避免自动运行误停机。 @@ -66,63 +66,63 @@ - `background/mail-rule-registry.js`:flow-aware 邮件规则注册表,负责按 `activeFlowId` 选择验证码规则构造器,并统一输出注册/登录验证码轮询 payload。 - `background/message-router.js`:后台消息路由层,负责处理 `chrome.runtime.onMessage` 进入的所有业务消息;`LOG / STEP_COMPLETE / STEP_ERROR` 会把消息里的真实 `step` 继续传给结构化日志,跳过登录验证码这类派生日志也按当前步骤 key 写入;当前额外接入 PayPal 账号池的新增与切换、2925 账号池的新增、导入、切换、登录、禁用与删除消息,以及 IP 代理的同步、切换、Change 与出口检测消息;手动改邮箱、手动取邮箱等入口也统一经这里落到 `setEmailState`,同步维护注册邮箱运行态;当侧栏关闭 `oauthFlowTimeoutEnabled` 时,会立即清空已存在的 OAuth 总预算 deadline;手动 `EXECUTE_NODE` 会先按 `stepExecutionRangeByFlow` 校验当前节点是否允许执行。 - `background/registration-email-state.js`:注册邮箱运行态共享模块,负责维护 `registrationEmailState = { current, previous, source, updatedAt }`,并提供“当前邮箱清空时保留上一比较基线”“Duck 生成前解析比较基线”“Step 8 `add-email` 写入邮箱时按需保留手机号身份”的统一工具。 -- `background/runtime-state.js`:运行态视图与 patch 构造模块,负责把会话字段归一到 `runtimeState / sharedState / serviceState / flowState` 结构,并维护 flow-aware 的 session patch、节点状态默认值与兼容视图输出。 +- `core/flow-kernel/runtime-state.js`:运行态视图与 patch 构造模块,负责把会话字段归一到 `runtimeState / sharedState / serviceState / flowState` 结构,并维护 flow-aware 的 session patch、节点状态默认值与兼容视图输出。 - `background/navigation-utils.js`:导航与 URL 判断工具层,负责 callback、入口页、CPA / SUB2API / Codex2API 地址归一化、来源标签页家族判断与步骤跳转相关判断。 - `background/paypal-account-store.js`:PayPal 账号池持久化模块,负责保存账号列表、切换当前账号,并把当前选中账号同步回兼容字段 `paypalEmail / paypalPassword`。 - `background/phone-verification-flow.js`:OpenAI 专属手机号验证码流程模块,负责复用 HeroSMS / 5sim / NexSMS 的取号、复用、轮询、重发、换号与收尾能力;既承接 OpenAI OAuth 后置 `add-phone / phone-verification` 页面,也承接 OpenAI 手机号注册 Step 4 和 Step 8 中真实出现的 `phone-verification` 页面,并保持 `signupPhone*` 运行态与 `currentPhoneActivation` 隔离;后续多注册 flow 架构中,该模块应收拢到 `flows/openai` 边界内,不作为 core 通用接码服务提前抽象;后置 add-phone 成功后会把已绑定手机号写入运行态 `phoneNumber`,供账号记录并入同一轮;提交后置手机号验证码时会一并透传可复用的资料页 payload,便于认证页偶发跳回资料页时继续补齐;该流程由调用方传入当前可见步骤号,手机号日志不会再固定显示普通模式 Step 9。 - `background/panel-bridge.js`:来源桥接层;CPA 继续封装页面打开、脚本注入和通信,SUB2API / Codex2API 则通过后台协议生成 OAuth 地址。 - `background/sub2api-api.js`:SUB2API 管理接口直连模块,负责后台登录 SUB2API、解析 openai 分组、按配置选择代理、生成 OpenAI OAuth 地址、交换 localhost callback 授权码并创建 SUB2API 账号;主链路不再依赖 SUB2API 后台页面 DOM 自动化。 - `background/signup-flow-helpers.js`:注册页辅助层,负责打开注册入口、Step 2 打开入口页后的加载完成与额外 3 秒稳定等待、等待密码页以及解析当前流程所用邮箱;在 Gmail 与 `2925 + provide` 模式下会优先复用已经存在且仍兼容的完整注册邮箱,只有不兼容或为空时才重新生成;当 provider 为 2925 且启用了 provide 模式下的号池时,会先确保账号池中已选中可用账号;若邮箱生成器在生成阶段已经写回运行态,这里不会再重复覆盖 `registrationEmailState`,但仍会在 Step 8 `add-email` 的手机号身份场景下通过共享持久化按需保留手机号身份。 -- `background/tab-runtime.js`:标签页与内容脚本运行时基础设施,封装标签注册、冲突清理、消息超时、注入重试与队列;内容脚本恢复等待日志支持 `logStep / logStepKey`,用于保持 Plus 复用步骤的日志标签正确;当前等待标签完成、等待标签完成并短暂稳定、注入后的短暂延迟和内容脚本重试等待都已做 Stop 感知,避免用户停止后后台还继续等待并恢复执行。 +- `core/flow-kernel/tab-runtime.js`:标签页与内容脚本运行时基础设施,封装标签注册、冲突清理、消息超时、注入重试与队列;内容脚本恢复等待日志支持 `logStep / logStepKey`,用于保持 Plus 复用步骤的日志标签正确;当前等待标签完成、等待标签完成并短暂稳定、注入后的短暂延迟和内容脚本重试等待都已做 Stop 感知,避免用户停止后后台还继续等待并恢复执行。 - `background/verification-flow.js`:注册/登录验证码共享流程层,封装重发、轮询、提交、失败回退、自定义邮箱跳过、共享验证码自动重发次数配置、Cloud Mail / YYDS Mail API 轮询以及 2925 长轮询参数;日志步骤号使用 `completionStep`,因此在 Plus 仍走 OAuth 尾链的场景下,登录验证码会显示当前可见步骤而不是内部复用的 Step 8;当前验证码提交重试上限为 15 次,2925 每次重发验证码之间都会固定跑完一轮 15 次邮箱刷新轮询;若 `mail2925Mode = receive`,会额外把目标注册邮箱传给 2925 内容脚本做弱匹配;若登录验证码提交后页面转入 `add-phone / 手机号页`,则会把“后续需要手机号验证”的结果继续传给 OAuth 确认步骤,而不是误判为普通失败;若登录验证码提交后通信中断但认证页已进入 OAuth 同意页或手机号验证页,会按真实页面状态继续后续流程;若 2925 轮询命中“子邮箱已达上限邮箱”,会转入 2925 会话模块执行“记录时间、禁用 24 小时、切下一个账号并重新登录”,然后直接结束当前尝试。 -- `background/workflow-engine.js`:workflow 节点状态引擎,负责按当前 flow 解析节点列表、默认状态、后继节点、首个未完成节点和运行中节点,并为自动运行、恢复与手动执行提供统一 node 视图。 +- `core/flow-kernel/workflow-engine.js`:workflow 节点状态引擎,负责按当前 flow 解析节点列表、默认状态、后继节点、首个未完成节点和运行中节点,并为自动运行、恢复与手动执行提供统一 node 视图。 ## `background/steps/` -- `background/steps/wait-registration-success.js`:步骤 6 实现,负责注册资料提交后等待 20 秒,让注册成功状态和页面跳转稳定;默认不清理 cookies,只有侧栏第六步 `清 Cookies` 开关开启时才会在等待结束后清理 ChatGPT / OpenAI 相关 cookies。 -- `background/steps/confirm-oauth.js`:步骤 9 实现,负责 OAuth 同意页按钮定位、点击、localhost 回调监听与回调完成;等待 callback 期间会动态检查 OAuth 总预算开关,关闭后仅保留本地回调等待超时。 -- `background/steps/create-plus-checkout.js`:Plus 模式第 6 步实现,负责在已登录 ChatGPT 页面创建 PayPal / GoPay checkout session,或在 GPC 模式下读取 accessToken 并通过 `https://gpc.qlhazycoder.top/api/gp/tasks` 创建队列任务,记录 checkout / GPC task 运行态。 -- `background/steps/fetch-login-code.js`:步骤 8 实现,负责按真实认证页状态分发登录后续流程:邮箱验证码页走邮箱轮询、验证码回填与回退控制;真实 `phone-verification` 页才复用 OpenAI 专属手机号验证码流程;手机号注册登录后若进入 `add-email`,会先按共享邮箱状态持久化规则生成/解析邮箱并提交绑定,在不丢失当前手机号身份的前提下再进入邮箱验证码轮询;邮箱分支对非 2925 provider 会固定当前验证码页显示邮箱作为本次 Step 8 的目标邮箱,当 provider 为 2925 时,会在轮询前先确保当前 2925 账号已自动登录;命中 `email_in_use` 时会仅清空当前邮箱并保留上一轮比较基线,再在当前 Step 8 内重开 `add-email` 获取新邮箱,`max_check_attempts` 也只重启当前步骤恢复链。 -- `background/steps/fetch-signup-code.js`:步骤 4 实现,负责注册验证码阶段的页面准备与验证码流程入口;邮箱注册继续走邮箱验证码,手机号注册改走短信验证码;当 provider 为 2925 时,会在邮箱轮询前先确保当前 2925 账号已自动登录。 -- `background/steps/fill-plus-checkout.js`:Plus 模式第 7 步实现,负责驱动 PayPal / GoPay checkout 页面选择付款方式、填写账单地址、提交订阅并等待跳转;GPC 模式则轮询队列任务,按远端 `api_waiting_for` 提交 OTP / PIN,并在任务失败、过期或取消时清理运行态。 -- `background/steps/gopay-manual-confirm.js`:GoPay 手动确认节点,负责在需要人工完成订阅时发布待确认运行态,并按当前 Plus 尾链动态生成“继续 OAuth / 继续导入 SUB2API / 继续导入 CPA”的提示文案。 -- `background/steps/gopay-approve.js`:GoPay 自动审批节点,负责接管 GoPay / GPC 相关标签页、识别 OTP / PIN / Pay now / Hubungkan 等页面状态,并在支付页失效时回退到重新创建 checkout。 -- `background/steps/fill-password.js`:步骤 3 实现,负责密码生成、保存、回填与提交;当前已改为身份中立,手机号注册遇到无密码页时会按页面状态自动跳过。 -- `background/steps/fill-profile.js`:步骤 5 实现,负责姓名、生日填写并把资料提交给注册页内容脚本。 -- `background/steps/oauth-login.js`:步骤 7 实现,负责刷新 OAuth 链接、登录和确保进入验证码页;当前会根据 `accountIdentifierType / accountIdentifier` 选择邮箱或手机号登录,手机号账号会先探测并切换手机号登录入口;普通可恢复登录态失败会按上限重试,但一旦识别到认证流程进入 `add-phone / 手机号页`,会立即退出步骤 7 内部重试。 -- `background/steps/open-chatgpt.js`:步骤 1 实现,负责打开 ChatGPT 官网并确认入口就绪。 -- `background/steps/paypal-approve.js`:Plus 模式第 8 步实现,负责驱动 PayPal 登录、关闭可见通行密钥提示、点击“同意并继续”,并等待授权流程离开 PayPal 页面。 -- `background/steps/cpa-session-import.js`:Plus 模式下的 CPA 会话导入尾节点,负责定位当前已登录的 ChatGPT / OpenAI 标签页、读取会话与 accessToken,并调用 `background/cpa-api.js` 直接导入 CPA。 -- `background/steps/platform-verify.js`:平台回调验证实现,普通模式为步骤 10;Plus 场景下只有仍走 OAuth 尾链时才会出现该节点,并按当前可见 `platform-verify` 步骤上报(常见为第 13 步);负责 CPA 页面回调验证、SUB2API 管理接口 callback code/state 交换与账号创建,以及 Codex2API 的协议式 callback code/state 交换。 -- `background/steps/plus-return-confirm.js`:Plus 模式第 9 步实现,负责等待 PayPal 授权后回跳到 ChatGPT / OpenAI 页面,页面加载完成后固定等待 1 秒再完成。 -- `background/steps/sub2api-session-import.js`:Plus 模式下的 SUB2API 会话导入尾节点,负责读取当前 ChatGPT 会话,并调用 `background/sub2api-api.js` 的 `codex-session` 导入接口直接创建或更新 SUB2API 账号。 -- `background/steps/registry.js`:节点注册表工厂,负责保留 `flowId / nodeId / displayOrder / executeKey` 等稳定节点元数据,并把 flow-specific workflow node 映射到后台执行器。 -- `background/steps/submit-signup-email.js`:步骤 2 实现,负责按 `signupMethod` 在邮箱注册与手机号注册之间分发;切回已有注册页标签时会先等待加载完成并额外稳定 3 秒,再检查注册入口状态;邮箱分支会先点击官网“免费注册”再提交邮箱并清理旧手机号注册运行态,手机号分支会先点击官网“免费注册”并切到手机号注册入口,确认手机号输入页就绪后优先复用已有 signup activation,其次使用手动运行态手机号,最后才申请接码平台号码并提交,最后根据落地页判断是否跳过后续密码步骤。 +- `flows/openai/background/steps/wait-registration-success.js`:步骤 6 实现,负责注册资料提交后等待 20 秒,让注册成功状态和页面跳转稳定;默认不清理 cookies,只有侧栏第六步 `清 Cookies` 开关开启时才会在等待结束后清理 ChatGPT / OpenAI 相关 cookies。 +- `flows/openai/background/steps/confirm-oauth.js`:步骤 9 实现,负责 OAuth 同意页按钮定位、点击、localhost 回调监听与回调完成;等待 callback 期间会动态检查 OAuth 总预算开关,关闭后仅保留本地回调等待超时。 +- `flows/openai/background/steps/create-plus-checkout.js`:Plus 模式第 6 步实现,负责在已登录 ChatGPT 页面创建 PayPal / GoPay checkout session,或在 GPC 模式下读取 accessToken 并通过 `https://gpc.qlhazycoder.top/api/gp/tasks` 创建队列任务,记录 checkout / GPC task 运行态。 +- `flows/openai/background/steps/fetch-login-code.js`:步骤 8 实现,负责按真实认证页状态分发登录后续流程:邮箱验证码页走邮箱轮询、验证码回填与回退控制;真实 `phone-verification` 页才复用 OpenAI 专属手机号验证码流程;手机号注册登录后若进入 `add-email`,会先按共享邮箱状态持久化规则生成/解析邮箱并提交绑定,在不丢失当前手机号身份的前提下再进入邮箱验证码轮询;邮箱分支对非 2925 provider 会固定当前验证码页显示邮箱作为本次 Step 8 的目标邮箱,当 provider 为 2925 时,会在轮询前先确保当前 2925 账号已自动登录;命中 `email_in_use` 时会仅清空当前邮箱并保留上一轮比较基线,再在当前 Step 8 内重开 `add-email` 获取新邮箱,`max_check_attempts` 也只重启当前步骤恢复链。 +- `flows/openai/background/steps/fetch-signup-code.js`:步骤 4 实现,负责注册验证码阶段的页面准备与验证码流程入口;邮箱注册继续走邮箱验证码,手机号注册改走短信验证码;当 provider 为 2925 时,会在邮箱轮询前先确保当前 2925 账号已自动登录。 +- `flows/openai/background/steps/fill-plus-checkout.js`:Plus 模式第 7 步实现,负责驱动 PayPal / GoPay checkout 页面选择付款方式、填写账单地址、提交订阅并等待跳转;GPC 模式则轮询队列任务,按远端 `api_waiting_for` 提交 OTP / PIN,并在任务失败、过期或取消时清理运行态。 +- `flows/openai/background/steps/gopay-manual-confirm.js`:GoPay 手动确认节点,负责在需要人工完成订阅时发布待确认运行态,并按当前 Plus 尾链动态生成“继续 OAuth / 继续导入 SUB2API / 继续导入 CPA”的提示文案。 +- `flows/openai/background/steps/gopay-approve.js`:GoPay 自动审批节点,负责接管 GoPay / GPC 相关标签页、识别 OTP / PIN / Pay now / Hubungkan 等页面状态,并在支付页失效时回退到重新创建 checkout。 +- `flows/openai/background/steps/fill-password.js`:步骤 3 实现,负责密码生成、保存、回填与提交;当前已改为身份中立,手机号注册遇到无密码页时会按页面状态自动跳过。 +- `flows/openai/background/steps/fill-profile.js`:步骤 5 实现,负责姓名、生日填写并把资料提交给注册页内容脚本。 +- `flows/openai/background/steps/oauth-login.js`:步骤 7 实现,负责刷新 OAuth 链接、登录和确保进入验证码页;当前会根据 `accountIdentifierType / accountIdentifier` 选择邮箱或手机号登录,手机号账号会先探测并切换手机号登录入口;普通可恢复登录态失败会按上限重试,但一旦识别到认证流程进入 `add-phone / 手机号页`,会立即退出步骤 7 内部重试。 +- `flows/openai/background/steps/open-chatgpt.js`:步骤 1 实现,负责打开 ChatGPT 官网并确认入口就绪。 +- `flows/openai/background/steps/paypal-approve.js`:Plus 模式第 8 步实现,负责驱动 PayPal 登录、关闭可见通行密钥提示、点击“同意并继续”,并等待授权流程离开 PayPal 页面。 +- `flows/openai/background/steps/cpa-session-import.js`:Plus 模式下的 CPA 会话导入尾节点,负责定位当前已登录的 ChatGPT / OpenAI 标签页、读取会话与 accessToken,并调用 `background/cpa-api.js` 直接导入 CPA。 +- `flows/openai/background/steps/platform-verify.js`:平台回调验证实现,普通模式为步骤 10;Plus 场景下只有仍走 OAuth 尾链时才会出现该节点,并按当前可见 `platform-verify` 步骤上报(常见为第 13 步);负责 CPA 页面回调验证、SUB2API 管理接口 callback code/state 交换与账号创建,以及 Codex2API 的协议式 callback code/state 交换。 +- `flows/openai/background/steps/plus-return-confirm.js`:Plus 模式第 9 步实现,负责等待 PayPal 授权后回跳到 ChatGPT / OpenAI 页面,页面加载完成后固定等待 1 秒再完成。 +- `flows/openai/background/steps/sub2api-session-import.js`:Plus 模式下的 SUB2API 会话导入尾节点,负责读取当前 ChatGPT 会话,并调用 `background/sub2api-api.js` 的 `codex-session` 导入接口直接创建或更新 SUB2API 账号。 +- `core/flow-kernel/step-registry.js`:节点注册表工厂,负责保留 `flowId / nodeId / displayOrder / executeKey` 等稳定节点元数据,并把 flow-specific workflow node 映射到后台执行器。 +- `flows/openai/background/steps/submit-signup-email.js`:步骤 2 实现,负责按 `signupMethod` 在邮箱注册与手机号注册之间分发;切回已有注册页标签时会先等待加载完成并额外稳定 3 秒,再检查注册入口状态;邮箱分支会先点击官网“免费注册”再提交邮箱并清理旧手机号注册运行态,手机号分支会先点击官网“免费注册”并切到手机号注册入口,确认手机号输入页就绪后优先复用已有 signup activation,其次使用手动运行态手机号,最后才申请接码平台号码并提交,最后根据落地页判断是否跳过后续密码步骤。 ## `content/` - `content/activation-utils.js`:内容脚本通用激活策略工具,负责按钮点击方式判断与平台回调可恢复失败文案判断。 -- `content/auth-page-recovery.js`:认证页共享恢复模块,统一封装 `Try again / 重试` 页识别、点击重试和等待退出重试页;当前由注册验证码等待、OAuth 登录恢复、OAuth 同意页重试恢复链路复用。 +- `flows/openai/content/auth-page-recovery.js`:认证页共享恢复模块,统一封装 `Try again / 重试` 页识别、点击重试和等待退出重试页;当前由注册验证码等待、OAuth 登录恢复、OAuth 同意页重试恢复链路复用。 - `content/duck-mail.js`:DuckDuckGo Email Protection 页面脚本,负责生成或读取 `@duck.com` 地址;生成新地址时会优先读取显式传入的基线邮箱,并要求新地址稳定出现两次后才接受,避免页面旧值尚未渲染完成时误判“地址已变化”。 - `content/gmail-mail.js`:Gmail 邮箱轮询脚本,负责在 Gmail 页面中匹配验证码邮件。 -- `content/gopay-flow.js`:GoPay 页面内容脚本,负责读取当前支付页状态、提交手机号/OTP/PIN、点击 Continue / Pay now,并把页面内的按钮交互统一包进操作间延迟门控。 +- `flows/openai/content/gopay-flow.js`:GoPay 页面内容脚本,负责读取当前支付页状态、提交手机号/OTP/PIN、点击 Continue / Pay now,并把页面内的按钮交互统一包进操作间延迟门控。 - `content/icloud-mail.js`:iCloud 邮箱页面脚本,负责在 iCloud Mail 页面中读取邮件详情和验证码。 - `content/inbucket-mail.js`:Inbucket 邮箱轮询脚本,负责在 Inbucket 页面中读取、删除验证码邮件。 -- `content/kiro/register-page.js`:Kiro 注册页内容脚本,负责识别 Kiro 官方登录页、AWS Builder ID 注册页、Kiro Web 登录完成页,并执行选择 Builder ID、填写邮箱、姓名、验证码、密码与“确认并继续 / 允许访问”等页面交互。 -- `content/kiro/desktop-authorize-page.js`:Kiro 桌面授权页内容脚本,负责识别桌面授权过程中的 relogin、OTP、consent 与跳转完成状态,并向后台汇报授权页进度。 +- `flows/kiro/content/register-page.js`:Kiro 注册页内容脚本,负责识别 Kiro 官方登录页、AWS Builder ID 注册页、Kiro Web 登录完成页,并执行选择 Builder ID、填写邮箱、姓名、验证码、密码与“确认并继续 / 允许访问”等页面交互。 +- `flows/kiro/content/desktop-authorize-page.js`:Kiro 桌面授权页内容脚本,负责识别桌面授权过程中的 relogin、OTP、consent 与跳转完成状态,并向后台汇报授权页进度。 - `content/mail-163.js`:163 / 163 VIP / 126 邮箱轮询脚本,负责网页邮箱验证码读取和邮件清理。 - `content/mail-2925.js`:2925 邮箱页面脚本,负责 2925 邮箱自动登录态确认、收件轮询、按步骤会话隔离“已试验证码”、在每次重发验证码之间执行一轮最多 15 次的邮箱刷新轮询、命中邮件后立即删当前邮件,以及在成功后配合后台执行整箱清理;若页面出现“子邮箱已达上限邮箱”提示,会立即上报后台进入切号链路;当后台显式开启 `mail2925MatchTargetEmail` 时,会对邮件里显式出现的目标邮箱做弱匹配,避免 `receive` 模式误捞别人的验证码。 - `content/operation-delay.js`:内容脚本操作间延迟层,负责在覆盖到的注册/支付脚本里把点击、输入和短等待收敛成统一的 2 秒节奏;当前显式排除 `confirm-oauth / platform-verify` 等后台步骤。 -- `content/paypal-flow.js`:PayPal 页面脚本,负责识别登录表单、填写 PayPal 账号密码、在账号页固定执行 `focus -> clear -> fill -> blur` 重新触发邮箱输入事件、处理页面内可见通行密钥提示,并点击 PayPal 授权页的“同意并继续”按钮。 -- `content/phone-country-utils.js`:手机号国家/区号共享工具层,负责手机号纯数字归一化、国家文案/别名归一化、`+(44)` / `(+44)` / `+44` 区号提取、按号码最长区号匹配国家选项,并由注册页与后续手机号验证页共同复用。 -- `content/phone-auth.js`:认证页手机号验证脚本,负责识别 `add-phone / phone-verification` 页面、选择国家、填写手机号、提交短信验证码、触发重发短信,以及把“回到 add-phone / 进入 OAuth 同意页”的结果反馈给后台。 -- `content/plus-checkout.js`:ChatGPT Plus checkout 页面脚本,负责读取 `/api/auth/session` 创建 Plus checkout、选择 PayPal、填写账单姓名、触发 Google 地址推荐、校验结构化地址字段并点击订阅。 +- `flows/openai/content/paypal-flow.js`:PayPal 页面脚本,负责识别登录表单、填写 PayPal 账号密码、在账号页固定执行 `focus -> clear -> fill -> blur` 重新触发邮箱输入事件、处理页面内可见通行密钥提示,并点击 PayPal 授权页的“同意并继续”按钮。 +- `flows/openai/content/phone-country-utils.js`:手机号国家/区号共享工具层,负责手机号纯数字归一化、国家文案/别名归一化、`+(44)` / `(+44)` / `+44` 区号提取、按号码最长区号匹配国家选项,并由注册页与后续手机号验证页共同复用。 +- `flows/openai/content/phone-auth.js`:认证页手机号验证脚本,负责识别 `add-phone / phone-verification` 页面、选择国家、填写手机号、提交短信验证码、触发重发短信,以及把“回到 add-phone / 进入 OAuth 同意页”的结果反馈给后台。 +- `flows/openai/content/plus-checkout.js`:ChatGPT Plus checkout 页面脚本,负责读取 `/api/auth/session` 创建 Plus checkout、选择 PayPal、填写账单姓名、触发 Google 地址推荐、校验结构化地址字段并点击订阅。 - `content/qq-mail.js`:QQ 邮箱轮询脚本,负责网页邮箱验证码读取。 -- `content/signup-page.js`:注册、登录、授权主内容脚本,负责 OpenAI / ChatGPT 页面上的步骤执行;其中 Step 2 / 3 的延迟点击与延迟提交在真正触发前会先检查 Stop 状态,避免停止后页面继续自动点击;当前 Step 2 同时支持邮箱注册与手机号注册入口切换、官网注册入口点击前固定等待 3 秒、入口点击失败后最多 5 次重试、手机号国家下拉框同步校验、手机号填写与提交;Step 3 收尾会识别手机号注册密码页的 `Incorrect phone number or password` 并上报当前轮重开信号;Step 7 / 8 登录链路会额外识别手机号输入页、手机号登录入口、`phone-verification` 与真实 `add-email` 页面,避免把手机验证码页或添加邮箱页误判成普通邮箱登录入口;Step 9 后置手机号验证完成后若偶发进入资料页,会复用 Step 5 的姓名生日填写逻辑再继续等待 OAuth 同意页。 -- `content/sub2api-panel.js`:SUB2API 后台内容脚本,保留后台页面内生成 OAuth 地址和提交 localhost 回调的兼容能力;当前主流程已迁移到 `background/sub2api-api.js` 的管理接口直连路径。 +- `flows/openai/content/openai-auth.js`:注册、登录、授权主内容脚本,负责 OpenAI / ChatGPT 页面上的步骤执行;其中 Step 2 / 3 的延迟点击与延迟提交在真正触发前会先检查 Stop 状态,避免停止后页面继续自动点击;当前 Step 2 同时支持邮箱注册与手机号注册入口切换、官网注册入口点击前固定等待 3 秒、入口点击失败后最多 5 次重试、手机号国家下拉框同步校验、手机号填写与提交;Step 3 收尾会识别手机号注册密码页的 `Incorrect phone number or password` 并上报当前轮重开信号;Step 7 / 8 登录链路会额外识别手机号输入页、手机号登录入口、`phone-verification` 与真实 `add-email` 页面,避免把手机验证码页或添加邮箱页误判成普通邮箱登录入口;Step 9 后置手机号验证完成后若偶发进入资料页,会复用 Step 5 的姓名生日填写逻辑再继续等待 OAuth 同意页。 +- `flows/openai/content/sub2api-panel.js`:SUB2API 后台内容脚本,保留后台页面内生成 OAuth 地址和提交 localhost 回调的兼容能力;当前主流程已迁移到 `background/sub2api-api.js` 的管理接口直连路径。 - `content/utils.js`:内容脚本公共工具层,负责结构化日志、READY / COMPLETE / ERROR 上报、元素等待、输入与点击;内容脚本日志通过 `{ step, stepKey }` 上报,正文不再承担步骤号解析职责。 -- `content/vps-panel.js`:CPA 面板内容脚本,负责获取 OAuth 地址、提交回调 URL,并基于精确成功徽标与错误态做平台验证判定;当前支持普通步骤 10,以及 Plus 仍走 OAuth 尾链时的 `platform-verify` 节点。 -- `content/whatsapp-flow.js`:WhatsApp Web 读取脚本,负责在 GoPay 场景下从聊天消息中提取 OTP;它只参与轮询读取,不接入操作间延迟门控。 +- `flows/openai/content/vps-panel.js`:CPA 面板内容脚本,负责获取 OAuth 地址、提交回调 URL,并基于精确成功徽标与错误态做平台验证判定;当前支持普通步骤 10,以及 Plus 仍走 OAuth 尾链时的 `platform-verify` 节点。 +- `flows/openai/content/whatsapp-flow.js`:WhatsApp Web 读取脚本,负责在 GoPay 场景下从聊天消息中提取 OTP;它只参与轮询读取,不接入操作间延迟门控。 ## `data/` @@ -177,11 +177,11 @@ ## `shared/` -- `shared/flow-capabilities.js`:flow 能力矩阵归一化模块,负责根据 `activeFlowId` 与来源解析 sidepanel 的显隐能力、来源作用域、贡献模式边界、步骤范围 UI 作用域,以及 Plus 模式下 `账号接入策略` 的可选项、可编辑性与最终生效值。 -- `shared/flow-registry.js`:flow/source/settings group 总注册表,定义 `openai / kiro` 两套 flow、各自 target/runtime source、可见分组、driver 定义,以及默认 target 和默认 `kiro.rs` 配置。 +- `core/flow-kernel/flow-capabilities.js`:flow 能力矩阵归一化模块,负责根据 `activeFlowId` 与来源解析 sidepanel 的显隐能力、来源作用域、贡献模式边界、步骤范围 UI 作用域,以及 Plus 模式下 `账号接入策略` 的可选项、可编辑性与最终生效值。 +- `core/flow-kernel/flow-registry.js`:flow/source/settings group 总注册表,定义 `openai / kiro` 两套 flow、各自 target/runtime source、可见分组、driver 定义,以及默认 target 和默认 `kiro.rs` 配置。 - `shared/kiro-timeouts.js`:Kiro 共享超时常量与归一化工具,负责注册页/桌面授权链路复用的页面加载超时配置。 -- `shared/settings-schema.js`:统一设置 schema 与归一化层,负责把持久配置收敛到 `settingsState.services.*` 与 `settingsState.flows.*` 结构,并维护 `activeFlowId`、OpenAI 的 integration target、Kiro 的 `flows.kiro.targetId / targets` 与 `stepExecutionRangeByFlow`。 -- `shared/source-registry.js`:运行时来源注册表,负责合并 flow runtime source 与共享 mail source,统一 source family、driver、URL 归属和 cleanup owner 判定。 +- `core/flow-kernel/settings-schema.js`:统一设置 schema 与归一化层,负责把持久配置收敛到 `settingsState.services.*` 与 `settingsState.flows.*` 结构,并维护 `activeFlowId`、OpenAI 的 integration target、Kiro 的 `flows.kiro.targetId / targets` 与 `stepExecutionRangeByFlow`。 +- `core/flow-kernel/source-registry.js`:运行时来源注册表,负责合并 flow runtime source 与共享 mail source,统一 source family、driver、URL 归属和 cleanup owner 判定。 ## `sidepanel/` @@ -387,7 +387,7 @@ - `tests/signup-entry-diagnostics.test.js`:测试注册入口与密码页诊断快照输出,包括密码页错误文案字段。 - `tests/signup-step2-email-switch.test.js`:测试 Step 2 在手机号输入模式下切回邮箱输入模式、本地化邮箱输入框识别、官网注册入口点击等待与最多 5 次重试,以及手机号注册时国家下拉框可视区号同步。 - `tests/signup-verification-state-guard.test.js`:测试注册验证码页、重试页、资料页与已登录首页之间的状态优先级判定不会互相误伤。 -- `tests/signup-page-tab-cleanup.test.js`:测试注册页来源标签的冲突清理逻辑。 +- `tests/openai-auth-tab-cleanup.test.js`:测试注册页来源标签的冲突清理逻辑。 - `tests/source-registry-module.test.js`:测试共享来源注册表模块已接入且导出工厂,并覆盖 flow runtime source 与共享 mail source 的合并。 - `tests/plus-account-access-strategy.test.js`:测试侧边栏 `账号接入策略` 的 HTML 接线、capability 回显、动态禁用与步骤尾链刷新。 - `tests/step-definitions-module.test.js`:测试共享步骤定义模块、flow-specific workflow 输出与侧边栏脚本加载顺序。 @@ -417,3 +417,5 @@ - `tests/update-service.test.js`:测试侧边栏更新检查服务在 `Ultra` / 历史 `Pro` / legacy `v` 混合版本和旧缓存场景下都能按版本族正确排序,不会把历史版本误显示成最新可升级版本。 - `tests/verification-stop-propagation.test.js`:测试验证码流程在 Stop 场景下的错误传播与不中途降级。 - `tests/verification-flow-polling.test.js`:测试 2925 长轮询参数、验证码提交流程中的 `beforeSubmit` 钩子执行顺序,以及步骤 8 提交验证码后进入手机号页时会把“继续手机号验证”状态交给步骤 9 处理。 + +