diff --git a/README.md b/README.md
index 93e881e..e95a8d5 100644
--- a/README.md
+++ b/README.md
@@ -57,10 +57,7 @@
## Plus 模式
- Plus 模式下,侧边栏会先显示 `账号接入策略`,再显示 `Plus 支付`。
-- `账号接入策略` 会跟随当前来源动态变化:
- - `CPA`:支持 `OAuth`、`导入当前 ChatGPT 会话到 CPA`
- - `SUB2API`:支持 `OAuth`、`导入当前 ChatGPT 会话到 SUB2API`
- - `Codex2API`:当前仅支持 `OAuth`
+- `账号接入策略` 只选择接入方式:`OAuth` 或 `使用会话 JSON 导入`;会话导入的目标由上方 `来源` 自动决定,`Codex2API` 当前仅支持 `OAuth`。
- Plus 模式下的步骤不是固定一套,系统会按支付方式和账号接入策略动态切换尾链。
- Plus 模式当前只支持邮箱注册,不支持手机号注册。
diff --git a/shared/flow-capabilities.js b/shared/flow-capabilities.js
index 4c05fa0..7927ec8 100644
--- a/shared/flow-capabilities.js
+++ b/shared/flow-capabilities.js
@@ -145,6 +145,28 @@
return PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
}
+ function getPlusAccountSessionStrategyForTarget(targetId = '') {
+ const normalizedTargetId = String(targetId || '').trim().toLowerCase();
+ if (normalizedTargetId === 'sub2api') {
+ return PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION;
+ }
+ if (normalizedTargetId === 'cpa') {
+ return PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION;
+ }
+ return PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
+ }
+
+ function normalizePlusAccountAccessStrategyForTarget(value = '', targetId = '') {
+ const normalizedStrategy = normalizePlusAccountAccessStrategy(value);
+ if (
+ normalizedStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
+ || normalizedStrategy === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION
+ ) {
+ return getPlusAccountSessionStrategyForTarget(targetId);
+ }
+ return normalizedStrategy;
+ }
+
function normalizeOpenAiTargetList(values = []) {
if (!Array.isArray(values)) {
return [];
@@ -375,21 +397,23 @@
: (effectiveSignupMethods.includes(SIGNUP_METHOD_EMAIL)
? SIGNUP_METHOD_EMAIL
: effectiveSignupMethods[0]);
- const requestedPlusAccountAccessStrategy = normalizePlusAccountAccessStrategy(
- options?.plusAccountAccessStrategy ?? state?.plusAccountAccessStrategy
+ const requestedPlusAccountAccessStrategy = normalizePlusAccountAccessStrategyForTarget(
+ options?.plusAccountAccessStrategy ?? state?.plusAccountAccessStrategy,
+ effectiveTargetId
);
- const basePlusAccountAccessStrategies = [
- PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH,
- PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION,
- PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION,
- ];
+ const targetPlusAccountAccessStrategies = (Array.isArray(targetState.supportedPlusAccountAccessStrategies)
+ && targetState.supportedPlusAccountAccessStrategies.length > 0
+ ? targetState.supportedPlusAccountAccessStrategies
+ : [PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH])
+ .map(normalizePlusAccountAccessStrategy)
+ .filter((strategy, index, strategies) => strategy && strategies.indexOf(strategy) === index);
const availablePlusAccountAccessStrategies = activeFlowId === 'openai'
&& Boolean(flowState.supportsPlusMode)
&& Boolean(runtimeLocks.plusModeEnabled)
&& effectiveSignupMethod === SIGNUP_METHOD_EMAIL
? (runtimeLocks.accountContribution
? [PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION]
- : basePlusAccountAccessStrategies)
+ : targetPlusAccountAccessStrategies)
: [PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH];
const effectivePlusAccountAccessStrategy = runtimeLocks.accountContribution
&& runtimeLocks.plusModeEnabled
diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html
index 3d93895..286d262 100644
--- a/sidepanel/sidepanel.html
+++ b/sidepanel/sidepanel.html
@@ -317,8 +317,7 @@
当前来源仅支持 OAuth
diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js
index 5b63ba4..34d76f0 100644
--- a/sidepanel/sidepanel.js
+++ b/sidepanel/sidepanel.js
@@ -563,6 +563,7 @@ const DEFAULT_PLUS_PAYMENT_METHOD = PLUS_PAYMENT_METHOD_PAYPAL_HOSTED;
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_ACCOUNT_ACCESS_STRATEGY_CODEX_SESSION_UI = 'codex_session';
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
const SIGNUP_METHOD_EMAIL = 'email';
const SIGNUP_METHOD_PHONE = 'phone';
@@ -2990,6 +2991,37 @@ function normalizePlusAccountAccessStrategy(value = '') {
return PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
}
+function normalizePlusAccountAccessStrategyUiValue(value = '') {
+ const normalized = String(value || '').trim().toLowerCase();
+ if (
+ normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_CODEX_SESSION_UI
+ || normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION
+ || normalized === PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION
+ ) {
+ return PLUS_ACCOUNT_ACCESS_STRATEGY_CODEX_SESSION_UI;
+ }
+ return PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
+}
+
+function getPlusAccountSessionStrategyForTarget(targetId = '') {
+ const normalizedTargetId = normalizePlusStrategyTargetId(targetId);
+ if (normalizedTargetId === 'sub2api') {
+ return PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION;
+ }
+ if (normalizedTargetId === 'cpa') {
+ return PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION;
+ }
+ return PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
+}
+
+function resolvePlusAccountAccessStrategyForTarget(value = '', targetId = '') {
+ const normalizedUiValue = normalizePlusAccountAccessStrategyUiValue(value);
+ if (normalizedUiValue !== PLUS_ACCOUNT_ACCESS_STRATEGY_CODEX_SESSION_UI) {
+ return PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
+ }
+ return getPlusAccountSessionStrategyForTarget(targetId);
+}
+
function getSelectedPlusPaymentMethod(state = latestState) {
const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : 'paypal';
if (typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod?.value) {
@@ -3002,6 +3034,48 @@ function getRequestedPlusAccountAccessStrategy(state = latestState) {
const defaultStrategy = typeof DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY !== 'undefined'
? DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY
: 'oauth';
+ 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 resolveTargetId = () => {
+ if (typeof getSelectedPanelMode === 'function') {
+ return getSelectedPanelMode();
+ }
+ if (typeof selectPanelMode !== 'undefined' && selectPanelMode?.value) {
+ return selectPanelMode.value;
+ }
+ return state?.panelMode || state?.openaiIntegrationTargetId || 'cpa';
+ };
+ const resolveStrategyForTarget = typeof resolvePlusAccountAccessStrategyForTarget === 'function'
+ ? resolvePlusAccountAccessStrategyForTarget
+ : ((value = '', targetId = '') => {
+ 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)
+ : String(targetId || '').trim().toLowerCase();
+ if (normalizedTargetId === 'sub2api') {
+ return sub2apiSessionStrategyValue;
+ }
+ if (normalizedTargetId === 'cpa') {
+ return cpaSessionStrategyValue;
+ }
+ return oauthStrategyValue;
+ });
const fallbackStrategy = normalizePlusAccountAccessStrategy(
(typeof selectPlusAccountAccessStrategy !== 'undefined' && selectPlusAccountAccessStrategy?.dataset?.requestedValue)
|| state?.plusAccountAccessStrategy
@@ -3013,7 +3087,7 @@ function getRequestedPlusAccountAccessStrategy(state = latestState) {
&& selectPlusAccountAccessStrategy
&& !selectPlusAccountAccessStrategy.disabled
) {
- return normalizePlusAccountAccessStrategy(selectPlusAccountAccessStrategy.value || fallbackStrategy);
+ return resolveStrategyForTarget(selectPlusAccountAccessStrategy.value || fallbackStrategy, resolveTargetId());
}
return fallbackStrategy;
}
@@ -9208,6 +9282,9 @@ function updatePlusModeUI() {
const cpaSessionStrategyValue = typeof PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION !== 'undefined'
? PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION
: 'cpa_codex_session';
+ const sessionUiStrategyValue = typeof PLUS_ACCOUNT_ACCESS_STRATEGY_CODEX_SESSION_UI !== 'undefined'
+ ? PLUS_ACCOUNT_ACCESS_STRATEGY_CODEX_SESSION_UI
+ : 'codex_session';
const defaultMethod = typeof DEFAULT_PLUS_PAYMENT_METHOD !== 'undefined' ? DEFAULT_PLUS_PAYMENT_METHOD : paypalValue;
const resolveStrategyTargetId = typeof normalizePlusStrategyTargetId === 'function'
? normalizePlusStrategyTargetId
@@ -9240,6 +9317,19 @@ function updatePlusModeUI() {
}
return '通过 OAuth 回调创建 CPA 账号';
});
+ const normalizeStrategyUiValue = typeof normalizePlusAccountAccessStrategyUiValue === 'function'
+ ? normalizePlusAccountAccessStrategyUiValue
+ : ((value = '') => {
+ const normalized = String(value || '').trim().toLowerCase();
+ if (
+ normalized === sessionUiStrategyValue
+ || normalized === sub2apiSessionStrategyValue
+ || normalized === cpaSessionStrategyValue
+ ) {
+ return sessionUiStrategyValue;
+ }
+ return oauthStrategyValue;
+ });
const requestedPlusAccountAccessStrategy = typeof getRequestedPlusAccountAccessStrategy === 'function'
? getRequestedPlusAccountAccessStrategy(latestState)
: normalizePlusAccountAccessStrategy(latestState?.plusAccountAccessStrategy || DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY);
@@ -9356,15 +9446,17 @@ function updatePlusModeUI() {
row.style.display = enabled ? '' : 'none';
});
if (typeof selectPlusAccountAccessStrategy !== 'undefined' && selectPlusAccountAccessStrategy) {
- const availableStrategySet = new Set(availablePlusAccountAccessStrategies);
+ const availableStrategyUiValueSet = new Set(availablePlusAccountAccessStrategies.map(normalizeStrategyUiValue));
Array.from(selectPlusAccountAccessStrategy.options || []).forEach((option) => {
- const optionValue = normalizePlusAccountAccessStrategy(option?.value || '');
- const optionSupported = availableStrategySet.has(optionValue);
+ const optionValue = normalizeStrategyUiValue(option?.value || '');
+ const optionSupported = availableStrategyUiValueSet.has(optionValue);
option.hidden = enabled ? !optionSupported : false;
option.disabled = enabled ? !optionSupported : false;
});
- selectPlusAccountAccessStrategy.dataset.requestedValue = requestedPlusAccountAccessStrategy;
- selectPlusAccountAccessStrategy.value = effectivePlusAccountAccessStrategy;
+ selectPlusAccountAccessStrategy.dataset.requestedValue = capabilityState?.runtimeLocks?.accountContribution
+ ? requestedPlusAccountAccessStrategy
+ : effectivePlusAccountAccessStrategy;
+ selectPlusAccountAccessStrategy.value = normalizeStrategyUiValue(effectivePlusAccountAccessStrategy);
selectPlusAccountAccessStrategy.disabled = !enabled || !canEditPlusAccountAccessStrategy;
selectPlusAccountAccessStrategy.setAttribute('aria-disabled', String(selectPlusAccountAccessStrategy.disabled));
}
@@ -10523,7 +10615,7 @@ function applySettingsState(state) {
);
if (typeof selectPlusAccountAccessStrategy !== 'undefined' && selectPlusAccountAccessStrategy) {
selectPlusAccountAccessStrategy.dataset.requestedValue = currentPlusAccountAccessStrategy;
- selectPlusAccountAccessStrategy.value = currentPlusAccountAccessStrategy;
+ selectPlusAccountAccessStrategy.value = normalizePlusAccountAccessStrategyUiValue(currentPlusAccountAccessStrategy);
}
if (typeof inputGpcHelperApi !== 'undefined' && inputGpcHelperApi) {
const defaultGpcHelperApiUrl = typeof DEFAULT_GPC_HELPER_API_URL !== 'undefined'
@@ -15136,6 +15228,24 @@ selectPanelMode.addEventListener('change', async () => {
flowId: activeFlowId,
panelMode: nextPanelMode,
});
+ if (
+ typeof selectPlusAccountAccessStrategy !== 'undefined'
+ && selectPlusAccountAccessStrategy
+ && !latestState?.accountContributionEnabled
+ ) {
+ const nextPlusStrategy = resolvePlusAccountAccessStrategyForTarget(
+ selectPlusAccountAccessStrategy.value
+ || selectPlusAccountAccessStrategy.dataset?.requestedValue
+ || currentPlusAccountAccessStrategy,
+ nextPanelMode
+ );
+ currentPlusAccountAccessStrategy = nextPlusStrategy;
+ selectPlusAccountAccessStrategy.dataset.requestedValue = nextPlusStrategy;
+ selectPlusAccountAccessStrategy.value = normalizePlusAccountAccessStrategyUiValue(nextPlusStrategy);
+ syncLatestState({
+ plusAccountAccessStrategy: nextPlusStrategy,
+ });
+ }
} else {
syncLatestState({
activeFlowId,
@@ -15163,7 +15273,12 @@ selectPanelMode.addEventListener('change', async () => {
});
selectPlusAccountAccessStrategy?.addEventListener('change', () => {
- selectPlusAccountAccessStrategy.value = normalizePlusAccountAccessStrategy(selectPlusAccountAccessStrategy.value);
+ const nextUiValue = normalizePlusAccountAccessStrategyUiValue(selectPlusAccountAccessStrategy.value);
+ selectPlusAccountAccessStrategy.value = nextUiValue;
+ selectPlusAccountAccessStrategy.dataset.requestedValue = resolvePlusAccountAccessStrategyForTarget(
+ nextUiValue,
+ typeof getSelectedPanelMode === 'function' ? getSelectedPanelMode() : latestState?.panelMode
+ );
updatePlusModeUI();
markSettingsDirty(true);
saveSettings({ silent: true }).catch(() => { });
@@ -17131,7 +17246,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
currentPlusAccountAccessStrategy = normalizePlusAccountAccessStrategy(message.payload.plusAccountAccessStrategy);
selectPlusAccountAccessStrategy.dataset.requestedValue = currentPlusAccountAccessStrategy;
if (!selectPlusAccountAccessStrategy.disabled) {
- selectPlusAccountAccessStrategy.value = currentPlusAccountAccessStrategy;
+ selectPlusAccountAccessStrategy.value = normalizePlusAccountAccessStrategyUiValue(currentPlusAccountAccessStrategy);
}
}
if (message.payload.gopayHelperPhoneMode !== undefined && selectGpcHelperPhoneMode) {
diff --git a/tests/flow-capabilities-module.test.js b/tests/flow-capabilities-module.test.js
index a465bfa..547197a 100644
--- a/tests/flow-capabilities-module.test.js
+++ b/tests/flow-capabilities-module.test.js
@@ -212,7 +212,7 @@ test('flow capability registry exposes editable Plus account access strategies f
assert.deepEqual(
capabilityState.availablePlusAccountAccessStrategies,
- ['oauth', 'cpa_codex_session', 'sub2api_codex_session']
+ ['oauth', 'sub2api_codex_session']
);
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'sub2api_codex_session');
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'sub2api_codex_session');
@@ -220,6 +220,29 @@ test('flow capability registry exposes editable Plus account access strategies f
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'sub2api_codex_session');
});
+test('flow capability registry maps session import to the current source target', () => {
+ const api = loadApi();
+ const registry = api.createFlowCapabilityRegistry();
+
+ const capabilityState = registry.resolveSidepanelCapabilities({
+ state: {
+ activeFlowId: 'openai',
+ openaiIntegrationTargetId: 'sub2api',
+ signupMethod: 'email',
+ plusModeEnabled: true,
+ plusAccountAccessStrategy: 'cpa_codex_session',
+ },
+ });
+
+ assert.deepEqual(
+ capabilityState.availablePlusAccountAccessStrategies,
+ ['oauth', 'sub2api_codex_session']
+ );
+ assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'sub2api_codex_session');
+ assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'sub2api_codex_session');
+ assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'sub2api_codex_session');
+});
+
test('flow capability registry exposes editable Plus account access strategies for CPA', () => {
const api = loadApi();
const registry = api.createFlowCapabilityRegistry();
@@ -236,7 +259,7 @@ test('flow capability registry exposes editable Plus account access strategies f
assert.deepEqual(
capabilityState.availablePlusAccountAccessStrategies,
- ['oauth', 'cpa_codex_session', 'sub2api_codex_session']
+ ['oauth', 'cpa_codex_session']
);
assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'cpa_codex_session');
assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'cpa_codex_session');
@@ -244,7 +267,7 @@ test('flow capability registry exposes editable Plus account access strategies f
assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'cpa_codex_session');
});
-test('flow capability registry keeps Plus account access strategies independent from the current target', () => {
+test('flow capability registry falls back to OAuth when the current source cannot import sessions', () => {
const api = loadApi();
const registry = api.createFlowCapabilityRegistry();
@@ -260,12 +283,12 @@ test('flow capability registry keeps Plus account access strategies independent
assert.deepEqual(
capabilityState.availablePlusAccountAccessStrategies,
- ['oauth', 'cpa_codex_session', 'sub2api_codex_session']
+ ['oauth']
);
- assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'cpa_codex_session');
- assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'cpa_codex_session');
- assert.equal(capabilityState.canEditPlusAccountAccessStrategy, true);
- assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'cpa_codex_session');
+ assert.equal(capabilityState.requestedPlusAccountAccessStrategy, 'oauth');
+ assert.equal(capabilityState.effectivePlusAccountAccessStrategy, 'oauth');
+ assert.equal(capabilityState.canEditPlusAccountAccessStrategy, false);
+ assert.equal(capabilityState.stepDefinitionOptions.plusAccountAccessStrategy, 'oauth');
});
test('flow capability registry forces SUB2API session import only for contribution mode Plus runs', () => {
diff --git a/tests/plus-account-access-strategy.test.js b/tests/plus-account-access-strategy.test.js
index 1787c7f..10f2010 100644
--- a/tests/plus-account-access-strategy.test.js
+++ b/tests/plus-account-access-strategy.test.js
@@ -3,6 +3,7 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
const sidepanelSource = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
+const sidepanelHtml = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
const backgroundSource = fs.readFileSync('background.js', 'utf8');
function extractFunction(name) {
@@ -115,6 +116,20 @@ return {
`)();
}
+test('sidepanel Plus account access selector only exposes access methods', () => {
+ const selectorStart = sidepanelHtml.indexOf('id="select-plus-account-access-strategy"');
+ const selectorEnd = sidepanelHtml.indexOf('', selectorStart);
+ const selectorHtml = sidepanelHtml.slice(selectorStart, selectorEnd);
+
+ assert.notEqual(selectorStart, -1);
+ assert.match(selectorHtml, /value="oauth"[^>]*>OAuth);
+ assert.match(selectorHtml, /value="codex_session"[^>]*>使用会话 JSON 导入);
+ assert.doesNotMatch(selectorHtml, /cpa_codex_session/);
+ assert.doesNotMatch(selectorHtml, /sub2api_codex_session/);
+ assert.doesNotMatch(selectorHtml, /导入当前 ChatGPT 会话到 CPA/);
+ assert.doesNotMatch(selectorHtml, /导入当前 ChatGPT 会话到 SUB2API/);
+});
+
test('sidepanel keeps requested Plus account strategy while contribution mode forces SUB2API', () => {
const api = buildHarness(
`{
@@ -138,12 +153,12 @@ test('sidepanel keeps requested Plus account strategy while contribution mode fo
assert.equal(api.rowPlusAccountAccessStrategy.style.display, '');
assert.equal(api.selectPlusAccountAccessStrategy.disabled, true);
assert.equal(api.selectPlusAccountAccessStrategy.dataset.requestedValue, 'cpa_codex_session');
- assert.equal(api.selectPlusAccountAccessStrategy.value, 'sub2api_codex_session');
+ assert.equal(api.selectPlusAccountAccessStrategy.value, 'codex_session');
assert.equal(api.getRequestedPlusAccountAccessStrategy(), 'cpa_codex_session');
assert.match(api.plusAccountAccessStrategyCaption.textContent, /SUB2API/);
});
-test('sidepanel enables SUB2API session strategy selection when the current Plus target supports it', () => {
+test('sidepanel maps generic session import to SUB2API when the current source is SUB2API', () => {
const api = buildHarness(
`{
canShowPlusSettings: true,
@@ -163,12 +178,12 @@ test('sidepanel enables SUB2API session strategy selection when the current Plus
assert.equal(api.rowPlusAccountAccessStrategy.style.display, '');
assert.equal(api.selectPlusAccountAccessStrategy.disabled, false);
- assert.equal(api.selectPlusAccountAccessStrategy.value, 'sub2api_codex_session');
+ assert.equal(api.selectPlusAccountAccessStrategy.value, 'codex_session');
assert.equal(api.getRequestedPlusAccountAccessStrategy(), 'sub2api_codex_session');
assert.match(api.plusAccountAccessStrategyCaption.textContent, /SUB2API/);
});
-test('sidepanel enables CPA session strategy selection when the current Plus target supports it', () => {
+test('sidepanel maps generic session import to CPA when the current source is CPA', () => {
const api = buildHarness(
`{
canShowPlusSettings: true,
@@ -190,11 +205,38 @@ test('sidepanel enables CPA session strategy selection when the current Plus tar
assert.equal(api.rowPlusAccountAccessStrategy.style.display, '');
assert.equal(api.selectPlusAccountAccessStrategy.disabled, false);
- assert.equal(api.selectPlusAccountAccessStrategy.value, 'cpa_codex_session');
+ assert.equal(api.selectPlusAccountAccessStrategy.value, 'codex_session');
assert.equal(api.getRequestedPlusAccountAccessStrategy(), 'cpa_codex_session');
assert.match(api.plusAccountAccessStrategyCaption.textContent, /CPA/);
});
+test('sidepanel falls back to OAuth when the current source cannot import sessions', () => {
+ const api = buildHarness(
+ `{
+ canShowPlusSettings: true,
+ runtimeLocks: { plusModeEnabled: true },
+ canEditPlusAccountAccessStrategy: false,
+ availablePlusAccountAccessStrategies: ['oauth'],
+ effectivePanelMode: 'codex2api',
+ effectivePlusAccountAccessStrategy: 'oauth',
+ }`,
+ `{
+ activeFlowId: 'openai',
+ panelMode: 'codex2api',
+ plusPaymentMethod: 'paypal',
+ plusAccountAccessStrategy: 'cpa_codex_session',
+ }`
+ );
+
+ api.updatePlusModeUI();
+
+ assert.equal(api.selectPlusAccountAccessStrategy.disabled, true);
+ assert.equal(api.selectPlusAccountAccessStrategy.dataset.requestedValue, 'oauth');
+ assert.equal(api.selectPlusAccountAccessStrategy.value, 'oauth');
+ assert.equal(api.getRequestedPlusAccountAccessStrategy(), 'oauth');
+ assert.equal(api.plusAccountAccessStrategyCaption.textContent, '当前来源仅支持 OAuth');
+});
+
test('sidepanel rebuilds step definitions and workflow nodes with the effective Plus account strategy', () => {
const bundle = [
extractFunction('normalizeSignupMethod'),
diff --git a/项目完整链路说明.md b/项目完整链路说明.md
index 82b8851..d6d2756 100644
--- a/项目完整链路说明.md
+++ b/项目完整链路说明.md
@@ -55,7 +55,7 @@
- 查询 GitHub Releases 并展示更新卡片;当前更新服务会区分 `Ultra`、历史 `Pro` 与 legacy `v` 三个版本族,排序时固定以 `Ultra` 为最高正式系列,同时会在读取缓存后重新排序,避免历史 `Pro` 或 `v` 版本误显示为比 `Ultra` 更新
- 展示一个单独的“接码”开关、注册方式 `signupMethod` 与“接码平台”下拉;接码平台当前支持 HeroSMS / 5sim / NexSMS。普通模式下开启接码后可把注册方式切到手机号注册,并在 OAuth 登录链路命中手机号登录验证码页时继续复用同一手机号续跑短信验证
- 侧栏在接码卡内提供一个独立运行态“注册手机号”输入框,位于接码订单运行状态下方;第 2 步自动拿到号码后立即回填,用户手动接管手机号注册时也可以直接改写这一个运行态槽位。这个输入框表达账号身份,不等同于接码订单;后续自动拉短信仍依赖 `signupPhoneActivation / signupPhoneCompletedActivation`
-- 展示 `Plus 模式` 开关、`账号接入策略` 与 `Plus 支付` 配置;`账号接入策略` 固定放在 `Plus 支付` 上方,并根据当前来源动态提供 `OAuth / 导入当前 ChatGPT 会话到 CPA / 导入当前 ChatGPT 会话到 SUB2API` 等可用选项,不支持的来源会直接禁用为仅 OAuth;支付方式支持 PayPal / GoPay / GPC,PayPal 展示账号池下拉与添加按钮,GoPay 展示手机号和 PIN,GPC 展示只读 API 地址、API Key、专用手机号、OTP 渠道、本地 OTP helper 开关与 URL、PIN;GPC 的 `购买卡密` 按钮打开卡密购买页,`转换 API Key` 按钮打开 `https://gpc.qlhazycoder.top/`;步骤列表会按支付方式和账号接入策略动态切换,普通模式的注册成功等待步骤不再显示或执行;Plus 模式下手机号注册不可用,只允许邮箱注册
+- 展示 `Plus 模式` 开关、`账号接入策略` 与 `Plus 支付` 配置;`账号接入策略` 固定放在 `Plus 支付` 上方,只提供 `OAuth / 使用会话 JSON 导入` 两种接入方式,会话导入的 CPA / SUB2API 目标由上方 `来源` 自动决定,不支持会话导入的来源会直接禁用为仅 OAuth;支付方式支持 PayPal / GoPay / GPC,PayPal 展示账号池下拉与添加按钮,GoPay 展示手机号和 PIN,GPC 展示只读 API 地址、API Key、专用手机号、OTP 渠道、本地 OTP helper 开关与 URL、PIN;GPC 的 `购买卡密` 按钮打开卡密购买页,`转换 API Key` 按钮打开 `https://gpc.qlhazycoder.top/`;步骤列表会按支付方式和账号接入策略动态切换,普通模式的注册成功等待步骤不再显示或执行;Plus 模式下手机号注册不可用,只允许邮箱注册
- 展示 `执行范围` 配置;当前 UI 只在 codex/openai flow 显示,保存为通用的 `stepExecutionRangeByFlow`,用于指定允许执行的起止步骤。开启后,范围外节点在侧栏中显示为禁用态,手动按钮、手动跳过入口与进度计数只按允许节点计算。
- 为 Hotmail / 2925 账号池复用同一套“添加账号 / 取消添加 / 批量导入 / 收起列表”表单交互;共享的显隐控制放在 `sidepanel/account-pool-ui.js`,各自 manager 只保留 provider 相关字段校验与业务操作
- `Cloudflare Temp Email` 在侧栏中有独立区域,支持“按注册邮箱查信 / 按接收邮箱查信”切换、可编辑域名列表、随机子域名开关,以及教程/部署入口;域名选择由 `sidepanel/editable-list-picker.js` 统一承接
diff --git a/项目文件结构说明.md b/项目文件结构说明.md
index a5d46a9..6b41410 100644
--- a/项目文件结构说明.md
+++ b/项目文件结构说明.md
@@ -207,7 +207,7 @@
receive 模式把账号池开关一起隐藏;当前在 `邮箱生成` 区域新增 `自定义邮箱池` 选项和多行邮箱池输入框,并在 `邮箱服务 = 自定义邮箱` 时
额外显示 `自定义号池` 文本框;当邮箱服务为 iCloud 时,额外提供目标邮箱类型与转发邮箱 provider 配置,用于选择直接从 iCloud 收件箱收
码或从 QQ / 网易 / Gmail 转发目标邮箱收码;当邮箱服务或邮箱生成器为 Cloud Mail 时,额外显示 API 地址、管理员账号、接收邮箱和生成域名配置行;当邮箱服务为 YYDS Mail 时,额外显示 API Key 与 Base URL 配置行,并隐藏普通邮箱生成/转发邮箱相关配置;来源下拉框当前支持 `CPA / SUB2API / Codex2API`,其中 Codex2API 额外提供后台地址和管理密
- 钥配置行;设置卡片新增 `IP代理` 开关与代理配置折叠区,支持 711Proxy 账号密码模式、代理状态卡与出口检测按钮;接码卡内把注册方式、接码配置、接码订单运行态与“注册手机号”身份运行态分层展示,“注册手机号”位于订单运行状态下方,不嵌入当前分配/验证码网格;设置卡片新增 `Plus 模式` 开关,并在 `Plus 支付` 之上新增 `账号接入策略` 下拉;该下拉会按当前来源动态显示 `OAuth / 导入当前 ChatGPT 会话到 CPA / 导入当前 ChatGPT 会话到 SUB2API`,不支持的来源会直接禁用并显示“当前来源仅支持 OAuth”;PayPal 账号下拉框继续使用公共表单弹窗添加账号;GPC Plus 配置额外提供只读 API 地址、API Key、专用手机号、OTP 渠道、本地 OTP helper 开关、helper URL 与 PIN,并提供 `购买卡密` 与 `转换 API Key` 入口;Hotmail / 2925 两个账号池当前都使用统一的头部“添加账号/取消添加”按钮和共享表单容器;设置卡片还新增“授权总超时”开关,用于控制 Step 7 后链 5 分钟总预算;注册邮箱下方新增 `执行范围` 行,目前只在 codex/openai flow 显示,用于配置允许执行的起止步骤。
+ 钥配置行;设置卡片新增 `IP代理` 开关与代理配置折叠区,支持 711Proxy 账号密码模式、代理状态卡与出口检测按钮;接码卡内把注册方式、接码配置、接码订单运行态与“注册手机号”身份运行态分层展示,“注册手机号”位于订单运行状态下方,不嵌入当前分配/验证码网格;设置卡片新增 `Plus 模式` 开关,并在 `Plus 支付` 之上新增 `账号接入策略` 下拉;该下拉只显示 `OAuth / 使用会话 JSON 导入` 两种接入方式,会话导入目标由当前来源自动决定,不支持的来源会直接禁用并显示“当前来源仅支持 OAuth”;PayPal 账号下拉框继续使用公共表单弹窗添加账号;GPC Plus 配置额外提供只读 API 地址、API Key、专用手机号、OTP 渠道、本地 OTP helper 开关、helper URL 与 PIN,并提供 `购买卡密` 与 `转换 API Key` 入口;Hotmail / 2925 两个账号池当前都使用统一的头部“添加账号/取消添加”按钮和共享表单容器;设置卡片还新增“授权总超时”开关,用于控制 Step 7 后链 5 分钟总预算;注册邮箱下方新增 `执行范围` 行,目前只在 codex/openai flow 显示,用于配置允许执行的起止步骤。
- `sidepanel/paypal-manager.js`:侧边栏 PayPal 账号管理器,负责 Plus 模式下的账号下拉框渲染、添加账号弹窗、保存账号与切换当前账号。
- `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、共享验证码自动重发次数配置与广播接收,并装
配 Hotmail / 2925 / iCloud / LuckMail / 贡献模式 / 账号记录面板 / 贡献内容更新服务;当前贡献模式的“开始贡献”会直接复用主自动流程启