refactor: 重构 Kiro flow 配置与运行链路

This commit is contained in:
QLHazyCoder
2026-05-18 19:34:59 +08:00
parent ccd21206c1
commit d9795f8b3a
14 changed files with 1048 additions and 762 deletions
+123 -183
View File
@@ -798,9 +798,8 @@ function getNodeDefinitionsForState(state = {}) {
} }
return getStepDefinitionsForState(state) return getStepDefinitionsForState(state)
.map((definition) => ({ .map((definition) => ({
legacyStepId: Number(definition?.id),
nodeId: String(definition?.key || '').trim(), 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(), title: String(definition?.title || '').trim(),
executeKey: String(definition?.key || '').trim(), executeKey: String(definition?.key || '').trim(),
})) }))
@@ -829,17 +828,21 @@ function getLastNodeIdForState(state = {}) {
} }
function getNodeIdByStepForState(step, state = {}) { function getNodeIdByStepForState(step, state = {}) {
const definition = getStepDefinitionForState(step, state); const numericStep = Number(step);
return String(definition?.key || '').trim(); 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 = {}) { function getStepIdByNodeIdForState(nodeId, state = {}) {
const normalizedNodeId = String(nodeId || '').trim(); const normalizedNodeId = String(nodeId || '').trim();
if (!normalizedNodeId) return null; if (!normalizedNodeId) return null;
const node = getNodeDefinitionForState(normalizedNodeId, state); const node = getNodeDefinitionForState(normalizedNodeId, state);
const legacyStepId = Number(node?.legacyStepId); const displayOrder = Number(node?.displayOrder);
if (Number.isInteger(legacyStepId) && legacyStepId > 0) { if (Number.isInteger(displayOrder) && displayOrder > 0) {
return legacyStepId; return displayOrder;
} }
return getStepIdByKeyForState(normalizedNodeId, state); return getStepIdByKeyForState(normalizedNodeId, state);
} }
@@ -1074,6 +1077,39 @@ const PERSISTED_SETTING_DEFAULTS = {
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS); const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
const PERSISTED_SETTINGS_SCHEMA_KEYS = ['settingsSchemaVersion', 'settingsState']; 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_SCHEMA_VERSION = 1;
const SETTINGS_EXPORT_FILENAME_PREFIX = 'multipage-settings'; const SETTINGS_EXPORT_FILENAME_PREFIX = 'multipage-settings';
const STEP6_REGISTRATION_SUCCESS_WAIT_MS = 20000; const STEP6_REGISTRATION_SUCCESS_WAIT_MS = 20000;
@@ -1087,91 +1123,14 @@ const DEFAULT_STATE = {
nodeStatuses: { ...DEFAULT_NODE_STATUSES }, nodeStatuses: { ...DEFAULT_NODE_STATUSES },
runtimeState: runtimeStateHelpers?.buildDefaultRuntimeState?.() || null, runtimeState: runtimeStateHelpers?.buildDefaultRuntimeState?.() || null,
...CONTRIBUTION_RUNTIME_DEFAULTS, ...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 }。 accounts: [], // 已生成账号记录:{ email, password, createdAt }。
accountRunHistory: [], // 账号运行历史快照,实际持久化在 chrome.storage.local。 accountRunHistory: [], // 账号运行历史快照,实际持久化在 chrome.storage.local。
manualAliasUsage: {}, manualAliasUsage: {},
preservedAliases: {}, preservedAliases: {},
icloudAliasCache: [], icloudAliasCache: [],
icloudAliasCacheAt: 0, 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: [], // 侧边栏展示的运行日志。 logs: [], // 侧边栏展示的运行日志。
...PERSISTED_SETTING_DEFAULTS, // 合并 chrome.storage.local 中持久化保存的用户配置。 ...PERSISTED_SETTING_DEFAULTS, // 合并 chrome.storage.local 中持久化保存的用户配置。
ipProxyApiPool: [],
ipProxyApiCurrentIndex: 0,
ipProxyApiCurrent: null,
ipProxyAccountPool: [],
ipProxyAccountCurrentIndex: 0,
ipProxyAccountCurrent: null,
ipProxyPool: [],
ipProxyCurrentIndex: 0,
ipProxyCurrent: null,
luckmailApiKey: '', luckmailApiKey: '',
luckmailBaseUrl: DEFAULT_LUCKMAIL_BASE_URL, luckmailBaseUrl: DEFAULT_LUCKMAIL_BASE_URL,
luckmailEmailType: DEFAULT_LUCKMAIL_EMAIL_TYPE, luckmailEmailType: DEFAULT_LUCKMAIL_EMAIL_TYPE,
@@ -1179,23 +1138,6 @@ const DEFAULT_STATE = {
luckmailUsedPurchases: {}, luckmailUsedPurchases: {},
luckmailPreserveTagId: 0, luckmailPreserveTagId: 0,
luckmailPreserveTagName: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME, 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: [], heroSmsLastPriceTiers: [],
heroSmsLastPriceCountryId: 0, heroSmsLastPriceCountryId: 0,
heroSmsLastPriceCountryLabel: '', heroSmsLastPriceCountryLabel: '',
@@ -3353,14 +3295,14 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
const settingsSchemaApi = typeof getSettingsSchemaApi === 'function' const settingsSchemaApi = typeof getSettingsSchemaApi === 'function'
? getSettingsSchemaApi() ? getSettingsSchemaApi()
: null; : null;
if (settingsSchemaApi?.normalizeSettingsState && settingsSchemaApi?.buildLegacySettingsPayload) { if (settingsSchemaApi?.normalizeSettingsState && settingsSchemaApi?.buildSettingsView) {
const settingsSchemaInput = {}; const settingsSchemaInput = {};
for (const key of persistedSettingKeys) { for (const key of persistedSettingKeys) {
if (normalizedInput[key] !== undefined) { if (normalizedInput[key] !== undefined) {
settingsSchemaInput[key] = payload[key]; settingsSchemaInput[key] = payload[key];
} }
} }
const normalizedSettingsState = settingsSchemaApi.normalizeSettingsState({ Object.assign(payload, projectSettingsSchemaView(settingsSchemaApi, {
...settingsSchemaInput, ...settingsSchemaInput,
...(isPlainObjectForSettingsSchema(normalizedInput.settingsState) ...(isPlainObjectForSettingsSchema(normalizedInput.settingsState)
? { settingsState: normalizedInput.settingsState } ? { settingsState: normalizedInput.settingsState }
@@ -3368,13 +3310,7 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
...(Object.prototype.hasOwnProperty.call(normalizedInput, 'settingsSchemaVersion') ...(Object.prototype.hasOwnProperty.call(normalizedInput, 'settingsSchemaVersion')
? { settingsSchemaVersion: normalizedInput.settingsSchemaVersion } ? { settingsSchemaVersion: normalizedInput.settingsSchemaVersion }
: {}), : {}),
}, { }, payload));
activeFlowId: settingsSchemaInput.activeFlowId || normalizedInput.activeFlowId || DEFAULT_ACTIVE_FLOW_ID,
});
Object.assign(
payload,
settingsSchemaApi.buildLegacySettingsPayload(normalizedSettingsState, 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() { async function getPersistedSettings() {
const stored = await chrome.storage.local.get([ const stored = await chrome.storage.local.get([
...PERSISTED_SETTING_KEYS, ...PERSISTED_SETTING_KEYS,
@@ -3481,6 +3443,9 @@ function buildAutoRunFreshResetSettingsState(prevState = {}, activeFlowId = DEFA
const nextSettingsStatePatch = { const nextSettingsStatePatch = {
activeFlowId, activeFlowId,
services: { services: {
account: {
customPassword: prevState?.customPassword,
},
email: { email: {
provider: prevState?.mailProvider, provider: prevState?.mailProvider,
}, },
@@ -3492,9 +3457,7 @@ function buildAutoRunFreshResetSettingsState(prevState = {}, activeFlowId = DEFA
}, },
flows: { flows: {
openai: { openai: {
source: { integrationTargetId: prevState?.openaiIntegrationTargetId || prevState?.panelMode,
selected: prevState?.panelMode,
},
autoRun: normalizedStepExecutionRangeByFlow.openai autoRun: normalizedStepExecutionRangeByFlow.openai
? { ? {
stepExecutionRange: normalizedStepExecutionRangeByFlow.openai, stepExecutionRange: normalizedStepExecutionRangeByFlow.openai,
@@ -3502,9 +3465,7 @@ function buildAutoRunFreshResetSettingsState(prevState = {}, activeFlowId = DEFA
: undefined, : undefined,
}, },
kiro: { kiro: {
source: { integrationTargetId: prevState?.kiroIntegrationTargetId || prevState?.kiroSourceId,
selected: prevState?.kiroSourceId,
},
autoRun: normalizedStepExecutionRangeByFlow.kiro autoRun: normalizedStepExecutionRangeByFlow.kiro
? { ? {
stepExecutionRange: normalizedStepExecutionRangeByFlow.kiro, stepExecutionRange: normalizedStepExecutionRangeByFlow.kiro,
@@ -3651,78 +3612,58 @@ async function setPersistentSettings(updates) {
const settingsSchemaApi = typeof getSettingsSchemaApi === 'function' const settingsSchemaApi = typeof getSettingsSchemaApi === 'function'
? getSettingsSchemaApi() ? getSettingsSchemaApi()
: null; : null;
const hasSchemaApi = Boolean(
settingsSchemaApi?.normalizeSettingsState
&& settingsSchemaApi?.buildSettingsView
);
const explicitFlatUpdates = {
...nextUpdates,
};
delete explicitFlatUpdates.settingsSchemaVersion;
delete explicitFlatUpdates.settingsState;
let persistedUpdates; let mergedSettingsState = nextUpdates.settingsState ?? currentSettings.settingsState;
if (settingsSchemaApi?.normalizeSettingsState && settingsSchemaApi?.buildLegacySettingsPayload) { if (hasSchemaApi) {
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;
};
const currentSettingsState = settingsSchemaApi.normalizeSettingsState( const currentSettingsState = settingsSchemaApi.normalizeSettingsState(
isPlainObjectForSettingsSchema(currentSettings?.settingsState) isPlainObjectValue(currentSettings?.settingsState)
? { settingsState: currentSettings.settingsState } ? { settingsState: currentSettings.settingsState }
: currentSettings, : currentSettings,
{ {
activeFlowId: currentSettings?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID, activeFlowId: currentSettings?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID,
} }
); );
const mergedSettingsState = isPlainObjectForSettingsSchema(nextUpdates.settingsState) mergedSettingsState = isPlainObjectValue(nextUpdates.settingsState)
? mergeSettingsState(currentSettingsState, nextUpdates.settingsState) ? (typeof settingsSchemaApi.mergeSettingsState === 'function'
? settingsSchemaApi.mergeSettingsState(currentSettingsState, nextUpdates.settingsState)
: nextUpdates.settingsState)
: currentSettingsState; : 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) { 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; return persistedUpdates;
} }
@@ -13563,9 +13504,8 @@ function buildNodeRegistry(definitions = []) {
return self.MultiPageBackgroundStepRegistry?.createNodeRegistry( return self.MultiPageBackgroundStepRegistry?.createNodeRegistry(
definitions.map((definition) => ({ definitions.map((definition) => ({
...definition, ...definition,
legacyStepId: definition.legacyStepId || definition.id,
nodeId: definition.nodeId || definition.key, nodeId: definition.nodeId || definition.key,
displayOrder: definition.displayOrder || definition.order, displayOrder: definition.displayOrder || definition.id || definition.order,
executeKey: definition.executeKey || definition.key, executeKey: definition.executeKey || definition.key,
execute: stepExecutorsByKey[definition.executeKey || definition.key || definition.nodeId], execute: stepExecutorsByKey[definition.executeKey || definition.key || definition.nodeId],
})) }))
@@ -13580,15 +13520,15 @@ function buildStepRegistry(definitions = []) {
const normalizedDefinitions = (Array.isArray(definitions) ? definitions : []) const normalizedDefinitions = (Array.isArray(definitions) ? definitions : [])
.map((definition) => ({ .map((definition) => ({
...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(), nodeId: String(definition?.nodeId || definition?.key || '').trim(),
})) }))
.filter((definition) => definition.nodeId); .filter((definition) => definition.nodeId);
const nodeRegistry = buildNodeRegistry(normalizedDefinitions); const nodeRegistry = buildNodeRegistry(normalizedDefinitions);
const stepToNodeDefinition = new Map( const stepToNodeDefinition = new Map(
normalizedDefinitions normalizedDefinitions
.filter((definition) => Number.isInteger(definition.legacyStepId) && definition.legacyStepId > 0) .filter((definition) => Number.isInteger(definition.displayOrder) && definition.displayOrder > 0)
.map((definition) => [definition.legacyStepId, definition]) .map((definition) => [definition.displayOrder, definition])
); );
return { 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 activeFlowId = String(state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID;
const cacheKey = `${activeFlowId}:${(Array.isArray(definitions) ? definitions : []) const cacheKey = `${activeFlowId}:${(Array.isArray(definitions) ? definitions : [])
.map((definition) => [ .map((definition) => [
Number(definition?.legacyStepId ?? definition?.id) || 0, Number(definition?.displayOrder ?? definition?.id ?? definition?.order) || 0,
String(definition?.nodeId || definition?.key || '').trim(), String(definition?.nodeId || definition?.key || '').trim(),
String(definition?.executeKey || 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(':'))
.join('|')}`; .join('|')}`;
+135 -80
View File
@@ -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') { function buildScopedFlowState(baseFlowState = {}, state = {}, flowId = 'openai') {
const fieldGroups = FLOW_FIELD_GROUPS[flowId] || {}; const fieldGroups = FLOW_FIELD_GROUPS[flowId] || {};
const baseScopedState = cloneValue(normalizePlainObject(baseFlowState[flowId])); const baseScopedState = cloneValue(normalizePlainObject(baseFlowState[flowId]));
@@ -276,7 +261,7 @@
return scopedState; return scopedState;
} }
function buildOpenAiFlowState(baseValue = {}, state = {}) { function buildFlowState(baseValue = {}, state = {}) {
const baseFlowState = cloneValue(normalizePlainObject(baseValue)); const baseFlowState = cloneValue(normalizePlainObject(baseValue));
return { return {
...baseFlowState, ...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() { function buildRuntimeStateDefault() {
return { return {
flowId: DEFAULT_ACTIVE_FLOW_ID, flowId: DEFAULT_ACTIVE_FLOW_ID,
@@ -366,69 +471,18 @@
nodeStatuses, nodeStatuses,
sharedState: buildSharedState(baseRuntimeState.sharedState, state), sharedState: buildSharedState(baseRuntimeState.sharedState, state),
serviceState: buildServiceState(baseRuntimeState.serviceState, state), serviceState: buildServiceState(baseRuntimeState.serviceState, state),
flowState: buildOpenAiFlowState(baseRuntimeState.flowState, state), flowState: buildFlowState(baseRuntimeState.flowState, state),
}; };
} }
function buildFlattenedUpdates(updates = {}) { function buildPersistentPatchPayload(updates = {}) {
const ignoredKeys = new Set(['current' + 'Step', 'step' + 'Statuses', 'legacy' + 'StepCompat']);
const next = {}; const next = {};
for (const [key, value] of Object.entries(updates || {})) { for (const [key, value] of Object.entries(normalizePlainObject(updates))) {
if (!ignoredKeys.has(key)) { if (RUNTIME_PATCH_IGNORED_KEYS.has(key)) {
next[key] = value; 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; return next;
} }
@@ -436,6 +490,7 @@
const runtimeState = ensureRuntimeState(state); const runtimeState = ensureRuntimeState(state);
return { return {
...state, ...state,
...projectRuntimeViewFields(runtimeState),
flowId: runtimeState.flowId, flowId: runtimeState.flowId,
runId: runtimeState.runId, runId: runtimeState.runId,
activeFlowId: runtimeState.activeFlowId, activeFlowId: runtimeState.activeFlowId,
@@ -453,15 +508,15 @@
} }
function buildSessionStatePatch(currentState = {}, updates = {}) { function buildSessionStatePatch(currentState = {}, updates = {}) {
const flattenedUpdates = buildFlattenedUpdates(updates); const currentRuntimeState = ensureRuntimeState(currentState);
const nextState = { const runtimeState = ensureRuntimeState({
...currentState, runtimeState: currentRuntimeState,
...flattenedUpdates, ...projectRuntimeViewFields(currentRuntimeState),
}; ...buildRuntimeInputFromPatch(updates),
const runtimeState = ensureRuntimeState(nextState); });
return { return {
...flattenedUpdates, ...buildPersistentPatchPayload(updates),
flowId: runtimeState.flowId, flowId: runtimeState.flowId,
runId: runtimeState.runId, runId: runtimeState.runId,
activeFlowId: runtimeState.activeFlowId, activeFlowId: runtimeState.activeFlowId,
+1 -2
View File
@@ -4,10 +4,9 @@
function createNodeRegistry(definitions = []) { function createNodeRegistry(definitions = []) {
const ordered = (Array.isArray(definitions) ? definitions : []) const ordered = (Array.isArray(definitions) ? definitions : [])
.map((definition) => ({ .map((definition) => ({
legacyStepId: Number(definition?.legacyStepId ?? definition?.id) || 0,
flowId: String(definition?.flowId || '').trim(), flowId: String(definition?.flowId || '').trim(),
nodeId: String(definition?.nodeId || definition?.key || '').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(), executeKey: String(definition?.executeKey || definition?.key || definition?.nodeId || '').trim(),
title: String(definition?.title || '').trim(), title: String(definition?.title || '').trim(),
execute: definition?.execute, execute: definition?.execute,
+1 -2
View File
@@ -348,11 +348,10 @@
function cloneNodes(steps = [], options = {}, flowId = DEFAULT_ACTIVE_FLOW_ID) { function cloneNodes(steps = [], options = {}, flowId = DEFAULT_ACTIVE_FLOW_ID) {
const { builder } = getFlowDefinitionBuilder({ activeFlowId: flowId }); const { builder } = getFlowDefinitionBuilder({ activeFlowId: flowId });
return steps.map((step) => ({ return steps.map((step) => ({
legacyStepId: Number(step.id),
nodeId: String(step.key || '').trim(), nodeId: String(step.key || '').trim(),
flowId, flowId,
title: builder?.resolveStepTitle ? builder.resolveStepTitle(step, options) : step.title, 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', nodeType: 'task',
sourceId: step.sourceId || '', sourceId: step.sourceId || '',
driverId: step.driverId || '', driverId: step.driverId || '',
+163 -87
View File
@@ -5,12 +5,11 @@
const flowRegistryApi = rootScope.MultiPageFlowRegistry || {}; const flowRegistryApi = rootScope.MultiPageFlowRegistry || {};
const settingsSchemaApi = rootScope.MultiPageSettingsSchema || {}; const settingsSchemaApi = rootScope.MultiPageSettingsSchema || {};
const DEFAULT_FLOW_ID = flowRegistryApi.DEFAULT_FLOW_ID || 'openai'; const DEFAULT_FLOW_ID = flowRegistryApi.DEFAULT_FLOW_ID || 'openai';
const DEFAULT_PANEL_MODE = flowRegistryApi.DEFAULT_OPENAI_SOURCE_ID || 'cpa'; const DEFAULT_OPENAI_INTEGRATION_TARGET_ID = flowRegistryApi.DEFAULT_OPENAI_INTEGRATION_TARGET_ID || 'cpa';
const LEGACY_OPENAI_FLOW_ALIAS = String(flowRegistryApi.LEGACY_OPENAI_FLOW_ALIAS || 'codex').trim().toLowerCase();
const SIGNUP_METHOD_EMAIL = 'email'; const SIGNUP_METHOD_EMAIL = 'email';
const SIGNUP_METHOD_PHONE = 'phone'; const SIGNUP_METHOD_PHONE = 'phone';
const VALID_PANEL_MODES = Array.isArray(flowRegistryApi.OPENAI_SOURCE_IDS) const VALID_OPENAI_INTEGRATION_TARGET_IDS = Array.isArray(flowRegistryApi.OPENAI_INTEGRATION_TARGET_IDS)
? flowRegistryApi.OPENAI_SOURCE_IDS.slice() ? flowRegistryApi.OPENAI_INTEGRATION_TARGET_IDS.slice()
: ['cpa', 'sub2api', 'codex2api']; : ['cpa', 'sub2api', 'codex2api'];
const REGISTERED_FLOW_IDS = Array.isArray(flowRegistryApi.getRegisteredFlowIds?.()) const REGISTERED_FLOW_IDS = Array.isArray(flowRegistryApi.getRegisteredFlowIds?.())
? flowRegistryApi.getRegisteredFlowIds().map((flowId) => String(flowId || '').trim().toLowerCase()).filter(Boolean) ? flowRegistryApi.getRegisteredFlowIds().map((flowId) => String(flowId || '').trim().toLowerCase()).filter(Boolean)
@@ -23,7 +22,7 @@
supportsPhoneVerificationSettings: false, supportsPhoneVerificationSettings: false,
supportsPlusMode: false, supportsPlusMode: false,
supportsContributionMode: false, supportsContributionMode: false,
supportsPlatformBinding: [], supportedIntegrationTargets: [],
supportsLuckmail: false, supportsLuckmail: false,
supportsOauthTimeoutBudget: false, supportsOauthTimeoutBudget: false,
canSwitchFlow: true, canSwitchFlow: true,
@@ -48,7 +47,7 @@
) )
); );
const DEFAULT_PANEL_CAPABILITIES = Object.freeze({ const DEFAULT_INTEGRATION_TARGET_CAPABILITIES = Object.freeze({
supportsPhoneSignup: true, supportsPhoneSignup: true,
requiresPhoneSignupWarning: false, requiresPhoneSignupWarning: false,
}); });
@@ -61,9 +60,11 @@
'plusModeEnabled', 'plusModeEnabled',
'signupMethod', 'signupMethod',
'kiroSourceId', 'kiroSourceId',
'openaiIntegrationTargetId',
'kiroIntegrationTargetId',
]); ]);
const PANEL_CAPABILITIES = Object.freeze({ const OPENAI_INTEGRATION_TARGET_CAPABILITIES = Object.freeze({
cpa: Object.freeze({ cpa: Object.freeze({
supportsPhoneSignup: true, supportsPhoneSignup: true,
requiresPhoneSignupWarning: true, requiresPhoneSignupWarning: true,
@@ -88,30 +89,26 @@
function normalizeCapabilityFlowId(value = '', fallback = DEFAULT_FLOW_ID) { function normalizeCapabilityFlowId(value = '', fallback = DEFAULT_FLOW_ID) {
const normalized = String(value || '').trim().toLowerCase(); const normalized = String(value || '').trim().toLowerCase();
if (normalized === LEGACY_OPENAI_FLOW_ALIAS) {
return DEFAULT_FLOW_ID;
}
if (normalized) { if (normalized) {
return normalized; return normalized;
} }
const normalizedFallback = String(fallback || '').trim().toLowerCase(); return normalizeFlowId(fallback, DEFAULT_FLOW_ID);
if (normalizedFallback === LEGACY_OPENAI_FLOW_ALIAS) {
return DEFAULT_FLOW_ID;
}
return normalizedFallback || DEFAULT_FLOW_ID;
} }
function isRegisteredFlowId(flowId = '') { 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(); const normalized = String(value || '').trim().toLowerCase();
if (VALID_PANEL_MODES.includes(normalized)) { if (VALID_OPENAI_INTEGRATION_TARGET_IDS.includes(normalized)) {
return normalized; return normalized;
} }
const fallbackValue = String(fallback || '').trim().toLowerCase(); 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 = '') { function normalizeSignupMethod(value = '') {
@@ -120,41 +117,50 @@
: SIGNUP_METHOD_EMAIL; : SIGNUP_METHOD_EMAIL;
} }
function normalizePanelModeList(values = []) { function normalizeOpenAiIntegrationTargetList(values = []) {
if (!Array.isArray(values)) { if (!Array.isArray(values)) {
return []; return [];
} }
const seen = new Set(); const seen = new Set();
const normalized = []; const normalized = [];
values.forEach((value) => { values.forEach((value) => {
const mode = normalizePanelMode(value, ''); const integrationTargetId = normalizeOpenAiIntegrationTargetId(value, '');
if (!mode || seen.has(mode)) { if (!integrationTargetId || seen.has(integrationTargetId)) {
return; return;
} }
seen.add(mode); seen.add(integrationTargetId);
normalized.push(mode); normalized.push(integrationTargetId);
}); });
return normalized; return normalized;
} }
function getPanelModeLabel(panelMode = '') { function getIntegrationTargetLabel(flowId = DEFAULT_FLOW_ID, integrationTargetId = '') {
const normalized = normalizePanelMode(panelMode); if (
isRegisteredFlowId(flowId)
&& typeof flowRegistryApi.getIntegrationTargetLabel === 'function'
) {
return flowRegistryApi.getIntegrationTargetLabel(flowId, integrationTargetId);
}
const normalized = String(integrationTargetId || '').trim().toLowerCase();
if (normalized === 'sub2api') { if (normalized === 'sub2api') {
return 'SUB2API'; return 'SUB2API';
} }
if (normalized === 'codex2api') { if (normalized === 'codex2api') {
return 'Codex2API'; return 'Codex2API';
} }
return 'CPA'; if (normalized === 'cpa') {
return 'CPA';
}
return normalized || String(integrationTargetId || '').trim();
} }
function createFlowCapabilityRegistry(deps = {}) { function createFlowCapabilityRegistry(deps = {}) {
const { const {
defaultFlowCapabilities = DEFAULT_FLOW_CAPABILITIES, defaultFlowCapabilities = DEFAULT_FLOW_CAPABILITIES,
defaultFlowId = DEFAULT_FLOW_ID, defaultFlowId = DEFAULT_FLOW_ID,
defaultPanelCapabilities = DEFAULT_PANEL_CAPABILITIES, defaultIntegrationTargetCapabilities = DEFAULT_INTEGRATION_TARGET_CAPABILITIES,
flowCapabilities = FLOW_CAPABILITIES, flowCapabilities = FLOW_CAPABILITIES,
panelCapabilities = PANEL_CAPABILITIES, integrationTargetCapabilities = OPENAI_INTEGRATION_TARGET_CAPABILITIES,
} = deps; } = deps;
const settingsSchema = settingsSchemaApi.createSettingsSchema const settingsSchema = settingsSchemaApi.createSettingsSchema
? settingsSchemaApi.createSettingsSchema({ ? settingsSchemaApi.createSettingsSchema({
@@ -165,21 +171,72 @@
function getFlowCapabilities(flowId) { function getFlowCapabilities(flowId) {
const normalizedFlowId = normalizeCapabilityFlowId(flowId, defaultFlowId); const normalizedFlowId = normalizeCapabilityFlowId(flowId, defaultFlowId);
const entry = flowCapabilities[normalizedFlowId] || null; 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 { return {
...defaultFlowCapabilities, ...defaultFlowCapabilities,
...(entry || {}), ...(entry || {}),
supportsPlatformBinding: normalizePanelModeList(entry?.supportsPlatformBinding || defaultFlowCapabilities.supportsPlatformBinding), supportedIntegrationTargets,
}; };
} }
function getPanelCapabilities(panelMode) { function getOpenAiIntegrationTargetCapabilities(integrationTargetId) {
const normalizedPanelMode = normalizePanelMode(panelMode); const normalizedIntegrationTargetId = normalizeOpenAiIntegrationTargetId(integrationTargetId);
return { return {
...defaultPanelCapabilities, ...defaultIntegrationTargetCapabilities,
...(panelCapabilities[normalizedPanelMode] || {}), ...(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 = []) { function normalizeChangedKeys(values = []) {
const list = Array.isArray(values) ? values : []; const list = Array.isArray(values) ? values : [];
const seen = new Set(); const seen = new Set();
@@ -195,28 +252,33 @@
return normalized; return normalized;
} }
function resolveEffectiveSourceId(activeFlowId, state = {}, requestedPanelMode = DEFAULT_PANEL_MODE) { function resolveEffectiveIntegrationTargetId(activeFlowId, state = {}, requestedIntegrationTargetId = DEFAULT_OPENAI_INTEGRATION_TARGET_ID) {
if (!isRegisteredFlowId(activeFlowId)) { if (!isRegisteredFlowId(activeFlowId)) {
return normalizePanelMode(state?.panelMode || requestedPanelMode, requestedPanelMode || DEFAULT_PANEL_MODE); return normalizeRequestedIntegrationTargetId(activeFlowId, state, {
integrationTargetId: requestedIntegrationTargetId,
});
} }
if (settingsSchema?.getSelectedSourceId) { if (settingsSchema?.getSelectedIntegrationTargetId) {
const sourceFromSchema = settingsSchema.getSelectedSourceId({ const integrationTargetId = settingsSchema.getSelectedIntegrationTargetId({
...state, ...state,
activeFlowId, activeFlowId,
}, activeFlowId); }, activeFlowId);
if (sourceFromSchema) { if (integrationTargetId) {
return sourceFromSchema; return integrationTargetId;
} }
} }
if (typeof flowRegistryApi.normalizeSourceId === 'function') { if (typeof flowRegistryApi.normalizeIntegrationTargetId === 'function') {
if (activeFlowId === 'openai') { return flowRegistryApi.normalizeIntegrationTargetId(
return flowRegistryApi.normalizeSourceId('openai', state?.panelMode || requestedPanelMode, DEFAULT_PANEL_MODE); activeFlowId,
} activeFlowId === 'openai'
return flowRegistryApi.normalizeSourceId(activeFlowId, state?.kiroSourceId || '', flowRegistryApi.getDefaultSourceId?.(activeFlowId)); ? (state?.openaiIntegrationTargetId || state?.panelMode || requestedIntegrationTargetId)
: (state?.kiroIntegrationTargetId || state?.kiroSourceId || requestedIntegrationTargetId),
flowRegistryApi.getDefaultIntegrationTargetId?.(activeFlowId)
);
} }
return activeFlowId === 'openai' return activeFlowId === 'openai'
? normalizePanelMode(state?.panelMode || requestedPanelMode) ? normalizeOpenAiIntegrationTargetId(requestedIntegrationTargetId)
: ''; : String(requestedIntegrationTargetId || '').trim().toLowerCase();
} }
function resolveSidepanelCapabilities(options = {}) { function resolveSidepanelCapabilities(options = {}) {
@@ -226,19 +288,25 @@
defaultFlowId defaultFlowId
); );
const flowState = getFlowCapabilities(activeFlowId); const flowState = getFlowCapabilities(activeFlowId);
const requestedPanelMode = normalizePanelMode( const requestedIntegrationTargetId = normalizeRequestedIntegrationTargetId(
options?.panelMode ?? state?.panelMode, activeFlowId,
DEFAULT_PANEL_MODE state,
options
); );
const supportedPanelModes = normalizePanelModeList(flowState.supportsPlatformBinding); const supportedIntegrationTargets = activeFlowId === 'openai'
const panelModeSupported = supportedPanelModes.length === 0 ? normalizeOpenAiIntegrationTargetList(flowState.supportedIntegrationTargets)
: (Array.isArray(flowState.supportedIntegrationTargets)
? flowState.supportedIntegrationTargets.slice()
: []);
const integrationTargetSupported = supportedIntegrationTargets.length === 0
? true ? true
: supportedPanelModes.includes(requestedPanelMode); : supportedIntegrationTargets.includes(requestedIntegrationTargetId);
const effectivePanelMode = panelModeSupported const effectiveIntegrationTargetId = integrationTargetSupported
? requestedPanelMode ? requestedIntegrationTargetId
: (supportedPanelModes[0] || requestedPanelMode); : (supportedIntegrationTargets[0] || requestedIntegrationTargetId);
const panelState = getPanelCapabilities(effectivePanelMode); const integrationTargetState = activeFlowId === 'openai'
const effectiveSourceId = resolveEffectiveSourceId(activeFlowId, state, effectivePanelMode); ? getOpenAiIntegrationTargetCapabilities(effectiveIntegrationTargetId)
: defaultIntegrationTargetCapabilities;
const runtimeLocks = { const runtimeLocks = {
autoRunLocked: Boolean(options?.autoRunLocked ?? state?.autoRunLocked), autoRunLocked: Boolean(options?.autoRunLocked ?? state?.autoRunLocked),
contributionMode: activeFlowId === 'openai' && flowState.supportsContributionMode && Boolean(state?.contributionMode), contributionMode: activeFlowId === 'openai' && flowState.supportsContributionMode && Boolean(state?.contributionMode),
@@ -252,7 +320,7 @@
} }
const canSelectPhoneSignup = activeFlowId === 'openai' const canSelectPhoneSignup = activeFlowId === 'openai'
&& Boolean(flowState.supportsPhoneSignup) && Boolean(flowState.supportsPhoneSignup)
&& Boolean(panelState.supportsPhoneSignup) && Boolean(integrationTargetState.supportsPhoneSignup)
&& runtimeLocks.phoneVerificationEnabled && runtimeLocks.phoneVerificationEnabled
&& !runtimeLocks.plusModeEnabled && !runtimeLocks.plusModeEnabled
&& !runtimeLocks.contributionMode; && !runtimeLocks.contributionMode;
@@ -272,7 +340,7 @@
: effectiveSignupMethods[0]); : effectiveSignupMethods[0]);
const visibleGroupIds = typeof flowRegistryApi.getVisibleGroupIds === 'function' const visibleGroupIds = typeof flowRegistryApi.getVisibleGroupIds === 'function'
&& isRegisteredFlowId(activeFlowId) && isRegisteredFlowId(activeFlowId)
? flowRegistryApi.getVisibleGroupIds(activeFlowId, effectiveSourceId) ? flowRegistryApi.getVisibleGroupIds(activeFlowId, effectiveIntegrationTargetId)
: []; : [];
return { return {
@@ -283,33 +351,38 @@
canShowPlusSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode), canShowPlusSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode),
canSwitchFlow: Boolean(flowState.canSwitchFlow), canSwitchFlow: Boolean(flowState.canSwitchFlow),
canUsePhoneSignup: canSelectPhoneSignup, canUsePhoneSignup: canSelectPhoneSignup,
canUseSelectedPanelMode: panelModeSupported, canUseSelectedPanelMode: integrationTargetSupported,
effectivePanelMode, effectiveIntegrationTargetId,
effectivePanelMode: effectiveIntegrationTargetId,
effectiveSignupMethod, effectiveSignupMethod,
effectiveSignupMethods, effectiveSignupMethods,
effectiveSourceId, effectiveSourceId: effectiveIntegrationTargetId,
flowCapabilities: flowState, flowCapabilities: flowState,
panelCapabilities: panelState, integrationTargetCapabilities: integrationTargetState,
panelMode: effectivePanelMode, panelCapabilities: integrationTargetState,
requestedPanelMode, panelMode: effectiveIntegrationTargetId,
requestedIntegrationTargetId,
requestedPanelMode: requestedIntegrationTargetId,
requestedSignupMethod, requestedSignupMethod,
runtimeLocks, runtimeLocks,
shouldWarnCpaPhoneSignup: effectiveSignupMethod === SIGNUP_METHOD_PHONE shouldWarnCpaPhoneSignup: effectiveSignupMethod === SIGNUP_METHOD_PHONE
&& Boolean(panelState.requiresPhoneSignupWarning), && Boolean(integrationTargetState.requiresPhoneSignupWarning),
stepDefinitionOptions: { stepDefinitionOptions: {
activeFlowId, activeFlowId,
panelMode: effectivePanelMode, integrationTargetId: effectiveIntegrationTargetId,
panelMode: effectiveIntegrationTargetId,
plusModeEnabled: runtimeLocks.plusModeEnabled, plusModeEnabled: runtimeLocks.plusModeEnabled,
signupMethod: effectiveSignupMethod, signupMethod: effectiveSignupMethod,
}, },
supportedPanelModes, supportedIntegrationTargets,
supportedPanelModes: supportedIntegrationTargets,
visibleGroupIds, visibleGroupIds,
}; };
} }
function buildPhoneSignupValidationError(capabilityState = {}) { function buildPhoneSignupValidationError(capabilityState = {}) {
const flowState = capabilityState.flowCapabilities || {}; const flowState = capabilityState.flowCapabilities || {};
const panelState = capabilityState.panelCapabilities || {}; const integrationTargetState = capabilityState.integrationTargetCapabilities || {};
const runtimeLocks = capabilityState.runtimeLocks || {}; const runtimeLocks = capabilityState.runtimeLocks || {};
if (!flowState.supportsPhoneSignup) { if (!flowState.supportsPhoneSignup) {
@@ -318,10 +391,10 @@
message: '当前 flow 不支持手机号注册。', message: '当前 flow 不支持手机号注册。',
}; };
} }
if (!panelState.supportsPhoneSignup) { if (!integrationTargetState.supportsPhoneSignup) {
return { return {
code: 'phone_signup_panel_unsupported', code: 'phone_signup_panel_unsupported',
message: `当前来源 ${getPanelModeLabel(capabilityState.requestedPanelMode)} 不支持手机号注册。`, message: `当前来源 ${getIntegrationTargetLabel(capabilityState.activeFlowId, capabilityState.requestedIntegrationTargetId)} 不支持手机号注册。`,
}; };
} }
if (!runtimeLocks.phoneVerificationEnabled) { if (!runtimeLocks.phoneVerificationEnabled) {
@@ -354,13 +427,13 @@
const errors = []; const errors = [];
if ( if (
Array.isArray(capabilityState.supportedPanelModes) Array.isArray(capabilityState.supportedIntegrationTargets)
&& capabilityState.supportedPanelModes.length > 0 && capabilityState.supportedIntegrationTargets.length > 0
&& capabilityState.canUseSelectedPanelMode === false && capabilityState.canUseSelectedPanelMode === false
) { ) {
errors.push({ errors.push({
code: 'panel_mode_unsupported', 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)); const shouldReconcileSignupMethod = MODE_SWITCH_RELEVANT_KEYS.some((key) => changedKeySet.has(key));
if ( if (
changedKeySet.has('panelMode') (changedKeySet.has('panelMode') || changedKeySet.has('openaiIntegrationTargetId') || changedKeySet.has('kiroIntegrationTargetId'))
&& Array.isArray(capabilityState.supportedPanelModes) && Array.isArray(capabilityState.supportedIntegrationTargets)
&& capabilityState.supportedPanelModes.length > 0 && capabilityState.supportedIntegrationTargets.length > 0
&& capabilityState.canUseSelectedPanelMode === false && 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({ errors.push({
code: 'panel_mode_unsupported', code: 'panel_mode_unsupported',
message: `当前 flow 不支持 ${getPanelModeLabel(capabilityState.requestedPanelMode)} 来源。`, message: `当前 flow 不支持 ${getIntegrationTargetLabel(capabilityState.activeFlowId, capabilityState.requestedIntegrationTargetId)} 来源。`,
}); });
} }
@@ -480,9 +556,9 @@
return { return {
canUsePhoneSignup, canUsePhoneSignup,
getFlowCapabilities, getFlowCapabilities,
getPanelCapabilities, getOpenAiIntegrationTargetCapabilities,
normalizeFlowId, normalizeFlowId,
normalizePanelMode, normalizeOpenAiIntegrationTargetId,
normalizeSignupMethod, normalizeSignupMethod,
resolveSidepanelCapabilities, resolveSidepanelCapabilities,
resolveSignupMethod, resolveSignupMethod,
@@ -495,15 +571,15 @@
createFlowCapabilityRegistry, createFlowCapabilityRegistry,
DEFAULT_FLOW_CAPABILITIES, DEFAULT_FLOW_CAPABILITIES,
DEFAULT_FLOW_ID, DEFAULT_FLOW_ID,
DEFAULT_PANEL_CAPABILITIES, DEFAULT_INTEGRATION_TARGET_CAPABILITIES,
DEFAULT_PANEL_MODE, DEFAULT_OPENAI_INTEGRATION_TARGET_ID,
FLOW_CAPABILITIES, FLOW_CAPABILITIES,
PANEL_CAPABILITIES, OPENAI_INTEGRATION_TARGET_CAPABILITIES,
SIGNUP_METHOD_EMAIL, SIGNUP_METHOD_EMAIL,
SIGNUP_METHOD_PHONE, SIGNUP_METHOD_PHONE,
VALID_PANEL_MODES, VALID_OPENAI_INTEGRATION_TARGET_IDS,
normalizeFlowId, normalizeFlowId,
normalizePanelMode, normalizeOpenAiIntegrationTargetId,
normalizeSignupMethod, normalizeSignupMethod,
}; };
}); });
+105 -81
View File
@@ -2,11 +2,11 @@
root.MultiPageFlowRegistry = factory(); root.MultiPageFlowRegistry = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createFlowRegistryModule() { })(typeof self !== 'undefined' ? self : globalThis, function createFlowRegistryModule() {
const DEFAULT_FLOW_ID = 'openai'; const DEFAULT_FLOW_ID = 'openai';
const LEGACY_OPENAI_FLOW_ALIAS = 'codex'; const DEFAULT_OPENAI_INTEGRATION_TARGET_ID = 'cpa';
const DEFAULT_OPENAI_SOURCE_ID = 'cpa'; const DEFAULT_KIRO_INTEGRATION_TARGET_ID = 'kiro-rs';
const DEFAULT_KIRO_SOURCE_ID = 'kiro-rs'; const DEFAULT_KIRO_PUBLICATION_TARGET_ID = 'kiro-rs';
const DEFAULT_KIRO_RS_URL = 'https://kiro.leftcode.xyz/admin'; 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 SHARED_SERVICE_IDS = Object.freeze(['account', 'email', 'proxy']);
const DEFAULT_FLOW_CAPABILITIES = Object.freeze({ const DEFAULT_FLOW_CAPABILITIES = Object.freeze({
@@ -15,7 +15,7 @@
supportsPhoneVerificationSettings: false, supportsPhoneVerificationSettings: false,
supportsPlusMode: false, supportsPlusMode: false,
supportsContributionMode: false, supportsContributionMode: false,
supportsPlatformBinding: [], supportedIntegrationTargets: [],
supportsLuckmail: false, supportsLuckmail: false,
supportsOauthTimeoutBudget: false, supportsOauthTimeoutBudget: false,
canSwitchFlow: true, canSwitchFlow: true,
@@ -44,7 +44,7 @@
supportsPhoneVerificationSettings: true, supportsPhoneVerificationSettings: true,
supportsPlusMode: true, supportsPlusMode: true,
supportsContributionMode: true, supportsContributionMode: true,
supportsPlatformBinding: [...OPENAI_SOURCE_IDS], supportedIntegrationTargets: [...OPENAI_INTEGRATION_TARGET_IDS],
supportsLuckmail: true, supportsLuckmail: true,
supportsOauthTimeoutBudget: true, supportsOauthTimeoutBudget: true,
stepDefinitionMode: 'openai-dynamic', stepDefinitionMode: 'openai-dynamic',
@@ -55,24 +55,21 @@
'openai-oauth', 'openai-oauth',
'openai-step6', 'openai-step6',
], ],
sources: { integrationTargets: {
cpa: { cpa: {
id: 'cpa', id: 'cpa',
label: 'CPA 面板', label: 'CPA 面板',
legacyPanelMode: 'cpa', groups: ['openai-target-cpa'],
groups: ['openai-source-cpa'],
}, },
sub2api: { sub2api: {
id: 'sub2api', id: 'sub2api',
label: 'SUB2API', label: 'SUB2API',
legacyPanelMode: 'sub2api', groups: ['openai-target-sub2api'],
groups: ['openai-source-sub2api'],
}, },
codex2api: { codex2api: {
id: 'codex2api', id: 'codex2api',
label: 'Codex2API', label: 'Codex2API',
legacyPanelMode: 'codex2api', groups: ['openai-target-codex2api'],
groups: ['openai-source-codex2api'],
}, },
}, },
runtimeSources: { runtimeSources: {
@@ -206,16 +203,23 @@
services: ['account', 'email', 'proxy'], services: ['account', 'email', 'proxy'],
capabilities: { capabilities: {
...DEFAULT_FLOW_CAPABILITIES, ...DEFAULT_FLOW_CAPABILITIES,
supportedIntegrationTargets: [DEFAULT_KIRO_INTEGRATION_TARGET_ID],
stepDefinitionMode: 'kiro-device-auth', stepDefinitionMode: 'kiro-device-auth',
}, },
baseGroups: [ baseGroups: [
'kiro-runtime-status', 'kiro-runtime-status',
], ],
sources: { integrationTargets: {
'kiro-rs': {
id: 'kiro-rs',
label: 'kiro.rs',
groups: ['kiro-target-kiro-rs'],
},
},
publicationTargets: {
'kiro-rs': { 'kiro-rs': {
id: 'kiro-rs', id: 'kiro-rs',
label: 'kiro.rs', label: 'kiro.rs',
groups: ['kiro-source-kiro-rs'],
}, },
}, },
runtimeSources: { runtimeSources: {
@@ -280,13 +284,13 @@
label: 'IP 代理', label: 'IP 代理',
sectionIds: ['ip-proxy-section'], sectionIds: ['ip-proxy-section'],
}, },
'openai-source-cpa': { 'openai-target-cpa': {
id: 'openai-source-cpa', id: 'openai-target-cpa',
label: 'CPA 来源', label: 'CPA 来源',
rowIds: ['row-vps-url', 'row-vps-password', 'row-local-cpa-step9-mode'], rowIds: ['row-vps-url', 'row-vps-password', 'row-local-cpa-step9-mode'],
}, },
'openai-source-sub2api': { 'openai-target-sub2api': {
id: 'openai-source-sub2api', id: 'openai-target-sub2api',
label: 'SUB2API 来源', label: 'SUB2API 来源',
rowIds: [ rowIds: [
'row-sub2api-url', 'row-sub2api-url',
@@ -297,17 +301,15 @@
'row-sub2api-default-proxy', 'row-sub2api-default-proxy',
], ],
}, },
'openai-source-codex2api': { 'openai-target-codex2api': {
id: 'openai-source-codex2api', id: 'openai-target-codex2api',
label: 'Codex2API 来源', label: 'Codex2API 来源',
rowIds: ['row-codex2api-url', 'row-codex2api-admin-key'], rowIds: ['row-codex2api-url', 'row-codex2api-admin-key'],
}, },
'openai-plus': { 'openai-plus': {
id: 'openai-plus', id: 'openai-plus',
label: 'Plus', label: 'Plus',
rowIds: [ rowIds: ['row-plus-mode'],
'row-plus-mode',
],
}, },
'openai-phone': { 'openai-phone': {
id: 'openai-phone', id: 'openai-phone',
@@ -325,8 +327,8 @@
label: '第六步', label: '第六步',
rowIds: ['row-step6-cookie-settings'], rowIds: ['row-step6-cookie-settings'],
}, },
'kiro-source-kiro-rs': { 'kiro-target-kiro-rs': {
id: 'kiro-source-kiro-rs', id: 'kiro-target-kiro-rs',
label: 'kiro.rs 配置', label: 'kiro.rs 配置',
rowIds: ['row-kiro-rs-url', 'row-kiro-rs-key'], rowIds: ['row-kiro-rs-url', 'row-kiro-rs-key'],
}, },
@@ -339,16 +341,10 @@
function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) { function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) {
const normalized = String(value || '').trim().toLowerCase(); 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)) { if (normalized && Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, normalized)) {
return normalized; return normalized;
} }
const fallbackValue = String(fallback || '').trim().toLowerCase(); const fallbackValue = String(fallback || '').trim().toLowerCase();
if (fallbackValue === LEGACY_OPENAI_FLOW_ALIAS) {
return DEFAULT_FLOW_ID;
}
return Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, fallbackValue) return Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, fallbackValue)
? fallbackValue ? fallbackValue
: DEFAULT_FLOW_ID; : DEFAULT_FLOW_ID;
@@ -359,73 +355,92 @@
} }
function getFlowDefinition(flowId) { function getFlowDefinition(flowId) {
const normalizedFlowId = normalizeFlowId(flowId); return FLOW_DEFINITIONS[normalizeFlowId(flowId)] || FLOW_DEFINITIONS[DEFAULT_FLOW_ID];
return FLOW_DEFINITIONS[normalizedFlowId] || FLOW_DEFINITIONS[DEFAULT_FLOW_ID];
} }
function getFlowLabel(flowId) { function getFlowLabel(flowId) {
return getFlowDefinition(flowId)?.label || normalizeFlowId(flowId); return getFlowDefinition(flowId)?.label || normalizeFlowId(flowId);
} }
function getDefaultSourceId(flowId) { function getDefaultIntegrationTargetId(flowId) {
return normalizeFlowId(flowId) === 'kiro' return normalizeFlowId(flowId) === 'kiro'
? DEFAULT_KIRO_SOURCE_ID ? DEFAULT_KIRO_INTEGRATION_TARGET_ID
: DEFAULT_OPENAI_SOURCE_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(); const normalized = String(value || '').trim().toLowerCase();
if (OPENAI_SOURCE_IDS.includes(normalized)) { if (OPENAI_INTEGRATION_TARGET_IDS.includes(normalized)) {
return normalized; return normalized;
} }
const fallbackValue = String(fallback || '').trim().toLowerCase(); 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(); const normalized = String(value || '').trim().toLowerCase();
if (normalized === DEFAULT_KIRO_SOURCE_ID) { if (normalized === DEFAULT_KIRO_INTEGRATION_TARGET_ID) {
return normalized; return normalized;
} }
const fallbackValue = String(fallback || '').trim().toLowerCase(); 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); const normalizedFlowId = normalizeFlowId(flowId);
if (normalizedFlowId === 'kiro') { 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) { function getIntegrationTargetDefinitions(flowId) {
return getFlowDefinition(flowId)?.sources || {}; return getFlowDefinition(flowId)?.integrationTargets || {};
} }
function getSourceDefinition(flowId, sourceId) { function getIntegrationTargetDefinition(flowId, integrationTargetId) {
const normalizedFlowId = normalizeFlowId(flowId); const normalizedFlowId = normalizeFlowId(flowId);
const normalizedSourceId = normalizeSourceId(normalizedFlowId, sourceId, getDefaultSourceId(normalizedFlowId)); const normalizedIntegrationTargetId = normalizeIntegrationTargetId(
return getSourceDefinitions(normalizedFlowId)[normalizedSourceId] || null; normalizedFlowId,
integrationTargetId,
getDefaultIntegrationTargetId(normalizedFlowId)
);
return getIntegrationTargetDefinitions(normalizedFlowId)[normalizedIntegrationTargetId] || null;
} }
function getSourceOptions(flowId) { function getIntegrationTargetOptions(flowId) {
return Object.values(getSourceDefinitions(flowId)); return Object.values(getIntegrationTargetDefinitions(flowId));
} }
function getSourceLabel(flowId, sourceId) { function getIntegrationTargetLabel(flowId, integrationTargetId) {
return getSourceDefinition(flowId, sourceId)?.label || normalizeSourceId(flowId, sourceId); return getIntegrationTargetDefinition(flowId, integrationTargetId)?.label
|| normalizeIntegrationTargetId(flowId, integrationTargetId);
} }
function mapPanelModeToSourceId(panelMode = '', fallback = DEFAULT_OPENAI_SOURCE_ID) { function getPublicationTargetDefinitions(flowId) {
return normalizeOpenAiSourceId(panelMode, fallback); return getFlowDefinition(flowId)?.publicationTargets || {};
} }
function mapSourceIdToPanelMode(flowId, sourceId = '', fallback = DEFAULT_OPENAI_SOURCE_ID) { function getPublicationTargetDefinition(flowId, publicationTargetId) {
if (normalizeFlowId(flowId) !== DEFAULT_FLOW_ID) { const normalizedFlowId = normalizeFlowId(flowId);
return normalizeOpenAiSourceId(fallback, DEFAULT_OPENAI_SOURCE_ID); const normalizedPublicationTargetId = String(
} publicationTargetId || (
return normalizeOpenAiSourceId(sourceId, fallback || DEFAULT_OPENAI_SOURCE_ID); normalizedFlowId === 'kiro'
? DEFAULT_KIRO_PUBLICATION_TARGET_ID
: ''
)
).trim().toLowerCase();
return getPublicationTargetDefinitions(normalizedFlowId)[normalizedPublicationTargetId] || null;
} }
function getFlowCapabilities(flowId) { function getFlowCapabilities(flowId) {
@@ -435,18 +450,27 @@
}; };
} }
function getVisibleGroupIds(flowId, sourceId, options = {}) { function getVisibleGroupIds(flowId, integrationTargetId, options = {}) {
const normalizedFlowId = normalizeFlowId(flowId); const normalizedFlowId = normalizeFlowId(flowId);
const flowDefinition = getFlowDefinition(normalizedFlowId); const flowDefinition = getFlowDefinition(normalizedFlowId);
const normalizedSourceId = normalizeSourceId(normalizedFlowId, sourceId, getDefaultSourceId(normalizedFlowId)); const normalizedIntegrationTargetId = normalizeIntegrationTargetId(
const sourceDefinition = getSourceDefinition(normalizedFlowId, normalizedSourceId); normalizedFlowId,
integrationTargetId,
getDefaultIntegrationTargetId(normalizedFlowId)
);
const integrationTargetDefinition = getIntegrationTargetDefinition(
normalizedFlowId,
normalizedIntegrationTargetId
);
const includeSharedServices = options?.includeSharedServices !== false; const includeSharedServices = options?.includeSharedServices !== false;
const serviceGroups = includeSharedServices 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([ return Array.from(new Set([
...(Array.isArray(flowDefinition?.baseGroups) ? flowDefinition.baseGroups : []), ...(Array.isArray(flowDefinition?.baseGroups) ? flowDefinition.baseGroups : []),
...(Array.isArray(sourceDefinition?.groups) ? sourceDefinition.groups : []), ...(Array.isArray(integrationTargetDefinition?.groups) ? integrationTargetDefinition.groups : []),
...serviceGroups, ...serviceGroups,
])); ]));
} }
@@ -479,33 +503,33 @@
return { return {
DEFAULT_FLOW_CAPABILITIES, DEFAULT_FLOW_CAPABILITIES,
DEFAULT_FLOW_ID, DEFAULT_FLOW_ID,
DEFAULT_KIRO_INTEGRATION_TARGET_ID,
DEFAULT_KIRO_PUBLICATION_TARGET_ID,
DEFAULT_KIRO_RS_URL, DEFAULT_KIRO_RS_URL,
DEFAULT_KIRO_SOURCE_ID, DEFAULT_OPENAI_INTEGRATION_TARGET_ID,
DEFAULT_OPENAI_SOURCE_ID,
FLOW_DEFINITIONS, FLOW_DEFINITIONS,
LEGACY_OPENAI_FLOW_ALIAS, OPENAI_INTEGRATION_TARGET_IDS,
OPENAI_SOURCE_IDS,
SETTINGS_GROUP_DEFINITIONS, SETTINGS_GROUP_DEFINITIONS,
SHARED_SERVICE_IDS, SHARED_SERVICE_IDS,
getDefaultSourceId, getDefaultIntegrationTargetId,
getDriverDefinitions, getDriverDefinitions,
getFlowCapabilities, getFlowCapabilities,
getFlowDefinition, getFlowDefinition,
getFlowLabel, getFlowLabel,
getIntegrationTargetDefinition,
getIntegrationTargetDefinitions,
getIntegrationTargetLabel,
getIntegrationTargetOptions,
getPublicationTargetDefinition,
getPublicationTargetDefinitions,
getRegisteredFlowIds, getRegisteredFlowIds,
getRuntimeSourceDefinitions, getRuntimeSourceDefinitions,
getSettingsGroupDefinition, getSettingsGroupDefinition,
getSettingsGroupDefinitions, getSettingsGroupDefinitions,
getSourceDefinition,
getSourceDefinitions,
getSourceLabel,
getSourceOptions,
getVisibleGroupIds, getVisibleGroupIds,
mapPanelModeToSourceId,
mapSourceIdToPanelMode,
normalizeFlowId, normalizeFlowId,
normalizeKiroSourceId, normalizeIntegrationTargetId,
normalizeOpenAiSourceId, normalizeKiroIntegrationTargetId,
normalizeSourceId, normalizeOpenAiIntegrationTargetId,
}; };
}); });
+233 -188
View File
@@ -32,9 +32,11 @@
function createSettingsSchema(deps = {}) { function createSettingsSchema(deps = {}) {
const rootScope = typeof self !== 'undefined' ? self : globalThis; const rootScope = typeof self !== 'undefined' ? self : globalThis;
const flowRegistry = deps.flowRegistry || rootScope.MultiPageFlowRegistry || {}; const flowRegistry = deps.flowRegistry || rootScope.MultiPageFlowRegistry || {};
const defaultFlowId = String(deps.defaultFlowId || flowRegistry.DEFAULT_FLOW_ID || 'openai').trim().toLowerCase() || 'openai'; const defaultFlowId = String(
const defaultOpenAiSourceId = flowRegistry.DEFAULT_OPENAI_SOURCE_ID || 'cpa'; deps.defaultFlowId || flowRegistry.DEFAULT_FLOW_ID || 'openai'
const defaultKiroSourceId = flowRegistry.DEFAULT_KIRO_SOURCE_ID || 'kiro-rs'; ).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 defaultKiroRsUrl = flowRegistry.DEFAULT_KIRO_RS_URL || 'https://kiro.leftcode.xyz/admin';
const normalizeFlowId = typeof flowRegistry.normalizeFlowId === 'function' const normalizeFlowId = typeof flowRegistry.normalizeFlowId === 'function'
? flowRegistry.normalizeFlowId ? flowRegistry.normalizeFlowId
@@ -42,19 +44,13 @@
const normalized = String(value || '').trim().toLowerCase(); const normalized = String(value || '').trim().toLowerCase();
return normalized || String(fallback || '').trim().toLowerCase() || defaultFlowId; return normalized || String(fallback || '').trim().toLowerCase() || defaultFlowId;
}); });
const normalizeSourceId = typeof flowRegistry.normalizeSourceId === 'function' const normalizeIntegrationTargetId = typeof flowRegistry.normalizeIntegrationTargetId === 'function'
? flowRegistry.normalizeSourceId ? flowRegistry.normalizeIntegrationTargetId
: ((flowId, value = '', fallback = '') => String(value || fallback || '').trim().toLowerCase()); : ((_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());
function buildDefaultSettingsState() { function buildDefaultSettingsState() {
return { return {
schemaVersion: 3, schemaVersion: 4,
activeFlowId: defaultFlowId, activeFlowId: defaultFlowId,
services: { services: {
account: { account: {
@@ -71,27 +67,25 @@
}, },
flows: { flows: {
openai: { openai: {
source: { integrationTargetId: defaultOpenAiIntegrationTargetId,
selected: defaultOpenAiSourceId, integrationTargets: {
entries: { cpa: {
cpa: { vpsUrl: '',
vpsUrl: '', vpsPassword: '',
vpsPassword: '', localCpaStep9Mode: 'submit',
localCpaStep9Mode: 'submit', },
}, sub2api: {
sub2api: { sub2apiUrl: '',
sub2apiUrl: '', sub2apiEmail: '',
sub2apiEmail: '', sub2apiPassword: '',
sub2apiPassword: '', sub2apiGroupName: 'codex',
sub2apiGroupName: 'codex', sub2apiGroupNames: ['codex', 'openai-plus'],
sub2apiGroupNames: ['codex', 'openai-plus'], sub2apiAccountPriority: 1,
sub2apiAccountPriority: 1, sub2apiDefaultProxyName: '',
sub2apiDefaultProxyName: '', },
}, codex2api: {
codex2api: { codex2apiUrl: '',
codex2apiUrl: '', codex2apiAdminKey: '',
codex2apiAdminKey: '',
},
}, },
}, },
signup: { signup: {
@@ -112,21 +106,13 @@
}, },
}, },
kiro: { kiro: {
source: { integrationTargetId: defaultKiroIntegrationTargetId,
selected: defaultKiroSourceId, integrationTargets: {
entries: { 'kiro-rs': {
'kiro-rs': { baseUrl: defaultKiroRsUrl,
kiroRsUrl: defaultKiroRsUrl, apiKey: '',
kiroRsKey: '',
},
}, },
}, },
options: {
kiroRsPriority: 0,
kiroRsEndpoint: '',
kiroRsAuthRegion: '',
kiroRsApiRegion: '',
},
autoRun: { autoRun: {
stepExecutionRange: { stepExecutionRange: {
enabled: false, enabled: false,
@@ -139,7 +125,7 @@
}; };
} }
function getSourceValue(settingsState, pathGetter, fallback = {}) { function getIntegrationTargetValue(settingsState, pathGetter, fallback = {}) {
return cloneValue(pathGetter(isPlainObject(settingsState) ? settingsState : {}) || fallback); return cloneValue(pathGetter(isPlainObject(settingsState) ? settingsState : {}) || fallback);
} }
@@ -155,20 +141,21 @@
?? defaults.activeFlowId, ?? defaults.activeFlowId,
defaults.activeFlowId defaults.activeFlowId
); );
const openaiSelectedSource = normalizeSourceId( const openaiIntegrationTargetId = normalizeIntegrationTargetId(
'openai', 'openai',
nested?.flows?.openai?.source?.selected nested?.flows?.openai?.integrationTargetId
?? input?.openaiIntegrationTargetId
?? input?.panelMode ?? input?.panelMode
?? input?.openaiSourceId ?? defaults.flows.openai.integrationTargetId,
?? defaults.flows.openai.source.selected, defaults.flows.openai.integrationTargetId
defaults.flows.openai.source.selected
); );
const kiroSelectedSource = normalizeSourceId( const kiroIntegrationTargetId = normalizeIntegrationTargetId(
'kiro', 'kiro',
nested?.flows?.kiro?.source?.selected nested?.flows?.kiro?.integrationTargetId
?? input?.kiroIntegrationTargetId
?? input?.kiroSourceId ?? input?.kiroSourceId
?? defaults.flows.kiro.source.selected, ?? defaults.flows.kiro.integrationTargetId,
defaults.flows.kiro.source.selected defaults.flows.kiro.integrationTargetId
); );
const stepExecutionRangeByFlow = isPlainObject(input?.stepExecutionRangeByFlow) const stepExecutionRangeByFlow = isPlainObject(input?.stepExecutionRangeByFlow)
? input.stepExecutionRangeByFlow ? input.stepExecutionRangeByFlow
@@ -206,58 +193,115 @@
customPassword: String( customPassword: String(
input?.customPassword input?.customPassword
?? nested?.services?.account?.customPassword ?? nested?.services?.account?.customPassword
?? nested?.flows?.openai?.account?.customPassword
?? defaults.services.account.customPassword ?? defaults.services.account.customPassword
).trim(), ).trim(),
}, },
}, },
flows: { flows: {
openai: { openai: {
source: { integrationTargetId: openaiIntegrationTargetId,
selected: openaiSelectedSource, integrationTargets: {
entries: { cpa: {
cpa: { ...defaults.flows.openai.integrationTargets.cpa,
...defaults.flows.openai.source.entries.cpa, ...getIntegrationTargetValue(nested, (state) => state.flows?.openai?.integrationTargets?.cpa),
...getSourceValue(nested, (state) => state.flows?.openai?.source?.entries?.cpa), vpsUrl: String(
vpsUrl: String(input?.vpsUrl ?? nested?.flows?.openai?.source?.entries?.cpa?.vpsUrl ?? '').trim(), input?.vpsUrl
vpsPassword: String(input?.vpsPassword ?? nested?.flows?.openai?.source?.entries?.cpa?.vpsPassword ?? ''), ?? nested?.flows?.openai?.integrationTargets?.cpa?.vpsUrl
localCpaStep9Mode: String( ?? ''
input?.localCpaStep9Mode ).trim(),
?? nested?.flows?.openai?.source?.entries?.cpa?.localCpaStep9Mode vpsPassword: String(
?? defaults.flows.openai.source.entries.cpa.localCpaStep9Mode input?.vpsPassword
).trim() || defaults.flows.openai.source.entries.cpa.localCpaStep9Mode, ?? nested?.flows?.openai?.integrationTargets?.cpa?.vpsPassword
}, ?? ''
sub2api: { ),
...defaults.flows.openai.source.entries.sub2api, localCpaStep9Mode: String(
...getSourceValue(nested, (state) => state.flows?.openai?.source?.entries?.sub2api), input?.localCpaStep9Mode
sub2apiUrl: String(input?.sub2apiUrl ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiUrl ?? '').trim(), ?? nested?.flows?.openai?.integrationTargets?.cpa?.localCpaStep9Mode
sub2apiEmail: String(input?.sub2apiEmail ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiEmail ?? '').trim(), ?? defaults.flows.openai.integrationTargets.cpa.localCpaStep9Mode
sub2apiPassword: String(input?.sub2apiPassword ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiPassword ?? ''), ).trim() || defaults.flows.openai.integrationTargets.cpa.localCpaStep9Mode,
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) sub2api: {
? input.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean) ...defaults.flows.openai.integrationTargets.sub2api,
: (Array.isArray(nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiGroupNames) ...getIntegrationTargetValue(nested, (state) => state.flows?.openai?.integrationTargets?.sub2api),
? nested.flows.openai.source.entries.sub2api.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean) sub2apiUrl: String(
: [...defaults.flows.openai.source.entries.sub2api.sub2apiGroupNames]), input?.sub2apiUrl
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), ?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiUrl
sub2apiDefaultProxyName: String(input?.sub2apiDefaultProxyName ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiDefaultProxyName ?? '').trim(), ?? ''
}, ).trim(),
codex2api: { sub2apiEmail: String(
...defaults.flows.openai.source.entries.codex2api, input?.sub2apiEmail
...getSourceValue(nested, (state) => state.flows?.openai?.source?.entries?.codex2api), ?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiEmail
codex2apiUrl: String(input?.codex2apiUrl ?? nested?.flows?.openai?.source?.entries?.codex2api?.codex2apiUrl ?? '').trim(), ?? ''
codex2apiAdminKey: String(input?.codex2apiAdminKey ?? nested?.flows?.openai?.source?.entries?.codex2api?.codex2apiAdminKey ?? '').trim(), ).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: { signup: {
signupMethod: String(input?.signupMethod ?? nested?.flows?.openai?.signup?.signupMethod ?? defaults.flows.openai.signup.signupMethod).trim().toLowerCase() === 'phone' ? 'phone' : 'email', signupMethod: String(
phoneVerificationEnabled: Boolean(input?.phoneVerificationEnabled ?? nested?.flows?.openai?.signup?.phoneVerificationEnabled ?? defaults.flows.openai.signup.phoneVerificationEnabled), input?.signupMethod
phoneSignupReloginAfterBindEmailEnabled: Boolean(input?.phoneSignupReloginAfterBindEmailEnabled ?? nested?.flows?.openai?.signup?.phoneSignupReloginAfterBindEmailEnabled ?? defaults.flows.openai.signup.phoneSignupReloginAfterBindEmailEnabled), ?? 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: { plus: {
plusModeEnabled: Boolean(input?.plusModeEnabled ?? nested?.flows?.openai?.plus?.plusModeEnabled ?? defaults.flows.openai.plus.plusModeEnabled), plusModeEnabled: Boolean(
plusPaymentMethod: String(input?.plusPaymentMethod ?? nested?.flows?.openai?.plus?.plusPaymentMethod ?? defaults.flows.openai.plus.plusPaymentMethod).trim() || defaults.flows.openai.plus.plusPaymentMethod, 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: { autoRun: {
stepExecutionRange: normalizeStepExecutionRangeEntry( stepExecutionRange: normalizeStepExecutionRangeEntry(
@@ -269,47 +313,25 @@
}, },
}, },
kiro: { kiro: {
source: { integrationTargetId: kiroIntegrationTargetId,
selected: kiroSelectedSource, integrationTargets: {
entries: { 'kiro-rs': {
'kiro-rs': { ...defaults.flows.kiro.integrationTargets['kiro-rs'],
...defaults.flows.kiro.source.entries['kiro-rs'], ...getIntegrationTargetValue(nested, (state) => state.flows?.kiro?.integrationTargets?.['kiro-rs']),
...getSourceValue(nested, (state) => state.flows?.kiro?.source?.entries?.['kiro-rs']), baseUrl: String(
kiroRsUrl: String( input?.kiroRsUrl
input?.kiroRsUrl ?? input?.kiroRsBaseUrl
?? nested?.flows?.kiro?.source?.entries?.['kiro-rs']?.kiroRsUrl ?? nested?.flows?.kiro?.integrationTargets?.['kiro-rs']?.baseUrl
?? defaults.flows.kiro.source.entries['kiro-rs'].kiroRsUrl ?? defaults.flows.kiro.integrationTargets['kiro-rs'].baseUrl
).trim() || defaults.flows.kiro.source.entries['kiro-rs'].kiroRsUrl, ).trim() || defaults.flows.kiro.integrationTargets['kiro-rs'].baseUrl,
kiroRsKey: String( apiKey: String(
input?.kiroRsKey input?.kiroRsKey
?? nested?.flows?.kiro?.source?.entries?.['kiro-rs']?.kiroRsKey ?? input?.kiroRsApiKey
?? defaults.flows.kiro.source.entries['kiro-rs'].kiroRsKey ?? 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: { autoRun: {
stepExecutionRange: normalizeStepExecutionRangeEntry( stepExecutionRange: normalizeStepExecutionRangeEntry(
nested?.flows?.kiro?.autoRun?.stepExecutionRange 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 = {}) { function buildStepExecutionRangeByFlow(settingsState = {}) {
const normalizedState = normalizeSettingsState(settingsState); const normalizedState = normalizeSettingsState(settingsState);
return { return {
@@ -337,23 +406,7 @@
}; };
} }
function getFlowSettings(settingsState = {}, flowId) { function buildSettingsView(settingsState = {}, baseInput = {}) {
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 = {}) {
const normalizedState = normalizeSettingsState(settingsState); const normalizedState = normalizeSettingsState(settingsState);
const next = { const next = {
...(isPlainObject(baseInput) ? cloneValue(baseInput) : {}), ...(isPlainObject(baseInput) ? cloneValue(baseInput) : {}),
@@ -361,20 +414,22 @@
const openaiState = normalizedState.flows.openai; const openaiState = normalizedState.flows.openai;
const kiroState = normalizedState.flows.kiro; const kiroState = normalizedState.flows.kiro;
next.activeFlowId = normalizedState.activeFlowId; next.activeFlowId = normalizedState.activeFlowId;
next.panelMode = mapSourceIdToPanelMode('openai', openaiState.source.selected, defaultOpenAiSourceId); next.openaiIntegrationTargetId = getSelectedIntegrationTargetId(normalizedState, 'openai');
next.kiroSourceId = getSelectedSourceId(normalizedState, 'kiro'); next.kiroIntegrationTargetId = getSelectedIntegrationTargetId(normalizedState, 'kiro');
next.vpsUrl = openaiState.source.entries.cpa.vpsUrl; next.panelMode = next.openaiIntegrationTargetId;
next.vpsPassword = openaiState.source.entries.cpa.vpsPassword; next.kiroSourceId = next.kiroIntegrationTargetId;
next.localCpaStep9Mode = openaiState.source.entries.cpa.localCpaStep9Mode; next.vpsUrl = openaiState.integrationTargets.cpa.vpsUrl;
next.sub2apiUrl = openaiState.source.entries.sub2api.sub2apiUrl; next.vpsPassword = openaiState.integrationTargets.cpa.vpsPassword;
next.sub2apiEmail = openaiState.source.entries.sub2api.sub2apiEmail; next.localCpaStep9Mode = openaiState.integrationTargets.cpa.localCpaStep9Mode;
next.sub2apiPassword = openaiState.source.entries.sub2api.sub2apiPassword; next.sub2apiUrl = openaiState.integrationTargets.sub2api.sub2apiUrl;
next.sub2apiGroupName = openaiState.source.entries.sub2api.sub2apiGroupName; next.sub2apiEmail = openaiState.integrationTargets.sub2api.sub2apiEmail;
next.sub2apiGroupNames = cloneValue(openaiState.source.entries.sub2api.sub2apiGroupNames); next.sub2apiPassword = openaiState.integrationTargets.sub2api.sub2apiPassword;
next.sub2apiAccountPriority = openaiState.source.entries.sub2api.sub2apiAccountPriority; next.sub2apiGroupName = openaiState.integrationTargets.sub2api.sub2apiGroupName;
next.sub2apiDefaultProxyName = openaiState.source.entries.sub2api.sub2apiDefaultProxyName; next.sub2apiGroupNames = cloneValue(openaiState.integrationTargets.sub2api.sub2apiGroupNames);
next.codex2apiUrl = openaiState.source.entries.codex2api.codex2apiUrl; next.sub2apiAccountPriority = openaiState.integrationTargets.sub2api.sub2apiAccountPriority;
next.codex2apiAdminKey = openaiState.source.entries.codex2api.codex2apiAdminKey; 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.customPassword = normalizedState.services.account.customPassword;
next.signupMethod = openaiState.signup.signupMethod; next.signupMethod = openaiState.signup.signupMethod;
next.phoneVerificationEnabled = openaiState.signup.phoneVerificationEnabled; next.phoneVerificationEnabled = openaiState.signup.phoneVerificationEnabled;
@@ -385,13 +440,8 @@
next.ipProxyEnabled = normalizedState.services.proxy.enabled; next.ipProxyEnabled = normalizedState.services.proxy.enabled;
next.ipProxyService = normalizedState.services.proxy.provider; next.ipProxyService = normalizedState.services.proxy.provider;
next.ipProxyMode = normalizedState.services.proxy.mode; next.ipProxyMode = normalizedState.services.proxy.mode;
next.kiroRsUrl = kiroState.source.entries['kiro-rs'].kiroRsUrl; next.kiroRsUrl = kiroState.integrationTargets['kiro-rs'].baseUrl;
next.kiroRsKey = kiroState.source.entries['kiro-rs'].kiroRsKey; next.kiroRsKey = kiroState.integrationTargets['kiro-rs'].apiKey;
next.kiroRsPriority = kiroState.options.kiroRsPriority;
next.kiroRsEndpoint = kiroState.options.kiroRsEndpoint;
next.kiroRsAuthRegion = kiroState.options.kiroRsAuthRegion;
next.kiroRsApiRegion = kiroState.options.kiroRsApiRegion;
delete next.kiroRegion;
next.stepExecutionRangeByFlow = buildStepExecutionRangeByFlow(normalizedState); next.stepExecutionRangeByFlow = buildStepExecutionRangeByFlow(normalizedState);
next.settingsSchemaVersion = normalizedState.schemaVersion; next.settingsSchemaVersion = normalizedState.schemaVersion;
next.settingsState = cloneValue(normalizedState); next.settingsState = cloneValue(normalizedState);
@@ -401,34 +451,29 @@
function getFlowInputState(settingsState = {}, flowId) { function getFlowInputState(settingsState = {}, flowId) {
const normalizedState = normalizeSettingsState(settingsState); const normalizedState = normalizeSettingsState(settingsState);
const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId); const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId);
const integrationTargetId = getSelectedIntegrationTargetId(normalizedState, normalizedFlowId);
if (normalizedFlowId === 'kiro') { if (normalizedFlowId === 'kiro') {
return { return {
activeFlowId: normalizedFlowId, activeFlowId: normalizedFlowId,
sourceId: getSelectedSourceId(normalizedState, 'kiro'), integrationTargetId,
kiroRsUrl: normalizedState.flows.kiro.source.entries['kiro-rs'].kiroRsUrl, kiroRsUrl: normalizedState.flows.kiro.integrationTargets['kiro-rs'].baseUrl,
kiroRsKey: normalizedState.flows.kiro.source.entries['kiro-rs'].kiroRsKey, kiroRsKey: normalizedState.flows.kiro.integrationTargets['kiro-rs'].apiKey,
}; };
} }
return { return {
activeFlowId: normalizedFlowId, activeFlowId: normalizedFlowId,
sourceId: getSelectedSourceId(normalizedState, 'openai'), integrationTargetId,
panelMode: mapSourceIdToPanelMode('openai', normalizedState.flows.openai.source.selected, defaultOpenAiSourceId),
}; };
} }
function normalizeFlatInput(input = {}) {
const state = normalizeSettingsState(input);
return buildLegacySettingsPayload(state, input);
}
return { return {
buildDefaultSettingsState, buildDefaultSettingsState,
buildLegacySettingsPayload, buildSettingsView,
buildStepExecutionRangeByFlow, buildStepExecutionRangeByFlow,
getFlowInputState, getFlowInputState,
getFlowSettings, getFlowSettings,
getSelectedSourceId, getSelectedIntegrationTargetId,
normalizeFlatInput, mergeSettingsState,
normalizeSettingsState, normalizeSettingsState,
}; };
} }
+3 -3
View File
@@ -683,10 +683,10 @@
</label> </label>
</div> </div>
<div class="setting-group setting-group-secondary step-range-controls"> <div class="setting-group setting-group-secondary step-range-controls">
<span class="setting-caption">步骤</span> <span class="setting-caption">节点</span>
<input type="number" id="input-step-execution-range-from" class="data-input auto-delay-input step-range-input" value="1" min="1" step="1" title="允许执行的起始步骤" /> <select id="input-step-execution-range-from" class="data-select step-range-input" title="允许执行的起始节点"></select>
<span class="data-unit"></span> <span class="data-unit"></span>
<input type="number" id="input-step-execution-range-to" class="data-input auto-delay-input step-range-input" value="1" min="1" step="1" title="允许执行的结束步骤" /> <select id="input-step-execution-range-to" class="data-select step-range-input" title="允许执行的结束节点"></select>
</div> </div>
</div> </div>
</div> </div>
+81 -31
View File
@@ -884,18 +884,17 @@ function getWorkflowNodesForMode(plusModeEnabled = false, options = {}) {
}); });
if (Array.isArray(nodes) && nodes.length) { if (Array.isArray(nodes) && nodes.length) {
return nodes.slice().sort((left, right) => { return nodes.slice().sort((left, right) => {
const leftOrder = Number.isFinite(Number(left.displayOrder)) ? Number(left.displayOrder) : Number(left.legacyStepId); const leftOrder = Number.isFinite(Number(left.displayOrder)) ? Number(left.displayOrder) : 0;
const rightOrder = Number.isFinite(Number(right.displayOrder)) ? Number(right.displayOrder) : Number(right.legacyStepId); const rightOrder = Number.isFinite(Number(right.displayOrder)) ? Number(right.displayOrder) : 0;
if (leftOrder !== rightOrder) return leftOrder - rightOrder; if (leftOrder !== rightOrder) return leftOrder - rightOrder;
return String(left.nodeId || '').localeCompare(String(right.nodeId || '')); return String(left.nodeId || '').localeCompare(String(right.nodeId || ''));
}); });
} }
return getStepDefinitionsForMode(plusModeEnabled, options).map((step) => ({ return getStepDefinitionsForMode(plusModeEnabled, options).map((step) => ({
legacyStepId: Number(step.id),
nodeId: String(step.key || '').trim(), nodeId: String(step.key || '').trim(),
title: step.title, 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(), executeKey: String(step.key || '').trim(),
})).filter((node) => node.nodeId); })).filter((node) => node.nodeId);
} }
@@ -911,7 +910,7 @@ function getStepIdByKeyForCurrentMode(stepKey = '') {
function getNodeIdByStepForCurrentMode(step) { function getNodeIdByStepForCurrentMode(step) {
const numericStep = Number(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) { if (node?.nodeId) {
return String(node.nodeId).trim(); return String(node.nodeId).trim();
} }
@@ -925,9 +924,9 @@ function getStepIdByNodeIdForCurrentMode(nodeId = '') {
return 0; return 0;
} }
const node = (workflowNodes || []).find((candidate) => String(candidate?.nodeId || '').trim() === normalizedNodeId); const node = (workflowNodes || []).find((candidate) => String(candidate?.nodeId || '').trim() === normalizedNodeId);
const legacyStepId = Number(node?.legacyStepId); const displayOrder = Number(node?.displayOrder);
if (Number.isInteger(legacyStepId) && legacyStepId > 0) { if (Number.isInteger(displayOrder) && displayOrder > 0) {
return legacyStepId; return displayOrder;
} }
return getStepIdByKeyForCurrentMode(normalizedNodeId); return getStepIdByKeyForCurrentMode(normalizedNodeId);
} }
@@ -970,10 +969,9 @@ function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) {
phoneSignupReloginAfterBindEmailEnabled: currentPhoneSignupReloginAfterBindEmailEnabled, phoneSignupReloginAfterBindEmailEnabled: currentPhoneSignupReloginAfterBindEmailEnabled,
}) })
: stepDefinitions.map((step) => ({ : stepDefinitions.map((step) => ({
legacyStepId: Number(step.id),
nodeId: String(step.key || step.id || '').trim(), nodeId: String(step.key || step.id || '').trim(),
title: step.title, 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') { if (typeof workflowNodes !== 'undefined') {
workflowNodes = nextWorkflowNodes; workflowNodes = nextWorkflowNodes;
@@ -2436,24 +2434,76 @@ function getLastCurrentStepId() {
return STEP_IDS.length ? Math.max(...STEP_IDS) : 1; 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 `<option value="${escapeHtml(nodeId)}"${nodeId === selectedValue ? ' selected' : ''}>${escapeHtml(label)}</option>`;
}).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) { function isStepExecutionRangeUiAvailable(state = latestState) {
return getCurrentStepExecutionRangeFlowId(state) === DEFAULT_ACTIVE_FLOW_ID; return getCurrentStepExecutionRangeFlowId(state) === DEFAULT_ACTIVE_FLOW_ID;
} }
function clampStepExecutionRangeInputs() { function clampStepExecutionRangeInputs() {
const maxStep = getLastCurrentStepId(); const nodes = getStepExecutionRangeNodes();
const fromStep = Math.min(maxStep, Math.max(1, normalizePositiveStepNumber(inputStepExecutionRangeFrom?.value, 1))); const firstNodeId = String(nodes[0]?.nodeId || '').trim();
const toStep = Math.min(maxStep, Math.max(1, normalizePositiveStepNumber(inputStepExecutionRangeTo?.value, maxStep))); const lastNodeId = String(nodes[nodes.length - 1]?.nodeId || '').trim();
const normalizedFrom = Math.min(fromStep, toStep); const selectedFromNodeId = String(inputStepExecutionRangeFrom?.value || firstNodeId).trim() || firstNodeId;
const normalizedTo = Math.max(fromStep, toStep); 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) { if (inputStepExecutionRangeFrom) {
inputStepExecutionRangeFrom.max = String(maxStep); inputStepExecutionRangeFrom.value = normalizedFromNodeId;
inputStepExecutionRangeFrom.value = String(normalizedFrom);
} }
if (inputStepExecutionRangeTo) { if (inputStepExecutionRangeTo) {
inputStepExecutionRangeTo.max = String(maxStep); inputStepExecutionRangeTo.value = normalizedToNodeId;
inputStepExecutionRangeTo.value = String(normalizedTo);
} }
return {
fromNodeId: normalizedFromNodeId,
toNodeId: normalizedToNodeId,
fromStep: normalizedFromStep,
toStep: normalizedToStep,
};
} }
function buildStepExecutionRangeByFlowPayload(existingConfig = latestState?.stepExecutionRangeByFlow || {}) { function buildStepExecutionRangeByFlowPayload(existingConfig = latestState?.stepExecutionRangeByFlow || {}) {
@@ -2461,12 +2511,12 @@ function buildStepExecutionRangeByFlowPayload(existingConfig = latestState?.step
if (!isStepExecutionRangeUiAvailable(latestState)) { if (!isStepExecutionRangeUiAvailable(latestState)) {
return config; return config;
} }
clampStepExecutionRangeInputs(); const normalizedRange = clampStepExecutionRangeInputs();
const flowId = getCurrentStepExecutionRangeFlowId(latestState); const flowId = getCurrentStepExecutionRangeFlowId(latestState);
config[flowId] = normalizeStepExecutionRangeEntry({ config[flowId] = normalizeStepExecutionRangeEntry({
enabled: Boolean(inputStepExecutionRangeEnabled?.checked), enabled: Boolean(inputStepExecutionRangeEnabled?.checked),
fromStep: inputStepExecutionRangeFrom?.value, fromStep: normalizedRange?.fromStep,
toStep: inputStepExecutionRangeTo?.value, toStep: normalizedRange?.toStep,
}); });
return config; return config;
} }
@@ -2493,17 +2543,15 @@ function applyStepExecutionRangeState(state = latestState) {
} }
const available = isStepExecutionRangeUiAvailable(state); const available = isStepExecutionRangeUiAvailable(state);
rowStepExecutionRange.style.display = available ? '' : 'none'; rowStepExecutionRange.style.display = available ? '' : 'none';
const maxStep = getLastCurrentStepId();
const range = getStepExecutionRangeForCurrentFlow(state); const range = getStepExecutionRangeForCurrentFlow(state);
const fromNodeId = getStepExecutionRangeBoundaryNodeId(range.fromStep, 'start');
const toNodeId = getStepExecutionRangeBoundaryNodeId(range.toStep, 'end');
syncStepExecutionRangeSelectOptions(fromNodeId, toNodeId);
if (inputStepExecutionRangeFrom) { if (inputStepExecutionRangeFrom) {
inputStepExecutionRangeFrom.min = '1'; inputStepExecutionRangeFrom.value = fromNodeId;
inputStepExecutionRangeFrom.max = String(maxStep);
inputStepExecutionRangeFrom.value = String(Math.min(maxStep, Math.max(1, normalizePositiveStepNumber(range.fromStep, 1))));
} }
if (inputStepExecutionRangeTo) { if (inputStepExecutionRangeTo) {
inputStepExecutionRangeTo.min = '1'; inputStepExecutionRangeTo.value = toNodeId;
inputStepExecutionRangeTo.max = String(maxStep);
inputStepExecutionRangeTo.value = String(Math.min(maxStep, Math.max(1, normalizePositiveStepNumber(range.toStep, maxStep))));
} }
if (inputStepExecutionRangeEnabled) { if (inputStepExecutionRangeEnabled) {
inputStepExecutionRangeEnabled.checked = Boolean(range.enabled); inputStepExecutionRangeEnabled.checked = Boolean(range.enabled);
@@ -14932,14 +14980,16 @@ selectFlow?.addEventListener('change', () => {
}); });
[inputStepExecutionRangeFrom, inputStepExecutionRangeTo].forEach((input) => { [inputStepExecutionRangeFrom, inputStepExecutionRangeTo].forEach((input) => {
input?.addEventListener('input', () => { const handleRangeChange = () => {
const stepExecutionRangeByFlow = buildStepExecutionRangeByFlowPayload(latestState?.stepExecutionRangeByFlow); const stepExecutionRangeByFlow = buildStepExecutionRangeByFlowPayload(latestState?.stepExecutionRangeByFlow);
syncLatestState({ stepExecutionRangeByFlow }); syncLatestState({ stepExecutionRangeByFlow });
markSettingsDirty(true); markSettingsDirty(true);
renderStepStatuses(latestState); renderStepStatuses(latestState);
updateButtonStates(); updateButtonStates();
scheduleSettingsAutoSave(); scheduleSettingsAutoSave();
}); };
input?.addEventListener('input', handleRangeChange);
input?.addEventListener('change', handleRangeChange);
input?.addEventListener('blur', () => { input?.addEventListener('blur', () => {
clampStepExecutionRangeInputs(); clampStepExecutionRangeInputs();
saveSettings({ silent: true }).catch(() => { }); saveSettings({ silent: true }).catch(() => { });
@@ -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.activeFlowId, 'openai');
assert.equal(patch.activeRunId, 'run-001'); assert.equal(patch.activeRunId, 'run-001');
assert.equal(patch.currentNodeId, 'oauth-login'); assert.equal(patch.currentNodeId, 'oauth-login');
assert.equal(patch.oauthUrl, 'https://new.example.com/start'); assert.equal(Object.prototype.hasOwnProperty.call(patch, 'oauthUrl'), false);
assert.equal(patch.plusCheckoutTabId, 99); 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, 'currentStep'), false);
assert.equal(Object.prototype.hasOwnProperty.call(patch, 'stepStatuses'), false); assert.equal(Object.prototype.hasOwnProperty.call(patch, 'stepStatuses'), false);
assert.deepStrictEqual(patch.nodeStatuses, { 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.auth.oauthUrl, 'https://new.example.com/start');
assert.equal(patch.runtimeState.flowState.openai.plus.plusCheckoutTabId, 99); 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', () => { test('runtime-state patch prefers explicit activeFlowId over stale legacy flowId', () => {
@@ -57,6 +57,39 @@ ${flowRegistrySource}
${settingsSchemaSource} ${settingsSchemaSource}
const DEFAULT_ACTIVE_FLOW_ID = 'openai'; const DEFAULT_ACTIVE_FLOW_ID = 'openai';
const DEFAULT_SUB2API_GROUP_NAMES = ['codex', 'openai-plus']; 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 = { const PERSISTED_SETTING_DEFAULTS = {
activeFlowId: DEFAULT_ACTIVE_FLOW_ID, activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
panelMode: 'cpa', panelMode: 'cpa',
@@ -77,6 +110,9 @@ const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
const PERSISTED_SETTINGS_SCHEMA_KEYS = ['settingsSchemaVersion', 'settingsState']; const PERSISTED_SETTINGS_SCHEMA_KEYS = ['settingsSchemaVersion', 'settingsState'];
const LEGACY_AUTO_STEP_DELAY_KEYS = []; const LEGACY_AUTO_STEP_DELAY_KEYS = [];
const LEGACY_VERIFICATION_RESEND_COUNT_KEYS = []; const LEGACY_VERIFICATION_RESEND_COUNT_KEYS = [];
function isPlainObjectValue(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function normalizePanelMode(value = '') { function normalizePanelMode(value = '') {
const normalized = String(value || '').trim().toLowerCase(); const normalized = String(value || '').trim().toLowerCase();
return normalized === 'sub2api' || normalized === 'codex2api' ? normalized : 'cpa'; return normalized === 'sub2api' || normalized === 'codex2api' ? normalized : 'cpa';
@@ -131,6 +167,8 @@ function resolveSignupMethod(state = {}) {
function resolveLegacyAutoStepDelaySeconds() { return undefined; } function resolveLegacyAutoStepDelaySeconds() { return undefined; }
${extractFunction('normalizePersistentSettingValue')} ${extractFunction('normalizePersistentSettingValue')}
${extractFunction('getSettingsSchemaApi')} ${extractFunction('getSettingsSchemaApi')}
${extractFunction('projectSettingsSchemaView')}
${extractFunction('buildPersistedSettingsStoragePayload')}
${extractFunction('buildPersistentSettingsPayload')} ${extractFunction('buildPersistentSettingsPayload')}
${extractFunction('getPersistedSettings')} ${extractFunction('getPersistedSettings')}
${extractFunction('setPersistentSettings')} ${extractFunction('setPersistentSettings')}
@@ -141,6 +179,7 @@ return {
setPersistentSettings, setPersistentSettings,
getRequestedKeys: typeof getRequestedKeys === 'function' ? getRequestedKeys : () => [], getRequestedKeys: typeof getRequestedKeys === 'function' ? getRequestedKeys : () => [],
getPersistedWrites: typeof getPersistedWrites === 'function' ? getPersistedWrites : () => [], 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.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(payload.kiroRsKey, 'secret-key'); assert.equal(payload.kiroRsKey, 'secret-key');
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false); 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.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', () => { test('buildPersistentSettingsPayload accepts schema-only input when requireKnownKeys is enabled', () => {
const api = buildHarness(); const api = buildHarness();
const payload = api.buildPersistentSettingsPayload({ const payload = api.buildPersistentSettingsPayload({
settingsSchemaVersion: 3, settingsSchemaVersion: 4,
settingsState: { settingsState: {
activeFlowId: 'kiro', activeFlowId: 'kiro',
services: { services: {
account: { customPassword: '' },
email: { provider: '163' }, email: { provider: '163' },
proxy: { enabled: false, provider: '711proxy', mode: 'account' }, proxy: { enabled: false, provider: '711proxy', mode: 'account' },
}, },
flows: { flows: {
openai: { openai: {
source: { selected: 'cpa', entries: {} }, integrationTargetId: 'cpa',
account: { customPassword: '' }, integrationTargets: {
cpa: {
vpsUrl: '',
vpsPassword: '',
localCpaStep9Mode: 'submit',
},
sub2api: {
sub2apiUrl: '',
sub2apiEmail: '',
sub2apiPassword: '',
sub2apiGroupName: 'codex',
sub2apiGroupNames: ['codex', 'openai-plus'],
sub2apiAccountPriority: 1,
sub2apiDefaultProxyName: '',
},
codex2api: {
codex2apiUrl: '',
codex2apiAdminKey: '',
},
},
signup: { signup: {
signupMethod: 'email', signupMethod: 'email',
phoneVerificationEnabled: false, phoneVerificationEnabled: false,
@@ -193,17 +256,13 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
}, },
}, },
kiro: { kiro: {
source: { integrationTargetId: 'kiro-rs',
selected: 'kiro-rs', integrationTargets: {
entries: { 'kiro-rs': {
'kiro-rs': { baseUrl: 'https://kiro.example.com/admin',
kiroRsUrl: 'https://kiro.example.com/admin', apiKey: 'schema-only-key',
kiroRsKey: 'schema-only-key',
},
}, },
}, },
options: {
},
autoRun: { autoRun: {
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 7 }, 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.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(payload.kiroRsKey, 'schema-only-key'); assert.equal(payload.kiroRsKey, 'schema-only-key');
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false); 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 () => { 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('settingsSchemaVersion'));
assert.ok(api.getRequestedKeys().includes('settingsState')); assert.ok(api.getRequestedKeys().includes('settingsState'));
assert.equal(state.settingsSchemaVersion, 3); assert.equal(state.settingsSchemaVersion, 4);
assert.equal(state.settingsState.activeFlowId, 'openai'); assert.equal(state.settingsState.activeFlowId, 'openai');
}); });
@@ -253,20 +312,37 @@ const chrome = {
local: { local: {
async get() { async get() {
return { return {
settingsSchemaVersion: 3, settingsSchemaVersion: 4,
settingsState: { settingsState: {
activeFlowId: 'kiro', activeFlowId: 'kiro',
services: { services: {
account: { customPassword: '' },
email: { provider: 'hotmail' }, email: { provider: 'hotmail' },
proxy: { enabled: true, provider: '711proxy', mode: 'account' }, proxy: { enabled: true, provider: '711proxy', mode: 'account' },
}, },
flows: { flows: {
openai: { openai: {
source: { integrationTargetId: 'sub2api',
selected: 'sub2api', integrationTargets: {
entries: {}, cpa: {
vpsUrl: '',
vpsPassword: '',
localCpaStep9Mode: 'submit',
},
sub2api: {
sub2apiUrl: '',
sub2apiEmail: '',
sub2apiPassword: '',
sub2apiGroupName: 'codex',
sub2apiGroupNames: ['codex', 'openai-plus'],
sub2apiAccountPriority: 1,
sub2apiDefaultProxyName: '',
},
codex2api: {
codex2apiUrl: '',
codex2apiAdminKey: '',
},
}, },
account: { customPassword: '' },
signup: { signup: {
signupMethod: 'email', signupMethod: 'email',
phoneVerificationEnabled: false, phoneVerificationEnabled: false,
@@ -281,17 +357,13 @@ const chrome = {
}, },
}, },
kiro: { kiro: {
source: { integrationTargetId: 'kiro-rs',
selected: 'kiro-rs', integrationTargets: {
entries: { 'kiro-rs': {
'kiro-rs': { baseUrl: 'https://kiro.example.com/admin',
kiroRsUrl: 'https://kiro.example.com/admin', apiKey: 'stored-key',
kiroRsKey: 'stored-key',
},
}, },
}, },
options: {
},
autoRun: { autoRun: {
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 7 }, stepExecutionRange: { enabled: true, fromStep: 1, toStep: 7 },
}, },
@@ -324,12 +396,16 @@ const chrome = {
test('setPersistentSettings materializes canonical schema keys for schema-only updates', async () => { test('setPersistentSettings materializes canonical schema keys for schema-only updates', async () => {
const api = buildHarness(` const api = buildHarness(`
const persistedWrites = []; const persistedWrites = [];
const removedKeys = [];
const chrome = { const chrome = {
storage: { storage: {
local: { local: {
async get() { async get() {
return {}; return {};
}, },
async remove(keys) {
removedKeys.push(...(Array.isArray(keys) ? keys : [keys]));
},
async set(payload) { async set(payload) {
persistedWrites.push(JSON.parse(JSON.stringify(payload))); persistedWrites.push(JSON.parse(JSON.stringify(payload)));
}, },
@@ -339,20 +415,43 @@ const chrome = {
function getPersistedWrites() { function getPersistedWrites() {
return persistedWrites; return persistedWrites;
} }
function getRemovedKeys() {
return removedKeys;
}
`); `);
const persisted = await api.setPersistentSettings({ const persisted = await api.setPersistentSettings({
settingsSchemaVersion: 3, settingsSchemaVersion: 4,
settingsState: { settingsState: {
activeFlowId: 'kiro', activeFlowId: 'kiro',
services: { services: {
account: { customPassword: '' },
email: { provider: '163' }, email: { provider: '163' },
proxy: { enabled: false, provider: '711proxy', mode: 'account' }, proxy: { enabled: false, provider: '711proxy', mode: 'account' },
}, },
flows: { flows: {
openai: { openai: {
source: { selected: 'cpa', entries: {} }, integrationTargetId: 'cpa',
account: { customPassword: '' }, integrationTargets: {
cpa: {
vpsUrl: '',
vpsPassword: '',
localCpaStep9Mode: 'submit',
},
sub2api: {
sub2apiUrl: '',
sub2apiEmail: '',
sub2apiPassword: '',
sub2apiGroupName: 'codex',
sub2apiGroupNames: ['codex', 'openai-plus'],
sub2apiAccountPriority: 1,
sub2apiDefaultProxyName: '',
},
codex2api: {
codex2apiUrl: '',
codex2apiAdminKey: '',
},
},
signup: { signup: {
signupMethod: 'email', signupMethod: 'email',
phoneVerificationEnabled: false, phoneVerificationEnabled: false,
@@ -367,17 +466,13 @@ function getPersistedWrites() {
}, },
}, },
kiro: { kiro: {
source: { integrationTargetId: 'kiro-rs',
selected: 'kiro-rs', integrationTargets: {
entries: { 'kiro-rs': {
'kiro-rs': { baseUrl: 'https://kiro.example.com/admin',
kiroRsUrl: 'https://kiro.example.com/admin', apiKey: 'nested-only-key',
kiroRsKey: 'nested-only-key',
},
}, },
}, },
options: {
},
autoRun: { autoRun: {
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 7 }, 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.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(persisted.kiroRsKey, 'nested-only-key'); assert.equal(persisted.kiroRsKey, 'nested-only-key');
assert.equal(Object.prototype.hasOwnProperty.call(persisted, 'kiroRegion'), false); assert.equal(Object.prototype.hasOwnProperty.call(persisted, 'kiroRegion'), false);
assert.equal(persisted.settingsSchemaVersion, 3); assert.equal(persisted.settingsSchemaVersion, 4);
assert.equal(write.activeFlowId, 'kiro'); assert.equal(Object.prototype.hasOwnProperty.call(write, 'activeFlowId'), false);
assert.equal(write.kiroRsUrl, 'https://kiro.example.com/admin'); assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRsUrl'), false);
assert.equal(write.kiroRsKey, 'nested-only-key'); assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRsKey'), false);
assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRegion'), 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.activeFlowId, 'kiro');
assert.equal(write.settingsState.flows.kiro.integrationTargetId, 'kiro-rs');
assert.ok(api.getRemovedKeys().includes('panelMode'));
assert.ok(api.getRemovedKeys().includes('kiroRsUrl'));
}); });
@@ -8,9 +8,8 @@ test('background node registry preserves node metadata even before an executor i
const registry = api.createNodeRegistry([ const registry = api.createNodeRegistry([
{ {
flowId: 'kiro', flowId: 'kiro',
legacyStepId: 1,
nodeId: 'kiro-start-device-login', nodeId: 'kiro-start-device-login',
displayOrder: 10, displayOrder: 1,
executeKey: 'kiro-start-device-login', executeKey: 'kiro-start-device-login',
title: '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'); const node = registry.getNodeDefinition('kiro-start-device-login');
assert.equal(node.flowId, 'kiro'); assert.equal(node.flowId, 'kiro');
assert.equal(node.legacyStepId, 1); assert.equal(node.displayOrder, 1);
assert.equal(node.title, 'Start device login'); assert.equal(node.title, 'Start device login');
assert.throws( assert.throws(
() => registry.executeNode('kiro-start-device-login', {}), () => 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([ const registry = api.createNodeRegistry([
{ {
flowId: 'openai', flowId: 'openai',
legacyStepId: 2, displayOrder: 2,
nodeId: 'submit-signup-email', nodeId: 'submit-signup-email',
displayOrder: 20,
executeKey: 'submit-signup-email', executeKey: 'submit-signup-email',
title: 'Submit signup email', title: 'Submit signup email',
execute: async (state) => { execute: async (state) => {
@@ -45,9 +43,8 @@ test('background node registry executes registered nodes in display order', asyn
}, },
{ {
flowId: 'openai', flowId: 'openai',
legacyStepId: 1, displayOrder: 1,
nodeId: 'open-chatgpt', nodeId: 'open-chatgpt',
displayOrder: 10,
executeKey: 'open-chatgpt', executeKey: 'open-chatgpt',
title: 'Open ChatGPT', title: 'Open ChatGPT',
}, },
+18 -15
View File
@@ -21,7 +21,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
const enabledState = registry.resolveSidepanelCapabilities({ const enabledState = registry.resolveSidepanelCapabilities({
state: { state: {
activeFlowId: 'openai', activeFlowId: 'openai',
panelMode: 'cpa', openaiIntegrationTargetId: 'cpa',
phoneVerificationEnabled: true, phoneVerificationEnabled: true,
plusModeEnabled: false, plusModeEnabled: false,
contributionMode: false, contributionMode: false,
@@ -37,7 +37,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
const plusLockedState = registry.resolveSidepanelCapabilities({ const plusLockedState = registry.resolveSidepanelCapabilities({
state: { state: {
activeFlowId: 'openai', activeFlowId: 'openai',
panelMode: 'sub2api', openaiIntegrationTargetId: 'sub2api',
phoneVerificationEnabled: true, phoneVerificationEnabled: true,
plusModeEnabled: true, plusModeEnabled: true,
contributionMode: false, contributionMode: false,
@@ -58,7 +58,7 @@ test('flow capability registry defaults unknown flows to minimal non-phone capab
const capabilityState = registry.resolveSidepanelCapabilities({ const capabilityState = registry.resolveSidepanelCapabilities({
state: { state: {
activeFlowId: 'site-a', activeFlowId: 'site-a',
panelMode: 'codex2api', openaiIntegrationTargetId: 'codex2api',
phoneVerificationEnabled: true, phoneVerificationEnabled: true,
plusModeEnabled: true, plusModeEnabled: true,
contributionMode: 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.canUsePhoneSignup, false);
assert.equal(capabilityState.effectiveSignupMethod, 'email'); assert.equal(capabilityState.effectiveSignupMethod, 'email');
assert.equal(capabilityState.panelMode, 'codex2api'); 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', () => { 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({ const capabilityState = registry.resolveSidepanelCapabilities({
state: { state: {
activeFlowId: 'kiro', activeFlowId: 'kiro',
kiroSourceId: 'kiro-rs', kiroIntegrationTargetId: 'kiro-rs',
panelMode: 'sub2api', openaiIntegrationTargetId: 'sub2api',
signupMethod: 'phone', signupMethod: 'phone',
plusModeEnabled: true, plusModeEnabled: true,
phoneVerificationEnabled: 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.canShowPhoneSettings, false);
assert.equal(capabilityState.canShowPlusSettings, false); assert.equal(capabilityState.canShowPlusSettings, false);
assert.equal(capabilityState.effectiveSignupMethod, 'email'); assert.equal(capabilityState.effectiveSignupMethod, 'email');
assert.equal(capabilityState.effectiveSourceId, 'kiro-rs'); assert.equal(capabilityState.effectiveIntegrationTargetId, 'kiro-rs');
assert.deepEqual( assert.deepEqual(
capabilityState.visibleGroupIds, 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 api = loadApi();
const registry = api.createFlowCapabilityRegistry({ const registry = api.createFlowCapabilityRegistry({
flowCapabilities: { flowCapabilities: {
openai: api.FLOW_CAPABILITIES.openai, openai: api.FLOW_CAPABILITIES.openai,
'site-a': { 'site-a': {
...api.DEFAULT_FLOW_CAPABILITIES, ...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({ const plusLockedResult = registry.validateAutoRunStart({
state: { state: {
activeFlowId: 'openai', activeFlowId: 'openai',
panelMode: 'cpa', openaiIntegrationTargetId: 'cpa',
signupMethod: 'phone', signupMethod: 'phone',
phoneVerificationEnabled: true, phoneVerificationEnabled: true,
plusModeEnabled: true, plusModeEnabled: true,
@@ -131,7 +131,7 @@ test('flow capability registry exposes shared auto-run validation for phone lock
const unsupportedPanelResult = registry.validateAutoRunStart({ const unsupportedPanelResult = registry.validateAutoRunStart({
state: { state: {
activeFlowId: 'site-a', activeFlowId: 'site-a',
panelMode: 'sub2api', openaiIntegrationTargetId: 'sub2api',
signupMethod: 'email', signupMethod: 'email',
}, },
}); });
@@ -147,7 +147,7 @@ test('flow capability registry normalizes unsupported mode switches back to the
openai: api.FLOW_CAPABILITIES.openai, openai: api.FLOW_CAPABILITIES.openai,
'site-a': { 'site-a': {
...api.DEFAULT_FLOW_CAPABILITIES, ...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({ const validation = registry.validateModeSwitch({
state: { state: {
activeFlowId: 'site-a', activeFlowId: 'site-a',
panelMode: 'sub2api', openaiIntegrationTargetId: 'sub2api',
signupMethod: 'phone', signupMethod: 'phone',
phoneVerificationEnabled: true, phoneVerificationEnabled: true,
plusModeEnabled: true, plusModeEnabled: true,
contributionMode: true, contributionMode: true,
}, },
changedKeys: [ changedKeys: [
'panelMode', 'openaiIntegrationTargetId',
'signupMethod', 'signupMethod',
'phoneVerificationEnabled', 'phoneVerificationEnabled',
'plusModeEnabled', 'plusModeEnabled',
@@ -173,6 +173,9 @@ test('flow capability registry normalizes unsupported mode switches back to the
assert.equal(validation.ok, false); assert.equal(validation.ok, false);
assert.deepEqual(validation.normalizedUpdates, { assert.deepEqual(validation.normalizedUpdates, {
panelMode: 'cpa', panelMode: 'cpa',
openaiIntegrationTargetId: 'cpa',
kiroIntegrationTargetId: 'cpa',
kiroSourceId: 'cpa',
signupMethod: 'email', signupMethod: 'email',
phoneVerificationEnabled: false, phoneVerificationEnabled: false,
plusModeEnabled: false, plusModeEnabled: false,
+30 -34
View File
@@ -13,37 +13,31 @@ function loadApis() {
};`)(scope); };`)(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(); const { flowRegistry } = loadApis();
assert.deepEqual(flowRegistry.getRegisteredFlowIds(), ['openai', 'kiro']); assert.deepEqual(flowRegistry.getRegisteredFlowIds(), ['openai', 'kiro']);
assert.equal(flowRegistry.getFlowLabel('codex'), 'Codex / OpenAI'); assert.equal(flowRegistry.normalizeFlowId('kiro'), 'kiro');
assert.equal(flowRegistry.normalizeFlowId('codex'), 'openai'); assert.equal(flowRegistry.normalizeFlowId('unknown'), 'openai');
assert.equal(flowRegistry.normalizeSourceId('openai', 'sub2api'), 'sub2api'); assert.equal(flowRegistry.getFlowLabel('openai'), 'Codex / OpenAI');
assert.equal(flowRegistry.normalizeSourceId('kiro', 'anything-else'), 'kiro-rs'); assert.equal(flowRegistry.normalizeIntegrationTargetId('openai', 'sub2api'), 'sub2api');
assert.equal(flowRegistry.normalizeIntegrationTargetId('kiro', 'anything-else'), 'kiro-rs');
assert.deepEqual( assert.deepEqual(
flowRegistry.getVisibleGroupIds('openai', 'cpa'), 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( assert.deepEqual(
flowRegistry.getVisibleGroupIds('kiro', 'kiro-rs'), 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( assert.deepEqual(
flowRegistry.getSettingsGroupDefinition('openai-plus')?.rowIds || [], flowRegistry.getIntegrationTargetOptions('openai').map((entry) => entry.id),
['row-plus-mode'] ['cpa', 'sub2api', 'codex2api']
);
assert.deepEqual(
flowRegistry.getSettingsGroupDefinition('openai-phone')?.rowIds || [],
[]
);
assert.deepEqual(
flowRegistry.getSettingsGroupDefinition('openai-step6')?.rowIds || [],
['row-step6-cookie-settings']
); );
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 { settingsSchema } = loadApis();
const schema = settingsSchema.createSettingsSchema(); 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.email.provider, 'hotmail');
assert.equal(normalized.services.proxy.enabled, true); assert.equal(normalized.services.proxy.enabled, true);
assert.equal(normalized.services.account.customPassword, 'SharedSecret123!'); assert.equal(normalized.services.account.customPassword, 'SharedSecret123!');
assert.equal(normalized.flows.openai.source.selected, 'sub2api'); assert.equal(normalized.flows.openai.integrationTargetId, 'sub2api');
assert.equal(normalized.flows.kiro.source.selected, 'kiro-rs'); assert.equal(normalized.flows.kiro.integrationTargetId, 'kiro-rs');
assert.equal(normalized.flows.kiro.source.entries['kiro-rs'].kiroRsUrl, 'https://kiro.example.com/admin'); assert.equal(normalized.flows.kiro.integrationTargets['kiro-rs'].baseUrl, 'https://kiro.example.com/admin');
assert.equal(normalized.flows.kiro.source.entries['kiro-rs'].kiroRsKey, 'secret-key'); assert.equal(normalized.flows.kiro.integrationTargets['kiro-rs'].apiKey, 'secret-key');
assert.deepEqual(normalized.flows.kiro.autoRun.stepExecutionRange, { assert.deepEqual(normalized.flows.kiro.autoRun.stepExecutionRange, {
enabled: true, enabled: true,
fromStep: 1, 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 { settingsSchema } = loadApis();
const schema = settingsSchema.createSettingsSchema(); const schema = settingsSchema.createSettingsSchema();
const payload = schema.buildLegacySettingsPayload(schema.normalizeSettingsState({ const normalized = schema.normalizeSettingsState({
activeFlowId: 'kiro', activeFlowId: 'kiro',
kiroSourceId: 'kiro-rs', kiroIntegrationTargetId: 'kiro-rs',
kiroRsUrl: 'https://kiro.example.com/admin', kiroRsUrl: 'https://kiro.example.com/admin',
kiroRsKey: 'key-123', kiroRsKey: 'key-123',
})); });
const view = schema.buildSettingsView(normalized);
assert.equal(payload.activeFlowId, 'kiro'); assert.equal(view.activeFlowId, 'kiro');
assert.equal(payload.panelMode, 'cpa'); assert.equal(view.openaiIntegrationTargetId, 'cpa');
assert.equal(payload.kiroSourceId, 'kiro-rs'); assert.equal(view.kiroIntegrationTargetId, 'kiro-rs');
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin'); assert.equal(view.panelMode, 'cpa');
assert.equal(payload.kiroRsKey, 'key-123'); assert.equal(view.kiroSourceId, 'kiro-rs');
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false); assert.equal(view.kiroRsUrl, 'https://kiro.example.com/admin');
assert.equal(payload.settingsSchemaVersion, 3); assert.equal(view.kiroRsKey, 'key-123');
assert.equal(payload.settingsState.activeFlowId, 'kiro'); assert.equal(view.settingsSchemaVersion, 4);
assert.equal(view.settingsState.activeFlowId, 'kiro');
}); });