dsh-plugin-prompt-tool 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -222
- package/engine/anchor-match.mjs +117 -0
- package/engine/compaction-epoch.mjs +139 -0
- package/engine/compositions/library/bootstrap-filesystem.yml +19 -0
- package/engine/compositions/library/compaction.yml +52 -0
- package/engine/compositions/library/context-gate.yml +40 -0
- package/engine/compositions/library/custom-bash.yml +14 -0
- package/engine/compositions/library/delegation.yml +85 -0
- package/engine/compositions/library/official-agent-instructions.yml +17 -0
- package/engine/compositions/library/official-persistent-shell.yml +56 -0
- package/engine/compositions/library/official-skill-filesystem-cordis.yml +10 -0
- package/engine/compositions/library/official-tool-bash.yml +6 -0
- package/engine/compositions/library/official-tool-cordis.yml +13 -0
- package/engine/compositions/library/official-tool-presentation.yml +7 -0
- package/engine/compositions/library/official-tool-skill.yml +14 -0
- package/engine/compositions/library/persistent-shell.yml +36 -0
- package/engine/compositions/library/persona.yml +9 -0
- package/engine/compositions/library/planning.yml +41 -0
- package/engine/compositions/library/prompt-config-engine.yml +16 -0
- package/engine/compositions/library/run-code-env.yml +15 -0
- package/engine/compositions/library/skill-filesystem.yml +13 -0
- package/engine/compositions/library/skill-search.yml +14 -0
- package/engine/compositions/library/str-replace-editor.yml +10 -0
- package/engine/compositions/library/tool-ask-user.yml +8 -0
- package/engine/compositions/library/tool-bash.yml +16 -0
- package/engine/compositions/library/tool-bootstrap.yml +16 -0
- package/engine/compositions/library/tool-filter.yml +12 -0
- package/engine/compositions/library/tool-fs-search.yml +18 -0
- package/engine/compositions/library/tool-fs.yml +10 -0
- package/engine/compositions/library/tool-goal.yml +19 -0
- package/engine/compositions/library/tool-jobs.yml +23 -0
- package/engine/compositions/library/tool-pwsh.yml +12 -0
- package/engine/compositions/library/tool-todo.yml +11 -0
- package/engine/compositions/library/tool-web.yml +9 -0
- package/engine/compositions/source/local/context-gate.yml +37 -0
- package/engine/compositions/source/local/custom-bash.yml +11 -0
- package/engine/compositions/source/local/prompt-config-engine.yml +13 -0
- package/engine/compositions/source/local/run-code-env.yml +12 -0
- package/engine/compositions/source/local/skill-search.yml +11 -0
- package/engine/compositions/source/local/tool-bootstrap.yml +13 -0
- package/engine/context-gate.mjs +305 -0
- package/{preset → engine}/custom-bash.mjs +243 -243
- package/engine/executor.mjs +278 -0
- package/engine/fillers.mjs +273 -0
- package/engine/interpolate.mjs +66 -0
- package/engine/layers.mjs +224 -0
- package/engine/prompt-config-engine.mjs +49 -0
- package/engine/run-code-env.mjs +208 -0
- package/engine/schema.mjs +312 -0
- package/engine/session-vars.mjs +52 -0
- package/{preset → engine}/shared.mjs +35 -0
- package/engine/strategies.mjs +213 -0
- package/{preset → engine}/tool-bootstrap.mjs +350 -282
- package/engine/tool-filter.mjs +80 -0
- package/engine/vendor/yaml/LICENSE +13 -0
- package/engine/vendor/yaml/dist/compose/compose-collection.js +88 -0
- package/engine/vendor/yaml/dist/compose/compose-doc.js +43 -0
- package/engine/vendor/yaml/dist/compose/compose-node.js +109 -0
- package/engine/vendor/yaml/dist/compose/compose-scalar.js +86 -0
- package/engine/vendor/yaml/dist/compose/composer.js +219 -0
- package/engine/vendor/yaml/dist/compose/resolve-block-map.js +115 -0
- package/engine/vendor/yaml/dist/compose/resolve-block-scalar.js +198 -0
- package/engine/vendor/yaml/dist/compose/resolve-block-seq.js +49 -0
- package/engine/vendor/yaml/dist/compose/resolve-end.js +37 -0
- package/engine/vendor/yaml/dist/compose/resolve-flow-collection.js +207 -0
- package/engine/vendor/yaml/dist/compose/resolve-flow-scalar.js +225 -0
- package/engine/vendor/yaml/dist/compose/resolve-props.js +146 -0
- package/engine/vendor/yaml/dist/compose/util-contains-newline.js +34 -0
- package/engine/vendor/yaml/dist/compose/util-empty-scalar-position.js +26 -0
- package/engine/vendor/yaml/dist/compose/util-flow-indent-check.js +15 -0
- package/engine/vendor/yaml/dist/compose/util-map-includes.js +13 -0
- package/engine/vendor/yaml/dist/doc/Document.js +335 -0
- package/engine/vendor/yaml/dist/doc/anchors.js +71 -0
- package/engine/vendor/yaml/dist/doc/applyReviver.js +55 -0
- package/engine/vendor/yaml/dist/doc/createNode.js +88 -0
- package/engine/vendor/yaml/dist/doc/directives.js +176 -0
- package/engine/vendor/yaml/dist/errors.js +57 -0
- package/engine/vendor/yaml/dist/index.js +17 -0
- package/engine/vendor/yaml/dist/log.js +11 -0
- package/engine/vendor/yaml/dist/nodes/Alias.js +116 -0
- package/engine/vendor/yaml/dist/nodes/Collection.js +147 -0
- package/engine/vendor/yaml/dist/nodes/Node.js +38 -0
- package/engine/vendor/yaml/dist/nodes/Pair.js +36 -0
- package/engine/vendor/yaml/dist/nodes/Scalar.js +24 -0
- package/engine/vendor/yaml/dist/nodes/YAMLMap.js +144 -0
- package/engine/vendor/yaml/dist/nodes/YAMLSeq.js +113 -0
- package/engine/vendor/yaml/dist/nodes/addPairToJSMap.js +63 -0
- package/engine/vendor/yaml/dist/nodes/identity.js +36 -0
- package/engine/vendor/yaml/dist/nodes/toJS.js +37 -0
- package/engine/vendor/yaml/dist/parse/cst-scalar.js +214 -0
- package/engine/vendor/yaml/dist/parse/cst-stringify.js +61 -0
- package/engine/vendor/yaml/dist/parse/cst-visit.js +97 -0
- package/engine/vendor/yaml/dist/parse/cst.js +98 -0
- package/engine/vendor/yaml/dist/parse/lexer.js +721 -0
- package/engine/vendor/yaml/dist/parse/line-counter.js +39 -0
- package/engine/vendor/yaml/dist/parse/parser.js +975 -0
- package/engine/vendor/yaml/dist/public-api.js +102 -0
- package/engine/vendor/yaml/dist/schema/Schema.js +37 -0
- package/engine/vendor/yaml/dist/schema/common/map.js +17 -0
- package/engine/vendor/yaml/dist/schema/common/null.js +15 -0
- package/engine/vendor/yaml/dist/schema/common/seq.js +17 -0
- package/engine/vendor/yaml/dist/schema/common/string.js +14 -0
- package/engine/vendor/yaml/dist/schema/core/bool.js +19 -0
- package/engine/vendor/yaml/dist/schema/core/float.js +43 -0
- package/engine/vendor/yaml/dist/schema/core/int.js +38 -0
- package/engine/vendor/yaml/dist/schema/core/schema.js +23 -0
- package/engine/vendor/yaml/dist/schema/json/schema.js +62 -0
- package/engine/vendor/yaml/dist/schema/tags.js +96 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/binary.js +58 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/bool.js +26 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/float.js +46 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/int.js +71 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/merge.js +67 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/omap.js +74 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/pairs.js +78 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/schema.js +39 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/set.js +93 -0
- package/engine/vendor/yaml/dist/schema/yaml-1.1/timestamp.js +101 -0
- package/engine/vendor/yaml/dist/stringify/foldFlowLines.js +146 -0
- package/engine/vendor/yaml/dist/stringify/stringify.js +129 -0
- package/engine/vendor/yaml/dist/stringify/stringifyCollection.js +153 -0
- package/engine/vendor/yaml/dist/stringify/stringifyComment.js +20 -0
- package/engine/vendor/yaml/dist/stringify/stringifyDocument.js +85 -0
- package/engine/vendor/yaml/dist/stringify/stringifyNumber.js +25 -0
- package/engine/vendor/yaml/dist/stringify/stringifyPair.js +150 -0
- package/engine/vendor/yaml/dist/stringify/stringifyString.js +336 -0
- package/engine/vendor/yaml/dist/util.js +11 -0
- package/engine/vendor/yaml/dist/visit.js +233 -0
- package/engine/vendor/yaml/index.js +5 -0
- package/engine/vendor/yaml/package.json +11 -0
- package/lib/client.js +4724 -775
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +530 -51
- package/lib/index.mjs +4164 -740
- package/lib/preset-core.d.mts +7 -37
- package/lib/preset-core.mjs +43 -285
- package/lib/prompt-configs-B4vH09wx.d.mts +100 -0
- package/lib/prompt-configs-ThS4iXPg.mjs +771 -0
- package/package.json +36 -29
- package/preset/anchored/preset.yml +342 -0
- package/preset/creative/preset.yml +65 -0
- package/preset/creative/skills/cordis-plugin-development/SKILL.md +420 -0
- package/preset/creative/skills/editing-cordis-compositions/SKILL.md +165 -0
- package/preset/custom/preset.yml +13 -0
- package/preset/liangshen/preset.yml +72 -0
- package/preset/minimal/preset.yml +44 -0
- package/preset/ptc/preset.yml +56 -0
- package/preset/standard/preset.yml +55 -0
- package/skills/manifest.json +7 -0
- package/skills/sandboxmod/SKILL.md +49 -49
- package/skills/web ui/SKILL.md +42 -0
- package/templates/10-pre-step.yml +28 -0
- package/templates/11-merged-a.yml +11 -0
- package/templates/13-anchor.yml +19 -0
- package/templates/14-first-turn-anchor.yml +27 -0
- package/templates/15-guide-auto.yml +25 -0
- package/templates/16-custom-fallback.yml +21 -0
- package/templates/17-instruction-hint.yml +23 -0
- package/templates/18-placeholder-env-facts.yml +11 -0
- package/templates/19-placeholder-skill-catalog.yml +19 -0
- package/templates/20-system-section.yml +19 -0
- package/templates/30-runtime-context.yml +10 -0
- package/templates/31-runtime-context-placeholder.yml +18 -0
- package/templates/40-agent-request.yml +11 -0
- package/templates/50-llm-stream.yml +9 -0
- package/templates/60-tool-pipeline.yml +12 -0
- package/AGENTS.md +0 -4
- package/plan.md +0 -312
- package/preset/agent.cordis.yml +0 -443
- package/preset/compaction-epoch.mjs +0 -81
- package/preset/context-gate.mjs +0 -165
- package/preset/instruction-hint.mjs +0 -217
- package/preset/near-anchor.mjs +0 -101
- package/preset/preset.yml +0 -3
- package/preset/prompt-injector.mjs +0 -112
- package/preset/router-first-turn.mjs +0 -73
- package/preset/router-guide.mjs +0 -79
- package/preset.md +0 -115
- package/upstream/dsh-anchored-standard/LICENSE +0 -22
- package/upstream/dsh-anchored-standard/NOTICE +0 -19
- package/upstream/dsh-anchored-standard/REVISION +0 -1
- package/upstream/dsh-anchored-standard/preset/agent.cordis.yml +0 -440
- package/upstream/dsh-anchored-standard/preset/compaction-epoch.mjs +0 -81
- package/upstream/dsh-anchored-standard/preset/context-gate.mjs +0 -202
- package/upstream/dsh-anchored-standard/preset/custom-bash.mjs +0 -219
- package/upstream/dsh-anchored-standard/preset/dev-tool-search.mjs +0 -131
- package/upstream/dsh-anchored-standard/preset/instruction-hint.mjs +0 -231
- package/upstream/dsh-anchored-standard/preset/preset.yml +0 -3
- package/upstream/dsh-anchored-standard/preset/skill-search.mjs +0 -142
- package/upstream/dsh-anchored-standard/preset/tool-bootstrap.mjs +0 -301
- /package/{preset → engine}/skill-search.mjs +0 -0
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { Pair, Scalar, YAMLMap, YAMLSeq, parse, parseDocument } from "yaml";
|
|
3
|
+
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
//#region src/host/paths.ts
|
|
8
|
+
/** host 数据层的部署路径与序数常量(settings 层与运行时共用,避免 host→config 反向依赖)。 */
|
|
9
|
+
/**
|
|
10
|
+
* 包内 skills 目录。
|
|
11
|
+
* 源码位于 src/host/(../../skills = 包根/skills),构建后内联进 lib/(层级变浅)。
|
|
12
|
+
* 统一向上查找最近包含 skills/manifest.json 的包根,两种形态都正确。
|
|
13
|
+
*/
|
|
14
|
+
const SKILLS_DIR = (() => {
|
|
15
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
for (let depth = 0; depth < 6; depth += 1) {
|
|
17
|
+
if (existsSync(join(dir, "skills", "manifest.json"))) return join(dir, "skills");
|
|
18
|
+
const parent = dirname(dir);
|
|
19
|
+
if (parent === dir) break;
|
|
20
|
+
dir = parent;
|
|
21
|
+
}
|
|
22
|
+
return join(dir, "skills");
|
|
23
|
+
})();
|
|
24
|
+
/**
|
|
25
|
+
* 部署路径默认值;凡是不同部署可能需要不同值的参数都通过 Config 暴露,
|
|
26
|
+
* cordis.yml 可以覆盖,无需改代码。
|
|
27
|
+
* 与官方 dsh-home-paths.resolveDshHome 语义对齐:
|
|
28
|
+
* 未设置 / 空串 / 纯空白回退 OS home 下的 .dsh;支持 ~、~/、~\;
|
|
29
|
+
* 相对路径按进程 cwd 解析为绝对路径,避免生成目录随运行目录漂移。
|
|
30
|
+
*/
|
|
31
|
+
function resolveDshHome() {
|
|
32
|
+
const ambient = process.env.DSH_HOME;
|
|
33
|
+
if (typeof ambient !== "string" || ambient.trim().length === 0) return join(homedir(), ".dsh");
|
|
34
|
+
const expanded = ambient === "~" ? homedir() : ambient.startsWith("~/") || ambient.startsWith("~\\") ? join(homedir(), ambient.slice(2)) : ambient;
|
|
35
|
+
return resolve(expanded);
|
|
36
|
+
}
|
|
37
|
+
const DSH_HOME = resolveDshHome();
|
|
38
|
+
const DEFAULT_RESIDENT_AGENTS_PATH = join(DSH_HOME, "AGENTS.md");
|
|
39
|
+
/**
|
|
40
|
+
* 预设根:官方 USER_PRESET_DIR(DSH_HOME/.agent-presets)。
|
|
41
|
+
* 每个预设一个官方预设目录(agent.cordis.yml 组合本体 + preset.yml 参数),
|
|
42
|
+
* 宿主 agent-presets 直接可挂载;共享引擎在 .engine(点前缀 → discovery 跳过)。
|
|
43
|
+
*/
|
|
44
|
+
const DEFAULT_PRESET_DIR = join(DSH_HOME, ".agent-presets");
|
|
45
|
+
join(DEFAULT_PRESET_DIR, ".engine");
|
|
46
|
+
/** 旧版用户预设目录(~/.dsh/presets;迁移后归档为 presets.bak-*)。 */
|
|
47
|
+
const LEGACY_USER_PRESETS_DIR = join(DSH_HOME, "presets");
|
|
48
|
+
/** 旧版容器根(.agent-presets/prompt-tool;迁移后归档为 prompt-tool.bak-*)。 */
|
|
49
|
+
const LEGACY_CONTAINER_DIR = join(DEFAULT_PRESET_DIR, "prompt-tool");
|
|
50
|
+
const DEFAULT_SKILLS_DIR = SKILLS_DIR;
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/host/manifest.ts
|
|
53
|
+
/**
|
|
54
|
+
* manifest — 预设模板单一参数 YAML(preset.yml)的加载与引擎参数解析。
|
|
55
|
+
*
|
|
56
|
+
* 一个预设 = 一个 preset.yml:
|
|
57
|
+
* - modules/params/content/meta 全部是直读参数,无模板语法;
|
|
58
|
+
* - 默认提示词配置由引擎按 params 生成(见 write-preset),promptConfigs 仅为可选覆盖;
|
|
59
|
+
* - 组合模块中的 __TOKEN__ 由引擎内部 renderEngineTokens 按 params 生成,
|
|
60
|
+
* 不再暴露到参数文件。
|
|
61
|
+
* 本模块负责参数归一化与引擎 token 渲染;所有 anchored 专属行为都在引擎内部。
|
|
62
|
+
*/
|
|
63
|
+
/** 包根 preset/ 目录(配置/模板文件夹):兼容源码运行(src/host)与打包运行(lib/)。 */
|
|
64
|
+
function packagePresetDir() {
|
|
65
|
+
const candidates = [new URL("../preset/", import.meta.url), new URL("../../preset/", import.meta.url)];
|
|
66
|
+
for (const candidate of candidates) {
|
|
67
|
+
const dir = fileURLToPath(candidate);
|
|
68
|
+
if (existsSync(dir)) return dir;
|
|
69
|
+
}
|
|
70
|
+
throw new Error("prompt-tool: cannot locate package preset/ directory");
|
|
71
|
+
}
|
|
72
|
+
/** 包根 engine/ 目录(插件引擎,与配置文件夹分离):兼容源码与打包运行。 */
|
|
73
|
+
function packageEngineDir() {
|
|
74
|
+
const candidates = [new URL("../engine/", import.meta.url), new URL("../../engine/", import.meta.url)];
|
|
75
|
+
for (const candidate of candidates) {
|
|
76
|
+
const dir = fileURLToPath(candidate);
|
|
77
|
+
if (existsSync(join(dir, "prompt-config-engine.mjs"))) return dir;
|
|
78
|
+
}
|
|
79
|
+
throw new Error("prompt-tool: cannot locate package engine/ directory");
|
|
80
|
+
}
|
|
81
|
+
/** 加载某个预设模板的单一参数文件 preset/<name>/preset.yml。 */
|
|
82
|
+
function loadPresetSpec(dir) {
|
|
83
|
+
const raw = readFileSync(join(dir, "preset.yml"), "utf8");
|
|
84
|
+
const parsed = parse(raw, { logLevel: "silent" });
|
|
85
|
+
if (parsed === null || typeof parsed !== "object") throw new Error(`preset ${join(dir, "preset.yml")} is not a YAML map`);
|
|
86
|
+
if (typeof parsed.id !== "string" || parsed.id.length === 0) parsed.id = basename(dir);
|
|
87
|
+
return parsed;
|
|
88
|
+
}
|
|
89
|
+
/** 读取预设模板内容资产(presetText / agentsText);模板缺失时静默降级。
|
|
90
|
+
* 模板目录按 resolvePresetDir 解析(用户自定义预设优先,包内模板回退)。 */
|
|
91
|
+
function loadPresetContent(template = "anchored") {
|
|
92
|
+
try {
|
|
93
|
+
const spec = loadPresetSpec(resolvePresetDir(template));
|
|
94
|
+
return {
|
|
95
|
+
presetText: typeof spec.content?.presetText === "string" ? spec.content.presetText : "",
|
|
96
|
+
agentsText: typeof spec.content?.agentsText === "string" ? spec.content.agentsText : ""
|
|
97
|
+
};
|
|
98
|
+
} catch {
|
|
99
|
+
return {
|
|
100
|
+
presetText: "",
|
|
101
|
+
agentsText: ""
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const asString = (value, fallback = "") => {
|
|
106
|
+
if (typeof value === "string") return value;
|
|
107
|
+
if (value === void 0 || value === null) return fallback;
|
|
108
|
+
return String(value);
|
|
109
|
+
};
|
|
110
|
+
/** 预设根:官方 USER_PRESET_DIR(~/.dsh/.agent-presets),导入/新建/种子化的预设都放这里。 */
|
|
111
|
+
function userPresetsDir() {
|
|
112
|
+
return DEFAULT_PRESET_DIR;
|
|
113
|
+
}
|
|
114
|
+
/** 在指定扫描目录内按 template 定位预设目录:目录名精确匹配优先,preset.yml 的 id 匹配兜底。 */
|
|
115
|
+
function findPresetDir(scanDir, template) {
|
|
116
|
+
const exact = join(scanDir, template);
|
|
117
|
+
if (existsSync(join(exact, "preset.yml"))) return exact;
|
|
118
|
+
try {
|
|
119
|
+
for (const entry of readdirSync(scanDir, { withFileTypes: true })) {
|
|
120
|
+
if (!entry.isDirectory()) continue;
|
|
121
|
+
const dir = join(scanDir, entry.name);
|
|
122
|
+
try {
|
|
123
|
+
if (loadPresetSpec(dir).id === template) return dir;
|
|
124
|
+
} catch {}
|
|
125
|
+
}
|
|
126
|
+
} catch {}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 解析预设模板目录:用户自定义优先,包内模板回退。
|
|
130
|
+
* 目录名与 preset.yml id 双匹配(UI 切换值=目录名;旧 settings 存量值=id 也兼容)。
|
|
131
|
+
*/
|
|
132
|
+
function resolvePresetDir(template) {
|
|
133
|
+
return findPresetDir(userPresetsDir(), template) ?? findPresetDir(packagePresetDir(), template) ?? join(packagePresetDir(), template);
|
|
134
|
+
}
|
|
135
|
+
/** 可用预设清单:全部来自预设根 ~/.dsh/.agent-presets(官方预设目录,含 agent.cordis.yml
|
|
136
|
+
* 即被宿主挂载;点前缀目录与无 preset.yml 的官方目录跳过,不占本插件列表)。 */
|
|
137
|
+
function listPresets() {
|
|
138
|
+
const scan = (dir) => {
|
|
139
|
+
try {
|
|
140
|
+
return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).flatMap((entry) => {
|
|
141
|
+
try {
|
|
142
|
+
const spec = loadPresetSpec(join(dir, entry.name));
|
|
143
|
+
if (typeof spec.id !== "string" || spec.id.length === 0) return [];
|
|
144
|
+
return [{
|
|
145
|
+
id: entry.name,
|
|
146
|
+
name: spec.name,
|
|
147
|
+
user: true,
|
|
148
|
+
...typeof spec.description === "string" && spec.description.length > 0 ? { description: spec.description } : {},
|
|
149
|
+
...spec.meta !== void 0 && spec.meta !== null ? { meta: spec.meta } : {}
|
|
150
|
+
}];
|
|
151
|
+
} catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
} catch {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const byId = /* @__PURE__ */ new Map();
|
|
160
|
+
for (const preset of scan(userPresetsDir())) byId.set(preset.id, preset);
|
|
161
|
+
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
162
|
+
}
|
|
163
|
+
/** 插件目录模板清单(「新建」选择器与首次种子化数据源;不直接出现在预设列表)。 */
|
|
164
|
+
function listBuiltinTemplates() {
|
|
165
|
+
try {
|
|
166
|
+
return readdirSync(packagePresetDir(), { withFileTypes: true }).filter((entry) => entry.isDirectory()).filter((entry) => entry.name !== "custom").flatMap((entry) => {
|
|
167
|
+
try {
|
|
168
|
+
const spec = loadPresetSpec(join(packagePresetDir(), entry.name));
|
|
169
|
+
return [{
|
|
170
|
+
id: entry.name,
|
|
171
|
+
name: spec.name
|
|
172
|
+
}];
|
|
173
|
+
} catch {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
177
|
+
} catch {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** 插件状态文件(DSH_HOME 下,预设根之外):与用户资产解耦,删除/备份/迁移预设根不影响状态。
|
|
182
|
+
* 原子写(tmp+rename),避免半写文件。 */
|
|
183
|
+
function stateFilePath() {
|
|
184
|
+
return join(DSH_HOME, ".prompt-tool-state.json");
|
|
185
|
+
}
|
|
186
|
+
/** 读插件状态;文件缺失/损坏返回空对象(按未标记处理,触发首次动作)。 */
|
|
187
|
+
function readPluginState() {
|
|
188
|
+
try {
|
|
189
|
+
const parsed = JSON.parse(readFileSync(stateFilePath(), "utf8"));
|
|
190
|
+
return parsed !== null && typeof parsed === "object" ? parsed : {};
|
|
191
|
+
} catch {
|
|
192
|
+
return {};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/** 原子写插件状态(tmp+rename)。 */
|
|
196
|
+
function writePluginState(state) {
|
|
197
|
+
const file = stateFilePath();
|
|
198
|
+
const tmp = `${file}.tmp`;
|
|
199
|
+
writeFileSync(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
200
|
+
renameSync(tmp, file);
|
|
201
|
+
}
|
|
202
|
+
/** 首次启动种子化:把插件目录全部内置模板复制到预设根(state.seeded 后不再自动补)。
|
|
203
|
+
* 用户删除的预设不会自动复活;升级新增的模板用「新建」按需复制。
|
|
204
|
+
* 兼容旧版预设根内 .pt-seeded 标记:存在即视为已种子化,并迁入状态文件后删除。 */
|
|
205
|
+
function ensurePresetSeed() {
|
|
206
|
+
const root = userPresetsDir();
|
|
207
|
+
const legacyMark = join(root, ".pt-seeded");
|
|
208
|
+
const state = readPluginState();
|
|
209
|
+
if (state.seeded === true || existsSync(legacyMark)) {
|
|
210
|
+
if (existsSync(legacyMark)) try {
|
|
211
|
+
writePluginState({
|
|
212
|
+
...state,
|
|
213
|
+
seeded: true
|
|
214
|
+
});
|
|
215
|
+
rmSync(legacyMark, { force: true });
|
|
216
|
+
} catch {}
|
|
217
|
+
return { created: [] };
|
|
218
|
+
}
|
|
219
|
+
const created = [];
|
|
220
|
+
try {
|
|
221
|
+
mkdirSync(root, { recursive: true });
|
|
222
|
+
for (const entry of readdirSync(packagePresetDir(), { withFileTypes: true })) {
|
|
223
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
224
|
+
const target = join(root, entry.name);
|
|
225
|
+
if (existsSync(target)) continue;
|
|
226
|
+
cpSync(join(packagePresetDir(), entry.name), target, { recursive: true });
|
|
227
|
+
created.push(entry.name);
|
|
228
|
+
}
|
|
229
|
+
writePluginState({
|
|
230
|
+
...readPluginState(),
|
|
231
|
+
seeded: true
|
|
232
|
+
});
|
|
233
|
+
} catch {}
|
|
234
|
+
return { created };
|
|
235
|
+
}
|
|
236
|
+
/** 从插件目录复制内置预设到预设根(新建/还原)。
|
|
237
|
+
* autoSuffix=true(自定义预设入口)时同名自动递增(custom → custom-2 → …);否则同名拒绝。 */
|
|
238
|
+
function cloneBuiltinPreset(id, autoSuffix = false) {
|
|
239
|
+
if (typeof id !== "string" || id.length === 0 || id === "." || id === ".." || id.includes("/") || id.includes("\\")) return {
|
|
240
|
+
ok: false,
|
|
241
|
+
message: `非法预设 id:${id}`
|
|
242
|
+
};
|
|
243
|
+
const builtin = findPresetDir(packagePresetDir(), id);
|
|
244
|
+
if (builtin === void 0) return {
|
|
245
|
+
ok: false,
|
|
246
|
+
message: `预设 ${id} 不是包内置预设`
|
|
247
|
+
};
|
|
248
|
+
let targetId = id;
|
|
249
|
+
let target = join(userPresetsDir(), targetId);
|
|
250
|
+
if (existsSync(target)) {
|
|
251
|
+
if (!autoSuffix) return {
|
|
252
|
+
ok: false,
|
|
253
|
+
message: `用户目录已存在同名预设 ${id},请先删除再新建`
|
|
254
|
+
};
|
|
255
|
+
for (let suffix = 2;; suffix++) {
|
|
256
|
+
targetId = `${id}-${suffix}`;
|
|
257
|
+
target = join(userPresetsDir(), targetId);
|
|
258
|
+
if (!existsSync(target)) break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
mkdirSync(userPresetsDir(), { recursive: true });
|
|
263
|
+
cpSync(builtin, target, {
|
|
264
|
+
recursive: true,
|
|
265
|
+
force: true
|
|
266
|
+
});
|
|
267
|
+
return {
|
|
268
|
+
ok: true,
|
|
269
|
+
id: targetId
|
|
270
|
+
};
|
|
271
|
+
} catch (error) {
|
|
272
|
+
return {
|
|
273
|
+
ok: false,
|
|
274
|
+
message: `新建预设失败:${error instanceof Error ? error.message : String(error)}`
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/** 复制用户预设目录为新预设(id 自动递增:<id>-copy / <id>-copy-2 / …)。
|
|
279
|
+
* 复制的是用户目录完整副本(preset.yml / agent.cordis.yml / prompt-configs /
|
|
280
|
+
* 内容资产 / 覆盖文件),与「从内置模板新建」互补:后者还原模板,前者备份现状。 */
|
|
281
|
+
function duplicateUserPreset(id, presetRoot = userPresetsDir()) {
|
|
282
|
+
if (typeof id !== "string" || id.length === 0 || id === "." || id === ".." || id.includes("/") || id.includes("\\")) return {
|
|
283
|
+
ok: false,
|
|
284
|
+
message: `非法预设 id:${id}`
|
|
285
|
+
};
|
|
286
|
+
const root = resolve(presetRoot);
|
|
287
|
+
const source = resolve(join(root, id));
|
|
288
|
+
const rootResolved = resolve(root);
|
|
289
|
+
if (source !== rootResolved && !source.startsWith(rootResolved + sep)) return {
|
|
290
|
+
ok: false,
|
|
291
|
+
message: `预设路径越界:${id}`
|
|
292
|
+
};
|
|
293
|
+
if (!existsSync(source)) return {
|
|
294
|
+
ok: false,
|
|
295
|
+
message: `预设 ${id} 不存在`
|
|
296
|
+
};
|
|
297
|
+
let targetId = `${id}-copy`;
|
|
298
|
+
let target = join(root, targetId);
|
|
299
|
+
for (let suffix = 2; existsSync(target); suffix++) {
|
|
300
|
+
targetId = `${id}-copy${suffix}`;
|
|
301
|
+
target = join(root, targetId);
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
mkdirSync(root, { recursive: true });
|
|
305
|
+
cpSync(source, target, {
|
|
306
|
+
recursive: true,
|
|
307
|
+
force: true
|
|
308
|
+
});
|
|
309
|
+
return {
|
|
310
|
+
ok: true,
|
|
311
|
+
id: targetId
|
|
312
|
+
};
|
|
313
|
+
} catch (error) {
|
|
314
|
+
return {
|
|
315
|
+
ok: false,
|
|
316
|
+
message: `复制预设失败:${error instanceof Error ? error.message : String(error)}`
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/** 在系统文件管理器中打开预设目录(尽力而为:无桌面环境时打开失败也返回路径供 UI 展示)。 */
|
|
321
|
+
function openPresetLocation(id, presetRoot = userPresetsDir()) {
|
|
322
|
+
const isBareId = typeof id === "string" && id.length > 0 && id !== "." && id !== ".." && !id.includes("/") && !id.includes("\\");
|
|
323
|
+
const isCardPath = typeof id === "string" && /^\/\.[a-zA-Z0-9_-]+\/[^/\\]+$/.test(id);
|
|
324
|
+
if (!isBareId && !isCardPath) return {
|
|
325
|
+
ok: false,
|
|
326
|
+
message: `非法预设 id:${id}`,
|
|
327
|
+
path: ""
|
|
328
|
+
};
|
|
329
|
+
const dir = resolve(join(presetRoot, id));
|
|
330
|
+
const rootResolved = resolve(presetRoot);
|
|
331
|
+
if (dir !== rootResolved && !dir.startsWith(rootResolved + sep)) return {
|
|
332
|
+
ok: false,
|
|
333
|
+
message: `预设路径越界:${id}`,
|
|
334
|
+
path: dir
|
|
335
|
+
};
|
|
336
|
+
if (!existsSync(dir)) return {
|
|
337
|
+
ok: false,
|
|
338
|
+
message: `预设 ${id} 不存在`,
|
|
339
|
+
path: dir
|
|
340
|
+
};
|
|
341
|
+
try {
|
|
342
|
+
const command = process.platform === "win32" ? "explorer" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
343
|
+
spawn(command, [dir], {
|
|
344
|
+
detached: true,
|
|
345
|
+
stdio: "ignore"
|
|
346
|
+
}).unref();
|
|
347
|
+
return {
|
|
348
|
+
ok: true,
|
|
349
|
+
path: dir
|
|
350
|
+
};
|
|
351
|
+
} catch (error) {
|
|
352
|
+
return {
|
|
353
|
+
ok: false,
|
|
354
|
+
message: `打开目录失败:${error instanceof Error ? error.message : String(error)}`,
|
|
355
|
+
path: dir
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* 保存预设参数:写激活预设目录 preset.yml 的 params(merge)/ promptConfigs(整体替换)。
|
|
361
|
+
* parseDocument 保留注释与未知键(preset.yml 模板含大量注释);空值键跳过
|
|
362
|
+
* (保留 preset.yml 模板默认)。写入后删除 prompt-tool.overrides.yml(旧参数覆盖
|
|
363
|
+
* 通道残留——参数已并入 preset.yml,避免旧值覆盖新值)。
|
|
364
|
+
*/
|
|
365
|
+
function savePresetParams(presetRoot, templateName, params, promptConfigs, variables, variablesEnabled) {
|
|
366
|
+
const file = join(presetRoot, templateName, "preset.yml");
|
|
367
|
+
if (!existsSync(file)) throw new Error(`preset ${templateName} 无 preset.yml`);
|
|
368
|
+
const doc = parseDocument(readFileSync(file, "utf8"), { logLevel: "silent" });
|
|
369
|
+
if (params !== void 0) for (const [key, value] of Object.entries(params)) {
|
|
370
|
+
if (value === void 0 || value === null || key.trim().length === 0) continue;
|
|
371
|
+
doc.setIn(["params", key], value);
|
|
372
|
+
}
|
|
373
|
+
if (promptConfigs !== void 0) {
|
|
374
|
+
const cleaned = promptConfigs.map((config) => {
|
|
375
|
+
if (config === null || typeof config !== "object" || Array.isArray(config)) return config;
|
|
376
|
+
const vars = config.variables;
|
|
377
|
+
if (vars === null || typeof vars !== "object" || Array.isArray(vars)) return config;
|
|
378
|
+
const kept = Object.fromEntries(Object.entries(vars).filter(([key]) => key.trim().length > 0));
|
|
379
|
+
const next = { ...config };
|
|
380
|
+
if (Object.keys(kept).length > 0) next.variables = kept;
|
|
381
|
+
else delete next.variables;
|
|
382
|
+
return next;
|
|
383
|
+
});
|
|
384
|
+
doc.setIn(["promptConfigs"], cleaned);
|
|
385
|
+
}
|
|
386
|
+
if (variables !== void 0) {
|
|
387
|
+
const kept = Object.fromEntries(Object.entries(variables).filter(([key, value]) => key.trim().length > 0 && typeof value === "string"));
|
|
388
|
+
if (Object.keys(kept).length > 0) doc.setIn(["variables"], kept);
|
|
389
|
+
else doc.deleteIn(["variables"]);
|
|
390
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
391
|
+
if (typeof value !== "string") continue;
|
|
392
|
+
doc.deleteIn(["params", key]);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (variablesEnabled !== void 0) {
|
|
396
|
+
if (variablesEnabled) doc.deleteIn(["variablesEnabled"]);
|
|
397
|
+
else doc.setIn(["variablesEnabled"], false);
|
|
398
|
+
}
|
|
399
|
+
writeFileSync(file, doc.toString(), "utf8");
|
|
400
|
+
rmSync(join(presetRoot, templateName, "prompt-tool.overrides.yml"), { force: true });
|
|
401
|
+
}
|
|
402
|
+
/** 删除预设目录(预设根/<id>;含同名导入的 .bak-* 备份目录)。
|
|
403
|
+
* 仅作用于预设根(官方 USER_PRESET_DIR),包内置模板天然不受影响;路径越界与非法 id 拒绝。
|
|
404
|
+
* 删除后宿主 agent-presets 目录列表自然不再出现该预设(官方 roster 即目录列表)。 */
|
|
405
|
+
function removeUserPreset(id, presetRoot = userPresetsDir()) {
|
|
406
|
+
if (typeof id !== "string" || id.length === 0 || id === "." || id === ".." || id.includes("/") || id.includes("\\")) return {
|
|
407
|
+
ok: false,
|
|
408
|
+
message: `非法预设 id:${id}`
|
|
409
|
+
};
|
|
410
|
+
const root = presetRoot;
|
|
411
|
+
const target = resolve(join(root, id));
|
|
412
|
+
const rootResolved = resolve(root);
|
|
413
|
+
if (target !== rootResolved && !target.startsWith(rootResolved + sep)) return {
|
|
414
|
+
ok: false,
|
|
415
|
+
message: `预设路径越界:${id}`
|
|
416
|
+
};
|
|
417
|
+
if (existsSync(target)) try {
|
|
418
|
+
rmSync(target, {
|
|
419
|
+
recursive: true,
|
|
420
|
+
force: true
|
|
421
|
+
});
|
|
422
|
+
return { ok: true };
|
|
423
|
+
} catch (error) {
|
|
424
|
+
return {
|
|
425
|
+
ok: false,
|
|
426
|
+
message: `删除失败:${error instanceof Error ? error.message : String(error)}`
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
ok: false,
|
|
431
|
+
message: `预设 ${id} 不存在(预设根 ~/.dsh/.agent-presets)`
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
/** 从导入的 preset.yml 文本解析预设 id(非法/缺失回退目录名)。 */
|
|
435
|
+
function parseImportedPresetId(presetYaml, fallback) {
|
|
436
|
+
try {
|
|
437
|
+
const parsed = parse(presetYaml, { logLevel: "silent" });
|
|
438
|
+
return typeof parsed?.id === "string" && /^[a-zA-Z0-9\u4e00-\u9fff][a-zA-Z0-9\u4e00-\u9fff-]*$/.test(parsed.id) ? parsed.id : fallback;
|
|
439
|
+
} catch {
|
|
440
|
+
return fallback;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
/** on/off 等字面开关归一化为布尔。 */
|
|
444
|
+
function normalizeParam(value) {
|
|
445
|
+
if (value === "on") return true;
|
|
446
|
+
if (value === "off") return false;
|
|
447
|
+
return value;
|
|
448
|
+
}
|
|
449
|
+
/** 预设 params(默认参数)与运行时 settings 合并;settings 值优先。 */
|
|
450
|
+
function resolvePresetParams(spec, runtime) {
|
|
451
|
+
const params = {};
|
|
452
|
+
for (const [key, value] of Object.entries(spec.params ?? {})) params[key] = normalizeParam(value);
|
|
453
|
+
for (const [key, value] of Object.entries(runtime)) if (value !== void 0) params[key] = value;
|
|
454
|
+
if (typeof params.promptText !== "string" || params.promptText.length === 0) params.promptText = typeof runtime.promptText === "string" ? runtime.promptText : "";
|
|
455
|
+
return params;
|
|
456
|
+
}
|
|
457
|
+
/** {{key}} 嵌套插值(块模板内引用 params);键正则与引擎插值及 processStText 中文变量支持一致。 */
|
|
458
|
+
function interpolateNested(text, scope) {
|
|
459
|
+
return text.replace(/\{\{([A-Za-z0-9_.\u4e00-\u9fff-]+)\}\}/g, (whole, key, offset) => {
|
|
460
|
+
if (!Object.prototype.hasOwnProperty.call(scope, key)) return whole;
|
|
461
|
+
const value = asString(scope[key]);
|
|
462
|
+
if (!value.includes("\n")) return value;
|
|
463
|
+
const lineStart = text.lastIndexOf("\n", offset) + 1;
|
|
464
|
+
const prefix = text.slice(lineStart, offset);
|
|
465
|
+
return value.split("\n").map((line, at) => at === 0 ? line : line.length > 0 ? prefix + line : "").join("\n");
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
/** 逗号分隔 / YAML flow 数组 / 空格分隔的字符串列表 → 字符串数组。 */
|
|
469
|
+
function parseListParam(value) {
|
|
470
|
+
if (Array.isArray(value)) return value.map((item) => String(item)).filter((item) => item.length > 0);
|
|
471
|
+
if (typeof value !== "string") return [];
|
|
472
|
+
const trimmed = value.trim();
|
|
473
|
+
if (trimmed.length === 0) return [];
|
|
474
|
+
return (trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed).split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* 引擎内部 token 渲染:把直读参数变成组合模块里的 __TOKEN__ 值。
|
|
478
|
+
* anchored 的全部组合行为(usePtcMode/bootstrapMaxTokens/模型路由与委派完整自定义)
|
|
479
|
+
* 在这里完成参数化,用户参数文件不需要任何模板语法。
|
|
480
|
+
* 模型路由/委派参数统一扁平键(modelProvider/subagentModelProvider/toolFilterAllow/maxDepth 等),
|
|
481
|
+
* 与官方 AgentOptions{provider,model} / toolFilter{allow,deny} / maxDepth 对齐。
|
|
482
|
+
*/
|
|
483
|
+
function renderEngineTokens(params) {
|
|
484
|
+
const provider = asString(params.subagentModelProvider, "");
|
|
485
|
+
const model = asString(params.subagentModelName, "");
|
|
486
|
+
const subagentPersona = asString(params.subagentPersona);
|
|
487
|
+
const toolFilterAllow = parseListParam(params.toolFilterAllow);
|
|
488
|
+
const toolFilterDeny = parseListParam(params.toolFilterDeny);
|
|
489
|
+
const mainFilterAllow = parseListParam(params.toolFilterAllow);
|
|
490
|
+
const mainFilterDeny = parseListParam(params.toolFilterDeny);
|
|
491
|
+
const mainFilterSubagents = params.toolFilterSubagents === true;
|
|
492
|
+
const mainFilterAllowLine = mainFilterAllow.length > 0 ? `[${mainFilterAllow.join(", ")}]` : "";
|
|
493
|
+
const mainFilterDenyLine = mainFilterDeny.length > 0 ? `[${mainFilterDeny.join(", ")}]` : "";
|
|
494
|
+
const rawMaxDepth = params.maxDepth;
|
|
495
|
+
const subagentMaxDepth = rawMaxDepth === "provider-managed" ? "provider-managed" : Number.isSafeInteger(rawMaxDepth) && rawMaxDepth >= 0 ? String(rawMaxDepth) : "";
|
|
496
|
+
const subagentLines = [];
|
|
497
|
+
if (provider.length > 0 && model.length > 0) subagentLines.push("agentOptions:", ` provider: ${provider}`, ` model: ${model}`);
|
|
498
|
+
if (subagentPersona.length > 0) subagentLines.push(interpolateNested("persona: |-\n {{subagentPersona}}", { subagentPersona }));
|
|
499
|
+
if (toolFilterAllow.length > 0 || toolFilterDeny.length > 0) {
|
|
500
|
+
subagentLines.push("toolFilter:");
|
|
501
|
+
if (toolFilterAllow.length > 0) subagentLines.push(` allow: [${toolFilterAllow.join(", ")}]`);
|
|
502
|
+
if (toolFilterDeny.length > 0) subagentLines.push(` deny: [${toolFilterDeny.join(", ")}]`);
|
|
503
|
+
}
|
|
504
|
+
if (subagentMaxDepth.length > 0) subagentLines.push(`maxDepth: ${subagentMaxDepth}`);
|
|
505
|
+
const subagentConfigBlock = subagentLines.join("\n");
|
|
506
|
+
const bootstrap = typeof params.bootstrapMaxTokens === "number" && params.bootstrapMaxTokens > 0 ? `bootstrapMaxTokens: ${params.bootstrapMaxTokens}` : "";
|
|
507
|
+
const editorMaxOutputChars = typeof params.strReplaceEditorMaxOutputChars === "number" && Number.isSafeInteger(params.strReplaceEditorMaxOutputChars) && params.strReplaceEditorMaxOutputChars > 0 ? String(params.strReplaceEditorMaxOutputChars) : "16000";
|
|
508
|
+
return {
|
|
509
|
+
usePtcMode: params.usePtcMode === void 0 ? "" : params.usePtcMode === true ? "true" : "false",
|
|
510
|
+
bootstrapMaxTokens: bootstrap,
|
|
511
|
+
subagentConfig: subagentConfigBlock,
|
|
512
|
+
allowKinds: params.allowKinds === void 0 ? "" : Array.isArray(params.allowKinds) ? `[${params.allowKinds.map((item) => String(item)).join(", ")}]` : asString(params.allowKinds),
|
|
513
|
+
strReplaceEditorMaxOutputChars: editorMaxOutputChars,
|
|
514
|
+
mainToolFilterAllow: mainFilterAllowLine,
|
|
515
|
+
mainToolFilterDeny: mainFilterDenyLine,
|
|
516
|
+
mainToolFilterSubagents: String(mainFilterSubagents)
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
/** 预设 params + runtime → 组合 token 渲染(便捷入口)。 */
|
|
520
|
+
function resolvePresetTokens(spec, runtime) {
|
|
521
|
+
return renderEngineTokens(resolvePresetParams(spec, runtime));
|
|
522
|
+
}
|
|
523
|
+
/** 按参数文件的 modules 清单从引擎模块库装配组合文本。 */
|
|
524
|
+
/** 空模块清单兜底骨架(自定义预设空白起点:模块集与 minimal 一致,参数/配置全空)。 */
|
|
525
|
+
const FALLBACK_MODULES = [
|
|
526
|
+
"official-persistent-shell",
|
|
527
|
+
"bootstrap-filesystem",
|
|
528
|
+
"context-gate",
|
|
529
|
+
"tool-bootstrap",
|
|
530
|
+
"prompt-config-engine",
|
|
531
|
+
"run-code-env",
|
|
532
|
+
"custom-bash",
|
|
533
|
+
"skill-search"
|
|
534
|
+
];
|
|
535
|
+
function assembleModules(spec, library) {
|
|
536
|
+
const parts = [];
|
|
537
|
+
for (const name of spec.modules ?? []) {
|
|
538
|
+
if (typeof name !== "string" || name.length === 0) throw new Error(`preset ${spec.id}: modules must be non-empty strings`);
|
|
539
|
+
try {
|
|
540
|
+
parts.push(readFileSync(join(library, `${name}.yml`), "utf8"));
|
|
541
|
+
} catch (error) {
|
|
542
|
+
throw new Error(`preset ${spec.id}: module ${name} not found in engine library: ${String(error.message ?? error)}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return parts.join("\n");
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* 加载预设声明的组合:
|
|
549
|
+
* 1. `modules:` 清单 → 引擎模块库按序装配(最终形态);
|
|
550
|
+
* 2. 兼容 `composition:` 内联文本或组合清单名。
|
|
551
|
+
*/
|
|
552
|
+
/**
|
|
553
|
+
* 行级 config 合并(moduleConfigs):仅支持 map 型 config 浅合并;
|
|
554
|
+
* 数组型 config(如 compaction)与未声明模块原样保留。
|
|
555
|
+
* 未声明 moduleConfigs 时返回原文(零开销);parseDocument 往返保留注释与 __TOKEN__。
|
|
556
|
+
*/
|
|
557
|
+
function applyModuleConfigs(raw, configs) {
|
|
558
|
+
if (configs === void 0 || Object.keys(configs).length === 0) return raw;
|
|
559
|
+
const doc = parseDocument(raw, { logLevel: "silent" });
|
|
560
|
+
if (!(doc.contents instanceof YAMLSeq)) return raw;
|
|
561
|
+
const rows = doc.contents;
|
|
562
|
+
let changed = false;
|
|
563
|
+
for (const item of rows.items) {
|
|
564
|
+
if (!(item instanceof YAMLMap)) continue;
|
|
565
|
+
const idNode = item.get("id", true);
|
|
566
|
+
const id = idNode !== null && typeof idNode === "object" && "value" in idNode ? String(idNode.value) : void 0;
|
|
567
|
+
if (id === void 0 || !Object.prototype.hasOwnProperty.call(configs, id)) continue;
|
|
568
|
+
const existingConfig = item.get("config", true);
|
|
569
|
+
let configNode;
|
|
570
|
+
if (existingConfig instanceof YAMLMap) configNode = existingConfig;
|
|
571
|
+
else if (existingConfig === null || existingConfig === void 0) {
|
|
572
|
+
configNode = new YAMLMap();
|
|
573
|
+
item.items.push(new Pair(new Scalar("config"), configNode));
|
|
574
|
+
changed = true;
|
|
575
|
+
} else continue;
|
|
576
|
+
for (const [key, value] of Object.entries(configs[id])) configNode.set(key, value);
|
|
577
|
+
changed = true;
|
|
578
|
+
}
|
|
579
|
+
return changed ? doc.toString() : raw;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* 加载预设声明的组合(原始 token 文本,未渲染)。
|
|
583
|
+
* - `modules:` 清单 → 引擎模块库按序装配;
|
|
584
|
+
* - `composition: ./xxx.yml` → 预设模板目录内组合文件(官方预设直用);
|
|
585
|
+
* - `composition:` 内联文本或组合清单名。
|
|
586
|
+
*/
|
|
587
|
+
function loadCompositionText(spec, templateDir) {
|
|
588
|
+
const library = join(packageEngineDir(), "compositions", "library");
|
|
589
|
+
let raw;
|
|
590
|
+
const modules = Array.isArray(spec.modules) && spec.modules.length === 0 ? FALLBACK_MODULES : spec.modules;
|
|
591
|
+
if (Array.isArray(modules)) raw = assembleModules({
|
|
592
|
+
...spec,
|
|
593
|
+
modules
|
|
594
|
+
}, library);
|
|
595
|
+
else {
|
|
596
|
+
const name = typeof spec.composition === "string" ? spec.composition : "";
|
|
597
|
+
if (name.includes("\n")) raw = name;
|
|
598
|
+
else if (name.startsWith("./")) {
|
|
599
|
+
if (templateDir === void 0) throw new Error(`preset ${spec.id}: composition relative path needs a templateDir (${JSON.stringify(name)})`);
|
|
600
|
+
try {
|
|
601
|
+
raw = readFileSync(join(templateDir, name.slice(2)), "utf8");
|
|
602
|
+
} catch (error) {
|
|
603
|
+
throw new Error(`preset ${spec.id}: composition file not found (${join(templateDir, name.slice(2))}): ${String(error.message ?? error)}`);
|
|
604
|
+
}
|
|
605
|
+
} else if (name.length > 0) {
|
|
606
|
+
const file = join(dirname(library), `${name}.yml`);
|
|
607
|
+
try {
|
|
608
|
+
raw = readFileSync(file, "utf8");
|
|
609
|
+
} catch (error) {
|
|
610
|
+
throw new Error(`preset ${spec.id}: engine composition ${name} not found (${file}): ${String(error.message ?? error)}`);
|
|
611
|
+
}
|
|
612
|
+
} else if (templateDir !== void 0) try {
|
|
613
|
+
raw = readFileSync(join(templateDir, "agent.cordis.yml"), "utf8");
|
|
614
|
+
} catch (error) {
|
|
615
|
+
throw new Error(`preset ${spec.id}: no modules/composition and no agent.cordis.yml in template dir (${templateDir}): ${String(error.message ?? error)}`);
|
|
616
|
+
}
|
|
617
|
+
else throw new Error(`preset ${spec.id}: no modules list and no composition declared`);
|
|
618
|
+
}
|
|
619
|
+
return raw;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* 预设组合渲染完整链路:token 渲染 → moduleConfigs 行级合并。
|
|
623
|
+
* 合并必须发生在 token 渲染之后(独立行 token 在渲染前是非法 YAML,
|
|
624
|
+
* 如 __SUBAGENT_CONFIG__ / __BOOTSTRAP_MAX_TOKENS__)。
|
|
625
|
+
*/
|
|
626
|
+
function renderComposition(spec, runtime, templateDir) {
|
|
627
|
+
const tokens = resolvePresetTokens(spec, runtime);
|
|
628
|
+
return applyModuleConfigs(renderTemplateVariables(loadCompositionText(spec, templateDir), tokens), spec.moduleConfigs);
|
|
629
|
+
}
|
|
630
|
+
/** 组合文本基础校验(模板无关):无未解析 token,且必须是 YAML 数组。 */
|
|
631
|
+
function assertCompositionArray(raw, spec) {
|
|
632
|
+
const unresolved = raw.match(/__[A-Za-z0-9_]+__/g);
|
|
633
|
+
if (unresolved !== null) throw new Error(`generated agent.cordis.yml has unresolved variables: ${unresolved.join(", ")}`);
|
|
634
|
+
const parsed = parse(raw, { logLevel: "silent" });
|
|
635
|
+
if (!Array.isArray(parsed)) throw new Error(`generated agent.cordis.yml is not a YAML array (preset ${spec.id})`);
|
|
636
|
+
return parsed;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* 文本 token 替换:
|
|
640
|
+
* - token 独立成行时,替换块按该行缩进前缀(用于 line/block);
|
|
641
|
+
* - token 内联时直接替换(用于布尔/字符串标量)。
|
|
642
|
+
*/
|
|
643
|
+
function renderTemplateVariables(raw, variables) {
|
|
644
|
+
let text = raw.replace(/^[ \t]*(?:[A-Za-z0-9_.-]+:[ \t]*)?__([A-Za-z0-9_]+)__[ \t]*\r?\n/gm, (whole, key) => Object.prototype.hasOwnProperty.call(variables, key) && variables[key] === "" ? "" : whole);
|
|
645
|
+
return text.replace(/__([A-Za-z0-9_]+)__/g, (whole, key, offset) => {
|
|
646
|
+
if (!Object.prototype.hasOwnProperty.call(variables, key)) return whole;
|
|
647
|
+
const value = variables[key];
|
|
648
|
+
if (value === "") return "";
|
|
649
|
+
const lineStart = text.lastIndexOf("\n", offset) + 1;
|
|
650
|
+
const lineEndRaw = text.indexOf("\n", offset);
|
|
651
|
+
const lineEnd = lineEndRaw < 0 ? text.length : lineEndRaw;
|
|
652
|
+
const prefix = text.slice(lineStart, offset);
|
|
653
|
+
const rest = text.slice(offset + whole.length, lineEnd);
|
|
654
|
+
if (prefix.trim() === "" && rest.trim() === "") return value.split("\n").map((line, at) => at === 0 ? line : line.length > 0 ? prefix + line : "").join("\n");
|
|
655
|
+
return value;
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
//#endregion
|
|
659
|
+
//#region src/host/prompt-configs.ts
|
|
660
|
+
/**
|
|
661
|
+
* 提示词配置数据接口:默认提示词配置构建、通用 YAML 渲染、多源合并与目录加载。
|
|
662
|
+
*
|
|
663
|
+
* 这是「用户自定义注入内容 + 自定义注入层级位置」的功能层:
|
|
664
|
+
* 1. settings.promptConfigs 数组(UI 设置最终消费此接口)
|
|
665
|
+
* 2. 生成目录 prompt-configs/(yml/json 提示词配置文件)
|
|
666
|
+
* 3. 默认四条提示词配置
|
|
667
|
+
* 三者按此优先级合并,同名 id 后者覆盖,新 id 追加在默认提示词配置之后。
|
|
668
|
+
* 引擎(engine/prompt-config-engine.mjs)在运行时对生成 yml 做权威校验。
|
|
669
|
+
*/
|
|
670
|
+
/** 文本块缩进 n 个空格(YAML block scalar)。 */
|
|
671
|
+
function indentBy(level, text) {
|
|
672
|
+
const pad = " ".repeat(level);
|
|
673
|
+
return text.split(/\r?\n/).map((line) => line.length === 0 ? "" : pad + line).join("\n");
|
|
674
|
+
}
|
|
675
|
+
/** YAML 标量渲染:字符串非空用 block scalar,空串 quoted,布尔/数字原样。 */
|
|
676
|
+
function yamlScalar(key, level, value) {
|
|
677
|
+
const pad = " ".repeat(level);
|
|
678
|
+
if (typeof value === "string") return value.length === 0 ? `${pad}${key}: ''` : `${pad}${key}: |-\n${indentBy(level + 2, value)}`;
|
|
679
|
+
if (typeof value === "boolean" || typeof value === "number") return `${pad}${key}: ${String(value)}`;
|
|
680
|
+
if (value === null || value === void 0) return "";
|
|
681
|
+
return `${pad}${key}: ${JSON.stringify(value)}`;
|
|
682
|
+
}
|
|
683
|
+
/** 嵌套 map 渲染(variables / params / identity)。 */
|
|
684
|
+
function yamlMap(level, value) {
|
|
685
|
+
const pad = " ".repeat(level);
|
|
686
|
+
const lines = [];
|
|
687
|
+
for (const [key, item] of Object.entries(value)) {
|
|
688
|
+
if (item === null || item === void 0) continue;
|
|
689
|
+
if (typeof item === "object" && !Array.isArray(item)) {
|
|
690
|
+
lines.push(`${pad}${key}:`);
|
|
691
|
+
lines.push(...yamlMap(level + 2, item));
|
|
692
|
+
} else {
|
|
693
|
+
const line = yamlScalar(key, level, item);
|
|
694
|
+
if (line.length > 0) lines.push(line);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return lines;
|
|
698
|
+
}
|
|
699
|
+
/** 把任意提示词配置 spec 渲染为独立提示词配置模块 yml(全部字段开放可配置)。 */
|
|
700
|
+
function renderPromptConfigYaml(spec) {
|
|
701
|
+
const lines = [`id: ${spec.id}`];
|
|
702
|
+
if (typeof spec.name === "string" && spec.name.length > 0 && spec.name !== spec.id) lines.push(`name: ${spec.name}`);
|
|
703
|
+
if (spec.configKind !== void 0) lines.push(`configKind: ${spec.configKind}`);
|
|
704
|
+
if (spec.layer !== void 0) lines.push(`layer: ${spec.layer}`);
|
|
705
|
+
if (spec.order !== void 0) lines.push(`order: ${spec.order}`);
|
|
706
|
+
if (spec.role !== void 0) lines.push(`role: ${spec.role}`);
|
|
707
|
+
lines.push(`enabled: ${spec.enabled !== false}`);
|
|
708
|
+
lines.push(`strategy: ${spec.strategy ?? "static"}`);
|
|
709
|
+
if (spec.position !== void 0) lines.push(`position: ${spec.position}`);
|
|
710
|
+
if (spec.dedupe !== void 0) lines.push(`dedupe: ${spec.dedupe}`);
|
|
711
|
+
if (spec.promotion !== void 0) lines.push(`promotion: ${spec.promotion}`);
|
|
712
|
+
if (spec.audience !== void 0 && spec.audience !== null) lines.push(`audience: ${spec.audience}`);
|
|
713
|
+
if (spec.modelScope !== void 0) lines.push(`modelScope: ${spec.modelScope}`);
|
|
714
|
+
if (typeof spec.group === "string" && spec.group.length > 0) lines.push(`group: ${spec.group}`);
|
|
715
|
+
if (spec.exclusive === true) lines.push("exclusive: true");
|
|
716
|
+
if (typeof spec.sourceKind === "string" && spec.sourceKind.length > 0 && spec.sourceKind !== spec.id) lines.push(`sourceKind: ${spec.sourceKind}`);
|
|
717
|
+
if (typeof spec.form === "string" && spec.form.length > 0 && spec.form !== "notice") lines.push(`form: ${spec.form}`);
|
|
718
|
+
if (typeof spec.summary === "string" && spec.summary.length > 0) lines.push(yamlScalar("summary", 0, spec.summary));
|
|
719
|
+
if (typeof spec.templateFile === "string" && spec.templateFile.length > 0) lines.push(`templateFile: ${spec.templateFile}`);
|
|
720
|
+
if (typeof spec.fill === "string" && spec.fill.length > 0) lines.push(`fill: ${spec.fill}`);
|
|
721
|
+
const texts = [...typeof spec.text === "string" && spec.text.length > 0 ? [spec.text] : [], ...Array.isArray(spec.texts) ? spec.texts : []];
|
|
722
|
+
if (texts.length > 0) lines.push(`texts: ${JSON.stringify(texts)}`);
|
|
723
|
+
if (spec.mergeMode !== void 0 && spec.mergeMode !== "separate") lines.push(`mergeMode: ${spec.mergeMode}`);
|
|
724
|
+
if (spec.identity !== void 0 && spec.identity.value !== spec.id) lines.push("identity:", ` field: ${spec.identity.field}`, ` value: ${spec.identity.value}`);
|
|
725
|
+
if (spec.variables !== void 0 && Object.keys(spec.variables).length > 0) {
|
|
726
|
+
lines.push("variables:");
|
|
727
|
+
lines.push(...yamlMap(2, spec.variables));
|
|
728
|
+
}
|
|
729
|
+
if (spec.params !== void 0 && Object.keys(spec.params).length > 0) {
|
|
730
|
+
lines.push("params:");
|
|
731
|
+
lines.push(...yamlMap(2, spec.params));
|
|
732
|
+
}
|
|
733
|
+
return lines.join("\n").trimEnd();
|
|
734
|
+
}
|
|
735
|
+
/** 多源提示词配置合并:同名 id 后者覆盖(保留默认提示词配置位置),新 id 追加在末尾。 */
|
|
736
|
+
function mergePromptConfigs(...sources) {
|
|
737
|
+
const ordered = [];
|
|
738
|
+
const byId = /* @__PURE__ */ new Map();
|
|
739
|
+
for (const source of sources) for (const spec of source ?? []) {
|
|
740
|
+
if (spec === null || typeof spec !== "object" || typeof spec.id !== "string" || spec.id.length === 0) throw new TypeError("every prompt config must have a non-empty string id");
|
|
741
|
+
const existing = byId.get(spec.id);
|
|
742
|
+
if (existing === void 0) {
|
|
743
|
+
byId.set(spec.id, ordered.length);
|
|
744
|
+
ordered.push(spec);
|
|
745
|
+
} else ordered[existing] = spec;
|
|
746
|
+
}
|
|
747
|
+
return ordered;
|
|
748
|
+
}
|
|
749
|
+
/** 从用户提示词配置目录加载 yml/json 提示词配置(文件名排序;内容必须能解析)。 */
|
|
750
|
+
function loadPromptConfigFiles(dir) {
|
|
751
|
+
if (dir.length === 0) return [];
|
|
752
|
+
let entries;
|
|
753
|
+
try {
|
|
754
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
755
|
+
} catch (error) {
|
|
756
|
+
throw new Error(`提示词配置目录 ${JSON.stringify(dir)} 不可读: ${String(error.message ?? error)}`);
|
|
757
|
+
}
|
|
758
|
+
const specs = [];
|
|
759
|
+
const files = entries.filter((entry) => entry.isFile() && /\.(ya?ml|json)$/i.test(entry.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
760
|
+
for (const entry of files) {
|
|
761
|
+
if (entry.name === "variables.yml") continue;
|
|
762
|
+
const raw = readFileSync(join(dir, entry.name), "utf8");
|
|
763
|
+
const parsed = /\.json$/i.test(entry.name) ? JSON.parse(raw) : parse(raw, { logLevel: "silent" });
|
|
764
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`prompt config file ${entry.name} must contain a single config object`);
|
|
765
|
+
if (typeof parsed.id !== "string") throw new Error(`prompt config file ${entry.name} must declare a string id`);
|
|
766
|
+
specs.push(parsed);
|
|
767
|
+
}
|
|
768
|
+
return specs;
|
|
769
|
+
}
|
|
770
|
+
//#endregion
|
|
771
|
+
export { userPresetsDir as C, DEFAULT_SKILLS_DIR as D, DEFAULT_RESIDENT_AGENTS_PATH as E, LEGACY_CONTAINER_DIR as O, savePresetParams as S, DEFAULT_PRESET_DIR as T, readPluginState as _, asString as a, resolvePresetDir as b, duplicateUserPreset as c, listPresets as d, loadPresetContent as f, parseImportedPresetId as g, packageEngineDir as h, applyModuleConfigs as i, LEGACY_USER_PRESETS_DIR as k, ensurePresetSeed as l, openPresetLocation as m, mergePromptConfigs as n, assertCompositionArray as o, loadPresetSpec as p, renderPromptConfigYaml as r, cloneBuiltinPreset as s, loadPromptConfigFiles as t, listBuiltinTemplates as u, removeUserPreset as v, writePluginState as w, resolvePresetParams as x, renderComposition as y };
|