Fix legacy task regex migration compatibility

This commit is contained in:
Youzini-afk
2026-04-09 12:11:57 +08:00
parent e4cdcc5562
commit 14a0dbe59d
3 changed files with 322 additions and 50 deletions

View File

@@ -908,6 +908,118 @@ function buildRegexConfigSignature(config = {}, taskType = "global") {
});
}
function getDefaultRegexConfigForTaskType(taskType = "global") {
if (TASK_TYPES.includes(String(taskType || "").trim())) {
return normalizeGlobalTaskRegex(
createDefaultTaskProfile(taskType).regex || {},
taskType,
);
}
return normalizeGlobalTaskRegex(createDefaultGlobalTaskRegex(), "global");
}
export function describeLegacyTaskRegexConfig(taskType = "", regexConfig = {}) {
const normalizedTaskType = String(taskType || "").trim();
const effectiveTaskType = TASK_TYPES.includes(normalizedTaskType)
? normalizedTaskType
: "global";
const normalizedRegex = normalizeGlobalTaskRegex(
regexConfig || {},
effectiveTaskType,
);
const defaultRegex = getDefaultRegexConfigForTaskType(effectiveTaskType);
const configSignature = buildRegexConfigSignature(
normalizedRegex,
effectiveTaskType,
);
const defaultConfigSignature = buildRegexConfigSignature(
defaultRegex,
effectiveTaskType,
);
const hasRules = normalizedRegex.localRules.length > 0;
const hasConfigDiff = configSignature !== defaultConfigSignature;
return {
taskType: effectiveTaskType,
regex: normalizedRegex,
defaultRegex,
configSignature,
defaultConfigSignature,
hasRules,
hasConfigDiff,
hasLegacyRegex: hasRules || hasConfigDiff,
};
}
export function migrateLegacyProfileRegexToGlobal(
globalTaskRegex = {},
profile = null,
{ applyLegacyConfig = true } = {},
) {
const currentGlobalRegex = normalizeGlobalTaskRegex(globalTaskRegex, "global");
const profileTaskType = String(profile?.taskType || "").trim();
const legacy = describeLegacyTaskRegexConfig(profileTaskType, profile?.regex || {});
if (!legacy.hasLegacyRegex) {
return {
globalTaskRegex: currentGlobalRegex,
mergedRuleCount: 0,
profile,
clearedLegacyRules: false,
hasConfigDiff: false,
appliedLegacyConfig: false,
hasLegacyRegex: false,
};
}
const mergedRules = dedupeRegexRules(
[
...(Array.isArray(currentGlobalRegex.localRules)
? currentGlobalRegex.localRules
: []),
...(Array.isArray(legacy.regex?.localRules) ? legacy.regex.localRules : []),
],
"global",
);
const nextGlobalRegexBase =
applyLegacyConfig && legacy.hasConfigDiff
? {
...currentGlobalRegex,
enabled: legacy.regex.enabled !== false,
inheritStRegex: legacy.regex.inheritStRegex !== false,
sources: {
...(legacy.regex.sources || {}),
},
stages: {
...normalizeTaskRegexStages(legacy.regex.stages || {}),
},
}
: currentGlobalRegex;
return {
globalTaskRegex: {
...nextGlobalRegexBase,
localRules: mergedRules,
},
mergedRuleCount: Math.max(
0,
mergedRules.length -
(Array.isArray(currentGlobalRegex.localRules)
? currentGlobalRegex.localRules.length
: 0),
),
profile: {
...(profile || {}),
regex: {},
},
clearedLegacyRules: true,
hasConfigDiff: legacy.hasConfigDiff,
appliedLegacyConfig: Boolean(applyLegacyConfig && legacy.hasConfigDiff),
hasLegacyRegex: true,
};
}
function normalizeTaskProfilesState(taskProfiles = {}) {
return ensureTaskProfiles({ taskProfiles });
}
@@ -1451,6 +1563,7 @@ export function migratePerTaskRegexToGlobal(settings = {}) {
existingGlobalRegex,
"global",
);
const hasExistingGlobalRules = existingGlobalRegex.localRules.length > 0;
const defaultGlobalConfigSignature = buildRegexConfigSignature(
defaultGlobalRegex,
"global",
@@ -1459,33 +1572,16 @@ export function migratePerTaskRegexToGlobal(settings = {}) {
for (const taskType of TASK_TYPES) {
const bucket = taskProfiles[taskType];
const defaultProfileRegex = normalizeGlobalTaskRegex(
createDefaultTaskProfile(taskType).regex || {},
taskType,
);
const defaultProfileConfigSignature = buildRegexConfigSignature(
defaultProfileRegex,
taskType,
);
for (const profile of Array.isArray(bucket?.profiles) ? bucket.profiles : []) {
const normalizedProfileRegex = normalizeGlobalTaskRegex(
profile?.regex || {},
taskType,
);
const profileConfigSignature = buildRegexConfigSignature(
normalizedProfileRegex,
taskType,
);
const hasRules = normalizedProfileRegex.localRules.length > 0;
const hasConfigDiff = profileConfigSignature !== defaultProfileConfigSignature;
if (!hasRules && !hasConfigDiff) continue;
const legacy = describeLegacyTaskRegexConfig(taskType, profile?.regex || {});
if (!legacy.hasLegacyRegex) continue;
profilesWithLegacyRegex.push({
taskType,
profileId: String(profile?.id || ""),
regex: normalizedProfileRegex,
configSignature: profileConfigSignature,
hasConfigDiff,
regex: legacy.regex,
configSignature: legacy.configSignature,
hasConfigDiff: legacy.hasConfigDiff,
});
}
}
@@ -1533,8 +1629,14 @@ export function migratePerTaskRegexToGlobal(settings = {}) {
"global",
);
const normalizedSelectedConfig = normalizeGlobalTaskRegex(selectedConfig, "global");
const nextGlobalRegex = {
...normalizeGlobalTaskRegex(selectedConfig, "global"),
...normalizedSelectedConfig,
enabled:
existingGlobalConfigSignature !== defaultGlobalConfigSignature ||
hasExistingGlobalRules
? normalizedSelectedConfig.enabled !== false
: false,
localRules: mergedLocalRules,
};

View File

@@ -3,7 +3,10 @@ import {
createDefaultTaskProfiles,
ensureTaskProfiles,
getActiveTaskProfile,
migrateLegacyProfileRegexToGlobal,
migrateLegacyTaskProfiles,
migratePerTaskRegexToGlobal,
normalizeTaskProfile,
} from "../prompting/prompt-profiles.js";
const legacySettings = {
@@ -389,4 +392,168 @@ assert.ok(
.every((block) => block.role === "system"),
);
const legacyRegexSettings = {
taskProfilesVersion: 3,
taskProfiles: createDefaultTaskProfiles(),
};
legacyRegexSettings.taskProfiles.extract.activeProfileId = "default";
legacyRegexSettings.taskProfiles.extract.profiles.push(
normalizeTaskProfile("extract", {
id: "extract-legacy-regex",
taskType: "extract",
name: "旧正则副本",
builtin: false,
regex: {
enabled: true,
inheritStRegex: true,
localRules: [
{
id: "legacy-rule-1",
script_name: "隐藏规则",
enabled: true,
find_regex: "/SECRET/g",
replace_string: "MASK",
},
],
},
}),
);
const migratedLegacyRegex = migratePerTaskRegexToGlobal(legacyRegexSettings);
assert.equal(migratedLegacyRegex.changed, true);
assert.equal(migratedLegacyRegex.settings.globalTaskRegex.enabled, false);
assert.deepEqual(
migratedLegacyRegex.settings.globalTaskRegex.localRules.map((rule) => rule.script_name),
["隐藏规则"],
);
assert.deepEqual(
migratedLegacyRegex.settings.taskProfiles.extract.profiles.find(
(profile) => profile.id === "extract-legacy-regex",
)?.regex?.localRules || [],
[],
);
const existingGlobalRegexSettings = {
taskProfilesVersion: 3,
globalTaskRegex: {
enabled: true,
inheritStRegex: true,
sources: {
global: true,
preset: true,
character: true,
},
stages: {
"input.userMessage": true,
"input.recentMessages": true,
},
localRules: [
{
id: "existing-global-rule",
script_name: "现有通用规则",
enabled: true,
find_regex: "/GLOBAL/g",
replace_string: "KEEP",
},
],
},
taskProfiles: createDefaultTaskProfiles(),
};
existingGlobalRegexSettings.taskProfiles.extract.profiles.push(
normalizeTaskProfile("extract", {
id: "extract-legacy-extra",
taskType: "extract",
name: "旧规则补充",
builtin: false,
regex: {
localRules: [
{
id: "legacy-extra-rule",
script_name: "额外旧规则",
enabled: true,
find_regex: "/EXTRA/g",
replace_string: "ADD",
},
],
},
}),
);
const migratedWithExistingGlobal = migratePerTaskRegexToGlobal(
existingGlobalRegexSettings,
);
assert.equal(migratedWithExistingGlobal.settings.globalTaskRegex.enabled, true);
assert.deepEqual(
migratedWithExistingGlobal.settings.globalTaskRegex.localRules.map(
(rule) => rule.script_name,
),
["现有通用规则", "额外旧规则"],
);
const importedLegacyProfileMigration = migrateLegacyProfileRegexToGlobal(
{
enabled: true,
inheritStRegex: true,
sources: {
global: true,
preset: true,
character: true,
},
stages: {
"input.userMessage": true,
"input.recentMessages": true,
},
localRules: [],
},
{
taskType: "extract",
regex: {
enabled: false,
inheritStRegex: false,
sources: {
global: false,
preset: false,
character: false,
},
stages: {
"input.userMessage": false,
},
localRules: [
{
id: "legacy-import-rule",
script_name: "旧导入规则",
enabled: true,
find_regex: "/A/g",
replace_string: "B",
},
],
},
},
{
applyLegacyConfig: true,
},
);
assert.equal(importedLegacyProfileMigration.appliedLegacyConfig, true);
assert.equal(importedLegacyProfileMigration.globalTaskRegex.enabled, false);
assert.equal(
importedLegacyProfileMigration.globalTaskRegex.inheritStRegex,
false,
);
assert.equal(
importedLegacyProfileMigration.globalTaskRegex.sources.global,
false,
);
assert.equal(
importedLegacyProfileMigration.globalTaskRegex.stages["input.userMessage"],
false,
);
assert.deepEqual(
importedLegacyProfileMigration.globalTaskRegex.localRules.map(
(rule) => rule.script_name,
),
["旧导入规则"],
);
assert.deepEqual(
importedLegacyProfileMigration.profile?.regex || {},
{},
);
console.log("task-profile-migration tests passed");

View File

@@ -33,6 +33,7 @@ import {
getTaskTypeOptions,
importTaskProfile as parseImportedTaskProfile,
isTaskRegexStageEnabled,
migrateLegacyProfileRegexToGlobal,
normalizeGlobalTaskRegex,
normalizeTaskRegexStages,
restoreDefaultTaskProfile,
@@ -4804,6 +4805,9 @@ function _bindTaskProfileWorkspace() {
const legacyRuleMerge = _mergeProfileRegexRulesIntoGlobal(
nextGlobalTaskRegex,
imported.profile,
{
applyLegacyConfig: !importedGlobalMerge.replacedConfig,
},
);
nextGlobalTaskRegex = legacyRuleMerge.globalTaskRegex;
if (legacyRuleMerge.clearedLegacyRules) {
@@ -4873,6 +4877,8 @@ function _bindTaskProfileWorkspace() {
nextGlobalTaskRegex = importedGlobalMerge.globalTaskRegex;
let importedCount = 0;
let mergedLegacyRuleCount = 0;
let legacyConfigImported = Boolean(importedGlobalMerge.replacedConfig);
let skippedLegacyConfigCount = 0;
for (const [taskType, entry] of Object.entries(parsed.profiles)) {
try {
let imported = parseImportedTaskProfile(
@@ -4883,9 +4889,17 @@ function _bindTaskProfileWorkspace() {
const legacyRuleMerge = _mergeProfileRegexRulesIntoGlobal(
nextGlobalTaskRegex,
imported.profile,
{
applyLegacyConfig: !legacyConfigImported,
},
);
nextGlobalTaskRegex = legacyRuleMerge.globalTaskRegex;
mergedLegacyRuleCount += legacyRuleMerge.mergedRuleCount;
if (legacyRuleMerge.appliedLegacyConfig) {
legacyConfigImported = true;
} else if (legacyRuleMerge.hasConfigDiff && legacyConfigImported) {
skippedLegacyConfigCount += 1;
}
if (legacyRuleMerge.clearedLegacyRules) {
imported = {
...imported,
@@ -4920,6 +4934,11 @@ function _bindTaskProfileWorkspace() {
);
const mergedRuleCount =
importedGlobalMerge.mergedRuleCount + mergedLegacyRuleCount;
if (skippedLegacyConfigCount > 0) {
console.warn(
`[ST-BME] 导入全部旧版预设时检测到 ${skippedLegacyConfigCount} 份额外任务级正则配置冲突,已保留第一份迁移到通用正则的配置,其余仅合并规则。`,
);
}
toastr.success(
mergedRuleCount > 0
? `已导入 ${importedCount} 个任务预设,并合并 ${mergedRuleCount} 条通用正则规则`
@@ -8166,35 +8185,19 @@ function _mergeImportedGlobalRegex(currentGlobalRegex = {}, importedGlobalRegex
};
}
function _mergeProfileRegexRulesIntoGlobal(currentGlobalRegex = {}, profile = null) {
const current = _normalizeGlobalRegexDraft(currentGlobalRegex);
const legacyRules = Array.isArray(profile?.regex?.localRules)
? profile.regex.localRules
: [];
if (legacyRules.length === 0) {
return {
globalTaskRegex: current,
mergedRuleCount: 0,
profile,
clearedLegacyRules: false,
};
}
const mergedRules = dedupeRegexRules(
[...(current.localRules || []), ...legacyRules],
"global",
function _mergeProfileRegexRulesIntoGlobal(
currentGlobalRegex = {},
profile = null,
options = {},
) {
const merged = migrateLegacyProfileRegexToGlobal(
_normalizeGlobalRegexDraft(currentGlobalRegex),
profile,
options,
);
return {
globalTaskRegex: {
...current,
localRules: mergedRules,
},
mergedRuleCount: Math.max(0, mergedRules.length - current.localRules.length),
profile: {
...(profile || {}),
regex: {},
},
clearedLegacyRules: true,
...merged,
globalTaskRegex: _normalizeGlobalRegexDraft(merged.globalTaskRegex || {}),
};
}