dsh-plugin-prompt-tool 0.8.0 → 1.0.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.
Files changed (75) hide show
  1. package/README.md +58 -50
  2. package/engine/character-tools.mjs +3 -6
  3. package/engine/compositions/library/agent-instructions.yml +1 -1
  4. package/engine/compositions/library/command-goal.yml +1 -1
  5. package/engine/compositions/library/compaction.yml +1 -1
  6. package/engine/compositions/library/delegation-ptc.yml +1 -1
  7. package/engine/compositions/library/delegation.yml +1 -1
  8. package/engine/compositions/library/persistent-shell.yml +1 -1
  9. package/engine/compositions/library/planning.yml +1 -1
  10. package/engine/compositions/library/skill-filesystem-cordis.yml +1 -1
  11. package/engine/compositions/library/skill-filesystem.yml +1 -1
  12. package/engine/compositions/library/tool-ask-user.yml +1 -1
  13. package/engine/compositions/library/tool-bash.yml +1 -1
  14. package/engine/compositions/library/tool-cordis.yml +1 -1
  15. package/engine/compositions/library/tool-fs-search.yml +1 -1
  16. package/engine/compositions/library/tool-fs.yml +1 -1
  17. package/engine/compositions/library/tool-goal.yml +1 -1
  18. package/engine/compositions/library/tool-jobs.yml +1 -1
  19. package/engine/compositions/library/tool-plugin-manager-disabled.yml +1 -1
  20. package/engine/compositions/library/tool-plugin-manager.yml +1 -1
  21. package/engine/compositions/library/tool-present.yml +1 -1
  22. package/engine/compositions/library/tool-presentation.yml +1 -1
  23. package/engine/compositions/library/tool-pwsh.yml +1 -1
  24. package/engine/compositions/library/tool-skill.yml +1 -1
  25. package/engine/compositions/library/tool-todo.yml +1 -1
  26. package/engine/compositions/library/tool-web.yml +1 -1
  27. package/engine/compositions/source/local/character-tools.yml +4 -4
  28. package/engine/compositions/source/local/prompt-config-engine.yml +1 -1
  29. package/engine/compositions/source/local/session-var-tools.yml +4 -4
  30. package/engine/compositions/source/local/world-book-tools.yml +4 -4
  31. package/engine/executor.mjs +39 -26
  32. package/engine/instruction-hint.mjs +6 -2
  33. package/engine/layers.mjs +38 -30
  34. package/engine/order.mjs +17 -0
  35. package/engine/schema.mjs +16 -6
  36. package/engine/session-var-tools.mjs +3 -6
  37. package/engine/st-render.mjs +8 -4
  38. package/engine/subagent-tool-policy.mjs +25 -10
  39. package/engine/world-book-tools.mjs +3 -6
  40. package/lib/client.js +3451 -3519
  41. package/lib/client.js.map +1 -1
  42. package/lib/index.d.mts +233 -272
  43. package/lib/index.mjs +1020 -1226
  44. package/lib/{preset-package-BnJqrU4R.mjs → module-package-Cm3fHHlp.mjs} +997 -594
  45. package/lib/{paths-CY5psBqE.d.mts → paths-BgL42sSM.d.mts} +191 -149
  46. package/lib/preset-transfer.d.mts +2 -2
  47. package/lib/preset-transfer.mjs +2 -2
  48. package/modules/ponytail/configs/0000-ponytail-rules.yml +102 -0
  49. package/modules/ponytail/configs/0010-ponytail-level-full.yml +19 -0
  50. package/modules/ponytail/configs/0020-ponytail-level-lite.yml +19 -0
  51. package/modules/ponytail/configs/0030-ponytail-level-ultra.yml +19 -0
  52. package/modules/ponytail/module.yml +172 -0
  53. package/package.json +46 -80
  54. package/templates/20-system-section.yml +4 -1
  55. package/templates/70-subagent-maintenance.yml +1 -1
  56. package/templates/tools/50-delegate-worldbook.yml +1 -1
  57. package/preset/pt-cordis/preset.yml +0 -43
  58. package/preset/pt-cordis/skills/cordis-composition-reference/SKILL.md +0 -29
  59. package/preset/pt-cordis/skills/cordis-composition-reference/references/packages.md +0 -501
  60. package/preset/pt-cordis/skills/cordis-plugin-development/SKILL.md +0 -38
  61. package/preset/pt-cordis/skills/cordis-plugin-development/references/host-plugin.md +0 -60
  62. package/preset/pt-cordis/skills/cordis-plugin-development/references/mcp-bundle.md +0 -5
  63. package/preset/pt-cordis/skills/cordis-plugin-development/references/ui-plugin.md +0 -13
  64. package/preset/pt-cordis/skills/cordis-plugin-development/references/verification.md +0 -5
  65. package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/client.js +0 -22
  66. package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/cordis.patch.yml +0 -3
  67. package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/index.js +0 -2
  68. package/preset/pt-cordis/skills/cordis-plugin-development/templates/decoration/package.json +0 -15
  69. package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/cordis.patch.yml +0 -8
  70. package/preset/pt-cordis/skills/cordis-plugin-development/templates/mcp/package.json +0 -7
  71. package/preset/pt-cordis/skills/editing-cordis-compositions/SKILL.md +0 -84
  72. package/preset/pt-custom/preset.yml +0 -15
  73. package/preset/pt-minimal/preset.yml +0 -27
  74. package/preset/pt-ptc/preset.yml +0 -45
  75. package/preset/pt-standard/preset.yml +0 -43
package/lib/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { $ as ensurePresetSeed, $t as USER_SKILLS_DIR, A as decodeAssetFile, At as createWarnOnce, Bt as ENGINE_CAPABILITIES, C as listCharacterCards, Ct as KNOWN_AUDIENCES, D as removeCharacterFromPreset, Dt as createEpochPromotion, E as recordedCharacterModules, Et as KNOWN_PROMOTIONS, F as stPresetId, Ft as assertPresetId, G as stripConfigFieldSources, Gt as isEngineCapabilityPresent, H as savePresetTriggers, Ht as ENGINE_RECIPES, I as buildWorldBookEntry, It as canonicalPresetRoot, J as asString, Jt as WRITER_PARAM_KEYS, K as appendPresetModules, Kt as validateCustomToolIdentities, L as deleteWorldBookEntry, Lt as presetPathExists, M as convertStToPreset, Mt as PresetLayerSettingsError, N as mergeStPresets, Nt as PRESET_ENGINE_PREFIX, O as requiredCharacterModules, Ot as isSuccessfulCompactionEnd, P as processStText, Pt as assertPresetDirectory, Q as duplicateUserPreset, Qt as DSH_HOME, R as listWorldBookEntries, Rt as DEFAULT_PRESET_ID, S as importCharacterCardFile, St as renderPromptConfigYaml, T as readCharacterMemory, Tt as KNOWN_POSITIONS, U as triggerPromptConfigOptions, Ut as engineCapability, V as readPresetTriggers, Vt as ENGINE_EDITOR_GROUP_MAP, W as readConfigFieldSources, Wt as engineRecipe, X as cloneBuiltinPreset, Xt as validateEngineParamValues, Y as buildModuleConfigsFromParams, Yt as moduleParamFallbacks, Z as createEngineCapabilityInPreset, Zt as DEFAULT_PRESET_DIR, _ as characterModuleStillNeeded, _t as withPresetDoc, a as writePreset, at as openPresetLocation, b as deleteCharacterCard, bt as configFileName, c as lastWorldBookDiagnostics, ct as removePresetModule, d as computePreviewRevision, dt as resolvePresetDir, et as invalidatePresetSpec, f as directoryVersionOf, ft as resolvePresetModuleFacts, g as applyCharacterToPreset, gt as userPresetsDir, h as appendMemoryFile, ht as savePresetPersona, i as presetImportPreview, it as loadPresetSpec, j as prepareImport, jt as sessionEvents, k as syncImportedCharacterMemory, kt as PROMOTE_EVENTS, l as validateCustomTools, lt as removeUserPreset, m as appendCharacterMemory, mt as savePresetParams, n as exportPresetPackage, nt as listPresets, o as confirmDelivered, ot as packageEngineDir, p as CHARACTER_MODULES_KEY, pt as resolvePresetParams, q as applyModuleConfigs, qt as ENGINE_PARAM_KEYS, r as installPresetPackage, rt as loadPresetContent, s as runPreStepBatch, st as removeEngineCapabilityFromPreset, t as expandPresetSource, tt as listBuiltinTemplates, u as safeAssetPath, ut as renderComposition, v as charactersDir, vt as atomicWriteTextFile, w as previewCharacterCard, wt as KNOWN_MODEL_SCOPES, x as importCharacterCard, xt as listPromptConfigSpecs, y as declaredCharacterModules, yt as assertSafeConfigId, z as upsertWorldBookEntry, zt as readPersonaSpec } from "./preset-package-BnJqrU4R.mjs";
2
- import { accessSync, chmodSync, constants, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
1
+ import { $ as setModuleEnabled, $t as moduleParamFallbacks, A as requiredCharacterModules, At as createPromptConfigs, B as listWorldBookEntries, Bt as presetPathExists, C as importCharacterCard, Ct as userModulesDir, D as readCharacterMemory, Dt as configFileName, E as previewCharacterCard, Et as assertSafeConfigId, F as mergeStPresets, Ft as createWarnOnce, G as triggerPromptConfigOptions, Gt as ENGINE_RECIPES, Ht as readPersonaSpec, I as processStText, It as PresetLayerSettingsError, J as readConfigOrder, Jt as isEngineCapabilityPresent, K as ModuleConfigOrderError, Kt as engineCapability, L as stPresetId, Lt as assertModuleDirectory, M as decodeAssetFile, Mt as compareConfigSequence, N as prepareImport, Nt as createEpochPromotion, O as recordedCharacterModules, Ot as listPromptConfigSpecs, P as convertStToPreset, Pt as PROMOTE_EVENTS, Q as resolveEditDir, Qt as WRITER_PARAM_KEYS, R as buildWorldBookEntry, Rt as assertPresetId, S as deleteCharacterCard, St as savePresetPersona, T as listCharacterCards, Tt as atomicWriteTextFile, U as readModuleTriggers, Ut as ENGINE_CAPABILITIES, V as upsertWorldBookEntry, Vt as DEFAULT_MODULE_ID, W as saveModuleTriggers, Wt as ENGINE_EDITOR_GROUP_MAP, X as saveModuleConfigOrder, Xt as ENGINE_PARAM_DEFINITIONS, Y as readModuleConfigOrder, Yt as validateCustomToolIdentities, Z as enabledModuleIds, Zt as ENGINE_PARAM_KEYS, _ as appendMemoryFile, _t as renderComposition, a as materializeModule, an as MODULE_DEFINITION_FILE, at as duplicateUserModule, b as charactersDir, bt as resolvePresetParams, c as confirmDelivered, ct as listBuiltinTemplates, d as validateCustomTools, dt as moduleDirExists, en as validateEngineParamValues, et as appendPresetModules, f as safeAssetPath, ft as openModuleLocation, g as appendCharacterMemory, gt as removeUserPreset, h as CHARACTER_MODULES_KEY, ht as removePresetModule, i as presetImportPreview, in as MODULE_CONFIGS_DIR, it as createEngineCapabilityInPreset, j as syncImportedCharacterMemory, jt as loadPromptConfigFiles, k as removeCharacterFromPreset, kt as renderPromptConfigYaml, l as runPreStepBatch, lt as listModules, m as directoryVersionOf, mt as removeEngineCapabilityFromPreset, n as exportPresetPackage, nn as DSH_HOME, nt as buildModuleConfigsFromParams, o as writePreset, on as USER_SKILLS_DIR, ot as ensurePresetSeed, p as computePreviewRevision, pt as packageEngineDir, q as appendModuleConfigOrder, qt as engineRecipe, r as installPresetPackage, rn as MODULES_DIR, rt as cloneBuiltinPreset, s as applyPromptConfigSources, st as invalidateModuleSpec, t as expandPresetSource, tn as LEGACY_PROMPT_PARAM_KEYS, tt as applyModuleConfigs, u as lastWorldBookDiagnostics, ut as loadModuleSpec, v as applyCharacterToPreset, vt as resolveModuleDir, w as importCharacterCardFile, wt as withPresetDoc, x as declaredCharacterModules, xt as saveModuleParams, y as characterModuleStillNeeded, yt as resolveModuleFacts, z as deleteWorldBookEntry, zt as canonicalPresetRoot } from "./module-package-Cm3fHHlp.mjs";
3
2
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
3
+ import { accessSync, chmodSync, constants, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import { Document, isMap, isScalar, parse, parseDocument, visit } from "yaml";
6
6
  import { homedir } from "node:os";
@@ -12,29 +12,10 @@ import { Transform } from "node:stream";
12
12
  import { pipeline } from "node:stream/promises";
13
13
  import { defineTool } from "@deepseek-ai/dsh-tools";
14
14
  import { FileSystemSkillProvider } from "@deepseek-ai/dsh-skill-filesystem";
15
- import { entryListProblem } from "@deepseek-ai/dsh-agent-preset-registry";
16
15
  //#region src/web-surface.ts
17
16
  /**
18
- * prompt-tool 自愈层:补当前 profile manifest 与本插件相关的装配项。
19
- *
20
- * 官方 profile 组合规则:`dsh plugin add` 只把声明了 `dsh.bundle` 的
21
- * **直接依赖**追加进 `dsh.profile.bundles`,而 `@deepseek-ai/dsh-web-app`
22
- * 是随 dsh 安装自带的 in-box bundle。自定义 profile(如 prompt-tool)
23
- * 首次初始化只含 `@deepseek-ai/dsh-base`,单独执行
24
- * `dsh plugin --profile prompt-tool add link:<本仓库>` 后,本插件等待的
25
- * `webServer` 服务不存在。
26
- *
27
- * 因此:
28
- * 1. 当前 profile 缺 webServer 时,把 web-app 补进当前 profile 的
29
- * bundles(base 之后、本插件之前),重启一次后生效;
30
- * 2. 不再改写同级 `web` / `dsh-tui` profile,也不主动退出进程;
31
- * 只提示用户重启,让官方装配路径在下次启动时读取新 manifest。
32
- *
33
- * 官方 Loader 只会在进程启动时读取 profile 的 bundles 列表,因此本层只
34
- * 持久化 manifest 并提示重启,不做运行中热挂载。所有写入都是幂等的,
35
- * 写前会保留一份 `.bak` 备份。
36
- *
37
- * 失败只降级为日志告警,绝不阻断启动。
17
+ * Web 能力诊断:profile 由官方插件管理器装配,本插件只报告缺失能力。
18
+ * 保留原公开函数名,启动与卸载均不改写 profile 或包链接。
38
19
  */
39
20
  /**
40
21
  * Web surface bundle:DSH 随安装自带的 in-box bundle。
@@ -43,8 +24,6 @@ import { entryListProblem } from "@deepseek-ai/dsh-agent-preset-registry";
43
24
  * 清单看起来像支持该字段,实际无人消费)。
44
25
  */
45
26
  const WEB_APP_BUNDLE = "@deepseek-ai/dsh-web-app";
46
- const BASE_BUNDLE = "@deepseek-ai/dsh-base";
47
- const PLUGIN_BUNDLE = "dsh-plugin-prompt-tool";
48
27
  /** 从 loader 根 Include 的 baseUrl 推导 profile 目录。 */
49
28
  function resolveProfileDir(ctx) {
50
29
  const baseUrl = ctx.baseUrl;
@@ -63,67 +42,25 @@ function readManifest(packagePath, warn) {
63
42
  return;
64
43
  }
65
44
  }
66
- function writeManifest(packagePath, manifest, warn) {
67
- try {
68
- if (existsSync(packagePath)) writeFileSync(`${packagePath}.bak`, readFileSync(packagePath, "utf8"), "utf8");
69
- const tmp = `${packagePath}.tmp-${process.pid}-${Date.now().toString(36)}`;
70
- writeFileSync(tmp, JSON.stringify(manifest, null, 2) + "\n", "utf8");
71
- renameSync(tmp, packagePath);
72
- return true;
73
- } catch (error) {
74
- warn(`prompt-tool: cannot update profile manifest ${packagePath}: ${error instanceof Error ? error.message : String(error)}`);
75
- return false;
76
- }
77
- }
78
- /**
79
- * 把 web-app 补进当前 profile 的 bundles(base 之后、本插件之前)。
80
- * 只用于当前 prompt-tool profile;dsh-tui 不需要 web-app。
81
- */
82
- function ensureCurrentWebAppBundle(manifest) {
83
- const bundles = manifest.dsh?.profile?.bundles;
84
- if (!Array.isArray(bundles)) return "invalid";
85
- if (bundles.includes(WEB_APP_BUNDLE)) return "already";
86
- const pluginIndex = bundles.indexOf(PLUGIN_BUNDLE);
87
- const baseIndex = bundles.indexOf(BASE_BUNDLE);
88
- const insertAt = pluginIndex >= 0 ? pluginIndex : baseIndex >= 0 ? baseIndex + 1 : bundles.length;
89
- bundles.splice(insertAt, 0, WEB_APP_BUNDLE);
90
- return "added";
91
- }
92
- /**
93
- * 每次启动:
94
- * 1. 只处理当前 profile:缺 webServer 时补 web-app 并提示重启;
95
- * 2. 不修改同级 profile,不主动退出进程。
96
- */
45
+ /** 只读诊断;安装与 profile 变更交给官方 plugin_manager。 */
97
46
  function ensureWebSurface(ctx, warn) {
47
+ if (ctx.get("webServer") !== void 0) return;
98
48
  const notify = (message) => {
99
49
  process.stderr.write(message + "\n");
100
50
  warn(message);
101
51
  };
102
52
  const profileDir = resolveProfileDir(ctx);
103
53
  if (profileDir === void 0) {
104
- notify("prompt-tool: cannot locate profile directory to repair bundles; add @deepseek-ai/dsh-web-app to dsh.profile.bundles manually");
54
+ notify(`prompt-tool: Web 服务未就绪,无法定位当前 profile;请通过官方 plugin_manager 检查或安装 ${WEB_APP_BUNDLE}`);
105
55
  return;
106
56
  }
107
- const packagePath = join(profileDir, "package.json");
108
- const manifest = readManifest(packagePath, notify);
109
- if (manifest === void 0) return;
110
57
  const profileName = basename(profileDir);
111
58
  if (profileName === "dsh-tui") return;
112
- if (ctx.get("webServer") !== void 0) return;
113
- const result = ensureCurrentWebAppBundle(manifest);
114
- if (result === "invalid") {
115
- notify(`prompt-tool: profile manifest has no dsh.profile.bundles: ${packagePath}`);
116
- return;
117
- }
118
- if (result === "added" && !writeManifest(packagePath, manifest, notify)) return;
119
- if (result === "added") {
120
- notify(`prompt-tool: auto-added ${WEB_APP_BUNDLE} to dsh.profile.bundles for profile "${profileName}"; next launch will mount the Web surface`);
121
- notify("prompt-tool: please restart the app for the repaired profile to take effect");
122
- }
59
+ const bundles = readManifest(join(profileDir, "package.json"), notify)?.dsh?.profile?.bundles;
60
+ notify(`prompt-tool: profile "${profileName}" 的 Web 服务未就绪${Array.isArray(bundles) && bundles.includes(WEB_APP_BUNDLE) ? "(已声明 Web bundle)" : ""};请通过官方 plugin_manager 检查或安装 ${WEB_APP_BUNDLE}`);
123
61
  }
124
62
  /**
125
- * 把首次启动自愈挂到插件生命周期上:延迟到本轮装配结束后执行,
126
- * 插件卸载会取消计时器,卸载后不再异步补写 profile。
63
+ * 延迟到本轮装配结束后诊断;插件卸载取消尚未执行的任务。
127
64
  */
128
65
  function scheduleWebSurfaceRepair(ctx, warn) {
129
66
  ctx.effect(() => {
@@ -136,7 +73,7 @@ function scheduleWebSurfaceRepair(ctx, warn) {
136
73
  cancelled = true;
137
74
  clearTimeout(timer);
138
75
  };
139
- }, "prompt-tool: deferred web surface repair");
76
+ }, "prompt-tool: deferred web surface diagnosis");
140
77
  }
141
78
  //#endregion
142
79
  //#region src/runtime/models.ts
@@ -373,37 +310,124 @@ function resolveSubagentStartOptions(isEnabled, provider, model, getReasoningEff
373
310
  //#endregion
374
311
  //#region src/shared/param-keys.ts
375
312
  /**
376
- * 引擎行为参数键(按预设存储:激活预设 preset.yml 的 layerSettings + promptConfigs)。
377
- * 不进 Config schema、不进 settings namespace——每预设一份,随预设走(官方范式:
378
- * Config = 部署轴,引擎行为在预设文件)。
313
+ * 引擎行为参数键(按模块存储:激活模块 module.yml 的 layerSettings + promptConfigs)。
314
+ * 不进 Config schema、不进 settings namespace——每模块一份,随模块走(官方范式:
315
+ * Config = 部署轴,引擎行为在 module.yml)。
379
316
  *
380
- * = ENGINE_PARAM_KEYS(唯一权威)+ promptConfigs,不再有旁路键清单。
381
- * 锚定/引导内容键(buildPattern、complexPattern、firstTurnBuild 等)已并入
382
- * ENGINE_PARAM_DEFINITIONS:它们本来就被 writePreset 映射进 near-anchor / router-guide
383
- * 的 promptConfig params,只挂在白名单里会让保存链「白名单接受、值校验拒绝」永久断层。
317
+ * = ENGINE_PARAM_KEYS + 旧规则快捷参数兼容白名单 + promptConfigs。
318
+ * 旧快捷参数只转换到规则实例,不重新进入新编辑器的共享字段目录。
384
319
  * 注意:variables.yml 的占位键(spec.variables 空值登记,供世界书条目 {{key}} 动态
385
320
  * 引用)只读顶层 variables;本集合不再用于推断哪些 params 是模板变量。
386
321
  * 放 shared:config/settings-bridge 共用,单一来源。
387
322
  */
388
323
  const EXTRA_PARAM_KEYS = ["promptConfigs"];
389
- const PARAM_KEYS = /* @__PURE__ */ new Set([...ENGINE_PARAM_KEYS, ...EXTRA_PARAM_KEYS]);
324
+ const PARAM_KEYS = /* @__PURE__ */ new Set([
325
+ ...ENGINE_PARAM_KEYS,
326
+ ...LEGACY_PROMPT_PARAM_KEYS,
327
+ ...EXTRA_PARAM_KEYS
328
+ ]);
390
329
  //#endregion
391
330
  //#region src/config.ts
392
331
  /** 插件配置、settings 数据模型与默认常量(settings 接口层)。 */
393
332
  const NS = "prompt-tool";
394
- const Config = z.object({
395
- writePreset: z.boolean().default(true).volatile(),
396
- presetTemplate: z.string().default(DEFAULT_PRESET_ID).volatile(),
397
- presetOrder: z.natural().default(5).volatile(),
398
- fallbackText: z.string().default("").volatile()
399
- });
400
- const PromptSettingsSchema = z.object({
401
- modelsAvailable: z.boolean().default(true),
402
- presetOrder: z.natural().default(5),
403
- fallbackText: z.string().default(""),
404
- writePreset: z.boolean().default(true),
405
- presetTemplate: z.string().default(DEFAULT_PRESET_ID)
406
- });
333
+ const Config = z.object({ writePreset: z.boolean().default(true).volatile() });
334
+ //#endregion
335
+ //#region src/shared/managed-config-fields.ts
336
+ /** writer 可投影的字段白名单;是否确由参数投影由 ConfigFieldSources 决定。 */
337
+ const MANAGED_CONFIG_FIELDS = [{
338
+ configId: "near-anchor",
339
+ fields: [
340
+ {
341
+ path: "enabled",
342
+ sourceParam: "firstTurnAnchor"
343
+ },
344
+ {
345
+ path: "params.useCustom",
346
+ sourceParam: "firstTurnCustom"
347
+ },
348
+ {
349
+ path: "params.text",
350
+ sourceParam: "firstTurnText"
351
+ },
352
+ {
353
+ path: "params.buildPattern",
354
+ sourceParam: "buildPattern"
355
+ },
356
+ {
357
+ path: "params.complexPattern",
358
+ sourceParam: "complexPattern"
359
+ },
360
+ {
361
+ path: "params.firstTurnBuild",
362
+ sourceParam: "firstTurnBuild"
363
+ },
364
+ {
365
+ path: "params.firstTurnInspect",
366
+ sourceParam: "firstTurnInspect"
367
+ },
368
+ {
369
+ path: "params.firstTurnDeep",
370
+ sourceParam: "firstTurnDeep"
371
+ }
372
+ ]
373
+ }, {
374
+ configId: "router-guide",
375
+ fields: [
376
+ {
377
+ path: "enabled",
378
+ sourceParam: "guideEnabled"
379
+ },
380
+ {
381
+ path: "modelScope",
382
+ sourceParam: "guideCustom",
383
+ derived: true,
384
+ fallbackNote: "followsCustom"
385
+ },
386
+ {
387
+ path: "params.useCustom",
388
+ sourceParam: "guideCustom",
389
+ derived: true
390
+ },
391
+ {
392
+ path: "params.text",
393
+ sourceParam: "guideText"
394
+ },
395
+ {
396
+ path: "params.complexPattern",
397
+ sourceParam: "complexPattern"
398
+ },
399
+ {
400
+ path: "params.guideWeak",
401
+ sourceParam: "guideWeak"
402
+ },
403
+ {
404
+ path: "params.guideDeep",
405
+ sourceParam: "guideDeep"
406
+ }
407
+ ]
408
+ }];
409
+ /** 严格投影白名单;未知路径、参数键和附加属性都不进入客户端。 */
410
+ function readConfigFieldSources(configId, raw) {
411
+ const spec = MANAGED_CONFIG_FIELDS.find((entry) => entry.configId === configId);
412
+ if (spec === void 0 || raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
413
+ const record = raw;
414
+ if (record.configId !== configId || !Array.isArray(record.fields)) return void 0;
415
+ return {
416
+ configId,
417
+ fields: spec.fields.flatMap(({ path }) => {
418
+ const source = record.fields.find((item) => item !== null && typeof item === "object" && item.path === path)?.source;
419
+ return source === "preset-param" || source === "prompt-config" ? [{
420
+ path,
421
+ source
422
+ }] : [];
423
+ })
424
+ };
425
+ }
426
+ /** 来源只用于读回;无论客户端传了什么来源,都不能写入模块定义。 */
427
+ function stripConfigFieldSources(config) {
428
+ const { fieldSources: _fieldSources, sequence: _sequence, ...definition } = config;
429
+ return definition;
430
+ }
407
431
  //#endregion
408
432
  //#region src/shared/official-orders.ts
409
433
  /** section 侧 6 组,覆盖官方 32 个档位名(顺序即官方数值升序)。 */
@@ -564,7 +588,7 @@ async function validatePromptConfigs(value, options = {}) {
564
588
  const { createPromptConfigs } = await import(pathToFileURL(join(packageEngineDir(), "prompt-config-engine.mjs")).href);
565
589
  const engineOptions = {
566
590
  ...options.strategyDir ? { strategyDir: options.strategyDir } : {},
567
- ...options.presetDir ? { templateBaseUrl: pathToFileURL(join(dirname(options.presetDir), ".engine", "schema.mjs")).href } : {}
591
+ ...options.moduleDir ? { templateBaseUrl: pathToFileURL(join(dirname(options.moduleDir), ".engine", "schema.mjs")).href } : {}
568
592
  };
569
593
  const files = [];
570
594
  for (let index = 0; index < specs.length; index += 1) {
@@ -1359,10 +1383,12 @@ const BRIDGE_ENDPOINTS = {
1359
1383
  assetUpload: "/asset-upload",
1360
1384
  assetRelease: "/asset-release",
1361
1385
  exportPreset: "/export-preset",
1362
- presetDelete: "/preset-delete",
1363
- presetClone: "/preset-clone",
1364
- presetDuplicate: "/preset-duplicate",
1365
- presetOpen: "/preset-open",
1386
+ moduleDelete: "/module-delete",
1387
+ moduleClone: "/module-clone",
1388
+ moduleDuplicate: "/module-duplicate",
1389
+ moduleOpen: "/module-open",
1390
+ moduleEnable: "/module-enable",
1391
+ moduleConfigOrder: "/module-config-order",
1366
1392
  charactersImport: "/characters-import",
1367
1393
  charactersImportStream: "/characters-import-stream",
1368
1394
  charactersList: "/characters-list",
@@ -1375,6 +1401,15 @@ const BRIDGE_ENDPOINTS = {
1375
1401
  engineCapability: "/engine-capability",
1376
1402
  worldBookDiagnostics: "/world-book-diagnostics"
1377
1403
  };
1404
+ /** 文件已保存,但宿主未采用新定义;调用方保留草稿并显示失败,不宣称已生效。 */
1405
+ const PRESET_ACTIVATION_FAILED = "preset-activation-failed";
1406
+ const MODULE_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
1407
+ /** 读编辑目标:形状不合法(空、超长、含非法字符)一律当「未声明」,由 host 回退启用表首项。 */
1408
+ function readEditTarget(value) {
1409
+ if (typeof value !== "string") return void 0;
1410
+ const id = value.trim();
1411
+ return id.length > 0 && id.length <= 64 && MODULE_ID_PATTERN.test(id) ? id : void 0;
1412
+ }
1378
1413
  //#endregion
1379
1414
  //#region src/host/skills-policy.ts
1380
1415
  /** 技能调用策略写入:唯一真相是技能文件自己的 frontmatter。
@@ -1744,7 +1779,7 @@ function withSkillWinners(entries, resolved) {
1744
1779
  * 只放两端都要理解的形状:读取快照、工作区上下文与写入结果;
1745
1780
  * 引擎侧的位置/晋升/受众算法不在这里复制。
1746
1781
  */
1747
- /** 单个指令文件正文上限(与官方指令行的 maxBytes 基线对齐;读写限制一致)。 */
1782
+ /** 文件卡正文读写上限;官方注入的读取与渲染预算由官方配置决定。 */
1748
1783
  const MAX_INSTRUCTION_FILE_BYTES = 65536;
1749
1784
  const INSTRUCTION_FILE_STATUSES = [
1750
1785
  "ready",
@@ -1756,12 +1791,10 @@ const INSTRUCTION_FILE_STATUSES = [
1756
1791
  //#region src/host/agents-cards.ts
1757
1792
  /**
1758
1793
  * AGENTS 指令文件探测与文件卡合成。
1759
- * 文件即真相:探测到的 AGENTS.md 不写进 preset.yml,只作为生成目录的 pre-step 卡;
1760
- * 卡内编辑框经 bridge 直接读写该文件;注入侧运行时读该文件正文
1761
- * (fill=instruction-file),文件缺失或空内容就不注入。
1794
+ * 文件卡只作编辑视图,不写进 preset.yml 或生成目录;正文经 bridge 写回原文件。
1795
+ * 官方负责注入,运行时只复用本模块的文件身份映射应用逐文件开关。
1762
1796
  *
1763
- * 本模块是文件身份、读取快照与读写校验的唯一来源:UI 与运行时都走同一套规则,
1764
- * 不各自实现一套转换。读取失败与「读取成功的空文件」严格区分。
1797
+ * 本模块拥有本地卡片的文件身份、读取快照与读写校验;读取失败与空文件严格区分。
1765
1798
  */
1766
1799
  /** 项目目录链候选文件(与 engine/instruction-hint.mjs 的探测候选同源)。 */
1767
1800
  const AGENTS_PROJECT_CANDIDATES = [
@@ -1805,6 +1838,17 @@ function findProjectRoot(cwd) {
1805
1838
  current = parent;
1806
1839
  }
1807
1840
  }
1841
+ /** 官方展示路径 → 既有卡片身份;只解析路径,不读取正文,也不构成写入授权。 */
1842
+ function instructionFileIdFromDisplayPath(displayPath, projectRoot, home = DSH_HOME) {
1843
+ const global = displayPath === "~/.dsh/AGENTS.md" || displayPath === "$DSH_HOME/AGENTS.md";
1844
+ if (!global && (projectRoot === void 0 || isAbsolute(displayPath) || displayPath.includes("\0"))) return void 0;
1845
+ const root = global ? resolve(home) : resolve(projectRoot);
1846
+ const path = global ? join(root, AGENTS_USER_GLOBAL_FILE) : resolve(root, displayPath);
1847
+ if (!within$1(root, path)) return void 0;
1848
+ const realPath = realPathOf(path) ?? path;
1849
+ if (!within$1(realPathOf(root) ?? root, realPath)) return void 0;
1850
+ return agentsFileId(realPath);
1851
+ }
1808
1852
  /** 项目根 → cwd 的目录链(broad→specific)。 */
1809
1853
  function ancestorChain(root, cwd) {
1810
1854
  const stop = resolve(root);
@@ -2043,98 +2087,48 @@ async function writeAgentsFileChecked(options) {
2043
2087
  //#endregion
2044
2088
  //#region src/host/instructions-policy.ts
2045
2089
  /**
2046
- * 指令文件卡策略:独立于预设与 settings 的本插件自有状态。
2090
+ * 指令文件卡策略:独立于模块与 settings 的本插件自有状态。
2047
2091
  *
2048
- * 位置:`<DSH_HOME>/.prompt-tool/instructions.yml`(同一 DSH_HOME 下的所有预设共享)。
2092
+ * 位置:`<DSH_HOME>/.prompt-tool/instructions.yml`(同一 DSH_HOME 下的所有模块共享)。
2049
2093
  * 只承载行为开关与展示名:正文永远在用户的原文件里,策略文件不存正文、读取版本、
2050
2094
  * 会话 ID 或任意客户端路径——路径与文件身份由服务端探测结果解析。
2051
2095
  *
2052
- * 缺省 `enabled: false`:独立来源必须先完成负责人切换(预设去掉官方指令行),再由用户
2053
- * 在工作台显式开启;键缺失 = 关闭,只有显式 `enabled: true` 才参战。不从各预设的旧
2054
- * agentsHints 推导;文件缺失时用默认值,不因读取自动创建。
2096
+ * 缺省放行官方注入;只有逐文件 enabled:false 才拦截后续内容。旧独立来源总开关与
2097
+ * 注入参数已退役,读取时忽略,下一次保存清理;文件缺失不因读取自动创建。
2055
2098
  * 写入使用 yaml Document API 保留注释与未知字段;解析失败或 schemaVersion 不认识时
2056
2099
  * 拒绝写入(不把损坏文件当空配置覆盖)。
2057
2100
  */
2058
2101
  /** 相对 DSH_HOME 的策略文件位置(唯一权威定义)。 */
2059
2102
  const INSTRUCTIONS_POLICY_RELATIVE = join(".prompt-tool", "instructions.yml");
2060
- const DEFAULT_VALUES = {
2061
- order: 30,
2062
- position: "after-user",
2063
- promotion: "none",
2064
- audience: null,
2065
- modelScope: "all"
2066
- };
2067
- const VALUE_KEYS = [
2103
+ const RETIRED_FILE_KEYS = [
2068
2104
  "order",
2069
2105
  "position",
2070
2106
  "promotion",
2071
2107
  "audience",
2072
2108
  "modelScope"
2073
2109
  ];
2074
- const OVERRIDE_KEYS = [
2075
- ...VALUE_KEYS,
2076
- "enabled",
2077
- "name"
2078
- ];
2110
+ const OVERRIDE_KEYS = ["enabled", "name"];
2079
2111
  function defaultInstructionPolicy() {
2080
- return {
2081
- enabled: false,
2082
- defaults: { ...DEFAULT_VALUES },
2083
- files: {}
2084
- };
2112
+ return { files: {} };
2085
2113
  }
2086
2114
  function instructionPolicyPath(dshHome = DSH_HOME) {
2087
2115
  return join(dshHome, INSTRUCTIONS_POLICY_RELATIVE);
2088
2116
  }
2089
- const isRecord$2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
2117
+ const isRecord$3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
2090
2118
  const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
2091
- /** 校验单个字段值;返回错误消息或 undefined。 */
2092
- function valueError(key, value) {
2093
- if (key === "order") return typeof value === "number" && Number.isFinite(value) && Number.isSafeInteger(value) && value >= 0 ? void 0 : "order 必须是非负安全整数";
2094
- if (key === "audience") return value === null || typeof value === "string" && KNOWN_AUDIENCES.has(value) ? void 0 : "audience 必须是 null/main/subagent";
2095
- if (typeof value !== "string") return `${key} 必须是字符串`;
2096
- return (key === "position" ? KNOWN_POSITIONS : key === "promotion" ? KNOWN_PROMOTIONS : KNOWN_MODEL_SCOPES).has(value) ? void 0 : `${key} 取值非法:${value}`;
2097
- }
2098
2119
  /** 请求体白名单校验:未知键一律拒绝,避免正文/路径/版本混进策略文件。 */
2099
2120
  function validateInstructionPolicyPatch(input) {
2100
- if (!isRecord$2(input)) return {
2121
+ if (!isRecord$3(input)) return {
2101
2122
  ok: false,
2102
2123
  message: "策略载荷必须是对象"
2103
2124
  };
2104
- for (const key of Object.keys(input)) if (key !== "enabled" && key !== "defaults" && key !== "files") return {
2125
+ for (const key of Object.keys(input)) if (key !== "files") return {
2105
2126
  ok: false,
2106
2127
  message: `未知字段:${key}`
2107
2128
  };
2108
2129
  const patch = {};
2109
- if (input.enabled !== void 0) {
2110
- if (typeof input.enabled !== "boolean") return {
2111
- ok: false,
2112
- message: "enabled 必须是布尔值"
2113
- };
2114
- patch.enabled = input.enabled;
2115
- }
2116
- if (input.defaults !== void 0) {
2117
- if (!isRecord$2(input.defaults)) return {
2118
- ok: false,
2119
- message: "defaults 必须是对象"
2120
- };
2121
- const defaults = {};
2122
- for (const [key, value] of Object.entries(input.defaults)) {
2123
- if (!VALUE_KEYS.includes(key)) return {
2124
- ok: false,
2125
- message: `defaults 未知字段:${key}`
2126
- };
2127
- const error = valueError(key, value);
2128
- if (error !== void 0) return {
2129
- ok: false,
2130
- message: error
2131
- };
2132
- defaults[key] = value;
2133
- }
2134
- patch.defaults = defaults;
2135
- }
2136
2130
  if (input.files !== void 0) {
2137
- if (!isRecord$2(input.files)) return {
2131
+ if (!isRecord$3(input.files)) return {
2138
2132
  ok: false,
2139
2133
  message: "files 必须是对象"
2140
2134
  };
@@ -2148,7 +2142,7 @@ function validateInstructionPolicyPatch(input) {
2148
2142
  files[fileId] = null;
2149
2143
  continue;
2150
2144
  }
2151
- if (!isRecord$2(raw)) return {
2145
+ if (!isRecord$3(raw)) return {
2152
2146
  ok: false,
2153
2147
  message: `files.${fileId} 必须是对象或 null`
2154
2148
  };
@@ -2174,12 +2168,6 @@ function validateInstructionPolicyPatch(input) {
2174
2168
  override.name = value;
2175
2169
  continue;
2176
2170
  }
2177
- const error = valueError(key, value);
2178
- if (error !== void 0) return {
2179
- ok: false,
2180
- message: `files.${fileId}.${error}`
2181
- };
2182
- override[key] = value;
2183
2171
  }
2184
2172
  files[fileId] = override;
2185
2173
  }
@@ -2237,7 +2225,7 @@ function readInstructionPolicy(file = instructionPolicyPath()) {
2237
2225
  error: `指令策略不是合法 YAML:${error instanceof Error ? error.message : String(error)}`
2238
2226
  };
2239
2227
  }
2240
- if (!isRecord$2(data)) return {
2228
+ if (!isRecord$3(data)) return {
2241
2229
  ...empty,
2242
2230
  exists: true,
2243
2231
  revision,
@@ -2250,11 +2238,7 @@ function readInstructionPolicy(file = instructionPolicyPath()) {
2250
2238
  revision,
2251
2239
  error: `指令策略 schemaVersion 不受支持:${String(version)}`
2252
2240
  };
2253
- const validated = validateInstructionPolicyPatch({
2254
- ...data.enabled === void 0 ? {} : { enabled: data.enabled },
2255
- ...data.defaults === void 0 ? {} : { defaults: data.defaults },
2256
- ...data.files === void 0 ? {} : { files: data.files }
2257
- });
2241
+ const validated = validateInstructionPolicyPatch(data.files === void 0 ? {} : { files: isRecord$3(data.files) ? Object.fromEntries(Object.entries(data.files).map(([id, value]) => [id, isRecord$3(value) ? Object.fromEntries(Object.entries(value).filter(([key]) => !RETIRED_FILE_KEYS.includes(key))) : value])) : data.files });
2258
2242
  if (!validated.ok) return {
2259
2243
  ...empty,
2260
2244
  exists: true,
@@ -2263,33 +2247,25 @@ function readInstructionPolicy(file = instructionPolicyPath()) {
2263
2247
  };
2264
2248
  const patch = validated.patch;
2265
2249
  return {
2266
- policy: {
2267
- enabled: patch.enabled ?? false,
2268
- defaults: {
2269
- ...DEFAULT_VALUES,
2270
- ...patch.defaults
2271
- },
2272
- files: Object.fromEntries(Object.entries(patch.files ?? {}).flatMap(([fileId, override]) => override === null ? [] : [[fileId, override]]))
2273
- },
2250
+ policy: { files: Object.fromEntries(Object.entries(patch.files ?? {}).flatMap(([fileId, override]) => override === null ? [] : [[fileId, override]])) },
2274
2251
  revision,
2275
2252
  exists: true,
2276
2253
  path: file
2277
2254
  };
2278
2255
  }
2279
- /** 单文件的有效策略:部署级开关优先于每文件开关。 */
2256
+ /** 单文件的有效策略:未配置时放行官方注入。 */
2280
2257
  function resolveInstructionPolicy(policy, fileId) {
2281
2258
  const override = policy.files[fileId] ?? {};
2282
2259
  return {
2283
- ...policy.defaults,
2284
2260
  ...override,
2285
- enabled: policy.enabled && override.enabled !== false
2261
+ enabled: override.enabled !== false
2286
2262
  };
2287
2263
  }
2288
2264
  /** 写盘用文档:保留注释与未知字段。 */
2289
2265
  function documentFor(file) {
2290
2266
  if (!existsSync(file)) {
2291
2267
  const doc = new Document({});
2292
- doc.commentBefore = " prompt-tool 指令文件卡策略(独立于预设与 settings)\n enabled: 文件来源是否参与注入(缺省 false,先完成负责人切换再开启)\n defaults: 未单独覆盖的文件使用的行为;files: 按 fileId 的覆盖\n 这里不放正文:正文始终在用户自己的 AGENTS.md/CLAUDE.md 里";
2268
+ doc.commentBefore = " prompt-tool 指令文件卡策略(独立于预设与 settings)\n files: 按 fileId 保存开关和显示名;enabled:false 只拦截后续官方注入\n 这里不放正文:正文始终在用户自己的 AGENTS.md/CLAUDE.md 里";
2293
2269
  doc.set("schemaVersion", 1);
2294
2270
  return { doc };
2295
2271
  }
@@ -2346,19 +2322,28 @@ function writeInstructionPolicy(options) {
2346
2322
  };
2347
2323
  const { doc } = prepared;
2348
2324
  const patch = validated.patch;
2349
- if (patch.enabled !== void 0) {
2350
- if (patch.enabled) doc.set("enabled", true);
2351
- else doc.delete("enabled");
2352
- }
2353
- if (patch.defaults !== void 0) {
2354
- for (const key of VALUE_KEYS) {
2355
- const value = patch.defaults[key];
2356
- if (value === void 0) continue;
2357
- if (value === DEFAULT_VALUES[key]) doc.deleteIn(["defaults", key]);
2358
- else doc.setIn(["defaults", key], value);
2359
- }
2360
- const defaults = doc.get("defaults");
2361
- if (isMap(defaults) && defaults.items.length === 0) doc.delete("defaults");
2325
+ const existingFiles = doc.get("files");
2326
+ const retiredMaps = new Set(isMap(existingFiles) ? existingFiles.items.flatMap((item) => {
2327
+ const value = item.value;
2328
+ return isMap(value) && RETIRED_FILE_KEYS.some((key) => value.has(key)) ? [value] : [];
2329
+ }) : []);
2330
+ let sharedRetired = false;
2331
+ visit(doc, { Alias(_key, alias) {
2332
+ const target = alias.resolve(doc);
2333
+ if (target === void 0 || !retiredMaps.has(target)) return;
2334
+ sharedRetired = true;
2335
+ return visit.BREAK;
2336
+ } });
2337
+ if (sharedRetired) return {
2338
+ ok: false,
2339
+ status: 400,
2340
+ code: "instructions-policy-invalid",
2341
+ message: "退役指令字段被 YAML alias 共享,无法安全清理;请先解除引用"
2342
+ };
2343
+ doc.delete("enabled");
2344
+ doc.delete("defaults");
2345
+ if (isMap(existingFiles)) {
2346
+ for (const item of existingFiles.items) if (isMap(item.value)) for (const key of RETIRED_FILE_KEYS) item.value.delete(key);
2362
2347
  }
2363
2348
  if (patch.files !== void 0) {
2364
2349
  if (!isMap(doc.get("files"))) doc.setIn(["files"], doc.createNode({}));
@@ -2437,147 +2422,101 @@ function writeInstructionPolicy(options) {
2437
2422
  };
2438
2423
  }
2439
2424
  //#endregion
2440
- //#region src/runtime/pre-step-coordinator.ts
2441
- /**
2442
- * pre-step 协调器:插件侧的薄装配层,用一个 pre-step 监听器统一执行两类来源。
2443
- *
2444
- * - 预设来源:各预设 mount 的引擎行(prompt-config-engine)把配置注册到本服务,
2445
- * 作用域与释放仍由注册 ctx(dsh-scope)决定——父/子 scope 继承、兄弟 scope 互不串。
2446
- * 同一 scope 内同名来源再次注册(宿主重挂同一个 preset、HMR 时序下旧 fiber 尚未释放)
2447
- * 时由**后来者接管**:先撤旧登记再插新,绝不让引擎行因重名而挂载失败;
2448
- * - 独立指令文件来源:本模块按会话工作区现场探测文件、读取独立策略
2449
- * (`$DSH_HOME/.prompt-tool/instructions.yml`)并编译成同样的提示词配置。
2450
- *
2451
- * 两类来源共用 engine/executor.mjs#runPreStepBatch 这一条批执行算法;本模块只负责
2452
- * 「谁参战」与「参战顺序」:预设来源保留声明顺序,文件卡按全局→项目根→cwd 的探测
2453
- * 顺序追加,最终仍按 order 排序、同值保持声明顺序。
2454
- *
2455
- * 文件版本可见状态(F4):候选资格 = 文件可读 + 策略启用 + 该 (fileId, revision, epoch)
2456
- * 身份在当前可见上下文里还没有同版本消息。可见性以 `session.deriveMessages()`(当前
2457
- * 模型可见面)为准,缺失时回退「持久日志里最后一次成功 compaction/end 之后的消息」;
2458
- * 因此压缩成功后同版本会重新注入一次,失败压缩不推进 epoch、不重复注入,重挂/恢复
2459
- * 直接从持久记录重建,不依赖进程内已投递集合。
2460
- *
2461
- * 负责人冲突:本 mount 的组合仍挂着官方指令加载行(`@deepseek-ai/dsh-agent-instructions`)
2462
- * 时,独立文件来源整体不参战——同一正文只由一方注入,不同时执行再靠文案去重。
2463
- */
2464
- /** 协调服务名:引擎行通过 ctx.get 解析本服务决定走管理路径还是独立路径。 */
2465
- const PRE_STEP_COORDINATOR_SERVICE = "promptToolPreStep";
2466
- /** 服务契约版本:引擎与协调器不同步时按「不认识就不接管」处理。 */
2467
- const PRE_STEP_COORDINATOR_VERSION = 1;
2468
- const WARN_LABEL = "prompt-tool:pre-step-coordinator";
2469
- const isRecord$1 = (value) => value !== null && typeof value === "object";
2470
- /** 会话工作区:来自该会话自己的 header.cwd;缺失不拿进程 cwd 兜底。 */
2471
- function sessionCwd(session) {
2472
- const cwd = isRecord$1(session) && isRecord$1(session.header) ? session.header.cwd : void 0;
2473
- return typeof cwd === "string" && cwd.length > 0 ? cwd : void 0;
2474
- }
2475
- /**
2476
- * 当前模型可见面的消息。优先 `deriveMessages()`(压缩投影后的真相);缺失时回退
2477
- * 持久日志里最后一次成功 compaction/end 之后的消息。
2478
- */
2479
- function visibleMessages(session) {
2480
- if (isRecord$1(session) && typeof session.deriveMessages === "function") try {
2481
- const list = session.deriveMessages();
2482
- if (Array.isArray(list)) return list;
2483
- } catch {}
2484
- const events = sessionEvents(session);
2485
- let boundary = -1;
2486
- for (const event of events) if (isSuccessfulCompactionEnd(event)) boundary = event?.seq ?? 0;
2487
- const out = [];
2488
- for (const event of events) {
2489
- if ((event?.seq ?? 0) <= boundary) continue;
2490
- const data = event?.data;
2491
- if (isRecord$1(data) && isRecord$1(data.source)) out.push(data);
2492
- if (isRecord$1(data) && isRecord$1(data.message)) out.push(data.message);
2493
- if (isRecord$1(data) && Array.isArray(data.messages)) out.push(...data.messages);
2494
- if (isRecord$1(event) && isRecord$1(event.message)) out.push(event.message);
2495
- }
2496
- return out;
2497
- }
2498
- const FILE_IDENTITY_PREFIX = "instruction-file:";
2499
- /** 记住的上次探测文件数上限(超限整体清空,只影响失效通知的展示路径)。 */
2500
- const MAX_TRACKED_FILES = 256;
2501
- /** 负责人事实按会话记录的条数上限(超限整体清空,下一次 pre-step 重新填充)。 */
2502
- const MAX_TRACKED_OWNER_SESSIONS = 4096;
2425
+ //#region src/runtime/official-instruction-filter.ts
2426
+ /** 官方待注入消息的逐文件过滤;不读取或改写会话历史。 */
2427
+ const isRecord$2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
2428
+ const actionForHeading = (heading) => heading === "Updated instructions from:" ? "replace" : heading === "Instructions removed:" ? "remove" : "set";
2503
2429
  /**
2504
- * 旧版 writePreset 在生成目录里物化的文件卡(id `agents-file-*` / sourceKind
2505
- * `instruction-file`)。独立来源接管后它们不再参战:既有用户目录在重新物化前
2506
- * 仍会带着这些卡,否则同一份正文会被注入两次。
2430
+ * 路径来自 source.changes;标题仅用于定位正文,不用于恢复官方加载状态。
2431
+ * 重复标题、未知包装或来源与段落不一致时原样放行,避免误删其他文件。
2507
2432
  */
2508
- function isLegacyFileCard(config) {
2509
- return config.id.startsWith("agents-file-") || config.sourceKind === "instruction-file";
2510
- }
2511
- /** 当前可见面里已存在的文件来源身份(只收本插件命名的身份)。 */
2512
- function visibleFileIdentities(session) {
2513
- const seen = /* @__PURE__ */ new Set();
2514
- for (const message of visibleMessages(session)) {
2515
- const plugin = isRecord$1(message) && isRecord$1(message.source) ? message.source.plugin : void 0;
2516
- if (typeof plugin === "string" && plugin.startsWith(FILE_IDENTITY_PREFIX)) seen.add(plugin);
2517
- }
2518
- return seen;
2519
- }
2520
- /** 文件来源身份:fileId + 内容版本 + 当前 surface epoch 编码进 plugin 命名空间。 */
2521
- function fileIdentity(file, epoch) {
2522
- if (!file.available) return `${FILE_IDENTITY_PREFIX}${file.fileId}:unavailable:e${Math.max(epoch, 0)}`;
2523
- return `${FILE_IDENTITY_PREFIX}${file.fileId}:${file.revision.slice(0, 16)}:e${Math.max(epoch, 0)}`;
2524
- }
2525
- /** 某一身份是否属于某个 fileId(含历史版本与失效通知)。 */
2526
- function identityFileId(identity) {
2527
- return identity.slice(17).split(":")[0] ?? "";
2528
- }
2529
- function messageIdOf(identity) {
2530
- return identity.replaceAll(":", "-");
2531
- }
2532
- /** 文件候选 → 批执行算法可消费的提示词配置(正文 literal,不经预设变量插值)。 */
2533
- function fileCardConfig(file, identity) {
2534
- return cardConfig(file, identity, `Instructions from: ${file.displayPath}\n\n${file.text}`);
2535
- }
2536
- /** 失效通知:正文已消失,历史和旧版本仍在会话日志里,不谎称已撤回。 */
2537
- function unavailableCardConfig(file, identity) {
2538
- const reason = file.reason ?? "文件当前不可读或为空";
2539
- return cardConfig(file, identity, `Instructions from: ${file.displayPath} are no longer available (${reason}). The previously injected content is stale; do not rely on it.`);
2540
- }
2541
- function cardConfig(file, identity, text) {
2542
- return {
2543
- id: `agents-file-${file.fileId}`,
2544
- layer: "pre-step",
2545
- enabled: true,
2546
- order: file.order,
2547
- position: file.position,
2548
- promotion: file.promotion,
2549
- audience: file.audience,
2550
- modelScope: file.modelScope,
2551
- role: "user",
2552
- dedupe: "none",
2553
- mergeMode: "separate",
2554
- sourceKind: "instruction-file",
2555
- form: "instructions",
2556
- texts: [],
2557
- params: {},
2558
- variables: {},
2559
- identity: {
2560
- field: "plugin",
2561
- value: identity
2562
- },
2563
- resolve: () => ({
2564
- id: messageIdOf(identity),
2565
- content: [{
2566
- type: "text",
2567
- text
2568
- }],
2433
+ function filterOfficialInstructionMessages(messages, isDisabled) {
2434
+ const diagnostics = [];
2435
+ let changed = false;
2436
+ const filtered = messages.flatMap((message) => {
2437
+ if (!isRecord$2(message) || !isRecord$2(message.source) || message.source.kind !== "agent-instructions") return [message];
2438
+ const source = message.source;
2439
+ if (!Array.isArray(source.changes)) return [message];
2440
+ const changes = source.changes;
2441
+ const disabled = new Set(changes.flatMap((change) => isRecord$2(change) && typeof change.path === "string" && isDisabled(change.path, source) ? [change.path] : []));
2442
+ if (disabled.size === 0) return [message];
2443
+ const keepOriginal = () => {
2444
+ diagnostics.push(`无法可靠分段官方指令消息 ${String(message.id ?? "")},已原样放行`);
2445
+ return [message];
2446
+ };
2447
+ if (!changes.every((change) => isRecord$2(change) && typeof change.path === "string" && [
2448
+ "set",
2449
+ "replace",
2450
+ "remove"
2451
+ ].includes(change.action)) || !Array.isArray(message.content)) return keepOriginal();
2452
+ const entries = changes;
2453
+ if (source.baseline === true && entries.some((entry) => entry.action === "remove")) return keepOriginal();
2454
+ const counts = /* @__PURE__ */ new Map();
2455
+ const keyOf = (path, action) => `${action}\0${path}`;
2456
+ for (const entry of entries) {
2457
+ const key = keyOf(entry.path, entry.action);
2458
+ counts.set(key, (counts.get(key) ?? 0) + 1);
2459
+ }
2460
+ const paths = new Set(entries.map((entry) => entry.path));
2461
+ const observed = /* @__PURE__ */ new Map();
2462
+ const content = [];
2463
+ for (const block of message.content) {
2464
+ if (!isRecord$2(block) || block.type !== "text" || typeof block.text !== "string") return keepOriginal();
2465
+ const text = block.text;
2466
+ const close = text.lastIndexOf("\n</system-reminder>");
2467
+ if (!text.startsWith("<system-reminder>\n") || close < 0 || text.slice(close) !== "\n</system-reminder>" || text.indexOf("</system-reminder>") !== close + 1) return keepOriginal();
2468
+ const headings = [...text.matchAll(/^(Instructions from:|Additional instructions from:|Updated instructions from:|Instructions removed:) ([^\r\n]+)\r?$/gm)].filter((match) => paths.has(match[2]));
2469
+ for (const heading of headings) {
2470
+ const key = keyOf(heading[2], actionForHeading(heading[1]));
2471
+ const count = (observed.get(key) ?? 0) + 1;
2472
+ if (count > (counts.get(key) ?? 0)) return keepOriginal();
2473
+ observed.set(key, count);
2474
+ }
2475
+ if (!headings.some((heading) => disabled.has(heading[2]))) {
2476
+ content.push(block);
2477
+ continue;
2478
+ }
2479
+ const kept = [];
2480
+ for (const [index, heading] of headings.entries()) {
2481
+ const next = headings[index + 1]?.index;
2482
+ if (next !== void 0 && text.slice(next - 2, next) !== "\n\n") return keepOriginal();
2483
+ if (!disabled.has(heading[2])) kept.push(text.slice(heading.index, next === void 0 ? close : next - 2));
2484
+ }
2485
+ if (kept.length > 0) {
2486
+ const body = text.slice(0, headings[0].index) + kept.join("\n\n");
2487
+ content.push({
2488
+ ...block,
2489
+ text: body + text.slice(close)
2490
+ });
2491
+ }
2492
+ }
2493
+ for (const [key, count] of counts) if (observed.get(key) !== count) return keepOriginal();
2494
+ const remaining = entries.filter((entry) => !disabled.has(entry.path));
2495
+ changed = true;
2496
+ if (remaining.length === 0) return [];
2497
+ return [{
2498
+ ...message,
2499
+ content,
2569
2500
  source: {
2570
- kind: "instruction-file",
2571
- plugin: identity,
2572
- form: "instructions"
2501
+ ...source,
2502
+ changes: remaining
2573
2503
  }
2574
- })
2504
+ }];
2505
+ });
2506
+ return {
2507
+ messages: changed ? filtered : messages,
2508
+ diagnostics
2575
2509
  };
2576
2510
  }
2577
- /**
2578
- * 独立策略解析(进程内按 mtime+size 缓存):策略缺失或损坏都返回 undefined,
2579
- * 即「不参战」——不能把损坏的策略文件当成空配置继续注入。
2580
- */
2511
+ //#endregion
2512
+ //#region src/runtime/pre-step-coordinator.ts
2513
+ /** 模块 pre-step 批执行与官方指令逐文件过滤;正文发现、读取和重注入归官方所有。 */
2514
+ const PRE_STEP_COORDINATOR_SERVICE = "promptToolPreStep";
2515
+ const PRE_STEP_COORDINATOR_VERSION = 1;
2516
+ const WARN_LABEL = "prompt-tool:pre-step-coordinator";
2517
+ const MAX_TRACKED_OWNER_SESSIONS = 4096;
2518
+ const isRecord$1 = (value) => value !== null && typeof value === "object";
2519
+ /** 文件修改时重读;损坏策略不猜,报告诊断并保留官方原消息。 */
2581
2520
  function createPolicyReader(policyFile) {
2582
2521
  let cached;
2583
2522
  return () => {
@@ -2585,113 +2524,19 @@ function createPolicyReader(policyFile) {
2585
2524
  let stamp;
2586
2525
  try {
2587
2526
  const info = statSync(path);
2588
- stamp = `${info.mtimeMs}:${info.size}`;
2527
+ stamp = `${path}:${info.mtimeMs}:${info.ctimeMs}:${info.size}`;
2589
2528
  } catch {
2590
- stamp = "missing";
2529
+ stamp = `${path}:missing`;
2591
2530
  }
2592
- if (cached !== void 0 && cached.path === path && cached.stamp === stamp) return cached.policy;
2593
- const read = readInstructionPolicy(path);
2594
- const policy = read.error === void 0 ? read.policy : void 0;
2531
+ if (cached?.stamp === stamp) return cached.snapshot;
2532
+ const snapshot = readInstructionPolicy(path);
2595
2533
  cached = {
2596
- path,
2597
2534
  stamp,
2598
- policy
2535
+ snapshot
2599
2536
  };
2600
- return policy;
2601
- };
2602
- }
2603
- /** 缺省文件来源:按会话工作区探测 → 独立策略过滤 → 单次读取形成一致快照。 */
2604
- function defaultCollectFiles(readPolicy, home) {
2605
- /**
2606
- * fileId → 上次探测到的展示路径与策略值:文件被删除或移出范围后,用它发一次
2607
- * 失效通知(是否真发由 fileConfigsFor 按「该会话曾注入过这个文件」判定)。
2608
- */
2609
- const lastSeen = /* @__PURE__ */ new Map();
2610
- return (_agent, session) => {
2611
- const policy = readPolicy();
2612
- if (policy === void 0 || policy.enabled !== true) return [];
2613
- const cwd = sessionCwd(session);
2614
- const cards = cwd === void 0 ? detectAgentsFiles({
2615
- projects: false,
2616
- home
2617
- }) : detectAgentsFiles({
2618
- cwd,
2619
- home
2620
- });
2621
- if (lastSeen.size > MAX_TRACKED_FILES) lastSeen.clear();
2622
- const out = [];
2623
- const detected = /* @__PURE__ */ new Set();
2624
- for (const card of cards) {
2625
- detected.add(card.fileId);
2626
- const values = resolveInstructionPolicy(policy, card.fileId);
2627
- if (!values.enabled) continue;
2628
- const snapshot = readAgentsFileSnapshot(card);
2629
- const base = {
2630
- fileId: card.fileId,
2631
- displayPath: card.displayPath,
2632
- order: values.order,
2633
- position: values.position,
2634
- promotion: values.promotion,
2635
- audience: values.audience,
2636
- modelScope: values.modelScope
2637
- };
2638
- lastSeen.set(card.fileId, {
2639
- displayPath: card.displayPath,
2640
- values
2641
- });
2642
- if (snapshot.status !== "ready" || snapshot.revision === null) {
2643
- out.push({
2644
- ...base,
2645
- revision: "unavailable",
2646
- text: "",
2647
- available: false,
2648
- reason: snapshot.message ?? snapshot.status
2649
- });
2650
- continue;
2651
- }
2652
- const text = snapshot.text.trim();
2653
- if (text.length === 0) {
2654
- out.push({
2655
- ...base,
2656
- revision: "unavailable",
2657
- text: "",
2658
- available: false,
2659
- reason: "file is empty"
2660
- });
2661
- continue;
2662
- }
2663
- out.push({
2664
- ...base,
2665
- revision: snapshot.revision,
2666
- text,
2667
- available: true
2668
- });
2669
- }
2670
- for (const [fileId, known] of lastSeen) {
2671
- if (detected.has(fileId)) continue;
2672
- out.push({
2673
- fileId,
2674
- displayPath: known.displayPath,
2675
- revision: "unavailable",
2676
- text: "",
2677
- available: false,
2678
- reason: "file is missing",
2679
- order: known.values.order,
2680
- position: known.values.position,
2681
- promotion: known.values.promotion,
2682
- audience: known.values.audience,
2683
- modelScope: known.values.modelScope
2684
- });
2685
- }
2686
- return out;
2537
+ return snapshot;
2687
2538
  };
2688
2539
  }
2689
- /**
2690
- * 安装协调器:发布 `promptToolPreStep` 服务并注册唯一的 pre-step 监听器。
2691
- * 普通监听器留在 prepend 门控内侧;来源监听器只登记后交出执行权。
2692
- * 即使服务迟到或重挂,下游的 pre-step 过滤(声明式触发器的 `pre-step-filter` 动作)
2693
- * 仍能在本监听器交出执行权之后过滤最终消息批。
2694
- */
2695
2540
  function installPreStepCoordinator(ctx, options = {}) {
2696
2541
  const layers = new ScopedLayers(() => {
2697
2542
  const entries = new NamedEntries((key) => /* @__PURE__ */ new Error(`prompt-tool: duplicate pre-step source ${key}`));
@@ -2706,61 +2551,62 @@ function installPreStepCoordinator(ctx, options = {}) {
2706
2551
  withSubagents: createEpochPromotion(PROMOTE_EVENTS.either, { includeSubagents: true })
2707
2552
  };
2708
2553
  const memo = /* @__PURE__ */ new Map();
2554
+ const officialOwner = /* @__PURE__ */ new Map();
2555
+ const warnOnce = createWarnOnce(ctx, WARN_LABEL);
2556
+ const readPolicy = createPolicyReader(options.policyFile);
2557
+ let active = true;
2558
+ const filterInstructions = (agent, messages) => {
2559
+ if (!active || !messages.some((message) => isRecord$1(message) && message.source?.kind === "agent-instructions")) return messages;
2560
+ const snapshot = readPolicy();
2561
+ if (snapshot.error !== void 0) {
2562
+ warnOnce(`${WARN_LABEL}: ${snapshot.error};已放行官方指令`);
2563
+ return messages;
2564
+ }
2565
+ if (!Object.values(snapshot.policy.files).some((file) => file.enabled === false)) return messages;
2566
+ const session = isRecord$1(agent) ? agent.session : void 0;
2567
+ const cwd = isRecord$1(session) && typeof session.header?.cwd === "string" ? session.header.cwd : void 0;
2568
+ let baseline = messages.findLast((message) => isRecord$1(message) && message.source?.kind === "agent-instructions" && message.source.baseline === true);
2569
+ if (baseline === void 0 && isRecord$1(session) && typeof session.deriveMessages === "function") try {
2570
+ const visible = session.deriveMessages();
2571
+ if (Array.isArray(visible)) baseline = visible.findLast((message) => isRecord$1(message) && message.source?.kind === "agent-instructions" && message.source.baseline === true);
2572
+ } catch {
2573
+ warnOnce(`${WARN_LABEL}: 无法确认官方基线项目根,已放行身份不明的项目指令`);
2574
+ }
2575
+ const result = filterOfficialInstructionMessages(messages, (path, source) => {
2576
+ const identity = source.baselineIdentity ?? (isRecord$1(baseline) ? baseline.source.baselineIdentity : void 0);
2577
+ let projectRoot;
2578
+ if (cwd !== void 0 && typeof identity === "string") try {
2579
+ const parsed = JSON.parse(identity);
2580
+ if (isRecord$1(parsed) && typeof parsed.projectRoot === "string" && !parsed.projectRoot.includes("\0")) projectRoot = resolve(cwd, parsed.projectRoot);
2581
+ } catch {}
2582
+ const id = instructionFileIdFromDisplayPath(path, projectRoot, options.home);
2583
+ return id !== void 0 && !resolveInstructionPolicy(snapshot.policy, id).enabled;
2584
+ });
2585
+ for (const diagnostic of result.diagnostics) warnOnce(`${WARN_LABEL}: ${diagnostic}`);
2586
+ return result.messages;
2587
+ };
2709
2588
  ctx.on("session/event", (session, event) => {
2710
2589
  promotion.main.observe(session, event);
2711
2590
  promotion.withSubagents.observe(session, event);
2712
2591
  confirmDelivered(memo, session, event);
2713
2592
  });
2714
- const warnOnce = createWarnOnce(ctx, WARN_LABEL);
2715
- const officialOwner = /* @__PURE__ */ new Map();
2716
- const readPolicy = createPolicyReader(options.policyFile);
2717
- const collectFiles = options.collectFiles ?? defaultCollectFiles(readPolicy, options.home);
2718
- const fileConfigsFor = (agent, session) => {
2719
- const contributions = collectFiles(agent, session);
2720
- if (contributions.length === 0) return [];
2721
- const visible = visibleFileIdentities(session);
2722
- const injectedFileIds = new Set([...visible].map(identityFileId));
2723
- const epoch = promotion.withSubagents.status(agent).boundary;
2724
- const out = [];
2725
- for (const file of contributions) {
2726
- const identity = fileIdentity(file, epoch);
2727
- if (!file.available && !injectedFileIds.has(file.fileId)) continue;
2728
- if (visible.has(identity)) continue;
2729
- out.push(file.available ? fileCardConfig(file, identity) : unavailableCardConfig(file, identity));
2730
- }
2731
- return out;
2732
- };
2733
2593
  ctx.on("agent/pre-step", async (payload, next) => {
2734
2594
  const decision = await next();
2735
- if (!isRecord$1(decision) || decision.kind === "reject") return decision;
2595
+ if (!active || !isRecord$1(decision) || decision.kind === "reject") return decision;
2736
2596
  const agent = isRecord$1(payload) ? payload.agent : void 0;
2737
2597
  const session = isRecord$1(agent) ? agent.session : void 0;
2738
2598
  if (agent === void 0 || session === void 0) return decision;
2739
2599
  try {
2740
2600
  const scope = isRecord$1(agent) && agent.ctx !== void 0 ? scopeOf(agent.ctx) : void 0;
2741
2601
  const sources = [...layers.merge(scope, (layer) => layer.entries).values()];
2742
- let official = false;
2743
- const configs = [];
2744
- for (const source of sources) {
2745
- if (source.officialInstructions === true) official = true;
2746
- for (const config of source.configs) {
2747
- if (isLegacyFileCard(config)) {
2748
- warnOnce(`${WARN_LABEL}: skipping legacy generated file card ${config.id} (independent file source owns instruction injection)`);
2749
- continue;
2750
- }
2751
- configs.push(config);
2752
- }
2753
- }
2602
+ const configs = sources.flatMap((source) => source.configs).filter((config) => !config.id.startsWith("agents-file-") && config.sourceKind !== "instruction-file").sort(compareConfigSequence);
2754
2603
  const sessionId = isRecord$1(session) && typeof session.id === "string" ? session.id : void 0;
2755
- const verified = sources.length > 0;
2756
2604
  if (sessionId !== void 0) {
2757
2605
  if (officialOwner.size >= MAX_TRACKED_OWNER_SESSIONS) officialOwner.clear();
2758
- if (verified) officialOwner.set(sessionId, official);
2606
+ if (sources.length > 0) officialOwner.set(sessionId, sources.some((source) => source.officialInstructions === true));
2759
2607
  else officialOwner.delete(sessionId);
2760
2608
  }
2761
- if (verified && !official) configs.push(...fileConfigsFor(agent, session));
2762
- if (configs.length === 0) return decision;
2763
- return await runPreStepBatch({
2609
+ return configs.length === 0 ? decision : await runPreStepBatch({
2764
2610
  ctx,
2765
2611
  agent,
2766
2612
  decision,
@@ -2774,12 +2620,23 @@ function installPreStepCoordinator(ctx, options = {}) {
2774
2620
  return decision;
2775
2621
  }
2776
2622
  });
2623
+ ctx.on("agent/pre-step", async (payload, next) => {
2624
+ const decision = await next();
2625
+ if (!active || !isRecord$1(decision) || decision.kind === "reject" || !Array.isArray(decision.messages)) return decision;
2626
+ const messages = filterInstructions(isRecord$1(payload) ? payload.agent : void 0, decision.messages);
2627
+ return messages === decision.messages ? decision : {
2628
+ ...decision,
2629
+ messages
2630
+ };
2631
+ }, { prepend: true });
2777
2632
  ctx.effect(() => () => {
2633
+ active = false;
2778
2634
  officialOwner.clear();
2779
2635
  memo.clear();
2780
2636
  });
2781
2637
  const service = {
2782
2638
  version: 1,
2639
+ filterInstructions,
2783
2640
  registerPreset(sourceCtx, sourceId, source) {
2784
2641
  const boundSource = {
2785
2642
  ...source,
@@ -2806,9 +2663,7 @@ function installPreStepCoordinator(ctx, options = {}) {
2806
2663
  };
2807
2664
  }, { label: `prompt-tool: pre-step source ${sourceId}` });
2808
2665
  },
2809
- officialOwnerOf(sessionId) {
2810
- return officialOwner.get(sessionId);
2811
- }
2666
+ officialOwnerOf: (sessionId) => officialOwner.get(sessionId)
2812
2667
  };
2813
2668
  ctx.provide(PRE_STEP_COORDINATOR_SERVICE, service);
2814
2669
  return service;
@@ -2941,7 +2796,7 @@ function readImportRequestParams(record) {
2941
2796
  };
2942
2797
  if (record.targetId !== void 0 && (typeof record.targetId !== "string" || record.targetId.length > 128 || !/^[a-z0-9][a-z0-9-]*$/.test(record.targetId))) return {
2943
2798
  ok: false,
2944
- message: "targetId 必须是合法小写预设 ID(最多 128 字符)"
2799
+ message: "targetId 必须是合法小写模块 ID(最多 128 字符)"
2945
2800
  };
2946
2801
  if (record.targetName !== void 0 && (typeof record.targetName !== "string" || record.targetName.trim().length === 0 || record.targetName.length > 256)) return {
2947
2802
  ok: false,
@@ -3041,7 +2896,7 @@ function withSnapshot(card, file) {
3041
2896
  };
3042
2897
  }
3043
2898
  /**
3044
- * 两类卡合并:预设卡原样保留,文件卡换成本次工作区的文件快照。
2899
+ * 两类卡合并:模块卡原样保留,文件卡换成本次工作区的文件快照。
3045
2900
  * 生成目录里属于其他工作区的文件卡不再展示(写盘白名单也不含它们);
3046
2901
  * 当前工作区里没有对应生成卡的文件按默认文件卡补齐。
3047
2902
  */
@@ -3101,8 +2956,8 @@ function projectToolSchemas(schemas) {
3101
2956
  }
3102
2957
  return [...unique.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
3103
2958
  }
3104
- function isEditablePresetDir(dir, presetRoot = userPresetsDir()) {
3105
- const root = resolve(presetRoot);
2959
+ function isEditablePresetDir(dir, moduleRoot = userModulesDir()) {
2960
+ const root = resolve(moduleRoot);
3106
2961
  const target = resolve(dir);
3107
2962
  return target !== root && target.startsWith(root + sep);
3108
2963
  }
@@ -3111,7 +2966,7 @@ function guardEditablePresetDir(dir, res) {
3111
2966
  writeBridgeJson(res, 403, {
3112
2967
  ok: false,
3113
2968
  code: "preset-readonly",
3114
- message: "system preset 只读,请先复制为用户预设"
2969
+ message: "system preset 只读,请先复制为用户模块"
3115
2970
  });
3116
2971
  return false;
3117
2972
  }
@@ -3205,7 +3060,7 @@ function uploadFileName(req) {
3205
3060
  throw new Error("上传文件名编码无效");
3206
3061
  }
3207
3062
  }
3208
- /** 自定义工具声明需要的预设模块(模块缺失时保存动作自动补齐)。 */
3063
+ /** 自定义工具声明需要的模块(模块缺失时保存动作自动补齐)。 */
3209
3064
  function customToolModules(tools) {
3210
3065
  const modules = /* @__PURE__ */ new Set();
3211
3066
  if (tools.length > 0) modules.add("tool-config-engine");
@@ -3227,19 +3082,28 @@ function customToolModules(tools) {
3227
3082
  function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngineStrategyDir, afterSkillsChange, getPresetConfigsDir, afterPresetImport, afterOverridesChange, afterPresetPackageImport, afterCapabilityChange, afterPresetListChange) {
3228
3083
  let invalidateCachedDescriptor = () => {};
3229
3084
  let capabilityQueue = Promise.resolve();
3230
- const refreshPresetList = async () => {
3085
+ let moduleOrderQueue = Promise.resolve();
3086
+ /** 写盘已完成:等待重建,失败立即返回统一错误,不覆盖持久化结果。 */
3087
+ const finishPresetChange = async (res, activate) => {
3231
3088
  try {
3232
- await afterPresetListChange?.();
3089
+ await activate();
3090
+ return true;
3233
3091
  } catch (error) {
3234
- ctx.logger?.warn(`prompt-tool: 预设目录已更新,但注册刷新失败:${String(error)}`);
3092
+ writeBridgeJson(res, 500, {
3093
+ ok: false,
3094
+ code: PRESET_ACTIVATION_FAILED,
3095
+ message: `更改已保存,但模块未生效;请重试:${String(error)}`
3096
+ });
3097
+ return false;
3235
3098
  }
3236
3099
  };
3237
- /** 等待当前预设重建;不传播附加回调的返回值。 */
3238
- const runOverridesChange = async () => {
3239
- try {
3240
- await afterOverridesChange?.();
3241
- } catch {}
3242
- };
3100
+ const runOverridesChange = (res, moduleId) => finishPresetChange(res, () => afterOverridesChange?.(moduleId));
3101
+ /**
3102
+ * 本次请求的编辑目标目录:读 `x-module-id` 头(形状不合法当未声明),交给注入的实现定位。
3103
+ * 所有「读/写哪个模块的配置」都走这里,端点不再各自记一个全局目标。
3104
+ */
3105
+ const editDir = (req) => getPresetConfigsDir?.(readEditTarget(req.headers["x-module-id"])) ?? "";
3106
+ const refreshPresetList = (res, id) => finishPresetChange(res, () => afterPresetListChange?.(id));
3243
3107
  ctx.inject(["settings", "webServer"], (sctx) => {
3244
3108
  sctx.effect(() => {
3245
3109
  const assetSources = createAssetSources(join(DSH_HOME, ".prompt-tool-uploads"));
@@ -3253,6 +3117,14 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3253
3117
  cachedAt = now;
3254
3118
  return cachedDescriptor;
3255
3119
  };
3120
+ /** 已解析的编辑目标只投影到本次响应,不改宿主设置或缓存描述符。 */
3121
+ const descriptorForTarget = (descriptor, dir) => dir.length === 0 ? descriptor : {
3122
+ ...descriptor,
3123
+ value: {
3124
+ ...asRecord(descriptor.value),
3125
+ presetTemplate: basename(dir)
3126
+ }
3127
+ };
3256
3128
  /** mutate 成功后强制失效,下次 findDescriptor 重查宿主(响应必须带新 view)。 */
3257
3129
  const invalidateDescriptor = () => {
3258
3130
  cachedDescriptor = void 0;
@@ -3280,29 +3152,36 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3280
3152
  const guardPresetFormat = (dir, res) => {
3281
3153
  if (dir.length === 0) return true;
3282
3154
  try {
3283
- loadPresetSpec(dir);
3155
+ loadModuleSpec(dir);
3284
3156
  } catch (error) {
3285
3157
  if (writeLayerSettingsError(res, error)) return false;
3286
3158
  }
3287
3159
  return true;
3288
3160
  };
3289
3161
  const guardPresetWrite = (dir, res) => guardEditablePresetDir(dir, res) && guardPresetFormat(dir, res);
3290
- /** 预设身份只作一致性检查,绝不用客户端 ID 构造写入路径。 */
3291
- const guardPresetIdentity = (record, dir, res) => {
3162
+ const writeModuleOrderError = (res, error) => {
3163
+ writeBridgeJson(res, error instanceof ModuleConfigOrderError ? error.status : 500, {
3164
+ ok: false,
3165
+ code: error instanceof ModuleConfigOrderError ? error.code : "module-config-order-failed",
3166
+ message: String(error?.message ?? error)
3167
+ });
3168
+ };
3169
+ /** 模块身份只作一致性检查,绝不用客户端 ID 构造写入路径。 */
3170
+ const guardPresetIdentity = (req, record, dir, res) => {
3292
3171
  const expected = record.expectedPresetId;
3293
3172
  if (expected !== void 0 && (typeof expected !== "string" || expected.length === 0 || expected.length > 256)) {
3294
3173
  writeBridgeJson(res, 400, {
3295
3174
  ok: false,
3296
3175
  code: "preset-identity-invalid",
3297
- message: "expectedPresetId 必须是非空预设 ID"
3176
+ message: "expectedPresetId 必须是非空模块 ID"
3298
3177
  });
3299
3178
  return false;
3300
3179
  }
3301
- if ((getPresetConfigsDir?.() ?? "") !== dir || expected !== void 0 && expected !== basename(dir)) {
3180
+ if (editDir(req) !== dir || expected !== void 0 && expected !== basename(dir)) {
3302
3181
  writeBridgeJson(res, 409, {
3303
3182
  ok: false,
3304
3183
  code: "preset-changed",
3305
- message: "当前预设已切换;旧草稿未写入,请重新读取后保存"
3184
+ message: "当前模块已切换;旧草稿未写入,请重新读取后保存"
3306
3185
  });
3307
3186
  return false;
3308
3187
  }
@@ -3314,7 +3193,15 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3314
3193
  const loadEngineMeta = async () => {
3315
3194
  const { getEngineMeta } = await import(pathToFileURL(join(packageEngineDir(), "schema.mjs")).href);
3316
3195
  const meta = getEngineMeta();
3317
- meta.presets = listPresets();
3196
+ const registry = sctx.get?.("agentPresets");
3197
+ const roster = registry === void 0 ? [] : await registry.list();
3198
+ const diagnostics = new Map(roster.map(({ id, broken }) => [id, broken]));
3199
+ const enabledSet = new Set(enabledModuleIds(userModulesDir()));
3200
+ meta.presets = listModules().map((preset) => ({
3201
+ ...preset,
3202
+ enabled: enabledSet.has(preset.id),
3203
+ ...diagnostics.get(preset.id) === void 0 ? {} : { broken: diagnostics.get(preset.id) }
3204
+ }));
3318
3205
  meta.builtinTemplates = listBuiltinTemplates();
3319
3206
  meta.editorGroups = ENGINE_EDITOR_GROUP_MAP.map(({ id, displayLayer, relatedLayers, hook }) => ({
3320
3207
  id,
@@ -3333,7 +3220,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3333
3220
  return meta;
3334
3221
  };
3335
3222
  /** describe 运行时事实(describe 端点与 /bootstrap 共用):检测状态、技能快照、
3336
- * 宿主默认模型、模型目录缓存、激活预设参数。不触网(模型目录只读 10min 缓存)。 */
3223
+ * 宿主默认模型、模型目录缓存、激活模块参数。不触网(模型目录只读 10min 缓存)。 */
3337
3224
  const collectSkills = async (sessionId) => {
3338
3225
  const cwd = sessionId === void 0 ? void 0 : localAgentCwd(sctx, sessionId);
3339
3226
  const agents = sctx.get?.("agents");
@@ -3367,7 +3254,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3367
3254
  };
3368
3255
  }
3369
3256
  };
3370
- const collectDescribeExtras = async (sessionId) => {
3257
+ const collectDescribeExtras = async (activeDir, sessionId) => {
3371
3258
  const detection = getModelsState();
3372
3259
  const skillsState = getSkillsState();
3373
3260
  const skillsSnapshot = await collectSkills(sessionId);
@@ -3389,18 +3276,18 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3389
3276
  let moduleFacts;
3390
3277
  let templatePreStepCount = 0;
3391
3278
  try {
3392
- const activeDir = getPresetConfigsDir?.() ?? "";
3393
- const templateName = activeDir.length > 0 ? basename(activeDir) : DEFAULT_PRESET_ID;
3394
- const spec = loadPresetSpec(activeDir.length > 0 ? activeDir : resolvePresetDir(templateName));
3279
+ const templateName = activeDir.length > 0 ? basename(activeDir) : DEFAULT_MODULE_ID;
3280
+ const spec = loadModuleSpec(activeDir.length > 0 ? activeDir : resolveModuleDir(templateName));
3395
3281
  presetParams = resolvePresetParams(spec, {});
3396
- const resolvedFacts = resolvePresetModuleFacts(spec, activeDir.length > 0 ? activeDir : void 0, isEditablePresetDir(activeDir.length > 0 ? activeDir : resolvePresetDir(templateName)));
3282
+ const resolvedFacts = resolveModuleFacts(spec, activeDir.length > 0 ? activeDir : void 0, isEditablePresetDir(activeDir.length > 0 ? activeDir : resolveModuleDir(templateName)));
3397
3283
  mergeModuleConfigFallbacks(presetParams, resolvedFacts);
3398
3284
  moduleFacts = {
3399
3285
  declaredModules: resolvedFacts.declaredModules,
3400
3286
  effectiveModules: resolvedFacts.effectiveModules,
3401
3287
  rowIds: resolvedFacts.rowIds,
3402
3288
  sourceMode: resolvedFacts.sourceMode,
3403
- editable: resolvedFacts.editable
3289
+ editable: resolvedFacts.editable,
3290
+ subagentToolPolicyEnabled: resolvedFacts.subagentToolPolicyEnabled === true
3404
3291
  };
3405
3292
  if (Array.isArray(spec.promptConfigs)) presetParams.promptConfigs = spec.promptConfigs;
3406
3293
  templatePreStepCount = (spec.promptConfigs ?? []).filter((config) => {
@@ -3426,10 +3313,10 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3426
3313
  skillFolders: skillsState.folders
3427
3314
  };
3428
3315
  };
3429
- /** 激活预设的引擎参数子集(/param-overrides 读取;preset.yml 按 mtime 缓存)。 */
3316
+ /** 激活模块的引擎参数子集(/param-overrides 读取;preset.yml 按 mtime 缓存)。 */
3430
3317
  const readParamOverrides = (dir) => {
3431
3318
  try {
3432
- const spec = loadPresetSpec(dir);
3319
+ const spec = loadModuleSpec(dir);
3433
3320
  const params = {};
3434
3321
  for (const key of PARAM_KEYS) {
3435
3322
  if (key === "promptConfigs") continue;
@@ -3441,10 +3328,10 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3441
3328
  return {};
3442
3329
  }
3443
3330
  };
3444
- /** 预设级模板变量(/preset-variables 读取)。 */
3331
+ /** 模块级模板变量(/preset-variables 读取)。 */
3445
3332
  const readPresetVariables = (dir) => {
3446
3333
  try {
3447
- const spec = loadPresetSpec(dir);
3334
+ const spec = loadModuleSpec(dir);
3448
3335
  const variables = {};
3449
3336
  for (const [key, value] of Object.entries(spec.variables ?? {})) if (typeof value === "string") variables[key] = value;
3450
3337
  return {
@@ -3462,13 +3349,15 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3462
3349
  /** 生成目录实际生效配置(/prompt-configs 读取)。 */
3463
3350
  const readPromptConfigs = (dir) => {
3464
3351
  try {
3465
- return dir.length > 0 ? listPromptConfigSpecs(join(dir, "prompt-configs")).map((config) => {
3352
+ const order = loadModuleSpec(dir).configOrder ?? {};
3353
+ return dir.length > 0 ? listPromptConfigSpecs(join(dir, MODULE_CONFIGS_DIR)).map((config) => {
3466
3354
  const raw = config;
3467
3355
  const definition = stripConfigFieldSources(raw);
3468
3356
  const fieldSources = readConfigFieldSources(config.id, raw.fieldSources);
3469
3357
  return {
3470
3358
  ...definition,
3471
- ...fieldSources === void 0 ? {} : { fieldSources }
3359
+ ...fieldSources === void 0 ? {} : { fieldSources },
3360
+ ...Object.hasOwn(order, config.id) ? { sequence: order[config.id] } : {}
3472
3361
  };
3473
3362
  }) : [];
3474
3363
  } catch {
@@ -3482,6 +3371,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3482
3371
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.bootstrap,
3483
3372
  handler: async (req, res) => {
3484
3373
  if (!guard(req, res)) return;
3374
+ const dir = editDir(req);
3485
3375
  const descriptor = findDescriptor();
3486
3376
  if (descriptor === void 0) {
3487
3377
  writeBridgeJson(res, 404, {
@@ -3504,12 +3394,11 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3504
3394
  }
3505
3395
  try {
3506
3396
  const meta = await loadEngineMeta();
3507
- const dir = getPresetConfigsDir?.() ?? "";
3508
- const extras = await collectDescribeExtras(session.sessionId);
3397
+ const extras = await collectDescribeExtras(dir, session.sessionId);
3509
3398
  const scope = resolveInstructionScope(sctx, session.sessionId);
3510
3399
  writeBridgeJson(res, 200, {
3511
3400
  ok: true,
3512
- value: descriptor,
3401
+ value: descriptorForTarget(descriptor, dir),
3513
3402
  meta: { meta },
3514
3403
  overrides: { overrides: dir.length > 0 ? readParamOverrides(dir) : {} },
3515
3404
  variables: dir.length > 0 ? readPresetVariables(dir) : {
@@ -3539,10 +3428,18 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3539
3428
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.meta,
3540
3429
  handler: async (req, res) => {
3541
3430
  if (!guard(req, res)) return;
3542
- writeBridgeJson(res, 200, {
3543
- ok: true,
3544
- value: { meta: await loadEngineMeta() }
3545
- });
3431
+ try {
3432
+ writeBridgeJson(res, 200, {
3433
+ ok: true,
3434
+ value: { meta: await loadEngineMeta() }
3435
+ });
3436
+ } catch (error) {
3437
+ writeBridgeJson(res, 503, {
3438
+ ok: false,
3439
+ code: "meta-unavailable",
3440
+ message: String(error)
3441
+ });
3442
+ }
3546
3443
  }
3547
3444
  }),
3548
3445
  sctx.webServer.register({
@@ -3550,6 +3447,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3550
3447
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.describe,
3551
3448
  handler: async (req, res) => {
3552
3449
  if (!guard(req, res)) return;
3450
+ const dir = editDir(req);
3553
3451
  const descriptor = findDescriptor();
3554
3452
  if (descriptor === void 0) {
3555
3453
  writeBridgeJson(res, 404, {
@@ -3562,8 +3460,8 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3562
3460
  try {
3563
3461
  writeBridgeJson(res, 200, {
3564
3462
  ok: true,
3565
- value: descriptor,
3566
- ...await collectDescribeExtras()
3463
+ value: descriptorForTarget(descriptor, dir),
3464
+ ...await collectDescribeExtras(dir)
3567
3465
  });
3568
3466
  } catch (error) {
3569
3467
  if (!writeLayerSettingsError(res, error)) throw error;
@@ -3650,7 +3548,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3650
3548
  writeBridgeJson(res, 400, {
3651
3549
  ok: false,
3652
3550
  code: "settings-rejected",
3653
- message: "引擎参数按预设存储:请用设置页保存(/param-overrides),settings 只接受全局开关"
3551
+ message: "引擎参数按模块存储:请用设置页保存(/param-overrides),settings 只接受全局开关"
3654
3552
  });
3655
3553
  return;
3656
3554
  }
@@ -3710,7 +3608,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3710
3608
  ok: true,
3711
3609
  value: await validatePromptConfigs(record.promptConfigs, {
3712
3610
  strategyDir: getEngineStrategyDir(),
3713
- presetDir: getPresetConfigsDir?.()
3611
+ moduleDir: editDir(req)
3714
3612
  })
3715
3613
  });
3716
3614
  }
@@ -4072,7 +3970,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4072
3970
  });
4073
3971
  return;
4074
3972
  }
4075
- const dir = getPresetConfigsDir?.() ?? "";
3973
+ const dir = editDir(req) ?? "";
4076
3974
  if (!guardPresetFormat(dir, res)) return;
4077
3975
  const scope = resolveInstructionScope(sctx, session.sessionId);
4078
3976
  writeBridgeJson(res, 200, {
@@ -4239,7 +4137,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4239
4137
  const { body } = parsedBody;
4240
4138
  const scope = (body ?? {}).scope === "agents" ? "agents" : "preset";
4241
4139
  try {
4242
- const dir = getPresetConfigsDir?.() ?? "";
4140
+ const dir = editDir(req) ?? "";
4243
4141
  writeBridgeJson(res, 200, {
4244
4142
  ok: true,
4245
4143
  value: { content: dir.length > 0 ? readFileSync(join(dir, scope === "preset" ? "preset.md" : "agents.md"), "utf8") : "" }
@@ -4301,21 +4199,21 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4301
4199
  content: item.content
4302
4200
  });
4303
4201
  }
4304
- const dir = getPresetConfigsDir?.() ?? "";
4202
+ const dir = editDir(req) ?? "";
4305
4203
  if (dir.length === 0) {
4306
4204
  writeBridgeJson(res, 400, {
4307
4205
  ok: false,
4308
4206
  code: "preset-dir-unavailable",
4309
- message: "presetDir 未配置"
4207
+ message: "模块目录未配置"
4310
4208
  });
4311
4209
  return;
4312
4210
  }
4313
4211
  if (!guardPresetWrite(dir, res)) return;
4314
4212
  try {
4315
- if (!guardPresetIdentity(record, dir, res)) return;
4213
+ if (!guardPresetIdentity(req, record, dir, res)) return;
4316
4214
  mkdirSync(dir, { recursive: true });
4317
4215
  for (const entry of contents) writeFileSync(join(dir, entry.scope === "preset" ? "preset.md" : "agents.md"), entry.content, "utf8");
4318
- afterPresetImport?.(contents.map((entry) => entry.scope));
4216
+ if (!await finishPresetChange(res, () => afterPresetImport?.(contents.map((entry) => entry.scope), basename(dir)))) return;
4319
4217
  writeBridgeJson(res, 200, {
4320
4218
  ok: true,
4321
4219
  value: { scopes: contents.map((entry) => entry.scope) }
@@ -4334,12 +4232,12 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4334
4232
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.triggers,
4335
4233
  handler: async (req, res) => {
4336
4234
  if (!guard(req, res)) return;
4337
- const dir = getPresetConfigsDir?.() ?? "";
4235
+ const dir = editDir(req) ?? "";
4338
4236
  if (dir.length === 0) {
4339
4237
  writeBridgeJson(res, 400, {
4340
4238
  ok: false,
4341
4239
  code: "preset-dir-unavailable",
4342
- message: "当前预设目录不可用"
4240
+ message: "当前模块目录不可用"
4343
4241
  });
4344
4242
  return;
4345
4243
  }
@@ -4355,11 +4253,11 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4355
4253
  writeBridgeJson(res, 400, {
4356
4254
  ok: false,
4357
4255
  code: "triggers-invalid",
4358
- message: "声明请求字段、预设身份或版本格式不合法"
4256
+ message: "声明请求字段、模块身份或版本格式不合法"
4359
4257
  });
4360
4258
  return;
4361
4259
  }
4362
- if (!guardPresetIdentity(record, dir, res)) return;
4260
+ if (!guardPresetIdentity(req, record, dir, res)) return;
4363
4261
  const writing = record.triggers !== void 0 && record.validateOnly !== true;
4364
4262
  if (writing && typeof record.expectedRevision !== "string") {
4365
4263
  writeBridgeJson(res, 400, {
@@ -4373,10 +4271,10 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4373
4271
  try {
4374
4272
  const { compileDeclarations } = await import(pathToFileURL(join(packageEngineDir(), "trigger-spec.mjs")).href);
4375
4273
  const { getTriggerEditorMeta } = await import(pathToFileURL(join(packageEngineDir(), "trigger-editor-meta.mjs")).href);
4376
- if (!guardPresetIdentity(record, dir, res)) return;
4377
- const snapshot = readPresetTriggers(dir);
4274
+ if (!guardPresetIdentity(req, record, dir, res)) return;
4275
+ const snapshot = readModuleTriggers(dir);
4378
4276
  if (record.triggers !== void 0) try {
4379
- const strategy = loadPresetSpec(dir).moduleConfigs?.["declared-triggers"]?.strategyDir;
4277
+ const strategy = loadModuleSpec(dir).moduleConfigs?.["declared-triggers"]?.strategyDir;
4380
4278
  compileDeclarations(record.triggers, { promptConfigOptions: triggerPromptConfigOptions(dir, strategy) });
4381
4279
  } catch (error) {
4382
4280
  writeBridgeJson(res, 400, {
@@ -4387,7 +4285,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4387
4285
  return;
4388
4286
  }
4389
4287
  if (writing) {
4390
- if (!Array.isArray(loadPresetSpec(dir).modules)) {
4288
+ if (!Array.isArray(loadModuleSpec(dir).modules)) {
4391
4289
  writeBridgeJson(res, 400, {
4392
4290
  ok: false,
4393
4291
  code: "triggers-composition-readonly",
@@ -4395,26 +4293,26 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4395
4293
  });
4396
4294
  return;
4397
4295
  }
4398
- if (!savePresetTriggers(dir, record.triggers, record.expectedRevision)) {
4296
+ if (!saveModuleTriggers(dir, record.triggers, record.expectedRevision)) {
4399
4297
  writeBridgeJson(res, 409, {
4400
4298
  ok: false,
4401
4299
  code: "triggers-conflict",
4402
- message: "预设文件已变化;草稿未写入,请重新读取后保存"
4300
+ message: "模块文件已变化;草稿未写入,请重新读取后保存"
4403
4301
  });
4404
4302
  return;
4405
4303
  }
4406
4304
  try {
4407
- await afterOverridesChange?.();
4305
+ await afterOverridesChange?.(basename(dir));
4408
4306
  } catch (error) {
4409
4307
  writeBridgeJson(res, 500, {
4410
4308
  ok: false,
4411
4309
  code: "triggers-rebuild-failed",
4412
- message: `声明已保存,但预设重建失败;请重新读取后重试:${String(error)}`
4310
+ message: `声明已保存,但模块重建失败;请重新读取后重试:${String(error)}`
4413
4311
  });
4414
4312
  return;
4415
4313
  }
4416
4314
  }
4417
- const result = writing ? readPresetTriggers(dir) : snapshot;
4315
+ const result = writing ? readModuleTriggers(dir) : snapshot;
4418
4316
  if (writing && !isDeepStrictEqual(result.triggers, record.triggers)) {
4419
4317
  writeBridgeJson(res, 409, {
4420
4318
  ok: false,
@@ -4445,22 +4343,22 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4445
4343
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.paramOverrides,
4446
4344
  handler: async (req, res) => {
4447
4345
  if (!guard(req, res)) return;
4448
- const dir = getPresetConfigsDir?.() ?? "";
4346
+ const dir = editDir(req) ?? "";
4449
4347
  if (dir.length === 0) {
4450
4348
  writeBridgeJson(res, 400, {
4451
4349
  ok: false,
4452
4350
  code: "preset-dir-unavailable",
4453
- message: "presetDir 未配置"
4351
+ message: "模块目录未配置"
4454
4352
  });
4455
4353
  return;
4456
4354
  }
4457
- const presetRoot = dirname(dir);
4355
+ const moduleRoot = dirname(dir);
4458
4356
  const templateName = basename(dir);
4459
4357
  const parsedBody = await readBridgeBodyForHandler(req, res);
4460
4358
  if (parsedBody === void 0) return;
4461
4359
  const { body } = parsedBody;
4462
4360
  const record = body ?? {};
4463
- if (!guardPresetIdentity(record, dir, res)) return;
4361
+ if (!guardPresetIdentity(req, record, dir, res)) return;
4464
4362
  if (record.rebuild !== void 0 && typeof record.rebuild !== "boolean") {
4465
4363
  writeBridgeJson(res, 400, {
4466
4364
  ok: false,
@@ -4527,7 +4425,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4527
4425
  if (Array.isArray(record.promptConfigs)) {
4528
4426
  const validation = await validatePromptConfigs(record.promptConfigs, {
4529
4427
  strategyDir: getEngineStrategyDir(),
4530
- presetDir: dir
4428
+ moduleDir: dir
4531
4429
  });
4532
4430
  if (!validation.valid) {
4533
4431
  writeBridgeJson(res, 400, {
@@ -4537,7 +4435,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4537
4435
  });
4538
4436
  return;
4539
4437
  }
4540
- const spec = loadPresetSpec(dir);
4438
+ const spec = loadModuleSpec(dir);
4541
4439
  const conflicting = record.promptConfigs.some((config) => {
4542
4440
  if (config === null || typeof config !== "object" || Array.isArray(config)) return false;
4543
4441
  const entry = config;
@@ -4552,9 +4450,9 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4552
4450
  return;
4553
4451
  }
4554
4452
  }
4555
- if (!guardPresetIdentity(record, dir, res)) return;
4556
- savePresetParams(presetRoot, templateName, rawOverrides, record.promptConfigs);
4557
- if (record.rebuild !== false) await runOverridesChange();
4453
+ if (!guardPresetIdentity(req, record, dir, res)) return;
4454
+ saveModuleParams(moduleRoot, templateName, rawOverrides, record.promptConfigs);
4455
+ if (record.rebuild !== false && !await runOverridesChange(res, basename(dir))) return;
4558
4456
  writeBridgeJson(res, 200, {
4559
4457
  ok: true,
4560
4458
  value: {
@@ -4577,22 +4475,22 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4577
4475
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.presetVariables,
4578
4476
  handler: async (req, res) => {
4579
4477
  if (!guard(req, res)) return;
4580
- const dir = getPresetConfigsDir?.() ?? "";
4478
+ const dir = editDir(req) ?? "";
4581
4479
  if (dir.length === 0) {
4582
4480
  writeBridgeJson(res, 400, {
4583
4481
  ok: false,
4584
4482
  code: "preset-dir-unavailable",
4585
- message: "presetDir 未配置"
4483
+ message: "模块目录未配置"
4586
4484
  });
4587
4485
  return;
4588
4486
  }
4589
- const presetRoot = dirname(dir);
4487
+ const moduleRoot = dirname(dir);
4590
4488
  const templateName = basename(dir);
4591
4489
  const parsedBody = await readBridgeBodyForHandler(req, res);
4592
4490
  if (parsedBody === void 0) return;
4593
4491
  const { body } = parsedBody;
4594
4492
  const record = body ?? {};
4595
- if (!guardPresetIdentity(record, dir, res)) return;
4493
+ if (!guardPresetIdentity(req, record, dir, res)) return;
4596
4494
  if (record.variables === void 0 && record.enabled === void 0) {
4597
4495
  writeBridgeJson(res, 200, {
4598
4496
  ok: true,
@@ -4618,9 +4516,9 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4618
4516
  }
4619
4517
  if (!guardPresetWrite(dir, res)) return;
4620
4518
  try {
4621
- if (!guardPresetIdentity(record, dir, res)) return;
4622
- savePresetParams(presetRoot, templateName, void 0, void 0, record.variables, record.enabled);
4623
- runOverridesChange();
4519
+ if (!guardPresetIdentity(req, record, dir, res)) return;
4520
+ saveModuleParams(moduleRoot, templateName, void 0, void 0, record.variables, record.enabled);
4521
+ if (!await runOverridesChange(res, basename(dir))) return;
4624
4522
  writeBridgeJson(res, 200, {
4625
4523
  ok: true,
4626
4524
  value: { variables: record.variables }
@@ -4639,12 +4537,12 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4639
4537
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.customTools,
4640
4538
  handler: async (req, res) => {
4641
4539
  if (!guard(req, res)) return;
4642
- const dir = getPresetConfigsDir?.() ?? "";
4540
+ const dir = editDir(req) ?? "";
4643
4541
  if (dir.length === 0) {
4644
4542
  writeBridgeJson(res, 400, {
4645
4543
  ok: false,
4646
4544
  code: "preset-dir-unavailable",
4647
- message: "presetDir 未配置"
4545
+ message: "模块目录未配置"
4648
4546
  });
4649
4547
  return;
4650
4548
  }
@@ -4652,10 +4550,10 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4652
4550
  if (parsedBody === void 0) return;
4653
4551
  const { body } = parsedBody;
4654
4552
  const record = body ?? {};
4655
- if (!guardPresetIdentity(record, dir, res)) return;
4553
+ if (!guardPresetIdentity(req, record, dir, res)) return;
4656
4554
  if (record.customTools === void 0) {
4657
4555
  try {
4658
- const spec = loadPresetSpec(dir);
4556
+ const spec = loadModuleSpec(dir);
4659
4557
  writeBridgeJson(res, 200, {
4660
4558
  ok: true,
4661
4559
  value: { customTools: Array.isArray(spec.customTools) ? spec.customTools : [] }
@@ -4690,7 +4588,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4690
4588
  return;
4691
4589
  }
4692
4590
  if (!guardPresetWrite(dir, res)) return;
4693
- if (!guardPresetIdentity(record, dir, res)) return;
4591
+ if (!guardPresetIdentity(req, record, dir, res)) return;
4694
4592
  withPresetDoc(dir, (doc) => {
4695
4593
  if (customTools.length === 0) doc.deleteIn(["customTools"]);
4696
4594
  else {
@@ -4698,7 +4596,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4698
4596
  appendPresetModules(doc, customToolModules(customTools));
4699
4597
  }
4700
4598
  });
4701
- runOverridesChange();
4599
+ if (!await runOverridesChange(res, basename(dir))) return;
4702
4600
  writeBridgeJson(res, 200, {
4703
4601
  ok: true,
4704
4602
  value: { customTools }
@@ -4717,24 +4615,24 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4717
4615
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.persona,
4718
4616
  handler: async (req, res) => {
4719
4617
  if (!guard(req, res)) return;
4720
- const dir = getPresetConfigsDir?.() ?? "";
4618
+ const dir = editDir(req) ?? "";
4721
4619
  if (dir.length === 0) {
4722
4620
  writeBridgeJson(res, 400, {
4723
4621
  ok: false,
4724
4622
  code: "preset-dir-unavailable",
4725
- message: "presetDir 未配置"
4623
+ message: "模块目录未配置"
4726
4624
  });
4727
4625
  return;
4728
4626
  }
4729
4627
  const parsedBody = await readBridgeBodyForHandler(req, res);
4730
4628
  if (parsedBody === void 0) return;
4731
4629
  const record = parsedBody.body ?? {};
4732
- if (!guardPresetIdentity(record, dir, res)) return;
4630
+ if (!guardPresetIdentity(req, record, dir, res)) return;
4733
4631
  if (record.persona === void 0) {
4734
4632
  try {
4735
4633
  writeBridgeJson(res, 200, {
4736
4634
  ok: true,
4737
- value: { persona: readPersonaSpec(loadPresetSpec(dir).persona) ?? null }
4635
+ value: { persona: readPersonaSpec(loadModuleSpec(dir).persona) ?? null }
4738
4636
  });
4739
4637
  } catch (error) {
4740
4638
  if (writeLayerSettingsError(res, error)) return;
@@ -4758,7 +4656,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4758
4656
  return;
4759
4657
  }
4760
4658
  if (persona?.complete === true) {
4761
- if ((loadPresetSpec(dir).promptConfigs ?? []).some((config) => {
4659
+ if ((loadModuleSpec(dir).promptConfigs ?? []).some((config) => {
4762
4660
  if (config === null || typeof config !== "object" || Array.isArray(config)) return false;
4763
4661
  const entry = config;
4764
4662
  return entry.enabled !== false && entry.params?.complete === true;
@@ -4772,7 +4670,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4772
4670
  }
4773
4671
  }
4774
4672
  savePresetPersona(dirname(dir), basename(dir), persona);
4775
- runOverridesChange();
4673
+ if (!await runOverridesChange(res, basename(dir))) return;
4776
4674
  writeBridgeJson(res, 200, {
4777
4675
  ok: true,
4778
4676
  value: { persona }
@@ -4864,7 +4762,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4864
4762
  }
4865
4763
  try {
4866
4764
  const files = await expandPresetSource(request.params.sourceId === void 0 ? readBridgeFiles(record.files) : assetSources.read(request.params.sourceId));
4867
- const preview = presetImportPreview(userPresetsDir(), files, request.params);
4765
+ const preview = presetImportPreview(userModulesDir(), files, request.params);
4868
4766
  if (!("prepared" in preview)) {
4869
4767
  if (!request.params.preview) throw new Error("请先选择内容类型或提示顺序组并重新预览");
4870
4768
  writeBridgeJson(res, 200, {
@@ -4891,21 +4789,15 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4891
4789
  });
4892
4790
  return;
4893
4791
  }
4894
- const installed = await installPresetPackage(userPresetsDir(), files, request.params);
4792
+ const installed = await installPresetPackage(userModulesDir(), files, request.params);
4895
4793
  invalidateDescriptor();
4896
- let refreshWarning;
4897
- try {
4898
- await afterPresetPackageImport?.(installed.id);
4899
- } catch (error) {
4900
- refreshWarning = `预设已安装,但刷新失败:${error instanceof Error ? error.message : String(error)}`;
4901
- }
4794
+ if (!await finishPresetChange(res, () => afterPresetPackageImport?.(installed.id))) return;
4902
4795
  writeBridgeJson(res, 200, {
4903
4796
  ok: true,
4904
4797
  value: {
4905
4798
  ...installed,
4906
4799
  sourceDigest,
4907
- ...prepared.report === void 0 ? {} : { report: prepared.report },
4908
- ...refreshWarning === void 0 ? {} : { refreshWarning }
4800
+ ...prepared.report === void 0 ? {} : { report: prepared.report }
4909
4801
  }
4910
4802
  });
4911
4803
  } catch (error) {
@@ -4937,14 +4829,14 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4937
4829
  ]);
4938
4830
  if (Object.keys(record).some((key) => !allowed.has(key))) throw new Error("导出请求包含未知字段");
4939
4831
  assertPresetId(record.id);
4940
- if (record.id.length > 128) throw new Error("预设 ID 过长");
4832
+ if (record.id.length > 128) throw new Error("模块 ID 过长");
4941
4833
  if (record.mode !== void 0 && record.mode !== "definition" && record.mode !== "zip") throw new Error("mode 必须是 definition 或 zip");
4942
4834
  if (record.preview !== void 0 && typeof record.preview !== "boolean") throw new Error("preview 必须是布尔值");
4943
4835
  if (record.expectedRevision !== void 0 && (typeof record.expectedRevision !== "string" || !SHA256_HEX_RE.test(record.expectedRevision))) throw new Error("expectedRevision 必须是 SHA-256 摘要");
4944
4836
  if (record.memoryChoices !== void 0 && (!isRecord(record.memoryChoices) || Object.keys(record.memoryChoices).length > 2048 || Object.entries(record.memoryChoices).some(([key, value]) => key.length === 0 || key.length > 256 || value !== "include" && value !== "exclude"))) throw new Error("memoryChoices 必须包含合法条目 ID 和 include/exclude 选择");
4945
4837
  writeBridgeJson(res, 200, {
4946
4838
  ok: true,
4947
- value: await exportPresetPackage(userPresetsDir(), record)
4839
+ value: await exportPresetPackage(userModulesDir(), record)
4948
4840
  });
4949
4841
  } catch (error) {
4950
4842
  const message = error instanceof Error ? error.message : String(error);
@@ -4959,7 +4851,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4959
4851
  }),
4960
4852
  sctx.webServer.register({
4961
4853
  kind: "exact",
4962
- path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.presetDelete,
4854
+ path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.moduleDelete,
4963
4855
  handler: async (req, res) => {
4964
4856
  if (!guard(req, res)) return;
4965
4857
  const parsedBody = await readBridgeBodyForHandler(req, res);
@@ -4971,19 +4863,16 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4971
4863
  writeBridgeJson(res, 400, {
4972
4864
  ok: false,
4973
4865
  code: "preset-delete-rejected",
4974
- message: "缺少预设 id"
4866
+ message: "缺少模块 id"
4975
4867
  });
4976
4868
  return;
4977
4869
  }
4978
- const descriptor = findDescriptor();
4979
- const value = descriptor?.value ?? {};
4980
- const base = descriptor?.base ?? {};
4981
- const active = typeof value.presetTemplate === "string" ? value.presetTemplate : typeof base.presetTemplate === "string" ? base.presetTemplate : void 0;
4982
- if (typeof active === "string" && active.length > 0 && active === id) {
4870
+ const activeDir = editDir(req);
4871
+ if (activeDir.length > 0 && basename(activeDir) === id) {
4983
4872
  writeBridgeJson(res, 400, {
4984
4873
  ok: false,
4985
4874
  code: "preset-in-use",
4986
- message: `预设「${id}」正在使用中,请先切换其他预设再删除`
4875
+ message: `模块「${id}」正在使用中,请先切换其他模块再删除`
4987
4876
  });
4988
4877
  return;
4989
4878
  }
@@ -4996,7 +4885,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4996
4885
  });
4997
4886
  return;
4998
4887
  }
4999
- await refreshPresetList();
4888
+ if (!await refreshPresetList(res, id)) return;
5000
4889
  writeBridgeJson(res, 200, {
5001
4890
  ok: true,
5002
4891
  value: { id }
@@ -5005,7 +4894,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5005
4894
  }),
5006
4895
  sctx.webServer.register({
5007
4896
  kind: "exact",
5008
- path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.presetClone,
4897
+ path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.moduleClone,
5009
4898
  handler: async (req, res) => {
5010
4899
  if (!guard(req, res)) return;
5011
4900
  const parsedBody = await readBridgeBodyForHandler(req, res);
@@ -5017,7 +4906,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5017
4906
  writeBridgeJson(res, 400, {
5018
4907
  ok: false,
5019
4908
  code: "preset-clone-rejected",
5020
- message: "缺少预设 id"
4909
+ message: "缺少模块 id"
5021
4910
  });
5022
4911
  return;
5023
4912
  }
@@ -5030,7 +4919,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5030
4919
  });
5031
4920
  return;
5032
4921
  }
5033
- await refreshPresetList();
4922
+ if (!await refreshPresetList(res, result.id)) return;
5034
4923
  writeBridgeJson(res, 200, {
5035
4924
  ok: true,
5036
4925
  value: { id: result.id }
@@ -5039,7 +4928,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5039
4928
  }),
5040
4929
  sctx.webServer.register({
5041
4930
  kind: "exact",
5042
- path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.presetDuplicate,
4931
+ path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.moduleDuplicate,
5043
4932
  handler: async (req, res) => {
5044
4933
  if (!guard(req, res)) return;
5045
4934
  const parsedBody = await readBridgeBodyForHandler(req, res);
@@ -5051,11 +4940,11 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5051
4940
  writeBridgeJson(res, 400, {
5052
4941
  ok: false,
5053
4942
  code: "preset-duplicate-rejected",
5054
- message: "缺少预设 id"
4943
+ message: "缺少模块 id"
5055
4944
  });
5056
4945
  return;
5057
4946
  }
5058
- const result = duplicateUserPreset(id);
4947
+ const result = duplicateUserModule(id);
5059
4948
  if (!result.ok) {
5060
4949
  writeBridgeJson(res, 400, {
5061
4950
  ok: false,
@@ -5064,7 +4953,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5064
4953
  });
5065
4954
  return;
5066
4955
  }
5067
- await refreshPresetList();
4956
+ if (!await refreshPresetList(res, result.id)) return;
5068
4957
  writeBridgeJson(res, 200, {
5069
4958
  ok: true,
5070
4959
  value: { id: result.id }
@@ -5073,7 +4962,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5073
4962
  }),
5074
4963
  sctx.webServer.register({
5075
4964
  kind: "exact",
5076
- path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.presetOpen,
4965
+ path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.moduleOpen,
5077
4966
  handler: async (req, res) => {
5078
4967
  if (!guard(req, res)) return;
5079
4968
  const parsedBody = await readBridgeBodyForHandler(req, res);
@@ -5085,11 +4974,11 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5085
4974
  writeBridgeJson(res, 400, {
5086
4975
  ok: false,
5087
4976
  code: "preset-open-rejected",
5088
- message: "缺少预设 id"
4977
+ message: "缺少模块 id"
5089
4978
  });
5090
4979
  return;
5091
4980
  }
5092
- const result = openPresetLocation(id);
4981
+ const result = openModuleLocation(id);
5093
4982
  if (!result.ok) {
5094
4983
  writeBridgeJson(res, 400, {
5095
4984
  ok: false,
@@ -5104,17 +4993,109 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5104
4993
  });
5105
4994
  }
5106
4995
  }),
4996
+ sctx.webServer.register({
4997
+ kind: "exact",
4998
+ path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.moduleEnable,
4999
+ handler: async (req, res) => {
5000
+ if (!guard(req, res)) return;
5001
+ const parsedBody = await readBridgeBodyForHandler(req, res);
5002
+ if (parsedBody === void 0) return;
5003
+ const { body } = parsedBody;
5004
+ const record = body ?? {};
5005
+ const id = typeof record.id === "string" ? record.id.trim() : "";
5006
+ if (id.length === 0) {
5007
+ writeBridgeJson(res, 400, {
5008
+ ok: false,
5009
+ code: "preset-enable-rejected",
5010
+ message: "缺少模块 id"
5011
+ });
5012
+ return;
5013
+ }
5014
+ if (typeof record.enabled !== "boolean") {
5015
+ writeBridgeJson(res, 400, {
5016
+ ok: false,
5017
+ code: "preset-enable-rejected",
5018
+ message: "enabled 必须是布尔值"
5019
+ });
5020
+ return;
5021
+ }
5022
+ if (record.enabled && !moduleDirExists(userModulesDir(), id)) {
5023
+ writeBridgeJson(res, 400, {
5024
+ ok: false,
5025
+ code: "preset-enable-rejected",
5026
+ message: `模块 ${id} 不存在`
5027
+ });
5028
+ return;
5029
+ }
5030
+ const enabled = record.enabled;
5031
+ const run = moduleOrderQueue.then(async () => {
5032
+ try {
5033
+ if (enabled) {
5034
+ const changed = appendModuleConfigOrder(userModulesDir(), id);
5035
+ if (!changed.includes(id) && !enabledModuleIds(userModulesDir()).includes(id) && readModuleConfigOrder(userModulesDir(), id).entries.length > 0) changed.push(id);
5036
+ for (const changedId of changed) if (!await runOverridesChange(res, changedId)) return;
5037
+ }
5038
+ setModuleEnabled(userModulesDir(), id, enabled);
5039
+ if (!await runOverridesChange(res)) return;
5040
+ writeBridgeJson(res, 200, {
5041
+ ok: true,
5042
+ value: { enabled: enabledModuleIds(userModulesDir()) }
5043
+ });
5044
+ } catch (error) {
5045
+ writeModuleOrderError(res, error);
5046
+ }
5047
+ });
5048
+ moduleOrderQueue = run.catch(() => {});
5049
+ await run;
5050
+ }
5051
+ }),
5052
+ sctx.webServer.register({
5053
+ kind: "exact",
5054
+ path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.moduleConfigOrder,
5055
+ handler: async (req, res) => {
5056
+ if (!guard(req, res)) return;
5057
+ const parsed = await readBridgeBodyForHandler(req, res);
5058
+ if (parsed === void 0) return;
5059
+ const body = parsed.body;
5060
+ const writing = isRecord(body) && (body.entries !== void 0 || body.expectedRevision !== void 0);
5061
+ if (body !== void 0 && (!isRecord(body) || Object.keys(body).some((key) => key !== "moduleId" && key !== "entries" && key !== "expectedRevision") || body.moduleId !== void 0 && (typeof body.moduleId !== "string" || readEditTarget(body.moduleId) !== body.moduleId) || writing && (typeof body.expectedRevision !== "string" || !SHA256_HEX_RE.test(body.expectedRevision) || !Array.isArray(body.entries) || body.entries.some((entry) => !isRecord(entry) || Object.keys(entry).some((key) => key !== "moduleId" && key !== "configId") || typeof entry.moduleId !== "string" || readEditTarget(entry.moduleId) !== entry.moduleId || typeof entry.configId !== "string" || entry.configId.length === 0 || entry.configId.length > 256)))) {
5062
+ writeBridgeJson(res, 400, {
5063
+ ok: false,
5064
+ code: "module-config-order-invalid",
5065
+ message: "排序只接受配置身份列表和读取时的版本"
5066
+ });
5067
+ return;
5068
+ }
5069
+ const run = moduleOrderQueue.then(async () => {
5070
+ try {
5071
+ const moduleId = isRecord(body) ? body.moduleId : void 0;
5072
+ if (writing) {
5073
+ const changedIds = saveModuleConfigOrder(userModulesDir(), body.expectedRevision, body.entries, moduleId);
5074
+ for (const id of changedIds) if (!await runOverridesChange(res, id)) return;
5075
+ }
5076
+ writeBridgeJson(res, 200, {
5077
+ ok: true,
5078
+ value: readModuleConfigOrder(userModulesDir(), moduleId)
5079
+ });
5080
+ } catch (error) {
5081
+ writeModuleOrderError(res, error);
5082
+ }
5083
+ });
5084
+ moduleOrderQueue = run.catch(() => {});
5085
+ await run;
5086
+ }
5087
+ }),
5107
5088
  sctx.webServer.register({
5108
5089
  kind: "exact",
5109
5090
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.charactersImport,
5110
5091
  handler: async (req, res) => {
5111
5092
  if (!guard(req, res)) return;
5112
- const dir = getPresetConfigsDir?.() ?? "";
5093
+ const dir = editDir(req) ?? "";
5113
5094
  if (dir.length === 0) {
5114
5095
  writeBridgeJson(res, 400, {
5115
5096
  ok: false,
5116
5097
  code: "preset-dir-unavailable",
5117
- message: "presetDir 未配置"
5098
+ message: "模块目录未配置"
5118
5099
  });
5119
5100
  return;
5120
5101
  }
@@ -5142,11 +5123,11 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5142
5123
  if (!guardPresetWrite(dir, res)) return;
5143
5124
  try {
5144
5125
  const files = await expandPresetSource(request.params.sourceId === void 0 ? readBridgeFiles(record.files) : assetSources.read(request.params.sourceId));
5145
- if (getPresetConfigsDir?.() !== dir) {
5126
+ if (editDir(req) !== dir) {
5146
5127
  writeBridgeJson(res, 409, {
5147
5128
  ok: false,
5148
5129
  code: "characters-preview-stale",
5149
- message: "当前预设已切换,请重新预览"
5130
+ message: "当前模块已切换,请重新预览"
5150
5131
  });
5151
5132
  return;
5152
5133
  }
@@ -5162,8 +5143,8 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5162
5143
  });
5163
5144
  return;
5164
5145
  }
5165
- const root = canonicalPresetRoot(userPresetsDir());
5166
- if (assertPresetDirectory(root, basename(dir)) !== realpathSync(dir)) throw new Error("当前预设不属于可写的官方预设根");
5146
+ const root = canonicalPresetRoot(userModulesDir());
5147
+ if (assertModuleDirectory(root, basename(dir)) !== realpathSync(dir)) throw new Error("当前模块不属于可写的官方模块根");
5167
5148
  const cards = canonicalPresetRoot(charactersDir(root), true);
5168
5149
  let targetId = request.params.targetId ?? prepared.spec.id;
5169
5150
  if (request.params.targetId === void 0 && request.params.overwrite !== true) {
@@ -5276,12 +5257,12 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5276
5257
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.charactersList,
5277
5258
  handler: async (req, res) => {
5278
5259
  if (!guard(req, res)) return;
5279
- const dir = getPresetConfigsDir?.() ?? "";
5260
+ const dir = editDir(req) ?? "";
5280
5261
  if (dir.length === 0) {
5281
5262
  writeBridgeJson(res, 400, {
5282
5263
  ok: false,
5283
5264
  code: "preset-dir-unavailable",
5284
- message: "presetDir 未配置"
5265
+ message: "模块目录未配置"
5285
5266
  });
5286
5267
  return;
5287
5268
  }
@@ -5296,12 +5277,12 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5296
5277
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.charactersDelete,
5297
5278
  handler: async (req, res) => {
5298
5279
  if (!guard(req, res)) return;
5299
- const dir = getPresetConfigsDir?.() ?? "";
5280
+ const dir = editDir(req) ?? "";
5300
5281
  if (dir.length === 0) {
5301
5282
  writeBridgeJson(res, 400, {
5302
5283
  ok: false,
5303
5284
  code: "preset-dir-unavailable",
5304
- message: "presetDir 未配置"
5285
+ message: "模块目录未配置"
5305
5286
  });
5306
5287
  return;
5307
5288
  }
@@ -5339,12 +5320,12 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5339
5320
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.charactersApply,
5340
5321
  handler: async (req, res) => {
5341
5322
  if (!guard(req, res)) return;
5342
- const dir = getPresetConfigsDir?.() ?? "";
5323
+ const dir = editDir(req) ?? "";
5343
5324
  if (dir.length === 0) {
5344
5325
  writeBridgeJson(res, 400, {
5345
5326
  ok: false,
5346
5327
  code: "preset-dir-unavailable",
5347
- message: "presetDir 未配置"
5328
+ message: "模块目录未配置"
5348
5329
  });
5349
5330
  return;
5350
5331
  }
@@ -5371,7 +5352,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5371
5352
  });
5372
5353
  return;
5373
5354
  }
5374
- runOverridesChange();
5355
+ if (!await runOverridesChange(res, basename(dir))) return;
5375
5356
  writeBridgeJson(res, 200, {
5376
5357
  ok: true,
5377
5358
  value: {
@@ -5386,12 +5367,12 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5386
5367
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.charactersRemove,
5387
5368
  handler: async (req, res) => {
5388
5369
  if (!guard(req, res)) return;
5389
- const dir = getPresetConfigsDir?.() ?? "";
5370
+ const dir = editDir(req) ?? "";
5390
5371
  if (dir.length === 0) {
5391
5372
  writeBridgeJson(res, 400, {
5392
5373
  ok: false,
5393
5374
  code: "preset-dir-unavailable",
5394
- message: "presetDir 未配置"
5375
+ message: "模块目录未配置"
5395
5376
  });
5396
5377
  return;
5397
5378
  }
@@ -5418,7 +5399,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5418
5399
  });
5419
5400
  return;
5420
5401
  }
5421
- runOverridesChange();
5402
+ if (!await runOverridesChange(res, basename(dir))) return;
5422
5403
  writeBridgeJson(res, 200, {
5423
5404
  ok: true,
5424
5405
  value: {
@@ -5433,12 +5414,12 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5433
5414
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.subagentToolPolicy,
5434
5415
  handler: async (req, res) => {
5435
5416
  if (!guard(req, res)) return;
5436
- const dir = getPresetConfigsDir?.() ?? "";
5417
+ const dir = editDir(req) ?? "";
5437
5418
  if (dir.length === 0) {
5438
5419
  writeBridgeJson(res, 400, {
5439
5420
  ok: false,
5440
5421
  code: "preset-dir-unavailable",
5441
- message: "presetDir 未配置"
5422
+ message: "模块目录未配置"
5442
5423
  });
5443
5424
  return;
5444
5425
  }
@@ -5446,12 +5427,12 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5446
5427
  if (parsedBody === void 0) return;
5447
5428
  const { body } = parsedBody;
5448
5429
  const record = body ?? {};
5449
- if (!guardPresetIdentity(record, dir, res)) return;
5430
+ if (!guardPresetIdentity(req, record, dir, res)) return;
5450
5431
  if (record.policy === void 0) {
5451
5432
  try {
5452
5433
  writeBridgeJson(res, 200, {
5453
5434
  ok: true,
5454
- value: { policy: loadPresetSpec(dir).subagentToolPolicy ?? null }
5435
+ value: { policy: loadModuleSpec(dir).subagentToolPolicy ?? null }
5455
5436
  });
5456
5437
  } catch (error) {
5457
5438
  if (writeLayerSettingsError(res, error)) return;
@@ -5477,7 +5458,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5477
5458
  });
5478
5459
  return;
5479
5460
  }
5480
- if (!guardPresetIdentity(record, dir, res)) return;
5461
+ if (!guardPresetIdentity(req, record, dir, res)) return;
5481
5462
  withPresetDoc(dir, (doc) => {
5482
5463
  if (isEmpty) doc.deleteIn(["subagentToolPolicy"]);
5483
5464
  else {
@@ -5485,7 +5466,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5485
5466
  appendPresetModules(doc, ["subagent-tool-policy"]);
5486
5467
  }
5487
5468
  });
5488
- runOverridesChange();
5469
+ if (!await runOverridesChange(res, basename(dir))) return;
5489
5470
  writeBridgeJson(res, 200, {
5490
5471
  ok: true,
5491
5472
  value: {
@@ -5507,12 +5488,12 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5507
5488
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.subagentToolPolicyPreview,
5508
5489
  handler: async (req, res) => {
5509
5490
  if (!guard(req, res)) return;
5510
- const dir = getPresetConfigsDir?.() ?? "";
5491
+ const dir = editDir(req) ?? "";
5511
5492
  if (dir.length === 0) {
5512
5493
  writeBridgeJson(res, 400, {
5513
5494
  ok: false,
5514
5495
  code: "preset-dir-unavailable",
5515
- message: "presetDir 未配置"
5496
+ message: "模块目录未配置"
5516
5497
  });
5517
5498
  return;
5518
5499
  }
@@ -5521,12 +5502,12 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5521
5502
  const { body } = parsedBody;
5522
5503
  const record = body ?? {};
5523
5504
  try {
5524
- const spec = loadPresetSpec(dir);
5505
+ const spec = loadModuleSpec(dir);
5525
5506
  if (spec.subagentToolPolicy === void 0 || spec.subagentToolPolicy === null) {
5526
5507
  writeBridgeJson(res, 400, {
5527
5508
  ok: false,
5528
5509
  code: "subagent-tool-policy-missing",
5529
- message: "当前预设未配置 subagentToolPolicy"
5510
+ message: "当前模块未配置 subagentToolPolicy"
5530
5511
  });
5531
5512
  return;
5532
5513
  }
@@ -5560,19 +5541,19 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5560
5541
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.engineCapability,
5561
5542
  handler: async (req, res) => {
5562
5543
  if (!guard(req, res)) return;
5563
- const dir = getPresetConfigsDir?.() ?? "";
5544
+ const dir = editDir(req) ?? "";
5564
5545
  if (dir.length === 0) {
5565
5546
  writeBridgeJson(res, 400, {
5566
5547
  ok: false,
5567
5548
  code: "preset-dir-unavailable",
5568
- message: "presetDir 未配置"
5549
+ message: "模块目录未配置"
5569
5550
  });
5570
5551
  return;
5571
5552
  }
5572
5553
  const parsedBody = await readBridgeBodyForHandler(req, res);
5573
5554
  if (parsedBody === void 0) return;
5574
5555
  const body = asRecord(parsedBody.body);
5575
- if (!guardPresetIdentity(body, dir, res)) return;
5556
+ if (!guardPresetIdentity(req, body, dir, res)) return;
5576
5557
  const action = body.action;
5577
5558
  const id = action === "create" || action === "remove" ? body.capabilityId : action === "create-recipe" ? body.recipeId : void 0;
5578
5559
  if (action !== "create" && action !== "remove" && action !== "create-recipe" || typeof id !== "string" || id.trim().length === 0 || id.length > 128) {
@@ -5585,8 +5566,8 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5585
5566
  }
5586
5567
  if (!guardPresetWrite(dir, res)) return;
5587
5568
  const run = capabilityQueue.then(async () => {
5588
- if (!guardPresetIdentity(body, dir, res)) return;
5589
- const file = join(dir, "preset.yml");
5569
+ if (!guardPresetIdentity(req, body, dir, res)) return;
5570
+ const file = join(dir, MODULE_DEFINITION_FILE);
5590
5571
  let original;
5591
5572
  try {
5592
5573
  original = readFileSync(file, "utf8");
@@ -5597,7 +5578,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5597
5578
  action: "create-recipe",
5598
5579
  recipeId: id.trim()
5599
5580
  });
5600
- if (result.changed) afterCapabilityChange?.();
5581
+ if (result.changed && !await finishPresetChange(res, () => afterCapabilityChange?.(basename(dir)))) return;
5601
5582
  writeBridgeJson(res, 200, {
5602
5583
  ok: true,
5603
5584
  value: result
@@ -5605,7 +5586,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5605
5586
  } catch (error) {
5606
5587
  if (original !== void 0) try {
5607
5588
  atomicWriteTextFile(file, original);
5608
- invalidatePresetSpec(dir);
5589
+ invalidateModuleSpec(dir);
5609
5590
  } catch {}
5610
5591
  writeBridgeJson(res, 409, {
5611
5592
  ok: false,
@@ -5821,18 +5802,48 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5821
5802
  return { invalidateDescriptor: () => invalidateCachedDescriptor() };
5822
5803
  }
5823
5804
  //#endregion
5805
+ //#region src/host/module-tool-target.ts
5806
+ /**
5807
+ * 工具目标来自执行 Agent 的**运行时配装记录**:这个 Agent 实际装上了哪几层提示词。
5808
+ *
5809
+ * 不再查询宿主 `agentPresets.composedPreset()`——模块与官方预设已解耦,会话绑定哪个
5810
+ * 官方预设与本插件的写入目标无关(那正是「装的是 A 的提示词、工具却写进 B」的成因)。
5811
+ * 装了多层时取启用表里的第一个,与配装顺序同源。
5812
+ *
5813
+ * 目标 id 只来自装配记录(宿主 `agent/created` 事件建立),**不取自工具参数或模型输出**;
5814
+ * 落点仍由 `assertModuleDirectory` 做存在性、链接与身份校验。
5815
+ */
5816
+ function resolveModuleToolTarget(exec, root, moduleIdsOf) {
5817
+ const agent = exec.agent;
5818
+ const id = agent === void 0 ? void 0 : moduleIdsOf(agent.id)[0];
5819
+ if (id === void 0) throw new Error("当前会话未配装提示词层:请先在模块页启用至少一个模块");
5820
+ return {
5821
+ id,
5822
+ root,
5823
+ dir: assertModuleDirectory(root, id)
5824
+ };
5825
+ }
5826
+ /** 定义已经保存时,不把重建失败伪装成整个写入未发生。 */
5827
+ async function rebuildSavedPreset(host, id) {
5828
+ try {
5829
+ await host.rebuild(id);
5830
+ } catch (error) {
5831
+ throw new Error(`模块 ${id} 已保存,但重建失败:${String(error)}`, { cause: error });
5832
+ }
5833
+ }
5834
+ //#endregion
5824
5835
  //#region src/runtime/character-tools.ts
5825
5836
  const text$2 = (text) => [{
5826
5837
  type: "text",
5827
5838
  text
5828
5839
  }];
5829
- /** 注册角色卡库模型工具;返回 disposer,随 character-tools 预设模块生命周期清理。 */
5840
+ /** 注册角色卡库模型工具;返回 disposer,随 character-tools 模块生命周期清理。 */
5830
5841
  function registerCharacterTools(ctx, host) {
5831
5842
  const fiber = ctx.inject(["tools"], (toolsCtx) => {
5832
5843
  const disposers = [];
5833
5844
  disposers.push(toolsCtx.tools.register(defineTool({
5834
5845
  name: "character_list",
5835
- description: "列出角色卡库:每张卡(id / 名称 / 描述 / 是否已导入当前预设)。导入角色卡、应用到当前预设或移除前先调用本工具获取 id。",
5846
+ description: "列出角色卡库:每张卡(id / 名称 / 描述 / 是否已导入当前模块)。导入角色卡、应用到当前模块或移除前先调用本工具获取 id。",
5836
5847
  parameters: {},
5837
5848
  output: {
5838
5849
  schema: {
@@ -5868,16 +5879,19 @@ function registerCharacterTools(ctx, host) {
5868
5879
  },
5869
5880
  render: (_args, value) => text$2(JSON.stringify(value.characters))
5870
5881
  },
5871
- execute: async () => ({ characters: listCharacterCards(host.presetRoot(), host.templateName()) })
5882
+ execute: async (_args, exec) => {
5883
+ const target = host.target(exec);
5884
+ return { characters: listCharacterCards(target.root, target.id) };
5885
+ }
5872
5886
  })));
5873
5887
  disposers.push(toolsCtx.tools.register(defineTool({
5874
5888
  name: "character_import",
5875
- description: "导入一张 SillyTavern 角色卡或自包含原生角色片段到角色卡库:接收 JSON / YAML 文本内容(可先读取文件)。PNG 角色卡请让用户从 UI 角色管理页导入。导入后需调用 character_apply 应用到当前预设。",
5889
+ description: "导入一张 SillyTavern 角色卡或自包含原生角色片段到角色卡库:接收 JSON / YAML 文本内容(可先读取文件)。PNG 角色卡请让用户从 UI 角色管理页导入。导入后需调用 character_apply 应用到当前模块。",
5876
5890
  parameters: {
5877
5891
  name: {
5878
5892
  type: "string",
5879
5893
  required: true,
5880
- description: "角色卡文件名(不含 .json 扩展名),将作为预设/角色卡 id 基础。"
5894
+ description: "角色卡文件名(不含 .json 扩展名),将作为模块/角色卡 id 基础。"
5881
5895
  },
5882
5896
  content: {
5883
5897
  type: "string",
@@ -5900,10 +5914,11 @@ function registerCharacterTools(ctx, host) {
5900
5914
  }
5901
5915
  }
5902
5916
  },
5903
- render: (_args, value) => text$2(`角色卡已入库:${value.name}(id=${value.id})。调用 character_apply 可应用到当前预设。`)
5917
+ render: (_args, value) => text$2(`角色卡已入库:${value.name}(id=${value.id})。调用 character_apply 可应用到当前模块。`)
5904
5918
  },
5905
- execute: async (args) => {
5906
- const result = importCharacterCard(host.presetRoot(), [{
5919
+ execute: async (args, exec) => {
5920
+ const target = host.target(exec);
5921
+ const result = importCharacterCard(target.root, [{
5907
5922
  path: `${args.name}.json`,
5908
5923
  content: args.content
5909
5924
  }]);
@@ -5916,7 +5931,7 @@ function registerCharacterTools(ctx, host) {
5916
5931
  })));
5917
5932
  disposers.push(toolsCtx.tools.register(defineTool({
5918
5933
  name: "character_apply",
5919
- description: "把角色卡库中一张角色卡的参数(角色设定 / 系统提示 / 开场白 / 世界书 / 提示词配置)合并进当前激活预设(promptConfigs 带 chara-<id>- 前缀防冲突,params 合并,meta.importedCharacters 记录),并立即重建生成目录。重复应用幂等。",
5934
+ description: "把角色卡库中一张角色卡的参数(角色设定 / 系统提示 / 开场白 / 世界书 / 提示词配置)合并进当前会话绑定的模块(promptConfigs 带 chara-<id>- 前缀防冲突,params 合并,meta.importedCharacters 记录),并立即重建生成目录。重复应用幂等。",
5920
5935
  parameters: { id: {
5921
5936
  type: "string",
5922
5937
  required: true,
@@ -5937,12 +5952,13 @@ function registerCharacterTools(ctx, host) {
5937
5952
  }
5938
5953
  }
5939
5954
  },
5940
- render: (_args, value) => text$2(`已导入到当前预设(${value.count} 条配置),生成目录已重建。`)
5955
+ render: (_args, value) => text$2(`已导入到当前模块(${value.count} 条配置),生成目录已重建。`)
5941
5956
  },
5942
- execute: async (args) => {
5943
- const result = applyCharacterToPreset(host.presetRoot(), host.templateName(), args.id);
5957
+ execute: async (args, exec) => {
5958
+ const target = host.target(exec);
5959
+ const result = applyCharacterToPreset(target.root, target.id, args.id);
5944
5960
  if (!result.ok) throw new Error(result.message);
5945
- host.rebuild();
5961
+ await rebuildSavedPreset(host, target.id);
5946
5962
  return {
5947
5963
  id: args.id,
5948
5964
  count: result.count
@@ -5951,7 +5967,7 @@ function registerCharacterTools(ctx, host) {
5951
5967
  })));
5952
5968
  disposers.push(toolsCtx.tools.register(defineTool({
5953
5969
  name: "character_remove",
5954
- description: "从当前激活预设移除一张已导入角色卡的参数(删 chara-<id>- 前缀配置、该卡声明的 params 键、meta.importedCharacters 除名),并立即重建生成目录。角色卡库条目不受影响。",
5970
+ description: "从当前会话绑定的模块移除一张已导入角色卡的参数(删 chara-<id>- 前缀配置、该卡声明的 params 键、meta.importedCharacters 除名),并立即重建生成目录。角色卡库条目不受影响。",
5955
5971
  parameters: { id: {
5956
5972
  type: "string",
5957
5973
  required: true,
@@ -5972,12 +5988,13 @@ function registerCharacterTools(ctx, host) {
5972
5988
  }
5973
5989
  }
5974
5990
  },
5975
- render: (_args, value) => text$2(`已从当前预设移除(${value.count} 条配置),生成目录已重建。`)
5991
+ render: (_args, value) => text$2(`已从当前模块移除(${value.count} 条配置),生成目录已重建。`)
5976
5992
  },
5977
- execute: async (args) => {
5978
- const result = removeCharacterFromPreset(host.presetRoot(), host.templateName(), args.id);
5993
+ execute: async (args, exec) => {
5994
+ const target = host.target(exec);
5995
+ const result = removeCharacterFromPreset(target.root, target.id, args.id);
5979
5996
  if (!result.ok) throw new Error(result.message);
5980
- host.rebuild();
5997
+ await rebuildSavedPreset(host, target.id);
5981
5998
  return {
5982
5999
  id: args.id,
5983
6000
  count: result.count
@@ -5986,7 +6003,7 @@ function registerCharacterTools(ctx, host) {
5986
6003
  })));
5987
6004
  disposers.push(toolsCtx.tools.register(defineTool({
5988
6005
  name: "character_delete",
5989
- description: "从角色卡库删除一张角色卡(含其转换参数与头像)。已导入当前预设的参数不受影响(如需清理请先调用 character_remove)。",
6006
+ description: "从角色卡库删除一张角色卡(含其转换参数与头像)。已导入当前模块的参数不受影响(如需清理请先调用 character_remove)。",
5990
6007
  parameters: { id: {
5991
6008
  type: "string",
5992
6009
  required: true,
@@ -6003,8 +6020,8 @@ function registerCharacterTools(ctx, host) {
6003
6020
  },
6004
6021
  render: (_args, value) => text$2(`角色卡 ${value.id} 已从库中删除。`)
6005
6022
  },
6006
- execute: async (args) => {
6007
- const result = deleteCharacterCard(host.presetRoot(), args.id);
6023
+ execute: async (args, exec) => {
6024
+ const result = deleteCharacterCard(host.target(exec).root, args.id);
6008
6025
  if (!result.ok) throw new Error(result.message);
6009
6026
  return { id: args.id };
6010
6027
  }
@@ -6023,37 +6040,38 @@ const text$1 = (text) => [{
6023
6040
  type: "text",
6024
6041
  text
6025
6042
  }];
6026
- /** 从条目 id 解析来源角色卡(chara-<cardId>- 前缀,cardId 可含连字符:遍历库目录取最长匹配)。 */
6027
- function sourceCardId(presetRoot, entryId) {
6028
- const root = join(presetRoot, ".characters");
6029
- if (!entryId.startsWith("chara-")) return void 0;
6043
+ /** 从条目 id 解析来源角色卡(并入前缀 + cardId,cardId 可含连字符:遍历库目录取最长匹配)。
6044
+ * 前缀**两种都认**:新写入是 `module-<id>-`,用户既有的老条目仍是 `chara-<id>-`。 */
6045
+ function sourceCardId(moduleRoot, entryId) {
6046
+ const root = charactersDir(moduleRoot);
6047
+ if (!entryId.startsWith("module-") && !entryId.startsWith("chara-")) return void 0;
6030
6048
  let best;
6031
6049
  try {
6032
6050
  for (const dir of readdirSync(root, { withFileTypes: true })) {
6033
6051
  if (!dir.isDirectory() || dir.name.startsWith(".")) continue;
6034
- if (entryId.startsWith(`chara-${dir.name}-`) && (best === void 0 || dir.name.length > best.length)) best = dir.name;
6052
+ if ((entryId.startsWith(`module-${dir.name}-`) || entryId.startsWith(`chara-${dir.name}-`)) && (best === void 0 || dir.name.length > best.length)) best = dir.name;
6035
6053
  }
6036
6054
  } catch {}
6037
6055
  return best;
6038
6056
  }
6039
- /** 注册世界书条目级模型工具;返回 disposer,随 world-book-tools 预设模块生命周期清理。 */
6057
+ /** 注册世界书条目级模型工具;返回 disposer,随 world-book-tools 模块生命周期清理。 */
6040
6058
  function registerWorldBookTools(ctx, host) {
6041
6059
  const fiber = ctx.inject(["tools"], (toolsCtx) => {
6042
6060
  const disposers = [];
6043
- /** note 归属写入:角色卡条目 → 卡记忆;其他 → 预设记忆。 */
6044
- const writeNote = (entryId, note) => {
6061
+ /** note 归属写入:角色卡条目 → 卡记忆;其他 → 模块记忆。 */
6062
+ const writeNote = (target, entryId, note) => {
6045
6063
  if (note === void 0 || note.trim().length === 0) return;
6046
- const cardId = entryId !== void 0 ? sourceCardId(host.presetRoot(), entryId) : void 0;
6064
+ const cardId = entryId !== void 0 ? sourceCardId(target.root, entryId) : void 0;
6047
6065
  if (cardId !== void 0) {
6048
- appendMemoryFile(join(host.presetRoot(), ".characters", cardId, "memory.md"), note, "# 角色记忆");
6066
+ appendMemoryFile(join(target.root, ".characters", cardId, "memory.md"), note, "# 角色记忆");
6049
6067
  try {
6050
- syncImportedCharacterMemory(host.presetRoot(), basename(host.activeDir()), cardId);
6068
+ syncImportedCharacterMemory(target.root, target.id, cardId);
6051
6069
  } catch {}
6052
- } else appendMemoryFile(join(host.activeDir(), "memory.md"), note, "# 本地记忆");
6070
+ } else appendMemoryFile(join(target.dir, "memory.md"), note, "# 本地记忆");
6053
6071
  };
6054
6072
  disposers.push(toolsCtx.tools.register(defineTool({
6055
6073
  name: "world_book_list",
6056
- description: "列出当前预设的世界书条目(world-book 策略配置:id / 名称 / 关键字 / 常驻 / 启用)。世界书 = 上下文条目:无 keys 的全局条目每次注入,有 keys 条目命中聊天内容才注入。增删改前先调用本工具获取 id。",
6074
+ description: "列出当前模块的世界书条目(world-book 策略配置:id / 名称 / 关键字 / 常驻 / 启用)。世界书 = 上下文条目:无 keys 的全局条目每次注入,有 keys 条目命中聊天内容才注入。增删改前先调用本工具获取 id。",
6057
6075
  parameters: {},
6058
6076
  output: {
6059
6077
  schema: {
@@ -6086,8 +6104,8 @@ function registerWorldBookTools(ctx, host) {
6086
6104
  },
6087
6105
  render: (_args, value) => text$1(JSON.stringify(value.entries))
6088
6106
  },
6089
- execute: async () => {
6090
- return { entries: listWorldBookEntries(host.activeDir()).map((config) => {
6107
+ execute: async (_args, exec) => {
6108
+ return { entries: listWorldBookEntries(host.target(exec).dir).map((config) => {
6091
6109
  const params = config.params;
6092
6110
  const keys = params?.keys;
6093
6111
  return {
@@ -6102,7 +6120,7 @@ function registerWorldBookTools(ctx, host) {
6102
6120
  })));
6103
6121
  disposers.push(toolsCtx.tools.register(defineTool({
6104
6122
  name: "world_book_upsert",
6105
- description: "新增或更新当前预设的一条世界书条目(world-book 策略配置):按 id 更新(不存在则新增,id 自动生成 lore-<n>)。constant=true 常驻注入;否则命中 keys(或 secondaryKeys)任一关键字注入;无 keys 条目按全局每次注入。note 可选:写入来源角色卡的持久记忆(memory.md,条目 id 带 chara-<卡>- 前缀时)或预设记忆——持久记忆跨会话跟随角色卡(与 session_var 会话变量的临时状态不同,适合长期关系记录)。写盘后立即重建生成目录。",
6123
+ description: "新增或更新当前模块的一条世界书条目(world-book 策略配置):按 id 更新(不存在则新增,id 自动生成 lore-<n>)。constant=true 常驻注入;否则命中 keys(或 secondaryKeys)任一关键字注入;无 keys 条目按全局每次注入。note 可选:写入来源角色卡的持久记忆(memory.md,条目 id 带 chara-<卡>- 前缀时)或模块记忆——持久记忆跨会话跟随角色卡(与 session_var 会话变量的临时状态不同,适合长期关系记录)。写盘后立即重建生成目录。",
6106
6124
  parameters: {
6107
6125
  id: {
6108
6126
  type: "string",
@@ -6142,7 +6160,7 @@ function registerWorldBookTools(ctx, host) {
6142
6160
  },
6143
6161
  note: {
6144
6162
  type: "string",
6145
- description: "可选:操作笔记,写入来源角色卡持久记忆(memory.md,跨会话跟随角色卡)或预设记忆。"
6163
+ description: "可选:操作笔记,写入来源角色卡持久记忆(memory.md,跨会话跟随角色卡)或模块记忆。"
6146
6164
  }
6147
6165
  },
6148
6166
  output: {
@@ -6162,8 +6180,8 @@ function registerWorldBookTools(ctx, host) {
6162
6180
  },
6163
6181
  render: (_args, value) => text$1(`世界书条目 ${value.id} 已保存(当前共 ${value.count} 条),生成目录已重建。`)
6164
6182
  },
6165
- execute: async (args) => {
6166
- const dir = host.activeDir();
6183
+ execute: async (args, exec) => {
6184
+ const target = host.target(exec);
6167
6185
  const targetId = args.id !== void 0 && args.id.length > 0 ? args.id : `lore-${Date.now().toString(36)}`;
6168
6186
  const entry = buildWorldBookEntry({
6169
6187
  id: targetId,
@@ -6175,12 +6193,12 @@ function registerWorldBookTools(ctx, host) {
6175
6193
  keys: Array.isArray(args.keys) && args.keys.length > 0 ? args.keys : void 0,
6176
6194
  secondaryKeys: Array.isArray(args.secondaryKeys) && args.secondaryKeys.length > 0 ? args.secondaryKeys : void 0
6177
6195
  });
6178
- const count = upsertWorldBookEntry(dir, {
6196
+ const count = upsertWorldBookEntry(target.dir, {
6179
6197
  ...entry,
6180
6198
  id: targetId
6181
6199
  });
6182
- writeNote(targetId, typeof args.note === "string" ? args.note : "");
6183
- host.rebuild();
6200
+ writeNote(target, targetId, typeof args.note === "string" ? args.note : "");
6201
+ await rebuildSavedPreset(host, target.id);
6184
6202
  return {
6185
6203
  id: targetId,
6186
6204
  count
@@ -6189,7 +6207,7 @@ function registerWorldBookTools(ctx, host) {
6189
6207
  })));
6190
6208
  disposers.push(toolsCtx.tools.register(defineTool({
6191
6209
  name: "world_book_delete",
6192
- description: "删除当前预设的一条世界书条目(world_book_list 获取 id)。note 可选:写入来源角色卡持久记忆(memory.md,跨会话跟随角色卡)或预设记忆。删除后立即重建生成目录。",
6210
+ description: "删除当前模块的一条世界书条目(world_book_list 获取 id)。note 可选:写入来源角色卡持久记忆(memory.md,跨会话跟随角色卡)或模块记忆。删除后立即重建生成目录。",
6193
6211
  parameters: {
6194
6212
  id: {
6195
6213
  type: "string",
@@ -6198,7 +6216,7 @@ function registerWorldBookTools(ctx, host) {
6198
6216
  },
6199
6217
  note: {
6200
6218
  type: "string",
6201
- description: "可选:操作笔记,写入来源角色卡持久记忆(memory.md)或预设记忆。"
6219
+ description: "可选:操作笔记,写入来源角色卡持久记忆(memory.md)或模块记忆。"
6202
6220
  }
6203
6221
  },
6204
6222
  output: {
@@ -6218,11 +6236,11 @@ function registerWorldBookTools(ctx, host) {
6218
6236
  },
6219
6237
  render: (_args, value) => text$1(`世界书条目 ${value.id} 已删除(剩余 ${value.count} 条),生成目录已重建。`)
6220
6238
  },
6221
- execute: async (args) => {
6222
- const dir = host.activeDir();
6223
- const keptCount = deleteWorldBookEntry(dir, args.id);
6224
- writeNote(args.id, typeof args.note === "string" ? args.note : "");
6225
- host.rebuild();
6239
+ execute: async (args, exec) => {
6240
+ const target = host.target(exec);
6241
+ const keptCount = deleteWorldBookEntry(target.dir, args.id);
6242
+ writeNote(target, args.id, typeof args.note === "string" ? args.note : "");
6243
+ await rebuildSavedPreset(host, target.id);
6226
6244
  return {
6227
6245
  id: args.id,
6228
6246
  count: keptCount
@@ -6243,13 +6261,13 @@ const text = (value) => [{
6243
6261
  type: "text",
6244
6262
  text: value
6245
6263
  }];
6246
- /** 注册会话变量模型工具;返回 disposer,随 session-var-tools 预设模块生命周期清理。 */
6264
+ /** 注册会话变量模型工具;返回 disposer,随 session-var-tools 模块生命周期清理。 */
6247
6265
  function registerSessionVarTools(ctx) {
6248
6266
  const fiber = ctx.inject(["tools"], (toolsCtx) => {
6249
6267
  const disposers = [];
6250
6268
  disposers.push(toolsCtx.tools.register(defineTool({
6251
6269
  name: "session_var",
6252
- description: "会话变量管理(SillyTavern setvar/getvar 语义):list 查看当前会话全部变量、get 读取、set 设置、clear 清除。提示词/世界书文本中的模板占位符(形如 {变量名},即双花括号包裹变量名)会在注入时替换为会话变量值(会话级覆盖预设默认值);适合维护角色状态(如 {心情}、{接受度} 等)。注意:会话变量仅存于当前会话(结束即失);跨会话长期记忆请用 world_book 工具的 note 参数写入角色卡记忆(持久,跟随角色卡)。",
6270
+ description: "会话变量管理(SillyTavern setvar/getvar 语义):list 查看当前会话全部变量、get 读取、set 设置、clear 清除。提示词/世界书文本中的模板占位符(形如 {变量名},即双花括号包裹变量名)会在注入时替换为会话变量值(会话级覆盖模块默认值);适合维护角色状态(如 {心情}、{接受度} 等)。注意:会话变量仅存于当前会话(结束即失);跨会话长期记忆请用 world_book 工具的 note 参数写入角色卡记忆(持久,跟随角色卡)。",
6253
6271
  parameters: {
6254
6272
  action: {
6255
6273
  type: "string",
@@ -6354,22 +6372,11 @@ function registerSessionVarTools(ctx) {
6354
6372
  //#endregion
6355
6373
  //#region src/runtime/tui.ts
6356
6374
  /** dsh-tui 全局开关:键名与 settings 路径一致(settings mutate)。 */
6357
- const TUI_GLOBAL_SWITCHES = [["writePreset", "启用锚定预设"]];
6358
- /** dsh-tui 参数开关:写激活预设 preset.yml(settings 不再承载引擎参数)。 */
6359
- const TUI_PARAM_SWITCHES = [
6360
- ["injectPrompt", "锚定确认后注入 preset.md"],
6361
- ["firstTurnAnchor", "追加任务引导"],
6362
- ["guideEnabled", "每轮引导(独立开关;缺省关闭)"],
6363
- ["firstTurnCustom", "使用自定义引导(首句)"],
6364
- ["guideCustom", "使用自定义引导(每轮)"]
6365
- ];
6366
- /** 参数显示行(从激活预设 preset.yml params 读)。 */
6375
+ const TUI_GLOBAL_SWITCHES = [["writePreset", "模块运行总开关"]];
6376
+ /** dsh-tui 参数开关:写激活模块 preset.yml(settings 不再承载引擎参数)。 */
6377
+ const TUI_PARAM_SWITCHES = Object.entries(ENGINE_PARAM_DEFINITIONS).filter(([, definition]) => definition.kind === "boolean").map(([key]) => [key, key]);
6378
+ /** 参数显示行(从激活模块 preset.yml params 读)。 */
6367
6379
  const TUI_PARAM_TEXT_LINES = [
6368
- [
6369
- "firstTurnText",
6370
- "firstTurnText",
6371
- "(空 = 按任务自动选择)"
6372
- ],
6373
6380
  [
6374
6381
  "modelProvider",
6375
6382
  "modelProvider",
@@ -6423,20 +6430,20 @@ const TUI_PARAM_TEXT_LINES = [
6423
6430
  ];
6424
6431
  /** 把布尔开关渲染成 dsh-tui 命令输出。 */
6425
6432
  /** 实际生效配置:生成目录优先(引擎加载源),settings 覆盖层作回退。 */
6426
- function resolvePromptConfigs(presetDir, fallback) {
6427
- if (presetDir === void 0 || presetDir.length === 0) return fallback;
6433
+ function resolvePromptConfigs(moduleDir, fallback) {
6434
+ if (moduleDir === void 0 || moduleDir.length === 0) return fallback;
6428
6435
  try {
6429
- const actual = listPromptConfigSpecs(join(presetDir, "prompt-configs"));
6436
+ const actual = listPromptConfigSpecs(join(moduleDir, MODULE_CONFIGS_DIR));
6430
6437
  return actual.length > 0 ? actual : fallback;
6431
6438
  } catch {
6432
6439
  return fallback;
6433
6440
  }
6434
6441
  }
6435
- /** 激活预设参数(status 显示与参数开关来源;settings 不再承载引擎参数)。 */
6436
- function readPresetParams(presetDir) {
6437
- if (presetDir === void 0 || presetDir.length === 0) return {};
6442
+ /** 激活模块参数(status 显示与参数开关来源;settings 不再承载引擎参数)。 */
6443
+ function readPresetParams(moduleDir) {
6444
+ if (moduleDir === void 0 || moduleDir.length === 0) return {};
6438
6445
  try {
6439
- const spec = loadPresetSpec(presetDir);
6446
+ const spec = loadModuleSpec(moduleDir);
6440
6447
  const params = resolvePresetParams(spec, {});
6441
6448
  if (Array.isArray(spec.promptConfigs)) params.promptConfigs = spec.promptConfigs;
6442
6449
  return params;
@@ -6446,7 +6453,7 @@ function readPresetParams(presetDir) {
6446
6453
  }
6447
6454
  function renderTuiStatus(source, params, promptConfigs) {
6448
6455
  const onOff = (value) => value ? "开" : "关";
6449
- const paramBoolean = (key) => params[key] === true;
6456
+ const paramBoolean = (key) => (params[key] ?? ENGINE_PARAM_DEFINITIONS[key]?.defaultValue) === true;
6450
6457
  const paramText = (key) => {
6451
6458
  const value = params[key];
6452
6459
  return typeof value === "string" && value.length > 0 ? value : "";
@@ -6458,9 +6465,9 @@ function renderTuiStatus(source, params, promptConfigs) {
6458
6465
  return `${key.padEnd(22)}${onOff(typeof value === "boolean" ? value : false)} ${label}`;
6459
6466
  }),
6460
6467
  ...TUI_PARAM_SWITCHES.map(([key, label]) => {
6461
- return `${key.padEnd(22)}${onOff(paramBoolean(key))} ${label}(预设)`;
6468
+ return `${key.padEnd(22)}${onOff(paramBoolean(key))} ${label}(模块)`;
6462
6469
  }),
6463
- "锚点文本:",
6470
+ "模型请求参数:",
6464
6471
  ...TUI_PARAM_TEXT_LINES.map(([key, label, emptyText]) => {
6465
6472
  const text = paramText(key);
6466
6473
  return ` ${label.padEnd(26)}${text.length > 0 ? text : emptyText}`;
@@ -6561,12 +6568,15 @@ function registerTuiCommand(ctx, ns, getSource, getModelsState, getModelCatalog,
6561
6568
  handler: async (invocation) => {
6562
6569
  const usage = () => ({
6563
6570
  kind: "error",
6564
- text: "用法:/prompt-tool status\n /prompt-tool on|off|toggle <writePreset|injectPrompt|firstTurnAnchor|firstTurnCustom|guideEnabled|guideCustom>\n /prompt-tool skill <frontmatter 技能名> on|off|toggle\n /prompt-tool config <id>(id 可含空格)\n /prompt-tool config <id> on|off|toggle"
6571
+ text: `用法:/prompt-tool status
6572
+ /prompt-tool on|off|toggle <${[...TUI_GLOBAL_SWITCHES, ...TUI_PARAM_SWITCHES].map(([key]) => key).join("|")}>\n /prompt-tool skill <frontmatter 技能名> on|off|toggle
6573
+ /prompt-tool config <id>(id 可含空格)
6574
+ /prompt-tool config <id> on|off|toggle`
6565
6575
  });
6566
6576
  const persistPresetParam = async (key, value) => {
6567
6577
  if (savePresetParam === void 0) return {
6568
6578
  kind: "error",
6569
- text: `无法保存 ${key}:预设参数保存回调不可用`
6579
+ text: `无法保存 ${key}:模块参数保存回调不可用`
6570
6580
  };
6571
6581
  try {
6572
6582
  await savePresetParam(key, value);
@@ -6580,9 +6590,9 @@ function registerTuiCommand(ctx, ns, getSource, getModelsState, getModelCatalog,
6580
6590
  };
6581
6591
  const tokens = invocation.rawInput.trim().split(/\s+/).filter((token) => token.length > 0);
6582
6592
  const source = getSource();
6583
- const presetDir = getPresetConfigsDir?.();
6584
- const params = readPresetParams(presetDir);
6585
- const promptConfigs = resolvePromptConfigs(presetDir, Array.isArray(params.promptConfigs) ? params.promptConfigs : []);
6593
+ const moduleDir = getPresetConfigsDir?.();
6594
+ const params = readPresetParams(moduleDir);
6595
+ const promptConfigs = resolvePromptConfigs(moduleDir, Array.isArray(params.promptConfigs) ? params.promptConfigs : []);
6586
6596
  if (tokens.length === 0 || tokens[0] === "status") {
6587
6597
  const detection = getModelsState();
6588
6598
  const catalog = await getModelCatalog();
@@ -6656,7 +6666,7 @@ ${renderConfigDetail(getSource(), id, resolvePromptConfigs(getPresetConfigsDir?.
6656
6666
  if (action !== "on" && action !== "off" && action !== "toggle") return usage();
6657
6667
  if (key === void 0 || !TUI_GLOBAL_SWITCHES.some(([candidate]) => candidate === key) && !TUI_PARAM_SWITCHES.some(([candidate]) => candidate === key)) return usage();
6658
6668
  const globalSwitch = TUI_GLOBAL_SWITCHES.some(([candidate]) => candidate === key);
6659
- const currentValue = globalSwitch ? source[key] : params[key];
6669
+ const currentValue = globalSwitch ? source[key] : params[key] ?? ENGINE_PARAM_DEFINITIONS[key]?.defaultValue;
6660
6670
  if (typeof currentValue !== "boolean") return {
6661
6671
  kind: "error",
6662
6672
  text: `${key} 不是布尔开关,不能这样切换`
@@ -7013,175 +7023,265 @@ function createSkillsRuntime(ctx, options = {}) {
7013
7023
  };
7014
7024
  }
7015
7025
  //#endregion
7016
- //#region src/host/preset-registry.ts
7026
+ //#region src/runtime/agent-assembly.ts
7017
7027
  /**
7018
- * 共享引擎模块的受管配置字段:值按**历史语义**相对 `<预设根>/.engine/` 书写
7019
- * (如 `../<id>/prompt-configs`),装配期换算为绝对 `file://`。
7028
+ * agent-assembly — 运行时配装通道。
7029
+ *
7030
+ * 目标形态:模块不再靠「注册进官方 agent-presets + 由宿主 Loader 挂载插件树」装配,
7031
+ * 而是插件在**每个 Agent 自己的 scope** 里建一份装配。
7032
+ *
7033
+ * 判据(与原模块形态逐项对齐,能力一项不删):
7034
+ * - 挂的是插件自己的引擎模块(`engine/*.mjs` 的 `apply(ctx, config)`),
7035
+ * 不再声明 `agent.cordis.yml` 这类「给宿主 Loader 用的组合本体」;
7036
+ * - 官方工具行(`dsh-tool-*` 等)由宿主提供;模块人设通过 systemPrompt 注册,
7037
+ * 本通道**不**装第二棵官方插件树——那些包也不在插件包的解析面内;
7038
+ * - 物化目录优先:`configs/`(原 `prompt-configs/`)、`custom-tools/` 存在就按它装配,
7039
+ * 缺失时回退 `module.yml` 内嵌,两条路径的切片同源(`resolveModuleFacts`)。
7040
+ *
7041
+ * 与官方挂载并存时不会重复:官方树里没有引擎行,引擎贡献只由本通道提供。
7020
7042
  */
7021
- const MANAGED_FIELDS = {
7022
- "prompt-config-engine.mjs": ["configsDir", "strategyDir"],
7023
- "tool-config-engine.mjs": ["configsDir"],
7024
- "subagent-tool-policy.mjs": ["policyFile"],
7025
- "declared-triggers.mjs": ["triggersFile"]
7043
+ /** 引擎模块的受管配置字段:值按历史语义相对模块根书写(如 `../<id>/custom-tools`)。 */
7044
+ const MANAGED_FIELDS = [
7045
+ "configsDir",
7046
+ "strategyDir",
7047
+ "policyFile",
7048
+ "triggersFile"
7049
+ ];
7050
+ /** 私有工具适配器依赖的服务;适配器仍复用包内引擎入口。 */
7051
+ const PRIVATE_SERVICES = {
7052
+ "character-tools": "pt-character-tools",
7053
+ "world-book-tools": "pt-world-book-tools",
7054
+ "session-var-tools": "pt-session-var-tools"
7026
7055
  };
7027
- /** 需要注入预设根基准的引擎模块——只有它们的 configContract 声明了 `presetRoot` 键。 */
7028
- const PRESET_ROOT_MODULES = /* @__PURE__ */ new Set([
7029
- "prompt-config-engine.mjs",
7030
- "tool-config-engine.mjs",
7031
- "declared-triggers.mjs"
7032
- ]);
7033
- function isJsExpr(value) {
7034
- return typeof value === "object" && value !== null && typeof value.__jsExpr === "string";
7035
- }
7036
- /** 行名引用的共享引擎模块名;只认插件包的说明符前缀(不再兼容旧布局)。 */
7037
- function engineModuleOf(name) {
7038
- return typeof name === "string" && name.startsWith("dsh-plugin-prompt-tool/engine/") ? name.slice(PRESET_ENGINE_PREFIX.length) : void 0;
7039
- }
7040
- /** config 层换算:受管字段 → 绝对 `file://`;需要基准的引擎行注入预设根。 */
7041
- function absolutizeConfig(config, engineModule, context) {
7042
- const fields = engineModule === void 0 ? void 0 : MANAGED_FIELDS[engineModule];
7043
- const out = {};
7044
- for (const [key, value] of Object.entries(config)) if (fields !== void 0 && fields.includes(key) && typeof value === "string" && value.startsWith(".")) out[key] = pathToFileURL(resolve(context.engineDir, value)).href;
7045
- else out[key] = value;
7046
- if (engineModule !== void 0 && PRESET_ROOT_MODULES.has(engineModule)) out.presetRoot = context.presetRootUrl;
7056
+ /**
7057
+ * 受管字段的路径换算:`configsDir` / `strategyDir` / `policyFile` / `triggersFile`
7058
+ * 一律解析到**当前模块目录内的真实位置**——声明怎么写都按模块目录作基准,三种形态同结果:
7059
+ * - `./configs`(新形态)→ `<模块目录>/configs`;
7060
+ * - `../<id>/triggers.yml`(历史形态:`ENGINE_MANAGED_PATHS` 的改写成这样,语义是
7061
+ * 「从历史引擎位置 `<模块根>/.engine/` 回到 `<模块根>/<id>/`」)→ `<模块目录>/triggers.yml`;
7062
+ * - `../subagent-tools/policy.yml`(组合源初值)→ 相对模块内历史引擎位置,
7063
+ * 同样落进当前模块目录;引擎继续负责资源越界校验。
7064
+ * 已是 `file:` URL 的值原样保留。
7065
+ */
7066
+ function absolutizeManagedFields(config, moduleDir) {
7067
+ const out = { ...config };
7068
+ for (const field of MANAGED_FIELDS) {
7069
+ const value = out[field];
7070
+ if (typeof value !== "string" || value.length === 0) continue;
7071
+ if (value.startsWith("file:")) continue;
7072
+ const base = value.startsWith("../") && !value.startsWith(`../${basename(moduleDir)}/`) ? join(moduleDir, ".engine") : moduleDir;
7073
+ out[field] = pathToFileURL(resolve(base, value)).href;
7074
+ }
7047
7075
  return out;
7048
7076
  }
7049
- /** 行级换算:只认行对象自己的 `name` 与 `config`,不误伤 config 里的同名业务字段。 */
7050
- function absolutizeRow(row, context) {
7051
- if (row === null || typeof row !== "object" || Array.isArray(row) || isJsExpr(row)) return row;
7052
- const source = row;
7053
- const engineModule = engineModuleOf(source.name);
7054
- const out = {};
7055
- for (const [key, item] of Object.entries(source)) if (key === "name" && typeof item === "string") out[key] = item.startsWith(".") && engineModule === void 0 ? pathToFileURL(resolve(context.presetDir, item)).href : item;
7056
- else if (key === "config" && Array.isArray(item)) out[key] = item.map((child) => absolutizeRow(child, context));
7057
- else if (key === "config" && item !== null && typeof item === "object" && !isJsExpr(item)) out[key] = absolutizeConfig(item, engineModule, context);
7058
- else out[key] = item;
7059
- return out;
7077
+ /** 该模块 id 是不是插件自带的引擎能力(官方工具行与未知 id 一律跳过)。 */
7078
+ function engineModuleFile(engineDir, id) {
7079
+ const file = join(engineDir, `${id}.mjs`);
7080
+ return existsSync(file) ? file : void 0;
7060
7081
  }
7061
7082
  /**
7062
- * 装配期换算:把组合里的本地引用换成 Loader 与引擎都能解析的绝对形态。
7083
+ * 读一份模块的装配输入:切片、引擎模块、所需宿主能力。
7063
7084
  *
7064
- * 三件事,全部只发生在内存注册定义,正本 `agent.cordis.yml` 一字不改:
7065
- * 1. **本地模块说明符**(`name` 以 `.` 开头):相对预设目录 → 绝对 `file://`。注册用的 Loader
7066
- * 树必须以**宿主锚点**建(`register()` 取调用方 ctx 的 `baseUrl` 解析每一行),否则组合内的
7067
- * 包名行会从预设目录起解析、整份注册被拒;锚点归宿主后,预设目录内的相对说明符必须在此换算。
7068
- * 2. **受管配置字段**(`configsDir` / `strategyDir` / `policyFile` / `triggersFile`):按历史语义
7069
- * 相对 `<预设根>/.engine/` 解析为绝对 `file://`。引擎由插件包提供后其 `import.meta.url` 不再
7070
- * 位于该目录,`file://` 也是对全部受管字段一致有效的唯一形态。
7071
- * 3. **预设根基准**:给需要 `templateFile` 越界校验的引擎行注入 `presetRoot`。
7072
- *
7073
- * 共享引擎行本身写 `dsh-plugin-prompt-tool/engine/*.mjs`(由生成侧 `rewritePresetEngineReferences`
7074
- * 产出),本函数不改写它。**不兼容旧预设**:仍写 `./engine/`、`../.engine/` 的预设按普通本地
7075
- * 说明符处理,指向已不再物化的目录并因此挂载失败,需重建后重新物化。
7085
+ * 纯读缝(只依赖包内引擎与模块目录),供挂载与回归测试共用;`hasService` 是宿主能力探针。
7076
7086
  */
7077
- function absolutizeLocalModules(rows, presetDir) {
7078
- if (!Array.isArray(rows)) return rows;
7079
- const presetRoot = resolve(presetDir, "..");
7080
- const context = {
7081
- presetDir,
7082
- engineDir: join(presetRoot, ".engine"),
7083
- presetRootUrl: `${pathToFileURL(presetRoot).href}/`
7084
- };
7085
- return rows.map((row) => absolutizeRow(row, context));
7086
- }
7087
- function readDefinition(root, id) {
7088
- const preset = loadPresetSpec(join(root, id));
7089
- const file = join(root, id, "agent.cordis.yml");
7090
- const source = readFileSync(file, "utf8");
7091
- const plugins = parse(source, {
7092
- version: "1.2",
7093
- customTags: [{
7094
- tag: "tag:yaml.org,2002:js",
7095
- resolve: (__jsExpr) => ({ __jsExpr })
7096
- }]
7087
+ async function prepareAssembly(moduleRoot, moduleId, hasService) {
7088
+ assertPresetId(moduleId);
7089
+ const moduleDir = resolveModuleDir(moduleId, moduleRoot);
7090
+ const spec = loadModuleSpec(moduleDir);
7091
+ const facts = resolveModuleFacts(spec, moduleDir);
7092
+ if (facts.effectiveModules === null) throw new Error(`模块 ${moduleId} 的模块声明无效,无法配装`);
7093
+ const configsByModule = facts.effectiveConfigs ?? {};
7094
+ const promptDir = join(moduleDir, MODULE_CONFIGS_DIR);
7095
+ const specs = existsSync(promptDir) ? loadPromptConfigFiles(pathToFileURL(promptDir + sep)) : spec.promptConfigs ?? [];
7096
+ const promptConfig = absolutizeManagedFields(configsByModule["prompt-config-engine"] ?? {}, moduleDir);
7097
+ const configs = createPromptConfigs(specs, {
7098
+ sourceModuleId: moduleId,
7099
+ configOrder: readConfigOrder(spec.configOrder),
7100
+ templateBaseUrl: pathToFileURL(join(moduleRoot, ".engine", "prompt-config-engine.mjs")),
7101
+ templatePresetRoot: pathToFileURL(moduleRoot + sep),
7102
+ strategyDir: typeof promptConfig.strategyDir === "string" ? promptConfig.strategyDir.replace(/\/?$/, "/") : void 0
7097
7103
  });
7098
- const problem = entryListProblem(plugins);
7099
- if (problem !== void 0) throw new Error(`预设 ${preset.id}:${problem}`);
7100
- const order = typeof preset.order === "number" && Number.isFinite(preset.order) ? preset.order : void 0;
7104
+ const exclusiveConfigs = configs.filter((config) => {
7105
+ if (config === null || typeof config !== "object") return false;
7106
+ const entry = config;
7107
+ return entry.enabled !== false && entry.params?.complete === true;
7108
+ });
7109
+ const personaComplete = spec.persona?.complete === true;
7110
+ if (exclusiveConfigs.length > 1 || personaComplete && exclusiveConfigs.length > 0) throw new Error(`模块 ${moduleId} 有多个生效的「独占」段(顶层人设${personaComplete ? "已" : "未"}开启),同一模块只能有一个:请先关闭其一`);
7111
+ const engineDir = packageEngineDir();
7112
+ const services = /* @__PURE__ */ new Set();
7113
+ const modules = [];
7114
+ for (const id of /* @__PURE__ */ new Set([...facts.effectiveModules, ...facts.rowIds])) {
7115
+ if (id === "prompt-config-engine") continue;
7116
+ const privateService = PRIVATE_SERVICES[id];
7117
+ if (privateService !== void 0) {
7118
+ if (!hasService(privateService)) throw new Error(`配装所需能力不可用:${privateService}`);
7119
+ services.add(privateService);
7120
+ }
7121
+ const file = engineModuleFile(engineDir, id);
7122
+ if (file === void 0) continue;
7123
+ const module = await import(pathToFileURL(file).href);
7124
+ if (typeof module.apply !== "function") throw new Error(`引擎能力缺少 apply:${id}`);
7125
+ for (const dependency of module.inject ?? []) services.add(dependency);
7126
+ const config = absolutizeManagedFields(configsByModule[id] ?? {}, moduleDir);
7127
+ if (id === "tool-config-engine" || id === "declared-triggers") config.presetRoot = pathToFileURL(moduleRoot + sep).href;
7128
+ module.configContract?.parse?.(config, id);
7129
+ modules.push({
7130
+ id,
7131
+ apply: module.apply,
7132
+ config
7133
+ });
7134
+ }
7135
+ if (configs.length > 0) {
7136
+ services.add("systemPrompt");
7137
+ services.add("tools");
7138
+ services.add("llm");
7139
+ }
7140
+ if (spec.persona !== void 0) services.add("systemPrompt");
7141
+ for (const name of services) if (!hasService(name)) throw new Error(`配装所需宿主能力不可用:${name}`);
7101
7142
  return {
7102
- id,
7103
- ...typeof preset.name === "string" && preset.name.length > 0 ? { name: preset.name } : {},
7104
- ...typeof preset.description === "string" ? { description: preset.description } : {},
7105
- ...order === void 0 ? {} : { order },
7106
- plugins: absolutizeLocalModules(plugins, join(root, id))
7143
+ configs,
7144
+ modules,
7145
+ services,
7146
+ moduleId,
7147
+ persona: spec.persona
7107
7148
  };
7108
7149
  }
7109
- /** Register generated user preset compositions with the v0.1.7 registry. */
7110
- function createPresetRegistrySync(ctx, root) {
7111
- const registrations = /* @__PURE__ */ new Map();
7112
- let queue = Promise.resolve();
7113
- let closed = false;
7114
- const refresh = (forceIds = []) => {
7115
- const turn = queue.then(async () => {
7116
- if (closed) return;
7117
- const errors = [];
7118
- for (const [id, current] of registrations) try {
7119
- if (statSync(join(root, id), { throwIfNoEntry: false }) !== void 0) continue;
7120
- await current.dispose();
7121
- registrations.delete(id);
7122
- } catch (error) {
7123
- errors.push(error);
7124
- }
7125
- for (const preset of listPresets(root)) try {
7126
- const definition = readDefinition(root, preset.id);
7127
- const fingerprint = JSON.stringify(definition);
7128
- const current = registrations.get(preset.id);
7129
- if (current?.fingerprint === fingerprint && !forceIds.includes(preset.id)) continue;
7130
- const registry = ctx.agentPresets;
7131
- const register = async (candidate) => {
7132
- const dispose = await registry.register(candidate);
7133
- try {
7134
- const result = await registry.resolve(candidate.id);
7135
- if (result.broken !== void 0) throw new Error(result.broken);
7136
- return dispose;
7137
- } catch (error) {
7138
- await dispose();
7139
- throw error;
7140
- }
7141
- };
7142
- if (current !== void 0) {
7143
- await (await register({
7144
- ...definition,
7145
- id: `prompt-tool-candidate-${randomUUID()}`
7146
- }))();
7147
- await current.dispose();
7148
- registrations.delete(preset.id);
7149
- }
7150
- try {
7151
- const dispose = await register(definition);
7152
- registrations.set(preset.id, {
7153
- definition,
7154
- fingerprint,
7155
- dispose
7150
+ function createAgentAssembly(ctx, options) {
7151
+ const mounts = /* @__PURE__ */ new Map();
7152
+ const disposed = /* @__PURE__ */ new WeakSet();
7153
+ const queues = /* @__PURE__ */ new Map();
7154
+ let active = true;
7155
+ const warn = (error) => {
7156
+ const message = `prompt-tool: 运行时配装失败:${error instanceof Error ? error.message : String(error)}`;
7157
+ if (options.warn !== void 0) options.warn(message);
7158
+ else ctx.logger?.warn?.(message);
7159
+ };
7160
+ const serial = (sessionId, task) => {
7161
+ const next = (queues.get(sessionId) ?? Promise.resolve()).then(task);
7162
+ const settled = next.then(() => {}, () => {});
7163
+ queues.set(sessionId, settled);
7164
+ settled.then(() => {
7165
+ if (queues.get(sessionId) === settled) queues.delete(sessionId);
7166
+ });
7167
+ return next;
7168
+ };
7169
+ const release = async (sessionId, agent) => {
7170
+ const current = mounts.get(sessionId);
7171
+ if (current === void 0) return;
7172
+ if (agent !== void 0 && current.agent !== agent) return;
7173
+ mounts.delete(sessionId);
7174
+ await current.fiber.dispose();
7175
+ };
7176
+ const mount = async (agent, prepared) => {
7177
+ const services = /* @__PURE__ */ new Set();
7178
+ for (const item of prepared) for (const name of item.services) services.add(name);
7179
+ const fiber = agent.ctx.plugin({
7180
+ name: "prompt-tool-assembly",
7181
+ inject: [...services],
7182
+ apply: async (scopeCtx) => {
7183
+ for (const item of prepared) if (item.persona !== void 0) {
7184
+ const persona = item.persona;
7185
+ scopeCtx.systemPrompt.section({
7186
+ name: `prompt-tool:${item.moduleId}:persona-prefix`,
7187
+ order: scopeCtx.systemPrompt.getSectionOrder("DEPLOYMENT_PERSONA_PREFIX"),
7188
+ text: persona.prefix,
7189
+ complete: persona.complete === true
7156
7190
  });
7157
- } catch (error) {
7158
- if (current !== void 0) {
7159
- const dispose = await register(current.definition);
7160
- registrations.set(preset.id, {
7161
- ...current,
7162
- dispose
7163
- });
7164
- }
7165
- throw error;
7191
+ scopeCtx.systemPrompt.section({
7192
+ name: `prompt-tool:${item.moduleId}:persona-suffix`,
7193
+ order: scopeCtx.systemPrompt.getSectionOrder("DEPLOYMENT_PERSONA_SUFFIX"),
7194
+ text: persona.suffix ?? ""
7195
+ });
7196
+ if (persona.includeRuntimeContext === false) scopeCtx.systemPrompt.suppressRuntimeContext();
7197
+ }
7198
+ scopeCtx.effect(() => applyPromptConfigSources(scopeCtx, prepared.filter((item) => item.configs.length > 0).map((item) => ({
7199
+ sourceId: `module:${item.moduleId}`,
7200
+ configs: item.configs
7201
+ })), { prepend: true }));
7202
+ for (const item of prepared) for (const module of item.modules) {
7203
+ const result = await module.apply(scopeCtx, module.config);
7204
+ if (typeof result === "function") scopeCtx.effect(() => result, module.id);
7166
7205
  }
7167
- } catch (error) {
7168
- errors.push(error);
7169
- ctx.logger?.warn?.(`prompt-tool: 预设 ${preset.id} 注册刷新失败:${String(error)}`);
7170
7206
  }
7171
- if (errors.length > 0) throw new AggregateError(errors, "预设注册刷新失败");
7172
7207
  });
7173
- queue = turn.catch(() => {});
7174
- return turn;
7208
+ const disposeFiber = ctx.effect(() => () => fiber.dispose(), `prompt-tool assembly ${agent.id}`);
7209
+ try {
7210
+ await fiber;
7211
+ } catch (error) {
7212
+ await disposeFiber();
7213
+ throw error;
7214
+ }
7215
+ if (!active || disposed.has(agent)) {
7216
+ await disposeFiber();
7217
+ return;
7218
+ }
7219
+ mounts.set(agent.id, {
7220
+ agent,
7221
+ fiber: { dispose: async () => {
7222
+ await disposeFiber();
7223
+ } },
7224
+ prepared,
7225
+ moduleIds: prepared.map((item) => item.moduleId)
7226
+ });
7227
+ };
7228
+ const install = async (agent, refresh = false) => {
7229
+ if (!active || disposed.has(agent)) return;
7230
+ const previous = mounts.get(agent.id);
7231
+ if (!refresh && previous?.agent === agent) return;
7232
+ const prepared = [];
7233
+ for (const id of options.enabledModules()) prepared.push(await prepareAssembly(options.moduleRoot, id, (name) => agent.ctx.get(name) !== void 0));
7234
+ if (!active || disposed.has(agent)) return;
7235
+ await release(agent.id);
7236
+ try {
7237
+ await mount(agent, prepared);
7238
+ } catch (error) {
7239
+ if (previous?.agent === agent && active && !disposed.has(agent)) await mount(agent, previous.prepared);
7240
+ throw error;
7241
+ }
7175
7242
  };
7243
+ const listeners = [ctx.on("agent/created", async ({ agent }) => {
7244
+ await serial(agent.id, async () => {
7245
+ try {
7246
+ await install(agent);
7247
+ } catch (error) {
7248
+ warn(error);
7249
+ }
7250
+ });
7251
+ }), ctx.on("agent/disposed", ({ agent }) => {
7252
+ disposed.add(agent);
7253
+ serial(agent.id, () => release(agent.id, agent)).catch(warn);
7254
+ })];
7255
+ ctx.effect(() => () => {
7256
+ active = false;
7257
+ for (const remove of listeners) remove();
7258
+ return Promise.all(queues.values()).then(async () => {
7259
+ for (const id of mounts.keys()) await release(id);
7260
+ });
7261
+ }, "prompt-tool assembly runtime");
7262
+ try {
7263
+ for (const agent of ctx.agents.list()) serial(agent.id, () => install(agent)).catch(warn);
7264
+ } catch (error) {
7265
+ warn(error);
7266
+ }
7176
7267
  return {
7177
- refresh,
7268
+ settled: () => Promise.all(queues.values()).then(() => void 0),
7269
+ hasMounted: (sessionId) => mounts.has(sessionId),
7270
+ moduleIds: (sessionId) => mounts.get(sessionId)?.moduleIds ?? [],
7271
+ refresh: async (moduleId) => {
7272
+ if (!active) throw new Error("运行时配装已释放");
7273
+ const errors = (await Promise.allSettled(ctx.agents.list().map((agent) => serial(agent.id, async () => {
7274
+ const current = mounts.get(agent.id);
7275
+ if (moduleId !== void 0 && current !== void 0 && !current.moduleIds.includes(moduleId) && !options.enabledModules().includes(moduleId)) return;
7276
+ await install(agent, true);
7277
+ })))).filter((result) => result.status === "rejected");
7278
+ if (errors.length > 0) throw new AggregateError(errors.map((result) => result.reason), "模块已保存,但运行时配装更新失败");
7279
+ },
7178
7280
  dispose: async () => {
7179
- closed = true;
7180
- await queue;
7181
- const results = await Promise.allSettled([...registrations.values()].map((registration) => registration.dispose()));
7182
- registrations.clear();
7183
- const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
7184
- if (errors.length > 0) throw new AggregateError(errors, "预设注册释放失败");
7281
+ active = false;
7282
+ for (const remove of listeners) remove();
7283
+ await Promise.all(queues.values());
7284
+ for (const id of mounts.keys()) await release(id);
7185
7285
  }
7186
7286
  };
7187
7287
  }
@@ -7194,169 +7294,32 @@ const inject = [
7194
7294
  "llm",
7195
7295
  "subagents"
7196
7296
  ];
7197
- function readPromptFile(template, fallbackText) {
7198
- const text = loadPresetContent(template).presetText;
7199
- return text.length > 0 ? text : fallbackText;
7200
- }
7201
- function readAgents(template) {
7202
- return loadPresetContent(template).agentsText;
7203
- }
7204
- /** 生成目录内容文件(writePreset 落盘;大文本存文件而非 settings)。 */
7205
- function readGeneratedContent(presetDir, name) {
7206
- try {
7207
- return readFileSync(join(presetDir, name), "utf8");
7208
- } catch {
7209
- return "";
7210
- }
7211
- }
7212
7297
  function warn(ctx, message) {
7213
7298
  try {
7214
7299
  ctx.logger?.warn(message);
7215
7300
  } catch {}
7216
7301
  }
7217
7302
  function apply(ctx, configIn) {
7218
- const seededPresets = new Set(ensurePresetSeed(DEFAULT_PRESET_DIR).created);
7219
- const readConfig = () => ({
7220
- writePreset: configIn.writePreset.get(),
7221
- presetTemplate: configIn.presetTemplate.get(),
7222
- presetOrder: configIn.presetOrder.get(),
7223
- fallbackText: configIn.fallbackText.get()
7224
- });
7303
+ const seededPresets = new Set(ensurePresetSeed(MODULES_DIR).created);
7304
+ const readConfig = () => ({ writePreset: configIn.writePreset.get() });
7225
7305
  const config = readConfig();
7226
- let registrySync;
7227
- const refreshPresets = (forceIds) => registrySync?.refresh(forceIds) ?? Promise.resolve();
7306
+ /** 运行时配装通道的运行态:工具写入目标按它报告的「本 Agent 装了哪几层提示词」解析。 */
7307
+ let assembly;
7228
7308
  const modelsState = () => detectModels(ctx);
7229
7309
  const getModelsState = () => modelsState();
7230
- const initialTemplate = typeof config.presetTemplate === "string" && config.presetTemplate.length > 0 ? config.presetTemplate : DEFAULT_PRESET_ID;
7231
- const initialPresetDir = join(DEFAULT_PRESET_DIR, /^[a-zA-Z0-9_-]+$/.test(initialTemplate) ? initialTemplate : DEFAULT_PRESET_ID);
7232
- let initialParams = {};
7233
- let initialSpec;
7234
- try {
7235
- initialSpec = loadPresetSpec(resolvePresetDir(initialTemplate));
7236
- initialParams = resolvePresetParams(initialSpec, {});
7237
- } catch (error) {
7238
- warn(ctx, `prompt-tool: 激活预设参数读取失败(使用默认值):${error instanceof Error ? error.message : String(error)}`);
7239
- }
7240
- let current = readGeneratedContent(initialPresetDir, "preset.md") || readPromptFile(initialTemplate, config.fallbackText);
7241
- let currentAgents = readGeneratedContent(initialPresetDir, "agents.md") || readAgents(initialTemplate);
7242
- /** 从激活预设 preset.yml 重读引擎参数到 runtime(参数保存/切换后调用)。 */
7243
- const reloadPresetParams = () => {
7244
- let spec;
7245
- try {
7246
- spec = loadPresetSpec(resolvePresetDir(runtime.presetTemplate));
7247
- } catch (error) {
7248
- warn(ctx, `prompt-tool: 读取激活预设参数失败:${error instanceof Error ? error.message : String(error)}`);
7249
- return;
7250
- }
7251
- const params = resolvePresetParams(spec, {});
7252
- for (const key of ENGINE_PARAM_KEYS) runtime[key] = params[key];
7253
- runtime.firstTurnAnchor = params.firstTurnAnchor === true;
7254
- runtime.firstTurnText = asString(params.firstTurnText);
7255
- runtime.firstTurnCustom = params.firstTurnCustom === true;
7256
- runtime.guideText = asString(params.guideText);
7257
- runtime.guideCustom = params.guideCustom === true;
7258
- runtime.guideEnabled = typeof params.guideEnabled === "boolean" ? params.guideEnabled : void 0;
7259
- runtime.modelProvider = asString(params.modelProvider);
7260
- runtime.modelName = asString(params.modelName);
7261
- runtime.subagentModelProvider = asString(params.subagentModelProvider);
7262
- runtime.subagentModelName = asString(params.subagentModelName);
7263
- runtime.modelReasoningEffort = asString(params.modelReasoningEffort);
7264
- runtime.modelTemperature = asString(params.modelTemperature);
7265
- runtime.modelMaxTokens = asString(params.modelMaxTokens);
7266
- runtime.subagentReasoningEffort = asString(params.subagentReasoningEffort);
7267
- runtime.subagentTemperature = asString(params.subagentTemperature);
7268
- runtime.subagentMaxTokens = asString(params.subagentMaxTokens);
7269
- runtime.injectPrompt = params.injectPrompt !== false;
7270
- runtime.maxDepth = params.maxDepth;
7271
- runtime.firstTurnWord = asString(params.firstTurnWord) || void 0;
7272
- runtime.promptConfigs = Array.isArray(spec.promptConfigs) ? spec.promptConfigs : [];
7310
+ const runtime = { ...config };
7311
+ const materialize = (id) => {
7312
+ materializeModule(id, {
7313
+ moduleDir: MODULES_DIR,
7314
+ warn: (message) => warn(ctx, message)
7315
+ });
7273
7316
  };
7274
- /** 重建生成目录并刷新官方注册;writePreset 关闭时保留空组合。 */
7275
- const rebuildPreset = (initial = false) => {
7276
- reloadPresetParams();
7277
- if (runtime.writePreset) {
7278
- const presetPrompt = runtime.injectPrompt && current.length > 0 ? current : "";
7279
- const options = {
7280
- ...Object.fromEntries(ENGINE_PARAM_KEYS.map((key) => [key, runtime[key]])),
7281
- firstTurnAnchor: runtime.firstTurnAnchor,
7282
- firstTurnText: runtime.firstTurnText,
7283
- firstTurnCustom: runtime.firstTurnCustom,
7284
- guideText: runtime.guideText,
7285
- guideCustom: runtime.guideCustom,
7286
- guideEnabled: runtime.guideEnabled,
7287
- injectPrompt: runtime.injectPrompt,
7288
- modelProvider: runtime.modelProvider,
7289
- modelName: runtime.modelName,
7290
- subagentModelProvider: runtime.subagentModelProvider,
7291
- subagentModelName: runtime.subagentModelName,
7292
- modelReasoningEffort: runtime.modelReasoningEffort,
7293
- modelTemperature: runtime.modelTemperature,
7294
- modelMaxTokens: runtime.modelMaxTokens,
7295
- subagentReasoningEffort: runtime.subagentReasoningEffort,
7296
- subagentTemperature: runtime.subagentTemperature,
7297
- subagentMaxTokens: runtime.subagentMaxTokens,
7298
- maxDepth: runtime.maxDepth,
7299
- firstTurnWord: runtime.firstTurnWord,
7300
- agentsInstructionText: currentAgents,
7301
- presetDir: DEFAULT_PRESET_DIR,
7302
- presetOrder: runtime.presetOrder,
7303
- promptConfigs: runtime.promptConfigs,
7304
- presetTemplate: runtime.presetTemplate,
7305
- outputId: runtime.presetTemplate,
7306
- warn: (message) => warn(ctx, message)
7307
- };
7308
- for (const preset of listPresets()) {
7309
- if (preset.id === runtime.presetTemplate) continue;
7310
- const targetDir = join(DEFAULT_PRESET_DIR, preset.id);
7311
- if (!seededPresets.has(preset.id) && !readGeneratedContent(targetDir, "agent.cordis.yml").startsWith("# prompt-tool writePreset disabled")) continue;
7312
- try {
7313
- const spec = loadPresetSpec(resolvePresetDir(preset.id));
7314
- if (!(Array.isArray(spec.modules) || spec.params !== null && typeof spec.params === "object") && existsSync(join(targetDir, "agent.cordis.yml"))) continue;
7315
- } catch {}
7316
- try {
7317
- writePreset(readPromptFile(preset.id, runtime.fallbackText), {
7318
- ...resolvePresetParams(loadPresetSpec(resolvePresetDir(preset.id)), {}),
7319
- presetDir: DEFAULT_PRESET_DIR,
7320
- presetOrder: runtime.presetOrder,
7321
- presetTemplate: preset.id,
7322
- outputId: preset.id,
7323
- promptConfigs: [],
7324
- agentsInstructionText: ""
7325
- });
7326
- } catch (error) {
7327
- warn(ctx, `prompt-tool: 补建预设 ${preset.id} 失败(切换时可重试):${error instanceof Error ? error.message : String(error)}`);
7328
- }
7329
- }
7330
- if (!initial || seededPresets.has(runtime.presetTemplate) || readGeneratedContent(activePresetDir(), "agent.cordis.yml").startsWith("# prompt-tool writePreset disabled")) writePreset(presetPrompt, options);
7331
- seededPresets.clear();
7332
- } else {
7333
- let cleaned = 0;
7334
- for (const preset of listPresets()) {
7335
- const dir = join(DEFAULT_PRESET_DIR, preset.id);
7336
- for (const name of [
7337
- "prompt-configs",
7338
- "custom-tools",
7339
- "preset.md",
7340
- "agents.md",
7341
- "agents-instruction.md",
7342
- "engine"
7343
- ]) try {
7344
- rmSync(join(dir, name), {
7345
- recursive: true,
7346
- force: true
7347
- });
7348
- } catch {}
7349
- try {
7350
- writeFileSync(join(dir, "agent.cordis.yml"), "# prompt-tool writePreset disabled\n[]\n", "utf8");
7351
- } catch {}
7352
- cleaned += 1;
7353
- }
7354
- warn(ctx, `prompt-tool: writePreset 已关闭,清空 ${cleaned} 个预设目录的组合(preset.yml 参数保留)`);
7355
- }
7356
- refreshPresets(runtime.writePreset ? [runtime.presetTemplate] : listPresets().map((preset) => preset.id)).catch((error) => warn(ctx, `prompt-tool: 重建后的预设注册刷新失败:${String(error)}`));
7317
+ const rebuildPreset = async (id = basename(activeModuleDir())) => {
7318
+ materialize(id);
7319
+ await assembly?.refresh(id);
7357
7320
  };
7358
- /** 激活预设目录(内容按预设根 <template>/ 隔离;非法名回退 standard)。 */
7359
- const activePresetDir = () => join(DEFAULT_PRESET_DIR, /^[a-zA-Z0-9\u4e00-\u9fff_-]+$/.test(runtime.presetTemplate) ? runtime.presetTemplate : DEFAULT_PRESET_ID);
7321
+ /** 无请求目标时供TUI使用的模块;编辑器的显式ID始终单独解析。 */
7322
+ const activeModuleDir = () => resolveEditDir(MODULES_DIR) || (moduleDirExists(MODULES_DIR, "ponytail") ? join(MODULES_DIR, "ponytail") : "");
7360
7323
  const skillsRuntime = createSkillsRuntime(ctx);
7361
7324
  const settingsBridge = registerSettingsBridge(ctx, NS, getModelsState, () => ({
7362
7325
  skillsRoot: skillsRuntime.skillsRoot,
@@ -7370,139 +7333,12 @@ function apply(ctx, configIn) {
7370
7333
  readSkillContent: skillsRuntime.readContent,
7371
7334
  writeSkillContent: skillsRuntime.writeContent,
7372
7335
  patchSkillFolders: skillsRuntime.setFolders
7373
- }), () => "", skillsRuntime.invalidate, () => activePresetDir(), (scopes) => {
7374
- for (const scope of scopes) if (scope === "preset") current = readGeneratedContent(activePresetDir(), "preset.md");
7375
- else currentAgents = readGeneratedContent(activePresetDir(), "agents.md");
7376
- try {
7377
- rebuildPreset();
7378
- } catch (error) {
7379
- warn(ctx, `prompt-tool: preset import rebuild failed: ${String(error)}`);
7380
- }
7381
- }, () => {
7382
- try {
7383
- rebuildPreset();
7384
- } catch (error) {
7385
- warn(ctx, `prompt-tool: overrides rebuild failed: ${String(error)}`);
7386
- throw error;
7387
- }
7388
- }, async (id) => {
7389
- if (id === runtime.presetTemplate) {
7390
- current = readGeneratedContent(activePresetDir(), "preset.md");
7391
- currentAgents = readGeneratedContent(activePresetDir(), "agents.md");
7392
- reloadPresetParams();
7393
- }
7336
+ }), () => "", skillsRuntime.invalidate, (moduleId) => moduleId === void 0 ? activeModuleDir() : resolveEditDir(MODULES_DIR, moduleId), (_scopes, id) => rebuildPreset(id), (id) => id === void 0 ? assembly?.refresh() : rebuildPreset(id), async (id) => {
7394
7337
  skillsRuntime.invalidate();
7395
- await refreshPresets([id]);
7396
- }, () => {
7397
- rebuildPreset();
7398
- }, () => refreshPresets());
7338
+ await assembly?.refresh(id);
7339
+ }, (id) => rebuildPreset(id), () => assembly?.refresh());
7399
7340
  scheduleWebSurfaceRepair(ctx, (message) => warn(ctx, message));
7400
7341
  ctx.effect(() => ctx.on("llm/adapters-updated", () => invalidateModelCatalog(ctx)));
7401
- const runtime = {
7402
- ...Object.fromEntries(ENGINE_PARAM_KEYS.map((key) => [key, initialParams[key]])),
7403
- writePreset: config.writePreset,
7404
- presetTemplate: initialTemplate,
7405
- firstTurnAnchor: initialParams.firstTurnAnchor === true,
7406
- firstTurnText: asString(initialParams.firstTurnText),
7407
- firstTurnCustom: initialParams.firstTurnCustom === true,
7408
- guideText: asString(initialParams.guideText),
7409
- guideCustom: initialParams.guideCustom === true,
7410
- guideEnabled: typeof initialParams.guideEnabled === "boolean" ? initialParams.guideEnabled : void 0,
7411
- injectPrompt: initialParams.injectPrompt !== false,
7412
- modelProvider: asString(initialParams.modelProvider),
7413
- modelName: asString(initialParams.modelName),
7414
- subagentModelProvider: asString(initialParams.subagentModelProvider),
7415
- subagentModelName: asString(initialParams.subagentModelName),
7416
- modelReasoningEffort: asString(initialParams.modelReasoningEffort),
7417
- modelTemperature: asString(initialParams.modelTemperature),
7418
- modelMaxTokens: asString(initialParams.modelMaxTokens),
7419
- subagentReasoningEffort: asString(initialParams.subagentReasoningEffort),
7420
- subagentTemperature: asString(initialParams.subagentTemperature),
7421
- subagentMaxTokens: asString(initialParams.subagentMaxTokens),
7422
- maxDepth: initialParams.maxDepth,
7423
- firstTurnWord: asString(initialParams.firstTurnWord) || void 0,
7424
- presetOrder: config.presetOrder,
7425
- fallbackText: config.fallbackText,
7426
- promptConfigs: Array.isArray(initialSpec?.promptConfigs) ? initialSpec.promptConfigs : []
7427
- };
7428
- const agentPresetsNs = "agent-preset-registry";
7429
- let hostSettingsService;
7430
- const settingsDocument = (service, ns) => service.describe().find((item) => String(item.ns) === ns)?.value;
7431
- const readHostDefault = (value) => {
7432
- if (value === null || typeof value !== "object") return void 0;
7433
- const candidate = value.selectedDefault;
7434
- return typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
7435
- };
7436
- let agentPresetsService;
7437
- let modeSelectionWarned = false;
7438
- /**
7439
- * 官方 modeSelectionEnabled=false 时忽略 selectedDefault,使用部署 default。
7440
- */
7441
- const modeSelectionEnabled = (value) => {
7442
- if (value === null || typeof value !== "object") return void 0;
7443
- const flag = value.modeSelectionEnabled;
7444
- return typeof flag === "boolean" ? flag : void 0;
7445
- };
7446
- const warnModeSelectionOnce = () => {
7447
- if (modeSelectionWarned) return;
7448
- modeSelectionWarned = true;
7449
- warn(ctx, "prompt-tool: 宿主已关闭 agent-preset-registry 模式选择(modeSelectionEnabled=false):写入 selectedDefault 不影响新会话。请修改 profile 的 agent-preset-registry config.default,或重新开启模式选择。");
7450
- };
7451
- /**
7452
- * 生效默认预设(与官方 selectionPolicy 同源):开关关闭时 official 只认
7453
- * `config.default`,该值只有服务 getter 能给出;服务未就绪时返回 undefined,
7454
- * 由 agentPresets 的迟到回调再对齐一次。
7455
- */
7456
- const effectiveHostDefault = (value) => {
7457
- const document = value ?? (hostSettingsService === void 0 ? void 0 : settingsDocument(hostSettingsService, agentPresetsNs));
7458
- if (modeSelectionEnabled(document) !== false && readHostDefault(document) !== void 0) return readHostDefault(document);
7459
- const effective = agentPresetsService?.defaultId;
7460
- return typeof effective === "string" && effective.length > 0 ? effective : void 0;
7461
- };
7462
- const managedPresetExists = (id) => {
7463
- try {
7464
- return listPresets().some((preset) => preset.id === id);
7465
- } catch {
7466
- return false;
7467
- }
7468
- };
7469
- /** 把本插件当前预设写进官方 selectedDefault。 */
7470
- const syncHostDefault = () => {
7471
- const s = hostSettingsService;
7472
- if (s === void 0) return;
7473
- const template = runtime.presetTemplate;
7474
- if (!/^[a-z0-9][a-z0-9-]*$/.test(template)) {
7475
- warn(ctx, `prompt-tool: 预设 id ${JSON.stringify(template)} 不符合预设目录命名(^[a-z0-9][a-z0-9-]*$),跳过 selectedDefault 同步`);
7476
- return;
7477
- }
7478
- if (modeSelectionEnabled(settingsDocument(s, agentPresetsNs)) === false) {
7479
- warnModeSelectionOnce();
7480
- return;
7481
- }
7482
- if (readHostDefault(settingsDocument(s, agentPresetsNs)) === template) return;
7483
- s.update(agentPresetsNs, { selectedDefault: template }).catch((error) => {
7484
- warn(ctx, `prompt-tool: 同步宿主 selectedDefault 失败:${error instanceof Error ? error.message : String(error)}`);
7485
- });
7486
- };
7487
- const syncTemplateFromHostDefault = (value, initial = false) => {
7488
- const s = hostSettingsService;
7489
- if (s === void 0) return;
7490
- const raw = effectiveHostDefault(value);
7491
- if (raw === void 0) return;
7492
- const template = raw;
7493
- if (template === runtime.presetTemplate) return;
7494
- if (!managedPresetExists(template)) {
7495
- if (initial && managedPresetExists(runtime.presetTemplate)) {
7496
- syncHostDefault();
7497
- return;
7498
- }
7499
- warn(ctx, `prompt-tool: 官方默认预设已切换为 ${JSON.stringify(template)},但该预设不在提示词工具管理目录中,跳过反向同步`);
7500
- return;
7501
- }
7502
- s.update(NS, { presetTemplate: template }).catch((error) => {
7503
- warn(ctx, `prompt-tool: 跟随官方默认预设失败:${error instanceof Error ? error.message : String(error)}`);
7504
- });
7505
- };
7506
7342
  const currentSource = () => ({
7507
7343
  ...readConfig(),
7508
7344
  modelsAvailable: getModelsState().available
@@ -7511,11 +7347,10 @@ function apply(ctx, configIn) {
7511
7347
  ...currentSource(),
7512
7348
  skillCatalog: skillsRuntime.listSkills(),
7513
7349
  activeSkillsDirs: [skillsRuntime.skillsRoot]
7514
- }), getModelsState, () => listAdvertisedModels(ctx), () => activePresetDir(), (key, value) => {
7515
- if (key === "promptConfigs") savePresetParams(DEFAULT_PRESET_DIR, runtime.presetTemplate, void 0, Array.isArray(value) ? value : void 0);
7516
- else savePresetParams(DEFAULT_PRESET_DIR, runtime.presetTemplate, { [key]: value }, void 0);
7517
- reloadPresetParams();
7518
- rebuildPreset();
7350
+ }), getModelsState, () => listAdvertisedModels(ctx), () => activeModuleDir(), async (key, value) => {
7351
+ if (key === "promptConfigs") saveModuleParams(MODULES_DIR, basename(activeModuleDir()), void 0, Array.isArray(value) ? value : void 0);
7352
+ else saveModuleParams(MODULES_DIR, basename(activeModuleDir()), { [key]: value }, void 0);
7353
+ await rebuildPreset();
7519
7354
  }, (name, enabled) => {
7520
7355
  const matches = skillsRuntime.listSkills().filter((skill) => skill.name === name);
7521
7356
  if (matches.length !== 1) return {
@@ -7534,94 +7369,53 @@ function apply(ctx, configIn) {
7534
7369
  };
7535
7370
  });
7536
7371
  let needsInitialApply = true;
7537
- const applyState = () => {
7372
+ const applyState = async () => {
7538
7373
  const next = currentSource();
7539
- const nextTemplate = typeof next.presetTemplate === "string" && next.presetTemplate.length > 0 ? next.presetTemplate : DEFAULT_PRESET_ID;
7540
- const nextRuntime = {
7541
- writePreset: typeof next.writePreset === "boolean" ? next.writePreset : config.writePreset,
7542
- presetTemplate: nextTemplate,
7543
- presetOrder: Number.isSafeInteger(next.presetOrder) && next.presetOrder >= 0 ? next.presetOrder : config.presetOrder,
7544
- fallbackText: typeof next.fallbackText === "string" ? next.fallbackText : config.fallbackText
7545
- };
7546
- const fallbackTextChanged = runtime.fallbackText !== nextRuntime.fallbackText;
7547
- const presetTemplateChanged = runtime.presetTemplate !== nextRuntime.presetTemplate;
7548
- const settingsChanged = runtime.writePreset !== nextRuntime.writePreset || runtime.presetTemplate !== nextRuntime.presetTemplate || runtime.presetOrder !== nextRuntime.presetOrder || fallbackTextChanged;
7549
- if (!needsInitialApply && !settingsChanged) return;
7374
+ const nextRuntime = { writePreset: typeof next.writePreset === "boolean" ? next.writePreset : config.writePreset };
7375
+ const writePresetChanged = runtime.writePreset !== nextRuntime.writePreset;
7376
+ if (!needsInitialApply && !writePresetChanged) return;
7550
7377
  const initial = needsInitialApply;
7551
7378
  needsInitialApply = false;
7552
- if (presetTemplateChanged) {
7553
- const newDir = join(DEFAULT_PRESET_DIR, /^[a-zA-Z0-9\u4e00-\u9fff_-]+$/.test(nextRuntime.presetTemplate) ? nextRuntime.presetTemplate : DEFAULT_PRESET_ID);
7554
- current = readGeneratedContent(newDir, "preset.md") || readPromptFile(nextRuntime.presetTemplate, nextRuntime.fallbackText);
7555
- currentAgents = readGeneratedContent(newDir, "agents.md") || readAgents(nextRuntime.presetTemplate);
7379
+ Object.assign(runtime, nextRuntime);
7380
+ if (initial) {
7381
+ for (const id of seededPresets) materialize(id);
7382
+ seededPresets.clear();
7556
7383
  }
7557
- runtime.writePreset = nextRuntime.writePreset;
7558
- runtime.presetTemplate = nextRuntime.presetTemplate;
7559
- runtime.presetOrder = nextRuntime.presetOrder;
7560
- runtime.fallbackText = nextRuntime.fallbackText;
7561
- rebuildPreset(initial);
7562
- if (presetTemplateChanged) syncHostDefault();
7384
+ await assembly?.refresh();
7563
7385
  };
7564
- ctx.provide("pt-character-tools", { mount: (scopeCtx) => registerCharacterTools(scopeCtx, {
7565
- presetRoot: () => dirname(activePresetDir()),
7566
- templateName: () => basename(activePresetDir()),
7567
- rebuild: () => {
7568
- try {
7569
- rebuildPreset();
7570
- } catch (error) {
7571
- warn(ctx, "prompt-tool: character tool rebuild failed: " + String(error));
7572
- }
7573
- }
7574
- }) });
7575
- ctx.provide("pt-world-book-tools", { mount: (scopeCtx) => registerWorldBookTools(scopeCtx, {
7576
- activeDir: () => activePresetDir(),
7577
- presetRoot: () => dirname(activePresetDir()),
7578
- rebuild: () => {
7579
- try {
7580
- rebuildPreset();
7581
- } catch (error) {
7582
- warn(ctx, "prompt-tool: world-book tool rebuild failed: " + String(error));
7583
- }
7584
- }
7585
- }) });
7386
+ const moduleToolHost = {
7387
+ target: (exec) => resolveModuleToolTarget(exec, MODULES_DIR, (sessionId) => assembly?.moduleIds(sessionId) ?? []),
7388
+ rebuild: (id) => rebuildPreset(id)
7389
+ };
7390
+ ctx.provide("pt-character-tools", { mount: (scopeCtx) => registerCharacterTools(scopeCtx, moduleToolHost) });
7391
+ ctx.provide("pt-world-book-tools", { mount: (scopeCtx) => registerWorldBookTools(scopeCtx, moduleToolHost) });
7586
7392
  ctx.provide("pt-session-var-tools", { mount: (scopeCtx) => registerSessionVarTools(scopeCtx) });
7587
7393
  installPreStepCoordinator(ctx);
7394
+ ctx.inject(["agents"], (actx) => {
7395
+ const mounted = createAgentAssembly(actx, {
7396
+ moduleRoot: MODULES_DIR,
7397
+ enabledModules: () => runtime.writePreset ? enabledModuleIds(MODULES_DIR).filter((id) => moduleDirExists(MODULES_DIR, id)) : [],
7398
+ warn: (message) => warn(ctx, message)
7399
+ });
7400
+ assembly = mounted;
7401
+ actx.effect(() => () => {
7402
+ if (assembly === mounted) assembly = void 0;
7403
+ return mounted.dispose();
7404
+ }, "prompt-tool assembly");
7405
+ });
7588
7406
  const applyConfig = () => {
7589
7407
  settingsBridge.invalidateDescriptor();
7590
- try {
7591
- applyState();
7592
- } catch (error) {
7593
- warn(ctx, `prompt-tool: applyState failed: ${error instanceof Error ? error.message : String(error)}`);
7594
- }
7408
+ applyState().catch((error) => warn(ctx, `prompt-tool: applyState failed: ${error instanceof Error ? error.message : String(error)}`));
7595
7409
  };
7596
7410
  applyConfig();
7597
7411
  ctx.effect(() => ctx.on("loader/volatile-update", applyConfig));
7598
- ctx.inject(["agentPresets"], (apctx) => {
7599
- agentPresetsService = apctx.get("agentPresets");
7600
- const sync = createPresetRegistrySync(apctx, DEFAULT_PRESET_DIR);
7601
- registrySync = sync;
7602
- apctx.effect(() => async () => {
7603
- if (registrySync === sync) {
7604
- registrySync = void 0;
7605
- agentPresetsService = void 0;
7606
- }
7607
- await sync.dispose();
7608
- });
7609
- sync.refresh().catch((error) => warn(ctx, `prompt-tool: 初始预设注册失败:${String(error)}`));
7610
- syncTemplateFromHostDefault(void 0, true);
7611
- });
7612
7412
  ctx.inject(["settings"], (sctx) => {
7613
- hostSettingsService = sctx.settings;
7614
7413
  sctx.effect(() => sctx.settings.configure({ auto: false }, ctx.fiber));
7615
- sctx.effect(() => () => {
7616
- if (hostSettingsService === sctx.settings) hostSettingsService = void 0;
7617
- });
7618
7414
  sctx.effect(() => sctx.on("settings/document-updated", (ns) => {
7619
- if (String(ns) === agentPresetsNs) syncTemplateFromHostDefault();
7620
7415
  if (String(ns) === "prompt-tool") applyConfig();
7621
7416
  }), "prompt-tool: follow settings documents");
7622
7417
  settingsBridge.invalidateDescriptor();
7623
- syncTemplateFromHostDefault(void 0, true);
7624
7418
  });
7625
7419
  }
7626
7420
  //#endregion
7627
- export { BRIDGE_ENDPOINTS, CHARACTER_MODULES_KEY, Config, DEFAULT_PRESET_ID, ENGINE_CAPABILITIES, ENGINE_PARAM_KEYS, ENGINE_RECIPES, INSTRUCTION_FILE_STATUSES, MAX_BRIDGE_BODY_BYTES, MAX_CHARACTER_CARD_STREAM_BYTES, MAX_INSTRUCTION_FILE_BYTES, PARAM_KEYS, PRE_STEP_COORDINATOR_SERVICE, PRE_STEP_COORDINATOR_VERSION, PromptSettingsSchema, SETTINGS_BRIDGE_PREFIX, SKILL_NAME_PATTERN, USER_SKILLS_DIR, WRITER_PARAM_KEYS, agentsFileCardSpecs, agentsFileId, appendCharacterMemory, appendMemoryFile, apply, applyCharacterToPreset, applyModuleConfigs, assertSafeConfigId, buildModuleConfigsFromParams, buildWorldBookEntry, catalogFromScan, characterModuleStillNeeded, cloneBuiltinPreset, configFileName, convertStToPreset, createEngineCapabilityInPreset, createSkillsReloader, createSkillsRuntime, declaredCharacterModules, deleteCharacterCard, deleteWorldBookEntry, detectAgentsFileSnapshots, detectAgentsFiles, detectModels, engineCapability, engineRecipe, ensurePresetSeed, ensureWebSurface, expandPresetSource, exportPresetPackage, importCharacterCard, importCharacterCardFile, importSkillsPackage, inject, installPreStepCoordinator, installPresetPackage, invalidateModelCatalog, invocationForScope, isEngineCapabilityPresent, listAdvertisedModels, listBuiltinTemplates, listCharacterCards, listPresets, listWorldBookEntries, loadPresetSpec, loadPromptTemplates, loadToolTemplates, mergeInstructionCards, mergeStPresets, name, parseFrontmatter, peekModelCatalog, presetImportPreview, processStText, readAgentsFileSnapshot, readCharacterMemory, readSkillInvocation, readSkillsState, recordedCharacterModules, registerCharacterTools, registerSettingsBridge, registerTuiCommand, registerWorldBookTools, removeCharacterFromPreset, removeEngineCapabilityFromPreset, removePresetModule, removeUserPreset, renderComposition, requiredCharacterModules, resolvePresetDir, resolvePresetModuleFacts, resolvePresetParams, resolveProfileDir, resolveProjectRoot, resolveSubagentStartOptions, savePresetParams, savePresetPersona, scanRoot, scanRoots, scheduleWebSurfaceRepair, scopeOfInvocation, setSkillInvocation, skillRoots, skillsStatePath, stPresetId, syncImportedCharacterMemory, upsertWorldBookEntry, userPresetsDir, validateCustomToolIdentities, validateEngineParamValues, validatePromptConfigs, writeAgentsFile, writeAgentsFileChecked, writePreset, writeSkillsState };
7421
+ export { BRIDGE_ENDPOINTS, CHARACTER_MODULES_KEY, Config, DEFAULT_MODULE_ID, ENGINE_CAPABILITIES, ENGINE_PARAM_KEYS, ENGINE_RECIPES, INSTRUCTION_FILE_STATUSES, MAX_BRIDGE_BODY_BYTES, MAX_CHARACTER_CARD_STREAM_BYTES, MAX_INSTRUCTION_FILE_BYTES, PARAM_KEYS, PRE_STEP_COORDINATOR_SERVICE, PRE_STEP_COORDINATOR_VERSION, SETTINGS_BRIDGE_PREFIX, SKILL_NAME_PATTERN, USER_SKILLS_DIR, WRITER_PARAM_KEYS, agentsFileCardSpecs, agentsFileId, appendCharacterMemory, appendMemoryFile, apply, applyCharacterToPreset, applyModuleConfigs, assertSafeConfigId, buildModuleConfigsFromParams, buildWorldBookEntry, catalogFromScan, characterModuleStillNeeded, cloneBuiltinPreset, configFileName, convertStToPreset, createEngineCapabilityInPreset, createSkillsReloader, createSkillsRuntime, declaredCharacterModules, deleteCharacterCard, deleteWorldBookEntry, detectAgentsFileSnapshots, detectAgentsFiles, detectModels, engineCapability, engineRecipe, ensurePresetSeed, ensureWebSurface, expandPresetSource, exportPresetPackage, importCharacterCard, importCharacterCardFile, importSkillsPackage, inject, installPreStepCoordinator, installPresetPackage, invalidateModelCatalog, invocationForScope, isEngineCapabilityPresent, listAdvertisedModels, listBuiltinTemplates, listCharacterCards, listModules, listWorldBookEntries, loadModuleSpec, loadPromptTemplates, loadToolTemplates, mergeInstructionCards, mergeStPresets, moduleDirExists, name, parseFrontmatter, peekModelCatalog, presetImportPreview, processStText, readAgentsFileSnapshot, readCharacterMemory, readSkillInvocation, readSkillsState, recordedCharacterModules, registerCharacterTools, registerSettingsBridge, registerTuiCommand, registerWorldBookTools, removeCharacterFromPreset, removeEngineCapabilityFromPreset, removePresetModule, removeUserPreset, renderComposition, requiredCharacterModules, resolveModuleDir, resolveModuleFacts, resolvePresetParams, resolveProfileDir, resolveProjectRoot, resolveSubagentStartOptions, saveModuleParams, savePresetPersona, scanRoot, scanRoots, scheduleWebSurfaceRepair, scopeOfInvocation, setSkillInvocation, skillRoots, skillsStatePath, stPresetId, syncImportedCharacterMemory, upsertWorldBookEntry, userModulesDir, validateCustomToolIdentities, validateEngineParamValues, validatePromptConfigs, writeAgentsFile, writeAgentsFileChecked, writePreset, writeSkillsState };