dsh-custom-mode 1.4.1 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-custom-mode",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
4
4
  "description": "Custom modes and custom prompts for DeepSeek Harness (dsh): edit a mode's system prompt on the settings page (it takes effect on the next model step), choose its base mode, switch plugins row by row, and keep several assistants side by side.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -142,25 +142,29 @@ function makeDefinition(modeName) {
142
142
  return {
143
143
  name: 'custom_prompt',
144
144
  description:
145
- 'Read or replace the system prompt of the 「' +
145
+ 'Read, replace or append to the system prompt of the 「' +
146
146
  modeName +
147
147
  '」 custom agent preset. ' +
148
- 'The prompt is a plain file; this tool reads it (action "read", the default) ' +
149
- 'or overwrites it wholesale (action "write" with text). A write takes effect ' +
148
+ 'The prompt is a plain file; this tool reads it (action "read", the default), ' +
149
+ 'overwrites it wholesale (action "write" with text) or adds text at the end ' +
150
+ '(action "append" with text — one call, instead of reading the whole prompt and ' +
151
+ 'writing it back to add a line). Any change takes effect ' +
150
152
  "on this session's next model step — no restart needed — and only affects " +
151
- 'sessions running this preset. Use it when the user asks to change ' +
152
- 'their system prompt or wants to see what it currently says.',
153
+ 'sessions running this preset. Both "write" and "append" ask the user for approval ' +
154
+ 'first. Use it when the user asks to change their system prompt, to remember a rule, ' +
155
+ 'or wants to see what it currently says.',
153
156
  parameters: {
154
157
  type: 'object',
155
158
  properties: {
156
159
  action: {
157
160
  type: 'string',
158
- enum: ['read', 'write'],
159
- description: 'Read the current prompt (default) or replace it.',
161
+ enum: ['read', 'write', 'append'],
162
+ description: 'Read the current prompt (default), replace it, or append text to it.',
160
163
  },
161
164
  text: {
162
165
  type: 'string',
163
- description: 'Required when action is "write": the complete new prompt text.',
166
+ description:
167
+ 'Required when action is "write" (the complete new prompt) or "append" (the text to add at the end).',
164
168
  },
165
169
  },
166
170
  },
@@ -183,6 +187,35 @@ function makeDefinition(modeName) {
183
187
  }
184
188
  }
185
189
 
190
+ if (action === 'append') {
191
+ if (typeof args.text !== 'string' || args.text.trim() === '') {
192
+ return '追加被拒绝:action 为 "append" 时必须提供非空的 text。'
193
+ }
194
+ let current = ''
195
+ try {
196
+ current = readFileSync(PROMPT_PATH, 'utf8')
197
+ } catch {
198
+ // 文件不存在就当作从空开始:读取器本来就对缺失文件有回退,追加没有理由不工作。
199
+ }
200
+ // 分隔:保证两块之间恰好一个换行,不在文件里堆空行。
201
+ const base = current === '' || current.endsWith('\n') ? current : current + '\n'
202
+ const added = args.text.endsWith('\n') ? args.text : args.text + '\n'
203
+ const combined = base + added
204
+ // 校验的是**合并之后**的完整文本:追加同样不能把未注册变量带进文件。
205
+ const verdict = checkPromptText(combined)
206
+ if (verdict.ok !== true) return '追加被拒绝(合并后的提示词未通过校验):' + verdict.error
207
+ try {
208
+ mkdirSync(dirname(PROMPT_PATH), { recursive: true })
209
+ writeAtomic(PROMPT_PATH, combined)
210
+ return (
211
+ '已追加到 ' + PROMPT_PATH + '(新增 ' + String(added.length) + ' 字符,现共 ' +
212
+ String(combined.length) + ' 字符)。本会话下一步模型调用即使用新提示词。'
213
+ )
214
+ } catch (error) {
215
+ return '追加失败:' + String((error && error.message) || error)
216
+ }
217
+ }
218
+
186
219
  if (typeof args.text !== 'string' || args.text.trim() === '') {
187
220
  return '写入被拒绝:action 为 "write" 时必须提供非空的 text。'
188
221
  }
@@ -231,14 +264,15 @@ function registerApprovalGate(ctx) {
231
264
  if (exec === null || typeof exec !== 'object' || exec.name !== TOOL_NAME) return next()
232
265
  const args = exec.arguments
233
266
  const action = args !== null && typeof args === 'object' ? args.action : undefined
234
- // 只拦写入;未指定 action 时工具按 read 处理,同样不拦。
235
- if (action !== 'write') return next()
267
+ // 拦所有会改文件的动作:write 与 append 都在改系统提示词;未指定 action 时按 read 处理,不拦。
268
+ if (action !== 'write' && action !== 'append') return next()
269
+ const verb = action === 'append' ? '追加到' : '整体替换为'
236
270
  const text = typeof args.text === 'string' ? args.text : ''
237
271
  const firstLine = text.split('\n').find((line) => line.trim() !== '') ?? ''
238
272
  return {
239
273
  kind: 'ask',
240
274
  reason:
241
- '把「' + resolveModeName(undefined) + '」的系统提示词整体替换为 ' + String(text.length) + ' 字符' +
275
+ '把「' + resolveModeName(undefined) + '」的系统提示词' + verb + ' ' + String(text.length) + ' 字符' +
242
276
  (firstLine === '' ? '' : ':' + firstLine.trim().slice(0, 60)) +
243
277
  '(写入 ' + PROMPT_PATH + ')',
244
278
  }