dsh-custom-mode 1.2.0 → 1.3.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/client.js +22 -0
- package/index.mjs +33 -0
- package/locales.mjs +8 -0
- package/package.json +1 -1
- package/preset/prompt-tool.mjs +56 -0
package/client.js
CHANGED
|
@@ -92,6 +92,10 @@ try {
|
|
|
92
92
|
"msg.reorderFailed": "调整顺序失败",
|
|
93
93
|
"msg.imported": "已导入到编辑器(还没有保存):检查后点「保存」。",
|
|
94
94
|
"msg.importFailed": "导入失败",
|
|
95
|
+
"warn.personaOffWithPrompt": "「身份(系统提示词)」这一行是关的,所以 prompt.md 不会被注入 —— 你写的提示词现在不起作用。要么打开这一行,要么清空提示词。",
|
|
96
|
+
"warn.toolOff": "「custom_prompt 工具」这一行是关的:会话里无法让 agent 改提示词,只能在本页改。",
|
|
97
|
+
"warn.noDescription": "没有描述:新建会话的模式选择器里会显示成「暂无描述」。",
|
|
98
|
+
"warn.noName": "没有名字:模式选择器里会显示成目录 id(例如 custom)。",
|
|
95
99
|
"history.label": "改动历史",
|
|
96
100
|
"history.pick": "选择要载入的版本…",
|
|
97
101
|
"history.load": "载入这一版",
|
|
@@ -231,6 +235,10 @@ try {
|
|
|
231
235
|
"msg.reorderFailed": "Could not reorder",
|
|
232
236
|
"msg.imported": "Imported into the editor (not saved yet) — review it, then click Save.",
|
|
233
237
|
"msg.importFailed": "Import failed",
|
|
238
|
+
"warn.personaOffWithPrompt": "The \"Identity (system prompt)\" row is off, so prompt.md is never injected — the prompt you wrote has no effect. Turn the row on, or clear the prompt.",
|
|
239
|
+
"warn.toolOff": "The \"custom_prompt tool\" row is off: the agent cannot change the prompt from inside a session, only this page can.",
|
|
240
|
+
"warn.noDescription": "No description: the new-session mode picker will show it as \"no description yet\".",
|
|
241
|
+
"warn.noName": "No name: the mode picker will show the directory id (e.g. custom).",
|
|
234
242
|
"history.label": "Change history",
|
|
235
243
|
"history.pick": "Pick a version to load…",
|
|
236
244
|
"history.load": "Load this version",
|
|
@@ -612,6 +620,8 @@ try {
|
|
|
612
620
|
// 改动历史:列表来自 state(只有元数据),正文点「载入这一版」时按需取。
|
|
613
621
|
history: Array.isArray(state.history) ? state.history : [],
|
|
614
622
|
historyPick: "",
|
|
623
|
+
// 「配置了却不生效」的告警码;文案按当前语言渲染。
|
|
624
|
+
warnings: Array.isArray(state.warnings) ? state.warnings : [],
|
|
615
625
|
}
|
|
616
626
|
}
|
|
617
627
|
|
|
@@ -1402,6 +1412,18 @@ try {
|
|
|
1402
1412
|
{ className: "cpfe" },
|
|
1403
1413
|
assistantList,
|
|
1404
1414
|
react.createElement("p", { className: "cpfe-note" }, t("assistant.switchHint")),
|
|
1415
|
+
// 配了却不生效的项:主动点名,而不是让用户对着"我明明写了"发呆。
|
|
1416
|
+
// `draft` 在没选中任何助手时是 null(列表还没加载完 / 一个都没有)—— 这里必须先守卫,
|
|
1417
|
+
// 否则整块设置页崩掉(实测:浏览器验收当场报 Cannot read properties of null)。
|
|
1418
|
+
draft === null || draft.warnings === undefined || draft.warnings.length === 0
|
|
1419
|
+
? null
|
|
1420
|
+
: react.createElement(
|
|
1421
|
+
"div",
|
|
1422
|
+
{ className: "cpfe-warns" },
|
|
1423
|
+
...draft.warnings.map((code) =>
|
|
1424
|
+
react.createElement("p", { key: code, className: "cpfe-warn" }, "⚠ " + t("warn." + code)),
|
|
1425
|
+
),
|
|
1426
|
+
),
|
|
1405
1427
|
...editorSections,
|
|
1406
1428
|
react.createElement(
|
|
1407
1429
|
"div",
|
package/index.mjs
CHANGED
|
@@ -211,6 +211,37 @@ function packagedPrompt() {
|
|
|
211
211
|
return packagedPromptCache
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
/**
|
|
215
|
+
* 「配置了,但不生效」的告警码。
|
|
216
|
+
*
|
|
217
|
+
* 这一条借自同生态的 whale-persona:它会主动点名"配了却没起作用"的项。这里对应四种**静默**的
|
|
218
|
+
* 自相矛盾 —— 每一种此前都只会让人以为"我明明写了提示词/描述,怎么没效果":
|
|
219
|
+
*
|
|
220
|
+
* - `personaOffWithPrompt`:身份行被关掉,`prompt.md` 根本不会被注入(最坑的一种);
|
|
221
|
+
* - `toolOff`:`custom_prompt` 工具行关掉 → 会话内改提示词不可用(设置页照旧);
|
|
222
|
+
* - `noDescription`:描述为空 → 新建会话的模式选择器里显示「暂无描述」;
|
|
223
|
+
* - `noName`:名字为空 → 选择器里显示成裸目录 id。
|
|
224
|
+
*
|
|
225
|
+
* 只返回**码**,文案由页面按语言渲染(双语文案的真值源在 locales.mjs 一处)。
|
|
226
|
+
*
|
|
227
|
+
* @param {string} text - the composition text.
|
|
228
|
+
* @param {string} prompt - the prompt file's content.
|
|
229
|
+
* @param {{name?: string, description?: string}} meta - `preset.yml`.
|
|
230
|
+
* @returns {string[]} warning codes, stable order.
|
|
231
|
+
*/
|
|
232
|
+
export function configWarnings(text, prompt, meta) {
|
|
233
|
+
const warnings = []
|
|
234
|
+
const rows = collectRows(text)
|
|
235
|
+
const find = (id) => rows.find((row) => row.id === id)
|
|
236
|
+
if (find('persona')?.disabled === true && typeof prompt === 'string' && prompt.trim() !== '') {
|
|
237
|
+
warnings.push('personaOffWithPrompt')
|
|
238
|
+
}
|
|
239
|
+
if (find('custom-prompt-tool')?.disabled === true) warnings.push('toolOff')
|
|
240
|
+
if (typeof meta?.description !== 'string' || meta.description.trim() === '') warnings.push('noDescription')
|
|
241
|
+
if (typeof meta?.name !== 'string' || meta.name.trim() === '') warnings.push('noName')
|
|
242
|
+
return warnings
|
|
243
|
+
}
|
|
244
|
+
|
|
214
245
|
export function readState(rows, id, options = {}) {
|
|
215
246
|
const directory = assistantDir(rows, id)
|
|
216
247
|
if (directory === undefined) return unknownAssistant(id)
|
|
@@ -242,6 +273,8 @@ export function readState(rows, id, options = {}) {
|
|
|
242
273
|
factoryPrompt: typeof options.factoryPrompt === 'string' ? options.factoryPrompt : null,
|
|
243
274
|
// 改动历史(只有元数据,正文按需取:见 GET /custom-mode/history)。
|
|
244
275
|
history: listHistory(directory),
|
|
276
|
+
// 「配置了却不生效」的告警码(文案在页面侧按语言渲染)。
|
|
277
|
+
warnings: configWarnings(text, prompt.ok === true ? prompt.text : '', meta),
|
|
245
278
|
}
|
|
246
279
|
}
|
|
247
280
|
|
package/locales.mjs
CHANGED
|
@@ -52,6 +52,10 @@ export const zh = {
|
|
|
52
52
|
'msg.reorderFailed': '调整顺序失败',
|
|
53
53
|
'msg.imported': '已导入到编辑器(还没有保存):检查后点「保存」。',
|
|
54
54
|
'msg.importFailed': '导入失败',
|
|
55
|
+
'warn.personaOffWithPrompt': '「身份(系统提示词)」这一行是关的,所以 prompt.md 不会被注入 —— 你写的提示词现在不起作用。要么打开这一行,要么清空提示词。',
|
|
56
|
+
'warn.toolOff': '「custom_prompt 工具」这一行是关的:会话里无法让 agent 改提示词,只能在本页改。',
|
|
57
|
+
'warn.noDescription': '没有描述:新建会话的模式选择器里会显示成「暂无描述」。',
|
|
58
|
+
'warn.noName': '没有名字:模式选择器里会显示成目录 id(例如 custom)。',
|
|
55
59
|
'history.label': '改动历史',
|
|
56
60
|
'history.pick': '选择要载入的版本…',
|
|
57
61
|
'history.load': '载入这一版',
|
|
@@ -208,6 +212,10 @@ export const en = {
|
|
|
208
212
|
'msg.reorderFailed': 'Could not reorder',
|
|
209
213
|
'msg.imported': 'Imported into the editor (not saved yet) — review it, then click Save.',
|
|
210
214
|
'msg.importFailed': 'Import failed',
|
|
215
|
+
'warn.personaOffWithPrompt': 'The "Identity (system prompt)" row is off, so prompt.md is never injected — the prompt you wrote has no effect. Turn the row on, or clear the prompt.',
|
|
216
|
+
'warn.toolOff': 'The "custom_prompt tool" row is off: the agent cannot change the prompt from inside a session, only this page can.',
|
|
217
|
+
'warn.noDescription': 'No description: the new-session mode picker will show it as "no description yet".',
|
|
218
|
+
'warn.noName': 'No name: the mode picker will show the directory id (e.g. custom).',
|
|
211
219
|
'history.label': 'Change history',
|
|
212
220
|
'history.pick': 'Pick a version to load…',
|
|
213
221
|
'history.load': 'Load this version',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-custom-mode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
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",
|
package/preset/prompt-tool.mjs
CHANGED
|
@@ -40,6 +40,7 @@ const META_PATH = fileURLToPath(new URL('./preset.yml', import.meta.url))
|
|
|
40
40
|
const MISSING = '(prompt.md 不存在,当前模式会退回上一次成功的提示词文本)'
|
|
41
41
|
|
|
42
42
|
/** Name used when neither the composition nor `preset.yml` supplies one. */
|
|
43
|
+
const TOOL_NAME = 'custom_prompt'
|
|
43
44
|
const FALLBACK_MODE_NAME = '自定义模式'
|
|
44
45
|
|
|
45
46
|
/**
|
|
@@ -201,12 +202,67 @@ function makeDefinition(modeName) {
|
|
|
201
202
|
}
|
|
202
203
|
}
|
|
203
204
|
|
|
205
|
+
/**
|
|
206
|
+
* 自我改提示词的审批闸门。
|
|
207
|
+
*
|
|
208
|
+
* `action: "write"` 会**覆盖整个系统提示词**,而调用它的可能是模型自己 —— 一次提示词注入就足以
|
|
209
|
+
* 让它重写自己的身份。此前这条路径没有任何门:工具直接落盘。
|
|
210
|
+
*
|
|
211
|
+
* 现在它走平台的审批缝:`tools/pre-execute` 是一个 waterfall,返回 `{kind:'ask'}` 会由审批策略决定
|
|
212
|
+
* 怎么处理。**实测(0.1.6-alpha.2,一次性实例)**:
|
|
213
|
+
*
|
|
214
|
+
* - 审批策略为 `ask`(权限预设 `workspace-write`):页面出现「等待审批」行,带这里的 reason,
|
|
215
|
+
* 按钮为「拒绝 / 允许一次」;点「允许一次」后工具真的执行,prompt.md 变成写入的内容;
|
|
216
|
+
* - 审批策略为 `never`(权限预设 `danger-full-access`):**不弹窗,直接判为拒绝**
|
|
217
|
+
* (轨迹里是 `Error: the user rejected tool "custom_prompt"`,文件未被修改)。
|
|
218
|
+
*
|
|
219
|
+
* 也就是说这条闸门**最坏情况是"改不成"而不是"悄悄改成了"** —— 与平台的默认语义一致
|
|
220
|
+
* (类型注释原话:missing approval support turns `ask` into denial)。
|
|
221
|
+
*
|
|
222
|
+
* 只拦 `write`:`read` 不改变任何东西,弹窗只会让人麻木。
|
|
223
|
+
*
|
|
224
|
+
* @param {object} ctx - the preset row's scope.
|
|
225
|
+
* @returns {boolean} whether a gate was registered.
|
|
226
|
+
*/
|
|
227
|
+
function registerApprovalGate(ctx) {
|
|
228
|
+
if (typeof ctx.on !== 'function') return false
|
|
229
|
+
ctx.effect(
|
|
230
|
+
() => ctx.on('tools/pre-execute', (exec, next) => {
|
|
231
|
+
if (exec === null || typeof exec !== 'object' || exec.name !== TOOL_NAME) return next()
|
|
232
|
+
const args = exec.arguments
|
|
233
|
+
const action = args !== null && typeof args === 'object' ? args.action : undefined
|
|
234
|
+
// 只拦写入;未指定 action 时工具按 read 处理,同样不拦。
|
|
235
|
+
if (action !== 'write') return next()
|
|
236
|
+
const text = typeof args.text === 'string' ? args.text : ''
|
|
237
|
+
const firstLine = text.split('\n').find((line) => line.trim() !== '') ?? ''
|
|
238
|
+
return {
|
|
239
|
+
kind: 'ask',
|
|
240
|
+
reason:
|
|
241
|
+
'把「' + resolveModeName(undefined) + '」的系统提示词整体替换为 ' + String(text.length) + ' 字符' +
|
|
242
|
+
(firstLine === '' ? '' : ':' + firstLine.trim().slice(0, 60)) +
|
|
243
|
+
'(写入 ' + PROMPT_PATH + ')',
|
|
244
|
+
}
|
|
245
|
+
}),
|
|
246
|
+
'custom-prompt.approval-gate',
|
|
247
|
+
)
|
|
248
|
+
return true
|
|
249
|
+
}
|
|
250
|
+
|
|
204
251
|
/** The tool registry is a hard dependency; without it there is no tool. */
|
|
205
252
|
export const inject = ['tools']
|
|
206
253
|
|
|
207
254
|
export function apply(ctx, config = {}) {
|
|
208
255
|
const definition = makeDefinition(resolveModeName(config))
|
|
209
256
|
ctx.effect(() => ctx.tools.register(definition), 'custom-prompt.tool')
|
|
257
|
+
|
|
258
|
+
// 审批闸门。宿主若不支持 `tools/pre-execute`(比本插件声明的下限还老的构建),这里会**明确**
|
|
259
|
+
// 说一声再继续 —— 降级是有的,但不许静默。
|
|
260
|
+
if (registerApprovalGate(ctx) !== true) {
|
|
261
|
+
console.error(
|
|
262
|
+
'custom-mode: 这个宿主没有 tools/pre-execute 事件,会话内改写系统提示词的审批闸门**未启用**' +
|
|
263
|
+
'(设置页不受影响)。请升级 DSH,或把「custom_prompt 工具」这一行关掉。',
|
|
264
|
+
)
|
|
265
|
+
}
|
|
210
266
|
}
|
|
211
267
|
|
|
212
268
|
/**
|