dsh-plugin-prompt-tool 0.3.1 → 0.4.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 +7 -5
- package/lib/client.js +30 -4
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +4 -1
- package/lib/index.mjs +30 -59
- package/lib/preset-core.d.mts +5 -1
- package/lib/preset-core.mjs +130 -31
- package/package.json +20 -20
- package/plan.md +19 -3
- package/preset/agent.cordis.yml +443 -0
- package/preset/compaction-epoch.mjs +81 -0
- package/preset/context-gate.mjs +165 -0
- package/preset/custom-bash.mjs +243 -0
- package/preset/instruction-hint.mjs +217 -0
- package/preset/near-anchor.mjs +6 -19
- package/preset/prompt-injector.mjs +111 -126
- package/preset/router-first-turn.mjs +73 -70
- package/preset/router-guide.mjs +14 -20
- package/preset/shared.mjs +83 -0
- package/preset/skill-search.mjs +142 -0
- package/preset/tool-bootstrap.mjs +282 -0
package/lib/index.mjs
CHANGED
|
@@ -33,7 +33,16 @@ const PRESET_TEMPLATE_INJECTOR = fileURLToPath(new URL("../preset/prompt-injecto
|
|
|
33
33
|
const PRESET_TEMPLATE_ANCHOR = fileURLToPath(new URL("../preset/near-anchor.mjs", import.meta.url));
|
|
34
34
|
const PRESET_TEMPLATE_ROUTER = fileURLToPath(new URL("../preset/router-first-turn.mjs", import.meta.url));
|
|
35
35
|
const PRESET_TEMPLATE_GUIDE = fileURLToPath(new URL("../preset/router-guide.mjs", import.meta.url));
|
|
36
|
-
const
|
|
36
|
+
const LOCAL_PRESET_DIR = fileURLToPath(new URL("../preset", import.meta.url));
|
|
37
|
+
const LOCAL_PRESET_SCRIPTS = [
|
|
38
|
+
"shared.mjs",
|
|
39
|
+
"context-gate.mjs",
|
|
40
|
+
"compaction-epoch.mjs",
|
|
41
|
+
"custom-bash.mjs",
|
|
42
|
+
"instruction-hint.mjs",
|
|
43
|
+
"skill-search.mjs",
|
|
44
|
+
"tool-bootstrap.mjs"
|
|
45
|
+
];
|
|
37
46
|
const Config = z.object({
|
|
38
47
|
text: z.string().default(""),
|
|
39
48
|
agentsText: z.string().default(""),
|
|
@@ -51,6 +60,7 @@ const Config = z.object({
|
|
|
51
60
|
subagentFlashProvider: z.string().default("deepseek-official"),
|
|
52
61
|
subagentFlashModel: z.string().default("deepseek-v4-flash"),
|
|
53
62
|
bootstrapMaxTokens: z.natural().default(0),
|
|
63
|
+
usePtcMode: z.boolean().default(true),
|
|
54
64
|
skillsDir: z.string().default(DEFAULT_SKILLS_DIR),
|
|
55
65
|
skillRankBase: z.natural().default(DEFAULT_SKILL_RANK_BASE),
|
|
56
66
|
residentAgentsPath: z.string().default(DEFAULT_RESIDENT_AGENTS_PATH),
|
|
@@ -71,6 +81,7 @@ const PromptSettingsSchema = z.object({
|
|
|
71
81
|
guideCustom: z.boolean().default(false),
|
|
72
82
|
subagentFlash: z.boolean().default(false),
|
|
73
83
|
bootstrapMaxTokens: z.natural().default(0),
|
|
84
|
+
usePtcMode: z.boolean().default(true),
|
|
74
85
|
deepseekAvailable: z.boolean().default(true),
|
|
75
86
|
injectPrompt: z.boolean().default(true),
|
|
76
87
|
skillSwitches: z.dict(z.boolean()).default({}),
|
|
@@ -228,28 +239,6 @@ function warn(ctx, message) {
|
|
|
228
239
|
ctx.logger?.warn(message);
|
|
229
240
|
} catch {}
|
|
230
241
|
}
|
|
231
|
-
/** 把上游 instruction-hint.mjs 的提示文本替换为读同目录 agents-instruction.txt。 */
|
|
232
|
-
function patchInstructionHint(source) {
|
|
233
|
-
source = source.replace(/\r\n/g, "\n");
|
|
234
|
-
const original = [
|
|
235
|
-
" const text = [",
|
|
236
|
-
" ...sections,",
|
|
237
|
-
" 'Do NOT assume their content. When a task touches this workspace, read the relevant instruction files first and follow them.',",
|
|
238
|
-
" ].join(' ')"
|
|
239
|
-
].join("\n");
|
|
240
|
-
const replacement = [
|
|
241
|
-
" const agentsInstructionText = readFileSync(new URL('./agents-instruction.txt', import.meta.url), 'utf8').trim()",
|
|
242
|
-
" const text = agentsInstructionText.length > 0",
|
|
243
|
-
" ? agentsInstructionText",
|
|
244
|
-
" : [",
|
|
245
|
-
" ...sections,",
|
|
246
|
-
" 'Do NOT assume their content. When a task touches this workspace, read the relevant instruction files first and follow them.',",
|
|
247
|
-
" ].join(' ')"
|
|
248
|
-
].join("\n");
|
|
249
|
-
if (!source.includes("import { createEpochPromotion }")) throw new Error("instruction-hint.mjs import marker missing");
|
|
250
|
-
if (!source.includes(original)) throw new Error("instruction-hint.mjs text marker missing");
|
|
251
|
-
return source.replace("import { createEpochPromotion } from './compaction-epoch.mjs'", "import { readFileSync } from 'node:fs'\nimport { createEpochPromotion } from './compaction-epoch.mjs'").replace(original, replacement);
|
|
252
|
-
}
|
|
253
242
|
const MAX_SETTINGS_BRIDGE_BODY = 65536;
|
|
254
243
|
/** 仅允许本机回环请求,镜像官方 settings bridge 的边界。 */
|
|
255
244
|
function isLoopbackRequest(req) {
|
|
@@ -296,7 +285,8 @@ const TUI_BOOLEAN_SWITCHES = [
|
|
|
296
285
|
["anchorFirstTurn", "追加任务引导"],
|
|
297
286
|
["anchorCustom", "使用自定义引导(首句)"],
|
|
298
287
|
["guideCustom", "使用自定义引导(每轮)"],
|
|
299
|
-
["subagentFlash", "子代理固定 Flash 模型"]
|
|
288
|
+
["subagentFlash", "子代理固定 Flash 模型"],
|
|
289
|
+
["usePtcMode", "使用 PTC 模式"]
|
|
300
290
|
];
|
|
301
291
|
/** 把布尔开关渲染成 dsh-tui 命令输出。 */
|
|
302
292
|
function renderTuiStatus(source) {
|
|
@@ -335,7 +325,7 @@ function registerTuiCommand(ctx, getSource, getDeepseekAvailable, getDeepseekSta
|
|
|
335
325
|
handler: async (invocation) => {
|
|
336
326
|
const usage = () => ({
|
|
337
327
|
kind: "error",
|
|
338
|
-
text: "用法:/prompt-tool status\n /prompt-tool on|off|toggle <writeAgents|writePreset|injectPrompt|injectAgentsPrompt|anchorFirstTurn|anchorCustom|guideCustom|subagentFlash>\n /prompt-tool skill <技能目录名> on|off|toggle\n /prompt-tool bootstrapMaxTokens <正整数|0(关闭)>"
|
|
328
|
+
text: "用法:/prompt-tool status\n /prompt-tool on|off|toggle <writeAgents|writePreset|injectPrompt|injectAgentsPrompt|anchorFirstTurn|anchorCustom|guideCustom|subagentFlash|usePtcMode>\n /prompt-tool skill <技能目录名> on|off|toggle\n /prompt-tool bootstrapMaxTokens <正整数|0(关闭)>"
|
|
339
329
|
});
|
|
340
330
|
const tokens = invocation.rawInput.trim().split(/\s+/).filter((token) => token.length > 0);
|
|
341
331
|
const source = getSource();
|
|
@@ -656,26 +646,6 @@ function installSubagentFlashRoute(ctx, isEnabled, provider, model) {
|
|
|
656
646
|
};
|
|
657
647
|
});
|
|
658
648
|
}
|
|
659
|
-
/** prompt-tool 补丁:子代理直接全量放行(assembled.tools 本身已是动态白名单)。 */
|
|
660
|
-
function patchToolBootstrap(source) {
|
|
661
|
-
source = source.replace(/\r\n/g, "\n");
|
|
662
|
-
const original = [
|
|
663
|
-
" const assembled = await next()",
|
|
664
|
-
" try {",
|
|
665
|
-
" const status = promotion.status(context.agent)"
|
|
666
|
-
].join("\n");
|
|
667
|
-
const replacement = [
|
|
668
|
-
" const assembled = await next()",
|
|
669
|
-
" try {",
|
|
670
|
-
" // prompt-tool 补丁:子代理跳过目录裁剪,直接使用组装结果;",
|
|
671
|
-
" // 调用方(如 dsh-mnemon)的工具白名单已先行过滤 assembled.tools,",
|
|
672
|
-
" // 因此任意前缀的新插件工具都会自动出现在子代理第一次会话。",
|
|
673
|
-
" if ((context.agent?.session?.header?.delegationDepth ?? 0) > 0) return assembled",
|
|
674
|
-
" const status = promotion.status(context.agent)"
|
|
675
|
-
].join("\n");
|
|
676
|
-
if (!source.includes(original)) throw new Error("tool-bootstrap.mjs assembled marker missing");
|
|
677
|
-
return source.replace(original, replacement);
|
|
678
|
-
}
|
|
679
649
|
function writePreset(prompt, options) {
|
|
680
650
|
const presetDir = options.presetDir;
|
|
681
651
|
mkdirSync(presetDir, { recursive: true });
|
|
@@ -689,21 +659,16 @@ function writePreset(prompt, options) {
|
|
|
689
659
|
subagentFlash: options.subagentFlash,
|
|
690
660
|
subagentFlashProvider: options.subagentFlashProvider,
|
|
691
661
|
subagentFlashModel: options.subagentFlashModel,
|
|
692
|
-
bootstrapMaxTokens: options.bootstrapMaxTokens
|
|
662
|
+
bootstrapMaxTokens: options.bootstrapMaxTokens,
|
|
663
|
+
usePtcMode: options.usePtcMode
|
|
693
664
|
}), "utf8");
|
|
694
665
|
const meta = readFileSync(PRESET_TEMPLATE_META, "utf8").replace(/^order:.*$/m, `order: ${options.presetOrder}`);
|
|
695
666
|
writeFileSync(join(presetDir, "preset.yml"), meta, "utf8");
|
|
696
|
-
for (const file of
|
|
697
|
-
|
|
698
|
-
const vendorSource = readFileSync(join(VENDOR_PRESET_DIR, file), "utf8");
|
|
699
|
-
writeFileSync(join(presetDir, file), file === "tool-bootstrap.mjs" ? patchToolBootstrap(vendorSource) : vendorSource, "utf8");
|
|
700
|
-
}
|
|
667
|
+
for (const file of LOCAL_PRESET_SCRIPTS) writeFileSync(join(presetDir, file), readFileSync(join(LOCAL_PRESET_DIR, file), "utf8"), "utf8");
|
|
668
|
+
rmSync(join(presetDir, "dev-tool-search.mjs"), { force: true });
|
|
701
669
|
const agentsInstructionPath = join(presetDir, "agents-instruction.txt");
|
|
702
|
-
if (options.agentsInstructionText !== void 0)
|
|
703
|
-
|
|
704
|
-
const hintPath = join(presetDir, "instruction-hint.mjs");
|
|
705
|
-
writeFileSync(hintPath, patchInstructionHint(readFileSync(hintPath, "utf8")), "utf8");
|
|
706
|
-
} else rmSync(agentsInstructionPath, { force: true });
|
|
670
|
+
if (options.agentsInstructionText !== void 0) writeFileSync(agentsInstructionPath, options.agentsInstructionText, "utf8");
|
|
671
|
+
else rmSync(agentsInstructionPath, { force: true });
|
|
707
672
|
const injectorPath = join(presetDir, "prompt-injector.mjs");
|
|
708
673
|
if (options.injectPrompt) writeFileSync(injectorPath, readFileSync(PRESET_TEMPLATE_INJECTOR, "utf8"), "utf8");
|
|
709
674
|
else rmSync(injectorPath, { force: true });
|
|
@@ -785,7 +750,8 @@ function apply(ctx, config) {
|
|
|
785
750
|
guideText: config.guideText,
|
|
786
751
|
guideCustom: config.guideCustom,
|
|
787
752
|
subagentFlash: config.subagentFlash && getDeepseekAvailable(),
|
|
788
|
-
bootstrapMaxTokens: config.bootstrapMaxTokens
|
|
753
|
+
bootstrapMaxTokens: config.bootstrapMaxTokens,
|
|
754
|
+
usePtcMode: config.usePtcMode
|
|
789
755
|
};
|
|
790
756
|
installSubagentFlashRoute(ctx, () => runtime.subagentFlash, config.subagentFlashProvider, config.subagentFlashModel);
|
|
791
757
|
let currentSource = () => ({
|
|
@@ -801,6 +767,7 @@ function apply(ctx, config) {
|
|
|
801
767
|
guideCustom: runtime.guideCustom,
|
|
802
768
|
subagentFlash: runtime.subagentFlash,
|
|
803
769
|
bootstrapMaxTokens: runtime.bootstrapMaxTokens,
|
|
770
|
+
usePtcMode: runtime.usePtcMode,
|
|
804
771
|
deepseekAvailable: getDeepseekAvailable(),
|
|
805
772
|
injectPrompt: runtime.injectPrompt,
|
|
806
773
|
skillSwitches: runtime.skillSwitches,
|
|
@@ -824,12 +791,13 @@ function apply(ctx, config) {
|
|
|
824
791
|
guideText: typeof next.guideText === "string" ? next.guideText : config.guideText,
|
|
825
792
|
guideCustom: typeof next.guideCustom === "boolean" ? next.guideCustom : config.guideCustom,
|
|
826
793
|
subagentFlash: (typeof next.subagentFlash === "boolean" ? next.subagentFlash : config.subagentFlash) && getDeepseekAvailable(),
|
|
827
|
-
bootstrapMaxTokens: Number.isSafeInteger(next.bootstrapMaxTokens) && next.bootstrapMaxTokens >= 0 ? next.bootstrapMaxTokens : config.bootstrapMaxTokens
|
|
794
|
+
bootstrapMaxTokens: Number.isSafeInteger(next.bootstrapMaxTokens) && next.bootstrapMaxTokens >= 0 ? next.bootstrapMaxTokens : config.bootstrapMaxTokens,
|
|
795
|
+
usePtcMode: typeof next.usePtcMode === "boolean" ? next.usePtcMode : config.usePtcMode
|
|
828
796
|
};
|
|
829
797
|
const promptChanged = next.promptText !== current;
|
|
830
798
|
const agentsChanged = next.agentsText !== currentAgents;
|
|
831
799
|
const skillSwitchesChanged = JSON.stringify(runtime.skillSwitches) !== JSON.stringify(nextRuntime.skillSwitches);
|
|
832
|
-
const settingsChanged = runtime.writeAgents !== nextRuntime.writeAgents || runtime.writePreset !== nextRuntime.writePreset || runtime.injectAgentsPrompt !== nextRuntime.injectAgentsPrompt || runtime.injectPrompt !== nextRuntime.injectPrompt || skillSwitchesChanged || runtime.anchorFirstTurn !== nextRuntime.anchorFirstTurn || runtime.anchorText !== nextRuntime.anchorText || runtime.anchorCustom !== nextRuntime.anchorCustom || runtime.guideText !== nextRuntime.guideText || runtime.guideCustom !== nextRuntime.guideCustom || runtime.subagentFlash !== nextRuntime.subagentFlash || runtime.bootstrapMaxTokens !== nextRuntime.bootstrapMaxTokens;
|
|
800
|
+
const settingsChanged = runtime.writeAgents !== nextRuntime.writeAgents || runtime.writePreset !== nextRuntime.writePreset || runtime.injectAgentsPrompt !== nextRuntime.injectAgentsPrompt || runtime.injectPrompt !== nextRuntime.injectPrompt || skillSwitchesChanged || runtime.anchorFirstTurn !== nextRuntime.anchorFirstTurn || runtime.anchorText !== nextRuntime.anchorText || runtime.anchorCustom !== nextRuntime.anchorCustom || runtime.guideText !== nextRuntime.guideText || runtime.guideCustom !== nextRuntime.guideCustom || runtime.subagentFlash !== nextRuntime.subagentFlash || runtime.bootstrapMaxTokens !== nextRuntime.bootstrapMaxTokens || runtime.usePtcMode !== nextRuntime.usePtcMode;
|
|
833
801
|
if (!needsInitialApply && !promptChanged && !agentsChanged && !settingsChanged) return;
|
|
834
802
|
needsInitialApply = false;
|
|
835
803
|
if (promptChanged) current = next.promptText;
|
|
@@ -846,6 +814,7 @@ function apply(ctx, config) {
|
|
|
846
814
|
runtime.guideCustom = nextRuntime.guideCustom;
|
|
847
815
|
runtime.subagentFlash = nextRuntime.subagentFlash;
|
|
848
816
|
runtime.bootstrapMaxTokens = nextRuntime.bootstrapMaxTokens;
|
|
817
|
+
runtime.usePtcMode = nextRuntime.usePtcMode;
|
|
849
818
|
skillSwitches = runtime.skillSwitches;
|
|
850
819
|
if (skillSwitchesChanged) invalidateSkills?.();
|
|
851
820
|
let residentAgentsWritten = false;
|
|
@@ -867,6 +836,7 @@ function apply(ctx, config) {
|
|
|
867
836
|
subagentFlashProvider: config.subagentFlashProvider,
|
|
868
837
|
subagentFlashModel: config.subagentFlashModel,
|
|
869
838
|
bootstrapMaxTokens: runtime.bootstrapMaxTokens,
|
|
839
|
+
usePtcMode: runtime.usePtcMode,
|
|
870
840
|
agentsInstructionText: runtime.injectAgentsPrompt && currentAgents.length > 0 ? currentAgents : void 0,
|
|
871
841
|
presetDir: config.presetDir,
|
|
872
842
|
presetOrder: config.presetOrder
|
|
@@ -893,6 +863,7 @@ function apply(ctx, config) {
|
|
|
893
863
|
guideCustom: config.guideCustom,
|
|
894
864
|
subagentFlash: config.subagentFlash && getDeepseekAvailable(),
|
|
895
865
|
bootstrapMaxTokens: config.bootstrapMaxTokens,
|
|
866
|
+
usePtcMode: config.usePtcMode,
|
|
896
867
|
deepseekAvailable: getDeepseekAvailable(),
|
|
897
868
|
injectPrompt: config.injectPrompt,
|
|
898
869
|
skillSwitches: { ...config.skillSwitches },
|
package/lib/preset-core.d.mts
CHANGED
|
@@ -30,7 +30,11 @@ interface BuildCordisOptions {
|
|
|
30
30
|
subagentFlashModel?: string;
|
|
31
31
|
/** 首轮输出封顶(bootstrapMaxTokens);0 或未设置 = 本项目默认无封顶。 */
|
|
32
32
|
bootstrapMaxTokens?: number;
|
|
33
|
+
/** 使用 PTC 模式:默认 true——晋升后把 wire 切换为 Code Mode(单一 run_code,完整插件工具经生成 SDK 调用);显式 false 时晋升后恢复原生完整工具目录。 */
|
|
34
|
+
usePtcMode?: boolean;
|
|
33
35
|
}
|
|
36
|
+
/** 给生成的 tool-bootstrap.mjs 打补丁:子代理全量放行 + 可选的 PTC 呈现切换。 */
|
|
37
|
+
declare function patchToolBootstrap(source: string): string;
|
|
34
38
|
declare function buildCordis(prompt: string, options?: BuildCordisOptions): string;
|
|
35
39
|
//#endregion
|
|
36
|
-
export { BuildCordisOptions, SkillFrontmatter, buildCordis, parseFrontmatter };
|
|
40
|
+
export { BuildCordisOptions, SkillFrontmatter, buildCordis, parseFrontmatter, patchToolBootstrap };
|
package/lib/preset-core.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { parse } from "yaml";
|
|
|
6
6
|
* prompt-tool preset 生成核心:与 dsh 插件上下文无关的纯函数,
|
|
7
7
|
* 独立成文件以便单元测试与复用。失败一律抛出(fail loud)。
|
|
8
8
|
*/
|
|
9
|
-
const
|
|
9
|
+
const CORDIS_TEMPLATE = fileURLToPath(new URL("../preset/agent.cordis.yml", import.meta.url));
|
|
10
10
|
const ROUTER_FIRST_TURN_BLOCK = `# prompt-tool 本地附加件(最优组合):首轮只保留计划段 + 训练原句,
|
|
11
11
|
# 并清空自动注入 contexts;子代理直接放行。注册在 tool-bootstrap 之后,
|
|
12
12
|
# 不参与首轮工具目录裁剪——tools 仍由原版 tool-bootstrap 负责。
|
|
@@ -69,33 +69,16 @@ const ROUTER_FLASH_PERSONA = [
|
|
|
69
69
|
"Before acting, briefly review what you have already done in this session and continue from where you left off; do not repeat completed steps. Do not run environment checks (echo, whoami, uname, node --version, date) or exhaustive grep/glob scans.",
|
|
70
70
|
"Think deeply first, then produce."
|
|
71
71
|
].join("\n");
|
|
72
|
-
/**
|
|
73
|
-
function
|
|
74
|
-
const block = `- id: context-gate
|
|
75
|
-
name: ./context-gate.mjs
|
|
76
|
-
config:
|
|
77
|
-
promoteOn: either
|
|
78
|
-
includeSubagents: true
|
|
79
|
-
allowKinds: [skill-invocation]`;
|
|
80
|
-
const replaced = `- id: context-gate
|
|
81
|
-
name: ./context-gate.mjs
|
|
82
|
-
config:
|
|
83
|
-
promoteOn: either
|
|
84
|
-
includeSubagents: false
|
|
85
|
-
allowKinds: [skill-invocation, near-anchor, router-guide]`;
|
|
86
|
-
if (!source.includes(block)) throw new Error("vendor agent.cordis.yml missing context-gate row");
|
|
87
|
-
return source.replace(block, replaced);
|
|
88
|
-
}
|
|
89
|
-
/** 按开关向上游 tool-bootstrap 行注入 bootstrapMaxTokens;0/未设置保持无封顶。 */
|
|
90
|
-
function applyBootstrapMaxTokens(source, value) {
|
|
91
|
-
if (value === void 0 || !Number.isSafeInteger(value) || value <= 0) return source;
|
|
72
|
+
/** 按开关向本地 tool-bootstrap 行注入 usePtcMode / bootstrapMaxTokens。 */
|
|
73
|
+
function applyToolBootstrapOptions(source, usePtcMode, bootstrapMaxTokens) {
|
|
92
74
|
const rowStart = source.indexOf("- id: tool-bootstrap\n name: ./tool-bootstrap.mjs\n config:\n");
|
|
93
|
-
if (rowStart < 0) throw new Error("
|
|
75
|
+
if (rowStart < 0) throw new Error("preset/agent.cordis.yml missing tool-bootstrap config block");
|
|
94
76
|
const include = source.indexOf(" includeSubagents: true\n", rowStart);
|
|
95
|
-
if (include < 0) throw new Error("
|
|
77
|
+
if (include < 0) throw new Error("preset/agent.cordis.yml tool-bootstrap row missing includeSubagents");
|
|
96
78
|
const at = include + 26 + 1;
|
|
97
|
-
|
|
98
|
-
|
|
79
|
+
const lines = [` usePtcMode: ${usePtcMode}`];
|
|
80
|
+
if (bootstrapMaxTokens !== void 0 && Number.isSafeInteger(bootstrapMaxTokens) && bootstrapMaxTokens > 0) lines.push(` bootstrapMaxTokens: ${bootstrapMaxTokens}`);
|
|
81
|
+
return source.slice(0, at) + lines.join("\n") + "\n" + source.slice(at);
|
|
99
82
|
}
|
|
100
83
|
/** 给 subagent/subagent_fork 行加 dsh-router-standard 的 Flash 子代理方案;关闭时保持继承主会话路由。 */
|
|
101
84
|
function applySubagentFlash(source, enabled, provider, model) {
|
|
@@ -125,24 +108,140 @@ function applySubagentFlash(source, enabled, provider, model) {
|
|
|
125
108
|
${agentOptions}
|
|
126
109
|
persona: |-
|
|
127
110
|
${persona}`;
|
|
128
|
-
if (!out.includes(block)) throw new Error(`
|
|
111
|
+
if (!out.includes(block)) throw new Error(`preset/agent.cordis.yml missing ${target.id} row`);
|
|
129
112
|
out = out.replace(block, replaced);
|
|
130
113
|
}
|
|
131
114
|
return out;
|
|
132
115
|
}
|
|
116
|
+
/** 给生成的 tool-bootstrap.mjs 打补丁:子代理全量放行 + 可选的 PTC 呈现切换。 */
|
|
117
|
+
function patchToolBootstrap(source) {
|
|
118
|
+
source = source.replace(/\r\n/g, "\n");
|
|
119
|
+
const original = [
|
|
120
|
+
" const assembled = await next()",
|
|
121
|
+
" try {",
|
|
122
|
+
" const status = promotion.status(context.agent)"
|
|
123
|
+
].join("\n");
|
|
124
|
+
const replacement = [
|
|
125
|
+
" const assembled = await next()",
|
|
126
|
+
" try {",
|
|
127
|
+
" const agent = context.agent",
|
|
128
|
+
" if (agent === undefined) return assembled",
|
|
129
|
+
" // prompt-tool patch: subagents skip catalog narrowing and use assembled tools directly.",
|
|
130
|
+
" // Callers (such as dsh-mnemon) already filter assembled.tools through their own whitelists.",
|
|
131
|
+
" // New plugin tools with any prefix therefore appear in the subagent first session automatically.",
|
|
132
|
+
" agentBySession.set(agent.session, agent)",
|
|
133
|
+
" if ((agent.session?.header?.delegationDepth ?? 0) > 0) {",
|
|
134
|
+
" if (usePtcMode) applyCodePresentation(agent)",
|
|
135
|
+
" return assembled",
|
|
136
|
+
" }",
|
|
137
|
+
" const status = promotion.status(agent)"
|
|
138
|
+
].join("\n");
|
|
139
|
+
if (!source.includes(original)) throw new Error("tool-bootstrap.mjs assembled marker missing");
|
|
140
|
+
source = source.replace(original, replacement);
|
|
141
|
+
const allowedOriginal = "const ALLOWED_KEYS = new Set(['bootstrapTools', 'promoteOn', 'bootstrapMaxTokens', 'compactionTools', 'includeSubagents'])";
|
|
142
|
+
const allowedReplacement = "const ALLOWED_KEYS = new Set(['bootstrapTools', 'promoteOn', 'bootstrapMaxTokens', 'compactionTools', 'includeSubagents', 'usePtcMode'])";
|
|
143
|
+
if (!source.includes(allowedOriginal)) throw new Error("tool-bootstrap.mjs ALLOWED_KEYS marker missing");
|
|
144
|
+
source = source.replace(allowedOriginal, allowedReplacement);
|
|
145
|
+
const parseOriginal = " const includeSubagents = booleanOption(source.includeSubagents, 'includeSubagents', false)";
|
|
146
|
+
const parseReplacement = " const includeSubagents = booleanOption(source.includeSubagents, 'includeSubagents', false)\n const usePtcMode = booleanOption(source.usePtcMode, 'usePtcMode', true)";
|
|
147
|
+
if (!source.includes(parseOriginal)) throw new Error("tool-bootstrap.mjs includeSubagents parse marker missing");
|
|
148
|
+
source = source.replace(parseOriginal, parseReplacement);
|
|
149
|
+
const trackerOriginal = [" const promotion = createEpochPromotion(promoteEvents, { includeSubagents })", " ctx.on('session/event', (session, event) => promotion.observe(session, event))"].join("\n");
|
|
150
|
+
const trackerReplacement = [
|
|
151
|
+
" const promotion = createEpochPromotion(promoteEvents, { includeSubagents })",
|
|
152
|
+
"",
|
|
153
|
+
" // prompt-tool patch: optional Code Mode (PTC) wire presentation after promotion.",
|
|
154
|
+
" const presentationBySession = new WeakMap()",
|
|
155
|
+
" const agentBySession = new WeakMap()",
|
|
156
|
+
" const presentationState = (session) => {",
|
|
157
|
+
" let state = presentationBySession.get(session)",
|
|
158
|
+
" if (state === undefined) {",
|
|
159
|
+
" state = { applied: false, disposer: undefined }",
|
|
160
|
+
" presentationBySession.set(session, state)",
|
|
161
|
+
" }",
|
|
162
|
+
" return state",
|
|
163
|
+
" }",
|
|
164
|
+
" const applyCodePresentation = (agent) => {",
|
|
165
|
+
" const session = agent?.session",
|
|
166
|
+
" if (session === undefined) return",
|
|
167
|
+
" const state = presentationState(session)",
|
|
168
|
+
" if (state.applied) return",
|
|
169
|
+
" const tools = agent.ctx?.tools",
|
|
170
|
+
" if (tools === undefined || typeof tools.presentAs !== 'function') return",
|
|
171
|
+
" state.disposer = tools.presentAs('code')",
|
|
172
|
+
" state.applied = true",
|
|
173
|
+
" }",
|
|
174
|
+
" const releaseCodePresentation = (session) => {",
|
|
175
|
+
" const state = presentationBySession.get(session)",
|
|
176
|
+
" if (state === undefined) return",
|
|
177
|
+
" if (typeof state.disposer === 'function') {",
|
|
178
|
+
" try { state.disposer() } catch { /* never brick the session */ }",
|
|
179
|
+
" }",
|
|
180
|
+
" state.disposer = undefined",
|
|
181
|
+
" state.applied = false",
|
|
182
|
+
" }",
|
|
183
|
+
" ctx.on('session/event', (session, event) => promotion.observe(session, event))",
|
|
184
|
+
"",
|
|
185
|
+
" ctx.on('session/event', (session, event) => {",
|
|
186
|
+
" if (!usePtcMode) return",
|
|
187
|
+
" if (event.type === 'compaction/end') {",
|
|
188
|
+
" releaseCodePresentation(session)",
|
|
189
|
+
" return",
|
|
190
|
+
" }",
|
|
191
|
+
" if (event.type !== 'step/end' && event.type !== 'turn/end') return",
|
|
192
|
+
" const agent = agentBySession.get(session)",
|
|
193
|
+
" if (agent !== undefined && promotion.status(agent).promoted) applyCodePresentation(agent)",
|
|
194
|
+
" })"
|
|
195
|
+
].join("\n");
|
|
196
|
+
if (!source.includes(trackerOriginal)) throw new Error("tool-bootstrap.mjs promotion tracker marker missing");
|
|
197
|
+
source = source.replace(trackerOriginal, trackerReplacement);
|
|
198
|
+
const promotedStart = source.indexOf(" if (status.promoted) {\n // PROMOTED:");
|
|
199
|
+
const promotedEnd = source.indexOf(" // Controlled phase:", promotedStart);
|
|
200
|
+
if (promotedStart < 0 || promotedEnd < 0) throw new Error("tool-bootstrap.mjs promoted/controlled marker missing");
|
|
201
|
+
const promotedReplacement = [
|
|
202
|
+
" if (status.promoted) {",
|
|
203
|
+
" // prompt-tool patch: both modes keep the assembled catalog after promotion.",
|
|
204
|
+
" // usePtcMode switches the wire presentation instead of narrowing resident tools.",
|
|
205
|
+
" if (usePtcMode) applyCodePresentation(agent)",
|
|
206
|
+
" return assembled",
|
|
207
|
+
" }",
|
|
208
|
+
""
|
|
209
|
+
].join("\n");
|
|
210
|
+
source = source.slice(0, promotedStart) + promotedReplacement + source.slice(promotedEnd);
|
|
211
|
+
const residentOriginal = "const RESIDENT_DISCOVERY_TOOLS = ['dev_tool_search', 'skill_search', 'skill_load']\n\n";
|
|
212
|
+
if (source.includes(residentOriginal)) source = source.replace(residentOriginal, "");
|
|
213
|
+
const unlockedStart = source.indexOf("/**\n * Tool names the model explicitly unlocked via `dev_tool_search`");
|
|
214
|
+
const unlockedEnd = source.indexOf(" /** Narrow the assembled catalog", unlockedStart);
|
|
215
|
+
if (unlockedStart >= 0 && unlockedEnd > unlockedStart) source = source.slice(0, unlockedStart) + source.slice(unlockedEnd);
|
|
216
|
+
const headerStart = source.indexOf(" * POST-PROMOTION RESIDENT SET");
|
|
217
|
+
const headerEnd = source.indexOf(" * COMPACTION (local addition)", headerStart);
|
|
218
|
+
if (headerStart >= 0 && headerEnd > headerStart) {
|
|
219
|
+
const headerReplacement = [
|
|
220
|
+
" * POST-PROMOTION CATALOG (prompt-tool patch): after promotion both modes",
|
|
221
|
+
" * keep the assembled catalog. `usePtcMode` switches the wire presentation",
|
|
222
|
+
" * to Code Mode (PTC, single run_code) instead of narrowing the resident set.",
|
|
223
|
+
" * The controlled phase below still narrows the catalog before promotion and",
|
|
224
|
+
" * after compaction.",
|
|
225
|
+
""
|
|
226
|
+
].join(String.fromCharCode(10));
|
|
227
|
+
source = source.slice(0, headerStart) + headerReplacement + source.slice(headerEnd);
|
|
228
|
+
}
|
|
229
|
+
return source;
|
|
230
|
+
}
|
|
133
231
|
function buildCordis(prompt, options = {}) {
|
|
134
232
|
const anchorFirstTurn = options.anchorFirstTurn === true;
|
|
135
233
|
const anchorCustom = options.anchorCustom === true;
|
|
136
234
|
const guideCustom = options.guideCustom === true;
|
|
137
235
|
const guideText = typeof options.guideText === "string" && options.guideText.length > 0 ? options.guideText : "";
|
|
138
236
|
const injectPrompt = options.injectPrompt !== false;
|
|
237
|
+
const usePtcMode = options.usePtcMode !== false;
|
|
139
238
|
const anchorText = typeof options.anchorText === "string" && options.anchorText.length > 0 ? options.anchorText : "";
|
|
140
239
|
const indent = (s) => s.split(/\r?\n/).map((l) => l.length === 0 ? "" : " " + l).join("\n");
|
|
141
|
-
const up =
|
|
240
|
+
const up = applyToolBootstrapOptions(applySubagentFlash(readFileSync(CORDIS_TEMPLATE, "utf8").replace(/\r\n/g, "\n"), options.subagentFlash === true, typeof options.subagentFlashProvider === "string" && options.subagentFlashProvider.length > 0 ? options.subagentFlashProvider : "deepseek-official", typeof options.subagentFlashModel === "string" && options.subagentFlashModel.length > 0 ? options.subagentFlashModel : "deepseek-v4-flash"), usePtcMode, options.bootstrapMaxTokens);
|
|
142
241
|
const bootstrap = /^-\s+id:\s+tool-bootstrap\s*$/m.exec(up);
|
|
143
|
-
if (!bootstrap) throw new Error("
|
|
242
|
+
if (!bootstrap) throw new Error("preset/agent.cordis.yml missing tool-bootstrap row");
|
|
144
243
|
let cursor = up.indexOf("\n", bootstrap.index);
|
|
145
|
-
if (cursor < 0) throw new Error("
|
|
244
|
+
if (cursor < 0) throw new Error("preset/agent.cordis.yml has no top-level row after tool-bootstrap");
|
|
146
245
|
cursor += 1;
|
|
147
246
|
let insertAt = -1;
|
|
148
247
|
while (cursor <= up.length) {
|
|
@@ -155,7 +254,7 @@ function buildCordis(prompt, options = {}) {
|
|
|
155
254
|
if (lineEnd < 0) break;
|
|
156
255
|
cursor = lineEnd + 1;
|
|
157
256
|
}
|
|
158
|
-
if (insertAt < 0) throw new Error("
|
|
257
|
+
if (insertAt < 0) throw new Error("preset/agent.cordis.yml has no top-level row after tool-bootstrap");
|
|
159
258
|
const head = up.slice(0, insertAt);
|
|
160
259
|
const tail = up.slice(insertAt);
|
|
161
260
|
const separator = head.endsWith("\n\n") ? "" : head.endsWith("\n") ? "\n" : "\n\n";
|
|
@@ -191,4 +290,4 @@ function buildCordis(prompt, options = {}) {
|
|
|
191
290
|
return out;
|
|
192
291
|
}
|
|
193
292
|
//#endregion
|
|
194
|
-
export { buildCordis, parseFrontmatter };
|
|
293
|
+
export { buildCordis, parseFrontmatter, patchToolBootstrap };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-prompt-tool",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "DSH 插件:提示词工具,规范模型的思维链与回答内容,提供 Web UI 编辑 preset.md 与 AGENTS.md,并按 skills 目录注册可开关技能。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,30 +36,30 @@
|
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
39
|
-
"@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.
|
|
40
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.0-rc.
|
|
41
|
-
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.
|
|
42
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.
|
|
43
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.0-rc.
|
|
44
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.
|
|
45
|
-
"@deepseek-ai/dsh-settings": "^0.1.0-rc.
|
|
46
|
-
"@deepseek-ai/dsh-skill": "^0.1.0-rc.
|
|
47
|
-
"@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.
|
|
39
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.7",
|
|
40
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.0-rc.7",
|
|
41
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.7",
|
|
42
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.7",
|
|
43
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.0-rc.7",
|
|
44
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.7",
|
|
45
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
|
|
46
|
+
"@deepseek-ai/dsh-skill": "^0.1.0-rc.7",
|
|
47
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.7",
|
|
48
48
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
49
49
|
"react": "^18.2.0",
|
|
50
|
-
"@deepseek-ai/dsh-commands": "^0.1.0-rc.
|
|
50
|
+
"@deepseek-ai/dsh-commands": "^0.1.0-rc.7"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
54
|
-
"@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.
|
|
55
|
-
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.
|
|
56
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.
|
|
57
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.
|
|
58
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.
|
|
59
|
-
"@deepseek-ai/dsh-commands": "0.1.0-rc.
|
|
60
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.
|
|
61
|
-
"@deepseek-ai/dsh-settings": "^0.1.0-rc.
|
|
62
|
-
"@deepseek-ai/dsh-skill": "^0.1.0-rc.
|
|
54
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.7",
|
|
55
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.7",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.7",
|
|
57
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.7",
|
|
58
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.7",
|
|
59
|
+
"@deepseek-ai/dsh-commands": "^0.1.0-rc.7",
|
|
60
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.7",
|
|
61
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
|
|
62
|
+
"@deepseek-ai/dsh-skill": "^0.1.0-rc.7",
|
|
63
63
|
"@types/node": "^26.2.0",
|
|
64
64
|
"@types/react": "~18.3.1",
|
|
65
65
|
"clsx": "^2.1.1",
|
package/plan.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# dsh-plugin-prompt-tool 重新设计计划(v2)
|
|
2
|
-
> **v2.
|
|
2
|
+
> **v2.19 preset 脚本审查清理(当前实现)**:抽取 `shared.mjs` 公共工具;
|
|
3
|
+
> router-first-turn 不再重复清 contexts(交给 context-gate);router-first-turn /
|
|
4
|
+
> router-guide / prompt-injector 统一使用 epoch-aware 晋升状态;修正 stale 注释与未用变量。
|
|
5
|
+
|
|
6
|
+
> **v2.18 全部预设脚本自有化**:`preset/` 新增 shared / context-gate /
|
|
7
|
+
> compaction-epoch / custom-bash / instruction-hint / skill-search / tool-bootstrap 最终快照;
|
|
8
|
+
> 公共配置校验、晋升解析、消息工具合并进 `shared.mjs`;运行时不再直读 `upstream/` 任何文件,
|
|
9
|
+
> `upstream/` 仅作溯源与 sync 对照。
|
|
10
|
+
|
|
11
|
+
> **v2.17 agent.cordis.yml 脱离上游**:新增自有模板 `preset/agent.cordis.yml`;
|
|
12
|
+
> 运行时不再直读 `upstream/.../agent.cordis.yml`,动态项仅由 `buildCordis` 注入。
|
|
13
|
+
|
|
14
|
+
> **v2.16 使用 PTC 模式开关**:新增 `usePtcMode`(默认开启)。
|
|
15
|
+
> 开启=晋升后把 wire 切换为 Code Mode(PTC,单一 run_code),完整插件工具经生成 SDK 调用;
|
|
16
|
+
> 关闭=晋升后恢复原生完整工具目录;两种模式都不再依赖 `dev_tool_search`,生成 preset 时直接移除该行且不复制 `dev-tool-search.mjs`。
|
|
17
|
+
|
|
18
|
+
> **v2.15 上游同步**:同步上游到 `25f21ae`;移除
|
|
3
19
|
> `customBashPath` 生成时改写,直接沿用上游 custom-bash 运行时探测。
|
|
4
20
|
> 新增 `bootstrapMaxTokens` 开关(0=关闭;正整数写入上游 tool-bootstrap)。
|
|
5
21
|
|
|
@@ -61,8 +77,8 @@ artifacts/(本地调试残留)。
|
|
|
61
77
|
| 首轮输出预算 | `bootstrapMaxTokens: 1024` 固定 | **opt-in,默认无 cap**(adapter 默认 256000 流过) | 实测(issue #11):Minimal schema 在 256000 下锚定 5/5(`We need` 首行、`we` 1.4、`let me` 0.0),而 pwsh/read 等标准家族 schema 在 256000 下 11/11 全失败——**工具 schema 是决定性变量,不是 cap** |
|
|
62
78
|
| agent.cordis.yml 结构 | 无 Minimal 组 | 新增 `persistent-shell` 组(dsh-terminal + dsh-terminal-bash + dsh-tool-bash-persistent)与 `bootstrap-filesystem` 组(dsh-fs-local + dsh-tool-str-replace-editor),与官方 minimal 字节一致;`tool-bash` 行全平台禁用(避免 `bash` 名重复注册) | 完全复制;prompt-injector 仍插在 tool-bootstrap 行后 |
|
|
63
79
|
| tool-bootstrap 参数 | `shellTools`+`commonTools` | `bootstrapTools: [bash, str_replace_editor]`;cap 监听器仅在显式设置 cap 时注册 | prompt-injector 逻辑不变(we 检测 + promoted 检测与参数无关) |
|
|
64
|
-
| 验证工具 | 无 | `verify/run-verify.mjs` + `verify-runner.mjs`:preset-roster compose + CLI driver,`--stop-after-first-assistant` 只测首请求(秒级) | 本地 harness 源码未构建(`apps/cli/lib/bin.js` 不存在),暂继续用 prebuilt rc.
|
|
65
|
-
| cap 行为差异 | — | rc.5 源码 checkout 上 cap 能到达首请求;rc.6 prebuilt 包会覆盖为 adapterDefaults |
|
|
80
|
+
| 验证工具 | 无 | `verify/run-verify.mjs` + `verify-runner.mjs`:preset-roster compose + CLI driver,`--stop-after-first-assistant` 只测首请求(秒级) | 本地 harness 源码未构建(`apps/cli/lib/bin.js` 不存在),暂继续用 prebuilt rc.7 的 dsh web HTTP API;将来构建 harness 后可切换 |
|
|
81
|
+
| cap 行为差异 | — | rc.5 源码 checkout 上 cap 能到达首请求;rc.6 prebuilt 包会覆盖为 adapterDefaults;rc.7 修复 max-tokens 截断后的 replay 续跑 | 默认不设 cap;开启 bootstrapMaxTokens 时 rc.7 更稳 |
|
|
66
82
|
|
|
67
83
|
结论:**设计骨架不变**(完全复制原版 + prompt-injector 附加件),只是复制源更新为
|
|
68
84
|
新版文件,测试断言从 `pwsh/read + 1024` 改为 `bash/str_replace_editor + 无 cap`。
|