dsh-plugin-prompt-tool 0.6.0 → 0.6.2

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 CHANGED
@@ -50,12 +50,19 @@ dsh --profile prompt-tool # 首次启
50
50
  | 锚定 | `firstTurnAnchor` `firstTurnCustom` `firstTurnText` `firstTurnWord` `firstTurnBuild` `firstTurnInspect` `firstTurnDeep` |
51
51
  | 引导 | `guideCustom` `guideText` `guideComplexPattern` `guideWeak` `guideDeep` `buildPattern` `complexPattern` |
52
52
  | PTC/门控 | `usePtcMode` `bootstrapMaxTokens` `injectPrompt` `allowKinds` |
53
- | 主对话模型 | `modelProvider` `modelName` `modelReasoningEffort` `modelTemperature` `modelMaxTokens` |
54
- | 子代理模型 | `subagentModelProvider` `subagentModelName` `subagentReasoningEffort` `subagentTemperature` `subagentMaxTokens` |
55
53
  | 人设 | 模块列表的 `persona-main`(system-section + `deployment:persona`,complete 互斥 + suppressRuntimeContext);`subagentPersona`(子代理显式,缺省经 scope 链继承主会话) |
56
54
  | 工具集 | `toolFilterAllow` `toolFilterDeny`(子代理 toolFilter;主对话 tool-filter 模块共用) |
57
55
  | 深度 | `maxDepth`(0 禁止委派 / `provider-managed` / 正整数) |
58
56
 
57
+ 模型参数在 **preset.yml 顶层 `model` / `subagentModel` 段**(官方 `agent-default-model` 同构):
58
+
59
+ | 段 | 键 |
60
+ |---|---|
61
+ | `model`(主对话) | `provider` `name` `reasoningEffort` `temperature` `maxTokens` |
62
+ | `subagentModel`(子代理固定路由) | `provider` `name` `reasoningEffort` `temperature` `maxTokens` |
63
+
64
+ 读取时顶层段展平进 params 扁平键(`modelProvider` 等),旧扁平键双读兼容;保存时写顶层段并清理旧键(保存即迁移)。
65
+
59
66
  > 根目录 **`preset.yml`** 是配置参数齐全、逐项注释的完整模板,复制即得自定义预设起点。
60
67
 
61
68
  ## 提示词配置(六层全家桶)
@@ -7,8 +7,9 @@
7
7
  *
8
8
  * 插值规则:
9
9
  * - {{key}}:配置 variables 有值 → 替换;否则 ST 运行时宏(lastusermessage /
10
- * lastcharmessage 等,大小写不敏感)→ 会话事件提取;否则内置变量(DSH_HOME /
11
- * WORKSPACE / CWD)→ 替换;否则保留字面(宽容,未注册变量不抛错)。
10
+ * lastcharmessage 等,大小写不敏感)→ 会话事件提取;否则动态宏(roll/random/
11
+ * pick/chance/time/date 等,支持 {{name::arg}} 参数)→ 运行时计算;否则内置
12
+ * 变量(DSH_HOME / WORKSPACE / CWD)→ 替换;否则保留字面(宽容)。
12
13
  * - 键字符集:字母数字、下划线、点、中文、连字符(与 ST setvar/getvar 一致)。
13
14
  */
14
15
 
@@ -30,13 +31,62 @@ function lastMessageOf(session, type) {
30
31
  return ''
31
32
  }
32
33
 
33
- /** ST 运行时宏(会话上下文,大小写不敏感;无会话上下文时为空串——不残留字面)。 */
34
+ /** 逗号分隔随机选一个({{random::a,b,c}} / {{pick::a,b,c}})。 */
35
+ function pickRandom(arg) {
36
+ const items = String(arg ?? '').split(',').map((item) => item.trim()).filter((item) => item.length > 0)
37
+ if (items.length === 0) return ''
38
+ return items[Math.floor(Math.random() * items.length)]
39
+ }
40
+
41
+ /** 骰子表达式({{roll::2d6+3}} / {{roll::1d20}};非法表达式原样返回)。 */
42
+ function rollDice(arg) {
43
+ const text = String(arg ?? '').replace(/\s+/g, '').toLowerCase()
44
+ const match = text.match(/^(\d*)d(\d+)([+-]\d+)?$/)
45
+ if (match === null) return text
46
+ const count = match[1] === '' ? 1 : Number.parseInt(match[1], 10)
47
+ const sides = Number.parseInt(match[2], 10)
48
+ const modifier = match[3] === undefined ? 0 : Number.parseInt(match[3], 10)
49
+ if (!Number.isSafeInteger(count) || count <= 0 || count > 100 || !Number.isSafeInteger(sides) || sides <= 0) return text
50
+ let sum = 0
51
+ for (let index = 0; index < count; index++) sum += 1 + Math.floor(Math.random() * sides)
52
+ return String(sum + modifier)
53
+ }
54
+
55
+ /** 百分比概率({{chance::50}} → true/false)。 */
56
+ function chancePercent(arg) {
57
+ const value = Number(String(arg ?? '').replace('%', '').trim())
58
+ if (!Number.isFinite(value)) return ''
59
+ return Math.random() * 100 < value ? 'true' : 'false'
60
+ }
61
+
62
+ /** 本地 HH:MM({{time}})。 */
63
+ function formatTime(now) {
64
+ return `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
65
+ }
66
+
67
+ /** UTC YYYY-MM-DD({{date}})。 */
68
+ function formatDate(now) {
69
+ return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
70
+ }
71
+
72
+ /** ST 动态宏(大小写不敏感;函数签名 (arg) => string;无会话上下文时同样可用)。 */
34
73
  const DYNAMIC_MACROS = {
35
- lastusermessage: (session) => lastMessageOf(session, 'user/message'),
36
- lastcharmessage: (session) => lastMessageOf(session, 'assistant/message'),
74
+ lastusermessage: (_arg, session) => lastMessageOf(session, 'user/message'),
75
+ lastcharmessage: (_arg, session) => lastMessageOf(session, 'assistant/message'),
37
76
  // charIfNotGroup:ST 群聊时空、单聊为角色名;dsh 会话 header 无角色名
38
77
  //(单角色会话),统一返回空串(不残留字面,也不注入错误内容)。
39
78
  charifnotgroup: () => '',
79
+ random: (arg) => pickRandom(arg),
80
+ pick: (arg) => pickRandom(arg),
81
+ roll: (arg) => rollDice(arg),
82
+ chance: (arg) => chancePercent(arg),
83
+ time: () => formatTime(new Date()),
84
+ date: () => formatDate(new Date()),
85
+ weekday: () => '星期' + '日一二三四五六'[new Date().getDay()],
86
+ isotime: () => new Date().toISOString().slice(11, 19),
87
+ isodate: () => new Date().toISOString().slice(0, 10),
88
+ newline: () => '\n',
89
+ pipe: () => '|',
40
90
  }
41
91
 
42
92
  /** 模板变量插值:配置 variables 优先,ST 运行时宏次之,内置 {{DSH_HOME}} / {{WORKSPACE}} / {{CWD}} 兜底。 */
@@ -46,21 +96,21 @@ export function interpolateVariables(text, variables, session) {
46
96
  WORKSPACE: process.env.DSH_WORKSPACE ?? session?.header?.cwd ?? process.cwd(),
47
97
  CWD: session?.header?.cwd ?? process.cwd(),
48
98
  }
49
- return text.replace(/\{\{([A-Za-z0-9_.\u4e00-\u9fff-]+)\}\}/g, (whole, key) => {
99
+ return text.replace(/\{\{([A-Za-z0-9_.\u4e00-\u9fff-]+)(?:::(.*?))?\}\}/g, (whole, key, arg) => {
50
100
  if (Object.prototype.hasOwnProperty.call(variables, key)) return String(variables[key])
51
101
  const dynamic = DYNAMIC_MACROS[key.toLowerCase()]
52
- if (dynamic !== undefined) return dynamic(session)
102
+ if (dynamic !== undefined) return dynamic(arg, session)
53
103
  return Object.prototype.hasOwnProperty.call(builtins, key) ? builtins[key] : whole
54
104
  })
55
105
  }
56
106
 
57
107
  /** 仅做配置级静态变量替换(无 session 上下文的层)。 */
58
108
  export function interpolateStatic(text, variables) {
59
- return text.replace(/\{\{([A-Za-z0-9_.\u4e00-\u9fff-]+)\}\}/g, (whole, key) => {
109
+ return text.replace(/\{\{([A-Za-z0-9_.\u4e00-\u9fff-]+)(?:::(.*?))?\}\}/g, (whole, key, arg) => {
60
110
  if (Object.prototype.hasOwnProperty.call(variables, key)) return String(variables[key])
61
111
  // ST 运行时宏在无会话上下文(system-section 注册期)时替换为空串——
62
112
  // 不残留字面,也不触发官方 unknown variable 渲染报错。
63
113
  const dynamic = DYNAMIC_MACROS[key.toLowerCase()]
64
- return dynamic !== undefined ? dynamic(undefined) : whole
114
+ return dynamic !== undefined ? dynamic(arg, undefined) : whole
65
115
  })
66
116
  }
package/lib/index.d.mts CHANGED
@@ -198,6 +198,10 @@ interface PresetSpec {
198
198
  composition?: string;
199
199
  /** 扁平参数:全部直读(true/false、数字、字符串),on/off 作为兼容写法。 */
200
200
  params?: Record<string, unknown>;
201
+ /** 顶层模型段(主对话,官方 agent-default-model 同构):provider/name/reasoningEffort/temperature/maxTokens。 */
202
+ model?: Record<string, unknown>;
203
+ /** 顶层子代理模型段:provider/name/reasoningEffort/temperature/maxTokens。 */
204
+ subagentModel?: Record<string, unknown>;
201
205
  /** 预设级模板变量({{key}} 插值源;与引擎行为参数 params 分离,顶层 variables 段)。 */
202
206
  variables?: Record<string, string>;
203
207
  /** 模板变量插值开关(缺省 true = 启用;false = 停用,writePreset 不生成变量文件)。 */
package/lib/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { C as userPresetsDir, D as DEFAULT_SKILLS_DIR, E as DEFAULT_RESIDENT_AGENTS_PATH, O as LEGACY_CONTAINER_DIR, S as savePresetParams, T as DEFAULT_PRESET_DIR, _ as readPluginState, a as asString, b as resolvePresetDir, c as duplicateUserPreset, d as listPresets, f as loadPresetContent, g as parseImportedPresetId, h as packageEngineDir, i as applyModuleConfigs, k as LEGACY_USER_PRESETS_DIR, l as ensurePresetSeed, m as openPresetLocation, n as mergePromptConfigs, o as assertCompositionArray, p as loadPresetSpec, r as renderPromptConfigYaml, s as cloneBuiltinPreset, t as loadPromptConfigFiles, u as listBuiltinTemplates, v as removeUserPreset, w as writePluginState, x as resolvePresetParams, y as renderComposition } from "./prompt-configs-ThS4iXPg.mjs";
1
+ import { C as userPresetsDir, D as DEFAULT_SKILLS_DIR, E as DEFAULT_RESIDENT_AGENTS_PATH, O as LEGACY_CONTAINER_DIR, S as savePresetParams, T as DEFAULT_PRESET_DIR, _ as readPluginState, a as asString, b as resolvePresetDir, c as duplicateUserPreset, d as listPresets, f as loadPresetContent, g as parseImportedPresetId, h as packageEngineDir, i as applyModuleConfigs, k as LEGACY_USER_PRESETS_DIR, l as ensurePresetSeed, m as openPresetLocation, n as mergePromptConfigs, o as assertCompositionArray, p as loadPresetSpec, r as renderPromptConfigYaml, s as cloneBuiltinPreset, t as loadPromptConfigFiles, u as listBuiltinTemplates, v as removeUserPreset, w as writePluginState, x as resolvePresetParams, y as renderComposition } from "./prompt-configs-BgFw_bFf.mjs";
2
2
  import { settingsNamespace } from "@deepseek-ai/dsh-settings";
3
3
  import { appendFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
4
4
  import { parse, parseDocument, stringify } from "yaml";
@@ -1,4 +1,4 @@
1
- import { n as mergePromptConfigs, o as assertCompositionArray, p as loadPresetSpec, r as renderPromptConfigYaml, t as loadPromptConfigFiles, y as renderComposition } from "./prompt-configs-ThS4iXPg.mjs";
1
+ import { n as mergePromptConfigs, o as assertCompositionArray, p as loadPresetSpec, r as renderPromptConfigYaml, t as loadPromptConfigFiles, y as renderComposition } from "./prompt-configs-BgFw_bFf.mjs";
2
2
  import { fileURLToPath } from "node:url";
3
3
  //#region src/preset-core.ts
4
4
  /**
@@ -84,6 +84,28 @@ function loadPresetSpec(dir) {
84
84
  const parsed = parse(raw, { logLevel: "silent" });
85
85
  if (parsed === null || typeof parsed !== "object") throw new Error(`preset ${join(dir, "preset.yml")} is not a YAML map`);
86
86
  if (typeof parsed.id !== "string" || parsed.id.length === 0) parsed.id = basename(dir);
87
+ const flattenModelGroup = (source, mapping) => {
88
+ if (source === null || typeof source !== "object" || Array.isArray(source)) return;
89
+ if (parsed.params === null || typeof parsed.params !== "object" || Array.isArray(parsed.params)) parsed.params = {};
90
+ for (const [segmentKey, flatKey] of Object.entries(mapping)) {
91
+ const value = source[segmentKey];
92
+ if (value !== void 0) parsed.params[flatKey] = value;
93
+ }
94
+ };
95
+ flattenModelGroup(parsed.model, {
96
+ provider: "modelProvider",
97
+ name: "modelName",
98
+ reasoningEffort: "modelReasoningEffort",
99
+ temperature: "modelTemperature",
100
+ maxTokens: "modelMaxTokens"
101
+ });
102
+ flattenModelGroup(parsed.subagentModel, {
103
+ provider: "subagentModelProvider",
104
+ name: "subagentModelName",
105
+ reasoningEffort: "subagentReasoningEffort",
106
+ temperature: "subagentTemperature",
107
+ maxTokens: "subagentMaxTokens"
108
+ });
87
109
  return parsed;
88
110
  }
89
111
  /** 读取预设模板内容资产(presetText / agentsText);模板缺失时静默降级。
@@ -363,12 +385,28 @@ function openPresetLocation(id, presetRoot = userPresetsDir()) {
363
385
  * 通道残留——参数已并入 preset.yml,避免旧值覆盖新值)。
364
386
  */
365
387
  function savePresetParams(presetRoot, templateName, params, promptConfigs, variables, variablesEnabled) {
388
+ const MODEL_KEY_MAP = {
389
+ modelProvider: ["model", "provider"],
390
+ modelName: ["model", "name"],
391
+ modelReasoningEffort: ["model", "reasoningEffort"],
392
+ modelTemperature: ["model", "temperature"],
393
+ modelMaxTokens: ["model", "maxTokens"],
394
+ subagentModelProvider: ["subagentModel", "provider"],
395
+ subagentModelName: ["subagentModel", "name"],
396
+ subagentReasoningEffort: ["subagentModel", "reasoningEffort"],
397
+ subagentTemperature: ["subagentModel", "temperature"],
398
+ subagentMaxTokens: ["subagentModel", "maxTokens"]
399
+ };
366
400
  const file = join(presetRoot, templateName, "preset.yml");
367
401
  if (!existsSync(file)) throw new Error(`preset ${templateName} 无 preset.yml`);
368
402
  const doc = parseDocument(readFileSync(file, "utf8"), { logLevel: "silent" });
369
403
  if (params !== void 0) for (const [key, value] of Object.entries(params)) {
370
404
  if (value === void 0 || value === null || key.trim().length === 0) continue;
371
- doc.setIn(["params", key], value);
405
+ const segment = MODEL_KEY_MAP[key];
406
+ if (segment !== void 0) {
407
+ doc.setIn([segment[0], segment[1]], value);
408
+ doc.deleteIn(["params", key]);
409
+ } else doc.setIn(["params", key], value);
372
410
  }
373
411
  if (promptConfigs !== void 0) {
374
412
  const cleaned = promptConfigs.map((config) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-prompt-tool",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "DSH 插件:提示词工具,规范模型的思维链与回答内容,提供 Web UI 编辑 preset.md 与 AGENTS.md,并按 skills 目录注册可开关技能。",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,38 @@
1
+ # 模板:子代理状态 / 记忆维护(audience=subagent 专用注入)
2
+ # 用途:把 ST 的「角色状态跟踪 + 关系记忆」委派给子代理完成——引擎保持确定性
3
+ # (插值/匹配/注入),智能判断(何时更新、更新成什么)交给委派子代理,
4
+ # 写入经确定性工具边界(session_var 会话状态 / world_book note 持久记忆)。
5
+ # 接入:模板选择器「消息批层」插入;默认关闭,插入后启用并按需改 stateKeys。
6
+ id: subagent-maintenance
7
+ name: 子代理状态/记忆维护
8
+ enabled: false
9
+ layer: pre-step
10
+ strategy: static
11
+ position: after-user
12
+ dedupe: none
13
+ promotion: none
14
+ audience: subagent
15
+ modelScope: all
16
+ configKind: ordered
17
+ order: 2000
18
+ role: user
19
+ mergeMode: merged
20
+ sourceKind: ''
21
+ form: hint
22
+ summary: ''
23
+ identity:
24
+ field: plugin
25
+ value: subagent-maintenance
26
+ text: |-
27
+ 你是本会话的「状态与记忆维护者」,与创作类子代理职责互补:
28
+ - 状态(本会话临时,模板变量注入用):观察互动中明确的角色状态变化
29
+ (心情、态度、接受度等),用 session_var 工具 set 更新——变量名见下方
30
+ 「状态变量」清单;状态未变化时不要重复写入。
31
+ - 记忆(跨会话持久):有人际关系进展或重要事实时,用 world_book_upsert
32
+ 的 note 参数写入来源角色卡记忆(条目 id 带 chara- 前缀 → 角色卡
33
+ memory.md,跨会话跟随角色卡);无归属条目写入预设记忆。
34
+ - 克制:仅在状态确实变化时更新;不确定时保留原值;不编造未发生的内容。
35
+ 状态变量清单:{{stateKeys}}
36
+ variables:
37
+ stateKeys: 心情, 接受值, 关系进度
38
+ params: {}