dsh-plugin-prompt-tool 0.6.2 → 0.7.1
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 +11 -9
- package/engine/anchor-match.mjs +38 -12
- package/engine/anchor-turn.mjs +68 -0
- package/engine/classify-task.mjs +35 -0
- package/engine/code-presentation.mjs +103 -0
- package/engine/compositions/library/anchor-turn.yml +10 -0
- package/engine/compositions/library/bootstrap-filesystem.yml +1 -1
- package/engine/compositions/library/code-presentation.yml +8 -0
- package/engine/compositions/library/command-goal.yml +11 -0
- package/engine/compositions/library/compaction.yml +1 -1
- package/engine/compositions/library/context-gate.yml +1 -2
- package/engine/compositions/library/cot-drip.yml +11 -0
- package/engine/compositions/library/custom-bash.yml +1 -1
- package/engine/compositions/library/delegation.yml +11 -7
- package/engine/compositions/library/deliberation-gate.yml +11 -0
- package/engine/compositions/library/persistent-shell.yml +25 -2
- package/engine/compositions/library/persona.yml +6 -2
- package/engine/compositions/library/planning.yml +1 -1
- package/engine/compositions/library/prompt-config-engine.yml +1 -1
- package/engine/compositions/library/run-code-env.yml +1 -1
- package/engine/compositions/library/skill-filesystem.yml +1 -1
- package/engine/compositions/library/skill-search.yml +1 -1
- package/engine/compositions/library/str-replace-editor.yml +2 -2
- package/engine/compositions/library/tool-ask-user.yml +1 -1
- package/engine/compositions/library/tool-bash.yml +1 -1
- package/engine/compositions/library/tool-bootstrap.yml +1 -3
- package/engine/compositions/library/tool-config-engine.yml +9 -0
- package/engine/compositions/library/tool-filter.yml +3 -7
- 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 -9
- package/engine/compositions/library/tool-jobs.yml +1 -1
- package/engine/compositions/library/tool-pwsh.yml +1 -1
- package/engine/compositions/library/tool-todo.yml +1 -1
- package/engine/compositions/library/tool-web.yml +2 -2
- package/engine/compositions/source/local/code-presentation.yml +8 -0
- package/engine/compositions/source/local/context-gate.yml +0 -1
- package/engine/compositions/source/local/str-replace-editor.yml +10 -0
- package/engine/compositions/source/local/tool-bootstrap.yml +1 -3
- package/engine/compositions/source/local/tool-config-engine.yml +9 -0
- package/engine/compositions/source/local/tool-filter.yml +8 -0
- package/engine/cot-drip.mjs +137 -0
- package/engine/custom-bash.mjs +73 -21
- package/engine/deliberation-gate.mjs +127 -0
- package/engine/layers.mjs +223 -198
- package/engine/run-code-env.mjs +8 -2
- package/engine/schema.mjs +1 -1
- package/engine/strategies.mjs +25 -14
- package/engine/tool-bootstrap.mjs +185 -59
- package/engine/tool-config-engine.mjs +390 -0
- package/lib/client.js +3840 -2311
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +186 -101
- package/lib/index.mjs +1012 -413
- package/lib/preset-core.d.mts +1 -1
- package/lib/preset-core.mjs +29 -3
- package/lib/{prompt-configs-BgFw_bFf.mjs → prompt-configs-BnQKRHVb.mjs} +211 -136
- package/lib/prompt-configs-CN0sJTUL.d.mts +193 -0
- package/package.json +121 -105
- package/preset/anchored/preset.yml +262 -308
- package/preset/creative/preset.yml +34 -32
- package/preset/custom/preset.yml +0 -1
- package/preset/liangshen/preset.yml +53 -50
- package/preset/minimal/preset.yml +18 -17
- package/preset/ptc/preset.yml +35 -33
- package/preset/standard/preset.yml +34 -32
- package/templates/10-pre-step.yml +18 -15
- package/templates/14-first-turn-anchor.yml +13 -13
- package/templates/15-guide-auto.yml +13 -13
- package/templates/16-custom-fallback.yml +19 -18
- package/templates/18-placeholder-env-facts.yml +13 -2
- package/templates/30-runtime-context.yml +3 -0
- package/templates/40-agent-request.yml +9 -9
- package/templates/60-tool-pipeline.yml +9 -8
- package/templates/tools/10-shell-command.yml +20 -0
- package/templates/tools/20-http-fetch.yml +24 -0
- package/templates/tools/30-fs-read.yml +20 -0
- package/templates/tools/40-delegate-character.yml +15 -0
- package/templates/tools/50-delegate-worldbook.yml +15 -0
- package/templates/tools/60-delegate-session-var.yml +15 -0
- package/templates/tools/70-delegate-worldbook-upsert.yml +36 -0
- package/lib/prompt-configs-B4vH09wx.d.mts +0 -100
- package/templates/11-merged-a.yml +0 -11
- package/templates/13-anchor.yml +0 -19
- package/templates/17-instruction-hint.yml +0 -23
- package/templates/19-placeholder-skill-catalog.yml +0 -19
- package/templates/31-runtime-context-placeholder.yml +0 -18
package/lib/index.mjs
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
1
|
+
import { A as DEFAULT_SKILLS_DIR, C as resolvePresetParams, D as writePluginState, E as withPresetDoc, M as LEGACY_USER_PRESETS_DIR, O as DEFAULT_PRESET_DIR, S as resolvePresetDir, T as userPresetsDir, _ as packageEngineDir, a as applyModuleConfigs, b as removeUserPreset, c as buildModuleConfigsFromParams, d as ensurePresetSeed, f as listBuiltinTemplates, g as openPresetLocation, h as loadPresetSpec, i as MODEL_SEGMENT_MAP, j as LEGACY_CONTAINER_DIR, k as DEFAULT_RESIDENT_AGENTS_PATH, l as cloneBuiltinPreset, m as loadPresetContent, n as mergePromptConfigs, o as asString, p as listPresets, r as renderPromptConfigYaml, s as assertCompositionArray, t as loadPromptConfigFiles, u as duplicateUserPreset, v as parseImportedPresetId, w as savePresetParams, x as renderComposition, y as readPluginState } from "./prompt-configs-BnQKRHVb.mjs";
|
|
3
2
|
import { appendFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
4
|
-
import { parse, parseDocument, stringify } from "yaml";
|
|
5
3
|
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
6
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { parse, parseDocument, stringify } from "yaml";
|
|
7
6
|
import z from "@deepseek-ai/schemastery";
|
|
8
7
|
import { createHash, randomBytes } from "node:crypto";
|
|
9
8
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
@@ -551,23 +550,20 @@ function installSubagentModelRoute(ctx, isEnabled, provider, model) {
|
|
|
551
550
|
});
|
|
552
551
|
}
|
|
553
552
|
//#endregion
|
|
554
|
-
//#region src/shared/
|
|
553
|
+
//#region src/shared/engine-params.ts
|
|
555
554
|
/**
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
* 这些键在 UI 有专门编辑入口(模型设置 / 工具与深度 / 开关),writePreset 把
|
|
561
|
-
* 预设级 params 合并进每条 promptConfig 的 params 时排除——「params(高级参数
|
|
562
|
-
* JSON)」框只保留内容变量与配置自身策略参数,避免 UI 已管理的参数冗余回写。
|
|
563
|
-
* 放 shared:host(write-preset)与 config/settings-bridge 共用,单一来源。
|
|
555
|
+
* 引擎行为参数键唯一权威(数组字面量 = 运行时事实)。
|
|
556
|
+
* 与 EngineParams 接口双向相等断言:新增参数必须同时改接口与列表,漏改任一侧
|
|
557
|
+
* typecheck 失败(防历史事故:PARAM_KEYS 漏 25 键混入 variables.yml 污染注入)。
|
|
564
558
|
*/
|
|
565
|
-
const
|
|
559
|
+
const ENGINE_PARAM_KEYS = [
|
|
566
560
|
"firstTurnAnchor",
|
|
567
561
|
"firstTurnText",
|
|
568
562
|
"firstTurnCustom",
|
|
569
563
|
"guideText",
|
|
570
564
|
"guideCustom",
|
|
565
|
+
"guideEnabled",
|
|
566
|
+
"injectPrompt",
|
|
571
567
|
"modelProvider",
|
|
572
568
|
"modelName",
|
|
573
569
|
"subagentModelProvider",
|
|
@@ -578,7 +574,6 @@ const PARAM_KEYS = /* @__PURE__ */ new Set([
|
|
|
578
574
|
"subagentReasoningEffort",
|
|
579
575
|
"subagentTemperature",
|
|
580
576
|
"subagentMaxTokens",
|
|
581
|
-
"subagentPersona",
|
|
582
577
|
"toolFilterAllow",
|
|
583
578
|
"toolFilterDeny",
|
|
584
579
|
"maxDepth",
|
|
@@ -586,13 +581,147 @@ const PARAM_KEYS = /* @__PURE__ */ new Set([
|
|
|
586
581
|
"firstTurnWord",
|
|
587
582
|
"bootstrapMaxTokens",
|
|
588
583
|
"usePtcMode",
|
|
584
|
+
"promoteGate",
|
|
585
|
+
"promoteAfterFirstResponse",
|
|
586
|
+
"maxPromoteSteps",
|
|
587
|
+
"bootstrapTools",
|
|
588
|
+
"compactionTools",
|
|
589
|
+
"personaSectionsOnly",
|
|
590
|
+
"workspaceLine",
|
|
591
|
+
"phase1FirstCallInstruction",
|
|
592
|
+
"messageSources",
|
|
593
|
+
"deferredSources",
|
|
594
|
+
"deferredGraceSteps",
|
|
595
|
+
"instructionHint",
|
|
596
|
+
"stages",
|
|
597
|
+
"stagePreUnlock",
|
|
598
|
+
"stageAdvanceTool",
|
|
599
|
+
"stageAdvanceDescription",
|
|
600
|
+
"stageSectionTemplate",
|
|
601
|
+
"toolFilterSubagents",
|
|
602
|
+
"strReplaceEditorMaxOutputChars",
|
|
603
|
+
"anchorTurn",
|
|
604
|
+
"anchorTurnText",
|
|
605
|
+
"deliberationGate",
|
|
606
|
+
"deliberationMinChars",
|
|
607
|
+
"deliberationMaxGatesPerTurn",
|
|
608
|
+
"cotDrip",
|
|
609
|
+
"cotDripEvery",
|
|
610
|
+
"cotDripMaxPerTurn"
|
|
611
|
+
];
|
|
612
|
+
/** writePreset.runtimeOf 实际透传键(与 PresetWriterParams 双向相等断言,防 Pick 漏键)。 */
|
|
613
|
+
const WRITER_PARAM_KEYS = [
|
|
614
|
+
"firstTurnAnchor",
|
|
615
|
+
"firstTurnText",
|
|
616
|
+
"firstTurnCustom",
|
|
617
|
+
"guideText",
|
|
618
|
+
"guideCustom",
|
|
619
|
+
"guideEnabled",
|
|
589
620
|
"injectPrompt",
|
|
621
|
+
"modelProvider",
|
|
622
|
+
"modelName",
|
|
623
|
+
"subagentModelProvider",
|
|
624
|
+
"subagentModelName",
|
|
625
|
+
"modelReasoningEffort",
|
|
626
|
+
"modelTemperature",
|
|
627
|
+
"modelMaxTokens",
|
|
628
|
+
"subagentReasoningEffort",
|
|
629
|
+
"subagentTemperature",
|
|
630
|
+
"subagentMaxTokens",
|
|
631
|
+
"toolFilterAllow",
|
|
632
|
+
"toolFilterDeny",
|
|
633
|
+
"maxDepth",
|
|
634
|
+
"allowKinds",
|
|
635
|
+
"firstTurnWord",
|
|
636
|
+
"bootstrapMaxTokens",
|
|
637
|
+
"usePtcMode",
|
|
638
|
+
"anchorTurn",
|
|
639
|
+
"anchorTurnText",
|
|
640
|
+
"deliberationGate",
|
|
641
|
+
"deliberationMinChars",
|
|
642
|
+
"deliberationMaxGatesPerTurn",
|
|
643
|
+
"cotDrip",
|
|
644
|
+
"cotDripEvery",
|
|
645
|
+
"cotDripMaxPerTurn"
|
|
646
|
+
];
|
|
647
|
+
const NUMERIC_PARAM_RULES = {
|
|
648
|
+
modelTemperature: validateTemperature,
|
|
649
|
+
subagentTemperature: validateTemperature,
|
|
650
|
+
modelMaxTokens: validateMaxTokens,
|
|
651
|
+
subagentMaxTokens: validateMaxTokens
|
|
652
|
+
};
|
|
653
|
+
function validateTemperature(value) {
|
|
654
|
+
return Number.isFinite(value) ? void 0 : "必须是有限数字";
|
|
655
|
+
}
|
|
656
|
+
function validateMaxTokens(value) {
|
|
657
|
+
return Number.isSafeInteger(value) && value > 0 ? void 0 : "必须是正整数";
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* 校验参数键值(键集合合法性由调用方白名单先行校验)。返回逐字段错误;空数组 = 通过。
|
|
661
|
+
* 类型收窄:只接受 number(preset.yml 手写数字)与 string(UI 字符串标量);
|
|
662
|
+
* '' 是合法删键值,其余空白串/非数字串/越界值在保存层响亮失败。
|
|
663
|
+
*/
|
|
664
|
+
function validateEngineParamValues(overrides) {
|
|
665
|
+
const errors = [];
|
|
666
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
667
|
+
const rule = NUMERIC_PARAM_RULES[key];
|
|
668
|
+
if (rule === void 0) continue;
|
|
669
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
670
|
+
if (typeof value !== "number" && typeof value !== "string") {
|
|
671
|
+
errors.push({
|
|
672
|
+
key,
|
|
673
|
+
message: `${key}: 必须是数字或数字字符串(留空 = 不设置)`
|
|
674
|
+
});
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
const text = typeof value === "string" ? value.trim() : String(value);
|
|
678
|
+
if (text.length === 0) {
|
|
679
|
+
errors.push({
|
|
680
|
+
key,
|
|
681
|
+
message: `${key}: 必须是数字(留空 = 不设置)`
|
|
682
|
+
});
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
const numeric = Number(text);
|
|
686
|
+
const reason = Number.isNaN(numeric) ? "必须是数字" : rule(numeric);
|
|
687
|
+
if (reason !== void 0) errors.push({
|
|
688
|
+
key,
|
|
689
|
+
message: `${key}: ${reason}(留空 = 不设置)`
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
return errors;
|
|
693
|
+
}
|
|
694
|
+
//#endregion
|
|
695
|
+
//#region src/shared/param-keys.ts
|
|
696
|
+
/**
|
|
697
|
+
* 引擎行为参数键(按预设存储:激活预设 preset.yml 的 params + promptConfigs)。
|
|
698
|
+
* 不进 Config schema、不进 settings namespace——每预设一份,随预设走(官方范式:
|
|
699
|
+
* Config = 部署轴,引擎行为在预设文件)。
|
|
700
|
+
*
|
|
701
|
+
* 派生自 ENGINE_PARAM_KEYS(唯一权威)+ 少量附加键:
|
|
702
|
+
* - 锚定/引导内容键:writePreset 映射进 near-anchor/router-guide 的 promptConfig
|
|
703
|
+
* params(策略消费),须排除出 variables.yml(避免同一键双落盘为模板变量);
|
|
704
|
+
* - promptConfigs:settings 提示词配置数组键。
|
|
705
|
+
* 注意:variables.yml 的占位键(spec.variables 空值登记,供世界书条目 {{key}} 动态
|
|
706
|
+
* 引用)走另一套通道,不属于 PARAM_KEYS——两套体系不互串。
|
|
707
|
+
* 放 shared:host(write-preset)与 config/settings-bridge 共用,单一来源。
|
|
708
|
+
*/
|
|
709
|
+
const EXTRA_PARAM_KEYS = [
|
|
710
|
+
"buildPattern",
|
|
711
|
+
"complexPattern",
|
|
712
|
+
"firstTurnBuild",
|
|
713
|
+
"firstTurnInspect",
|
|
714
|
+
"firstTurnDeep",
|
|
715
|
+
"guideComplexPattern",
|
|
716
|
+
"guideWeak",
|
|
717
|
+
"guideDeep",
|
|
590
718
|
"promptConfigs"
|
|
591
|
-
]
|
|
719
|
+
];
|
|
720
|
+
const PARAM_KEYS = /* @__PURE__ */ new Set([...ENGINE_PARAM_KEYS, ...EXTRA_PARAM_KEYS]);
|
|
592
721
|
//#endregion
|
|
593
722
|
//#region src/config.ts
|
|
594
723
|
/** 插件配置、settings 数据模型与默认常量(settings 接口层)。 */
|
|
595
|
-
const NS =
|
|
724
|
+
const NS = "prompt-tool";
|
|
596
725
|
const Config = z.object({
|
|
597
726
|
injectAgentsPrompt: z.boolean().default(false),
|
|
598
727
|
writeAgents: z.boolean().default(true),
|
|
@@ -730,6 +859,20 @@ function loadPromptTemplates() {
|
|
|
730
859
|
};
|
|
731
860
|
});
|
|
732
861
|
}
|
|
862
|
+
/** 工具模板库扫描(templates/tools/*.yml;损坏 fail loud——包内模板属于发布 bug)。 */
|
|
863
|
+
function loadToolTemplates() {
|
|
864
|
+
const dir = fileURLToPath(new URL("../templates/tools", import.meta.url));
|
|
865
|
+
return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && /\.ya?ml$/i.test(entry.name)).sort((a, b) => a.name.localeCompare(b.name)).map((entry) => {
|
|
866
|
+
const content = readFileSync(join(dir, entry.name), "utf8");
|
|
867
|
+
const parsed = parse(content, { logLevel: "silent" });
|
|
868
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed) || typeof parsed.id !== "string" || parsed.id.length === 0 || typeof parsed.name !== "string" || parsed.execute === null || typeof parsed.execute !== "object") throw new Error(`tool template ${entry.name} must contain id/name/execute`);
|
|
869
|
+
return {
|
|
870
|
+
file: entry.name,
|
|
871
|
+
content,
|
|
872
|
+
spec: parsed
|
|
873
|
+
};
|
|
874
|
+
});
|
|
875
|
+
}
|
|
733
876
|
//#endregion
|
|
734
877
|
//#region src/runtime/skill-fix.ts
|
|
735
878
|
/**
|
|
@@ -850,6 +993,73 @@ function fixSkillEntry(skillsDir, folder) {
|
|
|
850
993
|
};
|
|
851
994
|
}
|
|
852
995
|
//#endregion
|
|
996
|
+
//#region src/host/worldbook.ts
|
|
997
|
+
/** 世界书条目 CRUD 服务:promptConfigs world-book 策略配置的增删改查。
|
|
998
|
+
* 与角色卡导入(characters.applyCharacterToPreset)共用同一存储(preset.yml
|
|
999
|
+
* promptConfigs),模型工具(world_book_*)与未来 bridge 端点同源,
|
|
1000
|
+
* 不各自实现 parseDocument 往返。 */
|
|
1001
|
+
/**
|
|
1002
|
+
* 世界书条目结构工厂(能力归一)。
|
|
1003
|
+
* strategy/layer/position 固定值与 params 键集单一权威——ST 导入(sillytavern.ts)
|
|
1004
|
+
* 与模型工具(world_book_upsert)共用,不再各自手写条目结构。
|
|
1005
|
+
*/
|
|
1006
|
+
function buildWorldBookEntry(input) {
|
|
1007
|
+
return {
|
|
1008
|
+
...input.id !== void 0 ? { id: input.id } : {},
|
|
1009
|
+
name: input.name,
|
|
1010
|
+
strategy: "world-book",
|
|
1011
|
+
order: typeof input.order === "number" ? input.order : 100,
|
|
1012
|
+
text: input.text,
|
|
1013
|
+
layer: "pre-step",
|
|
1014
|
+
position: "before-all",
|
|
1015
|
+
...input.enabled !== void 0 ? { enabled: input.enabled } : {},
|
|
1016
|
+
params: {
|
|
1017
|
+
constant: input.constant,
|
|
1018
|
+
...input.keys !== void 0 && input.keys.length > 0 ? { keys: input.keys } : {},
|
|
1019
|
+
...input.secondaryKeys !== void 0 && input.secondaryKeys.length > 0 ? { secondaryKeys: input.secondaryKeys } : {},
|
|
1020
|
+
...input.caseSensitive === true ? { caseSensitive: true } : {},
|
|
1021
|
+
...input.wholeWords === true ? { wholeWords: true } : {},
|
|
1022
|
+
...typeof input.selectiveLogic === "number" ? { selectiveLogic: input.selectiveLogic } : {}
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
const isWorldBook = (config) => config !== null && typeof config === "object" && !Array.isArray(config) && config.strategy === "world-book";
|
|
1027
|
+
/** 当前预设全部世界书条目(保持文件顺序)。 */
|
|
1028
|
+
function listWorldBookEntries(presetDir) {
|
|
1029
|
+
const spec = loadPresetSpec(presetDir);
|
|
1030
|
+
return Array.isArray(spec.promptConfigs) ? spec.promptConfigs.filter(isWorldBook) : [];
|
|
1031
|
+
}
|
|
1032
|
+
/** 新增或更新一条世界书条目(按 id 定位;不存在则追加)。返回写入后条目总数。 */
|
|
1033
|
+
function upsertWorldBookEntry(presetDir, entry) {
|
|
1034
|
+
if (entry === null || typeof entry !== "object" || typeof entry.id !== "string" || entry.id.length === 0) throw new TypeError("世界书条目必须含非空字符串 id");
|
|
1035
|
+
let count = 0;
|
|
1036
|
+
withPresetDoc(presetDir, (doc) => {
|
|
1037
|
+
const current = doc.toJS();
|
|
1038
|
+
const configs = Array.isArray(current.promptConfigs) ? current.promptConfigs : [];
|
|
1039
|
+
const existing = configs.findIndex((config) => String(config.id ?? "") === entry.id);
|
|
1040
|
+
if (existing >= 0) configs[existing] = entry;
|
|
1041
|
+
else configs.push(entry);
|
|
1042
|
+
doc.setIn(["promptConfigs"], configs);
|
|
1043
|
+
count = configs.filter(isWorldBook).length;
|
|
1044
|
+
});
|
|
1045
|
+
return count;
|
|
1046
|
+
}
|
|
1047
|
+
/** 删除一条世界书条目;不存在抛错。返回删除后条目总数。 */
|
|
1048
|
+
function deleteWorldBookEntry(presetDir, id) {
|
|
1049
|
+
let count = 0;
|
|
1050
|
+
let removed = false;
|
|
1051
|
+
withPresetDoc(presetDir, (doc) => {
|
|
1052
|
+
const current = doc.toJS();
|
|
1053
|
+
const configs = Array.isArray(current.promptConfigs) ? current.promptConfigs : [];
|
|
1054
|
+
const kept = configs.filter((config) => !(isWorldBook(config) && String(config.id ?? "") === id));
|
|
1055
|
+
removed = kept.length !== configs.length;
|
|
1056
|
+
doc.setIn(["promptConfigs"], kept);
|
|
1057
|
+
count = kept.filter(isWorldBook).length;
|
|
1058
|
+
});
|
|
1059
|
+
if (!removed) throw new Error(`世界书条目 ${id} 不存在`);
|
|
1060
|
+
return count;
|
|
1061
|
+
}
|
|
1062
|
+
//#endregion
|
|
853
1063
|
//#region src/host/sillytavern.ts
|
|
854
1064
|
/**
|
|
855
1065
|
* SillyTavern 预设 → 本项目单文件预设转换引擎(纯函数,无文件 IO)。
|
|
@@ -1016,25 +1226,20 @@ function convertStToPreset(card, baseName) {
|
|
|
1016
1226
|
const secondaryKeys = Array.isArray(rawSecondary) ? rawSecondary.map(String).filter((key) => key.trim().length > 0) : [];
|
|
1017
1227
|
const constant = entry.constant === true || entry.add_always === true;
|
|
1018
1228
|
const enabled = entry.disable !== void 0 ? entry.disable !== true : entry.enabled !== false;
|
|
1019
|
-
|
|
1229
|
+
const stOrder = typeof entry.insertion_order === "number" ? entry.insertion_order : typeof entry.order === "number" ? entry.order : 100;
|
|
1230
|
+
configs.push(buildWorldBookEntry({
|
|
1020
1231
|
id: `lore-${String(entry.id ?? entry.uid ?? index)}`,
|
|
1021
1232
|
name: comment,
|
|
1022
1233
|
enabled,
|
|
1023
|
-
|
|
1024
|
-
order: typeof entry.insertion_order === "number" ? entry.insertion_order : typeof entry.order === "number" ? entry.order : 100,
|
|
1234
|
+
order: -stOrder,
|
|
1025
1235
|
text: content,
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
...(entry.match_whole_words ?? entry.matchWholeWords) === true ? { wholeWords: true } : {},
|
|
1034
|
-
...(entry.use_regex ?? entry.useRegex) === true ? { useRegex: true } : {},
|
|
1035
|
-
...typeof entry.selectiveLogic === "number" ? { selectiveLogic: entry.selectiveLogic } : typeof entry.selective_logic === "number" ? { selectiveLogic: entry.selective_logic } : {}
|
|
1036
|
-
}
|
|
1037
|
-
});
|
|
1236
|
+
constant,
|
|
1237
|
+
keys: keys.length > 0 ? keys : void 0,
|
|
1238
|
+
secondaryKeys: secondaryKeys.length > 0 ? secondaryKeys : void 0,
|
|
1239
|
+
...(entry.case_sensitive ?? entry.caseSensitive) === true ? { caseSensitive: true } : {},
|
|
1240
|
+
...(entry.match_whole_words ?? entry.matchWholeWords) === true ? { wholeWords: true } : {},
|
|
1241
|
+
...typeof entry.selectiveLogic === "number" ? { selectiveLogic: entry.selectiveLogic } : typeof entry.selective_logic === "number" ? { selectiveLogic: entry.selective_logic } : {}
|
|
1242
|
+
}));
|
|
1038
1243
|
}
|
|
1039
1244
|
}
|
|
1040
1245
|
const firstMes = clean(bodyText("first_mes"));
|
|
@@ -1188,6 +1393,70 @@ function sortConfigs(configs) {
|
|
|
1188
1393
|
return (Number(a.order) || 0) - (Number(b.order) || 0);
|
|
1189
1394
|
});
|
|
1190
1395
|
}
|
|
1396
|
+
/** 角色卡记忆条目字段(不含 id;id 由调用方加 chara-<卡>- 前缀)。 */
|
|
1397
|
+
function buildCharacterMemoryEntry(spec, memory) {
|
|
1398
|
+
if (memory.trim().length === 0) return void 0;
|
|
1399
|
+
return buildWorldBookEntry({
|
|
1400
|
+
name: "角色记忆",
|
|
1401
|
+
enabled: true,
|
|
1402
|
+
order: 3e3,
|
|
1403
|
+
text: `【${spec.name} 的关系记忆】\n${memory}`,
|
|
1404
|
+
constant: true
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
/** 角色卡记忆变更后同步已导入预设的 chara-<id>-memory 注入条目
|
|
1408
|
+
* (world_book note 写入卡记忆后调用;未导入当前预设的卡返回 synced=false)。 */
|
|
1409
|
+
function syncImportedCharacterMemory(presetRoot, templateName, cardId) {
|
|
1410
|
+
if (!validCardId(cardId)) return {
|
|
1411
|
+
ok: false,
|
|
1412
|
+
message: `非法角色卡 id:${cardId}`
|
|
1413
|
+
};
|
|
1414
|
+
const spec = loadConverted(cardDir(presetRoot, cardId));
|
|
1415
|
+
if (spec === void 0) return {
|
|
1416
|
+
ok: false,
|
|
1417
|
+
message: `角色卡 ${cardId} 不存在或参数损坏`
|
|
1418
|
+
};
|
|
1419
|
+
const memory = readCharacterMemory(presetRoot, cardId);
|
|
1420
|
+
const memoryId = `chara-${cardId}-memory`;
|
|
1421
|
+
let synced = false;
|
|
1422
|
+
try {
|
|
1423
|
+
withPresetDoc(join(presetRoot, templateName), (doc) => {
|
|
1424
|
+
const current = doc.toJS();
|
|
1425
|
+
if (!(Array.isArray(current.meta?.importedCharacters) ? current.meta.importedCharacters.map(String) : []).includes(cardId)) return;
|
|
1426
|
+
const configs = Array.isArray(current.promptConfigs) ? current.promptConfigs : [];
|
|
1427
|
+
const entry = buildCharacterMemoryEntry(spec, memory);
|
|
1428
|
+
const at = configs.findIndex((config) => config !== null && typeof config === "object" && String(config.id ?? "") === memoryId);
|
|
1429
|
+
if (entry === void 0) {
|
|
1430
|
+
if (at >= 0) {
|
|
1431
|
+
configs.splice(at, 1);
|
|
1432
|
+
synced = true;
|
|
1433
|
+
}
|
|
1434
|
+
} else if (at >= 0) {
|
|
1435
|
+
configs[at] = {
|
|
1436
|
+
...entry,
|
|
1437
|
+
id: memoryId
|
|
1438
|
+
};
|
|
1439
|
+
synced = true;
|
|
1440
|
+
} else {
|
|
1441
|
+
configs.push({
|
|
1442
|
+
...entry,
|
|
1443
|
+
id: memoryId
|
|
1444
|
+
});
|
|
1445
|
+
synced = true;
|
|
1446
|
+
}
|
|
1447
|
+
if (synced) doc.setIn(["promptConfigs"], sortConfigs(configs));
|
|
1448
|
+
});
|
|
1449
|
+
return {
|
|
1450
|
+
ok: true,
|
|
1451
|
+
synced
|
|
1452
|
+
};
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
return {
|
|
1455
|
+
ok: false,
|
|
1456
|
+
message: `同步失败:${error instanceof Error ? error.message : String(error)}`
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1191
1460
|
/** 角色卡库根:预设根下的点前缀目录(官方 roster 扫描跳过,与 .engine 同机制)。 */
|
|
1192
1461
|
function charactersDir(presetRoot) {
|
|
1193
1462
|
return join(presetRoot, ".characters");
|
|
@@ -1205,6 +1474,19 @@ function loadConverted(dir) {
|
|
|
1205
1474
|
return;
|
|
1206
1475
|
}
|
|
1207
1476
|
}
|
|
1477
|
+
/** 追加角色卡本地记忆(.characters/<id>/memory.md,跟随角色卡跨预设)。 */
|
|
1478
|
+
function appendCharacterMemory(presetRoot, cardId, note) {
|
|
1479
|
+
appendMemoryFile(join(cardDir(presetRoot, cardId), "memory.md"), note, "# 角色记忆");
|
|
1480
|
+
}
|
|
1481
|
+
/** 追加记忆文件(时间戳列表格式;header 标注文件类型)。世界书工具与角色卡共用。 */
|
|
1482
|
+
function appendMemoryFile(file, note, header = "# 本地记忆") {
|
|
1483
|
+
const content = note.trim();
|
|
1484
|
+
if (content.length === 0) return;
|
|
1485
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
1486
|
+
const line = `\n- [${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ")}] ${content}\n`;
|
|
1487
|
+
if (existsSync(file)) appendFileSync(file, line, "utf8");
|
|
1488
|
+
else writeFileSync(file, `${header}\n${line}`, "utf8");
|
|
1489
|
+
}
|
|
1208
1490
|
/** 读角色卡本地记忆文本(无记忆返回空串)。 */
|
|
1209
1491
|
function readCharacterMemory(presetRoot, cardId) {
|
|
1210
1492
|
try {
|
|
@@ -1303,13 +1585,6 @@ function deleteCharacterCard(presetRoot, id) {
|
|
|
1303
1585
|
};
|
|
1304
1586
|
}
|
|
1305
1587
|
}
|
|
1306
|
-
function withPresetDoc(presetRoot, templateName, mutate) {
|
|
1307
|
-
const file = join(presetRoot, templateName, "preset.yml");
|
|
1308
|
-
if (!existsSync(file)) throw new Error(`当前预设 ${templateName} 无 preset.yml`);
|
|
1309
|
-
const doc = parseDocument(readFileSync(file, "utf8"), { logLevel: "silent" });
|
|
1310
|
-
mutate(doc);
|
|
1311
|
-
writeFileSync(file, doc.toString(), "utf8");
|
|
1312
|
-
}
|
|
1313
1588
|
/** 角色卡参数导入当前预设:promptConfigs 合并(chara-<id>- 前缀防冲突)、
|
|
1314
1589
|
* params 合并(角色卡覆盖同键)、meta.importedCharacters 记录来源。 */
|
|
1315
1590
|
function applyCharacterToPreset(presetRoot, templateName, cardId) {
|
|
@@ -1323,9 +1598,31 @@ function applyCharacterToPreset(presetRoot, templateName, cardId) {
|
|
|
1323
1598
|
message: `角色卡 ${cardId} 不存在或参数损坏`
|
|
1324
1599
|
};
|
|
1325
1600
|
const prefix = `chara-${cardId}-`;
|
|
1601
|
+
const hasSystemSections = (spec.promptConfigs ?? []).some((config) => config !== null && typeof config === "object" && !Array.isArray(config) && config.layer === "system-section");
|
|
1602
|
+
let personaOpened = false;
|
|
1326
1603
|
try {
|
|
1327
|
-
withPresetDoc(presetRoot, templateName, (doc) => {
|
|
1604
|
+
withPresetDoc(join(presetRoot, templateName), (doc) => {
|
|
1328
1605
|
const current = doc.toJS();
|
|
1606
|
+
if (hasSystemSections) {
|
|
1607
|
+
const configs = Array.isArray(current.promptConfigs) ? current.promptConfigs : [];
|
|
1608
|
+
const personaIdx = configs.findIndex((config) => {
|
|
1609
|
+
if (config === null || typeof config !== "object" || Array.isArray(config)) return false;
|
|
1610
|
+
const entry = config;
|
|
1611
|
+
const params = entry.params;
|
|
1612
|
+
return entry.id === "persona-main" || params?.sectionName === "deployment:persona";
|
|
1613
|
+
});
|
|
1614
|
+
if (personaIdx >= 0) {
|
|
1615
|
+
if ((configs[personaIdx]?.params)?.complete === true) {
|
|
1616
|
+
doc.setIn([
|
|
1617
|
+
"promptConfigs",
|
|
1618
|
+
personaIdx,
|
|
1619
|
+
"params",
|
|
1620
|
+
"complete"
|
|
1621
|
+
], false);
|
|
1622
|
+
personaOpened = true;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1329
1626
|
const existing = Array.isArray(current.promptConfigs) ? current.promptConfigs.filter((config) => {
|
|
1330
1627
|
return config !== null && typeof config === "object" && !String(config.id ?? "").startsWith(prefix);
|
|
1331
1628
|
}) : [];
|
|
@@ -1341,31 +1638,36 @@ function applyCharacterToPreset(presetRoot, templateName, cardId) {
|
|
|
1341
1638
|
for (const [key, value] of Object.entries(spec.params ?? {})) doc.setIn(["params", key], value);
|
|
1342
1639
|
const memory = readCharacterMemory(presetRoot, cardId);
|
|
1343
1640
|
const memoryId = `${prefix}memory`;
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1641
|
+
const memoryEntry = buildCharacterMemoryEntry(spec, memory);
|
|
1642
|
+
const merged = sortConfigs([
|
|
1643
|
+
...existing,
|
|
1644
|
+
...added,
|
|
1645
|
+
...memoryEntry !== void 0 ? [{
|
|
1646
|
+
...memoryEntry,
|
|
1647
|
+
id: memoryId
|
|
1648
|
+
}] : []
|
|
1649
|
+
]);
|
|
1650
|
+
if (hasSystemSections) {
|
|
1651
|
+
const persona = merged.find((config) => {
|
|
1652
|
+
if (config === null || typeof config !== "object" || Array.isArray(config)) return false;
|
|
1653
|
+
const entry = config;
|
|
1654
|
+
const params = entry.params;
|
|
1655
|
+
return entry.id === "persona-main" || params?.sectionName === "deployment:persona";
|
|
1656
|
+
});
|
|
1657
|
+
if ((persona?.params)?.complete === true && persona !== void 0) {
|
|
1658
|
+
persona.params.complete = false;
|
|
1659
|
+
personaOpened = true;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
doc.setIn(["promptConfigs"], merged);
|
|
1362
1663
|
const list = Array.isArray(current.meta?.importedCharacters) ? current.meta.importedCharacters : [];
|
|
1363
1664
|
if (!list.map(String).includes(cardId)) list.push(cardId);
|
|
1364
1665
|
doc.setIn(["meta", "importedCharacters"], list);
|
|
1365
1666
|
});
|
|
1366
1667
|
return {
|
|
1367
1668
|
ok: true,
|
|
1368
|
-
count: (spec.promptConfigs ?? []).length
|
|
1669
|
+
count: (spec.promptConfigs ?? []).length,
|
|
1670
|
+
...personaOpened ? { personaOpened: true } : {}
|
|
1369
1671
|
};
|
|
1370
1672
|
} catch (error) {
|
|
1371
1673
|
return {
|
|
@@ -1385,7 +1687,7 @@ function removeCharacterFromPreset(presetRoot, templateName, cardId) {
|
|
|
1385
1687
|
const prefix = `chara-${cardId}-`;
|
|
1386
1688
|
try {
|
|
1387
1689
|
let removed = 0;
|
|
1388
|
-
withPresetDoc(presetRoot, templateName, (doc) => {
|
|
1690
|
+
withPresetDoc(join(presetRoot, templateName), (doc) => {
|
|
1389
1691
|
const current = doc.toJS();
|
|
1390
1692
|
const kept = (Array.isArray(current.promptConfigs) ? current.promptConfigs : []).filter((config) => {
|
|
1391
1693
|
const isCard = config !== null && typeof config === "object" && String(config.id ?? "").startsWith(prefix);
|
|
@@ -1412,14 +1714,15 @@ function removeCharacterFromPreset(presetRoot, templateName, cardId) {
|
|
|
1412
1714
|
//#region src/shared/bridge-contract.ts
|
|
1413
1715
|
/**
|
|
1414
1716
|
* settings bridge 跨端契约(host 注册 / client 消费的唯一来源)。
|
|
1415
|
-
* 铁律:
|
|
1717
|
+
* 铁律:25 端点载荷统一成功 `{ ok: true, value }`、失败 `{ ok: false, code?, message? }`;
|
|
1416
1718
|
* 端点附加字段只能以 value 旁的可选扩展字段出现(describe)。
|
|
1417
1719
|
* 改路径或载荷形状必须同步更新 test/shared/bridge-contract.test.mjs。
|
|
1418
1720
|
*/
|
|
1419
1721
|
const SETTINGS_BRIDGE_PREFIX = "/api/prompt-tool/settings";
|
|
1420
|
-
/**
|
|
1722
|
+
/** 桥端点路径(相对前缀)。新增端点必须同时登记到契约测试。 */
|
|
1421
1723
|
const BRIDGE_ENDPOINTS = {
|
|
1422
1724
|
meta: "/meta",
|
|
1725
|
+
bootstrap: "/bootstrap",
|
|
1423
1726
|
describe: "/describe",
|
|
1424
1727
|
models: "/models",
|
|
1425
1728
|
mutate: "/mutate",
|
|
@@ -1431,6 +1734,7 @@ const BRIDGE_ENDPOINTS = {
|
|
|
1431
1734
|
importPreset: "/import-preset",
|
|
1432
1735
|
paramOverrides: "/param-overrides",
|
|
1433
1736
|
presetVariables: "/preset-variables",
|
|
1737
|
+
customTools: "/custom-tools",
|
|
1434
1738
|
importPresetPackage: "/import-preset-package",
|
|
1435
1739
|
exportPreset: "/export-preset",
|
|
1436
1740
|
presetDelete: "/preset-delete",
|
|
@@ -1523,7 +1827,8 @@ function restorePresetImport(targetDir, backupDir) {
|
|
|
1523
1827
|
} catch {}
|
|
1524
1828
|
}
|
|
1525
1829
|
/** 自建 loopback settings bridge:替代 registerConfigurableProviders,避免模型设置区出现插件条目。 */
|
|
1526
|
-
function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngineStrategyDir,
|
|
1830
|
+
function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngineStrategyDir, afterSkillFix, getPresetConfigsDir, afterPresetImport, afterOverridesChange, afterPresetPackageImport) {
|
|
1831
|
+
let invalidateCachedDescriptor = () => {};
|
|
1527
1832
|
ctx.inject(["settings", "webServer"], (sctx) => {
|
|
1528
1833
|
sctx.effect(() => {
|
|
1529
1834
|
let cachedDescriptor;
|
|
@@ -1540,6 +1845,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1540
1845
|
const invalidateDescriptor = () => {
|
|
1541
1846
|
cachedDescriptor = void 0;
|
|
1542
1847
|
};
|
|
1848
|
+
invalidateCachedDescriptor = invalidateDescriptor;
|
|
1543
1849
|
const guard = (req, res) => {
|
|
1544
1850
|
if (!isLoopbackRequest(req)) {
|
|
1545
1851
|
writeBridgeJson(res, 403, {
|
|
@@ -1559,19 +1865,149 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1559
1865
|
}
|
|
1560
1866
|
return true;
|
|
1561
1867
|
};
|
|
1868
|
+
/** 引擎能力矩阵(meta 端点与 /bootstrap 共用):动态 import 引擎 schema。 */
|
|
1869
|
+
const loadEngineMeta = async () => {
|
|
1870
|
+
const { getEngineMeta } = await import(new URL("../engine/schema.mjs", import.meta.url).href);
|
|
1871
|
+
const meta = getEngineMeta();
|
|
1872
|
+
meta.presets = listPresets();
|
|
1873
|
+
meta.builtinTemplates = listBuiltinTemplates();
|
|
1874
|
+
return meta;
|
|
1875
|
+
};
|
|
1876
|
+
/** describe 运行时事实(describe 端点与 /bootstrap 共用):检测状态、技能快照、
|
|
1877
|
+
* 宿主默认模型、模型目录缓存、激活预设参数。不触网(模型目录只读 10min 缓存)。 */
|
|
1878
|
+
const collectDescribeExtras = () => {
|
|
1879
|
+
const detection = getModelsState();
|
|
1880
|
+
const skillsState = getSkillsState();
|
|
1881
|
+
let hostDefaultModel;
|
|
1882
|
+
try {
|
|
1883
|
+
const selection = sctx.settings.get("agent-default-model");
|
|
1884
|
+
if (selection !== null && typeof selection === "object") {
|
|
1885
|
+
const record = selection;
|
|
1886
|
+
hostDefaultModel = {
|
|
1887
|
+
...typeof record.provider === "string" && record.provider.length > 0 ? { provider: record.provider } : {},
|
|
1888
|
+
...typeof record.model === "string" && record.model.length > 0 ? { model: record.model } : {},
|
|
1889
|
+
...typeof record.reasoningEffort === "string" && record.reasoningEffort.length > 0 ? { reasoningEffort: record.reasoningEffort } : {}
|
|
1890
|
+
};
|
|
1891
|
+
if (Object.keys(hostDefaultModel).length === 0) hostDefaultModel = void 0;
|
|
1892
|
+
}
|
|
1893
|
+
} catch {}
|
|
1894
|
+
const modelCatalog = peekModelCatalog();
|
|
1895
|
+
let presetParams = {};
|
|
1896
|
+
let templatePreStepCount = 0;
|
|
1897
|
+
try {
|
|
1898
|
+
const activeDir = getPresetConfigsDir?.() ?? "";
|
|
1899
|
+
const templateName = activeDir.length > 0 ? basename(activeDir) : "anchored";
|
|
1900
|
+
const spec = loadPresetSpec(activeDir.length > 0 ? activeDir : resolvePresetDir(templateName));
|
|
1901
|
+
presetParams = resolvePresetParams(spec, {});
|
|
1902
|
+
if (Array.isArray(spec.promptConfigs)) presetParams.promptConfigs = spec.promptConfigs;
|
|
1903
|
+
templatePreStepCount = (spec.promptConfigs ?? []).filter((config) => {
|
|
1904
|
+
const layer = config.layer;
|
|
1905
|
+
return layer === void 0 || layer === "pre-step";
|
|
1906
|
+
}).length;
|
|
1907
|
+
} catch {
|
|
1908
|
+
templatePreStepCount = 0;
|
|
1909
|
+
}
|
|
1910
|
+
return {
|
|
1911
|
+
presetParams,
|
|
1912
|
+
hostDefaultModel,
|
|
1913
|
+
templatePreStepCount,
|
|
1914
|
+
modelsAvailable: detection.available,
|
|
1915
|
+
providers: detection.providers,
|
|
1916
|
+
modelCatalog,
|
|
1917
|
+
modelsError: detection.error,
|
|
1918
|
+
activeSkillsDirs: skillsState.activeSkillsDirs,
|
|
1919
|
+
skillsDirExists: Object.fromEntries(skillsState.activeSkillsDirs.map((dir) => [dir, existsSync(dir)])),
|
|
1920
|
+
skillCatalog: skillsState.skillCatalog
|
|
1921
|
+
};
|
|
1922
|
+
};
|
|
1923
|
+
/** 激活预设的引擎参数子集(/param-overrides 读取;preset.yml 按 mtime 缓存)。 */
|
|
1924
|
+
const readParamOverrides = (dir) => {
|
|
1925
|
+
try {
|
|
1926
|
+
const spec = loadPresetSpec(dir);
|
|
1927
|
+
const params = {};
|
|
1928
|
+
for (const key of PARAM_KEYS) {
|
|
1929
|
+
if (key === "promptConfigs") continue;
|
|
1930
|
+
if (spec.params !== null && typeof spec.params === "object" && key in spec.params) params[key] = spec.params[key];
|
|
1931
|
+
}
|
|
1932
|
+
return params;
|
|
1933
|
+
} catch {
|
|
1934
|
+
return {};
|
|
1935
|
+
}
|
|
1936
|
+
};
|
|
1937
|
+
/** 预设级模板变量(/preset-variables 读取)。 */
|
|
1938
|
+
const readPresetVariables = (dir) => {
|
|
1939
|
+
try {
|
|
1940
|
+
const spec = loadPresetSpec(dir);
|
|
1941
|
+
const variables = {};
|
|
1942
|
+
for (const [key, value] of Object.entries(spec.params ?? {})) if (!PARAM_KEYS.has(key) && typeof value === "string") variables[key] = value;
|
|
1943
|
+
for (const [key, value] of Object.entries(spec.variables ?? {})) if (typeof value === "string") variables[key] = value;
|
|
1944
|
+
return {
|
|
1945
|
+
variables,
|
|
1946
|
+
enabled: spec.variablesEnabled !== false
|
|
1947
|
+
};
|
|
1948
|
+
} catch {
|
|
1949
|
+
return {
|
|
1950
|
+
variables: {},
|
|
1951
|
+
enabled: true
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
};
|
|
1955
|
+
/** 生成目录实际生效配置(/prompt-configs 读取)。 */
|
|
1956
|
+
const readPromptConfigs = (dir) => {
|
|
1957
|
+
try {
|
|
1958
|
+
return dir.length > 0 ? loadPromptConfigFiles(join(dir, "prompt-configs")) : [];
|
|
1959
|
+
} catch {
|
|
1960
|
+
return [];
|
|
1961
|
+
}
|
|
1962
|
+
};
|
|
1562
1963
|
const disposers = [
|
|
1964
|
+
sctx.webServer.register({
|
|
1965
|
+
kind: "exact",
|
|
1966
|
+
path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.bootstrap,
|
|
1967
|
+
handler: async (req, res) => {
|
|
1968
|
+
if (!guard(req, res)) return;
|
|
1969
|
+
const descriptor = findDescriptor();
|
|
1970
|
+
if (descriptor === void 0) {
|
|
1971
|
+
writeBridgeJson(res, 404, {
|
|
1972
|
+
ok: false,
|
|
1973
|
+
code: "settings-not-exposed",
|
|
1974
|
+
message: "prompt-tool settings namespace is not registered"
|
|
1975
|
+
});
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
try {
|
|
1979
|
+
const meta = await loadEngineMeta();
|
|
1980
|
+
const dir = getPresetConfigsDir?.() ?? "";
|
|
1981
|
+
const extras = collectDescribeExtras();
|
|
1982
|
+
writeBridgeJson(res, 200, {
|
|
1983
|
+
ok: true,
|
|
1984
|
+
value: descriptor,
|
|
1985
|
+
meta: { meta },
|
|
1986
|
+
overrides: { overrides: dir.length > 0 ? readParamOverrides(dir) : {} },
|
|
1987
|
+
variables: dir.length > 0 ? readPresetVariables(dir) : {
|
|
1988
|
+
variables: {},
|
|
1989
|
+
enabled: true
|
|
1990
|
+
},
|
|
1991
|
+
promptConfigs: { promptConfigs: readPromptConfigs(dir) },
|
|
1992
|
+
...extras
|
|
1993
|
+
});
|
|
1994
|
+
} catch (error) {
|
|
1995
|
+
writeBridgeJson(res, 500, {
|
|
1996
|
+
ok: false,
|
|
1997
|
+
code: "bootstrap-failed",
|
|
1998
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}),
|
|
1563
2003
|
sctx.webServer.register({
|
|
1564
2004
|
kind: "exact",
|
|
1565
2005
|
path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.meta,
|
|
1566
2006
|
handler: async (req, res) => {
|
|
1567
2007
|
if (!guard(req, res)) return;
|
|
1568
|
-
const { getEngineMeta } = await import(new URL("../engine/schema.mjs", import.meta.url).href);
|
|
1569
|
-
const meta = getEngineMeta();
|
|
1570
|
-
meta.presets = listPresets();
|
|
1571
|
-
meta.builtinTemplates = listBuiltinTemplates();
|
|
1572
2008
|
writeBridgeJson(res, 200, {
|
|
1573
2009
|
ok: true,
|
|
1574
|
-
value: { meta }
|
|
2010
|
+
value: { meta: await loadEngineMeta() }
|
|
1575
2011
|
});
|
|
1576
2012
|
}
|
|
1577
2013
|
}),
|
|
@@ -1580,7 +2016,6 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1580
2016
|
path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.describe,
|
|
1581
2017
|
handler: async (req, res) => {
|
|
1582
2018
|
if (!guard(req, res)) return;
|
|
1583
|
-
ensureRegistered(sctx);
|
|
1584
2019
|
const descriptor = findDescriptor();
|
|
1585
2020
|
if (descriptor === void 0) {
|
|
1586
2021
|
writeBridgeJson(res, 404, {
|
|
@@ -1590,50 +2025,10 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1590
2025
|
});
|
|
1591
2026
|
return;
|
|
1592
2027
|
}
|
|
1593
|
-
const detection = getModelsState();
|
|
1594
|
-
const skillsState = getSkillsState();
|
|
1595
|
-
let hostDefaultModel;
|
|
1596
|
-
try {
|
|
1597
|
-
const selection = sctx.settings.get(settingsNamespace("agent-default-model"));
|
|
1598
|
-
if (selection !== null && typeof selection === "object") {
|
|
1599
|
-
const record = selection;
|
|
1600
|
-
hostDefaultModel = {
|
|
1601
|
-
...typeof record.provider === "string" && record.provider.length > 0 ? { provider: record.provider } : {},
|
|
1602
|
-
...typeof record.model === "string" && record.model.length > 0 ? { model: record.model } : {},
|
|
1603
|
-
...typeof record.reasoningEffort === "string" && record.reasoningEffort.length > 0 ? { reasoningEffort: record.reasoningEffort } : {}
|
|
1604
|
-
};
|
|
1605
|
-
if (Object.keys(hostDefaultModel).length === 0) hostDefaultModel = void 0;
|
|
1606
|
-
}
|
|
1607
|
-
} catch {}
|
|
1608
|
-
const modelCatalog = peekModelCatalog();
|
|
1609
|
-
let presetParams = {};
|
|
1610
|
-
let templatePreStepCount = 0;
|
|
1611
|
-
try {
|
|
1612
|
-
const settingsValue = descriptor.value !== null && typeof descriptor.value === "object" ? descriptor.value : {};
|
|
1613
|
-
const templateName = typeof settingsValue.presetTemplate === "string" && settingsValue.presetTemplate.length > 0 ? settingsValue.presetTemplate : "anchored";
|
|
1614
|
-
const spec = loadPresetSpec(resolvePresetDir(templateName));
|
|
1615
|
-
presetParams = resolvePresetParams(spec, {});
|
|
1616
|
-
if (Array.isArray(spec.promptConfigs)) presetParams.promptConfigs = spec.promptConfigs;
|
|
1617
|
-
templatePreStepCount = (spec.promptConfigs ?? []).filter((config) => {
|
|
1618
|
-
const layer = config.layer;
|
|
1619
|
-
return layer === void 0 || layer === "pre-step";
|
|
1620
|
-
}).length;
|
|
1621
|
-
} catch {
|
|
1622
|
-
templatePreStepCount = 0;
|
|
1623
|
-
}
|
|
1624
2028
|
writeBridgeJson(res, 200, {
|
|
1625
2029
|
ok: true,
|
|
1626
2030
|
value: descriptor,
|
|
1627
|
-
|
|
1628
|
-
hostDefaultModel,
|
|
1629
|
-
templatePreStepCount,
|
|
1630
|
-
modelsAvailable: detection.available,
|
|
1631
|
-
providers: detection.providers,
|
|
1632
|
-
modelCatalog,
|
|
1633
|
-
modelsError: detection.error,
|
|
1634
|
-
activeSkillsDirs: skillsState.activeSkillsDirs,
|
|
1635
|
-
skillsDirExists: Object.fromEntries(skillsState.activeSkillsDirs.map((dir) => [dir, existsSync(dir)])),
|
|
1636
|
-
skillCatalog: skillsState.skillCatalog
|
|
2031
|
+
...collectDescribeExtras()
|
|
1637
2032
|
});
|
|
1638
2033
|
}
|
|
1639
2034
|
}),
|
|
@@ -1642,7 +2037,6 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1642
2037
|
path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.models,
|
|
1643
2038
|
handler: async (req, res) => {
|
|
1644
2039
|
if (!guard(req, res)) return;
|
|
1645
|
-
ensureRegistered(sctx);
|
|
1646
2040
|
writeBridgeJson(res, 200, {
|
|
1647
2041
|
ok: true,
|
|
1648
2042
|
value: { modelCatalog: await listAdvertisedModels(sctx) }
|
|
@@ -1654,7 +2048,6 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1654
2048
|
path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.mutate,
|
|
1655
2049
|
handler: async (req, res) => {
|
|
1656
2050
|
if (!guard(req, res)) return;
|
|
1657
|
-
ensureRegistered(sctx);
|
|
1658
2051
|
const { body } = await readBridgeBody(req);
|
|
1659
2052
|
if (body === null || body === void 0 || typeof body !== "object") {
|
|
1660
2053
|
writeBridgeJson(res, 400, {
|
|
@@ -1716,7 +2109,15 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1716
2109
|
path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.configsValidate,
|
|
1717
2110
|
handler: async (req, res) => {
|
|
1718
2111
|
if (!guard(req, res)) return;
|
|
1719
|
-
const { body } = await readBridgeBody(req);
|
|
2112
|
+
const { body, tooLarge } = await readBridgeBody(req, PRESET_PACKAGE_MAX_BYTES);
|
|
2113
|
+
if (tooLarge) {
|
|
2114
|
+
writeBridgeJson(res, 413, {
|
|
2115
|
+
ok: false,
|
|
2116
|
+
code: "configs-too-large",
|
|
2117
|
+
message: `配置载荷超过 ${Math.round(PRESET_PACKAGE_MAX_BYTES / 1024 / 1024)}MB 上限`
|
|
2118
|
+
});
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
1720
2121
|
if (body === null || body === void 0 || typeof body !== "object") {
|
|
1721
2122
|
writeBridgeJson(res, 400, {
|
|
1722
2123
|
ok: false,
|
|
@@ -1736,7 +2137,6 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1736
2137
|
path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.skillFix,
|
|
1737
2138
|
handler: async (req, res) => {
|
|
1738
2139
|
if (!guard(req, res)) return;
|
|
1739
|
-
ensureRegistered(sctx);
|
|
1740
2140
|
const { body } = await readBridgeBody(req);
|
|
1741
2141
|
if (body === null || body === void 0 || typeof body !== "object") {
|
|
1742
2142
|
writeBridgeJson(res, 400, {
|
|
@@ -1815,7 +2215,10 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1815
2215
|
try {
|
|
1816
2216
|
writeBridgeJson(res, 200, {
|
|
1817
2217
|
ok: true,
|
|
1818
|
-
value: {
|
|
2218
|
+
value: {
|
|
2219
|
+
templates: loadPromptTemplates(),
|
|
2220
|
+
toolTemplates: loadToolTemplates()
|
|
2221
|
+
}
|
|
1819
2222
|
});
|
|
1820
2223
|
} catch (error) {
|
|
1821
2224
|
writeBridgeJson(res, 500, {
|
|
@@ -1831,18 +2234,11 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1831
2234
|
path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.promptConfigs,
|
|
1832
2235
|
handler: async (req, res) => {
|
|
1833
2236
|
if (!guard(req, res)) return;
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
});
|
|
1840
|
-
} catch {
|
|
1841
|
-
writeBridgeJson(res, 200, {
|
|
1842
|
-
ok: true,
|
|
1843
|
-
value: { promptConfigs: [] }
|
|
1844
|
-
});
|
|
1845
|
-
}
|
|
2237
|
+
const dir = getPresetConfigsDir?.() ?? "";
|
|
2238
|
+
writeBridgeJson(res, 200, {
|
|
2239
|
+
ok: true,
|
|
2240
|
+
value: { promptConfigs: readPromptConfigs(dir) }
|
|
2241
|
+
});
|
|
1846
2242
|
}
|
|
1847
2243
|
}),
|
|
1848
2244
|
sctx.webServer.register({
|
|
@@ -1871,7 +2267,15 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1871
2267
|
path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.importPreset,
|
|
1872
2268
|
handler: async (req, res) => {
|
|
1873
2269
|
if (!guard(req, res)) return;
|
|
1874
|
-
const { body } = await readBridgeBody(req);
|
|
2270
|
+
const { body, tooLarge } = await readBridgeBody(req, PRESET_PACKAGE_MAX_BYTES);
|
|
2271
|
+
if (tooLarge) {
|
|
2272
|
+
writeBridgeJson(res, 413, {
|
|
2273
|
+
ok: false,
|
|
2274
|
+
code: "preset-import-too-large",
|
|
2275
|
+
message: `内容资产超过 ${Math.round(PRESET_PACKAGE_MAX_BYTES / 1024 / 1024)}MB 上限`
|
|
2276
|
+
});
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
1875
2279
|
if (body === null || body === void 0 || typeof body !== "object") {
|
|
1876
2280
|
writeBridgeJson(res, 400, {
|
|
1877
2281
|
ok: false,
|
|
@@ -1881,8 +2285,27 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1881
2285
|
return;
|
|
1882
2286
|
}
|
|
1883
2287
|
const record = body;
|
|
1884
|
-
const
|
|
1885
|
-
|
|
2288
|
+
const contents = [];
|
|
2289
|
+
if (Array.isArray(record.contents)) for (const entry of record.contents) {
|
|
2290
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
2291
|
+
const item = entry;
|
|
2292
|
+
contents.push({
|
|
2293
|
+
scope: item.scope === "agents" ? "agents" : "preset",
|
|
2294
|
+
content: typeof item.content === "string" ? item.content : ""
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
else contents.push({
|
|
2298
|
+
scope: record.scope === "agents" ? "agents" : "preset",
|
|
2299
|
+
content: typeof record.content === "string" ? record.content : ""
|
|
2300
|
+
});
|
|
2301
|
+
if (contents.length === 0) {
|
|
2302
|
+
writeBridgeJson(res, 400, {
|
|
2303
|
+
ok: false,
|
|
2304
|
+
code: "settings-rejected",
|
|
2305
|
+
message: "unreadable JSON body"
|
|
2306
|
+
});
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
1886
2309
|
const dir = getPresetConfigsDir?.() ?? "";
|
|
1887
2310
|
if (dir.length === 0) {
|
|
1888
2311
|
writeBridgeJson(res, 400, {
|
|
@@ -1894,11 +2317,11 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1894
2317
|
}
|
|
1895
2318
|
try {
|
|
1896
2319
|
mkdirSync(dir, { recursive: true });
|
|
1897
|
-
writeFileSync(join(dir, scope === "preset" ? "preset.md" : "agents.md"), content, "utf8");
|
|
1898
|
-
afterPresetImport?.(scope);
|
|
2320
|
+
for (const entry of contents) writeFileSync(join(dir, entry.scope === "preset" ? "preset.md" : "agents.md"), entry.content, "utf8");
|
|
2321
|
+
afterPresetImport?.(contents.map((entry) => entry.scope));
|
|
1899
2322
|
writeBridgeJson(res, 200, {
|
|
1900
2323
|
ok: true,
|
|
1901
|
-
value: { scope }
|
|
2324
|
+
value: { scopes: contents.map((entry) => entry.scope) }
|
|
1902
2325
|
});
|
|
1903
2326
|
} catch (error) {
|
|
1904
2327
|
writeBridgeJson(res, 500, {
|
|
@@ -1925,31 +2348,47 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1925
2348
|
}
|
|
1926
2349
|
const presetRoot = dirname(dir);
|
|
1927
2350
|
const templateName = basename(dir);
|
|
1928
|
-
const { body } = await readBridgeBody(req);
|
|
2351
|
+
const { body, tooLarge } = await readBridgeBody(req, PRESET_PACKAGE_MAX_BYTES);
|
|
2352
|
+
if (tooLarge) {
|
|
2353
|
+
writeBridgeJson(res, 413, {
|
|
2354
|
+
ok: false,
|
|
2355
|
+
code: "overrides-too-large",
|
|
2356
|
+
message: `参数载荷超过 ${Math.round(PRESET_PACKAGE_MAX_BYTES / 1024 / 1024)}MB 上限`
|
|
2357
|
+
});
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
1929
2360
|
const record = body ?? {};
|
|
1930
2361
|
if (record.overrides === void 0 && record.promptConfigs === void 0) {
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
value: { overrides: {} }
|
|
2362
|
+
writeBridgeJson(res, 200, {
|
|
2363
|
+
ok: true,
|
|
2364
|
+
value: { overrides: readParamOverrides(dir) }
|
|
2365
|
+
});
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
const rawOverrides = record.overrides !== null && typeof record.overrides === "object" && !Array.isArray(record.overrides) ? record.overrides : void 0;
|
|
2369
|
+
if (rawOverrides !== void 0) {
|
|
2370
|
+
const unknownKeys = Object.keys(rawOverrides).filter((key) => key === "promptConfigs" || !PARAM_KEYS.has(key));
|
|
2371
|
+
if (unknownKeys.length > 0) {
|
|
2372
|
+
writeBridgeJson(res, 400, {
|
|
2373
|
+
ok: false,
|
|
2374
|
+
code: "overrides-unknown-key",
|
|
2375
|
+
message: `未知引擎参数键:${unknownKeys.join(", ")}`
|
|
1946
2376
|
});
|
|
2377
|
+
return;
|
|
1947
2378
|
}
|
|
2379
|
+
}
|
|
2380
|
+
const valueErrors = rawOverrides !== void 0 ? validateEngineParamValues(rawOverrides) : [];
|
|
2381
|
+
if (valueErrors.length > 0) {
|
|
2382
|
+
writeBridgeJson(res, 400, {
|
|
2383
|
+
ok: false,
|
|
2384
|
+
code: "overrides-invalid-value",
|
|
2385
|
+
message: valueErrors.map((item) => item.message).join("; ")
|
|
2386
|
+
});
|
|
1948
2387
|
return;
|
|
1949
2388
|
}
|
|
1950
2389
|
try {
|
|
1951
|
-
savePresetParams(presetRoot, templateName,
|
|
1952
|
-
afterOverridesChange?.();
|
|
2390
|
+
savePresetParams(presetRoot, templateName, rawOverrides, Array.isArray(record.promptConfigs) ? record.promptConfigs : void 0);
|
|
2391
|
+
if (record.rebuild !== false) afterOverridesChange?.();
|
|
1953
2392
|
writeBridgeJson(res, 200, {
|
|
1954
2393
|
ok: true,
|
|
1955
2394
|
value: {
|
|
@@ -1985,41 +2424,83 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
1985
2424
|
const { body } = await readBridgeBody(req);
|
|
1986
2425
|
const record = body ?? {};
|
|
1987
2426
|
if (record.variables === void 0 && record.enabled === void 0) {
|
|
2427
|
+
writeBridgeJson(res, 200, {
|
|
2428
|
+
ok: true,
|
|
2429
|
+
value: readPresetVariables(dir)
|
|
2430
|
+
});
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
try {
|
|
2434
|
+
savePresetParams(presetRoot, templateName, void 0, void 0, record.variables !== null && typeof record.variables === "object" && !Array.isArray(record.variables) ? record.variables : void 0, typeof record.enabled === "boolean" ? record.enabled : void 0);
|
|
2435
|
+
afterOverridesChange?.();
|
|
2436
|
+
writeBridgeJson(res, 200, {
|
|
2437
|
+
ok: true,
|
|
2438
|
+
value: { variables: record.variables }
|
|
2439
|
+
});
|
|
2440
|
+
} catch (error) {
|
|
2441
|
+
writeBridgeJson(res, 409, {
|
|
2442
|
+
ok: false,
|
|
2443
|
+
code: "preset-variables-rejected",
|
|
2444
|
+
message: `模板变量保存失败:${error instanceof Error ? error.message : String(error)}`
|
|
2445
|
+
});
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
}),
|
|
2449
|
+
sctx.webServer.register({
|
|
2450
|
+
kind: "exact",
|
|
2451
|
+
path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.customTools,
|
|
2452
|
+
handler: async (req, res) => {
|
|
2453
|
+
if (!guard(req, res)) return;
|
|
2454
|
+
const dir = getPresetConfigsDir?.() ?? "";
|
|
2455
|
+
if (dir.length === 0) {
|
|
2456
|
+
writeBridgeJson(res, 400, {
|
|
2457
|
+
ok: false,
|
|
2458
|
+
code: "preset-dir-unavailable",
|
|
2459
|
+
message: "presetDir 未配置"
|
|
2460
|
+
});
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
const { body } = await readBridgeBody(req);
|
|
2464
|
+
const record = body ?? {};
|
|
2465
|
+
if (record.customTools === void 0) {
|
|
1988
2466
|
try {
|
|
1989
2467
|
const spec = loadPresetSpec(dir);
|
|
1990
|
-
const variables = {};
|
|
1991
|
-
for (const [key, value] of Object.entries(spec.params ?? {})) if (!PARAM_KEYS.has(key) && typeof value === "string") variables[key] = value;
|
|
1992
|
-
for (const [key, value] of Object.entries(spec.variables ?? {})) if (typeof value === "string") variables[key] = value;
|
|
1993
2468
|
writeBridgeJson(res, 200, {
|
|
1994
2469
|
ok: true,
|
|
1995
|
-
value: {
|
|
1996
|
-
variables,
|
|
1997
|
-
enabled: spec.variablesEnabled !== false
|
|
1998
|
-
}
|
|
2470
|
+
value: { customTools: Array.isArray(spec.customTools) ? spec.customTools : [] }
|
|
1999
2471
|
});
|
|
2000
2472
|
} catch {
|
|
2001
2473
|
writeBridgeJson(res, 200, {
|
|
2002
2474
|
ok: true,
|
|
2003
|
-
value: {
|
|
2004
|
-
variables: {},
|
|
2005
|
-
enabled: true
|
|
2006
|
-
}
|
|
2475
|
+
value: { customTools: [] }
|
|
2007
2476
|
});
|
|
2008
2477
|
}
|
|
2009
2478
|
return;
|
|
2010
2479
|
}
|
|
2011
2480
|
try {
|
|
2012
|
-
|
|
2481
|
+
const customTools = record.customTools;
|
|
2482
|
+
if (!Array.isArray(customTools)) {
|
|
2483
|
+
writeBridgeJson(res, 400, {
|
|
2484
|
+
ok: false,
|
|
2485
|
+
code: "custom-tools-invalid",
|
|
2486
|
+
message: "customTools 必须是数组"
|
|
2487
|
+
});
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
withPresetDoc(dir, (doc) => {
|
|
2491
|
+
if (customTools.length === 0) doc.deleteIn(["customTools"]);
|
|
2492
|
+
else doc.setIn(["customTools"], customTools);
|
|
2493
|
+
});
|
|
2013
2494
|
afterOverridesChange?.();
|
|
2014
2495
|
writeBridgeJson(res, 200, {
|
|
2015
2496
|
ok: true,
|
|
2016
|
-
value: {
|
|
2497
|
+
value: { customTools }
|
|
2017
2498
|
});
|
|
2018
2499
|
} catch (error) {
|
|
2019
2500
|
writeBridgeJson(res, 409, {
|
|
2020
2501
|
ok: false,
|
|
2021
|
-
code: "
|
|
2022
|
-
message:
|
|
2502
|
+
code: "custom-tools-rejected",
|
|
2503
|
+
message: `自定义工具保存失败:${error instanceof Error ? error.message : String(error)}`
|
|
2023
2504
|
});
|
|
2024
2505
|
}
|
|
2025
2506
|
}
|
|
@@ -2214,7 +2695,6 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
2214
2695
|
path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.presetDelete,
|
|
2215
2696
|
handler: async (req, res) => {
|
|
2216
2697
|
if (!guard(req, res)) return;
|
|
2217
|
-
ensureRegistered(sctx);
|
|
2218
2698
|
const { body } = await readBridgeBody(req);
|
|
2219
2699
|
const record = body ?? {};
|
|
2220
2700
|
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
@@ -2259,7 +2739,6 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
2259
2739
|
path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.presetClone,
|
|
2260
2740
|
handler: async (req, res) => {
|
|
2261
2741
|
if (!guard(req, res)) return;
|
|
2262
|
-
ensureRegistered(sctx);
|
|
2263
2742
|
const { body } = await readBridgeBody(req);
|
|
2264
2743
|
const record = body ?? {};
|
|
2265
2744
|
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
@@ -2291,7 +2770,6 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
2291
2770
|
path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.presetDuplicate,
|
|
2292
2771
|
handler: async (req, res) => {
|
|
2293
2772
|
if (!guard(req, res)) return;
|
|
2294
|
-
ensureRegistered(sctx);
|
|
2295
2773
|
const { body } = await readBridgeBody(req);
|
|
2296
2774
|
const record = body ?? {};
|
|
2297
2775
|
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
@@ -2327,7 +2805,6 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
2327
2805
|
path: SETTINGS_BRIDGE_PREFIX + BRIDGE_ENDPOINTS.presetOpen,
|
|
2328
2806
|
handler: async (req, res) => {
|
|
2329
2807
|
if (!guard(req, res)) return;
|
|
2330
|
-
ensureRegistered(sctx);
|
|
2331
2808
|
const { body } = await readBridgeBody(req);
|
|
2332
2809
|
const record = body ?? {};
|
|
2333
2810
|
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
@@ -2583,6 +3060,7 @@ function registerSettingsBridge(ctx, ns, getModelsState, getSkillsState, getEngi
|
|
|
2583
3060
|
};
|
|
2584
3061
|
}, "prompt-tool: settings bridge");
|
|
2585
3062
|
});
|
|
3063
|
+
return { invalidateDescriptor: () => invalidateCachedDescriptor() };
|
|
2586
3064
|
}
|
|
2587
3065
|
//#endregion
|
|
2588
3066
|
//#region src/runtime/character-tools.ts
|
|
@@ -2590,9 +3068,11 @@ const text$2 = (text) => [{
|
|
|
2590
3068
|
type: "text",
|
|
2591
3069
|
text
|
|
2592
3070
|
}];
|
|
3071
|
+
/** 注册角色卡库模型工具;返回 disposer(预设切换重挂用)。enabled=false 返回空操作。 */
|
|
2593
3072
|
function registerCharacterTools(ctx, host) {
|
|
2594
|
-
ctx.inject(["tools"], (toolsCtx) => {
|
|
2595
|
-
|
|
3073
|
+
const fiber = ctx.inject(["tools"], (toolsCtx) => {
|
|
3074
|
+
const disposers = [];
|
|
3075
|
+
disposers.push(toolsCtx.tools.register(defineTool({
|
|
2596
3076
|
name: "character_list",
|
|
2597
3077
|
description: "列出 SillyTavern 角色卡库:每张卡(id / 名称 / 描述 / 是否已导入当前预设)。导入角色卡、应用到当前预设或移除前先调用本工具获取 id。",
|
|
2598
3078
|
parameters: {},
|
|
@@ -2627,8 +3107,8 @@ function registerCharacterTools(ctx, host) {
|
|
|
2627
3107
|
render: (_args, value) => text$2(JSON.stringify(value.characters))
|
|
2628
3108
|
},
|
|
2629
3109
|
execute: async () => ({ characters: listCharacterCards(host.presetRoot(), host.templateName()) })
|
|
2630
|
-
}));
|
|
2631
|
-
toolsCtx.tools.register(defineTool({
|
|
3110
|
+
})));
|
|
3111
|
+
disposers.push(toolsCtx.tools.register(defineTool({
|
|
2632
3112
|
name: "character_import",
|
|
2633
3113
|
description: "导入 SillyTavern 角色卡(chara_card_v2/v3 JSON)到角色卡库:接收角色卡 JSON 文本内容(可先读取文件)。PNG 角色卡请让用户从 UI 角色管理页导入。导入后需调用 character_apply 应用到当前预设。",
|
|
2634
3114
|
parameters: {
|
|
@@ -2671,8 +3151,8 @@ function registerCharacterTools(ctx, host) {
|
|
|
2671
3151
|
name: result.name
|
|
2672
3152
|
};
|
|
2673
3153
|
}
|
|
2674
|
-
}));
|
|
2675
|
-
toolsCtx.tools.register(defineTool({
|
|
3154
|
+
})));
|
|
3155
|
+
disposers.push(toolsCtx.tools.register(defineTool({
|
|
2676
3156
|
name: "character_apply",
|
|
2677
3157
|
description: "把角色卡库中一张角色卡的参数(角色设定 / 系统提示 / 开场白 / 世界书 / 提示词库 / 采样参数)合并进当前激活预设(promptConfigs 带 chara-<id>- 前缀防冲突,params 合并,meta.importedCharacters 记录),并立即重建生成目录。重复应用幂等。",
|
|
2678
3158
|
parameters: { id: {
|
|
@@ -2706,8 +3186,8 @@ function registerCharacterTools(ctx, host) {
|
|
|
2706
3186
|
count: result.count
|
|
2707
3187
|
};
|
|
2708
3188
|
}
|
|
2709
|
-
}));
|
|
2710
|
-
toolsCtx.tools.register(defineTool({
|
|
3189
|
+
})));
|
|
3190
|
+
disposers.push(toolsCtx.tools.register(defineTool({
|
|
2711
3191
|
name: "character_remove",
|
|
2712
3192
|
description: "从当前激活预设移除一张已导入角色卡的参数(删 chara-<id>- 前缀配置、该卡声明的 params 键、meta.importedCharacters 除名),并立即重建生成目录。角色卡库条目不受影响。",
|
|
2713
3193
|
parameters: { id: {
|
|
@@ -2741,8 +3221,8 @@ function registerCharacterTools(ctx, host) {
|
|
|
2741
3221
|
count: result.count
|
|
2742
3222
|
};
|
|
2743
3223
|
}
|
|
2744
|
-
}));
|
|
2745
|
-
toolsCtx.tools.register(defineTool({
|
|
3224
|
+
})));
|
|
3225
|
+
disposers.push(toolsCtx.tools.register(defineTool({
|
|
2746
3226
|
name: "character_delete",
|
|
2747
3227
|
description: "从角色卡库删除一张角色卡(含其转换参数与头像)。已导入当前预设的参数不受影响(如需清理请先调用 character_remove)。",
|
|
2748
3228
|
parameters: { id: {
|
|
@@ -2766,8 +3246,14 @@ function registerCharacterTools(ctx, host) {
|
|
|
2766
3246
|
if (!result.ok) throw new Error(result.message);
|
|
2767
3247
|
return { id: args.id };
|
|
2768
3248
|
}
|
|
2769
|
-
}));
|
|
3249
|
+
})));
|
|
3250
|
+
return () => {
|
|
3251
|
+
for (const dispose of disposers) dispose();
|
|
3252
|
+
};
|
|
2770
3253
|
});
|
|
3254
|
+
return () => {
|
|
3255
|
+
fiber.dispose();
|
|
3256
|
+
};
|
|
2771
3257
|
}
|
|
2772
3258
|
//#endregion
|
|
2773
3259
|
//#region src/runtime/world-book-tools.ts
|
|
@@ -2775,26 +3261,6 @@ const text$1 = (text) => [{
|
|
|
2775
3261
|
type: "text",
|
|
2776
3262
|
text
|
|
2777
3263
|
}];
|
|
2778
|
-
function readConfigs(dir) {
|
|
2779
|
-
const file = join(dir, "preset.yml");
|
|
2780
|
-
const current = parseDocument(readFileSync(file, "utf8"), { logLevel: "silent" }).toJS();
|
|
2781
|
-
return Array.isArray(current.promptConfigs) ? current.promptConfigs : [];
|
|
2782
|
-
}
|
|
2783
|
-
function writeConfigs(dir, configs) {
|
|
2784
|
-
const file = join(dir, "preset.yml");
|
|
2785
|
-
const doc = parseDocument(readFileSync(file, "utf8"), { logLevel: "silent" });
|
|
2786
|
-
doc.setIn(["promptConfigs"], configs);
|
|
2787
|
-
writeFileSync(file, doc.toString(), "utf8");
|
|
2788
|
-
}
|
|
2789
|
-
/** 追加记忆文件(角色卡 memory.md 或预设 memory.md)。 */
|
|
2790
|
-
function appendMemory(file, note) {
|
|
2791
|
-
const content = note.trim();
|
|
2792
|
-
if (content.length === 0) return;
|
|
2793
|
-
mkdirSync(join(file, ".."), { recursive: true });
|
|
2794
|
-
const line = `\n- [${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ")}] ${content}\n`;
|
|
2795
|
-
if (existsSync(file)) appendFileSync(file, line, "utf8");
|
|
2796
|
-
else writeFileSync(file, `# 本地记忆\n${line}`, "utf8");
|
|
2797
|
-
}
|
|
2798
3264
|
/** 从条目 id 解析来源角色卡(chara-<cardId>- 前缀,cardId 可含连字符:遍历库目录取最长匹配)。 */
|
|
2799
3265
|
function sourceCardId(presetRoot, entryId) {
|
|
2800
3266
|
const root = join(presetRoot, ".characters");
|
|
@@ -2808,16 +3274,22 @@ function sourceCardId(presetRoot, entryId) {
|
|
|
2808
3274
|
} catch {}
|
|
2809
3275
|
return best;
|
|
2810
3276
|
}
|
|
3277
|
+
/** 注册世界书条目级模型工具;返回 disposer(预设切换重挂用)。enabled=false 返回空操作。 */
|
|
2811
3278
|
function registerWorldBookTools(ctx, host) {
|
|
2812
|
-
ctx.inject(["tools"], (toolsCtx) => {
|
|
3279
|
+
const fiber = ctx.inject(["tools"], (toolsCtx) => {
|
|
3280
|
+
const disposers = [];
|
|
2813
3281
|
/** note 归属写入:角色卡条目 → 卡记忆;其他 → 预设记忆。 */
|
|
2814
3282
|
const writeNote = (entryId, note) => {
|
|
2815
3283
|
if (note === void 0 || note.trim().length === 0) return;
|
|
2816
3284
|
const cardId = entryId !== void 0 ? sourceCardId(host.presetRoot(), entryId) : void 0;
|
|
2817
|
-
if (cardId !== void 0)
|
|
2818
|
-
|
|
3285
|
+
if (cardId !== void 0) {
|
|
3286
|
+
appendMemoryFile(join(host.presetRoot(), ".characters", cardId, "memory.md"), note, "# 角色记忆");
|
|
3287
|
+
try {
|
|
3288
|
+
syncImportedCharacterMemory(host.presetRoot(), basename(host.activeDir()), cardId);
|
|
3289
|
+
} catch {}
|
|
3290
|
+
} else appendMemoryFile(join(host.activeDir(), "memory.md"), note, "# 本地记忆");
|
|
2819
3291
|
};
|
|
2820
|
-
toolsCtx.tools.register(defineTool({
|
|
3292
|
+
disposers.push(toolsCtx.tools.register(defineTool({
|
|
2821
3293
|
name: "world_book_list",
|
|
2822
3294
|
description: "列出当前预设的世界书条目(world-book 策略配置:id / 名称 / 关键字 / 常驻 / 启用)。世界书 = 上下文条目:无 keys 的全局条目每次注入,有 keys 条目命中聊天内容才注入。增删改前先调用本工具获取 id。",
|
|
2823
3295
|
parameters: {},
|
|
@@ -2853,7 +3325,7 @@ function registerWorldBookTools(ctx, host) {
|
|
|
2853
3325
|
render: (_args, value) => text$1(JSON.stringify(value.entries))
|
|
2854
3326
|
},
|
|
2855
3327
|
execute: async () => {
|
|
2856
|
-
return { entries:
|
|
3328
|
+
return { entries: listWorldBookEntries(host.activeDir()).map((config) => ({
|
|
2857
3329
|
id: String(config.id ?? ""),
|
|
2858
3330
|
name: String(config.name ?? config.id ?? ""),
|
|
2859
3331
|
keys: Array.isArray(config.params && config.params.keys) ? config.params.keys : void 0,
|
|
@@ -2861,8 +3333,8 @@ function registerWorldBookTools(ctx, host) {
|
|
|
2861
3333
|
enabled: config.enabled !== false
|
|
2862
3334
|
})) };
|
|
2863
3335
|
}
|
|
2864
|
-
}));
|
|
2865
|
-
toolsCtx.tools.register(defineTool({
|
|
3336
|
+
})));
|
|
3337
|
+
disposers.push(toolsCtx.tools.register(defineTool({
|
|
2866
3338
|
name: "world_book_upsert",
|
|
2867
3339
|
description: "新增或更新当前预设的一条世界书条目(world-book 策略配置):按 id 更新(不存在则新增,id 自动生成 lore-<n>)。constant=true 常驻注入;否则命中 keys(或 secondaryKeys)任一关键字注入;无 keys 条目按全局每次注入。note 可选:写入来源角色卡的持久记忆(memory.md,条目 id 带 chara-<卡>- 前缀时)或预设记忆——持久记忆跨会话跟随角色卡(与 session_var 会话变量的临时状态不同,适合长期关系记录)。写盘后立即重建生成目录。",
|
|
2868
3340
|
parameters: {
|
|
@@ -2926,37 +3398,29 @@ function registerWorldBookTools(ctx, host) {
|
|
|
2926
3398
|
},
|
|
2927
3399
|
execute: async (args) => {
|
|
2928
3400
|
const dir = host.activeDir();
|
|
2929
|
-
const configs = readConfigs(dir);
|
|
2930
3401
|
const targetId = args.id !== void 0 && args.id.length > 0 ? args.id : `lore-${Date.now().toString(36)}`;
|
|
2931
|
-
const
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
params
|
|
2945
|
-
};
|
|
2946
|
-
if (args.enabled === false) entry.enabled = false;
|
|
2947
|
-
const existing = configs.findIndex((config) => String(config.id ?? "") === targetId);
|
|
2948
|
-
if (existing >= 0) configs[existing] = entry;
|
|
2949
|
-
else configs.push(entry);
|
|
2950
|
-
writeConfigs(dir, configs);
|
|
3402
|
+
const count = upsertWorldBookEntry(dir, {
|
|
3403
|
+
...buildWorldBookEntry({
|
|
3404
|
+
id: targetId,
|
|
3405
|
+
name: args.name,
|
|
3406
|
+
text: args.content,
|
|
3407
|
+
order: typeof args.order === "number" ? args.order : void 0,
|
|
3408
|
+
enabled: args.enabled === false ? false : void 0,
|
|
3409
|
+
constant: args.constant === true,
|
|
3410
|
+
keys: Array.isArray(args.keys) && args.keys.length > 0 ? args.keys : void 0,
|
|
3411
|
+
secondaryKeys: Array.isArray(args.secondaryKeys) && args.secondaryKeys.length > 0 ? args.secondaryKeys : void 0
|
|
3412
|
+
}),
|
|
3413
|
+
id: targetId
|
|
3414
|
+
});
|
|
2951
3415
|
writeNote(targetId, typeof args.note === "string" ? args.note : "");
|
|
2952
3416
|
host.rebuild();
|
|
2953
3417
|
return {
|
|
2954
3418
|
id: targetId,
|
|
2955
|
-
count
|
|
3419
|
+
count
|
|
2956
3420
|
};
|
|
2957
3421
|
}
|
|
2958
|
-
}));
|
|
2959
|
-
toolsCtx.tools.register(defineTool({
|
|
3422
|
+
})));
|
|
3423
|
+
disposers.push(toolsCtx.tools.register(defineTool({
|
|
2960
3424
|
name: "world_book_delete",
|
|
2961
3425
|
description: "删除当前预设的一条世界书条目(world_book_list 获取 id)。note 可选:写入来源角色卡持久记忆(memory.md,跨会话跟随角色卡)或预设记忆。删除后立即重建生成目录。",
|
|
2962
3426
|
parameters: {
|
|
@@ -2988,20 +3452,22 @@ function registerWorldBookTools(ctx, host) {
|
|
|
2988
3452
|
render: (_args, value) => text$1(`世界书条目 ${value.id} 已删除(剩余 ${value.count} 条),生成目录已重建。`)
|
|
2989
3453
|
},
|
|
2990
3454
|
execute: async (args) => {
|
|
2991
|
-
const
|
|
2992
|
-
const configs = readConfigs(dir);
|
|
2993
|
-
const kept = configs.filter((config) => !(config.strategy === "world-book" && String(config.id ?? "") === args.id));
|
|
2994
|
-
if (kept.length === configs.length) throw new Error(`世界书条目 ${args.id} 不存在`);
|
|
2995
|
-
writeConfigs(dir, kept);
|
|
3455
|
+
const keptCount = deleteWorldBookEntry(host.activeDir(), args.id);
|
|
2996
3456
|
writeNote(args.id, typeof args.note === "string" ? args.note : "");
|
|
2997
3457
|
host.rebuild();
|
|
2998
3458
|
return {
|
|
2999
3459
|
id: args.id,
|
|
3000
|
-
count:
|
|
3460
|
+
count: keptCount
|
|
3001
3461
|
};
|
|
3002
3462
|
}
|
|
3003
|
-
}));
|
|
3463
|
+
})));
|
|
3464
|
+
return () => {
|
|
3465
|
+
for (const dispose of disposers) dispose();
|
|
3466
|
+
};
|
|
3004
3467
|
});
|
|
3468
|
+
return () => {
|
|
3469
|
+
fiber.dispose();
|
|
3470
|
+
};
|
|
3005
3471
|
}
|
|
3006
3472
|
//#endregion
|
|
3007
3473
|
//#region src/runtime/session-var-tools.ts
|
|
@@ -3009,9 +3475,11 @@ const text = (value) => [{
|
|
|
3009
3475
|
type: "text",
|
|
3010
3476
|
text: value
|
|
3011
3477
|
}];
|
|
3478
|
+
/** 注册会话变量模型工具;返回 disposer(预设切换重挂用)。enabled=false 返回空操作。 */
|
|
3012
3479
|
function registerSessionVarTools(ctx) {
|
|
3013
|
-
ctx.inject(["tools"], (toolsCtx) => {
|
|
3014
|
-
|
|
3480
|
+
const fiber = ctx.inject(["tools"], (toolsCtx) => {
|
|
3481
|
+
const disposers = [];
|
|
3482
|
+
disposers.push(toolsCtx.tools.register(defineTool({
|
|
3015
3483
|
name: "session_var",
|
|
3016
3484
|
description: "会话变量管理(SillyTavern setvar/getvar 语义):list 查看当前会话全部变量、get 读取、set 设置、clear 清除。提示词/世界书文本中的 {{变量名}} 会在注入时替换为会话变量值(会话级覆盖预设默认值);适合维护角色状态(如 {{心情}}、{{接受度}} 等)。注意:会话变量仅存于当前会话(结束即失);跨会话长期记忆请用 world_book 工具的 note 参数写入角色卡记忆(持久,跟随角色卡)。",
|
|
3017
3485
|
parameters: {
|
|
@@ -3106,8 +3574,14 @@ function registerSessionVarTools(ctx) {
|
|
|
3106
3574
|
error: `未知 action: ${action}`
|
|
3107
3575
|
};
|
|
3108
3576
|
}
|
|
3109
|
-
}));
|
|
3577
|
+
})));
|
|
3578
|
+
return () => {
|
|
3579
|
+
for (const dispose of disposers) dispose();
|
|
3580
|
+
};
|
|
3110
3581
|
});
|
|
3582
|
+
return () => {
|
|
3583
|
+
fiber.dispose();
|
|
3584
|
+
};
|
|
3111
3585
|
}
|
|
3112
3586
|
//#endregion
|
|
3113
3587
|
//#region src/runtime/tui.ts
|
|
@@ -3121,6 +3595,7 @@ const TUI_GLOBAL_SWITCHES = [
|
|
|
3121
3595
|
const TUI_PARAM_SWITCHES = [
|
|
3122
3596
|
["injectPrompt", "锚定确认后注入 preset.md"],
|
|
3123
3597
|
["firstTurnAnchor", "追加任务引导"],
|
|
3598
|
+
["guideEnabled", "每轮引导(独立开关;缺省跟随锚定)"],
|
|
3124
3599
|
["firstTurnCustom", "使用自定义引导(首句)"],
|
|
3125
3600
|
["guideCustom", "使用自定义引导(每轮)"],
|
|
3126
3601
|
["usePtcMode", "使用 PTC 模式"]
|
|
@@ -3291,18 +3766,56 @@ function parseTuiBoolean(token, current) {
|
|
|
3291
3766
|
if (token === "off") return false;
|
|
3292
3767
|
if (token === "toggle") return !current;
|
|
3293
3768
|
}
|
|
3769
|
+
function asCommandAction(token) {
|
|
3770
|
+
return token === "on" || token === "off" || token === "toggle" ? token : void 0;
|
|
3771
|
+
}
|
|
3772
|
+
/**
|
|
3773
|
+
* 解析可含空格的标识符与末尾动作。
|
|
3774
|
+
*
|
|
3775
|
+
* 斜杠命令没有 shell 引号语义,因此动作只认“最后一个独立 token”。config 的
|
|
3776
|
+
* 详情形态没有动作;只有去掉末尾动作后的候选 id 已存在时才把末尾 token 视为
|
|
3777
|
+
* 动作,避免把普通 id 误拆。
|
|
3778
|
+
*/
|
|
3779
|
+
function parseIdentifierAndAction(tokens, exists) {
|
|
3780
|
+
if (tokens.length === 0) return { id: "" };
|
|
3781
|
+
const last = tokens[tokens.length - 1];
|
|
3782
|
+
const action = asCommandAction(last ?? "");
|
|
3783
|
+
if (action !== void 0 && tokens.length >= 2) {
|
|
3784
|
+
const id = tokens.slice(0, -1).join(" ");
|
|
3785
|
+
if (exists(id)) return {
|
|
3786
|
+
id,
|
|
3787
|
+
action
|
|
3788
|
+
};
|
|
3789
|
+
}
|
|
3790
|
+
return { id: tokens.join(" ") };
|
|
3791
|
+
}
|
|
3294
3792
|
/** 通过 DSH 命令注册表暴露 /prompt-tool,Web 与 dsh-tui 都能执行。 */
|
|
3295
3793
|
function registerTuiCommand(ctx, ns, getSource, getModelsState, getModelCatalog, getPresetConfigsDir, savePresetParam) {
|
|
3296
3794
|
ctx.inject(["settings"], (sctx) => {
|
|
3297
3795
|
return sctx.commands.register({
|
|
3298
3796
|
name: "prompt-tool",
|
|
3299
3797
|
description: "提示词工具:查看或切换本插件开关",
|
|
3300
|
-
input: { hint: "status | on/off/toggle <开关> | skill <目录名> on/off | config <id> [on/off/toggle]" },
|
|
3798
|
+
input: { hint: "status | on/off/toggle <开关> | skill <目录名> on/off/toggle | config <id> [on/off/toggle]" },
|
|
3301
3799
|
handler: async (invocation) => {
|
|
3302
3800
|
const usage = () => ({
|
|
3303
3801
|
kind: "error",
|
|
3304
|
-
text: "用法:/prompt-tool status\n /prompt-tool on|off|toggle <writeAgents|writePreset|injectPrompt|injectAgentsPrompt|firstTurnAnchor|firstTurnCustom|guideCustom|usePtcMode>\n /prompt-tool skill <技能目录名> on|off|toggle
|
|
3802
|
+
text: "用法:/prompt-tool status\n /prompt-tool on|off|toggle <writeAgents|writePreset|injectPrompt|injectAgentsPrompt|firstTurnAnchor|firstTurnCustom|guideEnabled|guideCustom|usePtcMode>\n /prompt-tool skill <技能目录名> on|off|toggle(目录名可含空格)\n /prompt-tool config <id>(id 可含空格)\n /prompt-tool config <id> on|off|toggle\n /prompt-tool bootstrapMaxTokens <正整数|0(关闭)>"
|
|
3305
3803
|
});
|
|
3804
|
+
const persistPresetParam = async (key, value) => {
|
|
3805
|
+
if (savePresetParam === void 0) return {
|
|
3806
|
+
kind: "error",
|
|
3807
|
+
text: `无法保存 ${key}:预设参数保存回调不可用`
|
|
3808
|
+
};
|
|
3809
|
+
try {
|
|
3810
|
+
await savePresetParam(key, value);
|
|
3811
|
+
return;
|
|
3812
|
+
} catch (error) {
|
|
3813
|
+
return {
|
|
3814
|
+
kind: "error",
|
|
3815
|
+
text: `保存 ${key} 失败:${error instanceof Error ? error.message : String(error)}`
|
|
3816
|
+
};
|
|
3817
|
+
}
|
|
3818
|
+
};
|
|
3306
3819
|
const tokens = invocation.rawInput.trim().split(/\s+/).filter((token) => token.length > 0);
|
|
3307
3820
|
const source = getSource();
|
|
3308
3821
|
const presetDir = getPresetConfigsDir?.();
|
|
@@ -3320,10 +3833,9 @@ function registerTuiCommand(ctx, ns, getSource, getModelsState, getModelCatalog,
|
|
|
3320
3833
|
};
|
|
3321
3834
|
}
|
|
3322
3835
|
if (tokens[0] === "skill") {
|
|
3323
|
-
const folder = tokens
|
|
3324
|
-
if (folder ===
|
|
3325
|
-
const
|
|
3326
|
-
const next = parseTuiBoolean(tokens[2], current);
|
|
3836
|
+
const { id: folder, action } = parseIdentifierAndAction(tokens.slice(1), () => true);
|
|
3837
|
+
if (folder.length === 0) return usage();
|
|
3838
|
+
const next = parseTuiBoolean(action, source.skillSwitches[folder] !== false);
|
|
3327
3839
|
if (next === void 0) return usage();
|
|
3328
3840
|
await sctx.settings.mutate(ns, [{
|
|
3329
3841
|
op: "set",
|
|
@@ -3338,25 +3850,24 @@ ${renderTuiStatus(getSource(), readPresetParams(getPresetConfigsDir?.()), resolv
|
|
|
3338
3850
|
};
|
|
3339
3851
|
}
|
|
3340
3852
|
if (tokens[0] === "config") {
|
|
3341
|
-
const id = tokens
|
|
3342
|
-
if (id ===
|
|
3853
|
+
const { id, action } = parseIdentifierAndAction(tokens.slice(1), (candidate) => promptConfigs.some((config) => config.id === candidate));
|
|
3854
|
+
if (id.length === 0) return usage();
|
|
3343
3855
|
const current = promptConfigs.find((config) => config.id === id);
|
|
3344
3856
|
if (current === void 0) return {
|
|
3345
3857
|
kind: "error",
|
|
3346
3858
|
text: `未找到提示词配置 ${id};用 /prompt-tool status 查看全部 id。`
|
|
3347
3859
|
};
|
|
3348
|
-
const action = tokens[2];
|
|
3349
3860
|
if (action === void 0) return {
|
|
3350
3861
|
kind: "success",
|
|
3351
3862
|
text: renderConfigDetail(source, id, promptConfigs)
|
|
3352
3863
|
};
|
|
3353
3864
|
const next = parseTuiBoolean(action, current.enabled !== false);
|
|
3354
3865
|
if (next === void 0) return usage();
|
|
3355
|
-
const
|
|
3866
|
+
const failure = await persistPresetParam("promptConfigs", promptConfigs.map((config) => config.id === id ? {
|
|
3356
3867
|
...config,
|
|
3357
3868
|
enabled: next
|
|
3358
|
-
} : config);
|
|
3359
|
-
|
|
3869
|
+
} : config));
|
|
3870
|
+
if (failure !== void 0) return failure;
|
|
3360
3871
|
return {
|
|
3361
3872
|
kind: "success",
|
|
3362
3873
|
text: `已把提示词配置 ${id} 设为 ${next ? "开" : "关"}
|
|
@@ -3371,7 +3882,8 @@ ${renderConfigDetail(getSource(), id, resolvePromptConfigs(getPresetConfigsDir?.
|
|
|
3371
3882
|
kind: "error",
|
|
3372
3883
|
text: "bootstrapMaxTokens 需要非负整数:0 关闭封顶,正整数设置首轮 maxTokens。"
|
|
3373
3884
|
};
|
|
3374
|
-
|
|
3885
|
+
const failure = await persistPresetParam("bootstrapMaxTokens", value);
|
|
3886
|
+
if (failure !== void 0) return failure;
|
|
3375
3887
|
return {
|
|
3376
3888
|
kind: "success",
|
|
3377
3889
|
text: `已把 bootstrapMaxTokens 设为 ${value === 0 ? "关闭(不设封顶)" : String(value)}
|
|
@@ -3396,7 +3908,10 @@ ${renderTuiStatus(getSource(), readPresetParams(getPresetConfigsDir?.()), resolv
|
|
|
3396
3908
|
path: [key],
|
|
3397
3909
|
value: next
|
|
3398
3910
|
}]);
|
|
3399
|
-
else
|
|
3911
|
+
else {
|
|
3912
|
+
const failure = await persistPresetParam(key, next);
|
|
3913
|
+
if (failure !== void 0) return failure;
|
|
3914
|
+
}
|
|
3400
3915
|
return {
|
|
3401
3916
|
kind: "success",
|
|
3402
3917
|
text: `已把 ${key} 设为 ${next ? "开" : "关"}
|
|
@@ -3408,25 +3923,6 @@ ${renderTuiStatus(getSource(), readPresetParams(getPresetConfigsDir?.()), resolv
|
|
|
3408
3923
|
});
|
|
3409
3924
|
}
|
|
3410
3925
|
//#endregion
|
|
3411
|
-
//#region src/runtime/settings-registration.ts
|
|
3412
|
-
/**
|
|
3413
|
-
* 确保 `ns` 已注册:已注册(describe 命中)直接返回 true;未注册时补注册。
|
|
3414
|
-
* 官方 installSettingsSection 的 inject 回调只在 settings 服务首次可用时跑一次,
|
|
3415
|
-
* settings 服务实例被替换(provider fiber reload)后注册丢失且不自动重建;
|
|
3416
|
-
* 本函数由 Web bridge 每次请求前调用兜底。
|
|
3417
|
-
*/
|
|
3418
|
-
function ensureSettingsRegistered(sctx, ns, schema, hooks) {
|
|
3419
|
-
if (sctx.settings.describe({ redactSecrets: true }).some((entry) => String(entry.ns) === String(ns))) return true;
|
|
3420
|
-
try {
|
|
3421
|
-
const scope = sctx.settings.register(ns, schema, { base: hooks.base() });
|
|
3422
|
-
hooks.onRegistered(scope);
|
|
3423
|
-
return true;
|
|
3424
|
-
} catch (error) {
|
|
3425
|
-
hooks.onError(error instanceof Error ? error.message : String(error));
|
|
3426
|
-
return false;
|
|
3427
|
-
}
|
|
3428
|
-
}
|
|
3429
|
-
//#endregion
|
|
3430
3926
|
//#region src/runtime/agents-file.ts
|
|
3431
3927
|
/** AGENTS.md 常驻规则的受管块读写(保留文件其余内容)。 */
|
|
3432
3928
|
const RESIDENT_AGENTS_BEGIN = "# === prompt-tool managed block begin ===";
|
|
@@ -3547,8 +4043,9 @@ function runtimeOf(options, prompt) {
|
|
|
3547
4043
|
firstTurnText: typeof options.firstTurnText === "string" ? options.firstTurnText : "",
|
|
3548
4044
|
guideCustom: options.guideCustom === true,
|
|
3549
4045
|
guideText: typeof options.guideText === "string" ? options.guideText : "",
|
|
4046
|
+
guideEnabled: typeof options.guideEnabled === "boolean" ? options.guideEnabled : void 0,
|
|
3550
4047
|
injectPrompt: options.injectPrompt !== false,
|
|
3551
|
-
usePtcMode: options.usePtcMode
|
|
4048
|
+
usePtcMode: typeof options.usePtcMode === "boolean" ? options.usePtcMode : void 0,
|
|
3552
4049
|
bootstrapMaxTokens: Number.isSafeInteger(options.bootstrapMaxTokens) ? options.bootstrapMaxTokens : 0,
|
|
3553
4050
|
modelProvider: typeof options.modelProvider === "string" && options.modelProvider.length > 0 ? options.modelProvider : "",
|
|
3554
4051
|
modelName: typeof options.modelName === "string" && options.modelName.length > 0 ? options.modelName : "",
|
|
@@ -3560,12 +4057,19 @@ function runtimeOf(options, prompt) {
|
|
|
3560
4057
|
subagentReasoningEffort: typeof options.subagentReasoningEffort === "string" && options.subagentReasoningEffort.length > 0 ? options.subagentReasoningEffort : void 0,
|
|
3561
4058
|
subagentTemperature: typeof options.subagentTemperature === "string" && options.subagentTemperature.length > 0 ? options.subagentTemperature : void 0,
|
|
3562
4059
|
subagentMaxTokens: typeof options.subagentMaxTokens === "string" && options.subagentMaxTokens.length > 0 ? options.subagentMaxTokens : void 0,
|
|
3563
|
-
subagentPersona: typeof options.subagentPersona === "string" && options.subagentPersona.length > 0 ? options.subagentPersona : "",
|
|
3564
4060
|
toolFilterAllow: options.toolFilterAllow !== void 0 && (Array.isArray(options.toolFilterAllow) ? options.toolFilterAllow.length > 0 : String(options.toolFilterAllow).trim().length > 0) ? options.toolFilterAllow : void 0,
|
|
3565
4061
|
toolFilterDeny: options.toolFilterDeny !== void 0 && (Array.isArray(options.toolFilterDeny) ? options.toolFilterDeny.length > 0 : String(options.toolFilterDeny).trim().length > 0) ? options.toolFilterDeny : void 0,
|
|
3566
4062
|
maxDepth: options.maxDepth,
|
|
3567
4063
|
allowKinds: options.allowKinds,
|
|
3568
|
-
firstTurnWord: typeof options.firstTurnWord === "string" && options.firstTurnWord.length > 0 ? options.firstTurnWord : void 0
|
|
4064
|
+
firstTurnWord: typeof options.firstTurnWord === "string" && options.firstTurnWord.length > 0 ? options.firstTurnWord : void 0,
|
|
4065
|
+
anchorTurn: typeof options.anchorTurn === "boolean" ? options.anchorTurn : void 0,
|
|
4066
|
+
anchorTurnText: typeof options.anchorTurnText === "string" ? options.anchorTurnText : void 0,
|
|
4067
|
+
deliberationGate: typeof options.deliberationGate === "boolean" ? options.deliberationGate : void 0,
|
|
4068
|
+
deliberationMinChars: Number.isSafeInteger(options.deliberationMinChars) ? options.deliberationMinChars : void 0,
|
|
4069
|
+
deliberationMaxGatesPerTurn: Number.isSafeInteger(options.deliberationMaxGatesPerTurn) ? options.deliberationMaxGatesPerTurn : void 0,
|
|
4070
|
+
cotDrip: typeof options.cotDrip === "boolean" ? options.cotDrip : void 0,
|
|
4071
|
+
cotDripEvery: Number.isSafeInteger(options.cotDripEvery) ? options.cotDripEvery : void 0,
|
|
4072
|
+
cotDripMaxPerTurn: Number.isSafeInteger(options.cotDripMaxPerTurn) ? options.cotDripMaxPerTurn : void 0
|
|
3569
4073
|
};
|
|
3570
4074
|
}
|
|
3571
4075
|
/** 模型参数(思维程度/温度/输出上限)→ agent-request 提示词配置(官方 LlmCallConfig patch 浅合并)。
|
|
@@ -3609,6 +4113,8 @@ function writePreset(prompt, options) {
|
|
|
3609
4113
|
const presetDir = options.presetDir.trim().length > 0 ? options.presetDir : DEFAULT_PRESET_DIR;
|
|
3610
4114
|
const templateName = typeof options.presetTemplate === "string" && options.presetTemplate.trim().length > 0 ? options.presetTemplate.trim() : "anchored";
|
|
3611
4115
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(templateName)) throw new Error(`invalid presetTemplate ${JSON.stringify(templateName)}: must match official agent-presets id /^[a-z0-9][a-z0-9-]*$/`);
|
|
4116
|
+
const outputId = typeof options.outputId === "string" && options.outputId.trim().length > 0 ? options.outputId.trim() : templateName;
|
|
4117
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(outputId)) throw new Error(`invalid outputId ${JSON.stringify(outputId)}: must match official agent-presets id /^[a-z0-9][a-z0-9-]*$/`);
|
|
3612
4118
|
const templateDir = resolvePresetDir(templateName);
|
|
3613
4119
|
const spec = loadPresetSpec(templateDir);
|
|
3614
4120
|
const worldBook = spec.worldBook;
|
|
@@ -3638,8 +4144,7 @@ function writePreset(prompt, options) {
|
|
|
3638
4144
|
...keys.length > 0 ? { keys } : {},
|
|
3639
4145
|
...secondaryKeys.length > 0 ? { secondaryKeys } : {},
|
|
3640
4146
|
...entry.caseSensitive === true ? { caseSensitive: true } : {},
|
|
3641
|
-
...entry.wholeWords === true ? { wholeWords: true } : {}
|
|
3642
|
-
...entry.useRegex === true ? { useRegex: true } : {}
|
|
4147
|
+
...entry.wholeWords === true ? { wholeWords: true } : {}
|
|
3643
4148
|
}
|
|
3644
4149
|
});
|
|
3645
4150
|
}
|
|
@@ -3655,27 +4160,37 @@ function writePreset(prompt, options) {
|
|
|
3655
4160
|
}
|
|
3656
4161
|
const runtime = runtimeOf(options, prompt);
|
|
3657
4162
|
const params = resolvePresetParams(spec, runtime);
|
|
3658
|
-
const targetDir = join(presetDir,
|
|
4163
|
+
const targetDir = join(presetDir, outputId);
|
|
3659
4164
|
mkdirSync(presetDir, { recursive: true });
|
|
3660
|
-
const tmpDir = mkdtempSync(join(presetDir, `.${
|
|
4165
|
+
const tmpDir = mkdtempSync(join(presetDir, `.${outputId}.tmp-`));
|
|
3661
4166
|
const outDir = tmpDir;
|
|
3662
4167
|
try {
|
|
3663
4168
|
const overridesSrc = join(targetDir, "prompt-tool.overrides.yml");
|
|
3664
4169
|
if (existsSync(overridesSrc)) cpSync(overridesSrc, join(outDir, "prompt-tool.overrides.yml"), { force: true });
|
|
3665
4170
|
const composition = renderComposition(spec, runtime, templateDir);
|
|
3666
4171
|
assertCompositionArray(composition, spec);
|
|
3667
|
-
const subComposition = composition.replaceAll("./engine/", "../.engine/").replaceAll("../prompt-configs", `../${
|
|
4172
|
+
const subComposition = composition.replaceAll("./engine/", "../.engine/").replaceAll("../prompt-configs", `../${outputId}/prompt-configs`);
|
|
3668
4173
|
writeFileSync(join(outDir, "agent.cordis.yml"), subComposition, "utf8");
|
|
4174
|
+
const aliasSuffix = "(旧会话兼容)";
|
|
4175
|
+
const aliasName = options.aliasOf === true ? typeof spec.name === "string" && spec.name.length > 0 ? spec.name : outputId : void 0;
|
|
4176
|
+
const displayName = aliasName !== void 0 && !aliasName.endsWith(aliasSuffix) ? aliasName + aliasSuffix : aliasName;
|
|
3669
4177
|
const meta = spec.meta !== null && typeof spec.meta === "object" ? spec.meta : {};
|
|
3670
|
-
const
|
|
4178
|
+
const sourceYamlPath = options.aliasOf === true ? join(presetDir, templateName, "preset.yml") : join(targetDir, "preset.yml");
|
|
4179
|
+
const existingPresetYaml = existsSync(sourceYamlPath) ? readFileSync(sourceYamlPath, "utf8") : void 0;
|
|
3671
4180
|
if (existingPresetYaml !== void 0 && existingPresetYaml.trim().length > 0) {
|
|
3672
4181
|
const doc = parseDocument(existingPresetYaml, { logLevel: "silent" });
|
|
3673
4182
|
doc.setIn(["order"], options.presetOrder);
|
|
3674
|
-
if (typeof spec.name === "string" && spec.name.length > 0) doc.setIn(["name"], spec.name);
|
|
4183
|
+
if (typeof spec.name === "string" && spec.name.length > 0) doc.setIn(["name"], displayName ?? spec.name);
|
|
4184
|
+
if (options.aliasOf === true) doc.setIn(["id"], outputId);
|
|
3675
4185
|
if (typeof spec.description === "string" && spec.description.length > 0) doc.setIn(["description"], spec.description);
|
|
3676
4186
|
if (Object.keys(meta).length > 0) doc.setIn(["meta"], meta);
|
|
3677
4187
|
writeFileSync(join(outDir, "preset.yml"), doc.toString(), "utf8");
|
|
3678
|
-
} else writeFileSync(join(outDir, "preset.yml"), stringify({
|
|
4188
|
+
} else writeFileSync(join(outDir, "preset.yml"), stringify(displayName !== void 0 ? {
|
|
4189
|
+
...meta,
|
|
4190
|
+
id: outputId,
|
|
4191
|
+
name: displayName,
|
|
4192
|
+
order: options.presetOrder
|
|
4193
|
+
} : {
|
|
3679
4194
|
...meta,
|
|
3680
4195
|
order: options.presetOrder
|
|
3681
4196
|
}) + "\n", "utf8");
|
|
@@ -3742,7 +4257,7 @@ function writePreset(prompt, options) {
|
|
|
3742
4257
|
next.params = {
|
|
3743
4258
|
...next.params,
|
|
3744
4259
|
useCustom: params.firstTurnCustom === true,
|
|
3745
|
-
|
|
4260
|
+
text: asString(params.firstTurnText),
|
|
3746
4261
|
buildPattern: asString(params.buildPattern),
|
|
3747
4262
|
complexPattern: asString(params.complexPattern),
|
|
3748
4263
|
firstTurnBuild: asString(params.firstTurnBuild),
|
|
@@ -3750,14 +4265,15 @@ function writePreset(prompt, options) {
|
|
|
3750
4265
|
firstTurnDeep: asString(params.firstTurnDeep)
|
|
3751
4266
|
};
|
|
3752
4267
|
} else if (next.id === "router-guide") {
|
|
3753
|
-
|
|
3754
|
-
|
|
4268
|
+
const guideEnabled = params.guideEnabled === void 0 ? params.firstTurnAnchor === true : params.guideEnabled === true;
|
|
4269
|
+
next.enabled = guideEnabled;
|
|
4270
|
+
const useCustom = guideEnabled && params.guideCustom === true;
|
|
3755
4271
|
next.modelScope = useCustom ? "all" : "flash";
|
|
3756
4272
|
next.params = {
|
|
3757
4273
|
...next.params,
|
|
3758
4274
|
useCustom,
|
|
3759
4275
|
text: asString(params.guideText),
|
|
3760
|
-
|
|
4276
|
+
complexPattern: asString(params.complexPattern) || asString(params.guideComplexPattern),
|
|
3761
4277
|
guideWeak: asString(params.guideWeak),
|
|
3762
4278
|
guideDeep: asString(params.guideDeep)
|
|
3763
4279
|
};
|
|
@@ -3769,10 +4285,10 @@ function writePreset(prompt, options) {
|
|
|
3769
4285
|
const presetVariables = {};
|
|
3770
4286
|
for (const [key, value] of Object.entries(spec.params ?? {})) {
|
|
3771
4287
|
if (PARAM_KEYS.has(key)) continue;
|
|
3772
|
-
|
|
3773
|
-
|
|
4288
|
+
if (value === void 0 || value === null) continue;
|
|
4289
|
+
presetVariables[key] = String(value);
|
|
3774
4290
|
}
|
|
3775
|
-
for (const [key, value] of Object.entries(spec.variables ?? {})) if (typeof value === "string"
|
|
4291
|
+
for (const [key, value] of Object.entries(spec.variables ?? {})) if (typeof value === "string") presetVariables[key] = value;
|
|
3776
4292
|
const variablesEnabled = spec.variablesEnabled !== false;
|
|
3777
4293
|
const presetVariableKeys = new Set(Object.keys(presetVariables));
|
|
3778
4294
|
if (variablesEnabled && presetVariableKeys.size > 0) writeFileSync(join(promptConfigsDir, "variables.yml"), stringify(presetVariables), "utf8");
|
|
@@ -3781,15 +4297,30 @@ function writePreset(prompt, options) {
|
|
|
3781
4297
|
delete config.text;
|
|
3782
4298
|
config.texts = [];
|
|
3783
4299
|
config.enabled = params.injectPrompt !== false && prompt.trim().length > 0;
|
|
4300
|
+
const explicitWord = asString(config.params?.firstTurnWord ?? params.firstTurnWord, "");
|
|
4301
|
+
const signalWord = (text) => {
|
|
4302
|
+
const sentence = /the exact sentence:\s*([A-Za-z]+)/i.exec(text);
|
|
4303
|
+
if (sentence !== null && sentence[1] !== void 0) return sentence[1].toLowerCase();
|
|
4304
|
+
const first = /^\P{L}*([\p{L}]+)/u.exec(text.trim());
|
|
4305
|
+
return first !== null && first[1] !== void 0 ? first[1].toLowerCase() : void 0;
|
|
4306
|
+
};
|
|
4307
|
+
const derivedWords = [...new Set([
|
|
4308
|
+
signalWord(asString(params.firstTurnText)),
|
|
4309
|
+
signalWord(asString(params.firstTurnBuild)),
|
|
4310
|
+
signalWord(asString(params.firstTurnInspect)),
|
|
4311
|
+
signalWord(asString(params.firstTurnDeep))
|
|
4312
|
+
].filter((word) => word !== void 0))];
|
|
4313
|
+
const anchorWords = explicitWord.length > 0 ? [explicitWord] : derivedWords;
|
|
3784
4314
|
config.params = {
|
|
3785
4315
|
...config.params,
|
|
3786
4316
|
text: prompt,
|
|
3787
|
-
firstTurnWord:
|
|
4317
|
+
firstTurnWord: explicitWord.length > 0 ? explicitWord : anchorWords[0] ?? "we",
|
|
4318
|
+
anchorWords
|
|
3788
4319
|
};
|
|
3789
4320
|
}
|
|
3790
4321
|
if (config.fill === "instruction-hint") config.params = {
|
|
3791
4322
|
...config.params,
|
|
3792
|
-
agentsInstructionPath: `../${
|
|
4323
|
+
agentsInstructionPath: `../${outputId}/agents-instruction.md`,
|
|
3793
4324
|
...options.injectAgentsPrompt === true ? { text: asString(options.agentsInstructionText) } : {}
|
|
3794
4325
|
};
|
|
3795
4326
|
if (!variablesEnabled && presetVariableKeys.size > 0) {
|
|
@@ -3803,6 +4334,18 @@ function writePreset(prompt, options) {
|
|
|
3803
4334
|
}
|
|
3804
4335
|
writeFileSync(join(promptConfigsDir, `${String(index * 10).padStart(2, "0")}-${config.id}.yml`), renderPromptConfigYaml(config), "utf8");
|
|
3805
4336
|
}
|
|
4337
|
+
const customToolsDir = join(outDir, "custom-tools");
|
|
4338
|
+
rmSync(customToolsDir, {
|
|
4339
|
+
recursive: true,
|
|
4340
|
+
force: true
|
|
4341
|
+
});
|
|
4342
|
+
mkdirSync(customToolsDir, { recursive: true });
|
|
4343
|
+
const customTools = Array.isArray(spec.customTools) ? spec.customTools : [];
|
|
4344
|
+
for (const [index, tool] of customTools.entries()) {
|
|
4345
|
+
if (tool === null || typeof tool !== "object" || Array.isArray(tool) || typeof tool.id !== "string" || String(tool.id).length === 0) continue;
|
|
4346
|
+
const toolId = String(tool.id);
|
|
4347
|
+
writeFileSync(join(customToolsDir, `${String(index + 1).padStart(2, "0")}-${toolId}.yml`), stringify(tool, { lineWidth: 0 }), "utf8");
|
|
4348
|
+
}
|
|
3806
4349
|
for (const legacy of spec.legacyCleanup ?? []) rmSync(join(outDir, legacy), { force: true });
|
|
3807
4350
|
const agentsInstructionPath = join(outDir, "agents-instruction.md");
|
|
3808
4351
|
if (typeof options.agentsInstructionText === "string" && options.agentsInstructionText.trim().length > 0) writeFileSync(agentsInstructionPath, options.agentsInstructionText, "utf8");
|
|
@@ -3855,9 +4398,10 @@ function writePreset(prompt, options) {
|
|
|
3855
4398
|
function migrateLegacyLayout(presetRoot, legacyUserPresets) {
|
|
3856
4399
|
const legacyContainer = join(presetRoot, "prompt-tool");
|
|
3857
4400
|
const sharedEngine = join(presetRoot, ".engine");
|
|
4401
|
+
const isLegacyContainer = existsSync(legacyContainer) && !existsSync(join(legacyContainer, "preset.yml"));
|
|
3858
4402
|
let migrated = false;
|
|
3859
4403
|
const legacyEngine = join(legacyContainer, "engine");
|
|
3860
|
-
if (existsSync(legacyEngine) && !existsSync(sharedEngine)) try {
|
|
4404
|
+
if (isLegacyContainer && existsSync(legacyEngine) && !existsSync(sharedEngine)) try {
|
|
3861
4405
|
mkdirSync(presetRoot, { recursive: true });
|
|
3862
4406
|
renameSync(legacyEngine, sharedEngine);
|
|
3863
4407
|
migrated = true;
|
|
@@ -3884,7 +4428,7 @@ function migrateLegacyLayout(presetRoot, legacyUserPresets) {
|
|
|
3884
4428
|
}
|
|
3885
4429
|
})();
|
|
3886
4430
|
const stamp = Date.now().toString(36);
|
|
3887
|
-
if (existsSync(sharedEngine) && existsSync(legacyContainer)) try {
|
|
4431
|
+
if (isLegacyContainer && existsSync(sharedEngine) && existsSync(legacyContainer)) try {
|
|
3888
4432
|
renameSync(legacyContainer, join(presetRoot, `prompt-tool.bak-${stamp}`));
|
|
3889
4433
|
migrated = true;
|
|
3890
4434
|
} catch {}
|
|
@@ -3900,6 +4444,43 @@ function normalizePresetRootDir(presetDir, presetRoot, legacyContainer) {
|
|
|
3900
4444
|
if (presetDir.endsWith("/prompt-tool") || presetDir.endsWith("\\prompt-tool")) return presetRoot;
|
|
3901
4445
|
return presetDir;
|
|
3902
4446
|
}
|
|
4447
|
+
/**
|
|
4448
|
+
* 旧版参数覆盖文件(prompt-tool.overrides.yml)→ 激活预设 preset.yml 一次性退役迁移。
|
|
4449
|
+
* 旧写入路径(阶段 1)把引擎参数写进该文件;阶段 2 收敛为 savePresetParams 直接写
|
|
4450
|
+
* preset.yml 并删除覆盖文件。本函数仅处理存量升级场景:
|
|
4451
|
+
* - 键白名单 = PARAM_KEYS(含 guideEnabled/injectPrompt/usePtcMode,补上旧手写列表
|
|
4452
|
+
* 缺失的 3 键;promptConfigs 不走覆盖文件,排除);
|
|
4453
|
+
* - 空值键按 savePresetParams 语义删除(回落模板/引擎默认);
|
|
4454
|
+
* - 成功 = preset.yml 已写入且覆盖文件已删(savePresetParams 删除);失败(preset.yml
|
|
4455
|
+
* 缺失等)抛错且覆盖文件保留,调用方下次重建重试。
|
|
4456
|
+
* 返回 true = 已迁移(调用方需重读参数);false = 无文件/解析失败/无有效键(文件已尽力清理)。
|
|
4457
|
+
*/
|
|
4458
|
+
function migrateParamOverridesFile(presetRoot, templateName) {
|
|
4459
|
+
const file = join(presetRoot, templateName, "prompt-tool.overrides.yml");
|
|
4460
|
+
let raw;
|
|
4461
|
+
try {
|
|
4462
|
+
raw = readFileSync(file, "utf8");
|
|
4463
|
+
} catch {
|
|
4464
|
+
return false;
|
|
4465
|
+
}
|
|
4466
|
+
if (raw.trim().length === 0) {
|
|
4467
|
+
rmSync(file, { force: true });
|
|
4468
|
+
return false;
|
|
4469
|
+
}
|
|
4470
|
+
let parsed;
|
|
4471
|
+
try {
|
|
4472
|
+
parsed = parse(raw, { logLevel: "silent" });
|
|
4473
|
+
} catch {
|
|
4474
|
+
return false;
|
|
4475
|
+
}
|
|
4476
|
+
const valid = Object.fromEntries(Object.entries(parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {}).filter(([key]) => PARAM_KEYS.has(key) && key !== "promptConfigs"));
|
|
4477
|
+
if (Object.keys(valid).length === 0) {
|
|
4478
|
+
rmSync(file, { force: true });
|
|
4479
|
+
return false;
|
|
4480
|
+
}
|
|
4481
|
+
savePresetParams(presetRoot, templateName, valid, void 0);
|
|
4482
|
+
return true;
|
|
4483
|
+
}
|
|
3903
4484
|
//#endregion
|
|
3904
4485
|
//#region src/index.ts
|
|
3905
4486
|
const name = "prompt-tool";
|
|
@@ -3930,6 +4511,7 @@ function warn(ctx, message) {
|
|
|
3930
4511
|
} catch {}
|
|
3931
4512
|
}
|
|
3932
4513
|
function apply(ctx, configIn) {
|
|
4514
|
+
const hadLegacyContainer = existsSync(join(DEFAULT_PRESET_DIR, "prompt-tool")) && !existsSync(join(DEFAULT_PRESET_DIR, "prompt-tool", "preset.yml"));
|
|
3933
4515
|
let legacyMigrated = false;
|
|
3934
4516
|
try {
|
|
3935
4517
|
legacyMigrated = migrateLegacyLayout(DEFAULT_PRESET_DIR, LEGACY_USER_PRESETS_DIR);
|
|
@@ -3972,6 +4554,7 @@ function apply(ctx, configIn) {
|
|
|
3972
4554
|
runtime.firstTurnCustom = params.firstTurnCustom === true;
|
|
3973
4555
|
runtime.guideText = asString(params.guideText);
|
|
3974
4556
|
runtime.guideCustom = params.guideCustom === true;
|
|
4557
|
+
runtime.guideEnabled = typeof params.guideEnabled === "boolean" ? params.guideEnabled : void 0;
|
|
3975
4558
|
runtime.modelProvider = asString(params.modelProvider);
|
|
3976
4559
|
runtime.modelName = asString(params.modelName);
|
|
3977
4560
|
runtime.subagentModelProvider = asString(params.subagentModelProvider);
|
|
@@ -3983,9 +4566,8 @@ function apply(ctx, configIn) {
|
|
|
3983
4566
|
runtime.subagentTemperature = asString(params.subagentTemperature);
|
|
3984
4567
|
runtime.subagentMaxTokens = asString(params.subagentMaxTokens);
|
|
3985
4568
|
runtime.bootstrapMaxTokens = Number.isSafeInteger(params.bootstrapMaxTokens) && params.bootstrapMaxTokens >= 0 ? params.bootstrapMaxTokens : 0;
|
|
3986
|
-
runtime.usePtcMode = params.usePtcMode
|
|
4569
|
+
runtime.usePtcMode = typeof params.usePtcMode === "boolean" ? params.usePtcMode : void 0;
|
|
3987
4570
|
runtime.injectPrompt = params.injectPrompt !== false;
|
|
3988
|
-
runtime.subagentPersona = asString(params.subagentPersona) || void 0;
|
|
3989
4571
|
runtime.toolFilterAllow = params.toolFilterAllow;
|
|
3990
4572
|
runtime.toolFilterDeny = params.toolFilterDeny;
|
|
3991
4573
|
runtime.maxDepth = params.maxDepth;
|
|
@@ -3993,6 +4575,29 @@ function apply(ctx, configIn) {
|
|
|
3993
4575
|
runtime.firstTurnWord = asString(params.firstTurnWord) || void 0;
|
|
3994
4576
|
runtime.promptConfigs = Array.isArray(spec.promptConfigs) ? spec.promptConfigs : [];
|
|
3995
4577
|
};
|
|
4578
|
+
let legacyAliasHandledThisRun = readPluginState().legacyAliasHandled === true;
|
|
4579
|
+
const ensureLegacyAliasOnce = (presetPrompt, options) => {
|
|
4580
|
+
if (legacyAliasHandledThisRun) return;
|
|
4581
|
+
legacyAliasHandledThisRun = true;
|
|
4582
|
+
const aliasFile = join(runtime.presetDir, "prompt-tool", "preset.yml");
|
|
4583
|
+
try {
|
|
4584
|
+
if (!existsSync(aliasFile) && hadLegacyContainer) {
|
|
4585
|
+
writePreset(presetPrompt, {
|
|
4586
|
+
...options,
|
|
4587
|
+
outputId: "prompt-tool",
|
|
4588
|
+
aliasOf: true
|
|
4589
|
+
});
|
|
4590
|
+
warn(ctx, "prompt-tool: 已创建一次性旧会话兼容快照 prompt-tool;日常切换不再重建");
|
|
4591
|
+
}
|
|
4592
|
+
writePluginState({
|
|
4593
|
+
...readPluginState(),
|
|
4594
|
+
legacyAliasHandled: true
|
|
4595
|
+
});
|
|
4596
|
+
} catch (error) {
|
|
4597
|
+
legacyAliasHandledThisRun = false;
|
|
4598
|
+
warn(ctx, `prompt-tool: 创建旧会话兼容快照失败(下次重建重试):${error instanceof Error ? error.message : String(error)}`);
|
|
4599
|
+
}
|
|
4600
|
+
};
|
|
3996
4601
|
/** 重建生成目录(文本/组合/引擎/提示词配置);writePreset 关闭时移除旧目录。 */
|
|
3997
4602
|
const rebuildPreset = () => {
|
|
3998
4603
|
reloadPresetParams();
|
|
@@ -4005,6 +4610,7 @@ function apply(ctx, configIn) {
|
|
|
4005
4610
|
firstTurnCustom: runtime.firstTurnCustom,
|
|
4006
4611
|
guideText: runtime.guideText,
|
|
4007
4612
|
guideCustom: runtime.guideCustom,
|
|
4613
|
+
guideEnabled: runtime.guideEnabled,
|
|
4008
4614
|
injectPrompt: runtime.injectPrompt,
|
|
4009
4615
|
modelProvider: runtime.modelProvider,
|
|
4010
4616
|
modelName: runtime.modelName,
|
|
@@ -4016,7 +4622,6 @@ function apply(ctx, configIn) {
|
|
|
4016
4622
|
subagentReasoningEffort: runtime.subagentReasoningEffort,
|
|
4017
4623
|
subagentTemperature: runtime.subagentTemperature,
|
|
4018
4624
|
subagentMaxTokens: runtime.subagentMaxTokens,
|
|
4019
|
-
subagentPersona: runtime.subagentPersona,
|
|
4020
4625
|
toolFilterAllow: runtime.toolFilterAllow,
|
|
4021
4626
|
toolFilterDeny: runtime.toolFilterDeny,
|
|
4022
4627
|
maxDepth: runtime.maxDepth,
|
|
@@ -4047,6 +4652,7 @@ function apply(ctx, configIn) {
|
|
|
4047
4652
|
}
|
|
4048
4653
|
}
|
|
4049
4654
|
writePreset(presetPrompt, options);
|
|
4655
|
+
ensureLegacyAliasOnce(presetPrompt, options);
|
|
4050
4656
|
} else {
|
|
4051
4657
|
let cleaned = 0;
|
|
4052
4658
|
for (const preset of listPresets()) {
|
|
@@ -4054,6 +4660,7 @@ function apply(ctx, configIn) {
|
|
|
4054
4660
|
for (const name of [
|
|
4055
4661
|
"agent.cordis.yml",
|
|
4056
4662
|
"prompt-configs",
|
|
4663
|
+
"custom-tools",
|
|
4057
4664
|
"preset.md",
|
|
4058
4665
|
"agents.md",
|
|
4059
4666
|
"agents-instruction.md",
|
|
@@ -4082,49 +4689,15 @@ function apply(ctx, configIn) {
|
|
|
4082
4689
|
return true;
|
|
4083
4690
|
}
|
|
4084
4691
|
};
|
|
4085
|
-
/**
|
|
4692
|
+
/** 旧版参数覆盖文件(prompt-tool.overrides.yml)一次性退役迁移:白名单
|
|
4693
|
+
* (PARAM_KEYS,含 guideEnabled/injectPrompt/usePtcMode)过滤后并入 preset.yml。
|
|
4694
|
+
* 实现收敛到 migrateParamOverridesFile(migration.ts 单一权威)。 */
|
|
4086
4695
|
const applyParamOverrides = () => {
|
|
4087
|
-
const raw = readGeneratedContent(activePresetDir(), "prompt-tool.overrides.yml");
|
|
4088
|
-
if (raw.length === 0) return;
|
|
4089
|
-
let overrides;
|
|
4090
4696
|
try {
|
|
4091
|
-
|
|
4092
|
-
overrides = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
4697
|
+
if (migrateParamOverridesFile(runtime.presetDir, runtime.presetTemplate)) reloadPresetParams();
|
|
4093
4698
|
} catch {
|
|
4094
|
-
|
|
4699
|
+
warn(ctx, "prompt-tool: prompt-tool.overrides.yml 迁移到 preset.yml 失败(下次重建重试)");
|
|
4095
4700
|
}
|
|
4096
|
-
if (typeof overrides.firstTurnAnchor === "boolean") runtime.firstTurnAnchor = overrides.firstTurnAnchor;
|
|
4097
|
-
if (typeof overrides.firstTurnCustom === "boolean") runtime.firstTurnCustom = overrides.firstTurnCustom;
|
|
4098
|
-
if (typeof overrides.firstTurnText === "string") runtime.firstTurnText = overrides.firstTurnText;
|
|
4099
|
-
if (typeof overrides.guideCustom === "boolean") runtime.guideCustom = overrides.guideCustom;
|
|
4100
|
-
if (typeof overrides.guideText === "string") runtime.guideText = overrides.guideText;
|
|
4101
|
-
if (typeof overrides.modelProvider === "string") runtime.modelProvider = overrides.modelProvider;
|
|
4102
|
-
if (typeof overrides.modelName === "string") runtime.modelName = overrides.modelName;
|
|
4103
|
-
if (typeof overrides.subagentModelProvider === "string") runtime.subagentModelProvider = overrides.subagentModelProvider;
|
|
4104
|
-
if (typeof overrides.subagentModelName === "string") runtime.subagentModelName = overrides.subagentModelName;
|
|
4105
|
-
if (typeof overrides.modelReasoningEffort === "string") runtime.modelReasoningEffort = overrides.modelReasoningEffort;
|
|
4106
|
-
if (typeof overrides.modelTemperature === "string") runtime.modelTemperature = overrides.modelTemperature;
|
|
4107
|
-
if (typeof overrides.modelMaxTokens === "string") runtime.modelMaxTokens = overrides.modelMaxTokens;
|
|
4108
|
-
if (typeof overrides.subagentReasoningEffort === "string") runtime.subagentReasoningEffort = overrides.subagentReasoningEffort;
|
|
4109
|
-
if (typeof overrides.subagentTemperature === "string") runtime.subagentTemperature = overrides.subagentTemperature;
|
|
4110
|
-
if (typeof overrides.subagentMaxTokens === "string") runtime.subagentMaxTokens = overrides.subagentMaxTokens;
|
|
4111
|
-
if (typeof overrides.subagentPersona === "string") runtime.subagentPersona = overrides.subagentPersona;
|
|
4112
|
-
if (typeof overrides.firstTurnWord === "string") runtime.firstTurnWord = overrides.firstTurnWord;
|
|
4113
|
-
if (typeof overrides.bootstrapMaxTokens === "number") runtime.bootstrapMaxTokens = overrides.bootstrapMaxTokens;
|
|
4114
|
-
const listOf = (value) => {
|
|
4115
|
-
if (typeof value === "string") return value;
|
|
4116
|
-
if (Array.isArray(value)) return value.filter((item) => typeof item === "string");
|
|
4117
|
-
};
|
|
4118
|
-
const tfAllow = listOf(overrides.toolFilterAllow);
|
|
4119
|
-
if (tfAllow !== void 0) runtime.toolFilterAllow = tfAllow;
|
|
4120
|
-
const tfDeny = listOf(overrides.toolFilterDeny);
|
|
4121
|
-
if (tfDeny !== void 0) runtime.toolFilterDeny = tfDeny;
|
|
4122
|
-
const kinds = listOf(overrides.allowKinds);
|
|
4123
|
-
if (kinds !== void 0 && (typeof kinds === "string" ? kinds.trim().length > 0 : kinds.length > 0)) runtime.allowKinds = kinds;
|
|
4124
|
-
const depth = overrides.maxDepth;
|
|
4125
|
-
if (depth === "provider-managed") runtime.maxDepth = "provider-managed";
|
|
4126
|
-
else if (typeof depth === "number" && Number.isSafeInteger(depth) && depth >= 0) runtime.maxDepth = depth;
|
|
4127
|
-
else if (typeof depth === "string" && depth.length > 0) runtime.maxDepth = depth;
|
|
4128
4701
|
};
|
|
4129
4702
|
const legacySkillsDir = typeof config.skillsDir === "string" && config.skillsDir.length > 0 && config.skillsDir !== DEFAULT_SKILLS_DIR ? config.skillsDir : "";
|
|
4130
4703
|
const userSkillsDirs = Array.isArray(config.skillsDirs) && config.skillsDirs.length > 0 ? config.skillsDirs.filter((dir) => typeof dir === "string" && dir.trim().length > 0) : legacySkillsDir.length > 0 ? [legacySkillsDir] : [];
|
|
@@ -4238,15 +4811,15 @@ function apply(ctx, configIn) {
|
|
|
4238
4811
|
}
|
|
4239
4812
|
};
|
|
4240
4813
|
});
|
|
4241
|
-
registerSettingsBridge(ctx, NS, getModelsState, () => ({
|
|
4814
|
+
const settingsBridge = registerSettingsBridge(ctx, NS, getModelsState, () => ({
|
|
4242
4815
|
activeSkillsDirs,
|
|
4243
4816
|
skillCatalog
|
|
4244
|
-
}), () => "", (
|
|
4817
|
+
}), () => "", () => {
|
|
4245
4818
|
skillCatalog = catalogOf(readAllSkillsChecked());
|
|
4246
4819
|
cachedSkills.invalidate();
|
|
4247
4820
|
invalidateSkills?.();
|
|
4248
|
-
}, () => activePresetDir(), (
|
|
4249
|
-
if (scope === "preset") current = readGeneratedContent(activePresetDir(), "preset.md");
|
|
4821
|
+
}, () => activePresetDir(), (scopes) => {
|
|
4822
|
+
for (const scope of scopes) if (scope === "preset") current = readGeneratedContent(activePresetDir(), "preset.md");
|
|
4250
4823
|
else currentAgents = readGeneratedContent(activePresetDir(), "agents.md");
|
|
4251
4824
|
try {
|
|
4252
4825
|
rebuildPreset();
|
|
@@ -4283,6 +4856,7 @@ function apply(ctx, configIn) {
|
|
|
4283
4856
|
firstTurnCustom: initialParams.firstTurnCustom === true,
|
|
4284
4857
|
guideText: asString(initialParams.guideText),
|
|
4285
4858
|
guideCustom: initialParams.guideCustom === true,
|
|
4859
|
+
guideEnabled: typeof initialParams.guideEnabled === "boolean" ? initialParams.guideEnabled : void 0,
|
|
4286
4860
|
injectPrompt: initialParams.injectPrompt !== false,
|
|
4287
4861
|
modelProvider: asString(initialParams.modelProvider),
|
|
4288
4862
|
modelName: asString(initialParams.modelName),
|
|
@@ -4295,8 +4869,7 @@ function apply(ctx, configIn) {
|
|
|
4295
4869
|
subagentTemperature: asString(initialParams.subagentTemperature),
|
|
4296
4870
|
subagentMaxTokens: asString(initialParams.subagentMaxTokens),
|
|
4297
4871
|
bootstrapMaxTokens: Number.isSafeInteger(initialParams.bootstrapMaxTokens) && initialParams.bootstrapMaxTokens >= 0 ? initialParams.bootstrapMaxTokens : 0,
|
|
4298
|
-
usePtcMode: initialParams.usePtcMode
|
|
4299
|
-
subagentPersona: asString(initialParams.subagentPersona) || void 0,
|
|
4872
|
+
usePtcMode: typeof initialParams.usePtcMode === "boolean" ? initialParams.usePtcMode : void 0,
|
|
4300
4873
|
toolFilterAllow: initialParams.toolFilterAllow,
|
|
4301
4874
|
toolFilterDeny: initialParams.toolFilterDeny,
|
|
4302
4875
|
maxDepth: initialParams.maxDepth,
|
|
@@ -4309,9 +4882,21 @@ function apply(ctx, configIn) {
|
|
|
4309
4882
|
fallbackText: config.fallbackText,
|
|
4310
4883
|
promptConfigs: Array.isArray(initialSpec?.promptConfigs) ? initialSpec.promptConfigs : []
|
|
4311
4884
|
};
|
|
4885
|
+
const agentPresetsNs = "agent-presets";
|
|
4312
4886
|
let hostSettingsService;
|
|
4313
|
-
|
|
4314
|
-
|
|
4887
|
+
const readHostDefault = (value) => {
|
|
4888
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
4889
|
+
const candidate = value.default;
|
|
4890
|
+
return typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
|
|
4891
|
+
};
|
|
4892
|
+
const managedPresetExists = (id) => {
|
|
4893
|
+
try {
|
|
4894
|
+
return listPresets().some((preset) => preset.id === id);
|
|
4895
|
+
} catch {
|
|
4896
|
+
return false;
|
|
4897
|
+
}
|
|
4898
|
+
};
|
|
4899
|
+
const syncHostDefault = (_reason) => {
|
|
4315
4900
|
const s = hostSettingsService;
|
|
4316
4901
|
if (s === void 0) return;
|
|
4317
4902
|
const template = runtime.presetTemplate;
|
|
@@ -4319,17 +4904,31 @@ function apply(ctx, configIn) {
|
|
|
4319
4904
|
warn(ctx, `prompt-tool: 预设 id ${JSON.stringify(template)} 不符合官方 agent-presets 命名(^[a-z0-9][a-z0-9-]*$),跳过宿主 default 同步;请将预设目录改名为合法 id`);
|
|
4320
4905
|
return;
|
|
4321
4906
|
}
|
|
4322
|
-
if (
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
value: template
|
|
4329
|
-
}]);
|
|
4330
|
-
} catch (error) {
|
|
4907
|
+
if (readHostDefault(s.get(agentPresetsNs)) === template) return;
|
|
4908
|
+
s.mutate(agentPresetsNs, [{
|
|
4909
|
+
op: "set",
|
|
4910
|
+
path: ["default"],
|
|
4911
|
+
value: template
|
|
4912
|
+
}]).catch((error) => {
|
|
4331
4913
|
warn(ctx, `prompt-tool: 同步宿主 agent-presets default 失败:${error instanceof Error ? error.message : String(error)}`);
|
|
4914
|
+
});
|
|
4915
|
+
};
|
|
4916
|
+
const syncTemplateFromHostDefault = (value) => {
|
|
4917
|
+
const s = hostSettingsService;
|
|
4918
|
+
if (s === void 0) return;
|
|
4919
|
+
const template = readHostDefault(value ?? s.get(agentPresetsNs));
|
|
4920
|
+
if (template === void 0 || template === runtime.presetTemplate) return;
|
|
4921
|
+
if (!managedPresetExists(template)) {
|
|
4922
|
+
warn(ctx, `prompt-tool: 官方 agent-presets default 已切换为 ${JSON.stringify(template)},但该预设不在提示词工具管理目录中,跳过反向同步`);
|
|
4923
|
+
return;
|
|
4332
4924
|
}
|
|
4925
|
+
s.mutate(NS, [{
|
|
4926
|
+
op: "set",
|
|
4927
|
+
path: ["presetTemplate"],
|
|
4928
|
+
value: template
|
|
4929
|
+
}]).catch((error) => {
|
|
4930
|
+
warn(ctx, `prompt-tool: 跟随官方 agent-presets default 失败:${error instanceof Error ? error.message : String(error)}`);
|
|
4931
|
+
});
|
|
4333
4932
|
};
|
|
4334
4933
|
installSubagentModelRoute(ctx, () => runtime.subagentModelProvider.length > 0 && runtime.subagentModelName.length > 0, () => runtime.subagentModelProvider, () => runtime.subagentModelName);
|
|
4335
4934
|
const applyDefaultModel = installDefaultModelRoute(ctx, () => runtime.modelProvider.length > 0 && runtime.modelName.length > 0, () => runtime.modelProvider, () => runtime.modelName, () => runtime.modelReasoningEffort);
|
|
@@ -4352,14 +4951,10 @@ function apply(ctx, configIn) {
|
|
|
4352
4951
|
presetTemplate: runtime.presetTemplate
|
|
4353
4952
|
});
|
|
4354
4953
|
registerTuiCommand(ctx, NS, () => currentSource(), getModelsState, () => listAdvertisedModels(ctx), () => activePresetDir(), (key, value) => {
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
rebuildPreset();
|
|
4360
|
-
} catch (error) {
|
|
4361
|
-
warn(ctx, `prompt-tool: TUI 参数保存失败:${error instanceof Error ? error.message : String(error)}`);
|
|
4362
|
-
}
|
|
4954
|
+
if (key === "promptConfigs") savePresetParams(runtime.presetDir, runtime.presetTemplate, void 0, Array.isArray(value) ? value : void 0);
|
|
4955
|
+
else savePresetParams(runtime.presetDir, runtime.presetTemplate, { [key]: value }, void 0);
|
|
4956
|
+
reloadPresetParams();
|
|
4957
|
+
rebuildPreset();
|
|
4363
4958
|
});
|
|
4364
4959
|
/**
|
|
4365
4960
|
* 物化导入的预设:用其自身 preset.yml(spec 参数 + modules + promptConfigs)渲染
|
|
@@ -4381,7 +4976,6 @@ function apply(ctx, configIn) {
|
|
|
4381
4976
|
modelName: "",
|
|
4382
4977
|
subagentModelProvider: "",
|
|
4383
4978
|
subagentModelName: "",
|
|
4384
|
-
subagentPersona: "",
|
|
4385
4979
|
injectAgentsPrompt: false,
|
|
4386
4980
|
bootstrapMaxTokens: 0,
|
|
4387
4981
|
usePtcMode: false,
|
|
@@ -4454,51 +5048,44 @@ function apply(ctx, configIn) {
|
|
|
4454
5048
|
if (!residentAgentsWritten) warn(ctx, `prompt-tool: failed to remove resident rules block from ${runtime.residentAgentsPath}`);
|
|
4455
5049
|
}
|
|
4456
5050
|
rebuildPreset();
|
|
4457
|
-
if (presetTemplateChanged)
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
presetRoot: () => dirname(activePresetDir()),
|
|
4461
|
-
templateName: () => basename(activePresetDir()),
|
|
4462
|
-
rebuild: () => {
|
|
4463
|
-
try {
|
|
4464
|
-
rebuildPreset();
|
|
4465
|
-
} catch (error) {
|
|
4466
|
-
warn(ctx, `prompt-tool: character tool rebuild failed: ${String(error)}`);
|
|
4467
|
-
}
|
|
5051
|
+
if (presetTemplateChanged) {
|
|
5052
|
+
syncHostDefault("switch");
|
|
5053
|
+
applyBuiltinTools();
|
|
4468
5054
|
}
|
|
4469
|
-
}
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
5055
|
+
};
|
|
5056
|
+
let disposeBuiltinTools = [];
|
|
5057
|
+
const applyBuiltinTools = () => {
|
|
5058
|
+
for (const dispose of disposeBuiltinTools) dispose();
|
|
5059
|
+
disposeBuiltinTools = [];
|
|
5060
|
+
const push = (dispose) => {
|
|
5061
|
+
if (typeof dispose === "function") disposeBuiltinTools.push(dispose);
|
|
5062
|
+
};
|
|
5063
|
+
push(registerCharacterTools(ctx, {
|
|
5064
|
+
presetRoot: () => dirname(activePresetDir()),
|
|
5065
|
+
templateName: () => basename(activePresetDir()),
|
|
5066
|
+
rebuild: () => {
|
|
5067
|
+
try {
|
|
5068
|
+
rebuildPreset();
|
|
5069
|
+
} catch (error) {
|
|
5070
|
+
warn(ctx, `prompt-tool: character tool rebuild failed: ${String(error)}`);
|
|
5071
|
+
}
|
|
4478
5072
|
}
|
|
4479
|
-
}
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
base: () => settingsEntry,
|
|
4485
|
-
onRegistered: (scope) => {
|
|
4486
|
-
currentSource = () => scope.get();
|
|
4487
|
-
scope.watch(() => {
|
|
5073
|
+
}));
|
|
5074
|
+
push(registerWorldBookTools(ctx, {
|
|
5075
|
+
activeDir: () => activePresetDir(),
|
|
5076
|
+
presetRoot: () => dirname(activePresetDir()),
|
|
5077
|
+
rebuild: () => {
|
|
4488
5078
|
try {
|
|
4489
|
-
|
|
5079
|
+
rebuildPreset();
|
|
4490
5080
|
} catch (error) {
|
|
4491
|
-
warn(ctx, `prompt-tool:
|
|
5081
|
+
warn(ctx, `prompt-tool: world-book tool rebuild failed: ${String(error)}`);
|
|
4492
5082
|
}
|
|
4493
|
-
});
|
|
4494
|
-
try {
|
|
4495
|
-
applyState();
|
|
4496
|
-
} catch (error) {
|
|
4497
|
-
warn(ctx, `prompt-tool: applyState failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4498
5083
|
}
|
|
4499
|
-
}
|
|
4500
|
-
|
|
4501
|
-
}
|
|
5084
|
+
}));
|
|
5085
|
+
push(registerSessionVarTools(ctx));
|
|
5086
|
+
};
|
|
5087
|
+
applyBuiltinTools();
|
|
5088
|
+
const settingsEntry = currentSource();
|
|
4502
5089
|
/** 阶段 2 迁移:旧全局 settings 引擎参数 → 激活预设 preset.yml(一次性,state.paramsMigrated 后跳过)。
|
|
4503
5090
|
* 兼容旧版预设根内 .pt-params-migrated 标记:存在即视为已迁移,并迁入状态文件后删除。 */
|
|
4504
5091
|
const migrateSettingsParamsToPreset = (sctx) => {
|
|
@@ -4560,20 +5147,32 @@ function apply(ctx, configIn) {
|
|
|
4560
5147
|
warn(ctx, `prompt-tool: settings 参数迁移失败(下次启动重试):${error instanceof Error ? error.message : String(error)}`);
|
|
4561
5148
|
}
|
|
4562
5149
|
};
|
|
5150
|
+
ctx.inject(["settings", "agentPresets"], () => {
|
|
5151
|
+
syncTemplateFromHostDefault();
|
|
5152
|
+
});
|
|
4563
5153
|
ctx.inject(["settings"], (sctx) => {
|
|
4564
5154
|
hostSettingsService = sctx.settings;
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
}
|
|
4573
|
-
|
|
5155
|
+
sctx.effect(() => sctx.on("settings/updated", (ns, next) => {
|
|
5156
|
+
if (String(ns) !== String(agentPresetsNs)) return;
|
|
5157
|
+
syncTemplateFromHostDefault(next);
|
|
5158
|
+
}), "prompt-tool: follow agent-presets default");
|
|
5159
|
+
sctx.settings.installSection(ctx, NS, PromptSettingsSchema, settingsEntry, {
|
|
5160
|
+
setSource: (current) => {
|
|
5161
|
+
currentSource = current;
|
|
5162
|
+
},
|
|
5163
|
+
onChange: () => {
|
|
5164
|
+
settingsBridge.invalidateDescriptor();
|
|
5165
|
+
try {
|
|
5166
|
+
applyState();
|
|
5167
|
+
} catch (error) {
|
|
5168
|
+
warn(ctx, `prompt-tool: applyState failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
5169
|
+
}
|
|
4574
5170
|
}
|
|
4575
5171
|
});
|
|
5172
|
+
if (legacyMigrated) syncHostDefault("migrate");
|
|
5173
|
+
else syncTemplateFromHostDefault();
|
|
5174
|
+
migrateSettingsParamsToPreset(sctx);
|
|
4576
5175
|
});
|
|
4577
5176
|
}
|
|
4578
5177
|
//#endregion
|
|
4579
|
-
export { BRIDGE_ENDPOINTS, Config, PARAM_KEYS, PromptSettingsSchema, SETTINGS_BRIDGE_PREFIX, SKILL_NAME_RE, apply, applyModuleConfigs, cloneBuiltinPreset, createCachedSkillsReader, detectModels, ensurePresetSeed,
|
|
5178
|
+
export { BRIDGE_ENDPOINTS, Config, ENGINE_PARAM_KEYS, MODEL_SEGMENT_MAP, PARAM_KEYS, PromptSettingsSchema, SETTINGS_BRIDGE_PREFIX, SKILL_NAME_RE, WRITER_PARAM_KEYS, appendCharacterMemory, appendMemoryFile, apply, applyCharacterToPreset, applyModuleConfigs, buildModuleConfigsFromParams, buildWorldBookEntry, cloneBuiltinPreset, convertStToPreset, createCachedSkillsReader, deleteCharacterCard, deleteWorldBookEntry, detectModels, ensurePresetSeed, ensureWebSurface, fixSkillEntry, importCharacterCard, inject, installDefaultModelRoute, isValidSkill, listAdvertisedModels, listBuiltinTemplates, listCharacterCards, listPresets, listSkillFolders, listWorldBookEntries, loadPresetSpec, loadPromptTemplates, loadToolTemplates, mergeSkillDirs, mergeStPresets, migrateLegacyLayout, name, normalizePresetRootDir, parseFrontmatter, processStText, readCharacterMemory, readPluginState, readSkills, registerCharacterTools, registerSettingsBridge, registerTuiCommand, registerWorldBookTools, removeCharacterFromPreset, removeUserPreset, renderComposition, resolvePresetDir, resolvePresetParams, resolveProfileDir, resolveProfileSkillsDir, savePresetParams, stPresetId, syncImportedCharacterMemory, toKebabName, upsertWorldBookEntry, userPresetsDir, validSkills, validatePromptConfigs, writePluginState, writePreset };
|