diff --git a/background.js b/background.js index 903e078..f35fbe3 100644 --- a/background.js +++ b/background.js @@ -798,9 +798,8 @@ function getNodeDefinitionsForState(state = {}) { } return getStepDefinitionsForState(state) .map((definition) => ({ - legacyStepId: Number(definition?.id), nodeId: String(definition?.key || '').trim(), - displayOrder: Number.isFinite(Number(definition?.order)) ? Number(definition.order) : Number(definition?.id), + displayOrder: Number.isFinite(Number(definition?.id)) ? Number(definition.id) : Number(definition?.order), title: String(definition?.title || '').trim(), executeKey: String(definition?.key || '').trim(), })) @@ -829,17 +828,21 @@ function getLastNodeIdForState(state = {}) { } function getNodeIdByStepForState(step, state = {}) { - const definition = getStepDefinitionForState(step, state); - return String(definition?.key || '').trim(); + const numericStep = Number(step); + if (!Number.isInteger(numericStep) || numericStep <= 0) { + return ''; + } + const node = getNodeDefinitionsForState(state).find((definition) => Number(definition?.displayOrder) === numericStep); + return String(node?.nodeId || '').trim(); } function getStepIdByNodeIdForState(nodeId, state = {}) { const normalizedNodeId = String(nodeId || '').trim(); if (!normalizedNodeId) return null; const node = getNodeDefinitionForState(normalizedNodeId, state); - const legacyStepId = Number(node?.legacyStepId); - if (Number.isInteger(legacyStepId) && legacyStepId > 0) { - return legacyStepId; + const displayOrder = Number(node?.displayOrder); + if (Number.isInteger(displayOrder) && displayOrder > 0) { + return displayOrder; } return getStepIdByKeyForState(normalizedNodeId, state); } @@ -1074,6 +1077,39 @@ const PERSISTED_SETTING_DEFAULTS = { 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', + 'kiroIntegrationTargetId', + 'panelMode', + 'kiroSourceId', + 'vpsUrl', + 'vpsPassword', + 'localCpaStep9Mode', + 'sub2apiUrl', + 'sub2apiEmail', + 'sub2apiPassword', + 'sub2apiGroupName', + 'sub2apiGroupNames', + 'sub2apiAccountPriority', + 'sub2apiDefaultProxyName', + 'codex2apiUrl', + 'codex2apiAdminKey', + 'customPassword', + 'signupMethod', + 'phoneVerificationEnabled', + 'phoneSignupReloginAfterBindEmailEnabled', + 'plusModeEnabled', + 'plusPaymentMethod', + 'mailProvider', + 'ipProxyEnabled', + 'ipProxyService', + 'ipProxyMode', + 'kiroRsUrl', + 'kiroRsKey', + 'stepExecutionRangeByFlow', +]); +const SETTINGS_SCHEMA_VIEW_KEY_SET = new Set(SETTINGS_SCHEMA_VIEW_KEYS); const SETTINGS_EXPORT_SCHEMA_VERSION = 1; const SETTINGS_EXPORT_FILENAME_PREFIX = 'multipage-settings'; const STEP6_REGISTRATION_SUCCESS_WAIT_MS = 20000; @@ -1087,91 +1123,14 @@ const DEFAULT_STATE = { nodeStatuses: { ...DEFAULT_NODE_STATUSES }, runtimeState: runtimeStateHelpers?.buildDefaultRuntimeState?.() || null, ...CONTRIBUTION_RUNTIME_DEFAULTS, - oauthUrl: null, // 运行时抓取到的 OAuth 地址,不要手动预填。 - resolvedSignupMethod: null, // 当前自动轮次冻结后的实际注册方式。 - accountIdentifierType: null, - accountIdentifier: '', - registrationEmailState: { ...DEFAULT_REGISTRATION_EMAIL_STATE }, - email: null, // 运行时邮箱,由程序自动获取并写入,不能手动预填。 - password: null, // 运行时实际密码,由 customPassword 或程序自动生成后写入。 accounts: [], // 已生成账号记录:{ email, password, createdAt }。 accountRunHistory: [], // 账号运行历史快照,实际持久化在 chrome.storage.local。 manualAliasUsage: {}, preservedAliases: {}, icloudAliasCache: [], icloudAliasCacheAt: 0, - lastEmailTimestamp: null, // 最近一次获取到邮箱数据的运行时时间戳。 - lastSignupCode: null, // 注册验证码,运行时由程序自动读取并写入。 - lastLoginCode: null, // 登录验证码,运行时由程序自动读取并写入。 - localhostUrl: null, // 运行时捕获到的 localhost 回调地址,不要手动预填。 - cpaOAuthState: null, // CPA OAuth state。 - cpaManagementOrigin: null, // CPA 管理接口 origin。 - sub2apiSessionId: null, // SUB2API OpenAI Auth 会话 ID。 - sub2apiOAuthState: null, // SUB2API OpenAI Auth state。 - sub2apiGroupId: null, // SUB2API 目标分组 ID。 - sub2apiGroupIds: [], // SUB2API 多目标分组 ID。 - sub2apiDraftName: null, // SUB2API 本轮预生成的账号名称。 - sub2apiProxyId: null, // SUB2API 本轮使用的代理 ID。 - codex2apiSessionId: null, // Codex2API OAuth 会话 ID。 - codex2apiOAuthState: null, // Codex2API OAuth state。 - plusCheckoutTabId: null, // Plus checkout / PayPal 标签页 ID。 - automationWindowId: null, // 当前任务锁定的浏览器窗口 ID,避免新标签页跑到其它窗口。 - plusCheckoutUrl: null, // Plus checkout 运行时短链,不写入持久配置。 - plusCheckoutCountry: 'DE', - plusCheckoutCurrency: 'EUR', - plusCheckoutSource: '', - plusBillingCountryText: '', - plusBillingAddress: null, - plusPaypalApprovedAt: null, - plusGoPayApprovedAt: null, - plusReturnUrl: '', - plusManualConfirmationPending: false, - plusManualConfirmationRequestId: '', - plusManualConfirmationStep: 0, - plusManualConfirmationMethod: '', - plusManualConfirmationTitle: '', - plusManualConfirmationMessage: '', - gopayHelperReferenceId: '', - gopayHelperGoPayGuid: '', - gopayHelperRedirectUrl: '', - gopayHelperNextAction: '', - gopayHelperFlowId: '', - gopayHelperChallengeId: '', - gopayHelperStartPayload: null, - gopayHelperTaskId: '', - gopayHelperTaskStatus: '', - gopayHelperStatusText: '', - gopayHelperRemoteStage: '', - gopayHelperApiWaitingFor: '', - gopayHelperApiInputDeadlineAt: '', - gopayHelperApiInputWaitSeconds: 0, - gopayHelperLastInputError: '', - gopayHelperOtpInvalidCount: 0, - gopayHelperFailureStage: '', - gopayHelperFailureDetail: '', - gopayHelperTaskPayload: null, - gopayHelperOrderCreatedAt: 0, - gopayHelperTaskProgressSignature: '', - gopayHelperTaskProgressAt: 0, - gopayHelperTaskProgressTaskId: '', - gopayHelperPinPayload: null, - gopayHelperResolvedOtp: '', - gopayHelperOtpRequestId: '', - gopayHelperOtpReferenceId: '', - flowStartTime: null, // 当前流程开始时间。 - tabRegistry: {}, // 程序维护的标签页注册表。 - sourceLastUrls: {}, // 各来源页面最近一次打开的地址记录。 logs: [], // 侧边栏展示的运行日志。 ...PERSISTED_SETTING_DEFAULTS, // 合并 chrome.storage.local 中持久化保存的用户配置。 - ipProxyApiPool: [], - ipProxyApiCurrentIndex: 0, - ipProxyApiCurrent: null, - ipProxyAccountPool: [], - ipProxyAccountCurrentIndex: 0, - ipProxyAccountCurrent: null, - ipProxyPool: [], - ipProxyCurrentIndex: 0, - ipProxyCurrent: null, luckmailApiKey: '', luckmailBaseUrl: DEFAULT_LUCKMAIL_BASE_URL, luckmailEmailType: DEFAULT_LUCKMAIL_EMAIL_TYPE, @@ -1179,23 +1138,6 @@ const DEFAULT_STATE = { luckmailUsedPurchases: {}, luckmailPreserveTagId: 0, luckmailPreserveTagName: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME, - currentLuckmailPurchase: null, - currentLuckmailMailCursor: null, - currentYydsMailInbox: null, - currentPhoneActivation: null, - phoneNumber: '', - currentPhoneVerificationCode: '', - currentPhoneVerificationCountdownEndsAt: 0, - currentPhoneVerificationCountdownWindowIndex: 0, - currentPhoneVerificationCountdownWindowTotal: 0, - reusablePhoneActivation: null, - freeReusablePhoneActivation: null, - phoneReusableActivationPool: [], - signupPhoneNumber: '', - signupPhoneActivation: null, - signupPhoneCompletedActivation: null, - signupPhoneVerificationRequestedAt: null, - signupPhoneVerificationPurpose: '', heroSmsLastPriceTiers: [], heroSmsLastPriceCountryId: 0, heroSmsLastPriceCountryLabel: '', @@ -3353,14 +3295,14 @@ function buildPersistentSettingsPayload(input = {}, options = {}) { const settingsSchemaApi = typeof getSettingsSchemaApi === 'function' ? getSettingsSchemaApi() : null; - if (settingsSchemaApi?.normalizeSettingsState && settingsSchemaApi?.buildLegacySettingsPayload) { + if (settingsSchemaApi?.normalizeSettingsState && settingsSchemaApi?.buildSettingsView) { const settingsSchemaInput = {}; for (const key of persistedSettingKeys) { if (normalizedInput[key] !== undefined) { settingsSchemaInput[key] = payload[key]; } } - const normalizedSettingsState = settingsSchemaApi.normalizeSettingsState({ + Object.assign(payload, projectSettingsSchemaView(settingsSchemaApi, { ...settingsSchemaInput, ...(isPlainObjectForSettingsSchema(normalizedInput.settingsState) ? { settingsState: normalizedInput.settingsState } @@ -3368,13 +3310,7 @@ function buildPersistentSettingsPayload(input = {}, options = {}) { ...(Object.prototype.hasOwnProperty.call(normalizedInput, 'settingsSchemaVersion') ? { settingsSchemaVersion: normalizedInput.settingsSchemaVersion } : {}), - }, { - activeFlowId: settingsSchemaInput.activeFlowId || normalizedInput.activeFlowId || DEFAULT_ACTIVE_FLOW_ID, - }); - Object.assign( - payload, - settingsSchemaApi.buildLegacySettingsPayload(normalizedSettingsState, payload) - ); + }, payload)); } } @@ -3391,6 +3327,32 @@ function getSettingsSchemaApi() { }); } +function projectSettingsSchemaView(settingsSchemaApi, normalizedInput = {}, payload = {}) { + if ( + !settingsSchemaApi?.normalizeSettingsState + || !settingsSchemaApi?.buildSettingsView + ) { + return payload; + } + const normalizedSettingsState = settingsSchemaApi.normalizeSettingsState(normalizedInput, { + activeFlowId: normalizedInput?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID, + }); + return settingsSchemaApi.buildSettingsView(normalizedSettingsState, payload); +} + +function buildPersistedSettingsStoragePayload(payload = {}) { + const storagePayload = {}; + Object.entries(payload || {}).forEach(([key, value]) => { + if (SETTINGS_SCHEMA_VIEW_KEY_SET.has(key)) { + return; + } + storagePayload[key] = value; + }); + storagePayload.settingsSchemaVersion = Number(payload?.settingsSchemaVersion) || 0; + storagePayload.settingsState = payload?.settingsState; + return storagePayload; +} + async function getPersistedSettings() { const stored = await chrome.storage.local.get([ ...PERSISTED_SETTING_KEYS, @@ -3481,6 +3443,9 @@ function buildAutoRunFreshResetSettingsState(prevState = {}, activeFlowId = DEFA const nextSettingsStatePatch = { activeFlowId, services: { + account: { + customPassword: prevState?.customPassword, + }, email: { provider: prevState?.mailProvider, }, @@ -3492,9 +3457,7 @@ function buildAutoRunFreshResetSettingsState(prevState = {}, activeFlowId = DEFA }, flows: { openai: { - source: { - selected: prevState?.panelMode, - }, + integrationTargetId: prevState?.openaiIntegrationTargetId || prevState?.panelMode, autoRun: normalizedStepExecutionRangeByFlow.openai ? { stepExecutionRange: normalizedStepExecutionRangeByFlow.openai, @@ -3502,9 +3465,7 @@ function buildAutoRunFreshResetSettingsState(prevState = {}, activeFlowId = DEFA : undefined, }, kiro: { - source: { - selected: prevState?.kiroSourceId, - }, + integrationTargetId: prevState?.kiroIntegrationTargetId || prevState?.kiroSourceId, autoRun: normalizedStepExecutionRangeByFlow.kiro ? { stepExecutionRange: normalizedStepExecutionRangeByFlow.kiro, @@ -3651,78 +3612,58 @@ async function setPersistentSettings(updates) { const settingsSchemaApi = typeof getSettingsSchemaApi === 'function' ? getSettingsSchemaApi() : null; + const hasSchemaApi = Boolean( + settingsSchemaApi?.normalizeSettingsState + && settingsSchemaApi?.buildSettingsView + ); + const explicitFlatUpdates = { + ...nextUpdates, + }; + delete explicitFlatUpdates.settingsSchemaVersion; + delete explicitFlatUpdates.settingsState; - let persistedUpdates; - if (settingsSchemaApi?.normalizeSettingsState && settingsSchemaApi?.buildLegacySettingsPayload) { - const isPlainObjectForSettingsSchema = typeof isPlainObjectValue === 'function' - ? isPlainObjectValue - : ((value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value)); - const cloneSettingsValue = (value) => { - if (Array.isArray(value)) { - return value.map((entry) => cloneSettingsValue(entry)); - } - if (isPlainObjectForSettingsSchema(value)) { - return Object.fromEntries( - Object.entries(value).map(([key, entryValue]) => [key, cloneSettingsValue(entryValue)]) - ); - } - return value; - }; - const mergeSettingsState = (baseValue, patchValue) => { - if (Array.isArray(patchValue)) { - return patchValue.map((entry) => cloneSettingsValue(entry)); - } - if (!isPlainObjectForSettingsSchema(patchValue)) { - return patchValue === undefined ? cloneSettingsValue(baseValue) : patchValue; - } - - const baseObject = isPlainObjectForSettingsSchema(baseValue) ? baseValue : {}; - const nextObject = { - ...cloneSettingsValue(baseObject), - }; - for (const [key, entryValue] of Object.entries(patchValue)) { - nextObject[key] = mergeSettingsState(baseObject[key], entryValue); - } - return nextObject; - }; - + let mergedSettingsState = nextUpdates.settingsState ?? currentSettings.settingsState; + if (hasSchemaApi) { const currentSettingsState = settingsSchemaApi.normalizeSettingsState( - isPlainObjectForSettingsSchema(currentSettings?.settingsState) + isPlainObjectValue(currentSettings?.settingsState) ? { settingsState: currentSettings.settingsState } : currentSettings, { activeFlowId: currentSettings?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID, } ); - const mergedSettingsState = isPlainObjectForSettingsSchema(nextUpdates.settingsState) - ? mergeSettingsState(currentSettingsState, nextUpdates.settingsState) + mergedSettingsState = isPlainObjectValue(nextUpdates.settingsState) + ? (typeof settingsSchemaApi.mergeSettingsState === 'function' + ? settingsSchemaApi.mergeSettingsState(currentSettingsState, nextUpdates.settingsState) + : nextUpdates.settingsState) : currentSettingsState; - const explicitFlatUpdates = { - ...nextUpdates, - }; - delete explicitFlatUpdates.settingsSchemaVersion; - delete explicitFlatUpdates.settingsState; - - persistedUpdates = buildPersistentSettingsPayload({ - ...explicitFlatUpdates, - settingsSchemaVersion: nextUpdates.settingsSchemaVersion ?? currentSettings.settingsSchemaVersion, - settingsState: mergedSettingsState, - }, { - fillDefaults: true, - }); - } else { - persistedUpdates = buildPersistentSettingsPayload({ - ...currentSettings, - ...nextUpdates, - settingsSchemaVersion: nextUpdates.settingsSchemaVersion ?? currentSettings.settingsSchemaVersion, - settingsState: nextUpdates.settingsState ?? currentSettings.settingsState, - }, { - fillDefaults: true, - }); } + const nextPayloadInput = { + ...currentSettings, + ...explicitFlatUpdates, + settingsSchemaVersion: nextUpdates.settingsSchemaVersion ?? currentSettings.settingsSchemaVersion, + settingsState: mergedSettingsState, + }; + if (hasSchemaApi && isPlainObjectValue(nextUpdates.settingsState)) { + for (const key of SETTINGS_SCHEMA_VIEW_KEYS) { + delete nextPayloadInput[key]; + } + Object.assign(nextPayloadInput, explicitFlatUpdates); + } + + const persistedUpdates = buildPersistentSettingsPayload(nextPayloadInput, { + fillDefaults: true, + }); + if (Object.keys(persistedUpdates).length > 0) { - await chrome.storage.local.set(persistedUpdates); + const storagePayload = hasSchemaApi + ? buildPersistedSettingsStoragePayload(persistedUpdates) + : persistedUpdates; + if (hasSchemaApi && chrome.storage?.local?.remove) { + await chrome.storage.local.remove(SETTINGS_SCHEMA_VIEW_KEYS); + } + await chrome.storage.local.set(storagePayload); } return persistedUpdates; } @@ -13563,9 +13504,8 @@ function buildNodeRegistry(definitions = []) { return self.MultiPageBackgroundStepRegistry?.createNodeRegistry( definitions.map((definition) => ({ ...definition, - legacyStepId: definition.legacyStepId || definition.id, nodeId: definition.nodeId || definition.key, - displayOrder: definition.displayOrder || definition.order, + displayOrder: definition.displayOrder || definition.id || definition.order, executeKey: definition.executeKey || definition.key, execute: stepExecutorsByKey[definition.executeKey || definition.key || definition.nodeId], })) @@ -13580,15 +13520,15 @@ function buildStepRegistry(definitions = []) { const normalizedDefinitions = (Array.isArray(definitions) ? definitions : []) .map((definition) => ({ ...definition, - legacyStepId: Number(definition?.legacyStepId ?? definition?.id) || 0, + displayOrder: Number(definition?.displayOrder ?? definition?.id ?? definition?.order) || 0, nodeId: String(definition?.nodeId || definition?.key || '').trim(), })) .filter((definition) => definition.nodeId); const nodeRegistry = buildNodeRegistry(normalizedDefinitions); const stepToNodeDefinition = new Map( normalizedDefinitions - .filter((definition) => Number.isInteger(definition.legacyStepId) && definition.legacyStepId > 0) - .map((definition) => [definition.legacyStepId, definition]) + .filter((definition) => Number.isInteger(definition.displayOrder) && definition.displayOrder > 0) + .map((definition) => [definition.displayOrder, definition]) ); return { @@ -13617,10 +13557,10 @@ function getStepRegistryForState(state = {}) { const activeFlowId = String(state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID; const cacheKey = `${activeFlowId}:${(Array.isArray(definitions) ? definitions : []) .map((definition) => [ - Number(definition?.legacyStepId ?? definition?.id) || 0, + Number(definition?.displayOrder ?? definition?.id ?? definition?.order) || 0, String(definition?.nodeId || definition?.key || '').trim(), String(definition?.executeKey || definition?.key || '').trim(), - Number(definition?.displayOrder ?? definition?.order) || 0, + Number(definition?.displayOrder ?? definition?.id ?? definition?.order) || 0, ].join(':')) .join('|')}`; diff --git a/background/runtime-state.js b/background/runtime-state.js index babc891..cdb420d 100644 --- a/background/runtime-state.js +++ b/background/runtime-state.js @@ -244,21 +244,6 @@ }; } - function flattenFlowStateById(flowState = {}, flowId = 'openai') { - const fieldGroups = FLOW_FIELD_GROUPS[flowId] || {}; - const scopedState = normalizePlainObject(flowState[flowId]); - const next = {}; - for (const [groupKey, fields] of Object.entries(fieldGroups)) { - const group = normalizePlainObject(scopedState[groupKey]); - for (const field of fields) { - if (Object.prototype.hasOwnProperty.call(group, field)) { - next[field] = cloneValue(group[field]); - } - } - } - return next; - } - function buildScopedFlowState(baseFlowState = {}, state = {}, flowId = 'openai') { const fieldGroups = FLOW_FIELD_GROUPS[flowId] || {}; const baseScopedState = cloneValue(normalizePlainObject(baseFlowState[flowId])); @@ -276,7 +261,7 @@ return scopedState; } - function buildOpenAiFlowState(baseValue = {}, state = {}) { + function buildFlowState(baseValue = {}, state = {}) { const baseFlowState = cloneValue(normalizePlainObject(baseValue)); return { ...baseFlowState, @@ -285,6 +270,126 @@ }; } + function listFlowFieldNames() { + const fields = []; + for (const flowGroups of Object.values(FLOW_FIELD_GROUPS)) { + for (const groupFields of Object.values(normalizePlainObject(flowGroups))) { + for (const field of Array.isArray(groupFields) ? groupFields : []) { + const normalizedField = String(field || '').trim(); + if (normalizedField) { + fields.push(normalizedField); + } + } + } + } + return Array.from(new Set(fields)); + } + + const FLOW_RUNTIME_FIELDS = Object.freeze(listFlowFieldNames()); + const RUNTIME_TOP_LEVEL_FIELDS = Object.freeze([ + ...RUNTIME_SHARED_FIELDS, + ...RUNTIME_PROXY_FIELDS, + ...FLOW_RUNTIME_FIELDS, + ]); + const RUNTIME_TOP_LEVEL_FIELD_SET = new Set(RUNTIME_TOP_LEVEL_FIELDS); + const RUNTIME_PATCH_IGNORED_KEYS = new Set([ + 'runtimeState', + 'flowState', + 'sharedState', + 'serviceState', + 'flows', + 'shared', + 'services', + 'currentStep', + 'stepStatuses', + 'legacyStepCompat', + 'flowId', + 'runId', + 'activeFlowId', + 'activeRunId', + 'currentNodeId', + 'nodeStatuses', + ...RUNTIME_TOP_LEVEL_FIELDS, + ]); + + function projectScopedFlowFields(flowState = {}) { + const next = {}; + for (const [flowId, fieldGroups] of Object.entries(FLOW_FIELD_GROUPS)) { + const scopedState = normalizePlainObject(normalizePlainObject(flowState)[flowId]); + for (const [groupKey, fields] of Object.entries(fieldGroups)) { + const group = normalizePlainObject(scopedState[groupKey]); + Object.assign(next, pickDefinedFields(group, fields)); + } + } + return next; + } + + function projectRuntimeViewFields(runtimeState = {}) { + const normalizedRuntimeState = normalizePlainObject(runtimeState); + return { + ...pickDefinedFields( + normalizePlainObject(normalizedRuntimeState.sharedState), + RUNTIME_SHARED_FIELDS + ), + ...pickDefinedFields( + normalizePlainObject(normalizePlainObject(normalizedRuntimeState.serviceState).proxy), + RUNTIME_PROXY_FIELDS + ), + ...projectScopedFlowFields(normalizedRuntimeState.flowState), + }; + } + + function buildRuntimeInputFromPatch(updates = {}) { + const normalizedUpdates = normalizePlainObject(updates); + const runtimeStatePatch = normalizePlainObject(normalizedUpdates.runtimeState); + const next = { + ...pickDefinedFields(normalizedUpdates, [ + 'flowId', + 'runId', + 'activeFlowId', + 'activeRunId', + 'currentNodeId', + 'nodeStatuses', + ]), + ...pickDefinedFields(runtimeStatePatch, [ + 'flowId', + 'runId', + 'activeFlowId', + 'activeRunId', + 'currentNodeId', + 'nodeStatuses', + ]), + ...projectRuntimeViewFields(runtimeStatePatch), + ...pickDefinedFields(normalizedUpdates, RUNTIME_TOP_LEVEL_FIELDS), + }; + + if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'sharedState')) { + Object.assign( + next, + pickDefinedFields(normalizePlainObject(normalizedUpdates.sharedState), RUNTIME_SHARED_FIELDS) + ); + } + if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'serviceState')) { + Object.assign( + next, + pickDefinedFields( + normalizePlainObject(normalizePlainObject(normalizedUpdates.serviceState).proxy), + RUNTIME_PROXY_FIELDS + ) + ); + } + if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'flowState')) { + Object.assign(next, projectScopedFlowFields(normalizedUpdates.flowState)); + } + if (!Object.prototype.hasOwnProperty.call(next, 'flowId') && Object.prototype.hasOwnProperty.call(next, 'activeFlowId')) { + next.flowId = next.activeFlowId; + } + if (!Object.prototype.hasOwnProperty.call(next, 'runId') && Object.prototype.hasOwnProperty.call(next, 'activeRunId')) { + next.runId = next.activeRunId; + } + return next; + } + function buildRuntimeStateDefault() { return { flowId: DEFAULT_ACTIVE_FLOW_ID, @@ -366,69 +471,18 @@ nodeStatuses, sharedState: buildSharedState(baseRuntimeState.sharedState, state), serviceState: buildServiceState(baseRuntimeState.serviceState, state), - flowState: buildOpenAiFlowState(baseRuntimeState.flowState, state), + flowState: buildFlowState(baseRuntimeState.flowState, state), }; } - function buildFlattenedUpdates(updates = {}) { - const ignoredKeys = new Set(['current' + 'Step', 'step' + 'Statuses', 'legacy' + 'StepCompat']); + function buildPersistentPatchPayload(updates = {}) { const next = {}; - for (const [key, value] of Object.entries(updates || {})) { - if (!ignoredKeys.has(key)) { - next[key] = value; + for (const [key, value] of Object.entries(normalizePlainObject(updates))) { + if (RUNTIME_PATCH_IGNORED_KEYS.has(key)) { + continue; } + next[key] = cloneValue(value); } - const runtimeState = normalizePlainObject(updates.runtimeState); - const sharedState = normalizePlainObject(updates.sharedState); - const serviceState = normalizePlainObject(updates.serviceState); - const flowState = normalizePlainObject(updates.flowState); - - if (Object.prototype.hasOwnProperty.call(runtimeState, 'activeFlowId')) { - next.activeFlowId = runtimeState.activeFlowId; - } - if (Object.prototype.hasOwnProperty.call(runtimeState, 'flowId')) { - next.flowId = runtimeState.flowId; - next.activeFlowId = runtimeState.flowId; - } - if (Object.prototype.hasOwnProperty.call(runtimeState, 'activeRunId')) { - next.activeRunId = runtimeState.activeRunId; - } - if (Object.prototype.hasOwnProperty.call(runtimeState, 'runId')) { - next.runId = runtimeState.runId; - next.activeRunId = runtimeState.runId; - } - if (Object.prototype.hasOwnProperty.call(runtimeState, 'currentNodeId')) { - next.currentNodeId = runtimeState.currentNodeId; - } - if (Object.prototype.hasOwnProperty.call(runtimeState, 'nodeStatuses')) { - next.nodeStatuses = cloneValue(runtimeState.nodeStatuses); - } - Object.assign(next, pickDefinedFields(sharedState, RUNTIME_SHARED_FIELDS)); - if (Object.prototype.hasOwnProperty.call(runtimeState, 'sharedState')) { - Object.assign( - next, - pickDefinedFields(normalizePlainObject(runtimeState.sharedState), RUNTIME_SHARED_FIELDS) - ); - } - - const serviceProxy = normalizePlainObject(serviceState.proxy); - Object.assign(next, pickDefinedFields(serviceProxy, RUNTIME_PROXY_FIELDS)); - if (Object.prototype.hasOwnProperty.call(runtimeState, 'serviceState')) { - const runtimeServiceState = normalizePlainObject(runtimeState.serviceState); - Object.assign( - next, - pickDefinedFields(normalizePlainObject(runtimeServiceState.proxy), RUNTIME_PROXY_FIELDS) - ); - } - - Object.assign(next, flattenFlowStateById(flowState, 'openai')); - Object.assign(next, flattenFlowStateById(flowState, 'kiro')); - if (Object.prototype.hasOwnProperty.call(runtimeState, 'flowState')) { - const runtimeFlowState = normalizePlainObject(runtimeState.flowState); - Object.assign(next, flattenFlowStateById(runtimeFlowState, 'openai')); - Object.assign(next, flattenFlowStateById(runtimeFlowState, 'kiro')); - } - return next; } @@ -436,6 +490,7 @@ const runtimeState = ensureRuntimeState(state); return { ...state, + ...projectRuntimeViewFields(runtimeState), flowId: runtimeState.flowId, runId: runtimeState.runId, activeFlowId: runtimeState.activeFlowId, @@ -453,15 +508,15 @@ } function buildSessionStatePatch(currentState = {}, updates = {}) { - const flattenedUpdates = buildFlattenedUpdates(updates); - const nextState = { - ...currentState, - ...flattenedUpdates, - }; - const runtimeState = ensureRuntimeState(nextState); + const currentRuntimeState = ensureRuntimeState(currentState); + const runtimeState = ensureRuntimeState({ + runtimeState: currentRuntimeState, + ...projectRuntimeViewFields(currentRuntimeState), + ...buildRuntimeInputFromPatch(updates), + }); return { - ...flattenedUpdates, + ...buildPersistentPatchPayload(updates), flowId: runtimeState.flowId, runId: runtimeState.runId, activeFlowId: runtimeState.activeFlowId, diff --git a/background/steps/registry.js b/background/steps/registry.js index 072c513..780d394 100644 --- a/background/steps/registry.js +++ b/background/steps/registry.js @@ -4,10 +4,9 @@ function createNodeRegistry(definitions = []) { const ordered = (Array.isArray(definitions) ? definitions : []) .map((definition) => ({ - legacyStepId: Number(definition?.legacyStepId ?? definition?.id) || 0, flowId: String(definition?.flowId || '').trim(), nodeId: String(definition?.nodeId || definition?.key || '').trim(), - displayOrder: Number(definition?.displayOrder ?? definition?.order), + displayOrder: Number(definition?.displayOrder ?? definition?.order ?? definition?.id), executeKey: String(definition?.executeKey || definition?.key || definition?.nodeId || '').trim(), title: String(definition?.title || '').trim(), execute: definition?.execute, diff --git a/data/step-definitions.js b/data/step-definitions.js index 5548bb5..6bdc756 100644 --- a/data/step-definitions.js +++ b/data/step-definitions.js @@ -348,11 +348,10 @@ function cloneNodes(steps = [], options = {}, flowId = DEFAULT_ACTIVE_FLOW_ID) { const { builder } = getFlowDefinitionBuilder({ activeFlowId: flowId }); return steps.map((step) => ({ - legacyStepId: Number(step.id), nodeId: String(step.key || '').trim(), flowId, title: builder?.resolveStepTitle ? builder.resolveStepTitle(step, options) : step.title, - displayOrder: Number.isFinite(Number(step.order)) ? Number(step.order) : Number(step.id), + displayOrder: Number.isFinite(Number(step.id)) ? Number(step.id) : Number(step.order), nodeType: 'task', sourceId: step.sourceId || '', driverId: step.driverId || '', diff --git a/shared/flow-capabilities.js b/shared/flow-capabilities.js index 4ea7b1c..8d6af25 100644 --- a/shared/flow-capabilities.js +++ b/shared/flow-capabilities.js @@ -5,12 +5,11 @@ const flowRegistryApi = rootScope.MultiPageFlowRegistry || {}; const settingsSchemaApi = rootScope.MultiPageSettingsSchema || {}; const DEFAULT_FLOW_ID = flowRegistryApi.DEFAULT_FLOW_ID || 'openai'; - const DEFAULT_PANEL_MODE = flowRegistryApi.DEFAULT_OPENAI_SOURCE_ID || 'cpa'; - const LEGACY_OPENAI_FLOW_ALIAS = String(flowRegistryApi.LEGACY_OPENAI_FLOW_ALIAS || 'codex').trim().toLowerCase(); + const DEFAULT_OPENAI_INTEGRATION_TARGET_ID = flowRegistryApi.DEFAULT_OPENAI_INTEGRATION_TARGET_ID || 'cpa'; const SIGNUP_METHOD_EMAIL = 'email'; const SIGNUP_METHOD_PHONE = 'phone'; - const VALID_PANEL_MODES = Array.isArray(flowRegistryApi.OPENAI_SOURCE_IDS) - ? flowRegistryApi.OPENAI_SOURCE_IDS.slice() + const VALID_OPENAI_INTEGRATION_TARGET_IDS = Array.isArray(flowRegistryApi.OPENAI_INTEGRATION_TARGET_IDS) + ? flowRegistryApi.OPENAI_INTEGRATION_TARGET_IDS.slice() : ['cpa', 'sub2api', 'codex2api']; const REGISTERED_FLOW_IDS = Array.isArray(flowRegistryApi.getRegisteredFlowIds?.()) ? flowRegistryApi.getRegisteredFlowIds().map((flowId) => String(flowId || '').trim().toLowerCase()).filter(Boolean) @@ -23,7 +22,7 @@ supportsPhoneVerificationSettings: false, supportsPlusMode: false, supportsContributionMode: false, - supportsPlatformBinding: [], + supportedIntegrationTargets: [], supportsLuckmail: false, supportsOauthTimeoutBudget: false, canSwitchFlow: true, @@ -48,7 +47,7 @@ ) ); - const DEFAULT_PANEL_CAPABILITIES = Object.freeze({ + const DEFAULT_INTEGRATION_TARGET_CAPABILITIES = Object.freeze({ supportsPhoneSignup: true, requiresPhoneSignupWarning: false, }); @@ -61,9 +60,11 @@ 'plusModeEnabled', 'signupMethod', 'kiroSourceId', + 'openaiIntegrationTargetId', + 'kiroIntegrationTargetId', ]); - const PANEL_CAPABILITIES = Object.freeze({ + const OPENAI_INTEGRATION_TARGET_CAPABILITIES = Object.freeze({ cpa: Object.freeze({ supportsPhoneSignup: true, requiresPhoneSignupWarning: true, @@ -88,30 +89,26 @@ function normalizeCapabilityFlowId(value = '', fallback = DEFAULT_FLOW_ID) { const normalized = String(value || '').trim().toLowerCase(); - if (normalized === LEGACY_OPENAI_FLOW_ALIAS) { - return DEFAULT_FLOW_ID; - } if (normalized) { return normalized; } - const normalizedFallback = String(fallback || '').trim().toLowerCase(); - if (normalizedFallback === LEGACY_OPENAI_FLOW_ALIAS) { - return DEFAULT_FLOW_ID; - } - return normalizedFallback || DEFAULT_FLOW_ID; + return normalizeFlowId(fallback, DEFAULT_FLOW_ID); } function isRegisteredFlowId(flowId = '') { - return REGISTERED_FLOW_ID_SET.has(normalizeCapabilityFlowId(flowId, '')); + const normalized = String(flowId || '').trim().toLowerCase(); + return Boolean(normalized) && REGISTERED_FLOW_ID_SET.has(normalized); } - function normalizePanelMode(value = '', fallback = DEFAULT_PANEL_MODE) { + function normalizeOpenAiIntegrationTargetId(value = '', fallback = DEFAULT_OPENAI_INTEGRATION_TARGET_ID) { const normalized = String(value || '').trim().toLowerCase(); - if (VALID_PANEL_MODES.includes(normalized)) { + if (VALID_OPENAI_INTEGRATION_TARGET_IDS.includes(normalized)) { return normalized; } const fallbackValue = String(fallback || '').trim().toLowerCase(); - return VALID_PANEL_MODES.includes(fallbackValue) ? fallbackValue : DEFAULT_PANEL_MODE; + return VALID_OPENAI_INTEGRATION_TARGET_IDS.includes(fallbackValue) + ? fallbackValue + : DEFAULT_OPENAI_INTEGRATION_TARGET_ID; } function normalizeSignupMethod(value = '') { @@ -120,41 +117,50 @@ : SIGNUP_METHOD_EMAIL; } - function normalizePanelModeList(values = []) { + function normalizeOpenAiIntegrationTargetList(values = []) { if (!Array.isArray(values)) { return []; } const seen = new Set(); const normalized = []; values.forEach((value) => { - const mode = normalizePanelMode(value, ''); - if (!mode || seen.has(mode)) { + const integrationTargetId = normalizeOpenAiIntegrationTargetId(value, ''); + if (!integrationTargetId || seen.has(integrationTargetId)) { return; } - seen.add(mode); - normalized.push(mode); + seen.add(integrationTargetId); + normalized.push(integrationTargetId); }); return normalized; } - function getPanelModeLabel(panelMode = '') { - const normalized = normalizePanelMode(panelMode); + function getIntegrationTargetLabel(flowId = DEFAULT_FLOW_ID, integrationTargetId = '') { + if ( + isRegisteredFlowId(flowId) + && typeof flowRegistryApi.getIntegrationTargetLabel === 'function' + ) { + return flowRegistryApi.getIntegrationTargetLabel(flowId, integrationTargetId); + } + const normalized = String(integrationTargetId || '').trim().toLowerCase(); if (normalized === 'sub2api') { return 'SUB2API'; } if (normalized === 'codex2api') { return 'Codex2API'; } - return 'CPA'; + if (normalized === 'cpa') { + return 'CPA'; + } + return normalized || String(integrationTargetId || '').trim(); } function createFlowCapabilityRegistry(deps = {}) { const { defaultFlowCapabilities = DEFAULT_FLOW_CAPABILITIES, defaultFlowId = DEFAULT_FLOW_ID, - defaultPanelCapabilities = DEFAULT_PANEL_CAPABILITIES, + defaultIntegrationTargetCapabilities = DEFAULT_INTEGRATION_TARGET_CAPABILITIES, flowCapabilities = FLOW_CAPABILITIES, - panelCapabilities = PANEL_CAPABILITIES, + integrationTargetCapabilities = OPENAI_INTEGRATION_TARGET_CAPABILITIES, } = deps; const settingsSchema = settingsSchemaApi.createSettingsSchema ? settingsSchemaApi.createSettingsSchema({ @@ -165,21 +171,72 @@ function getFlowCapabilities(flowId) { const normalizedFlowId = normalizeCapabilityFlowId(flowId, defaultFlowId); const entry = flowCapabilities[normalizedFlowId] || null; + const supportedIntegrationTargets = normalizedFlowId === 'openai' + ? normalizeOpenAiIntegrationTargetList( + entry?.supportedIntegrationTargets || defaultFlowCapabilities.supportedIntegrationTargets + ) + : (Array.isArray(entry?.supportedIntegrationTargets) + ? entry.supportedIntegrationTargets.map((value) => String(value || '').trim().toLowerCase()).filter(Boolean) + : []); return { ...defaultFlowCapabilities, ...(entry || {}), - supportsPlatformBinding: normalizePanelModeList(entry?.supportsPlatformBinding || defaultFlowCapabilities.supportsPlatformBinding), + supportedIntegrationTargets, }; } - function getPanelCapabilities(panelMode) { - const normalizedPanelMode = normalizePanelMode(panelMode); + function getOpenAiIntegrationTargetCapabilities(integrationTargetId) { + const normalizedIntegrationTargetId = normalizeOpenAiIntegrationTargetId(integrationTargetId); return { - ...defaultPanelCapabilities, - ...(panelCapabilities[normalizedPanelMode] || {}), + ...defaultIntegrationTargetCapabilities, + ...(integrationTargetCapabilities[normalizedIntegrationTargetId] || {}), }; } + function normalizeRequestedIntegrationTargetId(activeFlowId, state = {}, options = {}) { + if (activeFlowId === 'openai') { + return normalizeOpenAiIntegrationTargetId( + options?.integrationTargetId + ?? options?.panelMode + ?? state?.openaiIntegrationTargetId + ?? state?.panelMode, + DEFAULT_OPENAI_INTEGRATION_TARGET_ID + ); + } + + const rawIntegrationTargetId = activeFlowId === 'kiro' + ? ( + options?.integrationTargetId + ?? state?.kiroIntegrationTargetId + ?? state?.kiroSourceId + ?? flowRegistryApi.getDefaultIntegrationTargetId?.(activeFlowId) + ?? '' + ) + : ( + options?.integrationTargetId + ?? state?.integrationTargetId + ?? state?.openaiIntegrationTargetId + ?? state?.panelMode + ?? state?.kiroIntegrationTargetId + ?? state?.kiroSourceId + ?? flowRegistryApi.getDefaultIntegrationTargetId?.(activeFlowId) + ?? '' + ); + + if ( + isRegisteredFlowId(activeFlowId) + && typeof flowRegistryApi.normalizeIntegrationTargetId === 'function' + ) { + return flowRegistryApi.normalizeIntegrationTargetId( + activeFlowId, + rawIntegrationTargetId, + flowRegistryApi.getDefaultIntegrationTargetId?.(activeFlowId) + ); + } + + return String(rawIntegrationTargetId || '').trim().toLowerCase(); + } + function normalizeChangedKeys(values = []) { const list = Array.isArray(values) ? values : []; const seen = new Set(); @@ -195,28 +252,33 @@ return normalized; } - function resolveEffectiveSourceId(activeFlowId, state = {}, requestedPanelMode = DEFAULT_PANEL_MODE) { + function resolveEffectiveIntegrationTargetId(activeFlowId, state = {}, requestedIntegrationTargetId = DEFAULT_OPENAI_INTEGRATION_TARGET_ID) { if (!isRegisteredFlowId(activeFlowId)) { - return normalizePanelMode(state?.panelMode || requestedPanelMode, requestedPanelMode || DEFAULT_PANEL_MODE); + return normalizeRequestedIntegrationTargetId(activeFlowId, state, { + integrationTargetId: requestedIntegrationTargetId, + }); } - if (settingsSchema?.getSelectedSourceId) { - const sourceFromSchema = settingsSchema.getSelectedSourceId({ + if (settingsSchema?.getSelectedIntegrationTargetId) { + const integrationTargetId = settingsSchema.getSelectedIntegrationTargetId({ ...state, activeFlowId, }, activeFlowId); - if (sourceFromSchema) { - return sourceFromSchema; + if (integrationTargetId) { + return integrationTargetId; } } - if (typeof flowRegistryApi.normalizeSourceId === 'function') { - if (activeFlowId === 'openai') { - return flowRegistryApi.normalizeSourceId('openai', state?.panelMode || requestedPanelMode, DEFAULT_PANEL_MODE); - } - return flowRegistryApi.normalizeSourceId(activeFlowId, state?.kiroSourceId || '', flowRegistryApi.getDefaultSourceId?.(activeFlowId)); + if (typeof flowRegistryApi.normalizeIntegrationTargetId === 'function') { + return flowRegistryApi.normalizeIntegrationTargetId( + activeFlowId, + activeFlowId === 'openai' + ? (state?.openaiIntegrationTargetId || state?.panelMode || requestedIntegrationTargetId) + : (state?.kiroIntegrationTargetId || state?.kiroSourceId || requestedIntegrationTargetId), + flowRegistryApi.getDefaultIntegrationTargetId?.(activeFlowId) + ); } return activeFlowId === 'openai' - ? normalizePanelMode(state?.panelMode || requestedPanelMode) - : ''; + ? normalizeOpenAiIntegrationTargetId(requestedIntegrationTargetId) + : String(requestedIntegrationTargetId || '').trim().toLowerCase(); } function resolveSidepanelCapabilities(options = {}) { @@ -226,19 +288,25 @@ defaultFlowId ); const flowState = getFlowCapabilities(activeFlowId); - const requestedPanelMode = normalizePanelMode( - options?.panelMode ?? state?.panelMode, - DEFAULT_PANEL_MODE + const requestedIntegrationTargetId = normalizeRequestedIntegrationTargetId( + activeFlowId, + state, + options ); - const supportedPanelModes = normalizePanelModeList(flowState.supportsPlatformBinding); - const panelModeSupported = supportedPanelModes.length === 0 + const supportedIntegrationTargets = activeFlowId === 'openai' + ? normalizeOpenAiIntegrationTargetList(flowState.supportedIntegrationTargets) + : (Array.isArray(flowState.supportedIntegrationTargets) + ? flowState.supportedIntegrationTargets.slice() + : []); + const integrationTargetSupported = supportedIntegrationTargets.length === 0 ? true - : supportedPanelModes.includes(requestedPanelMode); - const effectivePanelMode = panelModeSupported - ? requestedPanelMode - : (supportedPanelModes[0] || requestedPanelMode); - const panelState = getPanelCapabilities(effectivePanelMode); - const effectiveSourceId = resolveEffectiveSourceId(activeFlowId, state, effectivePanelMode); + : supportedIntegrationTargets.includes(requestedIntegrationTargetId); + const effectiveIntegrationTargetId = integrationTargetSupported + ? requestedIntegrationTargetId + : (supportedIntegrationTargets[0] || requestedIntegrationTargetId); + const integrationTargetState = activeFlowId === 'openai' + ? getOpenAiIntegrationTargetCapabilities(effectiveIntegrationTargetId) + : defaultIntegrationTargetCapabilities; const runtimeLocks = { autoRunLocked: Boolean(options?.autoRunLocked ?? state?.autoRunLocked), contributionMode: activeFlowId === 'openai' && flowState.supportsContributionMode && Boolean(state?.contributionMode), @@ -252,7 +320,7 @@ } const canSelectPhoneSignup = activeFlowId === 'openai' && Boolean(flowState.supportsPhoneSignup) - && Boolean(panelState.supportsPhoneSignup) + && Boolean(integrationTargetState.supportsPhoneSignup) && runtimeLocks.phoneVerificationEnabled && !runtimeLocks.plusModeEnabled && !runtimeLocks.contributionMode; @@ -272,7 +340,7 @@ : effectiveSignupMethods[0]); const visibleGroupIds = typeof flowRegistryApi.getVisibleGroupIds === 'function' && isRegisteredFlowId(activeFlowId) - ? flowRegistryApi.getVisibleGroupIds(activeFlowId, effectiveSourceId) + ? flowRegistryApi.getVisibleGroupIds(activeFlowId, effectiveIntegrationTargetId) : []; return { @@ -283,33 +351,38 @@ canShowPlusSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode), canSwitchFlow: Boolean(flowState.canSwitchFlow), canUsePhoneSignup: canSelectPhoneSignup, - canUseSelectedPanelMode: panelModeSupported, - effectivePanelMode, + canUseSelectedPanelMode: integrationTargetSupported, + effectiveIntegrationTargetId, + effectivePanelMode: effectiveIntegrationTargetId, effectiveSignupMethod, effectiveSignupMethods, - effectiveSourceId, + effectiveSourceId: effectiveIntegrationTargetId, flowCapabilities: flowState, - panelCapabilities: panelState, - panelMode: effectivePanelMode, - requestedPanelMode, + integrationTargetCapabilities: integrationTargetState, + panelCapabilities: integrationTargetState, + panelMode: effectiveIntegrationTargetId, + requestedIntegrationTargetId, + requestedPanelMode: requestedIntegrationTargetId, requestedSignupMethod, runtimeLocks, shouldWarnCpaPhoneSignup: effectiveSignupMethod === SIGNUP_METHOD_PHONE - && Boolean(panelState.requiresPhoneSignupWarning), + && Boolean(integrationTargetState.requiresPhoneSignupWarning), stepDefinitionOptions: { activeFlowId, - panelMode: effectivePanelMode, + integrationTargetId: effectiveIntegrationTargetId, + panelMode: effectiveIntegrationTargetId, plusModeEnabled: runtimeLocks.plusModeEnabled, signupMethod: effectiveSignupMethod, }, - supportedPanelModes, + supportedIntegrationTargets, + supportedPanelModes: supportedIntegrationTargets, visibleGroupIds, }; } function buildPhoneSignupValidationError(capabilityState = {}) { const flowState = capabilityState.flowCapabilities || {}; - const panelState = capabilityState.panelCapabilities || {}; + const integrationTargetState = capabilityState.integrationTargetCapabilities || {}; const runtimeLocks = capabilityState.runtimeLocks || {}; if (!flowState.supportsPhoneSignup) { @@ -318,10 +391,10 @@ message: '当前 flow 不支持手机号注册。', }; } - if (!panelState.supportsPhoneSignup) { + if (!integrationTargetState.supportsPhoneSignup) { return { code: 'phone_signup_panel_unsupported', - message: `当前来源 ${getPanelModeLabel(capabilityState.requestedPanelMode)} 不支持手机号注册。`, + message: `当前来源 ${getIntegrationTargetLabel(capabilityState.activeFlowId, capabilityState.requestedIntegrationTargetId)} 不支持手机号注册。`, }; } if (!runtimeLocks.phoneVerificationEnabled) { @@ -354,13 +427,13 @@ const errors = []; if ( - Array.isArray(capabilityState.supportedPanelModes) - && capabilityState.supportedPanelModes.length > 0 + Array.isArray(capabilityState.supportedIntegrationTargets) + && capabilityState.supportedIntegrationTargets.length > 0 && capabilityState.canUseSelectedPanelMode === false ) { errors.push({ code: 'panel_mode_unsupported', - message: `当前 flow 不支持 ${getPanelModeLabel(capabilityState.requestedPanelMode)} 来源。`, + message: `当前 flow 不支持 ${getIntegrationTargetLabel(capabilityState.activeFlowId, capabilityState.requestedIntegrationTargetId)} 来源。`, }); } @@ -408,15 +481,18 @@ const shouldReconcileSignupMethod = MODE_SWITCH_RELEVANT_KEYS.some((key) => changedKeySet.has(key)); if ( - changedKeySet.has('panelMode') - && Array.isArray(capabilityState.supportedPanelModes) - && capabilityState.supportedPanelModes.length > 0 + (changedKeySet.has('panelMode') || changedKeySet.has('openaiIntegrationTargetId') || changedKeySet.has('kiroIntegrationTargetId')) + && Array.isArray(capabilityState.supportedIntegrationTargets) + && capabilityState.supportedIntegrationTargets.length > 0 && capabilityState.canUseSelectedPanelMode === false ) { - normalizedUpdates.panelMode = capabilityState.effectivePanelMode; + normalizedUpdates.panelMode = capabilityState.effectiveIntegrationTargetId; + normalizedUpdates.openaiIntegrationTargetId = capabilityState.effectiveIntegrationTargetId; + normalizedUpdates.kiroIntegrationTargetId = capabilityState.effectiveIntegrationTargetId; + normalizedUpdates.kiroSourceId = capabilityState.effectiveIntegrationTargetId; errors.push({ code: 'panel_mode_unsupported', - message: `当前 flow 不支持 ${getPanelModeLabel(capabilityState.requestedPanelMode)} 来源。`, + message: `当前 flow 不支持 ${getIntegrationTargetLabel(capabilityState.activeFlowId, capabilityState.requestedIntegrationTargetId)} 来源。`, }); } @@ -480,9 +556,9 @@ return { canUsePhoneSignup, getFlowCapabilities, - getPanelCapabilities, + getOpenAiIntegrationTargetCapabilities, normalizeFlowId, - normalizePanelMode, + normalizeOpenAiIntegrationTargetId, normalizeSignupMethod, resolveSidepanelCapabilities, resolveSignupMethod, @@ -495,15 +571,15 @@ createFlowCapabilityRegistry, DEFAULT_FLOW_CAPABILITIES, DEFAULT_FLOW_ID, - DEFAULT_PANEL_CAPABILITIES, - DEFAULT_PANEL_MODE, + DEFAULT_INTEGRATION_TARGET_CAPABILITIES, + DEFAULT_OPENAI_INTEGRATION_TARGET_ID, FLOW_CAPABILITIES, - PANEL_CAPABILITIES, + OPENAI_INTEGRATION_TARGET_CAPABILITIES, SIGNUP_METHOD_EMAIL, SIGNUP_METHOD_PHONE, - VALID_PANEL_MODES, + VALID_OPENAI_INTEGRATION_TARGET_IDS, normalizeFlowId, - normalizePanelMode, + normalizeOpenAiIntegrationTargetId, normalizeSignupMethod, }; }); diff --git a/shared/flow-registry.js b/shared/flow-registry.js index acdc2bb..3d1d9ed 100644 --- a/shared/flow-registry.js +++ b/shared/flow-registry.js @@ -2,11 +2,11 @@ root.MultiPageFlowRegistry = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createFlowRegistryModule() { const DEFAULT_FLOW_ID = 'openai'; - const LEGACY_OPENAI_FLOW_ALIAS = 'codex'; - const DEFAULT_OPENAI_SOURCE_ID = 'cpa'; - const DEFAULT_KIRO_SOURCE_ID = 'kiro-rs'; + const DEFAULT_OPENAI_INTEGRATION_TARGET_ID = 'cpa'; + const DEFAULT_KIRO_INTEGRATION_TARGET_ID = 'kiro-rs'; + const DEFAULT_KIRO_PUBLICATION_TARGET_ID = 'kiro-rs'; const DEFAULT_KIRO_RS_URL = 'https://kiro.leftcode.xyz/admin'; - const OPENAI_SOURCE_IDS = Object.freeze(['cpa', 'sub2api', 'codex2api']); + const OPENAI_INTEGRATION_TARGET_IDS = Object.freeze(['cpa', 'sub2api', 'codex2api']); const SHARED_SERVICE_IDS = Object.freeze(['account', 'email', 'proxy']); const DEFAULT_FLOW_CAPABILITIES = Object.freeze({ @@ -15,7 +15,7 @@ supportsPhoneVerificationSettings: false, supportsPlusMode: false, supportsContributionMode: false, - supportsPlatformBinding: [], + supportedIntegrationTargets: [], supportsLuckmail: false, supportsOauthTimeoutBudget: false, canSwitchFlow: true, @@ -44,7 +44,7 @@ supportsPhoneVerificationSettings: true, supportsPlusMode: true, supportsContributionMode: true, - supportsPlatformBinding: [...OPENAI_SOURCE_IDS], + supportedIntegrationTargets: [...OPENAI_INTEGRATION_TARGET_IDS], supportsLuckmail: true, supportsOauthTimeoutBudget: true, stepDefinitionMode: 'openai-dynamic', @@ -55,24 +55,21 @@ 'openai-oauth', 'openai-step6', ], - sources: { + integrationTargets: { cpa: { id: 'cpa', label: 'CPA 面板', - legacyPanelMode: 'cpa', - groups: ['openai-source-cpa'], + groups: ['openai-target-cpa'], }, sub2api: { id: 'sub2api', label: 'SUB2API', - legacyPanelMode: 'sub2api', - groups: ['openai-source-sub2api'], + groups: ['openai-target-sub2api'], }, codex2api: { id: 'codex2api', label: 'Codex2API', - legacyPanelMode: 'codex2api', - groups: ['openai-source-codex2api'], + groups: ['openai-target-codex2api'], }, }, runtimeSources: { @@ -206,16 +203,23 @@ services: ['account', 'email', 'proxy'], capabilities: { ...DEFAULT_FLOW_CAPABILITIES, + supportedIntegrationTargets: [DEFAULT_KIRO_INTEGRATION_TARGET_ID], stepDefinitionMode: 'kiro-device-auth', }, baseGroups: [ 'kiro-runtime-status', ], - sources: { + integrationTargets: { + 'kiro-rs': { + id: 'kiro-rs', + label: 'kiro.rs', + groups: ['kiro-target-kiro-rs'], + }, + }, + publicationTargets: { 'kiro-rs': { id: 'kiro-rs', label: 'kiro.rs', - groups: ['kiro-source-kiro-rs'], }, }, runtimeSources: { @@ -280,13 +284,13 @@ label: 'IP 代理', sectionIds: ['ip-proxy-section'], }, - 'openai-source-cpa': { - id: 'openai-source-cpa', + 'openai-target-cpa': { + id: 'openai-target-cpa', label: 'CPA 来源', rowIds: ['row-vps-url', 'row-vps-password', 'row-local-cpa-step9-mode'], }, - 'openai-source-sub2api': { - id: 'openai-source-sub2api', + 'openai-target-sub2api': { + id: 'openai-target-sub2api', label: 'SUB2API 来源', rowIds: [ 'row-sub2api-url', @@ -297,17 +301,15 @@ 'row-sub2api-default-proxy', ], }, - 'openai-source-codex2api': { - id: 'openai-source-codex2api', + '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', - ], + rowIds: ['row-plus-mode'], }, 'openai-phone': { id: 'openai-phone', @@ -325,8 +327,8 @@ label: '第六步', rowIds: ['row-step6-cookie-settings'], }, - 'kiro-source-kiro-rs': { - id: 'kiro-source-kiro-rs', + 'kiro-target-kiro-rs': { + id: 'kiro-target-kiro-rs', label: 'kiro.rs 配置', rowIds: ['row-kiro-rs-url', 'row-kiro-rs-key'], }, @@ -339,16 +341,10 @@ function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) { const normalized = String(value || '').trim().toLowerCase(); - if (normalized === LEGACY_OPENAI_FLOW_ALIAS) { - return DEFAULT_FLOW_ID; - } if (normalized && Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, normalized)) { return normalized; } const fallbackValue = String(fallback || '').trim().toLowerCase(); - if (fallbackValue === LEGACY_OPENAI_FLOW_ALIAS) { - return DEFAULT_FLOW_ID; - } return Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, fallbackValue) ? fallbackValue : DEFAULT_FLOW_ID; @@ -359,73 +355,92 @@ } function getFlowDefinition(flowId) { - const normalizedFlowId = normalizeFlowId(flowId); - return FLOW_DEFINITIONS[normalizedFlowId] || FLOW_DEFINITIONS[DEFAULT_FLOW_ID]; + return FLOW_DEFINITIONS[normalizeFlowId(flowId)] || FLOW_DEFINITIONS[DEFAULT_FLOW_ID]; } function getFlowLabel(flowId) { return getFlowDefinition(flowId)?.label || normalizeFlowId(flowId); } - function getDefaultSourceId(flowId) { + function getDefaultIntegrationTargetId(flowId) { return normalizeFlowId(flowId) === 'kiro' - ? DEFAULT_KIRO_SOURCE_ID - : DEFAULT_OPENAI_SOURCE_ID; + ? DEFAULT_KIRO_INTEGRATION_TARGET_ID + : DEFAULT_OPENAI_INTEGRATION_TARGET_ID; } - function normalizeOpenAiSourceId(value = '', fallback = DEFAULT_OPENAI_SOURCE_ID) { + function normalizeOpenAiIntegrationTargetId(value = '', fallback = DEFAULT_OPENAI_INTEGRATION_TARGET_ID) { const normalized = String(value || '').trim().toLowerCase(); - if (OPENAI_SOURCE_IDS.includes(normalized)) { + if (OPENAI_INTEGRATION_TARGET_IDS.includes(normalized)) { return normalized; } const fallbackValue = String(fallback || '').trim().toLowerCase(); - return OPENAI_SOURCE_IDS.includes(fallbackValue) ? fallbackValue : DEFAULT_OPENAI_SOURCE_ID; + return OPENAI_INTEGRATION_TARGET_IDS.includes(fallbackValue) + ? fallbackValue + : DEFAULT_OPENAI_INTEGRATION_TARGET_ID; } - function normalizeKiroSourceId(value = '', fallback = DEFAULT_KIRO_SOURCE_ID) { + function normalizeKiroIntegrationTargetId(value = '', fallback = DEFAULT_KIRO_INTEGRATION_TARGET_ID) { const normalized = String(value || '').trim().toLowerCase(); - if (normalized === DEFAULT_KIRO_SOURCE_ID) { + if (normalized === DEFAULT_KIRO_INTEGRATION_TARGET_ID) { return normalized; } const fallbackValue = String(fallback || '').trim().toLowerCase(); - return fallbackValue === DEFAULT_KIRO_SOURCE_ID ? fallbackValue : DEFAULT_KIRO_SOURCE_ID; + return fallbackValue === DEFAULT_KIRO_INTEGRATION_TARGET_ID + ? fallbackValue + : DEFAULT_KIRO_INTEGRATION_TARGET_ID; } - function normalizeSourceId(flowId, sourceId = '', fallback = undefined) { + function normalizeIntegrationTargetId(flowId, integrationTargetId = '', fallback = undefined) { const normalizedFlowId = normalizeFlowId(flowId); if (normalizedFlowId === 'kiro') { - return normalizeKiroSourceId(sourceId, fallback || DEFAULT_KIRO_SOURCE_ID); + return normalizeKiroIntegrationTargetId( + integrationTargetId, + fallback || DEFAULT_KIRO_INTEGRATION_TARGET_ID + ); } - return normalizeOpenAiSourceId(sourceId, fallback || DEFAULT_OPENAI_SOURCE_ID); + return normalizeOpenAiIntegrationTargetId( + integrationTargetId, + fallback || DEFAULT_OPENAI_INTEGRATION_TARGET_ID + ); } - function getSourceDefinitions(flowId) { - return getFlowDefinition(flowId)?.sources || {}; + function getIntegrationTargetDefinitions(flowId) { + return getFlowDefinition(flowId)?.integrationTargets || {}; } - function getSourceDefinition(flowId, sourceId) { + function getIntegrationTargetDefinition(flowId, integrationTargetId) { const normalizedFlowId = normalizeFlowId(flowId); - const normalizedSourceId = normalizeSourceId(normalizedFlowId, sourceId, getDefaultSourceId(normalizedFlowId)); - return getSourceDefinitions(normalizedFlowId)[normalizedSourceId] || null; + const normalizedIntegrationTargetId = normalizeIntegrationTargetId( + normalizedFlowId, + integrationTargetId, + getDefaultIntegrationTargetId(normalizedFlowId) + ); + return getIntegrationTargetDefinitions(normalizedFlowId)[normalizedIntegrationTargetId] || null; } - function getSourceOptions(flowId) { - return Object.values(getSourceDefinitions(flowId)); + function getIntegrationTargetOptions(flowId) { + return Object.values(getIntegrationTargetDefinitions(flowId)); } - function getSourceLabel(flowId, sourceId) { - return getSourceDefinition(flowId, sourceId)?.label || normalizeSourceId(flowId, sourceId); + function getIntegrationTargetLabel(flowId, integrationTargetId) { + return getIntegrationTargetDefinition(flowId, integrationTargetId)?.label + || normalizeIntegrationTargetId(flowId, integrationTargetId); } - function mapPanelModeToSourceId(panelMode = '', fallback = DEFAULT_OPENAI_SOURCE_ID) { - return normalizeOpenAiSourceId(panelMode, fallback); + function getPublicationTargetDefinitions(flowId) { + return getFlowDefinition(flowId)?.publicationTargets || {}; } - function mapSourceIdToPanelMode(flowId, sourceId = '', fallback = DEFAULT_OPENAI_SOURCE_ID) { - if (normalizeFlowId(flowId) !== DEFAULT_FLOW_ID) { - return normalizeOpenAiSourceId(fallback, DEFAULT_OPENAI_SOURCE_ID); - } - return normalizeOpenAiSourceId(sourceId, fallback || DEFAULT_OPENAI_SOURCE_ID); + 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) { @@ -435,18 +450,27 @@ }; } - function getVisibleGroupIds(flowId, sourceId, options = {}) { + function getVisibleGroupIds(flowId, integrationTargetId, options = {}) { const normalizedFlowId = normalizeFlowId(flowId); const flowDefinition = getFlowDefinition(normalizedFlowId); - const normalizedSourceId = normalizeSourceId(normalizedFlowId, sourceId, getDefaultSourceId(normalizedFlowId)); - const sourceDefinition = getSourceDefinition(normalizedFlowId, normalizedSourceId); + const normalizedIntegrationTargetId = normalizeIntegrationTargetId( + normalizedFlowId, + integrationTargetId, + getDefaultIntegrationTargetId(normalizedFlowId) + ); + const integrationTargetDefinition = getIntegrationTargetDefinition( + normalizedFlowId, + normalizedIntegrationTargetId + ); const includeSharedServices = options?.includeSharedServices !== false; const serviceGroups = includeSharedServices - ? (Array.isArray(flowDefinition?.services) ? flowDefinition.services.map((serviceId) => `service-${serviceId}`) : []) + ? (Array.isArray(flowDefinition?.services) + ? flowDefinition.services.map((serviceId) => `service-${serviceId}`) + : []) : []; return Array.from(new Set([ ...(Array.isArray(flowDefinition?.baseGroups) ? flowDefinition.baseGroups : []), - ...(Array.isArray(sourceDefinition?.groups) ? sourceDefinition.groups : []), + ...(Array.isArray(integrationTargetDefinition?.groups) ? integrationTargetDefinition.groups : []), ...serviceGroups, ])); } @@ -479,33 +503,33 @@ return { DEFAULT_FLOW_CAPABILITIES, DEFAULT_FLOW_ID, + DEFAULT_KIRO_INTEGRATION_TARGET_ID, + DEFAULT_KIRO_PUBLICATION_TARGET_ID, DEFAULT_KIRO_RS_URL, - DEFAULT_KIRO_SOURCE_ID, - DEFAULT_OPENAI_SOURCE_ID, + DEFAULT_OPENAI_INTEGRATION_TARGET_ID, FLOW_DEFINITIONS, - LEGACY_OPENAI_FLOW_ALIAS, - OPENAI_SOURCE_IDS, + OPENAI_INTEGRATION_TARGET_IDS, SETTINGS_GROUP_DEFINITIONS, SHARED_SERVICE_IDS, - getDefaultSourceId, + getDefaultIntegrationTargetId, getDriverDefinitions, getFlowCapabilities, getFlowDefinition, getFlowLabel, + getIntegrationTargetDefinition, + getIntegrationTargetDefinitions, + getIntegrationTargetLabel, + getIntegrationTargetOptions, + getPublicationTargetDefinition, + getPublicationTargetDefinitions, getRegisteredFlowIds, getRuntimeSourceDefinitions, getSettingsGroupDefinition, getSettingsGroupDefinitions, - getSourceDefinition, - getSourceDefinitions, - getSourceLabel, - getSourceOptions, getVisibleGroupIds, - mapPanelModeToSourceId, - mapSourceIdToPanelMode, normalizeFlowId, - normalizeKiroSourceId, - normalizeOpenAiSourceId, - normalizeSourceId, + normalizeIntegrationTargetId, + normalizeKiroIntegrationTargetId, + normalizeOpenAiIntegrationTargetId, }; }); diff --git a/shared/settings-schema.js b/shared/settings-schema.js index 6cc441c..cc42150 100644 --- a/shared/settings-schema.js +++ b/shared/settings-schema.js @@ -32,9 +32,11 @@ function createSettingsSchema(deps = {}) { const rootScope = typeof self !== 'undefined' ? self : globalThis; const flowRegistry = deps.flowRegistry || rootScope.MultiPageFlowRegistry || {}; - const defaultFlowId = String(deps.defaultFlowId || flowRegistry.DEFAULT_FLOW_ID || 'openai').trim().toLowerCase() || 'openai'; - const defaultOpenAiSourceId = flowRegistry.DEFAULT_OPENAI_SOURCE_ID || 'cpa'; - const defaultKiroSourceId = flowRegistry.DEFAULT_KIRO_SOURCE_ID || 'kiro-rs'; + const defaultFlowId = String( + deps.defaultFlowId || flowRegistry.DEFAULT_FLOW_ID || 'openai' + ).trim().toLowerCase() || 'openai'; + const defaultOpenAiIntegrationTargetId = flowRegistry.DEFAULT_OPENAI_INTEGRATION_TARGET_ID || 'cpa'; + const defaultKiroIntegrationTargetId = flowRegistry.DEFAULT_KIRO_INTEGRATION_TARGET_ID || 'kiro-rs'; const defaultKiroRsUrl = flowRegistry.DEFAULT_KIRO_RS_URL || 'https://kiro.leftcode.xyz/admin'; const normalizeFlowId = typeof flowRegistry.normalizeFlowId === 'function' ? flowRegistry.normalizeFlowId @@ -42,19 +44,13 @@ const normalized = String(value || '').trim().toLowerCase(); return normalized || String(fallback || '').trim().toLowerCase() || defaultFlowId; }); - const normalizeSourceId = typeof flowRegistry.normalizeSourceId === 'function' - ? flowRegistry.normalizeSourceId - : ((flowId, value = '', fallback = '') => String(value || fallback || '').trim().toLowerCase()); - const mapSourceIdToPanelMode = typeof flowRegistry.mapSourceIdToPanelMode === 'function' - ? flowRegistry.mapSourceIdToPanelMode - : ((_flowId, sourceId = '', fallback = defaultOpenAiSourceId) => String(sourceId || fallback || defaultOpenAiSourceId).trim().toLowerCase()); - const mapPanelModeToSourceId = typeof flowRegistry.mapPanelModeToSourceId === 'function' - ? flowRegistry.mapPanelModeToSourceId - : ((panelMode = '', fallback = defaultOpenAiSourceId) => String(panelMode || fallback || defaultOpenAiSourceId).trim().toLowerCase()); + const normalizeIntegrationTargetId = typeof flowRegistry.normalizeIntegrationTargetId === 'function' + ? flowRegistry.normalizeIntegrationTargetId + : ((_flowId, value = '', fallback = '') => String(value || fallback || '').trim().toLowerCase()); function buildDefaultSettingsState() { return { - schemaVersion: 3, + schemaVersion: 4, activeFlowId: defaultFlowId, services: { account: { @@ -71,27 +67,25 @@ }, flows: { openai: { - source: { - selected: defaultOpenAiSourceId, - entries: { - cpa: { - vpsUrl: '', - vpsPassword: '', - localCpaStep9Mode: 'submit', - }, - sub2api: { - sub2apiUrl: '', - sub2apiEmail: '', - sub2apiPassword: '', - sub2apiGroupName: 'codex', - sub2apiGroupNames: ['codex', 'openai-plus'], - sub2apiAccountPriority: 1, - sub2apiDefaultProxyName: '', - }, - codex2api: { - codex2apiUrl: '', - codex2apiAdminKey: '', - }, + integrationTargetId: defaultOpenAiIntegrationTargetId, + integrationTargets: { + cpa: { + vpsUrl: '', + vpsPassword: '', + localCpaStep9Mode: 'submit', + }, + sub2api: { + sub2apiUrl: '', + sub2apiEmail: '', + sub2apiPassword: '', + sub2apiGroupName: 'codex', + sub2apiGroupNames: ['codex', 'openai-plus'], + sub2apiAccountPriority: 1, + sub2apiDefaultProxyName: '', + }, + codex2api: { + codex2apiUrl: '', + codex2apiAdminKey: '', }, }, signup: { @@ -112,21 +106,13 @@ }, }, kiro: { - source: { - selected: defaultKiroSourceId, - entries: { - 'kiro-rs': { - kiroRsUrl: defaultKiroRsUrl, - kiroRsKey: '', - }, + integrationTargetId: defaultKiroIntegrationTargetId, + integrationTargets: { + 'kiro-rs': { + baseUrl: defaultKiroRsUrl, + apiKey: '', }, }, - options: { - kiroRsPriority: 0, - kiroRsEndpoint: '', - kiroRsAuthRegion: '', - kiroRsApiRegion: '', - }, autoRun: { stepExecutionRange: { enabled: false, @@ -139,7 +125,7 @@ }; } - function getSourceValue(settingsState, pathGetter, fallback = {}) { + function getIntegrationTargetValue(settingsState, pathGetter, fallback = {}) { return cloneValue(pathGetter(isPlainObject(settingsState) ? settingsState : {}) || fallback); } @@ -155,20 +141,21 @@ ?? defaults.activeFlowId, defaults.activeFlowId ); - const openaiSelectedSource = normalizeSourceId( + const openaiIntegrationTargetId = normalizeIntegrationTargetId( 'openai', - nested?.flows?.openai?.source?.selected + nested?.flows?.openai?.integrationTargetId + ?? input?.openaiIntegrationTargetId ?? input?.panelMode - ?? input?.openaiSourceId - ?? defaults.flows.openai.source.selected, - defaults.flows.openai.source.selected + ?? defaults.flows.openai.integrationTargetId, + defaults.flows.openai.integrationTargetId ); - const kiroSelectedSource = normalizeSourceId( + const kiroIntegrationTargetId = normalizeIntegrationTargetId( 'kiro', - nested?.flows?.kiro?.source?.selected + nested?.flows?.kiro?.integrationTargetId + ?? input?.kiroIntegrationTargetId ?? input?.kiroSourceId - ?? defaults.flows.kiro.source.selected, - defaults.flows.kiro.source.selected + ?? defaults.flows.kiro.integrationTargetId, + defaults.flows.kiro.integrationTargetId ); const stepExecutionRangeByFlow = isPlainObject(input?.stepExecutionRangeByFlow) ? input.stepExecutionRangeByFlow @@ -206,58 +193,115 @@ customPassword: String( input?.customPassword ?? nested?.services?.account?.customPassword - ?? nested?.flows?.openai?.account?.customPassword ?? defaults.services.account.customPassword ).trim(), }, }, flows: { openai: { - source: { - selected: openaiSelectedSource, - entries: { - cpa: { - ...defaults.flows.openai.source.entries.cpa, - ...getSourceValue(nested, (state) => state.flows?.openai?.source?.entries?.cpa), - vpsUrl: String(input?.vpsUrl ?? nested?.flows?.openai?.source?.entries?.cpa?.vpsUrl ?? '').trim(), - vpsPassword: String(input?.vpsPassword ?? nested?.flows?.openai?.source?.entries?.cpa?.vpsPassword ?? ''), - localCpaStep9Mode: String( - input?.localCpaStep9Mode - ?? nested?.flows?.openai?.source?.entries?.cpa?.localCpaStep9Mode - ?? defaults.flows.openai.source.entries.cpa.localCpaStep9Mode - ).trim() || defaults.flows.openai.source.entries.cpa.localCpaStep9Mode, - }, - sub2api: { - ...defaults.flows.openai.source.entries.sub2api, - ...getSourceValue(nested, (state) => state.flows?.openai?.source?.entries?.sub2api), - sub2apiUrl: String(input?.sub2apiUrl ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiUrl ?? '').trim(), - sub2apiEmail: String(input?.sub2apiEmail ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiEmail ?? '').trim(), - sub2apiPassword: String(input?.sub2apiPassword ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiPassword ?? ''), - sub2apiGroupName: String(input?.sub2apiGroupName ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiGroupName ?? defaults.flows.openai.source.entries.sub2api.sub2apiGroupName).trim() || defaults.flows.openai.source.entries.sub2api.sub2apiGroupName, - sub2apiGroupNames: Array.isArray(input?.sub2apiGroupNames) - ? input.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean) - : (Array.isArray(nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiGroupNames) - ? nested.flows.openai.source.entries.sub2api.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean) - : [...defaults.flows.openai.source.entries.sub2api.sub2apiGroupNames]), - sub2apiAccountPriority: Math.max(1, Number(input?.sub2apiAccountPriority ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiAccountPriority ?? defaults.flows.openai.source.entries.sub2api.sub2apiAccountPriority) || defaults.flows.openai.source.entries.sub2api.sub2apiAccountPriority), - sub2apiDefaultProxyName: String(input?.sub2apiDefaultProxyName ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiDefaultProxyName ?? '').trim(), - }, - codex2api: { - ...defaults.flows.openai.source.entries.codex2api, - ...getSourceValue(nested, (state) => state.flows?.openai?.source?.entries?.codex2api), - codex2apiUrl: String(input?.codex2apiUrl ?? nested?.flows?.openai?.source?.entries?.codex2api?.codex2apiUrl ?? '').trim(), - codex2apiAdminKey: String(input?.codex2apiAdminKey ?? nested?.flows?.openai?.source?.entries?.codex2api?.codex2apiAdminKey ?? '').trim(), - }, + integrationTargetId: openaiIntegrationTargetId, + integrationTargets: { + cpa: { + ...defaults.flows.openai.integrationTargets.cpa, + ...getIntegrationTargetValue(nested, (state) => state.flows?.openai?.integrationTargets?.cpa), + vpsUrl: String( + input?.vpsUrl + ?? nested?.flows?.openai?.integrationTargets?.cpa?.vpsUrl + ?? '' + ).trim(), + vpsPassword: String( + input?.vpsPassword + ?? nested?.flows?.openai?.integrationTargets?.cpa?.vpsPassword + ?? '' + ), + localCpaStep9Mode: String( + input?.localCpaStep9Mode + ?? nested?.flows?.openai?.integrationTargets?.cpa?.localCpaStep9Mode + ?? defaults.flows.openai.integrationTargets.cpa.localCpaStep9Mode + ).trim() || defaults.flows.openai.integrationTargets.cpa.localCpaStep9Mode, + }, + sub2api: { + ...defaults.flows.openai.integrationTargets.sub2api, + ...getIntegrationTargetValue(nested, (state) => state.flows?.openai?.integrationTargets?.sub2api), + sub2apiUrl: String( + input?.sub2apiUrl + ?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiUrl + ?? '' + ).trim(), + sub2apiEmail: String( + input?.sub2apiEmail + ?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiEmail + ?? '' + ).trim(), + sub2apiPassword: String( + input?.sub2apiPassword + ?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiPassword + ?? '' + ), + sub2apiGroupName: String( + input?.sub2apiGroupName + ?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiGroupName + ?? defaults.flows.openai.integrationTargets.sub2api.sub2apiGroupName + ).trim() || defaults.flows.openai.integrationTargets.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]), + sub2apiAccountPriority: Math.max(1, Number( + input?.sub2apiAccountPriority + ?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiAccountPriority + ?? defaults.flows.openai.integrationTargets.sub2api.sub2apiAccountPriority + ) || defaults.flows.openai.integrationTargets.sub2api.sub2apiAccountPriority), + sub2apiDefaultProxyName: String( + input?.sub2apiDefaultProxyName + ?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiDefaultProxyName + ?? '' + ).trim(), + }, + codex2api: { + ...defaults.flows.openai.integrationTargets.codex2api, + ...getIntegrationTargetValue(nested, (state) => state.flows?.openai?.integrationTargets?.codex2api), + codex2apiUrl: String( + input?.codex2apiUrl + ?? nested?.flows?.openai?.integrationTargets?.codex2api?.codex2apiUrl + ?? '' + ).trim(), + codex2apiAdminKey: String( + input?.codex2apiAdminKey + ?? nested?.flows?.openai?.integrationTargets?.codex2api?.codex2apiAdminKey + ?? '' + ).trim(), }, }, signup: { - signupMethod: String(input?.signupMethod ?? nested?.flows?.openai?.signup?.signupMethod ?? defaults.flows.openai.signup.signupMethod).trim().toLowerCase() === 'phone' ? 'phone' : 'email', - phoneVerificationEnabled: Boolean(input?.phoneVerificationEnabled ?? nested?.flows?.openai?.signup?.phoneVerificationEnabled ?? defaults.flows.openai.signup.phoneVerificationEnabled), - phoneSignupReloginAfterBindEmailEnabled: Boolean(input?.phoneSignupReloginAfterBindEmailEnabled ?? nested?.flows?.openai?.signup?.phoneSignupReloginAfterBindEmailEnabled ?? defaults.flows.openai.signup.phoneSignupReloginAfterBindEmailEnabled), + signupMethod: String( + input?.signupMethod + ?? nested?.flows?.openai?.signup?.signupMethod + ?? defaults.flows.openai.signup.signupMethod + ).trim().toLowerCase() === 'phone' ? 'phone' : 'email', + phoneVerificationEnabled: Boolean( + input?.phoneVerificationEnabled + ?? nested?.flows?.openai?.signup?.phoneVerificationEnabled + ?? defaults.flows.openai.signup.phoneVerificationEnabled + ), + phoneSignupReloginAfterBindEmailEnabled: Boolean( + input?.phoneSignupReloginAfterBindEmailEnabled + ?? nested?.flows?.openai?.signup?.phoneSignupReloginAfterBindEmailEnabled + ?? defaults.flows.openai.signup.phoneSignupReloginAfterBindEmailEnabled + ), }, plus: { - plusModeEnabled: Boolean(input?.plusModeEnabled ?? nested?.flows?.openai?.plus?.plusModeEnabled ?? defaults.flows.openai.plus.plusModeEnabled), - plusPaymentMethod: String(input?.plusPaymentMethod ?? nested?.flows?.openai?.plus?.plusPaymentMethod ?? defaults.flows.openai.plus.plusPaymentMethod).trim() || defaults.flows.openai.plus.plusPaymentMethod, + plusModeEnabled: Boolean( + input?.plusModeEnabled + ?? nested?.flows?.openai?.plus?.plusModeEnabled + ?? defaults.flows.openai.plus.plusModeEnabled + ), + plusPaymentMethod: String( + input?.plusPaymentMethod + ?? nested?.flows?.openai?.plus?.plusPaymentMethod + ?? defaults.flows.openai.plus.plusPaymentMethod + ).trim() || defaults.flows.openai.plus.plusPaymentMethod, }, autoRun: { stepExecutionRange: normalizeStepExecutionRangeEntry( @@ -269,47 +313,25 @@ }, }, kiro: { - source: { - selected: kiroSelectedSource, - entries: { - 'kiro-rs': { - ...defaults.flows.kiro.source.entries['kiro-rs'], - ...getSourceValue(nested, (state) => state.flows?.kiro?.source?.entries?.['kiro-rs']), - kiroRsUrl: String( - input?.kiroRsUrl - ?? nested?.flows?.kiro?.source?.entries?.['kiro-rs']?.kiroRsUrl - ?? defaults.flows.kiro.source.entries['kiro-rs'].kiroRsUrl - ).trim() || defaults.flows.kiro.source.entries['kiro-rs'].kiroRsUrl, - kiroRsKey: String( - input?.kiroRsKey - ?? nested?.flows?.kiro?.source?.entries?.['kiro-rs']?.kiroRsKey - ?? defaults.flows.kiro.source.entries['kiro-rs'].kiroRsKey - ), - }, + integrationTargetId: kiroIntegrationTargetId, + integrationTargets: { + 'kiro-rs': { + ...defaults.flows.kiro.integrationTargets['kiro-rs'], + ...getIntegrationTargetValue(nested, (state) => state.flows?.kiro?.integrationTargets?.['kiro-rs']), + baseUrl: String( + input?.kiroRsUrl + ?? input?.kiroRsBaseUrl + ?? nested?.flows?.kiro?.integrationTargets?.['kiro-rs']?.baseUrl + ?? defaults.flows.kiro.integrationTargets['kiro-rs'].baseUrl + ).trim() || defaults.flows.kiro.integrationTargets['kiro-rs'].baseUrl, + apiKey: String( + input?.kiroRsKey + ?? input?.kiroRsApiKey + ?? nested?.flows?.kiro?.integrationTargets?.['kiro-rs']?.apiKey + ?? defaults.flows.kiro.integrationTargets['kiro-rs'].apiKey + ), }, }, - options: { - kiroRsPriority: Number( - input?.kiroRsPriority - ?? nested?.flows?.kiro?.options?.kiroRsPriority - ?? defaults.flows.kiro.options.kiroRsPriority - ) || 0, - kiroRsEndpoint: String( - input?.kiroRsEndpoint - ?? nested?.flows?.kiro?.options?.kiroRsEndpoint - ?? defaults.flows.kiro.options.kiroRsEndpoint - ).trim(), - kiroRsAuthRegion: String( - input?.kiroRsAuthRegion - ?? nested?.flows?.kiro?.options?.kiroRsAuthRegion - ?? defaults.flows.kiro.options.kiroRsAuthRegion - ).trim(), - kiroRsApiRegion: String( - input?.kiroRsApiRegion - ?? nested?.flows?.kiro?.options?.kiroRsApiRegion - ?? defaults.flows.kiro.options.kiroRsApiRegion - ).trim(), - }, autoRun: { stepExecutionRange: normalizeStepExecutionRangeEntry( nested?.flows?.kiro?.autoRun?.stepExecutionRange @@ -323,6 +345,53 @@ }; } + function mergeSettingsState(baseValue = {}, patchValue = {}) { + const baseSettingsState = normalizeSettingsState(baseValue); + const patchSettingsState = normalizeSettingsState({ + settingsState: patchValue, + activeFlowId: patchValue?.activeFlowId ?? baseSettingsState.activeFlowId, + }); + + function mergeRecursive(baseNode, patchNode) { + if (Array.isArray(patchNode)) { + return patchNode.map((entry) => cloneValue(entry)); + } + if (!isPlainObject(patchNode)) { + return patchNode === undefined ? cloneValue(baseNode) : patchNode; + } + const next = { + ...cloneValue(isPlainObject(baseNode) ? baseNode : {}), + }; + Object.entries(patchNode).forEach(([key, value]) => { + next[key] = mergeRecursive(baseNode?.[key], value); + }); + return next; + } + + return normalizeSettingsState({ + settingsState: mergeRecursive(baseSettingsState, patchSettingsState), + }); + } + + function getFlowSettings(settingsState = {}, flowId) { + const normalizedState = normalizeSettingsState(settingsState); + const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId); + return cloneValue(normalizedState?.flows?.[normalizedFlowId] || {}); + } + + function getSelectedIntegrationTargetId(settingsState = {}, flowId) { + const normalizedState = normalizeSettingsState(settingsState); + const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId); + const flowSettings = normalizedState?.flows?.[normalizedFlowId] || {}; + return normalizeIntegrationTargetId( + normalizedFlowId, + flowSettings?.integrationTargetId, + normalizedFlowId === 'kiro' + ? defaultKiroIntegrationTargetId + : defaultOpenAiIntegrationTargetId + ); + } + function buildStepExecutionRangeByFlow(settingsState = {}) { const normalizedState = normalizeSettingsState(settingsState); return { @@ -337,23 +406,7 @@ }; } - function getFlowSettings(settingsState = {}, flowId) { - const normalizedState = normalizeSettingsState(settingsState); - const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId); - return cloneValue(normalizedState?.flows?.[normalizedFlowId] || {}); - } - - function getSelectedSourceId(settingsState = {}, flowId) { - const flowSettings = getFlowSettings(settingsState, flowId); - const normalizedFlowId = normalizeFlowId(flowId, normalizeSettingsState(settingsState).activeFlowId); - return normalizeSourceId( - normalizedFlowId, - flowSettings?.source?.selected, - normalizedFlowId === 'kiro' ? defaultKiroSourceId : defaultOpenAiSourceId - ); - } - - function buildLegacySettingsPayload(settingsState = {}, baseInput = {}) { + function buildSettingsView(settingsState = {}, baseInput = {}) { const normalizedState = normalizeSettingsState(settingsState); const next = { ...(isPlainObject(baseInput) ? cloneValue(baseInput) : {}), @@ -361,20 +414,22 @@ const openaiState = normalizedState.flows.openai; const kiroState = normalizedState.flows.kiro; next.activeFlowId = normalizedState.activeFlowId; - next.panelMode = mapSourceIdToPanelMode('openai', openaiState.source.selected, defaultOpenAiSourceId); - next.kiroSourceId = getSelectedSourceId(normalizedState, 'kiro'); - next.vpsUrl = openaiState.source.entries.cpa.vpsUrl; - next.vpsPassword = openaiState.source.entries.cpa.vpsPassword; - next.localCpaStep9Mode = openaiState.source.entries.cpa.localCpaStep9Mode; - next.sub2apiUrl = openaiState.source.entries.sub2api.sub2apiUrl; - next.sub2apiEmail = openaiState.source.entries.sub2api.sub2apiEmail; - next.sub2apiPassword = openaiState.source.entries.sub2api.sub2apiPassword; - next.sub2apiGroupName = openaiState.source.entries.sub2api.sub2apiGroupName; - next.sub2apiGroupNames = cloneValue(openaiState.source.entries.sub2api.sub2apiGroupNames); - next.sub2apiAccountPriority = openaiState.source.entries.sub2api.sub2apiAccountPriority; - next.sub2apiDefaultProxyName = openaiState.source.entries.sub2api.sub2apiDefaultProxyName; - next.codex2apiUrl = openaiState.source.entries.codex2api.codex2apiUrl; - next.codex2apiAdminKey = openaiState.source.entries.codex2api.codex2apiAdminKey; + next.openaiIntegrationTargetId = getSelectedIntegrationTargetId(normalizedState, 'openai'); + next.kiroIntegrationTargetId = getSelectedIntegrationTargetId(normalizedState, 'kiro'); + next.panelMode = next.openaiIntegrationTargetId; + next.kiroSourceId = next.kiroIntegrationTargetId; + 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.customPassword = normalizedState.services.account.customPassword; next.signupMethod = openaiState.signup.signupMethod; next.phoneVerificationEnabled = openaiState.signup.phoneVerificationEnabled; @@ -385,13 +440,8 @@ next.ipProxyEnabled = normalizedState.services.proxy.enabled; next.ipProxyService = normalizedState.services.proxy.provider; next.ipProxyMode = normalizedState.services.proxy.mode; - next.kiroRsUrl = kiroState.source.entries['kiro-rs'].kiroRsUrl; - next.kiroRsKey = kiroState.source.entries['kiro-rs'].kiroRsKey; - next.kiroRsPriority = kiroState.options.kiroRsPriority; - next.kiroRsEndpoint = kiroState.options.kiroRsEndpoint; - next.kiroRsAuthRegion = kiroState.options.kiroRsAuthRegion; - next.kiroRsApiRegion = kiroState.options.kiroRsApiRegion; - delete next.kiroRegion; + next.kiroRsUrl = kiroState.integrationTargets['kiro-rs'].baseUrl; + next.kiroRsKey = kiroState.integrationTargets['kiro-rs'].apiKey; next.stepExecutionRangeByFlow = buildStepExecutionRangeByFlow(normalizedState); next.settingsSchemaVersion = normalizedState.schemaVersion; next.settingsState = cloneValue(normalizedState); @@ -401,34 +451,29 @@ function getFlowInputState(settingsState = {}, flowId) { const normalizedState = normalizeSettingsState(settingsState); const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId); + const integrationTargetId = getSelectedIntegrationTargetId(normalizedState, normalizedFlowId); if (normalizedFlowId === 'kiro') { return { activeFlowId: normalizedFlowId, - sourceId: getSelectedSourceId(normalizedState, 'kiro'), - kiroRsUrl: normalizedState.flows.kiro.source.entries['kiro-rs'].kiroRsUrl, - kiroRsKey: normalizedState.flows.kiro.source.entries['kiro-rs'].kiroRsKey, + integrationTargetId, + kiroRsUrl: normalizedState.flows.kiro.integrationTargets['kiro-rs'].baseUrl, + kiroRsKey: normalizedState.flows.kiro.integrationTargets['kiro-rs'].apiKey, }; } return { activeFlowId: normalizedFlowId, - sourceId: getSelectedSourceId(normalizedState, 'openai'), - panelMode: mapSourceIdToPanelMode('openai', normalizedState.flows.openai.source.selected, defaultOpenAiSourceId), + integrationTargetId, }; } - function normalizeFlatInput(input = {}) { - const state = normalizeSettingsState(input); - return buildLegacySettingsPayload(state, input); - } - return { buildDefaultSettingsState, - buildLegacySettingsPayload, + buildSettingsView, buildStepExecutionRangeByFlow, getFlowInputState, getFlowSettings, - getSelectedSourceId, - normalizeFlatInput, + getSelectedIntegrationTargetId, + mergeSettingsState, normalizeSettingsState, }; } diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 73cb5d9..54e9d8c 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -683,10 +683,10 @@
- 步骤 - + 节点 + - +
diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 5c747e9..31ac7f0 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -884,18 +884,17 @@ function getWorkflowNodesForMode(plusModeEnabled = false, options = {}) { }); if (Array.isArray(nodes) && nodes.length) { return nodes.slice().sort((left, right) => { - const leftOrder = Number.isFinite(Number(left.displayOrder)) ? Number(left.displayOrder) : Number(left.legacyStepId); - const rightOrder = Number.isFinite(Number(right.displayOrder)) ? Number(right.displayOrder) : Number(right.legacyStepId); + const leftOrder = Number.isFinite(Number(left.displayOrder)) ? Number(left.displayOrder) : 0; + const rightOrder = Number.isFinite(Number(right.displayOrder)) ? Number(right.displayOrder) : 0; if (leftOrder !== rightOrder) return leftOrder - rightOrder; return String(left.nodeId || '').localeCompare(String(right.nodeId || '')); }); } return getStepDefinitionsForMode(plusModeEnabled, options).map((step) => ({ - legacyStepId: Number(step.id), nodeId: String(step.key || '').trim(), title: step.title, - displayOrder: Number.isFinite(Number(step.order)) ? Number(step.order) : Number(step.id), + displayOrder: Number.isFinite(Number(step.id)) ? Number(step.id) : Number(step.order), executeKey: String(step.key || '').trim(), })).filter((node) => node.nodeId); } @@ -911,7 +910,7 @@ function getStepIdByKeyForCurrentMode(stepKey = '') { function getNodeIdByStepForCurrentMode(step) { const numericStep = Number(step); - const node = (workflowNodes || []).find((candidate) => Number(candidate?.legacyStepId) === numericStep); + const node = (workflowNodes || []).find((candidate) => Number(candidate?.displayOrder) === numericStep); if (node?.nodeId) { return String(node.nodeId).trim(); } @@ -925,9 +924,9 @@ function getStepIdByNodeIdForCurrentMode(nodeId = '') { return 0; } const node = (workflowNodes || []).find((candidate) => String(candidate?.nodeId || '').trim() === normalizedNodeId); - const legacyStepId = Number(node?.legacyStepId); - if (Number.isInteger(legacyStepId) && legacyStepId > 0) { - return legacyStepId; + const displayOrder = Number(node?.displayOrder); + if (Number.isInteger(displayOrder) && displayOrder > 0) { + return displayOrder; } return getStepIdByKeyForCurrentMode(normalizedNodeId); } @@ -970,10 +969,9 @@ function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) { phoneSignupReloginAfterBindEmailEnabled: currentPhoneSignupReloginAfterBindEmailEnabled, }) : stepDefinitions.map((step) => ({ - legacyStepId: Number(step.id), nodeId: String(step.key || step.id || '').trim(), title: step.title, - displayOrder: Number.isFinite(Number(step.order)) ? Number(step.order) : Number(step.id), + displayOrder: Number.isFinite(Number(step.id)) ? Number(step.id) : Number(step.order), })); if (typeof workflowNodes !== 'undefined') { workflowNodes = nextWorkflowNodes; @@ -2436,24 +2434,76 @@ function getLastCurrentStepId() { return STEP_IDS.length ? Math.max(...STEP_IDS) : 1; } +function getStepExecutionRangeNodes() { + return Array.isArray(workflowNodes) + ? workflowNodes.filter((node) => String(node?.nodeId || '').trim()) + : []; +} + +function getStepExecutionRangeNodeLabel(node = {}) { + const nodeId = String(node?.nodeId || '').trim(); + const displayOrder = Number(node?.displayOrder); + const title = String(node?.title || nodeId).trim(); + const orderLabel = Number.isInteger(displayOrder) && displayOrder > 0 + ? `步骤 ${displayOrder}` + : nodeId; + return title ? `${orderLabel} · ${title}` : orderLabel; +} + +function getStepExecutionRangeBoundaryNodeId(stepNumber, boundary = 'start') { + const nodes = getStepExecutionRangeNodes(); + const fallbackNode = boundary === 'end' ? nodes[nodes.length - 1] : nodes[0]; + const resolvedNodeId = getNodeIdByStepForCurrentMode(stepNumber); + return String(resolvedNodeId || fallbackNode?.nodeId || '').trim(); +} + +function syncStepExecutionRangeSelectOptions(selectedFromNodeId = '', selectedToNodeId = '') { + const nodes = getStepExecutionRangeNodes(); + const fromSelect = inputStepExecutionRangeFrom; + const toSelect = inputStepExecutionRangeTo; + if (!fromSelect || !toSelect) { + return; + } + + const buildOptions = (selectedValue) => nodes.map((node) => { + const nodeId = String(node?.nodeId || '').trim(); + const label = getStepExecutionRangeNodeLabel(node); + return ``; + }).join(''); + + fromSelect.innerHTML = buildOptions(String(selectedFromNodeId || nodes[0]?.nodeId || '').trim()); + toSelect.innerHTML = buildOptions(String(selectedToNodeId || nodes[nodes.length - 1]?.nodeId || '').trim()); +} + function isStepExecutionRangeUiAvailable(state = latestState) { return getCurrentStepExecutionRangeFlowId(state) === DEFAULT_ACTIVE_FLOW_ID; } function clampStepExecutionRangeInputs() { - const maxStep = getLastCurrentStepId(); - const fromStep = Math.min(maxStep, Math.max(1, normalizePositiveStepNumber(inputStepExecutionRangeFrom?.value, 1))); - const toStep = Math.min(maxStep, Math.max(1, normalizePositiveStepNumber(inputStepExecutionRangeTo?.value, maxStep))); - const normalizedFrom = Math.min(fromStep, toStep); - const normalizedTo = Math.max(fromStep, toStep); + const nodes = getStepExecutionRangeNodes(); + const firstNodeId = String(nodes[0]?.nodeId || '').trim(); + const lastNodeId = String(nodes[nodes.length - 1]?.nodeId || '').trim(); + const selectedFromNodeId = String(inputStepExecutionRangeFrom?.value || firstNodeId).trim() || firstNodeId; + const selectedToNodeId = String(inputStepExecutionRangeTo?.value || lastNodeId).trim() || lastNodeId; + const fromStep = Math.max(1, getStepIdByNodeIdForCurrentMode(selectedFromNodeId) || 1); + const toStep = Math.max(1, getStepIdByNodeIdForCurrentMode(selectedToNodeId) || fromStep); + const normalizedFromStep = Math.min(fromStep, toStep); + const normalizedToStep = Math.max(fromStep, toStep); + const normalizedFromNodeId = getStepExecutionRangeBoundaryNodeId(normalizedFromStep, 'start'); + const normalizedToNodeId = getStepExecutionRangeBoundaryNodeId(normalizedToStep, 'end'); + syncStepExecutionRangeSelectOptions(normalizedFromNodeId, normalizedToNodeId); if (inputStepExecutionRangeFrom) { - inputStepExecutionRangeFrom.max = String(maxStep); - inputStepExecutionRangeFrom.value = String(normalizedFrom); + inputStepExecutionRangeFrom.value = normalizedFromNodeId; } if (inputStepExecutionRangeTo) { - inputStepExecutionRangeTo.max = String(maxStep); - inputStepExecutionRangeTo.value = String(normalizedTo); + inputStepExecutionRangeTo.value = normalizedToNodeId; } + return { + fromNodeId: normalizedFromNodeId, + toNodeId: normalizedToNodeId, + fromStep: normalizedFromStep, + toStep: normalizedToStep, + }; } function buildStepExecutionRangeByFlowPayload(existingConfig = latestState?.stepExecutionRangeByFlow || {}) { @@ -2461,12 +2511,12 @@ function buildStepExecutionRangeByFlowPayload(existingConfig = latestState?.step if (!isStepExecutionRangeUiAvailable(latestState)) { return config; } - clampStepExecutionRangeInputs(); + const normalizedRange = clampStepExecutionRangeInputs(); const flowId = getCurrentStepExecutionRangeFlowId(latestState); config[flowId] = normalizeStepExecutionRangeEntry({ enabled: Boolean(inputStepExecutionRangeEnabled?.checked), - fromStep: inputStepExecutionRangeFrom?.value, - toStep: inputStepExecutionRangeTo?.value, + fromStep: normalizedRange?.fromStep, + toStep: normalizedRange?.toStep, }); return config; } @@ -2493,17 +2543,15 @@ function applyStepExecutionRangeState(state = latestState) { } const available = isStepExecutionRangeUiAvailable(state); rowStepExecutionRange.style.display = available ? '' : 'none'; - const maxStep = getLastCurrentStepId(); const range = getStepExecutionRangeForCurrentFlow(state); + const fromNodeId = getStepExecutionRangeBoundaryNodeId(range.fromStep, 'start'); + const toNodeId = getStepExecutionRangeBoundaryNodeId(range.toStep, 'end'); + syncStepExecutionRangeSelectOptions(fromNodeId, toNodeId); if (inputStepExecutionRangeFrom) { - inputStepExecutionRangeFrom.min = '1'; - inputStepExecutionRangeFrom.max = String(maxStep); - inputStepExecutionRangeFrom.value = String(Math.min(maxStep, Math.max(1, normalizePositiveStepNumber(range.fromStep, 1)))); + inputStepExecutionRangeFrom.value = fromNodeId; } if (inputStepExecutionRangeTo) { - inputStepExecutionRangeTo.min = '1'; - inputStepExecutionRangeTo.max = String(maxStep); - inputStepExecutionRangeTo.value = String(Math.min(maxStep, Math.max(1, normalizePositiveStepNumber(range.toStep, maxStep)))); + inputStepExecutionRangeTo.value = toNodeId; } if (inputStepExecutionRangeEnabled) { inputStepExecutionRangeEnabled.checked = Boolean(range.enabled); @@ -14932,14 +14980,16 @@ selectFlow?.addEventListener('change', () => { }); [inputStepExecutionRangeFrom, inputStepExecutionRangeTo].forEach((input) => { - input?.addEventListener('input', () => { + const handleRangeChange = () => { const stepExecutionRangeByFlow = buildStepExecutionRangeByFlowPayload(latestState?.stepExecutionRangeByFlow); syncLatestState({ stepExecutionRangeByFlow }); markSettingsDirty(true); renderStepStatuses(latestState); updateButtonStates(); scheduleSettingsAutoSave(); - }); + }; + input?.addEventListener('input', handleRangeChange); + input?.addEventListener('change', handleRangeChange); input?.addEventListener('blur', () => { clampStepExecutionRangeInputs(); saveSettings({ silent: true }).catch(() => { }); diff --git a/tests/background-runtime-state-module.test.js b/tests/background-runtime-state-module.test.js index 19ad344..70f413c 100644 --- a/tests/background-runtime-state-module.test.js +++ b/tests/background-runtime-state-module.test.js @@ -124,8 +124,8 @@ test('runtime-state patch accepts nested flow and node updates without legacy st assert.equal(patch.activeFlowId, 'openai'); assert.equal(patch.activeRunId, 'run-001'); assert.equal(patch.currentNodeId, 'oauth-login'); - assert.equal(patch.oauthUrl, 'https://new.example.com/start'); - assert.equal(patch.plusCheckoutTabId, 99); + assert.equal(Object.prototype.hasOwnProperty.call(patch, 'oauthUrl'), false); + assert.equal(Object.prototype.hasOwnProperty.call(patch, 'plusCheckoutTabId'), false); assert.equal(Object.prototype.hasOwnProperty.call(patch, 'currentStep'), false); assert.equal(Object.prototype.hasOwnProperty.call(patch, 'stepStatuses'), false); assert.deepStrictEqual(patch.nodeStatuses, { @@ -135,6 +135,10 @@ test('runtime-state patch accepts nested flow and node updates without legacy st }); assert.equal(patch.runtimeState.flowState.openai.auth.oauthUrl, 'https://new.example.com/start'); assert.equal(patch.runtimeState.flowState.openai.plus.plusCheckoutTabId, 99); + + const view = helpers.buildStateView(patch); + assert.equal(view.oauthUrl, 'https://new.example.com/start'); + assert.equal(view.plusCheckoutTabId, 99); }); test('runtime-state patch prefers explicit activeFlowId over stale legacy flowId', () => { diff --git a/tests/background-settings-schema-persistence.test.js b/tests/background-settings-schema-persistence.test.js index 5495fc0..305d253 100644 --- a/tests/background-settings-schema-persistence.test.js +++ b/tests/background-settings-schema-persistence.test.js @@ -57,6 +57,39 @@ ${flowRegistrySource} ${settingsSchemaSource} const DEFAULT_ACTIVE_FLOW_ID = 'openai'; const DEFAULT_SUB2API_GROUP_NAMES = ['codex', 'openai-plus']; +const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([ + 'activeFlowId', + 'openaiIntegrationTargetId', + 'kiroIntegrationTargetId', + 'panelMode', + 'kiroSourceId', + 'vpsUrl', + 'vpsPassword', + 'localCpaStep9Mode', + 'sub2apiUrl', + 'sub2apiEmail', + 'sub2apiPassword', + 'sub2apiGroupName', + 'sub2apiGroupNames', + 'sub2apiAccountPriority', + 'sub2apiDefaultProxyName', + 'codex2apiUrl', + 'codex2apiAdminKey', + 'customPassword', + 'signupMethod', + 'phoneVerificationEnabled', + 'phoneSignupReloginAfterBindEmailEnabled', + 'plusModeEnabled', + 'plusPaymentMethod', + 'mailProvider', + 'ipProxyEnabled', + 'ipProxyService', + 'ipProxyMode', + 'kiroRsUrl', + 'kiroRsKey', + 'stepExecutionRangeByFlow', +]); +const SETTINGS_SCHEMA_VIEW_KEY_SET = new Set(SETTINGS_SCHEMA_VIEW_KEYS); const PERSISTED_SETTING_DEFAULTS = { activeFlowId: DEFAULT_ACTIVE_FLOW_ID, panelMode: 'cpa', @@ -77,6 +110,9 @@ const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS); const PERSISTED_SETTINGS_SCHEMA_KEYS = ['settingsSchemaVersion', 'settingsState']; const LEGACY_AUTO_STEP_DELAY_KEYS = []; const LEGACY_VERIFICATION_RESEND_COUNT_KEYS = []; +function isPlainObjectValue(value) { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value); +} function normalizePanelMode(value = '') { const normalized = String(value || '').trim().toLowerCase(); return normalized === 'sub2api' || normalized === 'codex2api' ? normalized : 'cpa'; @@ -131,6 +167,8 @@ function resolveSignupMethod(state = {}) { function resolveLegacyAutoStepDelaySeconds() { return undefined; } ${extractFunction('normalizePersistentSettingValue')} ${extractFunction('getSettingsSchemaApi')} +${extractFunction('projectSettingsSchemaView')} +${extractFunction('buildPersistedSettingsStoragePayload')} ${extractFunction('buildPersistentSettingsPayload')} ${extractFunction('getPersistedSettings')} ${extractFunction('setPersistentSettings')} @@ -141,6 +179,7 @@ return { setPersistentSettings, getRequestedKeys: typeof getRequestedKeys === 'function' ? getRequestedKeys : () => [], getPersistedWrites: typeof getPersistedWrites === 'function' ? getPersistedWrites : () => [], + getRemovedKeys: typeof getRemovedKeys === 'function' ? getRemovedKeys : () => [], }; `)(); } @@ -159,26 +198,50 @@ test('buildPersistentSettingsPayload writes canonical settings schema into persi 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, 3); + assert.equal(payload.settingsSchemaVersion, 4); assert.equal(payload.settingsState.activeFlowId, 'kiro'); - assert.equal(payload.settingsState.flows.kiro.source.selected, 'kiro-rs'); + assert.equal(payload.settingsState.flows.kiro.integrationTargetId, 'kiro-rs'); + assert.equal( + payload.settingsState.flows.kiro.integrationTargets['kiro-rs'].baseUrl, + 'https://kiro.example.com/admin' + ); }); test('buildPersistentSettingsPayload accepts schema-only input when requireKnownKeys is enabled', () => { const api = buildHarness(); const payload = api.buildPersistentSettingsPayload({ - settingsSchemaVersion: 3, + settingsSchemaVersion: 4, settingsState: { activeFlowId: 'kiro', services: { + account: { customPassword: '' }, email: { provider: '163' }, proxy: { enabled: false, provider: '711proxy', mode: 'account' }, }, flows: { openai: { - source: { selected: 'cpa', entries: {} }, - account: { customPassword: '' }, + integrationTargetId: 'cpa', + integrationTargets: { + 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, @@ -193,17 +256,13 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown }, }, kiro: { - source: { - selected: 'kiro-rs', - entries: { - 'kiro-rs': { - kiroRsUrl: 'https://kiro.example.com/admin', - kiroRsKey: 'schema-only-key', - }, + integrationTargetId: 'kiro-rs', + integrationTargets: { + 'kiro-rs': { + baseUrl: 'https://kiro.example.com/admin', + apiKey: 'schema-only-key', }, }, - options: { - }, autoRun: { stepExecutionRange: { enabled: true, fromStep: 1, toStep: 7 }, }, @@ -217,7 +276,7 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown 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, 3); + assert.equal(payload.settingsSchemaVersion, 4); }); test('getPersistedSettings reads schema keys alongside legacy flat settings keys', async () => { @@ -242,7 +301,7 @@ function getRequestedKeys() { assert.ok(api.getRequestedKeys().includes('settingsSchemaVersion')); assert.ok(api.getRequestedKeys().includes('settingsState')); - assert.equal(state.settingsSchemaVersion, 3); + assert.equal(state.settingsSchemaVersion, 4); assert.equal(state.settingsState.activeFlowId, 'openai'); }); @@ -253,20 +312,37 @@ const chrome = { local: { async get() { return { - settingsSchemaVersion: 3, + settingsSchemaVersion: 4, settingsState: { activeFlowId: 'kiro', services: { + account: { customPassword: '' }, email: { provider: 'hotmail' }, proxy: { enabled: true, provider: '711proxy', mode: 'account' }, }, flows: { openai: { - source: { - selected: 'sub2api', - entries: {}, + integrationTargetId: 'sub2api', + integrationTargets: { + cpa: { + vpsUrl: '', + vpsPassword: '', + localCpaStep9Mode: 'submit', + }, + sub2api: { + sub2apiUrl: '', + sub2apiEmail: '', + sub2apiPassword: '', + sub2apiGroupName: 'codex', + sub2apiGroupNames: ['codex', 'openai-plus'], + sub2apiAccountPriority: 1, + sub2apiDefaultProxyName: '', + }, + codex2api: { + codex2apiUrl: '', + codex2apiAdminKey: '', + }, }, - account: { customPassword: '' }, signup: { signupMethod: 'email', phoneVerificationEnabled: false, @@ -281,17 +357,13 @@ const chrome = { }, }, kiro: { - source: { - selected: 'kiro-rs', - entries: { - 'kiro-rs': { - kiroRsUrl: 'https://kiro.example.com/admin', - kiroRsKey: 'stored-key', - }, + integrationTargetId: 'kiro-rs', + integrationTargets: { + 'kiro-rs': { + baseUrl: 'https://kiro.example.com/admin', + apiKey: 'stored-key', }, }, - options: { - }, autoRun: { stepExecutionRange: { enabled: true, fromStep: 1, toStep: 7 }, }, @@ -324,12 +396,16 @@ const chrome = { test('setPersistentSettings materializes canonical schema keys for schema-only updates', async () => { const api = buildHarness(` const persistedWrites = []; +const removedKeys = []; const chrome = { storage: { local: { async get() { return {}; }, + async remove(keys) { + removedKeys.push(...(Array.isArray(keys) ? keys : [keys])); + }, async set(payload) { persistedWrites.push(JSON.parse(JSON.stringify(payload))); }, @@ -339,20 +415,43 @@ const chrome = { function getPersistedWrites() { return persistedWrites; } +function getRemovedKeys() { + return removedKeys; +} `); const persisted = await api.setPersistentSettings({ - settingsSchemaVersion: 3, + settingsSchemaVersion: 4, settingsState: { activeFlowId: 'kiro', services: { + account: { customPassword: '' }, email: { provider: '163' }, proxy: { enabled: false, provider: '711proxy', mode: 'account' }, }, flows: { openai: { - source: { selected: 'cpa', entries: {} }, - account: { customPassword: '' }, + integrationTargetId: 'cpa', + integrationTargets: { + 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, @@ -367,17 +466,13 @@ function getPersistedWrites() { }, }, kiro: { - source: { - selected: 'kiro-rs', - entries: { - 'kiro-rs': { - kiroRsUrl: 'https://kiro.example.com/admin', - kiroRsKey: 'nested-only-key', - }, + integrationTargetId: 'kiro-rs', + integrationTargets: { + 'kiro-rs': { + baseUrl: 'https://kiro.example.com/admin', + apiKey: 'nested-only-key', }, }, - options: { - }, autoRun: { stepExecutionRange: { enabled: true, fromStep: 1, toStep: 7 }, }, @@ -392,11 +487,14 @@ function getPersistedWrites() { assert.equal(persisted.kiroRsUrl, 'https://kiro.example.com/admin'); assert.equal(persisted.kiroRsKey, 'nested-only-key'); assert.equal(Object.prototype.hasOwnProperty.call(persisted, 'kiroRegion'), false); - assert.equal(persisted.settingsSchemaVersion, 3); - assert.equal(write.activeFlowId, 'kiro'); - assert.equal(write.kiroRsUrl, 'https://kiro.example.com/admin'); - assert.equal(write.kiroRsKey, 'nested-only-key'); + assert.equal(persisted.settingsSchemaVersion, 4); + 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, 3); + assert.equal(write.settingsSchemaVersion, 4); assert.equal(write.settingsState.activeFlowId, 'kiro'); + assert.equal(write.settingsState.flows.kiro.integrationTargetId, 'kiro-rs'); + assert.ok(api.getRemovedKeys().includes('panelMode')); + assert.ok(api.getRemovedKeys().includes('kiroRsUrl')); }); diff --git a/tests/background-step-node-registry-module.test.js b/tests/background-step-node-registry-module.test.js index e749719..63f27c5 100644 --- a/tests/background-step-node-registry-module.test.js +++ b/tests/background-step-node-registry-module.test.js @@ -8,9 +8,8 @@ test('background node registry preserves node metadata even before an executor i const registry = api.createNodeRegistry([ { flowId: 'kiro', - legacyStepId: 1, nodeId: 'kiro-start-device-login', - displayOrder: 10, + displayOrder: 1, executeKey: 'kiro-start-device-login', title: 'Start device login', }, @@ -19,7 +18,7 @@ test('background node registry preserves node metadata even before an executor i const node = registry.getNodeDefinition('kiro-start-device-login'); assert.equal(node.flowId, 'kiro'); - assert.equal(node.legacyStepId, 1); + assert.equal(node.displayOrder, 1); assert.equal(node.title, 'Start device login'); assert.throws( () => registry.executeNode('kiro-start-device-login', {}), @@ -34,9 +33,8 @@ test('background node registry executes registered nodes in display order', asyn const registry = api.createNodeRegistry([ { flowId: 'openai', - legacyStepId: 2, + displayOrder: 2, nodeId: 'submit-signup-email', - displayOrder: 20, executeKey: 'submit-signup-email', title: 'Submit signup email', execute: async (state) => { @@ -45,9 +43,8 @@ test('background node registry executes registered nodes in display order', asyn }, { flowId: 'openai', - legacyStepId: 1, + displayOrder: 1, nodeId: 'open-chatgpt', - displayOrder: 10, executeKey: 'open-chatgpt', title: 'Open ChatGPT', }, diff --git a/tests/flow-capabilities-module.test.js b/tests/flow-capabilities-module.test.js index a87b759..fc2d2b6 100644 --- a/tests/flow-capabilities-module.test.js +++ b/tests/flow-capabilities-module.test.js @@ -21,7 +21,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run const enabledState = registry.resolveSidepanelCapabilities({ state: { activeFlowId: 'openai', - panelMode: 'cpa', + openaiIntegrationTargetId: 'cpa', phoneVerificationEnabled: true, plusModeEnabled: false, contributionMode: false, @@ -37,7 +37,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run const plusLockedState = registry.resolveSidepanelCapabilities({ state: { activeFlowId: 'openai', - panelMode: 'sub2api', + openaiIntegrationTargetId: 'sub2api', phoneVerificationEnabled: true, plusModeEnabled: true, contributionMode: false, @@ -58,7 +58,7 @@ test('flow capability registry defaults unknown flows to minimal non-phone capab const capabilityState = registry.resolveSidepanelCapabilities({ state: { activeFlowId: 'site-a', - panelMode: 'codex2api', + openaiIntegrationTargetId: 'codex2api', phoneVerificationEnabled: true, plusModeEnabled: true, contributionMode: true, @@ -73,7 +73,7 @@ test('flow capability registry defaults unknown flows to minimal non-phone capab assert.equal(capabilityState.canUsePhoneSignup, false); assert.equal(capabilityState.effectiveSignupMethod, 'email'); assert.equal(capabilityState.panelMode, 'codex2api'); - assert.deepEqual(capabilityState.supportedPanelModes, []); + assert.deepEqual(capabilityState.supportedIntegrationTargets, []); }); test('flow capability registry exposes Kiro as an independent flow with its own visible groups', () => { @@ -83,8 +83,8 @@ test('flow capability registry exposes Kiro as an independent flow with its own const capabilityState = registry.resolveSidepanelCapabilities({ state: { activeFlowId: 'kiro', - kiroSourceId: 'kiro-rs', - panelMode: 'sub2api', + kiroIntegrationTargetId: 'kiro-rs', + openaiIntegrationTargetId: 'sub2api', signupMethod: 'phone', plusModeEnabled: true, phoneVerificationEnabled: true, @@ -95,21 +95,21 @@ test('flow capability registry exposes Kiro as an independent flow with its own assert.equal(capabilityState.canShowPhoneSettings, false); assert.equal(capabilityState.canShowPlusSettings, false); assert.equal(capabilityState.effectiveSignupMethod, 'email'); - assert.equal(capabilityState.effectiveSourceId, 'kiro-rs'); + assert.equal(capabilityState.effectiveIntegrationTargetId, 'kiro-rs'); assert.deepEqual( capabilityState.visibleGroupIds, - ['kiro-runtime-status', 'kiro-source-kiro-rs', 'service-account', 'service-email', 'service-proxy'] + ['kiro-runtime-status', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy'] ); }); -test('flow capability registry exposes shared auto-run validation for phone locks and panel support', () => { +test('flow capability registry exposes shared auto-run validation for phone locks and target support', () => { const api = loadApi(); const registry = api.createFlowCapabilityRegistry({ flowCapabilities: { openai: api.FLOW_CAPABILITIES.openai, 'site-a': { ...api.DEFAULT_FLOW_CAPABILITIES, - supportsPlatformBinding: ['cpa'], + supportedIntegrationTargets: ['cpa'], }, }, }); @@ -117,7 +117,7 @@ test('flow capability registry exposes shared auto-run validation for phone lock const plusLockedResult = registry.validateAutoRunStart({ state: { activeFlowId: 'openai', - panelMode: 'cpa', + openaiIntegrationTargetId: 'cpa', signupMethod: 'phone', phoneVerificationEnabled: true, plusModeEnabled: true, @@ -131,7 +131,7 @@ test('flow capability registry exposes shared auto-run validation for phone lock const unsupportedPanelResult = registry.validateAutoRunStart({ state: { activeFlowId: 'site-a', - panelMode: 'sub2api', + openaiIntegrationTargetId: 'sub2api', signupMethod: 'email', }, }); @@ -147,7 +147,7 @@ test('flow capability registry normalizes unsupported mode switches back to the openai: api.FLOW_CAPABILITIES.openai, 'site-a': { ...api.DEFAULT_FLOW_CAPABILITIES, - supportsPlatformBinding: ['cpa'], + supportedIntegrationTargets: ['cpa'], }, }, }); @@ -155,14 +155,14 @@ test('flow capability registry normalizes unsupported mode switches back to the const validation = registry.validateModeSwitch({ state: { activeFlowId: 'site-a', - panelMode: 'sub2api', + openaiIntegrationTargetId: 'sub2api', signupMethod: 'phone', phoneVerificationEnabled: true, plusModeEnabled: true, contributionMode: true, }, changedKeys: [ - 'panelMode', + 'openaiIntegrationTargetId', 'signupMethod', 'phoneVerificationEnabled', 'plusModeEnabled', @@ -173,6 +173,9 @@ test('flow capability registry normalizes unsupported mode switches back to the assert.equal(validation.ok, false); assert.deepEqual(validation.normalizedUpdates, { panelMode: 'cpa', + openaiIntegrationTargetId: 'cpa', + kiroIntegrationTargetId: 'cpa', + kiroSourceId: 'cpa', signupMethod: 'email', phoneVerificationEnabled: false, plusModeEnabled: false, diff --git a/tests/flow-registry-settings-schema.test.js b/tests/flow-registry-settings-schema.test.js index 2f4fff9..9bb050c 100644 --- a/tests/flow-registry-settings-schema.test.js +++ b/tests/flow-registry-settings-schema.test.js @@ -13,37 +13,31 @@ function loadApis() { };`)(scope); } -test('flow registry exposes openai and kiro with canonical source metadata', () => { +test('flow registry exposes canonical flow and integration target metadata', () => { const { flowRegistry } = loadApis(); assert.deepEqual(flowRegistry.getRegisteredFlowIds(), ['openai', 'kiro']); - assert.equal(flowRegistry.getFlowLabel('codex'), 'Codex / OpenAI'); - assert.equal(flowRegistry.normalizeFlowId('codex'), 'openai'); - assert.equal(flowRegistry.normalizeSourceId('openai', 'sub2api'), 'sub2api'); - assert.equal(flowRegistry.normalizeSourceId('kiro', 'anything-else'), 'kiro-rs'); + assert.equal(flowRegistry.normalizeFlowId('kiro'), 'kiro'); + assert.equal(flowRegistry.normalizeFlowId('unknown'), 'openai'); + assert.equal(flowRegistry.getFlowLabel('openai'), 'Codex / OpenAI'); + assert.equal(flowRegistry.normalizeIntegrationTargetId('openai', 'sub2api'), 'sub2api'); + assert.equal(flowRegistry.normalizeIntegrationTargetId('kiro', 'anything-else'), 'kiro-rs'); assert.deepEqual( flowRegistry.getVisibleGroupIds('openai', 'cpa'), - ['openai-plus', 'openai-phone', 'openai-oauth', 'openai-step6', 'openai-source-cpa', 'service-account', 'service-email', 'service-proxy'] + ['openai-plus', 'openai-phone', 'openai-oauth', 'openai-step6', 'openai-target-cpa', 'service-account', 'service-email', 'service-proxy'] ); assert.deepEqual( flowRegistry.getVisibleGroupIds('kiro', 'kiro-rs'), - ['kiro-runtime-status', 'kiro-source-kiro-rs', 'service-account', 'service-email', 'service-proxy'] + ['kiro-runtime-status', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy'] ); assert.deepEqual( - flowRegistry.getSettingsGroupDefinition('openai-plus')?.rowIds || [], - ['row-plus-mode'] - ); - assert.deepEqual( - flowRegistry.getSettingsGroupDefinition('openai-phone')?.rowIds || [], - [] - ); - assert.deepEqual( - flowRegistry.getSettingsGroupDefinition('openai-step6')?.rowIds || [], - ['row-step6-cookie-settings'] + flowRegistry.getIntegrationTargetOptions('openai').map((entry) => entry.id), + ['cpa', 'sub2api', 'codex2api'] ); + assert.equal(flowRegistry.getPublicationTargetDefinition('kiro', 'kiro-rs')?.label, 'kiro.rs'); }); -test('settings schema normalizes flat input into canonical flow and service namespaces', () => { +test('settings schema normalizes view input into canonical nested namespaces', () => { const { settingsSchema } = loadApis(); const schema = settingsSchema.createSettingsSchema(); @@ -66,10 +60,10 @@ test('settings schema normalizes flat input into canonical flow and service name 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.source.selected, 'sub2api'); - assert.equal(normalized.flows.kiro.source.selected, 'kiro-rs'); - assert.equal(normalized.flows.kiro.source.entries['kiro-rs'].kiroRsUrl, 'https://kiro.example.com/admin'); - assert.equal(normalized.flows.kiro.source.entries['kiro-rs'].kiroRsKey, 'secret-key'); + assert.equal(normalized.flows.openai.integrationTargetId, 'sub2api'); + assert.equal(normalized.flows.kiro.integrationTargetId, 'kiro-rs'); + assert.equal(normalized.flows.kiro.integrationTargets['kiro-rs'].baseUrl, 'https://kiro.example.com/admin'); + assert.equal(normalized.flows.kiro.integrationTargets['kiro-rs'].apiKey, 'secret-key'); assert.deepEqual(normalized.flows.kiro.autoRun.stepExecutionRange, { enabled: true, fromStep: 1, @@ -77,22 +71,24 @@ test('settings schema normalizes flat input into canonical flow and service name }); }); -test('settings schema can project canonical state back to legacy payload without losing flow selection', () => { +test('settings schema can project canonical state into a read view without legacy rebuild helpers', () => { const { settingsSchema } = loadApis(); const schema = settingsSchema.createSettingsSchema(); - const payload = schema.buildLegacySettingsPayload(schema.normalizeSettingsState({ + const normalized = schema.normalizeSettingsState({ activeFlowId: 'kiro', - kiroSourceId: 'kiro-rs', + kiroIntegrationTargetId: 'kiro-rs', kiroRsUrl: 'https://kiro.example.com/admin', kiroRsKey: 'key-123', - })); + }); + const view = schema.buildSettingsView(normalized); - assert.equal(payload.activeFlowId, 'kiro'); - assert.equal(payload.panelMode, 'cpa'); - assert.equal(payload.kiroSourceId, 'kiro-rs'); - assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin'); - assert.equal(payload.kiroRsKey, 'key-123'); - assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false); - assert.equal(payload.settingsSchemaVersion, 3); - assert.equal(payload.settingsState.activeFlowId, 'kiro'); + assert.equal(view.activeFlowId, 'kiro'); + assert.equal(view.openaiIntegrationTargetId, 'cpa'); + assert.equal(view.kiroIntegrationTargetId, 'kiro-rs'); + assert.equal(view.panelMode, 'cpa'); + assert.equal(view.kiroSourceId, 'kiro-rs'); + assert.equal(view.kiroRsUrl, 'https://kiro.example.com/admin'); + assert.equal(view.kiroRsKey, 'key-123'); + assert.equal(view.settingsSchemaVersion, 4); + assert.equal(view.settingsState.activeFlowId, 'kiro'); });