dsh-plugin-prompt-tool 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/engine/actions/catalog.mjs +1 -1
- package/engine/actions/inbox-prepend.mjs +1 -2
- package/engine/actions/shared.mjs +1 -2
- package/engine/branch.mjs +1 -1
- package/engine/compositions/source/local/instruction-hint.yml +2 -3
- package/engine/conditions/composite.mjs +1 -1
- package/engine/conditions/count.mjs +1 -2
- package/engine/conditions/phase.mjs +1 -2
- package/engine/conditions/subject.mjs +1 -1
- package/engine/layers.mjs +3 -4
- package/engine/rule-spec.mjs +1 -1
- package/engine/shared.mjs +1 -13
- package/engine/subagent-tool-policy.mjs +2 -2
- package/engine/tool-config-engine.mjs +1 -1
- package/engine/trigger-spec.mjs +3 -3
- package/engine/trigger.mjs +2 -2
- package/lib/index.d.mts +1380 -1
- package/lib/index.mjs +33 -3
- package/lib/migrate-rules.mjs +572 -1
- package/lib/{module-package-CHPFweqK.mjs → module-package-DqJZaE71.mjs} +1 -1
- package/lib/{module-package-Cnd1_mwx.mjs → module-package-q2gDRu9H.mjs} +19 -22
- package/lib/{rules-migration-DJ6vASB5.mjs → rule-builder-DMrJten6.mjs} +28 -595
- package/package.json +4 -2
- package/skills/dsh-module/SKILL.md +199 -0
- package/skills/dsh-module/reference.md +158 -0
- package/templates/61-deny-tools-presentation.yml +27 -0
- package/templates/62-guard-deny-tools.yml +20 -0
- package/templates/63-append-context.yml +22 -0
- package/templates/64-pre-step-filter.yml +23 -0
- package/templates/67-inbox-prepend.yml +22 -0
- package/templates/80-tool-surface.yml +6 -6
- package/engine/compositions/library/agent-instructions.yml +0 -9
- package/engine/compositions/library/command-goal.yml +0 -7
- package/engine/compositions/library/compaction.yml +0 -22
- package/engine/compositions/library/delegation-ptc.yml +0 -58
- package/engine/compositions/library/delegation.yml +0 -56
- package/engine/compositions/library/persistent-shell.yml +0 -50
- package/engine/compositions/library/planning.yml +0 -26
- package/engine/compositions/library/skill-filesystem-cordis.yml +0 -10
- package/engine/compositions/library/skill-filesystem.yml +0 -7
- package/engine/compositions/library/tool-ask-user.yml +0 -7
- package/engine/compositions/library/tool-bash.yml +0 -8
- package/engine/compositions/library/tool-cordis.yml +0 -7
- package/engine/compositions/library/tool-fs-search.yml +0 -9
- package/engine/compositions/library/tool-fs.yml +0 -7
- package/engine/compositions/library/tool-goal.yml +0 -7
- package/engine/compositions/library/tool-jobs.yml +0 -7
- package/engine/compositions/library/tool-plugin-manager-disabled.yml +0 -8
- package/engine/compositions/library/tool-plugin-manager.yml +0 -8
- package/engine/compositions/library/tool-present.yml +0 -7
- package/engine/compositions/library/tool-presentation.yml +0 -9
- package/engine/compositions/library/tool-pwsh.yml +0 -8
- package/engine/compositions/library/tool-skill.yml +0 -7
- package/engine/compositions/library/tool-todo.yml +0 -9
- package/engine/compositions/library/tool-web.yml +0 -10
- package/engine/compositions/source/local/declared-triggers.yml +0 -12
- package/lib/paths-CyP0IkaM.d.mts +0 -1390
- package/lib/preset-transfer.d.mts +0 -2
- package/lib/preset-transfer.mjs +0 -3
package/lib/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { C as mergeStModulesWithReport, D as deleteWorldBookEntry, E as buildWorldBookEntry, M as DEFAULT_MODULE_ID, O as listWorldBookEntries, S as mergeStModules, T as stModuleId, _ as readModuleMemory, a as installModulePackage, b as convertStToModule, c as writeModule, d as validateCustomTools, f as safeAssetPath, g as mergeModuleIntoModule, h as importCharacterCardFile, i as exportModulePackage, j as ruleInjections, k as upsertWorldBookEntry, l as writePreset, m as importCharacterCard, o as moduleImportPreview, p as appendModuleMemory, r as expandModuleSource, s as ensureModuleReady, u as compileCustomTool, v as removeMergedModule, w as processStText, x as convertStToModuleWithReport, y as prepareImport } from "./module-package-
|
|
1
|
+
import { A as packageEngineDir, At as modulePathExists, B as userModulesDir, Bt as validateCustomToolIdentities, C as ensureModuleSeed, Ct as ModuleLayerSettingsError, D as loadModuleSpec, Dt as assertModuleId, E as listModules, Et as assertModuleDirectory, F as resolveModuleDir, Ft as INJECTION_POINT_ORDER, Gt as validateEngineParamValues, H as editModuleRules, Ht as ENGINE_PARAM_KEYS, I as resolveModuleFacts, It as MODULE_CAPABILITIES, J as loadModuleDefinition, L as resolveModuleParams, Lt as engineRecipe, M as removeModuleCapabilityDeclaration, Mt as readPersonaSpec, N as removeUserModule, Nt as ENGINE_EDITOR_GROUP_MAP, O as moduleDirExists, Ot as assertModuleTree, P as renderComposition, Pt as ENGINE_RECIPES, Q as compileRules, Qt as USER_SKILLS_DIR, R as saveModuleParams, Rt as isModuleCapabilityPresent, S as duplicateUserModule, St as RULE_OWNED_MODEL_PARAMS, T as listBuiltinTemplates, Tt as readModuleLayerSettings, U as readModuleRules, Ut as WRITER_PARAM_KEYS, V as withModuleDefinition, Vt as ENGINE_PARAM_DEFINITIONS, W as ModuleRulesError, Wt as moduleParamFallbacks, Xt as MODULES_DIR, Y as rulePromptConfigOptions, Yt as DSH_HOME, _ as applyModuleConfigs, _t as createEpochPromotion, a as appendModuleConfigOrder, at as confirmDelivered, b as cloneBuiltinModule, bt as createWarnOnce, c as saveModuleConfigOrder, ct as wireLayers, et as wireTriggerObservers, g as appendModuleCapabilities, h as setModuleEnabled, i as ModuleConfigOrderError, it as applyPromptConfigSources, j as removeModuleCapability, k as openModuleLocation, kt as canonicalModulesRoot, l as assertSafeConfigId, lt as actionMatches, m as resolveEditDir, mt as compareConfigSequence, nt as loadStandingMountFor, o as readConfigOrder, ot as runPreStepBatch, p as enabledModuleIds, q as ensureModuleSlices, qt as LEGACY_PROMPT_PARAM_KEYS, rt as prepareAction, s as readModuleConfigOrder, st as lastWorldBookDiagnostics, tt as compileWhen, u as configFileName, ut as ruleFrame, x as createModuleCapability, xt as atomicWriteTextFile, y as buildModuleConfigsFromParams, yt as PROMOTE_EVENTS, z as saveModulePersona, zt as moduleCapability } from "./rule-builder-DMrJten6.mjs";
|
|
2
|
+
import { C as mergeStModulesWithReport, D as deleteWorldBookEntry, E as buildWorldBookEntry, M as DEFAULT_MODULE_ID, O as listWorldBookEntries, S as mergeStModules, T as stModuleId, _ as readModuleMemory, a as installModulePackage, b as convertStToModule, c as writeModule, d as validateCustomTools, f as safeAssetPath, g as mergeModuleIntoModule, h as importCharacterCardFile, i as exportModulePackage, j as ruleInjections, k as upsertWorldBookEntry, l as writePreset, m as importCharacterCard, o as moduleImportPreview, p as appendModuleMemory, r as expandModuleSource, s as ensureModuleReady, u as compileCustomTool, v as removeMergedModule, w as processStText, x as convertStToModuleWithReport, y as prepareImport } from "./module-package-q2gDRu9H.mjs";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
-
import { accessSync, chmodSync, constants, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
|
|
4
|
+
import { accessSync, chmodSync, constants, cpSync, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { Document, isMap, isScalar, parse, parseDocument, visit } from "yaml";
|
|
7
7
|
import { homedir } from "node:os";
|
|
@@ -1095,6 +1095,35 @@ function deleteSkillTarget(allowedRoots, path) {
|
|
|
1095
1095
|
};
|
|
1096
1096
|
}
|
|
1097
1097
|
}
|
|
1098
|
+
/** 包根 `skills/` 目录:随包分发的**内置技能库**(目录形态与用户技能根同构)。兼容源码与打包运行。 */
|
|
1099
|
+
function packageSkillsDir() {
|
|
1100
|
+
const candidates = [new URL("../skills/", import.meta.url), new URL("../../skills/", import.meta.url)];
|
|
1101
|
+
for (const candidate of candidates) {
|
|
1102
|
+
const dir = fileURLToPath(candidate);
|
|
1103
|
+
if (existsSync(dir)) return dir;
|
|
1104
|
+
}
|
|
1105
|
+
throw new Error("prompt-tool: cannot locate package skills/ directory");
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* 按包内同名目录补建缺失的内置技能;已有目录的正文、资源与调用策略保持原样。
|
|
1109
|
+
*
|
|
1110
|
+
* 与内置模块的种子化同一纪律:只补缺失项,用户改过或删过的技能不会被重新铺写;
|
|
1111
|
+
* 失败(包内无 `skills/`、目标不可写等)不阻断启动,用户仍可经 UI 创建或导入。
|
|
1112
|
+
*/
|
|
1113
|
+
function ensureSkillSeed(root = USER_SKILLS_DIR) {
|
|
1114
|
+
const created = [];
|
|
1115
|
+
try {
|
|
1116
|
+
mkdirSync(root, { recursive: true });
|
|
1117
|
+
for (const entry of readdirSync(packageSkillsDir(), { withFileTypes: true })) {
|
|
1118
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
1119
|
+
const target = join(root, entry.name);
|
|
1120
|
+
if (existsSync(target)) continue;
|
|
1121
|
+
cpSync(join(packageSkillsDir(), entry.name), target, { recursive: true });
|
|
1122
|
+
created.push(entry.name);
|
|
1123
|
+
}
|
|
1124
|
+
} catch {}
|
|
1125
|
+
return { created };
|
|
1126
|
+
}
|
|
1098
1127
|
//#endregion
|
|
1099
1128
|
//#region src/host/asset-sources.ts
|
|
1100
1129
|
/** 原始上传只进入当前 bridge 生命周期拥有的暂存目录。 */
|
|
@@ -7031,6 +7060,7 @@ function warn(ctx, message) {
|
|
|
7031
7060
|
}
|
|
7032
7061
|
function apply(ctx, configIn) {
|
|
7033
7062
|
const seededModules = new Set(ensureModuleSeed(MODULES_DIR).created);
|
|
7063
|
+
ensureSkillSeed();
|
|
7034
7064
|
const readConfig = () => ({ modulesEnabled: readModulesEnabled({
|
|
7035
7065
|
modulesEnabled: configIn.modulesEnabled?.get(),
|
|
7036
7066
|
writePreset: configIn.writePreset?.get()
|
package/lib/migrate-rules.mjs
CHANGED
|
@@ -1,5 +1,576 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { $ as injectionConfigSpec, Ct as ModuleLayerSettingsError, Et as assertModuleDirectory, Jt as isLegacyPromptParam, K as decomposeModule, Kt as LEGACY_PROMPT_PARAM_DEFINITIONS, Ot as assertModuleTree, Q as compileRules, St as RULE_OWNED_MODEL_PARAMS, Tt as readModuleLayerSettings, X as validateModuleDefinitionText, Y as rulePromptConfigOptions, Z as withModuleLock, Zt as MODULE_DEFINITION_FILE, d as mergePromptConfigs, dt as createPromptConfigs, f as modelRequestConfigs, ft as loadPromptConfigFiles, kt as canonicalModulesRoot, n as legacyPolicyDefaults, o as readConfigOrder, pt as FILE_SEQUENCE, qt as LEGACY_PROMPT_PARAM_KEYS, r as promptConfigToRule, t as fillMissing, vt as actionExecutionPoint, w as invalidateModuleSpec, xt as atomicWriteTextFile } from "./rule-builder-DMrJten6.mjs";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync } from "node:fs";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
import { isMap, isSeq, parseDocument } from "yaml";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
//#region src/host/legacy-prompt-params.ts
|
|
9
|
+
/** 旧层参数到规则实例的唯一兼容投影;读取、导入物化与旧 API 保存共用。 */
|
|
10
|
+
const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
11
|
+
const text = (value) => value === void 0 || value === null ? "" : String(value);
|
|
12
|
+
function readLegacyPromptParams(source) {
|
|
13
|
+
const params = {};
|
|
14
|
+
if (!isRecord(source.layerSettings)) return params;
|
|
15
|
+
for (const [layer, settings] of Object.entries(source.layerSettings)) {
|
|
16
|
+
if (!isRecord(settings)) continue;
|
|
17
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
18
|
+
if (!isLegacyPromptParam(key)) continue;
|
|
19
|
+
if (layer !== "pre-step") throw new ModuleLayerSettingsError(`${key} 必须位于 layerSettings.pre-step`);
|
|
20
|
+
params[key] = value === "on" ? true : value === "off" ? false : value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return params;
|
|
24
|
+
}
|
|
25
|
+
function legacyBody(source, moduleDir, supplied) {
|
|
26
|
+
if (supplied !== void 0) return supplied;
|
|
27
|
+
if (moduleDir !== void 0) try {
|
|
28
|
+
const file = join(moduleDir, "preset.md");
|
|
29
|
+
if (lstatSync(file).isSymbolicLink()) throw new Error(`旧模块正文不能是链接:${file}`);
|
|
30
|
+
return readFileSync(file, "utf8");
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error.code !== "ENOENT") throw error;
|
|
33
|
+
}
|
|
34
|
+
return text(source.content?.presetText);
|
|
35
|
+
}
|
|
36
|
+
function resolveLegacyPromptConfigs(source, options = {}) {
|
|
37
|
+
const params = readLegacyPromptParams(source);
|
|
38
|
+
for (const [key, value] of Object.entries(options.overrides ?? {})) if (isLegacyPromptParam(key) && value !== void 0 && value !== null) params[key] = value;
|
|
39
|
+
const keys = LEGACY_PROMPT_PARAM_KEYS.filter((key) => Object.hasOwn(params, key));
|
|
40
|
+
const applies = (id) => keys.some((key) => LEGACY_PROMPT_PARAM_DEFINITIONS[key].targets.includes(id));
|
|
41
|
+
const provided = (key) => options.partial !== true || Object.hasOwn(params, key);
|
|
42
|
+
const projected = (source.promptConfigs ?? []).map((config) => {
|
|
43
|
+
if (!isRecord(config) || !applies(config.id)) return config;
|
|
44
|
+
const next = {
|
|
45
|
+
...config,
|
|
46
|
+
params: { ...config.params }
|
|
47
|
+
};
|
|
48
|
+
if (config.id === "near-anchor") {
|
|
49
|
+
if (provided("firstTurnAnchor")) next.enabled = params.firstTurnAnchor === true;
|
|
50
|
+
if (provided("firstTurnCustom")) next.params.useCustom = params.firstTurnCustom === true;
|
|
51
|
+
if (provided("firstTurnText")) next.params.text = text(params.firstTurnText);
|
|
52
|
+
for (const key of [
|
|
53
|
+
"buildPattern",
|
|
54
|
+
"complexPattern",
|
|
55
|
+
"firstTurnBuild",
|
|
56
|
+
"firstTurnInspect",
|
|
57
|
+
"firstTurnDeep"
|
|
58
|
+
]) if (provided(key)) next.params[key] = text(params[key]);
|
|
59
|
+
} else if (config.id === "router-guide") {
|
|
60
|
+
if (provided("guideEnabled")) next.enabled = params.guideEnabled === true;
|
|
61
|
+
if (provided("guideCustom") || provided("guideEnabled")) {
|
|
62
|
+
const useCustom = provided("guideCustom") ? params.guideCustom === true : next.params.useCustom === true;
|
|
63
|
+
next.params.useCustom = useCustom;
|
|
64
|
+
next.modelScope = useCustom ? "all" : "flash";
|
|
65
|
+
}
|
|
66
|
+
if (provided("guideText")) next.params.text = text(params.guideText);
|
|
67
|
+
for (const key of [
|
|
68
|
+
"complexPattern",
|
|
69
|
+
"guideWeak",
|
|
70
|
+
"guideDeep"
|
|
71
|
+
]) if (provided(key)) next.params[key] = text(params[key]);
|
|
72
|
+
}
|
|
73
|
+
return next;
|
|
74
|
+
});
|
|
75
|
+
const configs = mergePromptConfigs(projected, options.promptConfigs).map((config) => {
|
|
76
|
+
if (config.id !== "prompt-injector" || !applies(config.id)) return config;
|
|
77
|
+
const body = legacyBody(source, options.moduleDir, options.prompt);
|
|
78
|
+
const explicitWord = text(options.partial === true && Object.hasOwn(params, "firstTurnWord") ? params.firstTurnWord : config.params?.firstTurnWord ?? params.firstTurnWord);
|
|
79
|
+
const anchor = projected.find((entry) => entry.id === "near-anchor");
|
|
80
|
+
const signalWord = (value) => {
|
|
81
|
+
const sentence = /the exact sentence:\s*([A-Za-z]+)/i.exec(value);
|
|
82
|
+
const first = /^\P{L}*([\p{L}]+)/u.exec(value.trim());
|
|
83
|
+
return (sentence?.[1] ?? first?.[1])?.toLowerCase();
|
|
84
|
+
};
|
|
85
|
+
const derivedWords = [...new Set([
|
|
86
|
+
"firstTurnText",
|
|
87
|
+
"firstTurnBuild",
|
|
88
|
+
"firstTurnInspect",
|
|
89
|
+
"firstTurnDeep"
|
|
90
|
+
].map((key) => signalWord(text(Object.hasOwn(params, key) || options.partial !== true ? params[key] : anchor?.params?.[key === "firstTurnText" ? "text" : key]))).filter((word) => word !== void 0))];
|
|
91
|
+
const { text: _text, texts: _texts, ...rest } = config;
|
|
92
|
+
return {
|
|
93
|
+
...rest,
|
|
94
|
+
enabled: body.trim().length > 0 && (provided("injectPrompt") ? params.injectPrompt !== false : config.enabled !== false),
|
|
95
|
+
params: {
|
|
96
|
+
...config.params,
|
|
97
|
+
text: body,
|
|
98
|
+
firstTurnWord: explicitWord,
|
|
99
|
+
anchorWords: explicitWord.length > 0 ? [explicitWord] : derivedWords
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
const ids = new Set(configs.map((config) => config.id));
|
|
104
|
+
const consumedKeys = keys.filter((key) => LEGACY_PROMPT_PARAM_DEFINITIONS[key].targets.some((id) => ids.has(id)));
|
|
105
|
+
const unhandled = keys.filter((key) => !consumedKeys.includes(key));
|
|
106
|
+
return {
|
|
107
|
+
configs,
|
|
108
|
+
consumedKeys,
|
|
109
|
+
active: keys.length > 0,
|
|
110
|
+
warnings: unhandled.length === 0 ? [] : [`旧规则参数没有承接配置,原值保留:${unhandled.join(", ")}`]
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/host/rules-migration.ts
|
|
115
|
+
/** 旧规则只在显式离线迁移或外部导入时转换;运行时不调用此模块。 */
|
|
116
|
+
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
117
|
+
const condition = (items) => items.length === 0 ? void 0 : items.length === 1 ? items[0] : { all: items };
|
|
118
|
+
/** 只递归条件语法树;正文、正则字符串及未知对象字段均不扫描或改写。 */
|
|
119
|
+
function migrateConditionDefaults(value) {
|
|
120
|
+
if (!record(value)) return value;
|
|
121
|
+
if (Object.keys(value).length !== 1) return value;
|
|
122
|
+
if (record(value.phase) && value.phase.promoteGate === true) {
|
|
123
|
+
const phase = structuredClone(value.phase);
|
|
124
|
+
fillMissing(phase, legacyPolicyDefaults().promotionGate);
|
|
125
|
+
return { phase };
|
|
126
|
+
}
|
|
127
|
+
for (const key of [
|
|
128
|
+
"all",
|
|
129
|
+
"any",
|
|
130
|
+
"notAny"
|
|
131
|
+
]) if (Array.isArray(value[key])) return { [key]: value[key].map(migrateConditionDefaults) };
|
|
132
|
+
if (Object.hasOwn(value, "not")) return { not: migrateConditionDefaults(value.not) };
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
/** 参数桥优先于行配置;仅旧定义里确已打开的指令提示才携入旧非空模板。 */
|
|
136
|
+
function instructionHintPolicyPatch(source) {
|
|
137
|
+
const params = readModuleLayerSettings(source);
|
|
138
|
+
const modules = record(source.moduleConfigs) ? source.moduleConfigs : {};
|
|
139
|
+
const current = record(modules["instruction-hint"]) ? modules["instruction-hint"] : {};
|
|
140
|
+
if (!(Object.hasOwn(params, "instructionHint") ? params.instructionHint === true : current.enabled === true)) return void 0;
|
|
141
|
+
const patched = { ...current };
|
|
142
|
+
fillMissing(patched, legacyPolicyDefaults().instructionHint);
|
|
143
|
+
return JSON.stringify(current) === JSON.stringify(patched) ? void 0 : patched;
|
|
144
|
+
}
|
|
145
|
+
function rebaseTemplate(config, directory, previousBase) {
|
|
146
|
+
if (typeof config.templateFile !== "string" || config.templateFile.length === 0) return config;
|
|
147
|
+
const resolved = isAbsolute(config.templateFile) ? resolve(config.templateFile) : fileURLToPath(new URL(config.templateFile, previousBase));
|
|
148
|
+
const path = relative(directory, resolved);
|
|
149
|
+
if (path === ".." || path.startsWith(`..${sep}`) || isAbsolute(path)) throw new Error(`模板路径不在模块目录,无法安全迁移:${config.templateFile}`);
|
|
150
|
+
if (!existsSync(resolved)) throw new Error(`模板文件缺失,无法验证迁移:${config.templateFile}`);
|
|
151
|
+
return {
|
|
152
|
+
...config,
|
|
153
|
+
templateFile: `./${path.replaceAll(sep, "/")}`
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function convertLegacyModuleRules(source, options = {}) {
|
|
157
|
+
if (!record(source)) throw new TypeError("模块定义必须是对象");
|
|
158
|
+
const directory = options.directory;
|
|
159
|
+
const legacy = resolveLegacyPromptConfigs(source, { moduleDir: directory });
|
|
160
|
+
if (legacy.warnings.length > 0) throw new Error(`旧快捷参数没有无损承接规则:${legacy.warnings.join("; ")}`);
|
|
161
|
+
if (source.promptConfigs !== void 0 && !Array.isArray(source.promptConfigs)) throw new Error("promptConfigs 必须是数组");
|
|
162
|
+
if (source.triggers !== void 0 && !Array.isArray(source.triggers)) throw new Error("triggers 必须是数组");
|
|
163
|
+
if (source.rules !== void 0 && !Array.isArray(source.rules)) throw new Error("rules 必须是数组");
|
|
164
|
+
const root = directory === void 0 ? void 0 : dirname(directory);
|
|
165
|
+
let configs = mergePromptConfigs(modelRequestConfigs(readModuleLayerSettings(source)), legacy.configs);
|
|
166
|
+
const rules = structuredClone(source.rules ?? []);
|
|
167
|
+
const configOrder = readConfigOrder(source.configOrder);
|
|
168
|
+
if (directory !== void 0 && existsSync(join(directory, "configs"))) {
|
|
169
|
+
const actual = loadPromptConfigFiles(pathToFileURL(join(directory, "configs") + sep));
|
|
170
|
+
for (const [index, config] of actual.entries()) if (configOrder[config.id] === void 0) configOrder[config.id] = config[FILE_SEQUENCE] ?? index * 10;
|
|
171
|
+
if (!Object.hasOwn(source, "promptConfigs") && configs.length === 0) configs = actual;
|
|
172
|
+
else {
|
|
173
|
+
const options = {
|
|
174
|
+
sourceModuleId: source.id,
|
|
175
|
+
configOrder,
|
|
176
|
+
templateBaseUrl: pathToFileURL(join(root, ".engine", "prompt-config-engine.mjs")),
|
|
177
|
+
templateModuleRoot: pathToFileURL(root + sep)
|
|
178
|
+
};
|
|
179
|
+
const prepared = configs.map((config) => injectionConfigSpec({ id: config.id }, {
|
|
180
|
+
id: "inject",
|
|
181
|
+
kind: "inject-text",
|
|
182
|
+
config
|
|
183
|
+
}, source));
|
|
184
|
+
const comparable = (config) => config.strategy === "custom-fallback" ? {
|
|
185
|
+
...config,
|
|
186
|
+
strategy: "anchor-notice"
|
|
187
|
+
} : config;
|
|
188
|
+
const expected = createPromptConfigs(prepared.map(comparable), options);
|
|
189
|
+
const running = createPromptConfigs(actual.map(comparable), options);
|
|
190
|
+
const fingerprint = (entries) => contentHash(entries.map((config) => {
|
|
191
|
+
const value = { ...config };
|
|
192
|
+
if (value.enabled === false) delete value.texts;
|
|
193
|
+
return value;
|
|
194
|
+
}));
|
|
195
|
+
if (fingerprint(expected) !== fingerprint(running)) throw new Error(`模块 ${String(source.id ?? directory)} 的定义与旧实际物化内容不一致,无法无损迁移;请先明确选择来源`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const append = (rule, sequence) => {
|
|
199
|
+
if (rules.some((item) => item.id === rule.id)) throw new Error(`规则身份冲突,须先明确改名:${rule.id}`);
|
|
200
|
+
rules.push(rule);
|
|
201
|
+
if (configOrder[rule.id] === void 0) configOrder[rule.id] = sequence;
|
|
202
|
+
};
|
|
203
|
+
for (const [index, config] of configs.entries()) {
|
|
204
|
+
const rebased = directory === void 0 ? config : rebaseTemplate(config, directory, pathToFileURL(join(root, ".engine", "prompt-config-engine.mjs")));
|
|
205
|
+
append(promptConfigToRule(rebased), index * 10);
|
|
206
|
+
}
|
|
207
|
+
for (const [index, raw] of (source.triggers ?? []).entries()) {
|
|
208
|
+
if (!record(raw) || typeof raw.id !== "string" || raw.id.length === 0) throw new Error("旧声明必须有唯一 id");
|
|
209
|
+
const rawActions = Array.isArray(raw.do) ? raw.do : [raw.do];
|
|
210
|
+
let enabled;
|
|
211
|
+
const gates = raw.when == null ? [] : [migrateConditionDefaults(raw.when)];
|
|
212
|
+
const liftCondition = (gate) => {
|
|
213
|
+
if (gate === void 0) return;
|
|
214
|
+
if (rawActions.length !== 1) throw new Error(`声明 ${raw.id} 的动作局部条件不能无损提升为整卡条件;请先拆分声明`);
|
|
215
|
+
gates.push(gate);
|
|
216
|
+
};
|
|
217
|
+
const actions = rawActions.map((action, actionIndex) => {
|
|
218
|
+
if (!record(action) || typeof action.kind !== "string") throw new Error(`旧声明 ${raw.id} 的动作不合法`);
|
|
219
|
+
const point = actionExecutionPoint(action);
|
|
220
|
+
if (raw.channel !== point.channel || raw.phase !== void 0 && raw.phase !== point.phase) throw new Error(`旧声明 ${raw.id} 的执行点不合法`);
|
|
221
|
+
const next = structuredClone(action);
|
|
222
|
+
if (next.kind === "inject-text" && record(next.config)) {
|
|
223
|
+
const converted = promptConfigToRule(next.config);
|
|
224
|
+
if (converted.enabled === false && rawActions.length !== 1) throw new Error(`声明 ${raw.id} 的动作局部停用不能无损提升为整卡开关;请先拆分声明`);
|
|
225
|
+
if (rawActions.length === 1) enabled = converted.enabled;
|
|
226
|
+
liftCondition(converted.if);
|
|
227
|
+
const payload = converted.then[0]?.config;
|
|
228
|
+
const config = {
|
|
229
|
+
...next.config,
|
|
230
|
+
...record(payload) ? payload : {}
|
|
231
|
+
};
|
|
232
|
+
delete config.enabled;
|
|
233
|
+
const params = record(config.params) ? config.params : {};
|
|
234
|
+
if (params.complete !== true && params.suppressRuntimeContext !== true) for (const key of [
|
|
235
|
+
"audience",
|
|
236
|
+
"modelScope",
|
|
237
|
+
"promotion",
|
|
238
|
+
"subject",
|
|
239
|
+
"match"
|
|
240
|
+
]) delete config[key];
|
|
241
|
+
next.config = config;
|
|
242
|
+
}
|
|
243
|
+
if (next.kind === "request-params") {
|
|
244
|
+
const modelScope = Object.hasOwn(next, "modelScope") ? next.modelScope : "pro";
|
|
245
|
+
const scope = {
|
|
246
|
+
...next.audience == null || next.audience === "" ? {} : { audience: next.audience },
|
|
247
|
+
...modelScope == null || modelScope === "" || modelScope === "all" ? {} : { modelScope }
|
|
248
|
+
};
|
|
249
|
+
if (Object.keys(scope).length > 0) liftCondition({ scope });
|
|
250
|
+
delete next.audience;
|
|
251
|
+
delete next.modelScope;
|
|
252
|
+
}
|
|
253
|
+
if (next.kind === "inject-text" && record(next.config) && directory !== void 0) next.config = rebaseTemplate(next.config, directory, pathToFileURL(join(directory, "triggers.yml")));
|
|
254
|
+
return {
|
|
255
|
+
...next,
|
|
256
|
+
id: typeof next.id === "string" && next.id.length > 0 ? next.id : `action-${actionIndex + 1}`,
|
|
257
|
+
kind: action.kind,
|
|
258
|
+
channelOrder: typeof raw.channelOrder === "number" ? raw.channelOrder : 0,
|
|
259
|
+
...raw.waterfallPosition === void 0 ? {} : { waterfallPosition: raw.waterfallPosition }
|
|
260
|
+
};
|
|
261
|
+
});
|
|
262
|
+
if (actions.length > 1 && actions.some((action) => actionExecutionPoint(action).phase === "after-next")) throw new Error(`声明 ${raw.id} 含多个 after-next 动作,须先核对实际执行顺序再迁移`);
|
|
263
|
+
const layer = actions.find((action) => action.kind === "inject-text" && record(action.config))?.config;
|
|
264
|
+
append({
|
|
265
|
+
id: raw.id,
|
|
266
|
+
...enabled === void 0 ? {} : { enabled },
|
|
267
|
+
...layer?.layer === void 0 ? {} : { layer: layer.layer },
|
|
268
|
+
...condition(gates) === void 0 ? {} : { if: condition(gates) },
|
|
269
|
+
then: actions
|
|
270
|
+
}, (configs.length + index) * 10);
|
|
271
|
+
}
|
|
272
|
+
compileRules(rules, {
|
|
273
|
+
configOrder,
|
|
274
|
+
...directory === void 0 ? {} : {
|
|
275
|
+
moduleId: source.id,
|
|
276
|
+
promptConfigOptions: rulePromptConfigOptions(directory)
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
rules,
|
|
281
|
+
configOrder,
|
|
282
|
+
consumedLegacyKeys: Object.keys(readLegacyPromptParams(source))
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
const MODEL_KEYS = RULE_OWNED_MODEL_PARAMS;
|
|
286
|
+
const OLD_ENGINES = /* @__PURE__ */ new Set(["prompt-config-engine", "declared-triggers"]);
|
|
287
|
+
const digest = (value) => createHash("sha256").update(value).digest("hex");
|
|
288
|
+
const sortedValue = (value) => Array.isArray(value) ? value.map(sortedValue) : record(value) ? Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortedValue(value[key])])) : value;
|
|
289
|
+
const contentHash = (value) => digest(JSON.stringify(sortedValue(value)));
|
|
290
|
+
/** 原证明仍匹配旧卡时才推进角色记忆哈希;用户改过的卡保持未认领。 */
|
|
291
|
+
function migrateMemoryProofs(doc, source, rules) {
|
|
292
|
+
const proofs = record(source.meta) && record(source.meta.characterMemories) ? source.meta.characterMemories : {};
|
|
293
|
+
const configs = Array.isArray(source.promptConfigs) ? source.promptConfigs : [];
|
|
294
|
+
for (const [cardId, proof] of Object.entries(proofs)) {
|
|
295
|
+
if (!record(proof) || typeof proof.configId !== "string") continue;
|
|
296
|
+
const old = configs.find((config) => record(config) && config.id === proof.configId);
|
|
297
|
+
const rule = rules.find((item) => item.id === proof.configId);
|
|
298
|
+
if (old !== void 0 && rule !== void 0 && proof.contentHash === contentHash(old)) doc.setIn([
|
|
299
|
+
"meta",
|
|
300
|
+
"characterMemories",
|
|
301
|
+
cardId,
|
|
302
|
+
"contentHash"
|
|
303
|
+
], contentHash(rule));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/** 旧字段移入动作后仍把原注释保留在所属规则上,不把用户说明随旧段删除。 */
|
|
307
|
+
function setMigratedRules(doc, rules) {
|
|
308
|
+
const comments = (node) => {
|
|
309
|
+
const own = record(node) ? [node.commentBefore, node.comment].filter((value) => typeof value === "string" && value.length > 0) : [];
|
|
310
|
+
if (isMap(node)) return [...own, ...node.items.flatMap((pair) => [...comments(pair.key), ...comments(pair.value)])];
|
|
311
|
+
if (isSeq(node)) return [...own, ...node.items.flatMap(comments)];
|
|
312
|
+
return own;
|
|
313
|
+
};
|
|
314
|
+
const previous = [
|
|
315
|
+
"rules",
|
|
316
|
+
"promptConfigs",
|
|
317
|
+
"triggers"
|
|
318
|
+
].map((key) => doc.get(key, true)).filter(isSeq);
|
|
319
|
+
const nodes = doc.createNode(rules);
|
|
320
|
+
if (isSeq(nodes)) {
|
|
321
|
+
const groupComments = previous.flatMap((node) => [node.commentBefore, node.comment].filter((value) => typeof value === "string"));
|
|
322
|
+
if (groupComments.length > 0) nodes.commentBefore = groupComments.join("\n");
|
|
323
|
+
for (const node of nodes.items) if (isMap(node)) {
|
|
324
|
+
const text = comments(previous.flatMap((item) => item.items).find((item) => isMap(item) && item.get("id") === node.get("id"))).join("\n");
|
|
325
|
+
if (text.length > 0) node.commentBefore = text;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
doc.set("rules", nodes);
|
|
329
|
+
}
|
|
330
|
+
function assertAssetDirectory(root, id, definitionFile) {
|
|
331
|
+
if (definitionFile !== "module.yml") throw new Error("迁移只接受模块定义");
|
|
332
|
+
return assertModuleDirectory(root, id);
|
|
333
|
+
}
|
|
334
|
+
/** 包括资产和生成物的整个模块指纹;回滚不覆盖迁移后出现的任意用户改动。 */
|
|
335
|
+
function treeHash(directory) {
|
|
336
|
+
assertModuleTree(directory);
|
|
337
|
+
const hash = createHash("sha256");
|
|
338
|
+
const visit = (dir) => {
|
|
339
|
+
for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
|
|
340
|
+
const file = join(dir, entry.name);
|
|
341
|
+
const key = relative(directory, file).replaceAll(sep, "/");
|
|
342
|
+
hash.update(JSON.stringify([
|
|
343
|
+
key,
|
|
344
|
+
entry.isDirectory() ? "directory" : "file",
|
|
345
|
+
lstatSync(file).mode
|
|
346
|
+
]));
|
|
347
|
+
if (entry.isDirectory()) visit(file);
|
|
348
|
+
else hash.update(readFileSync(file));
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
visit(directory);
|
|
352
|
+
return hash.digest("hex");
|
|
353
|
+
}
|
|
354
|
+
/** 全量只读预检。root 必须显式提供,不从真实 DSH_HOME 推断。 */
|
|
355
|
+
function planRulesMigration(root, options = {}) {
|
|
356
|
+
if (typeof root !== "string" || root.trim().length === 0 || !isAbsolute(root)) throw new Error("迁移必须显式指定绝对 --root(模块根目录)");
|
|
357
|
+
const canonical = canonicalModulesRoot(root);
|
|
358
|
+
const definitionFile = MODULE_DEFINITION_FILE;
|
|
359
|
+
const items = [];
|
|
360
|
+
for (const entry of readdirSync(canonical, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
361
|
+
if (entry.name.startsWith(".")) continue;
|
|
362
|
+
if (entry.isSymbolicLink() && existsSync(join(canonical, entry.name, definitionFile))) throw new Error(`迁移目录不能是链接:${entry.name}`);
|
|
363
|
+
if (!entry.isDirectory()) continue;
|
|
364
|
+
if (!existsSync(join(canonical, entry.name, definitionFile))) continue;
|
|
365
|
+
const directory = assertAssetDirectory(canonical, entry.name, definitionFile);
|
|
366
|
+
assertModuleTree(directory);
|
|
367
|
+
const bytes = readFileSync(join(directory, definitionFile));
|
|
368
|
+
const doc = parseDocument(new TextDecoder("utf-8", { fatal: true }).decode(bytes), { logLevel: "silent" });
|
|
369
|
+
if (doc.errors.length > 0) throw new Error(`模块 ${entry.name} YAML 无效:${doc.errors[0].message}`);
|
|
370
|
+
const source = doc.toJS();
|
|
371
|
+
if (!record(source)) throw new Error(`模块 ${entry.name} 定义必须是对象`);
|
|
372
|
+
const layerParams = readModuleLayerSettings(source);
|
|
373
|
+
const oldModules = Array.isArray(source.modules) && source.modules.some((id) => OLD_ENGINES.has(String(id)));
|
|
374
|
+
const oldRules = Object.hasOwn(source, "promptConfigs") || Object.hasOwn(source, "triggers") || Object.keys(readLegacyPromptParams(source)).length > 0 || MODEL_KEYS.some((key) => Object.hasOwn(layerParams, key)) || oldModules;
|
|
375
|
+
const hintPatch = oldRules || !Object.hasOwn(source, "rules") ? instructionHintPolicyPatch(source) : void 0;
|
|
376
|
+
if (!(oldRules || hintPatch !== void 0)) {
|
|
377
|
+
validateModuleDefinitionText(directory, doc.toString());
|
|
378
|
+
if (options.decompose) items.push({
|
|
379
|
+
moduleId: entry.name,
|
|
380
|
+
directory,
|
|
381
|
+
definitionFile,
|
|
382
|
+
originalHash: treeHash(directory),
|
|
383
|
+
sourceHash: digest(bytes),
|
|
384
|
+
nextDefinition: new TextDecoder("utf-8", { fatal: true }).decode(bytes)
|
|
385
|
+
});
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (options.decompose) throw new Error(`模块 ${entry.name} 仍含旧规则来源,请先运行 --apply 迁移再分解`);
|
|
389
|
+
if (!Array.isArray(source.modules)) throw new Error(`模块 ${entry.name} 使用手写组合,无法证明旧引擎装配替换;请先显式整理 modules`);
|
|
390
|
+
if (record(source.params) && LEGACY_PROMPT_PARAM_KEYS.some((key) => Object.hasOwn(source.params, key))) throw new Error(`模块 ${entry.name} 含曾被运行时忽略的顶层旧参数,无法推测激活语义`);
|
|
391
|
+
const converted = convertLegacyModuleRules(source, { directory });
|
|
392
|
+
if (hintPatch !== void 0) {
|
|
393
|
+
for (const [key, value] of Object.entries(hintPatch)) if (!doc.hasIn([
|
|
394
|
+
"moduleConfigs",
|
|
395
|
+
"instruction-hint",
|
|
396
|
+
key
|
|
397
|
+
])) doc.setIn([
|
|
398
|
+
"moduleConfigs",
|
|
399
|
+
"instruction-hint",
|
|
400
|
+
key
|
|
401
|
+
], value);
|
|
402
|
+
}
|
|
403
|
+
migrateMemoryProofs(doc, source, converted.rules);
|
|
404
|
+
setMigratedRules(doc, converted.rules);
|
|
405
|
+
doc.set("configOrder", converted.configOrder);
|
|
406
|
+
doc.delete("promptConfigs");
|
|
407
|
+
doc.delete("triggers");
|
|
408
|
+
if (Array.isArray(source.modules)) doc.set("modules", [...new Set(source.modules.map((id) => OLD_ENGINES.has(String(id)) ? "rule-engine" : id))]);
|
|
409
|
+
if (record(source.layerSettings)) {
|
|
410
|
+
for (const [layer, values] of Object.entries(source.layerSettings)) if (record(values)) {
|
|
411
|
+
for (const key of [...converted.consumedLegacyKeys, ...MODEL_KEYS]) if (Object.hasOwn(values, key)) doc.deleteIn([
|
|
412
|
+
"layerSettings",
|
|
413
|
+
layer,
|
|
414
|
+
key
|
|
415
|
+
]);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (record(source.moduleConfigs)) {
|
|
419
|
+
if ([source.moduleConfigs["prompt-config-engine"], source.moduleConfigs["declared-triggers"]].filter(record).map((value) => value.strategyDir).filter((value) => typeof value === "string" && value.length > 0).length > 0) throw new Error(`模块 ${entry.name} 使用自定义策略目录,需先确认统一基准后迁移`);
|
|
420
|
+
for (const id of OLD_ENGINES) doc.deleteIn(["moduleConfigs", id]);
|
|
421
|
+
}
|
|
422
|
+
validateModuleDefinitionText(directory, doc.toString());
|
|
423
|
+
items.push({
|
|
424
|
+
moduleId: entry.name,
|
|
425
|
+
directory,
|
|
426
|
+
definitionFile,
|
|
427
|
+
originalHash: treeHash(directory),
|
|
428
|
+
sourceHash: digest(bytes),
|
|
429
|
+
nextDefinition: doc.toString()
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
root: canonical,
|
|
434
|
+
kind: "modules",
|
|
435
|
+
items
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
const BACKUP_DIR = ".rules-migration-backup";
|
|
439
|
+
/** 先完整生成所有候选,再复核全根;每个模块以目录 rename 原子交换并保留原树备份。 */
|
|
440
|
+
async function applyRulesMigration(plan) {
|
|
441
|
+
if (canonicalModulesRoot(plan.root) !== plan.root) throw new Error("迁移根目录身份已变化");
|
|
442
|
+
if (plan.items.length === 0) return { changed: [] };
|
|
443
|
+
const backup = join(plan.root, BACKUP_DIR);
|
|
444
|
+
if (existsSync(backup)) throw new Error(`已存在迁移备份,请先处理:${backup}`);
|
|
445
|
+
const workspace = mkdtempSync(join(plan.root, ".rules-migration-stage-"));
|
|
446
|
+
const staged = /* @__PURE__ */ new Map();
|
|
447
|
+
const manifest = {
|
|
448
|
+
version: 1,
|
|
449
|
+
root: plan.root,
|
|
450
|
+
status: "applying",
|
|
451
|
+
entries: []
|
|
452
|
+
};
|
|
453
|
+
const manifestFile = join(backup, "manifest.json");
|
|
454
|
+
const verify = (item) => {
|
|
455
|
+
if (assertAssetDirectory(plan.root, item.moduleId, item.definitionFile) !== item.directory || treeHash(item.directory) !== item.originalHash || digest(readFileSync(join(item.directory, item.definitionFile))) !== item.sourceHash) throw new Error(`来源 ${item.moduleId} 在预检后已变化,未覆盖`);
|
|
456
|
+
};
|
|
457
|
+
try {
|
|
458
|
+
for (const item of plan.items) {
|
|
459
|
+
verify(item);
|
|
460
|
+
const source = join(workspace, item.moduleId);
|
|
461
|
+
cpSync(item.directory, source, {
|
|
462
|
+
recursive: true,
|
|
463
|
+
errorOnExist: true,
|
|
464
|
+
force: false
|
|
465
|
+
});
|
|
466
|
+
atomicWriteTextFile(join(source, item.definitionFile), item.nextDefinition);
|
|
467
|
+
decomposeModule(source);
|
|
468
|
+
for (const path of [
|
|
469
|
+
"rules.yml",
|
|
470
|
+
"configs",
|
|
471
|
+
"agent.cordis.yml",
|
|
472
|
+
"custom-tools",
|
|
473
|
+
"subagent-tools"
|
|
474
|
+
]) rmSync(join(source, path), {
|
|
475
|
+
recursive: true,
|
|
476
|
+
force: true
|
|
477
|
+
});
|
|
478
|
+
const generated = source;
|
|
479
|
+
staged.set(item.moduleId, generated);
|
|
480
|
+
manifest.entries.push({
|
|
481
|
+
moduleId: item.moduleId,
|
|
482
|
+
definitionFile: item.definitionFile,
|
|
483
|
+
before: item.originalHash,
|
|
484
|
+
after: treeHash(generated),
|
|
485
|
+
written: false
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
for (const item of plan.items) verify(item);
|
|
489
|
+
mkdirSync(backup);
|
|
490
|
+
atomicWriteTextFile(manifestFile, JSON.stringify(manifest, null, 2) + "\n");
|
|
491
|
+
for (const item of plan.items) withModuleLock(plan.root, item.moduleId, () => {
|
|
492
|
+
verify(item);
|
|
493
|
+
const old = join(backup, item.moduleId);
|
|
494
|
+
renameSync(item.directory, old);
|
|
495
|
+
try {
|
|
496
|
+
renameSync(staged.get(item.moduleId), item.directory);
|
|
497
|
+
} catch (error) {
|
|
498
|
+
if (!existsSync(item.directory)) renameSync(old, item.directory);
|
|
499
|
+
throw error;
|
|
500
|
+
}
|
|
501
|
+
manifest.entries.find((entry) => entry.moduleId === item.moduleId).written = true;
|
|
502
|
+
invalidateModuleSpec(item.directory);
|
|
503
|
+
atomicWriteTextFile(manifestFile, JSON.stringify(manifest, null, 2) + "\n");
|
|
504
|
+
});
|
|
505
|
+
manifest.status = "applied";
|
|
506
|
+
atomicWriteTextFile(manifestFile, JSON.stringify(manifest, null, 2) + "\n");
|
|
507
|
+
return {
|
|
508
|
+
changed: plan.items.map((item) => item.moduleId),
|
|
509
|
+
backup
|
|
510
|
+
};
|
|
511
|
+
} catch (error) {
|
|
512
|
+
for (const entry of [...manifest.entries].reverse()) if (entry.written) {
|
|
513
|
+
const current = join(plan.root, entry.moduleId);
|
|
514
|
+
const original = join(backup, entry.moduleId);
|
|
515
|
+
withModuleLock(plan.root, entry.moduleId, () => {
|
|
516
|
+
if (existsSync(current) && treeHash(current) === entry.after && existsSync(original)) {
|
|
517
|
+
const retired = join(workspace, `failed-${entry.moduleId}`);
|
|
518
|
+
renameSync(current, retired);
|
|
519
|
+
renameSync(original, current);
|
|
520
|
+
entry.written = false;
|
|
521
|
+
invalidateModuleSpec(current);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
if (existsSync(manifestFile)) atomicWriteTextFile(manifestFile, JSON.stringify(manifest, null, 2) + "\n");
|
|
526
|
+
throw error;
|
|
527
|
+
} finally {
|
|
528
|
+
if (dirname(workspace) === plan.root && workspace.startsWith(join(plan.root, ".rules-migration-stage-"))) rmSync(workspace, {
|
|
529
|
+
recursive: true,
|
|
530
|
+
force: true
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function rollbackRulesMigration(root, options = {}) {
|
|
535
|
+
if (typeof root !== "string" || !isAbsolute(root)) throw new Error("回滚必须显式指定绝对 --root");
|
|
536
|
+
const canonical = canonicalModulesRoot(root);
|
|
537
|
+
const backup = join(canonical, BACKUP_DIR);
|
|
538
|
+
if (lstatSync(backup).isSymbolicLink()) throw new Error("迁移备份不能是链接");
|
|
539
|
+
const file = join(backup, "manifest.json");
|
|
540
|
+
const manifest = JSON.parse(readFileSync(file, "utf8"));
|
|
541
|
+
if (manifest.version !== 1 || manifest.root !== canonical || !Array.isArray(manifest.entries)) throw new Error("迁移备份不属于当前根");
|
|
542
|
+
if (manifest.status === "rolled-back") return { restored: [] };
|
|
543
|
+
const targets = manifest.entries.filter((entry) => entry.written);
|
|
544
|
+
for (const entry of targets) {
|
|
545
|
+
if (entry.definitionFile !== "module.yml") throw new Error("迁移备份定义类型无效");
|
|
546
|
+
const current = assertAssetDirectory(canonical, entry.moduleId, entry.definitionFile);
|
|
547
|
+
const original = assertAssetDirectory(backup, entry.moduleId, entry.definitionFile);
|
|
548
|
+
if (treeHash(current) !== entry.after || treeHash(original) !== entry.before) throw new Error(`模块 ${entry.moduleId} 或备份已变化,拒绝回滚覆盖`);
|
|
549
|
+
if (existsSync(join(backup, `${entry.moduleId}.migrated`))) throw new Error(`模块 ${entry.moduleId} 的回滚保留目录已存在`);
|
|
550
|
+
}
|
|
551
|
+
if (options.checkOnly === true) return { restored: targets.map((entry) => entry.moduleId) };
|
|
552
|
+
for (const entry of targets) {
|
|
553
|
+
const current = join(canonical, entry.moduleId);
|
|
554
|
+
const retired = join(backup, `${entry.moduleId}.migrated`);
|
|
555
|
+
withModuleLock(canonical, entry.moduleId, () => {
|
|
556
|
+
if (treeHash(current) !== entry.after || treeHash(join(backup, entry.moduleId)) !== entry.before) throw new Error(`模块 ${entry.moduleId} 在回滚期间变化,未覆盖`);
|
|
557
|
+
renameSync(current, retired);
|
|
558
|
+
try {
|
|
559
|
+
renameSync(join(backup, entry.moduleId), current);
|
|
560
|
+
} catch (error) {
|
|
561
|
+
if (!existsSync(current)) renameSync(retired, current);
|
|
562
|
+
throw error;
|
|
563
|
+
}
|
|
564
|
+
entry.written = false;
|
|
565
|
+
invalidateModuleSpec(current);
|
|
566
|
+
atomicWriteTextFile(file, JSON.stringify(manifest, null, 2) + "\n");
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
manifest.status = "rolled-back";
|
|
570
|
+
atomicWriteTextFile(file, JSON.stringify(manifest, null, 2) + "\n");
|
|
571
|
+
return { restored: targets.map((entry) => entry.moduleId) };
|
|
572
|
+
}
|
|
573
|
+
//#endregion
|
|
3
574
|
//#region src/migrate-rules.ts
|
|
4
575
|
const args = process.argv.slice(2).filter((value) => value !== "--");
|
|
5
576
|
let root;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as installModulePackage, o as moduleImportPreview } from "./module-package-
|
|
1
|
+
import { a as installModulePackage, o as moduleImportPreview } from "./module-package-q2gDRu9H.mjs";
|
|
2
2
|
export { installModulePackage, installModulePackage as installPresetPackage, moduleImportPreview, moduleImportPreview as presetImportPreview };
|