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