dsh-plugin-prompt-tool 1.0.0 → 2.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 (95) hide show
  1. package/README.md +38 -29
  2. package/engine/actions/append-context.mjs +49 -0
  3. package/engine/actions/assembly.mjs +150 -0
  4. package/engine/actions/catalog.mjs +92 -0
  5. package/engine/actions/content.mjs +205 -0
  6. package/engine/actions/decision.mjs +54 -0
  7. package/engine/actions/examples.mjs +11 -0
  8. package/engine/actions/guard.mjs +80 -0
  9. package/engine/actions/inbox-prepend.mjs +37 -0
  10. package/engine/actions/index.mjs +100 -0
  11. package/engine/actions/inject-text.mjs +22 -0
  12. package/engine/actions/pre-step-filter.mjs +55 -0
  13. package/engine/actions/registration.mjs +168 -0
  14. package/engine/actions/request-params.mjs +32 -0
  15. package/engine/actions/sdk-strip.mjs +30 -0
  16. package/engine/actions/shared.mjs +71 -0
  17. package/engine/actions.mjs +2 -931
  18. package/engine/branch.mjs +44 -0
  19. package/engine/compaction-epoch.mjs +31 -13
  20. package/engine/compositions/source/local/dev-tool-search.yml +11 -0
  21. package/engine/compositions/source/local/instruction-hint.yml +22 -1
  22. package/engine/compositions/source/local/rule-engine.yml +6 -0
  23. package/engine/condition.mjs +16 -3
  24. package/engine/conditions/anchor.mjs +32 -0
  25. package/engine/conditions/availability.mjs +47 -0
  26. package/engine/conditions/composite.mjs +83 -0
  27. package/engine/conditions/count.mjs +140 -0
  28. package/engine/conditions/evaluation.mjs +41 -0
  29. package/engine/conditions/examples.mjs +11 -0
  30. package/engine/conditions/index.mjs +85 -0
  31. package/engine/conditions/names.mjs +21 -0
  32. package/engine/conditions/phase.mjs +54 -0
  33. package/engine/conditions/preset.mjs +94 -0
  34. package/engine/conditions/scope.mjs +18 -0
  35. package/engine/conditions/session.mjs +22 -0
  36. package/engine/conditions/source.mjs +63 -0
  37. package/engine/conditions/subject.mjs +121 -0
  38. package/engine/conditions/text.mjs +44 -0
  39. package/engine/conditions/values.mjs +32 -0
  40. package/engine/dev-tool-search.mjs +213 -0
  41. package/engine/executor.mjs +55 -6
  42. package/engine/fillers.mjs +23 -16
  43. package/engine/instruction-hint.mjs +34 -22
  44. package/engine/layers.mjs +135 -49
  45. package/engine/order.mjs +4 -1
  46. package/engine/predicates.mjs +2 -753
  47. package/engine/rule-engine.mjs +40 -0
  48. package/engine/rule-runtime.mjs +110 -0
  49. package/engine/rule-spec.mjs +187 -0
  50. package/engine/schema.mjs +16 -5
  51. package/engine/shared.mjs +1 -1
  52. package/engine/strategies.mjs +2 -235
  53. package/engine/subagent-tool-policy-core.mjs +7 -7
  54. package/engine/subagent-tool-policy.mjs +20 -10
  55. package/engine/tool-config-engine.mjs +53 -32
  56. package/engine/trigger-editor-meta.mjs +2 -21
  57. package/engine/trigger-spec.mjs +49 -133
  58. package/lib/client.js +10446 -10820
  59. package/lib/client.js.map +1 -1
  60. package/lib/index.d.mts +82 -139
  61. package/lib/index.mjs +826 -1087
  62. package/lib/migrate-rules.d.mts +1 -0
  63. package/lib/migrate-rules.mjs +39 -0
  64. package/lib/module-package-CHPFweqK.mjs +2 -0
  65. package/lib/module-package-Cnd1_mwx.mjs +2872 -0
  66. package/lib/{paths-BgL42sSM.d.mts → paths-CyP0IkaM.d.mts} +223 -108
  67. package/lib/preset-transfer.d.mts +2 -2
  68. package/lib/preset-transfer.mjs +3 -2
  69. package/lib/{module-package-Cm3fHHlp.mjs → rules-migration-DJ6vASB5.mjs} +13696 -15557
  70. package/modules/ponytail/module.yml +381 -147
  71. package/modules/skill-surface/module.yml +74 -0
  72. package/modules/tool-surface/module.yml +67 -0
  73. package/package.json +5 -1
  74. package/templates/10-pre-step.yml +26 -22
  75. package/templates/14-first-turn-anchor.yml +25 -21
  76. package/templates/15-guide-auto.yml +34 -19
  77. package/templates/16-custom-fallback.yml +27 -15
  78. package/templates/18-placeholder-env-facts.yml +23 -14
  79. package/templates/20-system-section.yml +16 -10
  80. package/templates/30-runtime-context.yml +13 -7
  81. package/templates/40-agent-request.yml +11 -8
  82. package/templates/50-llm-stream.yml +14 -6
  83. package/templates/60-tool-pipeline.yml +15 -9
  84. package/templates/65-turn-stop.yml +19 -12
  85. package/templates/66-subagent-start.yml +12 -7
  86. package/templates/70-subagent-maintenance.yml +35 -29
  87. package/templates/75-subagent-end.yml +10 -5
  88. package/templates/80-tool-surface.yml +66 -0
  89. package/templates/policies/legacy-defaults.yml +34 -0
  90. package/engine/compositions/source/local/prompt-config-engine.yml +0 -7
  91. package/engine/prompt-config-engine.mjs +0 -104
  92. package/modules/ponytail/configs/0000-ponytail-rules.yml +0 -102
  93. package/modules/ponytail/configs/0010-ponytail-level-full.yml +0 -19
  94. package/modules/ponytail/configs/0020-ponytail-level-lite.yml +0 -19
  95. package/modules/ponytail/configs/0030-ponytail-level-ultra.yml +0 -19
package/lib/index.mjs CHANGED
@@ -1,12 +1,13 @@
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";
1
+ import { $ as wireTriggerObservers, A as packageEngineDir, At as INJECTION_POINT_ORDER, B as userModulesDir, Bt as validateEngineParamValues, C as ensureModuleSeed, Ct as assertModuleTree, D as loadModuleSpec, Dt as readPersonaSpec, E as listModules, F as resolveModuleDir, Ft as validateCustomToolIdentities, Gt as USER_SKILLS_DIR, H as editModuleRules, Ht as DSH_HOME, I as resolveModuleFacts, It as ENGINE_PARAM_DEFINITIONS, J as rulePromptConfigOptions, K as ensureModuleSlices, L as resolveModuleParams, Lt as ENGINE_PARAM_KEYS, M as removeModuleCapabilityDeclaration, Mt as engineRecipe, N as removeUserModule, Nt as isModuleCapabilityPresent, O as moduleDirExists, Ot as ENGINE_EDITOR_GROUP_MAP, P as renderComposition, Pt as moduleCapability, R as saveModuleParams, Rt as WRITER_PARAM_KEYS, S as duplicateUserModule, St as assertModuleId, T as listBuiltinTemplates, Tt as modulePathExists, U as readModuleRules, Ut as MODULES_DIR, V as withModuleDefinition, Vt as LEGACY_PROMPT_PARAM_KEYS, W as ModuleRulesError, Z as compileRules, _ as applyModuleConfigs, _t as RULE_OWNED_MODEL_PARAMS, at as runPreStepBatch, b as cloneBuiltinModule, bt as readModuleLayerSettings, c as readConfigOrder, ct as actionMatches, d as assertSafeConfigId, et as compileWhen, f as configFileName, g as appendModuleCapabilities, gt as atomicWriteTextFile, h as setModuleEnabled, ht as createWarnOnce, it as confirmDelivered, j as removeModuleCapability, jt as MODULE_CAPABILITIES, k as openModuleLocation, kt as ENGINE_RECIPES, l as readModuleConfigOrder, lt as ruleFrame, m as resolveEditDir, mt as PROMOTE_EVENTS, nt as prepareAction, o as ModuleConfigOrderError, ot as lastWorldBookDiagnostics, p as enabledModuleIds, pt as createEpochPromotion, q as loadModuleDefinition, rt as applyPromptConfigSources, s as appendModuleConfigOrder, st as wireLayers, tt as loadStandingMountFor, u as saveModuleConfigOrder, ut as compareConfigSequence, vt as ModuleLayerSettingsError, wt as canonicalModulesRoot, x as createModuleCapability, xt as assertModuleDirectory, y as buildModuleConfigsFromParams, z as saveModulePersona, zt as moduleParamFallbacks } from "./rules-migration-DJ6vASB5.mjs";
2
+ import { C as mergeStModulesWithReport, D as deleteWorldBookEntry, E as buildWorldBookEntry, M as DEFAULT_MODULE_ID, O as listWorldBookEntries, S as mergeStModules, T as stModuleId, _ as readModuleMemory, a as installModulePackage, b as convertStToModule, c as writeModule, d as validateCustomTools, f as safeAssetPath, g as mergeModuleIntoModule, h as importCharacterCardFile, i as exportModulePackage, j as ruleInjections, k as upsertWorldBookEntry, l as writePreset, m as importCharacterCard, o as moduleImportPreview, p as appendModuleMemory, r as expandModuleSource, s as ensureModuleReady, u as compileCustomTool, v as removeMergedModule, w as processStText, x as convertStToModuleWithReport, y as prepareImport } from "./module-package-Cnd1_mwx.mjs";
2
3
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
3
4
  import { accessSync, chmodSync, constants, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
4
5
  import { fileURLToPath, pathToFileURL } from "node:url";
5
6
  import { Document, isMap, isScalar, parse, parseDocument, visit } from "yaml";
6
7
  import { homedir } from "node:os";
8
+ import { isDeepStrictEqual } from "node:util";
7
9
  import { createHash, randomUUID } from "node:crypto";
8
10
  import { NamedEntries, ScopedLayers, scopeOf } from "@deepseek-ai/dsh-scope";
9
- import { isDeepStrictEqual } from "node:util";
10
11
  import z from "@deepseek-ai/schemastery";
11
12
  import { Transform } from "node:stream";
12
13
  import { pipeline } from "node:stream/promises";
@@ -327,107 +328,34 @@ const PARAM_KEYS = /* @__PURE__ */ new Set([
327
328
  ...EXTRA_PARAM_KEYS
328
329
  ]);
329
330
  //#endregion
331
+ //#region src/shared/module-settings.ts
332
+ /** 部署总闸的新旧字段只在读写边界归一。 */
333
+ function readModulesEnabled(value) {
334
+ const { modulesEnabled, writePreset } = value;
335
+ if (modulesEnabled !== void 0 && typeof modulesEnabled !== "boolean") throw new TypeError("modulesEnabled 必须是布尔值");
336
+ if (writePreset !== void 0 && typeof writePreset !== "boolean") throw new TypeError("旧 writePreset 必须是布尔值");
337
+ if (modulesEnabled !== void 0 && writePreset !== void 0 && modulesEnabled !== writePreset) throw new Error("modulesEnabled 与旧 writePreset 冲突");
338
+ return modulesEnabled ?? writePreset ?? true;
339
+ }
340
+ /** 一次官方 settings 事务完成改值与旧键退役,不产生新旧冲突中间态。 */
341
+ function modulesEnabledOps(enabled) {
342
+ return [{
343
+ op: "set",
344
+ path: ["modulesEnabled"],
345
+ value: enabled
346
+ }, {
347
+ op: "unset",
348
+ path: ["writePreset"]
349
+ }];
350
+ }
351
+ //#endregion
330
352
  //#region src/config.ts
331
353
  /** 插件配置、settings 数据模型与默认常量(settings 接口层)。 */
332
354
  const NS = "prompt-tool";
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
- }
355
+ const Config = z.object({
356
+ modulesEnabled: z.boolean().volatile(),
357
+ writePreset: z.boolean().volatile()
358
+ });
431
359
  //#endregion
432
360
  //#region src/shared/official-orders.ts
433
361
  /** section 侧 6 组,覆盖官方 32 个档位名(顺序即官方数值升序)。 */
@@ -538,89 +466,6 @@ function readOfficialOrderSegments(lookup) {
538
466
  };
539
467
  }
540
468
  //#endregion
541
- //#region src/runtime/configs-validate.ts
542
- /** 数组整体不是数组时返回单条 -1 错误;元素问题逐条收集。 */
543
- function shapeErrors(value) {
544
- if (!Array.isArray(value)) return [{
545
- index: -1,
546
- id: "",
547
- message: "promptConfigs must be an array"
548
- }];
549
- const errors = [];
550
- const seenIds = /* @__PURE__ */ new Set();
551
- for (let index = 0; index < value.length; index += 1) {
552
- const item = value[index];
553
- const label = `configs[${index}]`;
554
- if (item === null || typeof item !== "object" || Array.isArray(item)) {
555
- errors.push({
556
- index,
557
- id: "",
558
- message: `${label} must be an object`
559
- });
560
- continue;
561
- }
562
- const id = item.id;
563
- if (typeof id !== "string" || id.length === 0) errors.push({
564
- index,
565
- id: typeof id === "string" ? id : "",
566
- message: `${label}.id must be a non-empty string`
567
- });
568
- else if (seenIds.has(id)) errors.push({
569
- index,
570
- id,
571
- message: `${label} duplicate id ${JSON.stringify(id)} (后者覆盖前者会静默丢卡)`
572
- });
573
- else seenIds.add(id);
574
- }
575
- return errors;
576
- }
577
- /**
578
- * 逐条调用引擎权威校验并渲染预览文件。
579
- * 逐条(而非整组一次)校验保证一条坏配置不吞掉其余错误,且 index 可直接映射。
580
- */
581
- async function validatePromptConfigs(value, options = {}) {
582
- const errors = shapeErrors(value);
583
- if (errors.length > 0 || !Array.isArray(value)) return {
584
- valid: false,
585
- errors
586
- };
587
- const specs = value;
588
- const { createPromptConfigs } = await import(pathToFileURL(join(packageEngineDir(), "prompt-config-engine.mjs")).href);
589
- const engineOptions = {
590
- ...options.strategyDir ? { strategyDir: options.strategyDir } : {},
591
- ...options.moduleDir ? { templateBaseUrl: pathToFileURL(join(dirname(options.moduleDir), ".engine", "schema.mjs")).href } : {}
592
- };
593
- const files = [];
594
- for (let index = 0; index < specs.length; index += 1) {
595
- const spec = specs[index];
596
- const id = spec !== null && typeof spec === "object" && typeof spec.id === "string" ? spec.id : "";
597
- try {
598
- createPromptConfigs([spec], engineOptions);
599
- files.push({
600
- file: configFileName(index * 10, spec.id),
601
- content: renderPromptConfigYaml(spec)
602
- });
603
- } catch (error) {
604
- const message = error instanceof Error ? error.message : String(error);
605
- errors.push({
606
- index,
607
- id,
608
- message
609
- });
610
- }
611
- }
612
- if (errors.length > 0) return {
613
- valid: false,
614
- errors
615
- };
616
- return {
617
- valid: true,
618
- errors: [],
619
- configs: specs,
620
- files
621
- };
622
- }
623
- //#endregion
624
469
  //#region src/host/templates.ts
625
470
  /**
626
471
  * 默认提示词配置模板库:包内 templates/ 目录的只读扫描与解析。
@@ -637,6 +482,7 @@ function loadPromptTemplates() {
637
482
  const content = readFileSync(join(TEMPLATES_DIR, entry.name), "utf8");
638
483
  const parsed = parse(content, { logLevel: "silent" });
639
484
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed) || typeof parsed.id !== "string" || parsed.id.length === 0) throw new Error(`prompt config template ${entry.name} must contain a single config object with a non-empty string id`);
485
+ compileRules([parsed]);
640
486
  return {
641
487
  file: entry.name,
642
488
  content,
@@ -1358,7 +1204,6 @@ const BRIDGE_ENDPOINTS = {
1358
1204
  models: "/models",
1359
1205
  modelReasoning: "/model-reasoning",
1360
1206
  mutate: "/mutate",
1361
- configsValidate: "/configs-validate",
1362
1207
  skillsList: "/skills-list",
1363
1208
  skillPolicy: "/skill-policy",
1364
1209
  skillRead: "/skill-read",
@@ -1372,17 +1217,18 @@ const BRIDGE_ENDPOINTS = {
1372
1217
  promptConfigs: "/prompt-configs",
1373
1218
  agentsFile: "/agents-file",
1374
1219
  instructionsPolicy: "/instructions-policy",
1375
- presetContent: "/preset-content",
1220
+ moduleContent: "/module-content",
1376
1221
  importPreset: "/import-preset",
1377
1222
  paramOverrides: "/param-overrides",
1378
1223
  triggers: "/triggers",
1224
+ rules: "/rules",
1379
1225
  persona: "/persona",
1380
- presetVariables: "/preset-variables",
1226
+ moduleVariables: "/module-variables",
1381
1227
  customTools: "/custom-tools",
1382
- importPresetPackage: "/import-preset-package",
1228
+ importModulePackage: "/import-module-package",
1383
1229
  assetUpload: "/asset-upload",
1384
1230
  assetRelease: "/asset-release",
1385
- exportPreset: "/export-preset",
1231
+ exportModule: "/export-module",
1386
1232
  moduleDelete: "/module-delete",
1387
1233
  moduleClone: "/module-clone",
1388
1234
  moduleDuplicate: "/module-duplicate",
@@ -1391,18 +1237,16 @@ const BRIDGE_ENDPOINTS = {
1391
1237
  moduleConfigOrder: "/module-config-order",
1392
1238
  charactersImport: "/characters-import",
1393
1239
  charactersImportStream: "/characters-import-stream",
1394
- charactersList: "/characters-list",
1395
- charactersDelete: "/characters-delete",
1396
- charactersApply: "/characters-apply",
1397
- charactersRemove: "/characters-remove",
1240
+ moduleMerge: "/module-merge",
1241
+ moduleUnmerge: "/module-unmerge",
1398
1242
  subagentToolPolicy: "/subagent-tool-policy",
1399
1243
  subagentToolPolicyPreview: "/subagent-tool-policy-preview",
1400
1244
  toolSurface: "/tool-surface",
1401
- engineCapability: "/engine-capability",
1245
+ moduleCapability: "/module-capability",
1402
1246
  worldBookDiagnostics: "/world-book-diagnostics"
1403
1247
  };
1404
1248
  /** 文件已保存,但宿主未采用新定义;调用方保留草稿并显示失败,不宣称已生效。 */
1405
- const PRESET_ACTIVATION_FAILED = "preset-activation-failed";
1249
+ const MODULE_ACTIVATION_FAILED = "module-activation-failed";
1406
1250
  const MODULE_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
1407
1251
  /** 读编辑目标:形状不合法(空、超长、含非法字符)一律当「未声明」,由 host 回退启用表首项。 */
1408
1252
  function readEditTarget(value) {
@@ -2600,17 +2444,20 @@ function installPreStepCoordinator(ctx, options = {}) {
2600
2444
  const scope = isRecord$1(agent) && agent.ctx !== void 0 ? scopeOf(agent.ctx) : void 0;
2601
2445
  const sources = [...layers.merge(scope, (layer) => layer.entries).values()];
2602
2446
  const configs = sources.flatMap((source) => source.configs).filter((config) => !config.id.startsWith("agents-file-") && config.sourceKind !== "instruction-file").sort(compareConfigSequence);
2447
+ const ruleActions = sources.flatMap((source) => source.ruleActions ?? []);
2603
2448
  const sessionId = isRecord$1(session) && typeof session.id === "string" ? session.id : void 0;
2604
2449
  if (sessionId !== void 0) {
2605
2450
  if (officialOwner.size >= MAX_TRACKED_OWNER_SESSIONS) officialOwner.clear();
2606
2451
  if (sources.length > 0) officialOwner.set(sessionId, sources.some((source) => source.officialInstructions === true));
2607
2452
  else officialOwner.delete(sessionId);
2608
2453
  }
2609
- return configs.length === 0 ? decision : await runPreStepBatch({
2454
+ return configs.length === 0 && ruleActions.length === 0 ? decision : await runPreStepBatch({
2610
2455
  ctx,
2611
2456
  agent,
2457
+ payload,
2612
2458
  decision,
2613
2459
  configs,
2460
+ ruleActions,
2614
2461
  promotion,
2615
2462
  memo,
2616
2463
  warnOnce
@@ -2707,7 +2554,15 @@ function writeBridgeJson(res, status, body) {
2707
2554
  const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
2708
2555
  /** 当前层参数格式错误统一反馈,不能在读回时伪装成空配置。 */
2709
2556
  function writeLayerSettingsError(res, error) {
2710
- if (!(error instanceof PresetLayerSettingsError)) return false;
2557
+ if (error instanceof ModuleRulesError) {
2558
+ writeBridgeJson(res, error.status, {
2559
+ ok: false,
2560
+ code: error.code,
2561
+ message: error.message
2562
+ });
2563
+ return true;
2564
+ }
2565
+ if (!(error instanceof ModuleLayerSettingsError)) return false;
2711
2566
  writeBridgeJson(res, 400, {
2712
2567
  ok: false,
2713
2568
  code: error.code,
@@ -2900,10 +2755,10 @@ function withSnapshot(card, file) {
2900
2755
  * 生成目录里属于其他工作区的文件卡不再展示(写盘白名单也不含它们);
2901
2756
  * 当前工作区里没有对应生成卡的文件按默认文件卡补齐。
2902
2757
  */
2903
- function mergeInstructionCards(presetCards, files) {
2758
+ function mergeInstructionCards(moduleCards, files) {
2904
2759
  const byId = new Map(files.map((file) => [file.fileId, file]));
2905
2760
  const kept = [];
2906
- for (const card of presetCards) {
2761
+ for (const card of moduleCards) {
2907
2762
  if (!isFileCardSpec(card)) {
2908
2763
  kept.push(card);
2909
2764
  continue;
@@ -2956,16 +2811,16 @@ function projectToolSchemas(schemas) {
2956
2811
  }
2957
2812
  return [...unique.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
2958
2813
  }
2959
- function isEditablePresetDir(dir, moduleRoot = userModulesDir()) {
2814
+ function isEditableModuleDir(dir, moduleRoot = userModulesDir()) {
2960
2815
  const root = resolve(moduleRoot);
2961
2816
  const target = resolve(dir);
2962
2817
  return target !== root && target.startsWith(root + sep);
2963
2818
  }
2964
- function guardEditablePresetDir(dir, res) {
2965
- if (isEditablePresetDir(dir)) return true;
2819
+ function guardEditableModuleDir(dir, res) {
2820
+ if (isEditableModuleDir(dir)) return true;
2966
2821
  writeBridgeJson(res, 403, {
2967
2822
  ok: false,
2968
- code: "preset-readonly",
2823
+ code: "module-readonly",
2969
2824
  message: "system preset 只读,请先复制为用户模块"
2970
2825
  });
2971
2826
  return false;
@@ -3079,31 +2934,31 @@ function customToolModules(tools) {
3079
2934
  return [...modules];
3080
2935
  }
3081
2936
  /** 自建 loopback settings bridge:替代 registerConfigurableProviders,避免模型设置区出现插件条目。 */
3082
- function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngineStrategyDir, afterSkillsChange, getPresetConfigsDir, afterPresetImport, afterOverridesChange, afterPresetPackageImport, afterCapabilityChange, afterPresetListChange) {
2937
+ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, _getEngineStrategyDir, afterSkillsChange, getModuleDirectory, afterModuleContentImport, afterOverridesChange, afterModulePackageImport, afterCapabilityChange, afterModuleListChange) {
3083
2938
  let invalidateCachedDescriptor = () => {};
3084
2939
  let capabilityQueue = Promise.resolve();
3085
2940
  let moduleOrderQueue = Promise.resolve();
3086
2941
  /** 写盘已完成:等待重建,失败立即返回统一错误,不覆盖持久化结果。 */
3087
- const finishPresetChange = async (res, activate) => {
2942
+ const finishModuleChange = async (res, activate) => {
3088
2943
  try {
3089
2944
  await activate();
3090
2945
  return true;
3091
2946
  } catch (error) {
3092
2947
  writeBridgeJson(res, 500, {
3093
2948
  ok: false,
3094
- code: PRESET_ACTIVATION_FAILED,
2949
+ code: MODULE_ACTIVATION_FAILED,
3095
2950
  message: `更改已保存,但模块未生效;请重试:${String(error)}`
3096
2951
  });
3097
2952
  return false;
3098
2953
  }
3099
2954
  };
3100
- const runOverridesChange = (res, moduleId) => finishPresetChange(res, () => afterOverridesChange?.(moduleId));
2955
+ const runOverridesChange = (res, moduleId) => finishModuleChange(res, () => afterOverridesChange?.(moduleId));
3101
2956
  /**
3102
2957
  * 本次请求的编辑目标目录:读 `x-module-id` 头(形状不合法当未声明),交给注入的实现定位。
3103
2958
  * 所有「读/写哪个模块的配置」都走这里,端点不再各自记一个全局目标。
3104
2959
  */
3105
- const editDir = (req) => getPresetConfigsDir?.(readEditTarget(req.headers["x-module-id"])) ?? "";
3106
- const refreshPresetList = (res, id) => finishPresetChange(res, () => afterPresetListChange?.(id));
2960
+ const editDir = (req) => getModuleDirectory?.(readEditTarget(req.headers["x-module-id"])) ?? "";
2961
+ const refreshModuleList = (res, id) => finishModuleChange(res, () => afterModuleListChange?.(id));
3107
2962
  ctx.inject(["settings", "webServer"], (sctx) => {
3108
2963
  sctx.effect(() => {
3109
2964
  const assetSources = createAssetSources(join(DSH_HOME, ".prompt-tool-uploads"));
@@ -3118,12 +2973,16 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3118
2973
  return cachedDescriptor;
3119
2974
  };
3120
2975
  /** 已解析的编辑目标只投影到本次响应,不改宿主设置或缓存描述符。 */
3121
- const descriptorForTarget = (descriptor, dir) => dir.length === 0 ? descriptor : {
3122
- ...descriptor,
3123
- value: {
3124
- ...asRecord(descriptor.value),
3125
- presetTemplate: basename(dir)
3126
- }
2976
+ const descriptorForTarget = (descriptor, dir) => {
2977
+ const value = { ...asRecord(descriptor.value) };
2978
+ value.modulesEnabled = readModulesEnabled(value);
2979
+ delete value.writePreset;
2980
+ delete value.presetTemplate;
2981
+ if (dir.length > 0) value.moduleId = basename(dir);
2982
+ return {
2983
+ ...descriptor,
2984
+ value
2985
+ };
3127
2986
  };
3128
2987
  /** mutate 成功后强制失效,下次 findDescriptor 重查宿主(响应必须带新 view)。 */
3129
2988
  const invalidateDescriptor = () => {
@@ -3149,16 +3008,16 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3149
3008
  }
3150
3009
  return true;
3151
3010
  };
3152
- const guardPresetFormat = (dir, res) => {
3011
+ const guardModuleFormat = (dir, res) => {
3153
3012
  if (dir.length === 0) return true;
3154
3013
  try {
3155
- loadModuleSpec(dir);
3014
+ readModuleLayerSettings(loadModuleDefinition(dir).source);
3156
3015
  } catch (error) {
3157
3016
  if (writeLayerSettingsError(res, error)) return false;
3158
3017
  }
3159
3018
  return true;
3160
3019
  };
3161
- const guardPresetWrite = (dir, res) => guardEditablePresetDir(dir, res) && guardPresetFormat(dir, res);
3020
+ const guardModuleWrite = (dir, res) => guardEditableModuleDir(dir, res) && guardModuleFormat(dir, res);
3162
3021
  const writeModuleOrderError = (res, error) => {
3163
3022
  writeBridgeJson(res, error instanceof ModuleConfigOrderError ? error.status : 500, {
3164
3023
  ok: false,
@@ -3167,25 +3026,33 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3167
3026
  });
3168
3027
  };
3169
3028
  /** 模块身份只作一致性检查,绝不用客户端 ID 构造写入路径。 */
3170
- const guardPresetIdentity = (req, record, dir, res) => {
3171
- const expected = record.expectedPresetId;
3029
+ const guardModuleIdentity = (req, record, dir, res) => {
3030
+ if (record.expectedModuleId !== void 0 && record.expectedPresetId !== void 0 && record.expectedModuleId !== record.expectedPresetId) {
3031
+ writeBridgeJson(res, 400, {
3032
+ ok: false,
3033
+ code: "module-identity-invalid",
3034
+ message: "新旧模块身份字段冲突"
3035
+ });
3036
+ return false;
3037
+ }
3038
+ const expected = record.expectedModuleId ?? record.expectedPresetId;
3172
3039
  if (expected !== void 0 && (typeof expected !== "string" || expected.length === 0 || expected.length > 256)) {
3173
3040
  writeBridgeJson(res, 400, {
3174
3041
  ok: false,
3175
- code: "preset-identity-invalid",
3176
- message: "expectedPresetId 必须是非空模块 ID"
3042
+ code: "module-identity-invalid",
3043
+ message: "expectedModuleId 必须是非空模块 ID"
3177
3044
  });
3178
3045
  return false;
3179
3046
  }
3180
3047
  if (editDir(req) !== dir || expected !== void 0 && expected !== basename(dir)) {
3181
3048
  writeBridgeJson(res, 409, {
3182
3049
  ok: false,
3183
- code: "preset-changed",
3050
+ code: "module-changed",
3184
3051
  message: "当前模块已切换;旧草稿未写入,请重新读取后保存"
3185
3052
  });
3186
3053
  return false;
3187
3054
  }
3188
- return guardPresetFormat(dir, res);
3055
+ return guardModuleFormat(dir, res);
3189
3056
  };
3190
3057
  /** 官方刻度取值失败只告警一次:本函数被 /meta 与 /bootstrap 共用,不能每次调用都刷屏。 */
3191
3058
  let warnedOfficialOrders = false;
@@ -3193,16 +3060,13 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3193
3060
  const loadEngineMeta = async () => {
3194
3061
  const { getEngineMeta } = await import(pathToFileURL(join(packageEngineDir(), "schema.mjs")).href);
3195
3062
  const meta = getEngineMeta();
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
3063
  const enabledSet = new Set(enabledModuleIds(userModulesDir()));
3200
- meta.presets = listModules().map((preset) => ({
3064
+ meta.modules = listModules().map((preset) => ({
3201
3065
  ...preset,
3202
- enabled: enabledSet.has(preset.id),
3203
- ...diagnostics.get(preset.id) === void 0 ? {} : { broken: diagnostics.get(preset.id) }
3066
+ enabled: enabledSet.has(preset.id)
3204
3067
  }));
3205
3068
  meta.builtinTemplates = listBuiltinTemplates();
3069
+ if (modulePathExists(join(dirname(userModulesDir()), ".characters"))) meta.moduleWarnings = ["检测到旧 .characters 角色库;其内容尚未作为模块导入。请通过模块导入入口显式导入,旧目录不会自动迁移或删除。"];
3206
3070
  meta.editorGroups = ENGINE_EDITOR_GROUP_MAP.map(({ id, displayLayer, relatedLayers, hook }) => ({
3207
3071
  id,
3208
3072
  displayLayer,
@@ -3272,15 +3136,15 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3272
3136
  }
3273
3137
  } catch {}
3274
3138
  const modelCatalog = peekModelCatalog(sctx);
3275
- let presetParams = {};
3139
+ let moduleParams = {};
3276
3140
  let moduleFacts;
3277
3141
  let templatePreStepCount = 0;
3278
3142
  try {
3279
3143
  const templateName = activeDir.length > 0 ? basename(activeDir) : DEFAULT_MODULE_ID;
3280
3144
  const spec = loadModuleSpec(activeDir.length > 0 ? activeDir : resolveModuleDir(templateName));
3281
- presetParams = resolvePresetParams(spec, {});
3282
- const resolvedFacts = resolveModuleFacts(spec, activeDir.length > 0 ? activeDir : void 0, isEditablePresetDir(activeDir.length > 0 ? activeDir : resolveModuleDir(templateName)));
3283
- mergeModuleConfigFallbacks(presetParams, resolvedFacts);
3145
+ moduleParams = resolveModuleParams(spec, {});
3146
+ const resolvedFacts = resolveModuleFacts(spec, activeDir.length > 0 ? activeDir : void 0, isEditableModuleDir(activeDir.length > 0 ? activeDir : resolveModuleDir(templateName)));
3147
+ mergeModuleConfigFallbacks(moduleParams, resolvedFacts);
3284
3148
  moduleFacts = {
3285
3149
  declaredModules: resolvedFacts.declaredModules,
3286
3150
  effectiveModules: resolvedFacts.effectiveModules,
@@ -3289,17 +3153,13 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3289
3153
  editable: resolvedFacts.editable,
3290
3154
  subagentToolPolicyEnabled: resolvedFacts.subagentToolPolicyEnabled === true
3291
3155
  };
3292
- if (Array.isArray(spec.promptConfigs)) presetParams.promptConfigs = spec.promptConfigs;
3293
- templatePreStepCount = (spec.promptConfigs ?? []).filter((config) => {
3294
- const layer = config.layer;
3295
- return layer === void 0 || layer === "pre-step";
3296
- }).length;
3156
+ templatePreStepCount = (spec.rules ?? []).filter((rule) => rule.layer === "pre-step").length;
3297
3157
  } catch (error) {
3298
- if (error instanceof PresetLayerSettingsError) throw error;
3158
+ if (error instanceof ModuleLayerSettingsError || error instanceof ModuleRulesError) throw error;
3299
3159
  templatePreStepCount = 0;
3300
3160
  }
3301
3161
  return {
3302
- presetParams,
3162
+ moduleParams,
3303
3163
  moduleFacts,
3304
3164
  hostDefaultModel,
3305
3165
  templatePreStepCount,
@@ -3313,7 +3173,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3313
3173
  skillFolders: skillsState.folders
3314
3174
  };
3315
3175
  };
3316
- /** 激活模块的引擎参数子集(/param-overrides 读取;preset.yml 按 mtime 缓存)。 */
3176
+ /** 激活模块的引擎参数子集(/param-overrides 读取;module.yml 按 mtime 缓存)。 */
3317
3177
  const readParamOverrides = (dir) => {
3318
3178
  try {
3319
3179
  const spec = loadModuleSpec(dir);
@@ -3324,45 +3184,18 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3324
3184
  }
3325
3185
  return params;
3326
3186
  } catch (error) {
3327
- if (error instanceof PresetLayerSettingsError) throw error;
3187
+ if (error instanceof ModuleLayerSettingsError || error instanceof ModuleRulesError) throw error;
3328
3188
  return {};
3329
3189
  }
3330
3190
  };
3331
- /** 模块级模板变量(/preset-variables 读取)。 */
3332
- const readPresetVariables = (dir) => {
3333
- try {
3334
- const spec = loadModuleSpec(dir);
3335
- const variables = {};
3336
- for (const [key, value] of Object.entries(spec.variables ?? {})) if (typeof value === "string") variables[key] = value;
3337
- return {
3338
- variables,
3339
- enabled: spec.variablesEnabled !== false
3340
- };
3341
- } catch (error) {
3342
- if (error instanceof PresetLayerSettingsError) throw error;
3343
- return {
3344
- variables: {},
3345
- enabled: true
3346
- };
3347
- }
3348
- };
3349
- /** 生成目录实际生效配置(/prompt-configs 读取)。 */
3350
- const readPromptConfigs = (dir) => {
3351
- try {
3352
- const order = loadModuleSpec(dir).configOrder ?? {};
3353
- return dir.length > 0 ? listPromptConfigSpecs(join(dir, MODULE_CONFIGS_DIR)).map((config) => {
3354
- const raw = config;
3355
- const definition = stripConfigFieldSources(raw);
3356
- const fieldSources = readConfigFieldSources(config.id, raw.fieldSources);
3357
- return {
3358
- ...definition,
3359
- ...fieldSources === void 0 ? {} : { fieldSources },
3360
- ...Object.hasOwn(order, config.id) ? { sequence: order[config.id] } : {}
3361
- };
3362
- }) : [];
3363
- } catch {
3364
- return [];
3365
- }
3191
+ /** 完整已提交定义的只读变量快照,不因发布失败而再次触发切片恢复。 */
3192
+ const readModuleVariables = (dir) => {
3193
+ const snapshot = loadModuleDefinition(dir);
3194
+ return {
3195
+ variables: snapshot.variables,
3196
+ enabled: snapshot.variablesEnabled,
3197
+ revisions: snapshot.revisions
3198
+ };
3366
3199
  };
3367
3200
  const disposers = [
3368
3201
  assetSources.dispose,
@@ -3396,16 +3229,18 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3396
3229
  const meta = await loadEngineMeta();
3397
3230
  const extras = await collectDescribeExtras(dir, session.sessionId);
3398
3231
  const scope = resolveInstructionScope(sctx, session.sessionId);
3232
+ const variables = dir.length > 0 ? readModuleVariables(dir) : {
3233
+ variables: {},
3234
+ enabled: true
3235
+ };
3399
3236
  writeBridgeJson(res, 200, {
3400
3237
  ok: true,
3401
3238
  value: descriptorForTarget(descriptor, dir),
3402
3239
  meta: { meta },
3403
3240
  overrides: { overrides: dir.length > 0 ? readParamOverrides(dir) : {} },
3404
- variables: dir.length > 0 ? readPresetVariables(dir) : {
3405
- variables: {},
3406
- enabled: true
3407
- },
3408
- promptConfigs: { promptConfigs: mergeInstructionCards(readPromptConfigs(dir), scope.files) },
3241
+ variables,
3242
+ ..."revisions" in variables ? { variablesRevisions: variables.revisions } : {},
3243
+ promptConfigs: { promptConfigs: mergeInstructionCards([], scope.files) },
3409
3244
  instructions: {
3410
3245
  context: scope.context,
3411
3246
  files: scope.files,
@@ -3554,7 +3389,21 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3554
3389
  }
3555
3390
  const expectedRevision = typeof record.expectedRevision === "number" ? record.expectedRevision : void 0;
3556
3391
  try {
3557
- await sctx.settings.mutate(ns, record.ops, expectedRevision);
3392
+ const ops = record.ops;
3393
+ const toggles = ops.filter((op) => op.path.length === 1 && ["modulesEnabled", "writePreset"].includes(String(op.path[0])));
3394
+ const values = Object.fromEntries(toggles.filter((op) => op.op === "set").map((op) => [op.path[0], op.value]));
3395
+ if (toggles.length > 0) {
3396
+ readModulesEnabled(values);
3397
+ const normalized = ops.map((op) => op.path.length === 1 && op.path[0] === "writePreset" ? {
3398
+ ...op,
3399
+ path: ["modulesEnabled"]
3400
+ } : op);
3401
+ normalized.push({
3402
+ op: "unset",
3403
+ path: ["writePreset"]
3404
+ });
3405
+ await sctx.settings.mutate(ns, normalized, expectedRevision);
3406
+ } else await sctx.settings.mutate(ns, ops, expectedRevision);
3558
3407
  } catch (error) {
3559
3408
  writeBridgeJson(res, 409, {
3560
3409
  ok: false,
@@ -3575,41 +3424,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3575
3424
  }
3576
3425
  writeBridgeJson(res, 200, {
3577
3426
  ok: true,
3578
- value: descriptor
3579
- });
3580
- }
3581
- }),
3582
- sctx.webServer.register({
3583
- kind: "exact",
3584
- path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.configsValidate,
3585
- handler: async (req, res) => {
3586
- if (!guard(req, res)) return;
3587
- const parsedBody = await readBridgeBodyForHandler(req, res);
3588
- if (parsedBody === void 0) return;
3589
- const { body } = parsedBody;
3590
- if (body === null || body === void 0 || typeof body !== "object") {
3591
- writeBridgeJson(res, 400, {
3592
- ok: false,
3593
- code: "settings-rejected",
3594
- message: "unreadable JSON body"
3595
- });
3596
- return;
3597
- }
3598
- const record = body;
3599
- if (!Array.isArray(record.promptConfigs)) {
3600
- writeBridgeJson(res, 400, {
3601
- ok: false,
3602
- code: "prompt-configs-invalid",
3603
- message: "promptConfigs must be an array"
3604
- });
3605
- return;
3606
- }
3607
- writeBridgeJson(res, 200, {
3608
- ok: true,
3609
- value: await validatePromptConfigs(record.promptConfigs, {
3610
- strategyDir: getEngineStrategyDir(),
3611
- moduleDir: editDir(req)
3612
- })
3427
+ value: descriptorForTarget(descriptor, editDir(req))
3613
3428
  });
3614
3429
  }
3615
3430
  }),
@@ -3971,12 +3786,12 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
3971
3786
  return;
3972
3787
  }
3973
3788
  const dir = editDir(req) ?? "";
3974
- if (!guardPresetFormat(dir, res)) return;
3789
+ if (!guardModuleFormat(dir, res)) return;
3975
3790
  const scope = resolveInstructionScope(sctx, session.sessionId);
3976
3791
  writeBridgeJson(res, 200, {
3977
3792
  ok: true,
3978
3793
  value: {
3979
- promptConfigs: mergeInstructionCards(readPromptConfigs(dir), scope.files),
3794
+ promptConfigs: mergeInstructionCards([], scope.files),
3980
3795
  instructions: {
3981
3796
  context: scope.context,
3982
3797
  files: scope.files,
@@ -4129,7 +3944,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4129
3944
  }),
4130
3945
  sctx.webServer.register({
4131
3946
  kind: "exact",
4132
- path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.presetContent,
3947
+ path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.moduleContent,
4133
3948
  handler: async (req, res) => {
4134
3949
  if (!guard(req, res)) return;
4135
3950
  const parsedBody = await readBridgeBodyForHandler(req, res);
@@ -4203,17 +4018,17 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4203
4018
  if (dir.length === 0) {
4204
4019
  writeBridgeJson(res, 400, {
4205
4020
  ok: false,
4206
- code: "preset-dir-unavailable",
4021
+ code: "module-dir-unavailable",
4207
4022
  message: "模块目录未配置"
4208
4023
  });
4209
4024
  return;
4210
4025
  }
4211
- if (!guardPresetWrite(dir, res)) return;
4026
+ if (!guardModuleWrite(dir, res)) return;
4212
4027
  try {
4213
- if (!guardPresetIdentity(req, record, dir, res)) return;
4028
+ if (!guardModuleIdentity(req, record, dir, res)) return;
4214
4029
  mkdirSync(dir, { recursive: true });
4215
4030
  for (const entry of contents) writeFileSync(join(dir, entry.scope === "preset" ? "preset.md" : "agents.md"), entry.content, "utf8");
4216
- if (!await finishPresetChange(res, () => afterPresetImport?.(contents.map((entry) => entry.scope), basename(dir)))) return;
4031
+ if (!await finishModuleChange(res, () => afterModuleContentImport?.(contents.map((entry) => entry.scope), basename(dir)))) return;
4217
4032
  writeBridgeJson(res, 200, {
4218
4033
  ok: true,
4219
4034
  value: { scopes: contents.map((entry) => entry.scope) }
@@ -4221,7 +4036,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4221
4036
  } catch (error) {
4222
4037
  writeBridgeJson(res, 500, {
4223
4038
  ok: false,
4224
- code: "preset-import-failed",
4039
+ code: "module-import-failed",
4225
4040
  message: error instanceof Error ? error.message : String(error)
4226
4041
  });
4227
4042
  }
@@ -4230,13 +4045,25 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4230
4045
  sctx.webServer.register({
4231
4046
  kind: "exact",
4232
4047
  path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.triggers,
4048
+ handler: async (req, res) => {
4049
+ if (!guard(req, res)) return;
4050
+ writeBridgeJson(res, 410, {
4051
+ ok: false,
4052
+ code: "rules-route-retired",
4053
+ message: "声明规则已统一为 rules;请离线迁移旧模块并刷新客户端,使用 /rules 编辑。"
4054
+ });
4055
+ }
4056
+ }),
4057
+ sctx.webServer.register({
4058
+ kind: "exact",
4059
+ path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.rules,
4233
4060
  handler: async (req, res) => {
4234
4061
  if (!guard(req, res)) return;
4235
4062
  const dir = editDir(req) ?? "";
4236
4063
  if (dir.length === 0) {
4237
4064
  writeBridgeJson(res, 400, {
4238
4065
  ok: false,
4239
- code: "preset-dir-unavailable",
4066
+ code: "module-dir-unavailable",
4240
4067
  message: "当前模块目录不可用"
4241
4068
  });
4242
4069
  return;
@@ -4244,95 +4071,139 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4244
4071
  const parsed = await readBridgeBodyForHandler(req, res);
4245
4072
  if (parsed === void 0) return;
4246
4073
  const record = parsed.body ?? {};
4247
- if (typeof record.expectedPresetId !== "string" || record.expectedPresetId.length === 0 || Object.keys(record).some((key) => ![
4074
+ if (!isRecord(record) || Object.keys(record).some((key) => ![
4075
+ "expectedModuleId",
4248
4076
  "expectedPresetId",
4249
- "triggers",
4250
4077
  "expectedRevision",
4251
- "validateOnly"
4252
- ].includes(key)) || record.validateOnly !== void 0 && typeof record.validateOnly !== "boolean" || record.triggers !== void 0 && !Array.isArray(record.triggers) || record.expectedRevision !== void 0 && (typeof record.expectedRevision !== "string" || !SHA256_HEX_RE.test(record.expectedRevision))) {
4078
+ "expectedRevisions",
4079
+ "edits",
4080
+ "activateRuleId",
4081
+ "validateOnly",
4082
+ "refreshOnly"
4083
+ ].includes(key)) || typeof (record.expectedModuleId ?? record.expectedPresetId) !== "string" || (record.expectedModuleId ?? record.expectedPresetId) === "" || record.expectedRevision !== void 0 && (typeof record.expectedRevision !== "string" || !SHA256_HEX_RE.test(record.expectedRevision)) || record.edits !== void 0 && !Array.isArray(record.edits) || record.activateRuleId !== void 0 && (typeof record.activateRuleId !== "string" || record.activateRuleId.length === 0) || record.validateOnly !== void 0 && typeof record.validateOnly !== "boolean" || record.refreshOnly !== void 0 && typeof record.refreshOnly !== "boolean" || record.refreshOnly === true && (record.edits !== void 0 || record.activateRuleId !== void 0 || record.validateOnly !== void 0)) {
4253
4084
  writeBridgeJson(res, 400, {
4254
4085
  ok: false,
4255
- code: "triggers-invalid",
4256
- message: "声明请求字段、模块身份或版本格式不合法"
4086
+ code: "rules-invalid",
4087
+ message: "规则请求字段、模块身份或版本格式不合法"
4088
+ });
4089
+ return;
4090
+ }
4091
+ if (!guardModuleIdentity(req, record, dir, res)) return;
4092
+ if (record.refreshOnly === true) {
4093
+ if (!guardModuleWrite(dir, res)) return;
4094
+ const { getRuleEditorMeta } = await import(pathToFileURL(join(packageEngineDir(), "rule-spec.mjs")).href);
4095
+ try {
4096
+ await afterOverridesChange?.(basename(dir));
4097
+ } catch (error) {
4098
+ const committed = loadModuleDefinition(dir);
4099
+ writeBridgeJson(res, 200, {
4100
+ ok: true,
4101
+ value: {
4102
+ rules: committed.rules,
4103
+ revisions: committed.revisions,
4104
+ meta: getRuleEditorMeta(),
4105
+ persisted: true,
4106
+ publicationPending: true,
4107
+ publicationError: `运行刷新失败:${String(error)}`
4108
+ }
4109
+ });
4110
+ return;
4111
+ }
4112
+ const committed = loadModuleDefinition(dir);
4113
+ writeBridgeJson(res, 200, {
4114
+ ok: true,
4115
+ value: {
4116
+ rules: committed.rules,
4117
+ revisions: committed.revisions,
4118
+ meta: getRuleEditorMeta(),
4119
+ publicationPending: false
4120
+ }
4257
4121
  });
4258
4122
  return;
4259
4123
  }
4260
- if (!guardPresetIdentity(req, record, dir, res)) return;
4261
- const writing = record.triggers !== void 0 && record.validateOnly !== true;
4262
- if (writing && typeof record.expectedRevision !== "string") {
4124
+ const editing = record.edits !== void 0 || record.activateRuleId !== void 0;
4125
+ const writing = editing && record.validateOnly !== true;
4126
+ if (editing && record.expectedRevisions === void 0 && typeof record.expectedRevision !== "string") {
4263
4127
  writeBridgeJson(res, 400, {
4264
4128
  ok: false,
4265
- code: "triggers-invalid",
4266
- message: "保存声明必须提供读取时的 expectedRevision"
4129
+ code: "rules-invalid",
4130
+ message: "修改规则必须提供读取时的 expectedRevisions"
4267
4131
  });
4268
4132
  return;
4269
4133
  }
4270
- if (writing && !guardPresetWrite(dir, res)) return;
4134
+ if (writing && !guardModuleWrite(dir, res)) return;
4271
4135
  try {
4272
- const { compileDeclarations } = await import(pathToFileURL(join(packageEngineDir(), "trigger-spec.mjs")).href);
4273
- const { getTriggerEditorMeta } = await import(pathToFileURL(join(packageEngineDir(), "trigger-editor-meta.mjs")).href);
4274
- if (!guardPresetIdentity(req, record, dir, res)) return;
4275
- const snapshot = readModuleTriggers(dir);
4276
- if (record.triggers !== void 0) try {
4277
- const strategy = loadModuleSpec(dir).moduleConfigs?.["declared-triggers"]?.strategyDir;
4278
- compileDeclarations(record.triggers, { promptConfigOptions: triggerPromptConfigOptions(dir, strategy) });
4136
+ const { getRuleEditorMeta } = await import(pathToFileURL(join(packageEngineDir(), "rule-spec.mjs")).href);
4137
+ if (!guardModuleIdentity(req, record, dir, res)) return;
4138
+ const candidate = editing ? editModuleRules(dir, {
4139
+ ...record.expectedRevision === void 0 ? {} : { expectedRevision: record.expectedRevision },
4140
+ ...record.expectedRevisions === void 0 ? {} : { expectedRevisions: record.expectedRevisions },
4141
+ ...record.edits === void 0 ? {} : { edits: record.edits },
4142
+ ...record.activateRuleId === void 0 ? {} : { activateRuleId: record.activateRuleId },
4143
+ ...record.validateOnly === void 0 ? {} : { validateOnly: record.validateOnly }
4144
+ }) : isEditableModuleDir(dir) ? readModuleRules(dir) : ensureModuleSlices(dir, { writable: false });
4145
+ if (writing) try {
4146
+ await afterOverridesChange?.(basename(dir));
4279
4147
  } catch (error) {
4280
- writeBridgeJson(res, 400, {
4281
- ok: false,
4282
- code: "triggers-invalid",
4283
- message: String(error.message ?? error)
4148
+ writeBridgeJson(res, 200, {
4149
+ ok: true,
4150
+ value: {
4151
+ rules: candidate.rules,
4152
+ revisions: candidate.revisions,
4153
+ meta: getRuleEditorMeta(),
4154
+ persisted: true,
4155
+ publicationPending: true,
4156
+ publicationError: `规则已保存,但运行刷新失败:${String(error)}`
4157
+ }
4284
4158
  });
4285
4159
  return;
4286
4160
  }
4287
- if (writing) {
4288
- if (!Array.isArray(loadModuleSpec(dir).modules)) {
4289
- writeBridgeJson(res, 400, {
4290
- ok: false,
4291
- code: "triggers-composition-readonly",
4292
- message: "当前组合没有可编辑的 modules 清单,无法自动装配声明引擎"
4293
- });
4294
- return;
4295
- }
4296
- if (!saveModuleTriggers(dir, record.triggers, record.expectedRevision)) {
4297
- writeBridgeJson(res, 409, {
4298
- ok: false,
4299
- code: "triggers-conflict",
4300
- message: "模块文件已变化;草稿未写入,请重新读取后保存"
4301
- });
4302
- return;
4303
- }
4304
- try {
4305
- await afterOverridesChange?.(basename(dir));
4306
- } catch (error) {
4307
- writeBridgeJson(res, 500, {
4308
- ok: false,
4309
- code: "triggers-rebuild-failed",
4310
- message: `声明已保存,但模块重建失败;请重新读取后重试:${String(error)}`
4311
- });
4312
- return;
4313
- }
4314
- }
4315
- const result = writing ? readModuleTriggers(dir) : snapshot;
4316
- if (writing && !isDeepStrictEqual(result.triggers, record.triggers)) {
4161
+ const result = writing ? readModuleRules(dir) : candidate;
4162
+ if (writing && !isDeepStrictEqual(result.rules, candidate.rules)) {
4317
4163
  writeBridgeJson(res, 409, {
4318
4164
  ok: false,
4319
- code: "triggers-conflict",
4320
- message: "重建期间规则已再次变化;请重新读取,当前草稿仍保留"
4165
+ code: "rules-conflict",
4166
+ message: "重建期间规则再次变化;请重新读取,当前草稿仍保留"
4321
4167
  });
4322
4168
  return;
4323
4169
  }
4324
4170
  writeBridgeJson(res, 200, {
4325
4171
  ok: true,
4326
4172
  value: {
4327
- ...result,
4328
- meta: getTriggerEditorMeta()
4173
+ rules: result.rules,
4174
+ revisions: result.revisions,
4175
+ meta: getRuleEditorMeta()
4329
4176
  }
4330
4177
  });
4331
4178
  } catch (error) {
4179
+ if (error instanceof ModuleRulesError) {
4180
+ if (error.persisted) {
4181
+ const committed = loadModuleDefinition(dir);
4182
+ const { getRuleEditorMeta } = await import(pathToFileURL(join(packageEngineDir(), "rule-spec.mjs")).href);
4183
+ writeBridgeJson(res, 200, {
4184
+ ok: true,
4185
+ value: {
4186
+ rules: committed.rules,
4187
+ revisions: committed.revisions,
4188
+ meta: getRuleEditorMeta(),
4189
+ persisted: true,
4190
+ publicationPending: true,
4191
+ publicationError: error.message
4192
+ }
4193
+ });
4194
+ return;
4195
+ }
4196
+ writeBridgeJson(res, error.status, {
4197
+ ok: false,
4198
+ code: error.code,
4199
+ message: error.message
4200
+ });
4201
+ return;
4202
+ }
4332
4203
  if (writeLayerSettingsError(res, error)) return;
4333
4204
  writeBridgeJson(res, 500, {
4334
4205
  ok: false,
4335
- code: "triggers-failed",
4206
+ code: "rules-failed",
4336
4207
  message: String(error.message ?? error)
4337
4208
  });
4338
4209
  }
@@ -4347,7 +4218,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4347
4218
  if (dir.length === 0) {
4348
4219
  writeBridgeJson(res, 400, {
4349
4220
  ok: false,
4350
- code: "preset-dir-unavailable",
4221
+ code: "module-dir-unavailable",
4351
4222
  message: "模块目录未配置"
4352
4223
  });
4353
4224
  return;
@@ -4358,7 +4229,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4358
4229
  if (parsedBody === void 0) return;
4359
4230
  const { body } = parsedBody;
4360
4231
  const record = body ?? {};
4361
- if (!guardPresetIdentity(req, record, dir, res)) return;
4232
+ if (!guardModuleIdentity(req, record, dir, res)) return;
4362
4233
  if (record.rebuild !== void 0 && typeof record.rebuild !== "boolean") {
4363
4234
  writeBridgeJson(res, 400, {
4364
4235
  ok: false,
@@ -4367,7 +4238,15 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4367
4238
  });
4368
4239
  return;
4369
4240
  }
4370
- if (record.overrides === void 0 && record.promptConfigs === void 0) {
4241
+ if (record.promptConfigs !== void 0) {
4242
+ writeBridgeJson(res, 410, {
4243
+ ok: false,
4244
+ code: "rules-route-retired",
4245
+ message: "提示词配置已统一为 rules,请使用带版本的 /rules 事务编辑。"
4246
+ });
4247
+ return;
4248
+ }
4249
+ if (record.overrides === void 0) {
4371
4250
  if (record.rebuild !== void 0) {
4372
4251
  writeBridgeJson(res, 400, {
4373
4252
  ok: false,
@@ -4390,17 +4269,16 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4390
4269
  });
4391
4270
  return;
4392
4271
  }
4393
- if (record.promptConfigs !== void 0 && !Array.isArray(record.promptConfigs)) {
4394
- writeBridgeJson(res, 400, {
4272
+ if (!guardModuleWrite(dir, res)) return;
4273
+ const rawOverrides = record.overrides;
4274
+ if (rawOverrides !== void 0 && RULE_OWNED_MODEL_PARAMS.some((key) => Object.hasOwn(rawOverrides, key))) {
4275
+ writeBridgeJson(res, 410, {
4395
4276
  ok: false,
4396
- code: "prompt-configs-invalid",
4397
- message: "promptConfigs must be an array"
4277
+ code: "rules-route-retired",
4278
+ message: "模型路由和采样参数已归入 request-params 规则动作,请通过 /rules 编辑。"
4398
4279
  });
4399
4280
  return;
4400
4281
  }
4401
- if (!guardPresetWrite(dir, res)) return;
4402
- if (Array.isArray(record.promptConfigs)) record.promptConfigs = record.promptConfigs.map((config) => isRecord(config) ? stripConfigFieldSources(config) : config);
4403
- const rawOverrides = record.overrides;
4404
4282
  if (rawOverrides !== void 0) {
4405
4283
  const unknownKeys = Object.keys(rawOverrides).filter((key) => key === "promptConfigs" || !PARAM_KEYS.has(key));
4406
4284
  if (unknownKeys.length > 0) {
@@ -4422,43 +4300,12 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4422
4300
  return;
4423
4301
  }
4424
4302
  try {
4425
- if (Array.isArray(record.promptConfigs)) {
4426
- const validation = await validatePromptConfigs(record.promptConfigs, {
4427
- strategyDir: getEngineStrategyDir(),
4428
- moduleDir: dir
4429
- });
4430
- if (!validation.valid) {
4431
- writeBridgeJson(res, 400, {
4432
- ok: false,
4433
- code: "prompt-configs-invalid",
4434
- message: validation.errors.map(({ index, id, message }) => `[${index}] ${id}: ${message}`).join("; ")
4435
- });
4436
- return;
4437
- }
4438
- const spec = loadModuleSpec(dir);
4439
- const conflicting = record.promptConfigs.some((config) => {
4440
- if (config === null || typeof config !== "object" || Array.isArray(config)) return false;
4441
- const entry = config;
4442
- return entry.enabled !== false && entry.params?.complete === true;
4443
- });
4444
- if (spec.persona?.complete === true && conflicting) {
4445
- writeBridgeJson(res, 400, {
4446
- ok: false,
4447
- code: "overrides-invalid-value",
4448
- message: "顶层人设已开启「独占」;提示词配置的「独占」与之互斥,请先关闭其一"
4449
- });
4450
- return;
4451
- }
4452
- }
4453
- if (!guardPresetIdentity(req, record, dir, res)) return;
4454
- saveModuleParams(moduleRoot, templateName, rawOverrides, record.promptConfigs);
4303
+ if (!guardModuleIdentity(req, record, dir, res)) return;
4304
+ saveModuleParams(moduleRoot, templateName, rawOverrides, void 0);
4455
4305
  if (record.rebuild !== false && !await runOverridesChange(res, basename(dir))) return;
4456
4306
  writeBridgeJson(res, 200, {
4457
4307
  ok: true,
4458
- value: {
4459
- ...record.overrides !== void 0 ? { overrides: record.overrides } : {},
4460
- ...record.promptConfigs !== void 0 ? { promptConfigs: record.promptConfigs } : {}
4461
- }
4308
+ value: { overrides: record.overrides }
4462
4309
  });
4463
4310
  } catch (error) {
4464
4311
  if (writeLayerSettingsError(res, error)) return;
@@ -4472,14 +4319,14 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4472
4319
  }),
4473
4320
  sctx.webServer.register({
4474
4321
  kind: "exact",
4475
- path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.presetVariables,
4322
+ path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.moduleVariables,
4476
4323
  handler: async (req, res) => {
4477
4324
  if (!guard(req, res)) return;
4478
4325
  const dir = editDir(req) ?? "";
4479
4326
  if (dir.length === 0) {
4480
4327
  writeBridgeJson(res, 400, {
4481
4328
  ok: false,
4482
- code: "preset-dir-unavailable",
4329
+ code: "module-dir-unavailable",
4483
4330
  message: "模块目录未配置"
4484
4331
  });
4485
4332
  return;
@@ -4490,18 +4337,51 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4490
4337
  if (parsedBody === void 0) return;
4491
4338
  const { body } = parsedBody;
4492
4339
  const record = body ?? {};
4493
- if (!guardPresetIdentity(req, record, dir, res)) return;
4340
+ if (!guardModuleIdentity(req, record, dir, res)) return;
4341
+ if (record.refreshOnly !== void 0 && (typeof record.refreshOnly !== "boolean" || record.refreshOnly === true && (record.variables !== void 0 || record.enabled !== void 0))) {
4342
+ writeBridgeJson(res, 400, {
4343
+ ok: false,
4344
+ code: "module-variables-invalid",
4345
+ message: "refreshOnly 不能与变量写入混用"
4346
+ });
4347
+ return;
4348
+ }
4349
+ if (record.refreshOnly === true) {
4350
+ if (!guardModuleWrite(dir, res)) return;
4351
+ try {
4352
+ await afterOverridesChange?.(basename(dir));
4353
+ } catch (error) {
4354
+ writeBridgeJson(res, 200, {
4355
+ ok: true,
4356
+ value: {
4357
+ ...readModuleVariables(dir),
4358
+ persisted: true,
4359
+ publicationPending: true,
4360
+ publicationError: `运行刷新失败:${String(error)}`
4361
+ }
4362
+ });
4363
+ return;
4364
+ }
4365
+ writeBridgeJson(res, 200, {
4366
+ ok: true,
4367
+ value: {
4368
+ ...readModuleVariables(dir),
4369
+ publicationPending: false
4370
+ }
4371
+ });
4372
+ return;
4373
+ }
4494
4374
  if (record.variables === void 0 && record.enabled === void 0) {
4495
4375
  writeBridgeJson(res, 200, {
4496
4376
  ok: true,
4497
- value: readPresetVariables(dir)
4377
+ value: readModuleVariables(dir)
4498
4378
  });
4499
4379
  return;
4500
4380
  }
4501
4381
  if (record.variables !== void 0 && (!isRecord(record.variables) || Object.values(record.variables).some((value) => typeof value !== "string"))) {
4502
4382
  writeBridgeJson(res, 400, {
4503
4383
  ok: false,
4504
- code: "preset-variables-invalid",
4384
+ code: "module-variables-invalid",
4505
4385
  message: "variables must be an object with string values"
4506
4386
  });
4507
4387
  return;
@@ -4509,25 +4389,64 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4509
4389
  if (record.enabled !== void 0 && typeof record.enabled !== "boolean") {
4510
4390
  writeBridgeJson(res, 400, {
4511
4391
  ok: false,
4512
- code: "preset-variables-invalid",
4392
+ code: "module-variables-invalid",
4513
4393
  message: "enabled must be a boolean"
4514
4394
  });
4515
4395
  return;
4516
4396
  }
4517
- if (!guardPresetWrite(dir, res)) return;
4397
+ if (!isRecord(record.expectedRevisions) || Object.keys(record.expectedRevisions).some((key) => ![
4398
+ "rules",
4399
+ "settings",
4400
+ "variables"
4401
+ ].includes(key)) || record.variables !== void 0 && (typeof record.expectedRevisions.variables !== "string" || !SHA256_HEX_RE.test(record.expectedRevisions.variables)) || record.enabled !== void 0 && (typeof record.expectedRevisions.settings !== "string" || !SHA256_HEX_RE.test(record.expectedRevisions.settings))) {
4402
+ writeBridgeJson(res, 400, {
4403
+ ok: false,
4404
+ code: "module-variables-invalid",
4405
+ message: "保存变量或开关必须提供对应 expectedRevisions"
4406
+ });
4407
+ return;
4408
+ }
4409
+ if (!guardModuleWrite(dir, res)) return;
4518
4410
  try {
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;
4411
+ if (!guardModuleIdentity(req, record, dir, res)) return;
4412
+ saveModuleParams(moduleRoot, templateName, void 0, void 0, record.variables, record.enabled, record.expectedRevisions);
4413
+ const committed = readModuleVariables(dir);
4414
+ try {
4415
+ await afterOverridesChange?.(basename(dir));
4416
+ } catch (error) {
4417
+ writeBridgeJson(res, 200, {
4418
+ ok: true,
4419
+ value: {
4420
+ ...committed,
4421
+ persisted: true,
4422
+ publicationPending: true,
4423
+ publicationError: `变量已保存,但运行刷新失败:${String(error)}`
4424
+ }
4425
+ });
4426
+ return;
4427
+ }
4522
4428
  writeBridgeJson(res, 200, {
4523
4429
  ok: true,
4524
- value: { variables: record.variables }
4430
+ value: committed
4525
4431
  });
4526
4432
  } catch (error) {
4527
- writeBridgeJson(res, 409, {
4433
+ if (error instanceof ModuleRulesError && error.persisted) {
4434
+ writeBridgeJson(res, 200, {
4435
+ ok: true,
4436
+ value: {
4437
+ ...readModuleVariables(dir),
4438
+ persisted: true,
4439
+ publicationPending: true,
4440
+ publicationError: error.message
4441
+ }
4442
+ });
4443
+ return;
4444
+ }
4445
+ const message = error instanceof Error ? error.message : String(error);
4446
+ writeBridgeJson(res, error instanceof ModuleRulesError ? error.status : 409, {
4528
4447
  ok: false,
4529
- code: "preset-variables-rejected",
4530
- message: `模板变量保存失败:${error instanceof Error ? error.message : String(error)}`
4448
+ code: error instanceof ModuleRulesError ? error.code : "module-variables-rejected",
4449
+ message: `模板变量保存失败:${message}`
4531
4450
  });
4532
4451
  }
4533
4452
  }
@@ -4541,7 +4460,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4541
4460
  if (dir.length === 0) {
4542
4461
  writeBridgeJson(res, 400, {
4543
4462
  ok: false,
4544
- code: "preset-dir-unavailable",
4463
+ code: "module-dir-unavailable",
4545
4464
  message: "模块目录未配置"
4546
4465
  });
4547
4466
  return;
@@ -4550,7 +4469,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4550
4469
  if (parsedBody === void 0) return;
4551
4470
  const { body } = parsedBody;
4552
4471
  const record = body ?? {};
4553
- if (!guardPresetIdentity(req, record, dir, res)) return;
4472
+ if (!guardModuleIdentity(req, record, dir, res)) return;
4554
4473
  if (record.customTools === void 0) {
4555
4474
  try {
4556
4475
  const spec = loadModuleSpec(dir);
@@ -4587,13 +4506,13 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4587
4506
  });
4588
4507
  return;
4589
4508
  }
4590
- if (!guardPresetWrite(dir, res)) return;
4591
- if (!guardPresetIdentity(req, record, dir, res)) return;
4592
- withPresetDoc(dir, (doc) => {
4509
+ if (!guardModuleWrite(dir, res)) return;
4510
+ if (!guardModuleIdentity(req, record, dir, res)) return;
4511
+ withModuleDefinition(dir, (doc) => {
4593
4512
  if (customTools.length === 0) doc.deleteIn(["customTools"]);
4594
4513
  else {
4595
4514
  doc.setIn(["customTools"], customTools);
4596
- appendPresetModules(doc, customToolModules(customTools));
4515
+ appendModuleCapabilities(doc, customToolModules(customTools));
4597
4516
  }
4598
4517
  });
4599
4518
  if (!await runOverridesChange(res, basename(dir))) return;
@@ -4619,7 +4538,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4619
4538
  if (dir.length === 0) {
4620
4539
  writeBridgeJson(res, 400, {
4621
4540
  ok: false,
4622
- code: "preset-dir-unavailable",
4541
+ code: "module-dir-unavailable",
4623
4542
  message: "模块目录未配置"
4624
4543
  });
4625
4544
  return;
@@ -4627,7 +4546,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4627
4546
  const parsedBody = await readBridgeBodyForHandler(req, res);
4628
4547
  if (parsedBody === void 0) return;
4629
4548
  const record = parsedBody.body ?? {};
4630
- if (!guardPresetIdentity(req, record, dir, res)) return;
4549
+ if (!guardModuleIdentity(req, record, dir, res)) return;
4631
4550
  if (record.persona === void 0) {
4632
4551
  try {
4633
4552
  writeBridgeJson(res, 200, {
@@ -4643,33 +4562,29 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4643
4562
  }
4644
4563
  return;
4645
4564
  }
4646
- if (!guardPresetWrite(dir, res)) return;
4565
+ if (!guardModuleWrite(dir, res)) return;
4647
4566
  try {
4648
4567
  const raw = record.persona;
4649
4568
  const persona = raw === null ? null : readPersonaSpec(raw);
4650
4569
  if (persona === void 0) {
4651
4570
  writeBridgeJson(res, 400, {
4652
4571
  ok: false,
4653
- code: "preset-persona-invalid",
4572
+ code: "module-persona-invalid",
4654
4573
  message: "persona 必须包含字符串 prefix(可选 suffix / complete / includeRuntimeContext)"
4655
4574
  });
4656
4575
  return;
4657
4576
  }
4658
4577
  if (persona?.complete === true) {
4659
- if ((loadModuleSpec(dir).promptConfigs ?? []).some((config) => {
4660
- if (config === null || typeof config !== "object" || Array.isArray(config)) return false;
4661
- const entry = config;
4662
- return entry.enabled !== false && entry.params?.complete === true;
4663
- })) {
4578
+ if (ruleInjections(loadModuleSpec(dir).rules).some(({ rule, config }) => rule.enabled !== false && config.enabled !== false && config.params?.complete === true)) {
4664
4579
  writeBridgeJson(res, 400, {
4665
4580
  ok: false,
4666
- code: "preset-persona-complete-conflict",
4581
+ code: "module-persona-complete-conflict",
4667
4582
  message: "提示词配置已有「独占」段;顶层人设的「独占」与之互斥,请先关闭其一"
4668
4583
  });
4669
4584
  return;
4670
4585
  }
4671
4586
  }
4672
- savePresetPersona(dirname(dir), basename(dir), persona);
4587
+ saveModulePersona(dirname(dir), basename(dir), persona);
4673
4588
  if (!await runOverridesChange(res, basename(dir))) return;
4674
4589
  writeBridgeJson(res, 200, {
4675
4590
  ok: true,
@@ -4678,7 +4593,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4678
4593
  } catch (error) {
4679
4594
  writeBridgeJson(res, 409, {
4680
4595
  ok: false,
4681
- code: "preset-persona-rejected",
4596
+ code: "module-persona-rejected",
4682
4597
  message: `人设保存失败:${error instanceof Error ? error.message : String(error)}`
4683
4598
  });
4684
4599
  }
@@ -4736,7 +4651,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4736
4651
  }),
4737
4652
  sctx.webServer.register({
4738
4653
  kind: "exact",
4739
- path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.importPresetPackage,
4654
+ path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.importModulePackage,
4740
4655
  handler: async (req, res) => {
4741
4656
  if (!guard(req, res)) return;
4742
4657
  const parsedBody = await readBridgeBodyForHandler(req, res);
@@ -4755,14 +4670,14 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4755
4670
  if (!request.ok) {
4756
4671
  writeBridgeJson(res, 400, {
4757
4672
  ok: false,
4758
- code: "preset-package-invalid",
4673
+ code: "module-package-invalid",
4759
4674
  message: request.message
4760
4675
  });
4761
4676
  return;
4762
4677
  }
4763
4678
  try {
4764
- const files = await expandPresetSource(request.params.sourceId === void 0 ? readBridgeFiles(record.files) : assetSources.read(request.params.sourceId));
4765
- const preview = presetImportPreview(userModulesDir(), files, request.params);
4679
+ const files = await expandModuleSource(request.params.sourceId === void 0 ? readBridgeFiles(record.files) : assetSources.read(request.params.sourceId));
4680
+ const preview = moduleImportPreview(userModulesDir(), files, request.params);
4766
4681
  if (!("prepared" in preview)) {
4767
4682
  if (!request.params.preview) throw new Error("请先选择内容类型或提示顺序组并重新预览");
4768
4683
  writeBridgeJson(res, 200, {
@@ -4789,9 +4704,9 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4789
4704
  });
4790
4705
  return;
4791
4706
  }
4792
- const installed = await installPresetPackage(userModulesDir(), files, request.params);
4707
+ const installed = await installModulePackage(userModulesDir(), files, request.params);
4793
4708
  invalidateDescriptor();
4794
- if (!await finishPresetChange(res, () => afterPresetPackageImport?.(installed.id))) return;
4709
+ if (!await finishModuleChange(res, () => afterModulePackageImport?.(installed.id))) return;
4795
4710
  writeBridgeJson(res, 200, {
4796
4711
  ok: true,
4797
4712
  value: {
@@ -4801,8 +4716,8 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4801
4716
  }
4802
4717
  });
4803
4718
  } catch (error) {
4804
- const code = typeof error.code === "string" ? String(error.code) : "preset-package-invalid";
4805
- writeBridgeJson(res, code === "preset-preview-stale" ? 409 : 400, {
4719
+ const code = typeof error.code === "string" ? String(error.code) : "module-package-invalid";
4720
+ writeBridgeJson(res, code === "module-preview-stale" ? 409 : 400, {
4806
4721
  ok: false,
4807
4722
  code,
4808
4723
  message: error instanceof Error ? error.message : String(error)
@@ -4812,7 +4727,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4812
4727
  }),
4813
4728
  sctx.webServer.register({
4814
4729
  kind: "exact",
4815
- path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.exportPreset,
4730
+ path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.exportModule,
4816
4731
  handler: async (req, res) => {
4817
4732
  if (!guard(req, res)) return;
4818
4733
  const parsedBody = await readBridgeBodyForHandler(req, res);
@@ -4828,7 +4743,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4828
4743
  "memoryChoices"
4829
4744
  ]);
4830
4745
  if (Object.keys(record).some((key) => !allowed.has(key))) throw new Error("导出请求包含未知字段");
4831
- assertPresetId(record.id);
4746
+ assertModuleId(record.id);
4832
4747
  if (record.id.length > 128) throw new Error("模块 ID 过长");
4833
4748
  if (record.mode !== void 0 && record.mode !== "definition" && record.mode !== "zip") throw new Error("mode 必须是 definition 或 zip");
4834
4749
  if (record.preview !== void 0 && typeof record.preview !== "boolean") throw new Error("preview 必须是布尔值");
@@ -4836,11 +4751,11 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4836
4751
  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 选择");
4837
4752
  writeBridgeJson(res, 200, {
4838
4753
  ok: true,
4839
- value: await exportPresetPackage(userModulesDir(), record)
4754
+ value: await exportModulePackage(userModulesDir(), record)
4840
4755
  });
4841
4756
  } catch (error) {
4842
4757
  const message = error instanceof Error ? error.message : String(error);
4843
- const code = typeof error.code === "string" ? String(error.code) : "preset-export-failed";
4758
+ const code = typeof error.code === "string" ? String(error.code) : "module-export-failed";
4844
4759
  writeBridgeJson(res, code.includes("stale") ? 409 : 400, {
4845
4760
  ok: false,
4846
4761
  code,
@@ -4862,7 +4777,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4862
4777
  if (id.length === 0) {
4863
4778
  writeBridgeJson(res, 400, {
4864
4779
  ok: false,
4865
- code: "preset-delete-rejected",
4780
+ code: "module-delete-rejected",
4866
4781
  message: "缺少模块 id"
4867
4782
  });
4868
4783
  return;
@@ -4871,21 +4786,21 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4871
4786
  if (activeDir.length > 0 && basename(activeDir) === id) {
4872
4787
  writeBridgeJson(res, 400, {
4873
4788
  ok: false,
4874
- code: "preset-in-use",
4789
+ code: "module-in-use",
4875
4790
  message: `模块「${id}」正在使用中,请先切换其他模块再删除`
4876
4791
  });
4877
4792
  return;
4878
4793
  }
4879
- const result = removeUserPreset(id);
4794
+ const result = removeUserModule(id);
4880
4795
  if (!result.ok) {
4881
4796
  writeBridgeJson(res, 400, {
4882
4797
  ok: false,
4883
- code: "preset-delete-rejected",
4798
+ code: "module-delete-rejected",
4884
4799
  message: result.message
4885
4800
  });
4886
4801
  return;
4887
4802
  }
4888
- if (!await refreshPresetList(res, id)) return;
4803
+ if (!await refreshModuleList(res, id)) return;
4889
4804
  writeBridgeJson(res, 200, {
4890
4805
  ok: true,
4891
4806
  value: { id }
@@ -4905,21 +4820,21 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4905
4820
  if (id.length === 0) {
4906
4821
  writeBridgeJson(res, 400, {
4907
4822
  ok: false,
4908
- code: "preset-clone-rejected",
4823
+ code: "module-clone-rejected",
4909
4824
  message: "缺少模块 id"
4910
4825
  });
4911
4826
  return;
4912
4827
  }
4913
- const result = cloneBuiltinPreset(id, record.autoSuffix === true);
4828
+ const result = cloneBuiltinModule(id, record.autoSuffix === true);
4914
4829
  if (!result.ok) {
4915
4830
  writeBridgeJson(res, 400, {
4916
4831
  ok: false,
4917
- code: "preset-clone-rejected",
4832
+ code: "module-clone-rejected",
4918
4833
  message: result.message
4919
4834
  });
4920
4835
  return;
4921
4836
  }
4922
- if (!await refreshPresetList(res, result.id)) return;
4837
+ if (!await refreshModuleList(res, result.id)) return;
4923
4838
  writeBridgeJson(res, 200, {
4924
4839
  ok: true,
4925
4840
  value: { id: result.id }
@@ -4939,7 +4854,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4939
4854
  if (id.length === 0) {
4940
4855
  writeBridgeJson(res, 400, {
4941
4856
  ok: false,
4942
- code: "preset-duplicate-rejected",
4857
+ code: "module-duplicate-rejected",
4943
4858
  message: "缺少模块 id"
4944
4859
  });
4945
4860
  return;
@@ -4948,12 +4863,12 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4948
4863
  if (!result.ok) {
4949
4864
  writeBridgeJson(res, 400, {
4950
4865
  ok: false,
4951
- code: "preset-duplicate-rejected",
4866
+ code: "module-duplicate-rejected",
4952
4867
  message: result.message
4953
4868
  });
4954
4869
  return;
4955
4870
  }
4956
- if (!await refreshPresetList(res, result.id)) return;
4871
+ if (!await refreshModuleList(res, result.id)) return;
4957
4872
  writeBridgeJson(res, 200, {
4958
4873
  ok: true,
4959
4874
  value: { id: result.id }
@@ -4973,7 +4888,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4973
4888
  if (id.length === 0) {
4974
4889
  writeBridgeJson(res, 400, {
4975
4890
  ok: false,
4976
- code: "preset-open-rejected",
4891
+ code: "module-open-rejected",
4977
4892
  message: "缺少模块 id"
4978
4893
  });
4979
4894
  return;
@@ -4982,7 +4897,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
4982
4897
  if (!result.ok) {
4983
4898
  writeBridgeJson(res, 400, {
4984
4899
  ok: false,
4985
- code: "preset-open-rejected",
4900
+ code: "module-open-rejected",
4986
4901
  message: `${result.message}(${result.path})`
4987
4902
  });
4988
4903
  return;
@@ -5006,7 +4921,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5006
4921
  if (id.length === 0) {
5007
4922
  writeBridgeJson(res, 400, {
5008
4923
  ok: false,
5009
- code: "preset-enable-rejected",
4924
+ code: "module-enable-rejected",
5010
4925
  message: "缺少模块 id"
5011
4926
  });
5012
4927
  return;
@@ -5014,7 +4929,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5014
4929
  if (typeof record.enabled !== "boolean") {
5015
4930
  writeBridgeJson(res, 400, {
5016
4931
  ok: false,
5017
- code: "preset-enable-rejected",
4932
+ code: "module-enable-rejected",
5018
4933
  message: "enabled 必须是布尔值"
5019
4934
  });
5020
4935
  return;
@@ -5022,7 +4937,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5022
4937
  if (record.enabled && !moduleDirExists(userModulesDir(), id)) {
5023
4938
  writeBridgeJson(res, 400, {
5024
4939
  ok: false,
5025
- code: "preset-enable-rejected",
4940
+ code: "module-enable-rejected",
5026
4941
  message: `模块 ${id} 不存在`
5027
4942
  });
5028
4943
  return;
@@ -5094,7 +5009,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5094
5009
  if (dir.length === 0) {
5095
5010
  writeBridgeJson(res, 400, {
5096
5011
  ok: false,
5097
- code: "preset-dir-unavailable",
5012
+ code: "module-dir-unavailable",
5098
5013
  message: "模块目录未配置"
5099
5014
  });
5100
5015
  return;
@@ -5120,9 +5035,9 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5120
5035
  });
5121
5036
  return;
5122
5037
  }
5123
- if (!guardPresetWrite(dir, res)) return;
5038
+ if (!guardModuleWrite(dir, res)) return;
5124
5039
  try {
5125
- const files = await expandPresetSource(request.params.sourceId === void 0 ? readBridgeFiles(record.files) : assetSources.read(request.params.sourceId));
5040
+ const files = await expandModuleSource(request.params.sourceId === void 0 ? readBridgeFiles(record.files) : assetSources.read(request.params.sourceId));
5126
5041
  if (editDir(req) !== dir) {
5127
5042
  writeBridgeJson(res, 409, {
5128
5043
  ok: false,
@@ -5143,98 +5058,40 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5143
5058
  });
5144
5059
  return;
5145
5060
  }
5146
- const root = canonicalPresetRoot(userModulesDir());
5147
- if (assertModuleDirectory(root, basename(dir)) !== realpathSync(dir)) throw new Error("当前模块不属于可写的官方模块根");
5148
- const cards = canonicalPresetRoot(charactersDir(root), true);
5149
- let targetId = request.params.targetId ?? prepared.spec.id;
5150
- if (request.params.targetId === void 0 && request.params.overwrite !== true) {
5151
- const wanted = targetId;
5152
- for (let n = 1; presetPathExists(join(cards, targetId)); n++) targetId = `${wanted}-copy${n === 1 ? "" : "-" + n}`;
5153
- }
5154
- assertPresetId(targetId);
5155
- const options = {
5156
- ...request.params,
5157
- targetId
5158
- };
5159
- const converted = previewCharacterCard(files, options);
5160
- if (!converted.ok) throw new Error(converted.message);
5161
- const version = directoryVersionOf(join(cards, targetId));
5162
- const previewRevision = computePreviewRevision({
5163
- files: [{
5164
- path: "source",
5165
- content: converted.sourceDigest
5166
- }, {
5167
- path: "choices",
5168
- content: JSON.stringify([
5169
- targetId,
5170
- request.params.targetName ?? "",
5171
- request.params.overwrite === true,
5172
- request.params.sourceKind ?? null
5173
- ])
5174
- }],
5175
- ...request.params.promptOrderCharacterId === void 0 ? {} : { orderCharacterId: request.params.promptOrderCharacterId },
5176
- converter: "asset-character/1",
5177
- target: {
5178
- kind: "character-card",
5179
- targetId,
5180
- targetVersion: version,
5181
- ownerPreset: JSON.stringify([
5182
- root,
5183
- basename(dir),
5184
- directoryVersionOf(dir)
5185
- ])
5186
- }
5187
- });
5061
+ const root = userModulesDir();
5062
+ const preview = moduleImportPreview(root, files, request.params);
5063
+ if (!("prepared" in preview)) throw new Error("角色来源未形成有效模块候选");
5064
+ const { summary, sourceDigest, previewRevision } = preview;
5188
5065
  if (request.params.preview) {
5189
- const summary = {
5190
- sourceName: converted.sourceName,
5191
- kind: converted.kind,
5192
- targetId,
5193
- targetName: converted.name,
5194
- exists: version !== null,
5195
- files: files.map((file) => ({
5196
- path: file.path,
5197
- bytes: decodeAssetFile(file).length
5198
- })),
5199
- configCount: prepared.spec.promptConfigs?.length ?? 0,
5200
- warnings: []
5201
- };
5202
5066
  writeBridgeJson(res, 200, {
5203
5067
  ok: true,
5204
5068
  value: {
5205
5069
  preview: true,
5206
5070
  state: "ready",
5207
- name: converted.name,
5071
+ name: summary.targetName,
5208
5072
  summary,
5209
- sourceDigest: converted.sourceDigest,
5073
+ sourceDigest,
5210
5074
  previewRevision,
5211
- ...converted.report === void 0 ? {} : { report: converted.report }
5075
+ ...prepared.report === void 0 ? {} : { report: prepared.report }
5212
5076
  }
5213
5077
  });
5214
5078
  return;
5215
5079
  }
5216
- if (request.params.expectedPreviewRevision !== previewRevision || request.params.expectedSourceDigest !== converted.sourceDigest) {
5217
- writeBridgeJson(res, 409, {
5218
- ok: false,
5219
- code: "characters-preview-stale",
5220
- message: "预览已过期:来源、选择或目标版本已变化,请重新预览"
5221
- });
5222
- return;
5223
- }
5224
- if (version !== null && request.params.overwrite !== true) throw new Error("角色已存在,请明确选择更新或另存");
5225
- const result = importCharacterCard(root, files, options);
5226
- if (!result.ok) throw new Error(result.message);
5080
+ const result = await installModulePackage(root, files, request.params, { preserveAssets: true });
5081
+ invalidateDescriptor();
5082
+ if (!await finishModuleChange(res, () => afterModulePackageImport?.(result.id))) return;
5227
5083
  writeBridgeJson(res, 200, {
5228
5084
  ok: true,
5229
5085
  value: {
5230
5086
  id: result.id,
5231
- name: result.name
5087
+ name: summary.targetName
5232
5088
  }
5233
5089
  });
5234
5090
  } catch (error) {
5235
- writeBridgeJson(res, 400, {
5091
+ const code = typeof error.code === "string" ? String(error.code) : "characters-rejected";
5092
+ writeBridgeJson(res, code.includes("stale") ? 409 : 400, {
5236
5093
  ok: false,
5237
- code: "characters-rejected",
5094
+ code,
5238
5095
  message: error instanceof Error ? error.message : String(error)
5239
5096
  });
5240
5097
  }
@@ -5254,34 +5111,14 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5254
5111
  }),
5255
5112
  sctx.webServer.register({
5256
5113
  kind: "exact",
5257
- path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.charactersList,
5114
+ path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.moduleMerge,
5258
5115
  handler: async (req, res) => {
5259
5116
  if (!guard(req, res)) return;
5260
5117
  const dir = editDir(req) ?? "";
5261
5118
  if (dir.length === 0) {
5262
5119
  writeBridgeJson(res, 400, {
5263
5120
  ok: false,
5264
- code: "preset-dir-unavailable",
5265
- message: "模块目录未配置"
5266
- });
5267
- return;
5268
- }
5269
- writeBridgeJson(res, 200, {
5270
- ok: true,
5271
- value: { characters: listCharacterCards(dirname(dir), basename(dir)) }
5272
- });
5273
- }
5274
- }),
5275
- sctx.webServer.register({
5276
- kind: "exact",
5277
- path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.charactersDelete,
5278
- handler: async (req, res) => {
5279
- if (!guard(req, res)) return;
5280
- const dir = editDir(req) ?? "";
5281
- if (dir.length === 0) {
5282
- writeBridgeJson(res, 400, {
5283
- ok: false,
5284
- code: "preset-dir-unavailable",
5121
+ code: "module-dir-unavailable",
5285
5122
  message: "模块目录未配置"
5286
5123
  });
5287
5124
  return;
@@ -5299,51 +5136,8 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5299
5136
  });
5300
5137
  return;
5301
5138
  }
5302
- if (!guardPresetWrite(dir, res)) return;
5303
- const result = deleteCharacterCard(dirname(dir), id);
5304
- if (!result.ok) {
5305
- writeBridgeJson(res, 400, {
5306
- ok: false,
5307
- code: "characters-rejected",
5308
- message: result.message
5309
- });
5310
- return;
5311
- }
5312
- writeBridgeJson(res, 200, {
5313
- ok: true,
5314
- value: { id }
5315
- });
5316
- }
5317
- }),
5318
- sctx.webServer.register({
5319
- kind: "exact",
5320
- path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.charactersApply,
5321
- handler: async (req, res) => {
5322
- if (!guard(req, res)) return;
5323
- const dir = editDir(req) ?? "";
5324
- if (dir.length === 0) {
5325
- writeBridgeJson(res, 400, {
5326
- ok: false,
5327
- code: "preset-dir-unavailable",
5328
- message: "模块目录未配置"
5329
- });
5330
- return;
5331
- }
5332
- const parsedBody = await readBridgeBodyForHandler(req, res);
5333
- if (parsedBody === void 0) return;
5334
- const { body } = parsedBody;
5335
- const record = body ?? {};
5336
- const id = typeof record.id === "string" ? record.id.trim() : "";
5337
- if (id.length === 0) {
5338
- writeBridgeJson(res, 400, {
5339
- ok: false,
5340
- code: "characters-rejected",
5341
- message: "缺少角色卡 id"
5342
- });
5343
- return;
5344
- }
5345
- if (!guardPresetWrite(dir, res)) return;
5346
- const result = applyCharacterToPreset(dirname(dir), basename(dir), id);
5139
+ if (!guardModuleWrite(dir, res)) return;
5140
+ const result = mergeModuleIntoModule(dirname(dir), basename(dir), id);
5347
5141
  if (!result.ok) {
5348
5142
  writeBridgeJson(res, 400, {
5349
5143
  ok: false,
@@ -5364,14 +5158,14 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5364
5158
  }),
5365
5159
  sctx.webServer.register({
5366
5160
  kind: "exact",
5367
- path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.charactersRemove,
5161
+ path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.moduleUnmerge,
5368
5162
  handler: async (req, res) => {
5369
5163
  if (!guard(req, res)) return;
5370
5164
  const dir = editDir(req) ?? "";
5371
5165
  if (dir.length === 0) {
5372
5166
  writeBridgeJson(res, 400, {
5373
5167
  ok: false,
5374
- code: "preset-dir-unavailable",
5168
+ code: "module-dir-unavailable",
5375
5169
  message: "模块目录未配置"
5376
5170
  });
5377
5171
  return;
@@ -5389,8 +5183,8 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5389
5183
  });
5390
5184
  return;
5391
5185
  }
5392
- if (!guardPresetWrite(dir, res)) return;
5393
- const result = removeCharacterFromPreset(dirname(dir), basename(dir), id);
5186
+ if (!guardModuleWrite(dir, res)) return;
5187
+ const result = removeMergedModule(dirname(dir), basename(dir), id);
5394
5188
  if (!result.ok) {
5395
5189
  writeBridgeJson(res, 400, {
5396
5190
  ok: false,
@@ -5418,7 +5212,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5418
5212
  if (dir.length === 0) {
5419
5213
  writeBridgeJson(res, 400, {
5420
5214
  ok: false,
5421
- code: "preset-dir-unavailable",
5215
+ code: "module-dir-unavailable",
5422
5216
  message: "模块目录未配置"
5423
5217
  });
5424
5218
  return;
@@ -5427,7 +5221,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5427
5221
  if (parsedBody === void 0) return;
5428
5222
  const { body } = parsedBody;
5429
5223
  const record = body ?? {};
5430
- if (!guardPresetIdentity(req, record, dir, res)) return;
5224
+ if (!guardModuleIdentity(req, record, dir, res)) return;
5431
5225
  if (record.policy === void 0) {
5432
5226
  try {
5433
5227
  writeBridgeJson(res, 200, {
@@ -5443,7 +5237,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5443
5237
  }
5444
5238
  return;
5445
5239
  }
5446
- if (!guardPresetWrite(dir, res)) return;
5240
+ if (!guardModuleWrite(dir, res)) return;
5447
5241
  try {
5448
5242
  const policy = record.policy;
5449
5243
  const core = await import(pathToFileURL(join(packageEngineDir(), "subagent-tool-policy-core.mjs")).href);
@@ -5458,12 +5252,12 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5458
5252
  });
5459
5253
  return;
5460
5254
  }
5461
- if (!guardPresetIdentity(req, record, dir, res)) return;
5462
- withPresetDoc(dir, (doc) => {
5255
+ if (!guardModuleIdentity(req, record, dir, res)) return;
5256
+ withModuleDefinition(dir, (doc) => {
5463
5257
  if (isEmpty) doc.deleteIn(["subagentToolPolicy"]);
5464
5258
  else {
5465
5259
  doc.setIn(["subagentToolPolicy"], policy);
5466
- appendPresetModules(doc, ["subagent-tool-policy"]);
5260
+ appendModuleCapabilities(doc, ["subagent-tool-policy"]);
5467
5261
  }
5468
5262
  });
5469
5263
  if (!await runOverridesChange(res, basename(dir))) return;
@@ -5492,7 +5286,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5492
5286
  if (dir.length === 0) {
5493
5287
  writeBridgeJson(res, 400, {
5494
5288
  ok: false,
5495
- code: "preset-dir-unavailable",
5289
+ code: "module-dir-unavailable",
5496
5290
  message: "模块目录未配置"
5497
5291
  });
5498
5292
  return;
@@ -5538,14 +5332,14 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5538
5332
  }),
5539
5333
  sctx.webServer.register({
5540
5334
  kind: "exact",
5541
- path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.engineCapability,
5335
+ path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.moduleCapability,
5542
5336
  handler: async (req, res) => {
5543
5337
  if (!guard(req, res)) return;
5544
5338
  const dir = editDir(req) ?? "";
5545
5339
  if (dir.length === 0) {
5546
5340
  writeBridgeJson(res, 400, {
5547
5341
  ok: false,
5548
- code: "preset-dir-unavailable",
5342
+ code: "module-dir-unavailable",
5549
5343
  message: "模块目录未配置"
5550
5344
  });
5551
5345
  return;
@@ -5553,7 +5347,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5553
5347
  const parsedBody = await readBridgeBodyForHandler(req, res);
5554
5348
  if (parsedBody === void 0) return;
5555
5349
  const body = asRecord(parsedBody.body);
5556
- if (!guardPresetIdentity(req, body, dir, res)) return;
5350
+ if (!guardModuleIdentity(req, body, dir, res)) return;
5557
5351
  const action = body.action;
5558
5352
  const id = action === "create" || action === "remove" ? body.capabilityId : action === "create-recipe" ? body.recipeId : void 0;
5559
5353
  if (action !== "create" && action !== "remove" && action !== "create-recipe" || typeof id !== "string" || id.trim().length === 0 || id.length > 128) {
@@ -5564,30 +5358,31 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5564
5358
  });
5565
5359
  return;
5566
5360
  }
5567
- if (!guardPresetWrite(dir, res)) return;
5361
+ if (!guardModuleWrite(dir, res)) return;
5568
5362
  const run = capabilityQueue.then(async () => {
5569
- if (!guardPresetIdentity(req, body, dir, res)) return;
5570
- const file = join(dir, MODULE_DEFINITION_FILE);
5571
- let original;
5363
+ if (!guardModuleIdentity(req, body, dir, res)) return;
5572
5364
  try {
5573
- original = readFileSync(file, "utf8");
5574
- const result = action === "remove" ? removeEngineCapabilityFromPreset(dir, id.trim()) : createEngineCapabilityInPreset(dir, action === "create" ? {
5365
+ const result = action === "remove" ? removeModuleCapability(dir, id.trim()) : createModuleCapability(dir, action === "create" ? {
5575
5366
  action: "create",
5576
5367
  capabilityId: id.trim()
5577
5368
  } : {
5578
5369
  action: "create-recipe",
5579
5370
  recipeId: id.trim()
5580
5371
  });
5581
- if (result.changed && !await finishPresetChange(res, () => afterCapabilityChange?.(basename(dir)))) return;
5372
+ if (result.changed && !await finishModuleChange(res, () => afterCapabilityChange?.(basename(dir)))) return;
5582
5373
  writeBridgeJson(res, 200, {
5583
5374
  ok: true,
5584
5375
  value: result
5585
5376
  });
5586
5377
  } catch (error) {
5587
- if (original !== void 0) try {
5588
- atomicWriteTextFile(file, original);
5589
- invalidateModuleSpec(dir);
5590
- } catch {}
5378
+ if (error instanceof ModuleRulesError && error.persisted) {
5379
+ writeBridgeJson(res, 500, {
5380
+ ok: false,
5381
+ code: MODULE_ACTIVATION_FAILED,
5382
+ message: `能力变更已保存,但切片未发布;请重试刷新:${error.message}`
5383
+ });
5384
+ return;
5385
+ }
5591
5386
  writeBridgeJson(res, 409, {
5592
5387
  ok: false,
5593
5388
  code: "engine-capability-rejected",
@@ -5802,6 +5597,61 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
5802
5597
  return { invalidateDescriptor: () => invalidateCachedDescriptor() };
5803
5598
  }
5804
5599
  //#endregion
5600
+ //#region src/runtime/character-tools.ts
5601
+ function registerCharacterTools(ctx, host) {
5602
+ const fiber = ctx.inject(["tools"], (toolsCtx) => toolsCtx.tools.register(defineTool({
5603
+ name: "character_import",
5604
+ description: "把一张 SillyTavern 角色卡或自包含原生角色片段直接导入为普通模块,接收 JSON / YAML 文本。PNG 从模块页导入。默认同名另存,不自动启用;导入后通过模块管理选择启用,不再使用角色卡库或应用步骤。",
5605
+ parameters: {
5606
+ name: {
5607
+ type: "string",
5608
+ required: true,
5609
+ description: "来源文件名(不含扩展名),作为普通模块 id 的基础。"
5610
+ },
5611
+ content: {
5612
+ type: "string",
5613
+ required: true,
5614
+ description: "角色卡 JSON / YAML 文本;原生片段含 id/name/rules,规则注入动作使用内嵌 text/texts,不支持外部文件。"
5615
+ }
5616
+ },
5617
+ output: {
5618
+ schema: {
5619
+ type: "object",
5620
+ additionalProperties: false,
5621
+ properties: {
5622
+ id: {
5623
+ type: "string",
5624
+ required: true
5625
+ },
5626
+ name: {
5627
+ type: "string",
5628
+ required: true
5629
+ }
5630
+ }
5631
+ },
5632
+ render: (_args, value) => [{
5633
+ type: "text",
5634
+ text: `已导入普通模块:${value.name}(id=${value.id})。在模块管理中启用后参与装配。`
5635
+ }]
5636
+ },
5637
+ execute: async (args, exec) => {
5638
+ const target = host.target(exec);
5639
+ const result = await importCharacterCard(target.root, [{
5640
+ path: `${args.name}.json`,
5641
+ content: args.content
5642
+ }]);
5643
+ if (!result.ok) throw new Error(result.message);
5644
+ return {
5645
+ id: result.id,
5646
+ name: result.name
5647
+ };
5648
+ }
5649
+ })));
5650
+ return () => {
5651
+ fiber.dispose();
5652
+ };
5653
+ }
5654
+ //#endregion
5805
5655
  //#region src/host/module-tool-target.ts
5806
5656
  /**
5807
5657
  * 工具目标来自执行 Agent 的**运行时配装记录**:这个 Agent 实际装上了哪几层提示词。
@@ -5824,7 +5674,7 @@ function resolveModuleToolTarget(exec, root, moduleIdsOf) {
5824
5674
  };
5825
5675
  }
5826
5676
  /** 定义已经保存时,不把重建失败伪装成整个写入未发生。 */
5827
- async function rebuildSavedPreset(host, id) {
5677
+ async function refreshModuleAfterSave(host, id) {
5828
5678
  try {
5829
5679
  await host.rebuild(id);
5830
5680
  } catch (error) {
@@ -5832,243 +5682,35 @@ async function rebuildSavedPreset(host, id) {
5832
5682
  }
5833
5683
  }
5834
5684
  //#endregion
5835
- //#region src/runtime/character-tools.ts
5836
- const text$2 = (text) => [{
5685
+ //#region src/runtime/world-book-tools.ts
5686
+ const text$1 = (text) => [{
5837
5687
  type: "text",
5838
5688
  text
5839
5689
  }];
5840
- /** 注册角色卡库模型工具;返回 disposer,随 character-tools 模块生命周期清理。 */
5841
- function registerCharacterTools(ctx, host) {
5690
+ /** 注册世界书条目级模型工具;返回 disposer,随 world-book-tools 模块生命周期清理。 */
5691
+ function registerWorldBookTools(ctx, host) {
5842
5692
  const fiber = ctx.inject(["tools"], (toolsCtx) => {
5843
5693
  const disposers = [];
5694
+ const writeNote = (target, note) => {
5695
+ appendModuleMemory(target.dir, note);
5696
+ };
5844
5697
  disposers.push(toolsCtx.tools.register(defineTool({
5845
- name: "character_list",
5846
- description: "列出角色卡库:每张卡(id / 名称 / 描述 / 是否已导入当前模块)。导入角色卡、应用到当前模块或移除前先调用本工具获取 id。",
5698
+ name: "world_book_read_memory",
5699
+ description: "读取当前模块的持久记忆 memory.md。每次调用读取最新文件;没有记忆时返回空文本。记忆不会自动注入,读取失败会报错。",
5847
5700
  parameters: {},
5848
5701
  output: {
5849
5702
  schema: {
5850
5703
  type: "object",
5851
5704
  additionalProperties: false,
5852
- properties: { characters: {
5853
- type: "array",
5854
- required: true,
5855
- items: {
5856
- type: "object",
5857
- additionalProperties: false,
5858
- properties: {
5859
- id: {
5860
- type: "string",
5861
- required: true
5862
- },
5863
- name: {
5864
- type: "string",
5865
- required: true
5866
- },
5867
- description: { type: "string" },
5868
- hasAvatar: {
5869
- type: "boolean",
5870
- required: true
5871
- },
5872
- imported: {
5873
- type: "boolean",
5874
- required: true
5875
- }
5876
- }
5877
- }
5878
- } }
5879
- },
5880
- render: (_args, value) => text$2(JSON.stringify(value.characters))
5881
- },
5882
- execute: async (_args, exec) => {
5883
- const target = host.target(exec);
5884
- return { characters: listCharacterCards(target.root, target.id) };
5885
- }
5886
- })));
5887
- disposers.push(toolsCtx.tools.register(defineTool({
5888
- name: "character_import",
5889
- description: "导入一张 SillyTavern 角色卡或自包含原生角色片段到角色卡库:接收 JSON / YAML 文本内容(可先读取文件)。PNG 角色卡请让用户从 UI 角色管理页导入。导入后需调用 character_apply 应用到当前模块。",
5890
- parameters: {
5891
- name: {
5892
- type: "string",
5893
- required: true,
5894
- description: "角色卡文件名(不含 .json 扩展名),将作为模块/角色卡 id 基础。"
5895
- },
5896
- content: {
5897
- type: "string",
5898
- required: true,
5899
- description: "角色卡 JSON / YAML 文本:SillyTavern chara_card_v2/v3,或含 id/name/promptConfigs 的原生片段。原生片段只支持内嵌 text/texts 和控制配置,不支持外部文件。"
5900
- }
5901
- },
5902
- output: {
5903
- schema: {
5904
- type: "object",
5905
- additionalProperties: false,
5906
- properties: {
5907
- id: {
5908
- type: "string",
5909
- required: true
5910
- },
5911
- name: {
5912
- type: "string",
5913
- required: true
5914
- }
5915
- }
5916
- },
5917
- render: (_args, value) => text$2(`角色卡已入库:${value.name}(id=${value.id})。调用 character_apply 可应用到当前模块。`)
5918
- },
5919
- execute: async (args, exec) => {
5920
- const target = host.target(exec);
5921
- const result = importCharacterCard(target.root, [{
5922
- path: `${args.name}.json`,
5923
- content: args.content
5924
- }]);
5925
- if (!result.ok) throw new Error(result.message);
5926
- return {
5927
- id: result.id,
5928
- name: result.name
5929
- };
5930
- }
5931
- })));
5932
- disposers.push(toolsCtx.tools.register(defineTool({
5933
- name: "character_apply",
5934
- description: "把角色卡库中一张角色卡的参数(角色设定 / 系统提示 / 开场白 / 世界书 / 提示词配置)合并进当前会话绑定的模块(promptConfigs 带 chara-<id>- 前缀防冲突,params 合并,meta.importedCharacters 记录),并立即重建生成目录。重复应用幂等。",
5935
- parameters: { id: {
5936
- type: "string",
5937
- required: true,
5938
- description: "角色卡 id(character_list 返回)。"
5939
- } },
5940
- output: {
5941
- schema: {
5942
- type: "object",
5943
- additionalProperties: false,
5944
- properties: {
5945
- id: {
5946
- type: "string",
5947
- required: true
5948
- },
5949
- count: {
5950
- type: "integer",
5951
- required: true
5952
- }
5953
- }
5954
- },
5955
- render: (_args, value) => text$2(`已导入到当前模块(${value.count} 条配置),生成目录已重建。`)
5956
- },
5957
- execute: async (args, exec) => {
5958
- const target = host.target(exec);
5959
- const result = applyCharacterToPreset(target.root, target.id, args.id);
5960
- if (!result.ok) throw new Error(result.message);
5961
- await rebuildSavedPreset(host, target.id);
5962
- return {
5963
- id: args.id,
5964
- count: result.count
5965
- };
5966
- }
5967
- })));
5968
- disposers.push(toolsCtx.tools.register(defineTool({
5969
- name: "character_remove",
5970
- description: "从当前会话绑定的模块移除一张已导入角色卡的参数(删 chara-<id>- 前缀配置、该卡声明的 params 键、meta.importedCharacters 除名),并立即重建生成目录。角色卡库条目不受影响。",
5971
- parameters: { id: {
5972
- type: "string",
5973
- required: true,
5974
- description: "角色卡 id(character_list 返回,imported=true 的卡)。"
5975
- } },
5976
- output: {
5977
- schema: {
5978
- type: "object",
5979
- additionalProperties: false,
5980
- properties: {
5981
- id: {
5982
- type: "string",
5983
- required: true
5984
- },
5985
- count: {
5986
- type: "integer",
5987
- required: true
5988
- }
5989
- }
5990
- },
5991
- render: (_args, value) => text$2(`已从当前模块移除(${value.count} 条配置),生成目录已重建。`)
5992
- },
5993
- execute: async (args, exec) => {
5994
- const target = host.target(exec);
5995
- const result = removeCharacterFromPreset(target.root, target.id, args.id);
5996
- if (!result.ok) throw new Error(result.message);
5997
- await rebuildSavedPreset(host, target.id);
5998
- return {
5999
- id: args.id,
6000
- count: result.count
6001
- };
6002
- }
6003
- })));
6004
- disposers.push(toolsCtx.tools.register(defineTool({
6005
- name: "character_delete",
6006
- description: "从角色卡库删除一张角色卡(含其转换参数与头像)。已导入当前模块的参数不受影响(如需清理请先调用 character_remove)。",
6007
- parameters: { id: {
6008
- type: "string",
6009
- required: true,
6010
- description: "角色卡 id(character_list 返回)。"
6011
- } },
6012
- output: {
6013
- schema: {
6014
- type: "object",
6015
- additionalProperties: false,
6016
- properties: { id: {
5705
+ properties: { memory: {
6017
5706
  type: "string",
6018
5707
  required: true
6019
5708
  } }
6020
5709
  },
6021
- render: (_args, value) => text$2(`角色卡 ${value.id} 已从库中删除。`)
5710
+ render: (_args, value) => text$1(value.memory)
6022
5711
  },
6023
- execute: async (args, exec) => {
6024
- const result = deleteCharacterCard(host.target(exec).root, args.id);
6025
- if (!result.ok) throw new Error(result.message);
6026
- return { id: args.id };
6027
- }
5712
+ execute: async (_args, exec) => ({ memory: readModuleMemory(host.target(exec).dir) })
6028
5713
  })));
6029
- return () => {
6030
- for (const dispose of disposers) dispose();
6031
- };
6032
- });
6033
- return () => {
6034
- fiber.dispose();
6035
- };
6036
- }
6037
- //#endregion
6038
- //#region src/runtime/world-book-tools.ts
6039
- const text$1 = (text) => [{
6040
- type: "text",
6041
- text
6042
- }];
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;
6048
- let best;
6049
- try {
6050
- for (const dir of readdirSync(root, { withFileTypes: true })) {
6051
- if (!dir.isDirectory() || dir.name.startsWith(".")) continue;
6052
- if ((entryId.startsWith(`module-${dir.name}-`) || entryId.startsWith(`chara-${dir.name}-`)) && (best === void 0 || dir.name.length > best.length)) best = dir.name;
6053
- }
6054
- } catch {}
6055
- return best;
6056
- }
6057
- /** 注册世界书条目级模型工具;返回 disposer,随 world-book-tools 模块生命周期清理。 */
6058
- function registerWorldBookTools(ctx, host) {
6059
- const fiber = ctx.inject(["tools"], (toolsCtx) => {
6060
- const disposers = [];
6061
- /** note 归属写入:角色卡条目 → 卡记忆;其他 → 模块记忆。 */
6062
- const writeNote = (target, entryId, note) => {
6063
- if (note === void 0 || note.trim().length === 0) return;
6064
- const cardId = entryId !== void 0 ? sourceCardId(target.root, entryId) : void 0;
6065
- if (cardId !== void 0) {
6066
- appendMemoryFile(join(target.root, ".characters", cardId, "memory.md"), note, "# 角色记忆");
6067
- try {
6068
- syncImportedCharacterMemory(target.root, target.id, cardId);
6069
- } catch {}
6070
- } else appendMemoryFile(join(target.dir, "memory.md"), note, "# 本地记忆");
6071
- };
6072
5714
  disposers.push(toolsCtx.tools.register(defineTool({
6073
5715
  name: "world_book_list",
6074
5716
  description: "列出当前模块的世界书条目(world-book 策略配置:id / 名称 / 关键字 / 常驻 / 启用)。世界书 = 上下文条目:无 keys 的全局条目每次注入,有 keys 条目命中聊天内容才注入。增删改前先调用本工具获取 id。",
@@ -6120,7 +5762,7 @@ function registerWorldBookTools(ctx, host) {
6120
5762
  })));
6121
5763
  disposers.push(toolsCtx.tools.register(defineTool({
6122
5764
  name: "world_book_upsert",
6123
- description: "新增或更新当前模块的一条世界书条目(world-book 策略配置):按 id 更新(不存在则新增,id 自动生成 lore-<n>)。constant=true 常驻注入;否则命中 keys(或 secondaryKeys)任一关键字注入;无 keys 条目按全局每次注入。note 可选:写入来源角色卡的持久记忆(memory.md,条目 id 带 chara-<卡>- 前缀时)或模块记忆——持久记忆跨会话跟随角色卡(与 session_var 会话变量的临时状态不同,适合长期关系记录)。写盘后立即重建生成目录。",
5765
+ description: "新增或更新当前模块的一条世界书条目(world-book 策略配置):按 id 更新(不存在则新增,id 自动生成 lore-<n>)。constant=true 常驻注入;否则命中 keys(或 secondaryKeys)任一关键字注入;无 keys 条目按全局每次注入。note 可选:写入当前模块的持久记忆 memory.md,使用 world_book_read_memory 按需读取。写盘后立即重新装配。",
6124
5766
  parameters: {
6125
5767
  id: {
6126
5768
  type: "string",
@@ -6160,7 +5802,7 @@ function registerWorldBookTools(ctx, host) {
6160
5802
  },
6161
5803
  note: {
6162
5804
  type: "string",
6163
- description: "可选:操作笔记,写入来源角色卡持久记忆(memory.md,跨会话跟随角色卡)或模块记忆。"
5805
+ description: "可选:操作笔记,写入当前模块的 memory.md,不自动注入。"
6164
5806
  }
6165
5807
  },
6166
5808
  output: {
@@ -6197,8 +5839,8 @@ function registerWorldBookTools(ctx, host) {
6197
5839
  ...entry,
6198
5840
  id: targetId
6199
5841
  });
6200
- writeNote(target, targetId, typeof args.note === "string" ? args.note : "");
6201
- await rebuildSavedPreset(host, target.id);
5842
+ writeNote(target, typeof args.note === "string" ? args.note : "");
5843
+ await refreshModuleAfterSave(host, target.id);
6202
5844
  return {
6203
5845
  id: targetId,
6204
5846
  count
@@ -6207,7 +5849,7 @@ function registerWorldBookTools(ctx, host) {
6207
5849
  })));
6208
5850
  disposers.push(toolsCtx.tools.register(defineTool({
6209
5851
  name: "world_book_delete",
6210
- description: "删除当前模块的一条世界书条目(world_book_list 获取 id)。note 可选:写入来源角色卡持久记忆(memory.md,跨会话跟随角色卡)或模块记忆。删除后立即重建生成目录。",
5852
+ description: "删除当前模块的一条世界书条目(world_book_list 获取 id)。note 可选:写入当前模块的 memory.md,使用 world_book_read_memory 按需读取。删除后立即重新装配。",
6211
5853
  parameters: {
6212
5854
  id: {
6213
5855
  type: "string",
@@ -6216,7 +5858,7 @@ function registerWorldBookTools(ctx, host) {
6216
5858
  },
6217
5859
  note: {
6218
5860
  type: "string",
6219
- description: "可选:操作笔记,写入来源角色卡持久记忆(memory.md)或模块记忆。"
5861
+ description: "可选:操作笔记,写入当前模块的 memory.md,不自动注入。"
6220
5862
  }
6221
5863
  },
6222
5864
  output: {
@@ -6239,8 +5881,8 @@ function registerWorldBookTools(ctx, host) {
6239
5881
  execute: async (args, exec) => {
6240
5882
  const target = host.target(exec);
6241
5883
  const keptCount = deleteWorldBookEntry(target.dir, args.id);
6242
- writeNote(target, args.id, typeof args.note === "string" ? args.note : "");
6243
- await rebuildSavedPreset(host, target.id);
5884
+ writeNote(target, typeof args.note === "string" ? args.note : "");
5885
+ await refreshModuleAfterSave(host, target.id);
6244
5886
  return {
6245
5887
  id: args.id,
6246
5888
  count: keptCount
@@ -6372,92 +6014,22 @@ function registerSessionVarTools(ctx) {
6372
6014
  //#endregion
6373
6015
  //#region src/runtime/tui.ts
6374
6016
  /** dsh-tui 全局开关:键名与 settings 路径一致(settings mutate)。 */
6375
- const TUI_GLOBAL_SWITCHES = [["writePreset", "模块运行总开关"]];
6376
- /** dsh-tui 参数开关:写激活模块 preset.yml(settings 不再承载引擎参数)。 */
6017
+ const TUI_GLOBAL_SWITCHES = [["modulesEnabled", "模块运行总开关"]];
6018
+ /** dsh-tui 参数开关:写激活模块 module.yml(settings 不再承载引擎参数)。 */
6377
6019
  const TUI_PARAM_SWITCHES = Object.entries(ENGINE_PARAM_DEFINITIONS).filter(([, definition]) => definition.kind === "boolean").map(([key]) => [key, key]);
6378
- /** 参数显示行(从激活模块 preset.yml params 读)。 */
6379
- const TUI_PARAM_TEXT_LINES = [
6380
- [
6381
- "modelProvider",
6382
- "modelProvider",
6383
- "(空 = 不设置)"
6384
- ],
6385
- [
6386
- "modelName",
6387
- "modelName",
6388
- "(空 = 不设置)"
6389
- ],
6390
- [
6391
- "subagentModelProvider",
6392
- "subagentModelProvider",
6393
- "(空 = 不设置)"
6394
- ],
6395
- [
6396
- "subagentModelName",
6397
- "subagentModelName",
6398
- "(空 = 不设置)"
6399
- ],
6400
- [
6401
- "modelReasoningEffort",
6402
- "modelReasoningEffort",
6403
- "(空 = 不设置)"
6404
- ],
6405
- [
6406
- "modelTemperature",
6407
- "modelTemperature",
6408
- "(空 = 不设置)"
6409
- ],
6410
- [
6411
- "modelMaxTokens",
6412
- "modelMaxTokens",
6413
- "(空 = 不设置)"
6414
- ],
6415
- [
6416
- "subagentReasoningEffort",
6417
- "subagentReasoningEffort",
6418
- "(空 = 不设置)"
6419
- ],
6420
- [
6421
- "subagentTemperature",
6422
- "subagentTemperature",
6423
- "(空 = 不设置)"
6424
- ],
6425
- [
6426
- "subagentMaxTokens",
6427
- "subagentMaxTokens",
6428
- "(空 = 不设置)"
6429
- ]
6430
- ];
6431
- /** 把布尔开关渲染成 dsh-tui 命令输出。 */
6432
- /** 实际生效配置:生成目录优先(引擎加载源),settings 覆盖层作回退。 */
6433
- function resolvePromptConfigs(moduleDir, fallback) {
6434
- if (moduleDir === void 0 || moduleDir.length === 0) return fallback;
6435
- try {
6436
- const actual = listPromptConfigSpecs(join(moduleDir, MODULE_CONFIGS_DIR));
6437
- return actual.length > 0 ? actual : fallback;
6438
- } catch {
6439
- return fallback;
6440
- }
6441
- }
6442
6020
  /** 激活模块参数(status 显示与参数开关来源;settings 不再承载引擎参数)。 */
6443
- function readPresetParams(moduleDir) {
6021
+ function readModuleParams(moduleDir) {
6444
6022
  if (moduleDir === void 0 || moduleDir.length === 0) return {};
6445
6023
  try {
6446
6024
  const spec = loadModuleSpec(moduleDir);
6447
- const params = resolvePresetParams(spec, {});
6448
- if (Array.isArray(spec.promptConfigs)) params.promptConfigs = spec.promptConfigs;
6449
- return params;
6025
+ return resolveModuleParams(spec, {});
6450
6026
  } catch {
6451
6027
  return {};
6452
6028
  }
6453
6029
  }
6454
- function renderTuiStatus(source, params, promptConfigs) {
6030
+ function renderTuiStatus(source, params, rules) {
6455
6031
  const onOff = (value) => value ? "开" : "关";
6456
6032
  const paramBoolean = (key) => (params[key] ?? ENGINE_PARAM_DEFINITIONS[key]?.defaultValue) === true;
6457
- const paramText = (key) => {
6458
- const value = params[key];
6459
- return typeof value === "string" && value.length > 0 ? value : "";
6460
- };
6461
6033
  const lines = [
6462
6034
  "提示词工具开关",
6463
6035
  ...TUI_GLOBAL_SWITCHES.map(([key, label]) => {
@@ -6467,16 +6039,11 @@ function renderTuiStatus(source, params, promptConfigs) {
6467
6039
  ...TUI_PARAM_SWITCHES.map(([key, label]) => {
6468
6040
  return `${key.padEnd(22)}${onOff(paramBoolean(key))} ${label}(模块)`;
6469
6041
  }),
6470
- "模型请求参数:",
6471
- ...TUI_PARAM_TEXT_LINES.map(([key, label, emptyText]) => {
6472
- const text = paramText(key);
6473
- return ` ${label.padEnd(26)}${text.length > 0 ? text : emptyText}`;
6474
- }),
6475
6042
  ` modelsAvailable ${source.modelsAvailable ? "是" : "否(未检测到模型服务商)"}`,
6476
6043
  ` activeSkillsDirs ${source.activeSkillsDirs.length > 0 ? source.activeSkillsDirs.join(" → ") : "(未解析到技能目录)"}`,
6477
- "提示词配置:"
6044
+ "行为规则(条件 → 动作):"
6478
6045
  ];
6479
- for (const config of promptConfigs) lines.push(`${("config " + config.id).padEnd(22)}${onOff(config.enabled !== false)} layer=${config.layer ?? "pre-step"} strategy=${config.strategy ?? "static"}`);
6046
+ for (const rule of rules) lines.push(`${("config " + rule.id).padEnd(22)}${onOff(rule.enabled !== false)} ${rule.then.map((action) => action.kind).join(" → ")}`);
6480
6047
  lines.push("技能开关:");
6481
6048
  for (const skill of source.skillCatalog) {
6482
6049
  const value = skill.modelInvocable || skill.userInvocable;
@@ -6485,49 +6052,10 @@ function renderTuiStatus(source, params, promptConfigs) {
6485
6052
  }
6486
6053
  return lines.join("\n");
6487
6054
  }
6488
- /** 渲染单条提示词配置详情(params 以 JSON 摘要输出)。 */
6489
- function renderConfigDetail(source, id, promptConfigs) {
6490
- const config = promptConfigs.find((item) => item.id === id);
6491
- if (config === void 0) return `未找到提示词配置 ${id}`;
6492
- const lines = [`提示词配置 ${config.id}`, ""];
6493
- const fields = [
6494
- ["name", config.name],
6495
- ["enabled", config.enabled !== false],
6496
- ["layer", config.layer],
6497
- ["strategy", config.strategy],
6498
- ["position", config.position],
6499
- ["dedupe", config.dedupe],
6500
- ["promotion", config.promotion],
6501
- ["audience", config.audience ?? void 0],
6502
- ["modelScope", config.modelScope],
6503
- ["configKind", config.configKind],
6504
- ["order", config.order],
6505
- ["role", config.role],
6506
- ["mergeMode", config.mergeMode],
6507
- ["sourceKind", config.sourceKind],
6508
- ["form", config.form],
6509
- ["fill", config.fill],
6510
- ["templateFile", config.templateFile],
6511
- ["variables", config.variables],
6512
- ["params", config.params]
6513
- ];
6514
- for (const [key, value] of fields) {
6515
- if (value === void 0 || value === "" || Array.isArray(value) && value.length === 0) continue;
6516
- if (key === "variables" || key === "params") {
6517
- lines.push(`${key.padEnd(14)}${JSON.stringify(value)}`);
6518
- continue;
6519
- }
6520
- lines.push(`${key.padEnd(14)}${String(value)}`);
6521
- }
6522
- if (typeof config.text === "string" && config.text.length > 0) {
6523
- lines.push("text:");
6524
- lines.push(config.text);
6525
- }
6526
- if (Array.isArray(config.texts) && config.texts.length > 0) {
6527
- lines.push("texts:");
6528
- for (const item of config.texts) lines.push(` - ${item}`);
6529
- }
6530
- return lines.join("\n");
6055
+ /** 详情呈现唯一声明,不把动作投影误当可独立启停的配置。 */
6056
+ function renderConfigDetail(id, rules) {
6057
+ const rule = rules.find((item) => item.id === id);
6058
+ return rule === void 0 ? `未找到规则 ${id}` : JSON.stringify(rule, null, 2);
6531
6059
  }
6532
6060
  /** 解析 on/off/toggle 三种输入。 */
6533
6061
  function parseTuiBoolean(token, current) {
@@ -6559,7 +6087,7 @@ function parseIdentifierAndAction(tokens, exists) {
6559
6087
  return { id: tokens.join(" ") };
6560
6088
  }
6561
6089
  /** 通过 DSH 命令注册表暴露 /prompt-tool,Web 与 dsh-tui 都能执行。 */
6562
- function registerTuiCommand(ctx, ns, getSource, getModelsState, getModelCatalog, getPresetConfigsDir, savePresetParam, toggleSkillState) {
6090
+ function registerTuiCommand(ctx, ns, getSource, getModelsState, getModelCatalog, getModuleDirectory, saveModuleParam, toggleSkillState, refreshModule) {
6563
6091
  ctx.inject(["settings"], (sctx) => {
6564
6092
  return sctx.commands.register({
6565
6093
  name: "prompt-tool",
@@ -6573,13 +6101,13 @@ function registerTuiCommand(ctx, ns, getSource, getModelsState, getModelCatalog,
6573
6101
  /prompt-tool config <id>(id 可含空格)
6574
6102
  /prompt-tool config <id> on|off|toggle`
6575
6103
  });
6576
- const persistPresetParam = async (key, value) => {
6577
- if (savePresetParam === void 0) return {
6104
+ const persistModuleParam = async (key, value) => {
6105
+ if (saveModuleParam === void 0) return {
6578
6106
  kind: "error",
6579
6107
  text: `无法保存 ${key}:模块参数保存回调不可用`
6580
6108
  };
6581
6109
  try {
6582
- await savePresetParam(key, value);
6110
+ await saveModuleParam(key, value);
6583
6111
  return;
6584
6112
  } catch (error) {
6585
6113
  return {
@@ -6590,9 +6118,18 @@ function registerTuiCommand(ctx, ns, getSource, getModelsState, getModelCatalog,
6590
6118
  };
6591
6119
  const tokens = invocation.rawInput.trim().split(/\s+/).filter((token) => token.length > 0);
6592
6120
  const source = getSource();
6593
- const moduleDir = getPresetConfigsDir?.();
6594
- const params = readPresetParams(moduleDir);
6595
- const promptConfigs = resolvePromptConfigs(moduleDir, Array.isArray(params.promptConfigs) ? params.promptConfigs : []);
6121
+ const moduleDir = getModuleDirectory?.();
6122
+ const params = readModuleParams(moduleDir);
6123
+ let snapshot;
6124
+ try {
6125
+ if (moduleDir) snapshot = readModuleRules(moduleDir);
6126
+ } catch (error) {
6127
+ return {
6128
+ kind: "error",
6129
+ text: `读取规则失败:${error instanceof Error ? error.message : String(error)}`
6130
+ };
6131
+ }
6132
+ const rules = snapshot?.rules ?? [];
6596
6133
  if (tokens.length === 0 || tokens[0] === "status") {
6597
6134
  const detection = getModelsState();
6598
6135
  const catalog = await getModelCatalog();
@@ -6601,7 +6138,7 @@ function registerTuiCommand(ctx, ns, getSource, getModelsState, getModelCatalog,
6601
6138
  const modelsLine = catalogEntries.length > 0 ? `检测到的模型名: ${catalogEntries.map(([provider, models]) => `${provider} → ${models.join(", ")}`).join(";")}` : "未检测到模型名(adapter 未公布或查询失败)";
6602
6139
  return {
6603
6140
  kind: "success",
6604
- text: renderTuiStatus(source, params, promptConfigs) + "\n" + providersLine + "\n" + modelsLine
6141
+ text: renderTuiStatus(source, params, rules) + "\n" + providersLine + "\n" + modelsLine
6605
6142
  };
6606
6143
  }
6607
6144
  if (tokens[0] === "skill") {
@@ -6632,33 +6169,55 @@ function registerTuiCommand(ctx, ns, getSource, getModelsState, getModelCatalog,
6632
6169
  kind: "success",
6633
6170
  text: `已把技能 ${id} 设为 ${next ? "开" : "关"}
6634
6171
 
6635
- ${renderTuiStatus(getSource(), readPresetParams(getPresetConfigsDir?.()), resolvePromptConfigs(getPresetConfigsDir?.(), []))}`
6172
+ ${renderTuiStatus(getSource(), readModuleParams(moduleDir), rules)}`
6636
6173
  };
6637
6174
  }
6638
6175
  if (tokens[0] === "config") {
6639
- const { id, action } = parseIdentifierAndAction(tokens.slice(1), (candidate) => promptConfigs.some((config) => config.id === candidate));
6176
+ const { id, action } = parseIdentifierAndAction(tokens.slice(1), (candidate) => rules.some((rule) => rule.id === candidate));
6640
6177
  if (id.length === 0) return usage();
6641
- const current = promptConfigs.find((config) => config.id === id);
6178
+ const current = rules.find((rule) => rule.id === id);
6642
6179
  if (current === void 0) return {
6643
6180
  kind: "error",
6644
- text: `未找到提示词配置 ${id};用 /prompt-tool status 查看全部 id。`
6181
+ text: `未找到规则 ${id};用 /prompt-tool status 查看全部 id。`
6645
6182
  };
6646
6183
  if (action === void 0) return {
6647
6184
  kind: "success",
6648
- text: renderConfigDetail(source, id, promptConfigs)
6185
+ text: renderConfigDetail(id, rules)
6649
6186
  };
6650
6187
  const next = parseTuiBoolean(action, current.enabled !== false);
6651
6188
  if (next === void 0) return usage();
6652
- const failure = await persistPresetParam("promptConfigs", promptConfigs.map((config) => config.id === id ? {
6653
- ...config,
6654
- enabled: next
6655
- } : config));
6656
- if (failure !== void 0) return failure;
6189
+ if (!moduleDir || !snapshot || !refreshModule) return {
6190
+ kind: "error",
6191
+ text: "规则保存通道不可用"
6192
+ };
6193
+ try {
6194
+ editModuleRules(moduleDir, {
6195
+ expectedRevision: snapshot.revision,
6196
+ ...next ? { activateRuleId: id } : { edits: [{
6197
+ previousId: id,
6198
+ rule: {
6199
+ ...current,
6200
+ enabled: false
6201
+ }
6202
+ }] }
6203
+ });
6204
+ } catch (error) {
6205
+ return {
6206
+ kind: "error",
6207
+ text: `保存规则失败:${error instanceof Error ? error.message : String(error)}`
6208
+ };
6209
+ }
6210
+ try {
6211
+ await refreshModule(basename(moduleDir));
6212
+ } catch (error) {
6213
+ return {
6214
+ kind: "error",
6215
+ text: `规则已保存,但重新装配失败:${error instanceof Error ? error.message : String(error)}`
6216
+ };
6217
+ }
6657
6218
  return {
6658
6219
  kind: "success",
6659
- text: `已把提示词配置 ${id} 设为 ${next ? "开" : "关"}
6660
-
6661
- ${renderConfigDetail(getSource(), id, resolvePromptConfigs(getPresetConfigsDir?.(), []))}`
6220
+ text: `已把规则 ${id} 设为 ${next ? "开" : "关"}\n\n${renderConfigDetail(id, readModuleRules(moduleDir).rules)}`
6662
6221
  };
6663
6222
  }
6664
6223
  const action = tokens[0];
@@ -6673,20 +6232,16 @@ ${renderConfigDetail(getSource(), id, resolvePromptConfigs(getPresetConfigsDir?.
6673
6232
  };
6674
6233
  const next = parseTuiBoolean(action, currentValue);
6675
6234
  if (next === void 0) return usage();
6676
- if (globalSwitch) await sctx.settings.mutate(ns, [{
6677
- op: "set",
6678
- path: [key],
6679
- value: next
6680
- }]);
6235
+ if (globalSwitch) await sctx.settings.mutate(ns, modulesEnabledOps(next));
6681
6236
  else {
6682
- const failure = await persistPresetParam(key, next);
6237
+ const failure = await persistModuleParam(key, next);
6683
6238
  if (failure !== void 0) return failure;
6684
6239
  }
6685
6240
  return {
6686
6241
  kind: "success",
6687
6242
  text: `已把 ${key} 设为 ${next ? "开" : "关"}
6688
6243
 
6689
- ${renderTuiStatus(getSource(), readPresetParams(getPresetConfigsDir?.()), resolvePromptConfigs(getPresetConfigsDir?.(), []))}`
6244
+ ${renderTuiStatus(getSource(), readModuleParams(moduleDir), moduleDir ? readModuleRules(moduleDir).rules : [])}`
6690
6245
  };
6691
6246
  }
6692
6247
  });
@@ -7023,6 +6578,162 @@ function createSkillsRuntime(ctx, options = {}) {
7023
6578
  };
7024
6579
  }
7025
6580
  //#endregion
6581
+ //#region engine/rule-runtime.mjs
6582
+ const noNext = /* @__PURE__ */ new Set([
6583
+ "agent/inbox/inserted",
6584
+ "agent/turn-stopping",
6585
+ "subagent/start",
6586
+ "subagent/end"
6587
+ ]);
6588
+ const compare = (a, b) => a.action.channelOrder - b.action.channelOrder || a.moduleId.localeCompare(b.moduleId) || a.ruleIndex - b.ruleIndex || a.action.actionIndex - b.action.actionIndex;
6589
+ /** 所有来源只进入这个装配入口;注入保留原有批处理和协调器所有权。 */
6590
+ function mountRuleSources(ctx, sources, options = {}) {
6591
+ const warnOnce = options.warnOnce ?? createWarnOnce(ctx, options.plugin ?? "rule-engine");
6592
+ const releases = [];
6593
+ const points = /* @__PURE__ */ new Map();
6594
+ const injections = new Map(sources.map((source) => [source.moduleId, {
6595
+ sourceId: `module:${source.moduleId}`,
6596
+ configs: [],
6597
+ ruleActions: [],
6598
+ officialInstructions: source.officialInstructions === true
6599
+ }]));
6600
+ const rules = sources.flatMap((source) => source.rules.filter((rule) => rule.enabled !== false));
6601
+ const observer = wireTriggerObservers(ctx, rules, {
6602
+ plugin: options.plugin ?? "rule-engine",
6603
+ warnOnce
6604
+ });
6605
+ if (observer) releases.push(observer);
6606
+ let active = true;
6607
+ const bind = (item, on) => prepareAction(item.action, {
6608
+ plugin: options.plugin,
6609
+ warnOnce,
6610
+ promptConfigOptions: item.rule.promptConfigOptions,
6611
+ on
6612
+ })(ctx);
6613
+ try {
6614
+ for (const source of sources) for (const [ruleIndex, rule] of source.rules.entries()) {
6615
+ if (rule.enabled === false) continue;
6616
+ for (const action of rule.actions) {
6617
+ const item = {
6618
+ moduleId: source.moduleId,
6619
+ ruleIndex,
6620
+ rule,
6621
+ action
6622
+ };
6623
+ if (action.execution.lifecycle === "registration" && action.kind === "guard") {
6624
+ releases.push(bind(item));
6625
+ continue;
6626
+ }
6627
+ const point = action.execution;
6628
+ const key = `${point.channel}:${point.phase}:${action.waterfallPosition}`;
6629
+ const bucket = points.get(key) ?? {
6630
+ ...point,
6631
+ waterfallPosition: action.waterfallPosition,
6632
+ items: []
6633
+ };
6634
+ bucket.items.push(item);
6635
+ points.set(key, bucket);
6636
+ }
6637
+ }
6638
+ for (const point of points.values()) {
6639
+ point.items.sort(compare);
6640
+ const handlers = [];
6641
+ let textConfigs = [];
6642
+ const flushLayers = () => {
6643
+ if (!textConfigs.length) return;
6644
+ releases.push(wireLayers(ctx, textConfigs, warnOnce, { on: (channel, handler) => {
6645
+ if (channel !== point.channel) throw new TypeError(`rule action registered outside ${point.channel}: ${channel}`);
6646
+ handlers.push({ handler });
6647
+ return () => {};
6648
+ } }));
6649
+ textConfigs = [];
6650
+ };
6651
+ for (const item of point.items) {
6652
+ const { action, rule, moduleId } = item;
6653
+ if (action.kind === "inject-text") {
6654
+ const config = {
6655
+ ...action.compiledConfig,
6656
+ rule,
6657
+ sourceModuleId: moduleId,
6658
+ group: void 0,
6659
+ exclusive: false,
6660
+ enabled: true,
6661
+ actionWhen: action.actionWhen,
6662
+ bypassRuleWhen: action.bypassRuleWhen
6663
+ };
6664
+ if (config.layer === "pre-step") injections.get(moduleId).configs.push(config);
6665
+ else textConfigs.push(config);
6666
+ continue;
6667
+ }
6668
+ flushLayers();
6669
+ const on = (channel, handler) => {
6670
+ if (channel !== point.channel) throw new TypeError(`rule action registered outside ${point.channel}: ${channel}`);
6671
+ const entry = {
6672
+ rule,
6673
+ handler,
6674
+ sequence: action.channelOrder,
6675
+ sourceModuleId: moduleId,
6676
+ ruleId: rule.id,
6677
+ ruleActionIndex: action.actionIndex,
6678
+ id: action.id,
6679
+ actionWhen: action.actionWhen,
6680
+ bypassRuleWhen: action.bypassRuleWhen
6681
+ };
6682
+ if (channel === "agent/pre-step" && point.waterfallPosition === "default") injections.get(moduleId).ruleActions.push(entry);
6683
+ else handlers.push(entry);
6684
+ return () => {};
6685
+ };
6686
+ releases.push(bind(item, on));
6687
+ }
6688
+ flushLayers();
6689
+ if (!handlers.length) continue;
6690
+ releases.push(ctx.on(point.channel, (...args) => {
6691
+ const free = noNext.has(point.channel);
6692
+ const payload = free ? args : args.slice(0, -1);
6693
+ const next = free ? () => void 0 : args[args.length - 1];
6694
+ if (!active) return next();
6695
+ const frame = ruleFrame(point.channel, payload, warnOnce, ctx);
6696
+ if (free) {
6697
+ let pending;
6698
+ for (const entry of handlers) {
6699
+ const invoke = () => active && actionMatches(entry, frame) ? entry.handler(...payload, void 0, frame) : void 0;
6700
+ pending = pending?.then ? pending.then(invoke) : invoke();
6701
+ }
6702
+ return pending;
6703
+ }
6704
+ if (point.phase === "after-next") return Promise.resolve(next()).then(async (initial) => {
6705
+ let result = initial;
6706
+ for (const entry of handlers) {
6707
+ if (!active || !actionMatches(entry, frame)) continue;
6708
+ result = await entry.handler(...payload, () => result, frame);
6709
+ }
6710
+ return result;
6711
+ });
6712
+ const invoke = (index) => {
6713
+ if (!active || index === handlers.length) return next();
6714
+ const entry = handlers[index];
6715
+ return actionMatches(entry, frame) ? entry.handler(...payload, () => invoke(index + 1), frame) : invoke(index + 1);
6716
+ };
6717
+ return invoke(0);
6718
+ }, point.waterfallPosition === "outermost" ? { prepend: true } : void 0));
6719
+ }
6720
+ const preStep = [...injections.values()].filter((source) => source.configs.length || source.ruleActions.length);
6721
+ if (preStep.length) releases.push(applyPromptConfigSources(ctx, preStep, { layers: false }));
6722
+ } catch (error) {
6723
+ active = false;
6724
+ for (const dispose of releases.reverse()) try {
6725
+ dispose?.();
6726
+ } catch {}
6727
+ throw error;
6728
+ }
6729
+ return () => {
6730
+ active = false;
6731
+ for (const dispose of releases.splice(0).reverse()) try {
6732
+ dispose?.();
6733
+ } catch {}
6734
+ };
6735
+ }
6736
+ //#endregion
7026
6737
  //#region src/runtime/agent-assembly.ts
7027
6738
  /**
7028
6739
  * agent-assembly — 运行时配装通道。
@@ -7035,8 +6746,7 @@ function createSkillsRuntime(ctx, options = {}) {
7035
6746
  * 不再声明 `agent.cordis.yml` 这类「给宿主 Loader 用的组合本体」;
7036
6747
  * - 官方工具行(`dsh-tool-*` 等)由宿主提供;模块人设通过 systemPrompt 注册,
7037
6748
  * 本通道**不**装第二棵官方插件树——那些包也不在插件包的解析面内;
7038
- * - 物化目录优先:`configs/`(原 `prompt-configs/`)、`custom-tools/` 存在就按它装配,
7039
- * 缺失时回退 `module.yml` 内嵌,两条路径的切片同源(`resolveModuleFacts`)。
6749
+ * - 规则从校验发布的 rules/ 快照编译;工具与子代理策略从模块定义内联装配。
7040
6750
  *
7041
6751
  * 与官方挂载并存时不会重复:官方树里没有引擎行,引擎贡献只由本通道提供。
7042
6752
  */
@@ -7045,7 +6755,7 @@ const MANAGED_FIELDS = [
7045
6755
  "configsDir",
7046
6756
  "strategyDir",
7047
6757
  "policyFile",
7048
- "triggersFile"
6758
+ "rulesFile"
7049
6759
  ];
7050
6760
  /** 私有工具适配器依赖的服务;适配器仍复用包内引擎入口。 */
7051
6761
  const PRIVATE_SERVICES = {
@@ -7054,7 +6764,7 @@ const PRIVATE_SERVICES = {
7054
6764
  "session-var-tools": "pt-session-var-tools"
7055
6765
  };
7056
6766
  /**
7057
- * 受管字段的路径换算:`configsDir` / `strategyDir` / `policyFile` / `triggersFile`
6767
+ * 受管字段的路径换算:`configsDir` / `strategyDir` / `policyFile` / `rulesFile`
7058
6768
  * 一律解析到**当前模块目录内的真实位置**——声明怎么写都按模块目录作基准,三种形态同结果:
7059
6769
  * - `./configs`(新形态)→ `<模块目录>/configs`;
7060
6770
  * - `../<id>/triggers.yml`(历史形态:`ENGINE_MANAGED_PATHS` 的改写成这样,语义是
@@ -7085,34 +6795,37 @@ function engineModuleFile(engineDir, id) {
7085
6795
  * 纯读缝(只依赖包内引擎与模块目录),供挂载与回归测试共用;`hasService` 是宿主能力探针。
7086
6796
  */
7087
6797
  async function prepareAssembly(moduleRoot, moduleId, hasService) {
7088
- assertPresetId(moduleId);
6798
+ assertModuleId(moduleId);
7089
6799
  const moduleDir = resolveModuleDir(moduleId, moduleRoot);
7090
6800
  const spec = loadModuleSpec(moduleDir);
6801
+ if (spec.customTools !== void 0 && !Array.isArray(spec.customTools)) throw new TypeError("customTools must be an array");
6802
+ const toolErrors = validateCustomTools(spec.customTools ?? []);
6803
+ if (toolErrors.length > 0) throw new Error(`invalid customTools: ${toolErrors.join("; ")}`);
7091
6804
  const facts = resolveModuleFacts(spec, moduleDir);
7092
6805
  if (facts.effectiveModules === null) throw new Error(`模块 ${moduleId} 的模块声明无效,无法配装`);
7093
6806
  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,
6807
+ const ruleConfig = absolutizeManagedFields(configsByModule["rule-engine"] ?? {}, moduleDir);
6808
+ const rules = compileRules(spec.rules ?? [], {
6809
+ moduleId,
7099
6810
  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
7103
- });
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;
6811
+ variables: spec.variables,
6812
+ variablesEnabled: spec.variablesEnabled,
6813
+ promptConfigOptions: rulePromptConfigOptions(moduleDir, ruleConfig.strategyDir)
7108
6814
  });
6815
+ const ruleConditions = new Map((spec.rules ?? []).map((rule) => [rule.id, structuredClone(rule.if)]));
6816
+ const exclusiveConfigs = (spec.rules ?? []).filter((rule) => rule.enabled !== false).flatMap((rule) => rule.then.filter((action) => {
6817
+ const config = action.config;
6818
+ return action.kind === "inject-text" && config?.params?.complete === true;
6819
+ }));
7109
6820
  const personaComplete = spec.persona?.complete === true;
7110
6821
  if (exclusiveConfigs.length > 1 || personaComplete && exclusiveConfigs.length > 0) throw new Error(`模块 ${moduleId} 有多个生效的「独占」段(顶层人设${personaComplete ? "已" : "未"}开启),同一模块只能有一个:请先关闭其一`);
7111
6822
  const engineDir = packageEngineDir();
7112
6823
  const services = /* @__PURE__ */ new Set();
7113
6824
  const modules = [];
7114
6825
  for (const id of /* @__PURE__ */ new Set([...facts.effectiveModules, ...facts.rowIds])) {
7115
- if (id === "prompt-config-engine") continue;
6826
+ if (id === "rule-engine") continue;
6827
+ if (id === "subagent-tool-policy" && (spec.subagentToolPolicy === void 0 || spec.subagentToolPolicy === null)) continue;
6828
+ if (id === "prompt-config-engine" || id === "declared-triggers") throw new Error(`模块 ${moduleId} 仍声明旧规则引擎 ${id},请先离线迁移`);
7116
6829
  const privateService = PRIVATE_SERVICES[id];
7117
6830
  if (privateService !== void 0) {
7118
6831
  if (!hasService(privateService)) throw new Error(`配装所需能力不可用:${privateService}`);
@@ -7124,7 +6837,12 @@ async function prepareAssembly(moduleRoot, moduleId, hasService) {
7124
6837
  if (typeof module.apply !== "function") throw new Error(`引擎能力缺少 apply:${id}`);
7125
6838
  for (const dependency of module.inject ?? []) services.add(dependency);
7126
6839
  const config = absolutizeManagedFields(configsByModule[id] ?? {}, moduleDir);
7127
- if (id === "tool-config-engine" || id === "declared-triggers") config.presetRoot = pathToFileURL(moduleRoot + sep).href;
6840
+ if (id === "tool-config-engine") {
6841
+ config.tools = (spec.customTools ?? []).map((tool) => compileCustomTool(tool));
6842
+ config.resourceRoot = pathToFileURL(moduleRoot + sep).href;
6843
+ delete config.presetRoot;
6844
+ }
6845
+ if (id === "subagent-tool-policy") config.policy = structuredClone(spec.subagentToolPolicy);
7128
6846
  module.configContract?.parse?.(config, id);
7129
6847
  modules.push({
7130
6848
  id,
@@ -7132,7 +6850,7 @@ async function prepareAssembly(moduleRoot, moduleId, hasService) {
7132
6850
  config
7133
6851
  });
7134
6852
  }
7135
- if (configs.length > 0) {
6853
+ if (rules.length > 0) {
7136
6854
  services.add("systemPrompt");
7137
6855
  services.add("tools");
7138
6856
  services.add("llm");
@@ -7140,7 +6858,8 @@ async function prepareAssembly(moduleRoot, moduleId, hasService) {
7140
6858
  if (spec.persona !== void 0) services.add("systemPrompt");
7141
6859
  for (const name of services) if (!hasService(name)) throw new Error(`配装所需宿主能力不可用:${name}`);
7142
6860
  return {
7143
- configs,
6861
+ rules,
6862
+ ruleConditions,
7144
6863
  modules,
7145
6864
  services,
7146
6865
  moduleId,
@@ -7180,6 +6899,20 @@ function createAgentAssembly(ctx, options) {
7180
6899
  name: "prompt-tool-assembly",
7181
6900
  inject: [...services],
7182
6901
  apply: async (scopeCtx) => {
6902
+ const standingMountFor = await loadStandingMountFor();
6903
+ const ruleSources = prepared.filter((item) => item.rules.length > 0).map((item) => ({
6904
+ moduleId: item.moduleId,
6905
+ rules: item.rules.map((value) => {
6906
+ const rule = value;
6907
+ return {
6908
+ ...rule,
6909
+ when: compileWhen(item.ruleConditions.get(rule.id), {
6910
+ ctx: scopeCtx,
6911
+ standingMountFor
6912
+ })
6913
+ };
6914
+ })
6915
+ }));
7183
6916
  for (const item of prepared) if (item.persona !== void 0) {
7184
6917
  const persona = item.persona;
7185
6918
  scopeCtx.systemPrompt.section({
@@ -7195,10 +6928,7 @@ function createAgentAssembly(ctx, options) {
7195
6928
  });
7196
6929
  if (persona.includeRuntimeContext === false) scopeCtx.systemPrompt.suppressRuntimeContext();
7197
6930
  }
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 }));
6931
+ scopeCtx.effect(() => mountRuleSources(scopeCtx, ruleSources, { prepend: true }));
7202
6932
  for (const item of prepared) for (const module of item.modules) {
7203
6933
  const result = await module.apply(scopeCtx, module.config);
7204
6934
  if (typeof result === "function") scopeCtx.effect(() => result, module.id);
@@ -7300,22 +7030,25 @@ function warn(ctx, message) {
7300
7030
  } catch {}
7301
7031
  }
7302
7032
  function apply(ctx, configIn) {
7303
- const seededPresets = new Set(ensurePresetSeed(MODULES_DIR).created);
7304
- const readConfig = () => ({ writePreset: configIn.writePreset.get() });
7033
+ const seededModules = new Set(ensureModuleSeed(MODULES_DIR).created);
7034
+ const readConfig = () => ({ modulesEnabled: readModulesEnabled({
7035
+ modulesEnabled: configIn.modulesEnabled?.get(),
7036
+ writePreset: configIn.writePreset?.get()
7037
+ }) });
7305
7038
  const config = readConfig();
7306
7039
  /** 运行时配装通道的运行态:工具写入目标按它报告的「本 Agent 装了哪几层提示词」解析。 */
7307
7040
  let assembly;
7308
7041
  const modelsState = () => detectModels(ctx);
7309
7042
  const getModelsState = () => modelsState();
7310
7043
  const runtime = { ...config };
7311
- const materialize = (id) => {
7312
- materializeModule(id, {
7313
- moduleDir: MODULES_DIR,
7044
+ const refreshModuleSlices = (id) => {
7045
+ ensureModuleReady(id, {
7046
+ modulesRoot: MODULES_DIR,
7314
7047
  warn: (message) => warn(ctx, message)
7315
7048
  });
7316
7049
  };
7317
- const rebuildPreset = async (id = basename(activeModuleDir())) => {
7318
- materialize(id);
7050
+ const refreshModuleRuntime = async (id = basename(activeModuleDir())) => {
7051
+ refreshModuleSlices(id);
7319
7052
  await assembly?.refresh(id);
7320
7053
  };
7321
7054
  /** 无请求目标时供TUI使用的模块;编辑器的显式ID始终单独解析。 */
@@ -7333,10 +7066,10 @@ function apply(ctx, configIn) {
7333
7066
  readSkillContent: skillsRuntime.readContent,
7334
7067
  writeSkillContent: skillsRuntime.writeContent,
7335
7068
  patchSkillFolders: skillsRuntime.setFolders
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) => {
7069
+ }), () => "", skillsRuntime.invalidate, (moduleId) => moduleId === void 0 ? activeModuleDir() : resolveEditDir(MODULES_DIR, moduleId), (_scopes, id) => refreshModuleRuntime(id), (id) => id === void 0 ? assembly?.refresh() : refreshModuleRuntime(id), async (id) => {
7337
7070
  skillsRuntime.invalidate();
7338
7071
  await assembly?.refresh(id);
7339
- }, (id) => rebuildPreset(id), () => assembly?.refresh());
7072
+ }, (id) => refreshModuleRuntime(id), () => assembly?.refresh());
7340
7073
  scheduleWebSurfaceRepair(ctx, (message) => warn(ctx, message));
7341
7074
  ctx.effect(() => ctx.on("llm/adapters-updated", () => invalidateModelCatalog(ctx)));
7342
7075
  const currentSource = () => ({
@@ -7348,9 +7081,8 @@ function apply(ctx, configIn) {
7348
7081
  skillCatalog: skillsRuntime.listSkills(),
7349
7082
  activeSkillsDirs: [skillsRuntime.skillsRoot]
7350
7083
  }), 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();
7084
+ saveModuleParams(MODULES_DIR, basename(activeModuleDir()), { [key]: value }, void 0);
7085
+ await refreshModuleRuntime();
7354
7086
  }, (name, enabled) => {
7355
7087
  const matches = skillsRuntime.listSkills().filter((skill) => skill.name === name);
7356
7088
  if (matches.length !== 1) return {
@@ -7367,34 +7099,41 @@ function apply(ctx, configIn) {
7367
7099
  ok: false,
7368
7100
  message: result.message
7369
7101
  };
7370
- });
7102
+ }, (id) => refreshModuleRuntime(id));
7371
7103
  let needsInitialApply = true;
7372
7104
  const applyState = async () => {
7373
- const next = currentSource();
7374
- const nextRuntime = { writePreset: typeof next.writePreset === "boolean" ? next.writePreset : config.writePreset };
7375
- const writePresetChanged = runtime.writePreset !== nextRuntime.writePreset;
7376
- if (!needsInitialApply && !writePresetChanged) return;
7105
+ const nextRuntime = { modulesEnabled: currentSource().modulesEnabled };
7106
+ const modulesEnabledChanged = runtime.modulesEnabled !== nextRuntime.modulesEnabled;
7107
+ if (!needsInitialApply && !modulesEnabledChanged) return;
7377
7108
  const initial = needsInitialApply;
7378
7109
  needsInitialApply = false;
7379
7110
  Object.assign(runtime, nextRuntime);
7380
7111
  if (initial) {
7381
- for (const id of seededPresets) materialize(id);
7382
- seededPresets.clear();
7112
+ for (const id of seededModules) refreshModuleSlices(id);
7113
+ seededModules.clear();
7383
7114
  }
7384
7115
  await assembly?.refresh();
7385
7116
  };
7386
7117
  const moduleToolHost = {
7387
7118
  target: (exec) => resolveModuleToolTarget(exec, MODULES_DIR, (sessionId) => assembly?.moduleIds(sessionId) ?? []),
7388
- rebuild: (id) => rebuildPreset(id)
7119
+ rebuild: (id) => refreshModuleRuntime(id)
7389
7120
  };
7390
7121
  ctx.provide("pt-character-tools", { mount: (scopeCtx) => registerCharacterTools(scopeCtx, moduleToolHost) });
7391
7122
  ctx.provide("pt-world-book-tools", { mount: (scopeCtx) => registerWorldBookTools(scopeCtx, moduleToolHost) });
7392
7123
  ctx.provide("pt-session-var-tools", { mount: (scopeCtx) => registerSessionVarTools(scopeCtx) });
7124
+ import(new URL("../engine/skill-search.mjs", import.meta.url).href).then((mod) => {
7125
+ if (typeof mod.apply !== "function") return;
7126
+ ctx.inject(mod.inject ?? ["tools"], (skillCtx) => {
7127
+ mod.apply?.(skillCtx);
7128
+ });
7129
+ }).catch((error) => {
7130
+ warn(ctx, `prompt-tool: skill-search 未挂载(skill_search/skill_load 不可用):${error instanceof Error ? error.message : String(error)}`);
7131
+ });
7393
7132
  installPreStepCoordinator(ctx);
7394
7133
  ctx.inject(["agents"], (actx) => {
7395
7134
  const mounted = createAgentAssembly(actx, {
7396
7135
  moduleRoot: MODULES_DIR,
7397
- enabledModules: () => runtime.writePreset ? enabledModuleIds(MODULES_DIR).filter((id) => moduleDirExists(MODULES_DIR, id)) : [],
7136
+ enabledModules: () => runtime.modulesEnabled ? enabledModuleIds(MODULES_DIR).filter((id) => moduleDirExists(MODULES_DIR, id)) : [],
7398
7137
  warn: (message) => warn(ctx, message)
7399
7138
  });
7400
7139
  assembly = mounted;
@@ -7418,4 +7157,4 @@ function apply(ctx, configIn) {
7418
7157
  });
7419
7158
  }
7420
7159
  //#endregion
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 };
7160
+ export { BRIDGE_ENDPOINTS, Config, DEFAULT_MODULE_ID, MODULE_CAPABILITIES as ENGINE_CAPABILITIES, MODULE_CAPABILITIES, ENGINE_PARAM_KEYS, ENGINE_RECIPES, INJECTION_POINT_ORDER, INSTRUCTION_FILE_STATUSES, MAX_BRIDGE_BODY_BYTES, MAX_CHARACTER_CARD_STREAM_BYTES, MAX_INSTRUCTION_FILE_BYTES, ModuleLayerSettingsError, PARAM_KEYS, PRE_STEP_COORDINATOR_SERVICE, PRE_STEP_COORDINATOR_VERSION, SETTINGS_BRIDGE_PREFIX, SKILL_NAME_PATTERN, USER_SKILLS_DIR, WRITER_PARAM_KEYS, agentsFileCardSpecs, agentsFileId, appendModuleCapabilities, appendModuleMemory, apply, applyModuleConfigs, assertModuleDirectory, assertModuleId, assertModuleTree, assertSafeConfigId, buildModuleConfigsFromParams, buildWorldBookEntry, canonicalModulesRoot, catalogFromScan, cloneBuiltinModule, cloneBuiltinModule as cloneBuiltinPreset, configFileName, convertStToModule, convertStToModule as convertStToPreset, convertStToModuleWithReport, createModuleCapability as createEngineCapabilityInPreset, createModuleCapability, createSkillsReloader, createSkillsRuntime, deleteWorldBookEntry, detectAgentsFileSnapshots, detectAgentsFiles, detectModels, moduleCapability as engineCapability, moduleCapability, engineRecipe, ensureModuleReady, ensureModuleSeed, ensureModuleSeed as ensurePresetSeed, ensureWebSurface, expandModuleSource, expandModuleSource as expandPresetSource, exportModulePackage, exportModulePackage as exportPresetPackage, importCharacterCard, importCharacterCardFile, importSkillsPackage, inject, installModulePackage, installModulePackage as installPresetPackage, installPreStepCoordinator, invalidateModelCatalog, invocationForScope, isModuleCapabilityPresent as isEngineCapabilityPresent, isModuleCapabilityPresent, listAdvertisedModels, listBuiltinTemplates, listModules, listWorldBookEntries, loadModuleSpec, loadPromptTemplates, loadToolTemplates, mergeInstructionCards, mergeModuleIntoModule, mergeStModules, mergeStModules as mergeStPresets, mergeStModulesWithReport, moduleDirExists, moduleImportPreview, moduleImportPreview as presetImportPreview, modulePathExists, name, parseFrontmatter, peekModelCatalog, processStText, readAgentsFileSnapshot, readModuleLayerSettings, readModuleMemory, readSkillInvocation, readSkillsState, registerCharacterTools, registerSettingsBridge, registerTuiCommand, registerWorldBookTools, removeModuleCapability as removeEngineCapabilityFromPreset, removeModuleCapability, removeMergedModule, removeModuleCapabilityDeclaration, removeModuleCapabilityDeclaration as removePresetModule, removeUserModule, removeUserModule as removeUserPreset, renderComposition, resolveModuleDir, resolveModuleFacts, resolveModuleParams, resolveModuleParams as resolvePresetParams, resolveProfileDir, resolveProjectRoot, resolveSubagentStartOptions, saveModuleParams, saveModulePersona, saveModulePersona as savePresetPersona, scanRoot, scanRoots, scheduleWebSurfaceRepair, scopeOfInvocation, setSkillInvocation, skillRoots, skillsStatePath, stModuleId, stModuleId as stPresetId, upsertWorldBookEntry, userModulesDir, validateCustomToolIdentities, validateEngineParamValues, withModuleDefinition, writeAgentsFile, writeAgentsFileChecked, writeModule, writePreset, writeSkillsState };