dsh-custom-mode 2.0.1 → 2.2.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.i18n.yaml +2 -2
- package/README.md +34 -394
- package/README.zh.md +34 -290
- package/client.js +19 -15
- package/composition.mjs +6 -6
- package/defaults.mjs +59 -0
- package/index.mjs +66 -9
- package/locales.mjs +10 -8
- package/package.json +13 -24
package/index.mjs
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* GET /api/custom-mode — every assistant this feature manages.
|
|
8
8
|
* GET /api/custom-mode/state?id=… — one assistant: base mode, rows, switches, prompt.
|
|
9
9
|
* GET /api/custom-mode/history?id=…&n=… — one prompt revision (metadata comes with the state).
|
|
10
|
-
* POST /api/custom-mode/state — { id, mode, overrides, prompt, name, description }:
|
|
10
|
+
* POST /api/custom-mode/state — { id, mode, overrides, prompt, name, description, baseN? }:
|
|
11
11
|
* validate, render a fresh `agent.cordis.yml`, write both files.
|
|
12
|
+
* `baseN` is the newest history number the page loaded. A JSON
|
|
13
|
+
* number that is not the prompt now on disk refuses the write.
|
|
12
14
|
* POST /api/custom-mode/create — { name, description, posture?, locale? }: seed a new
|
|
13
15
|
* assistant. Omitting `posture` (or sending `develop`) is the
|
|
14
16
|
* historical path: the packaged prompt and the standard base,
|
|
@@ -53,7 +55,8 @@ import {
|
|
|
53
55
|
} from './preset-backend/index.mjs'
|
|
54
56
|
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
55
57
|
import { dirname, join } from 'node:path'
|
|
56
|
-
import { PROMPT_PATH, COMPOSITION_PATH, ROUTE_PATH, PRESET_DIR } from './paths.mjs'
|
|
58
|
+
import { PROMPT_PATH, COMPOSITION_PATH, ROUTE_PATH, PRESET_DIR, dshHome } from './paths.mjs'
|
|
59
|
+
import { ensureUnsetDefaultPreset } from './defaults.mjs'
|
|
57
60
|
import {
|
|
58
61
|
allowlistOverrides,
|
|
59
62
|
applyRowExclusivity,
|
|
@@ -492,6 +495,38 @@ export function readList(rows) {
|
|
|
492
495
|
*/
|
|
493
496
|
const writeChains = new Map()
|
|
494
497
|
|
|
498
|
+
/**
|
|
499
|
+
* 打开页面之后、按下保存之前,`prompt.md` 可能已被会话工具或手工改过。
|
|
500
|
+
*
|
|
501
|
+
* 先把磁盘上的那一版记进历史。页面带了 JSON 数字 `baseN`,且那个版本不是磁盘上这一版时,
|
|
502
|
+
* 拒绝写入:磁盘保持外部文本。不带 `baseN` 的旧客户端仍覆盖,但原文已经在历史里。
|
|
503
|
+
* 数字字符串不算 `baseN`。留痕自己失败时不挡住这次保存。
|
|
504
|
+
*
|
|
505
|
+
* @param {string} directory
|
|
506
|
+
* @param {string} prompt - the text this save wants to write.
|
|
507
|
+
* @param {unknown} baseN
|
|
508
|
+
* @returns {object|null} a `conflict` result, or null when the write may proceed.
|
|
509
|
+
*/
|
|
510
|
+
function refuseStalePrompt(directory, prompt, baseN) {
|
|
511
|
+
try {
|
|
512
|
+
const diskPrompt = readPrompt(directory)
|
|
513
|
+
if (diskPrompt.ok !== true || diskPrompt.text === prompt) return null
|
|
514
|
+
recordExternalChange(directory, diskPrompt.text)
|
|
515
|
+
if (typeof baseN === 'number' && readVersion(directory, baseN) !== diskPrompt.text) {
|
|
516
|
+
return {
|
|
517
|
+
ok: false,
|
|
518
|
+
code: 'conflict',
|
|
519
|
+
params: { baseN },
|
|
520
|
+
error: '磁盘上的提示词已经变了,这次保存没有写入。',
|
|
521
|
+
note: '磁盘上的提示词已经变了,这次保存没有写入。',
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
} catch {
|
|
525
|
+
/* 审计留痕失败不挡住这次保存 */
|
|
526
|
+
}
|
|
527
|
+
return null
|
|
528
|
+
}
|
|
529
|
+
|
|
495
530
|
function serializedWrite(key, work) {
|
|
496
531
|
const previous = writeChains.get(key) ?? Promise.resolve()
|
|
497
532
|
const next = previous.then(work, work)
|
|
@@ -509,12 +544,13 @@ function serializedWrite(key, work) {
|
|
|
509
544
|
* 的行集合也就不变,这是所有选项里破坏性最小的一个。
|
|
510
545
|
*
|
|
511
546
|
* @param {string} directory - the assistant's preset directory.
|
|
512
|
-
* @param {{id: string, mode: string, prompt: string, name: string, description: string, displayName?: string}} input
|
|
547
|
+
* @param {{id: string, mode: string, prompt: string, name: string, description: string, displayName?: string, baseN?: number}} input
|
|
513
548
|
* - the validated save; `name` decides whether `preset.yml` is written, `displayName` is what the response
|
|
514
|
-
* reports (the disk name when the request omitted one — issue #9).
|
|
549
|
+
* reports (the disk name when the request omitted one — issue #9). `baseN` is the same stale-save
|
|
550
|
+
* token as {@link saveState}: a JSON number only.
|
|
515
551
|
* @returns {object} the shape `saveState` returns, with `code: 'savedPromptOnly'`.
|
|
516
552
|
*/
|
|
517
|
-
export function savePromptOnly(directory, { id, mode, prompt, name, description, displayName }) {
|
|
553
|
+
export function savePromptOnly(directory, { id, mode, prompt, name, description, displayName, baseN }) {
|
|
518
554
|
// 与正常保存同一条纪律:提示词与 preset.yml 一起换名,失败则两个都不动。
|
|
519
555
|
let metaText = null
|
|
520
556
|
if (name !== '') {
|
|
@@ -522,6 +558,8 @@ export function savePromptOnly(directory, { id, mode, prompt, name, description,
|
|
|
522
558
|
if (rendered.ok !== true) return rendered
|
|
523
559
|
metaText = rendered.text
|
|
524
560
|
}
|
|
561
|
+
const conflict = refuseStalePrompt(directory, prompt, baseN)
|
|
562
|
+
if (conflict !== null) return conflict
|
|
525
563
|
try {
|
|
526
564
|
writeAtomicPair([
|
|
527
565
|
[promptFile(directory), prompt],
|
|
@@ -542,7 +580,7 @@ export function savePromptOnly(directory, { id, mode, prompt, name, description,
|
|
|
542
580
|
code: 'savedPromptOnly',
|
|
543
581
|
// 响应里的名字用**磁盘上的那个**(issue #9):请求省略 name 时不该把 params.name 报成裸目录 id。
|
|
544
582
|
params: { name: (displayName === undefined || displayName === '' ? name : displayName) === '' ? id : (displayName === undefined || displayName === '' ? name : displayName), mode },
|
|
545
|
-
note: '
|
|
583
|
+
note: '已保存系统提示词(本机取不到基础模式的出厂组成,插件开关与基础模式未改动)。下一步组装时会重读这个文件。',
|
|
546
584
|
}
|
|
547
585
|
}
|
|
548
586
|
|
|
@@ -618,7 +656,15 @@ export function saveState(rows, input) {
|
|
|
618
656
|
// 还想改开关时明确拒绝,而不是悄悄丢掉用户的意图。
|
|
619
657
|
if (isBaseCompositionUnavailable(error)) {
|
|
620
658
|
if (overrides.size === 0 && existsSync(compositionFile(directory))) {
|
|
621
|
-
return savePromptOnly(directory, {
|
|
659
|
+
return savePromptOnly(directory, {
|
|
660
|
+
id,
|
|
661
|
+
mode,
|
|
662
|
+
prompt,
|
|
663
|
+
name,
|
|
664
|
+
description,
|
|
665
|
+
displayName: effectiveName,
|
|
666
|
+
baseN: input !== null && typeof input === 'object' ? input.baseN : undefined,
|
|
667
|
+
})
|
|
622
668
|
}
|
|
623
669
|
return {
|
|
624
670
|
ok: false,
|
|
@@ -650,6 +696,8 @@ export function saveState(rows, input) {
|
|
|
650
696
|
if (rendered.ok !== true) return rendered
|
|
651
697
|
metaText = rendered.text
|
|
652
698
|
}
|
|
699
|
+
const conflict = refuseStalePrompt(directory, prompt, input !== null && typeof input === 'object' ? input.baseN : undefined)
|
|
700
|
+
if (conflict !== null) return conflict
|
|
653
701
|
try {
|
|
654
702
|
writeAtomicPair([
|
|
655
703
|
[compositionFile(directory), composition],
|
|
@@ -683,7 +731,7 @@ export function saveState(rows, input) {
|
|
|
683
731
|
params: { name: displayName, mode, rows: autoOff.join(', ') },
|
|
684
732
|
note:
|
|
685
733
|
'已保存(' + displayName + ',基础模式 ' + mode + ')。这两套壳注册同名工具、不能同时启用 —— ' +
|
|
686
|
-
'已自动关掉:' + autoOff.join('、') + '
|
|
734
|
+
'已自动关掉:' + autoOff.join('、') + '。下一步组装时会重读已保存的提示词。',
|
|
687
735
|
}
|
|
688
736
|
}
|
|
689
737
|
return {
|
|
@@ -692,7 +740,7 @@ export function saveState(rows, input) {
|
|
|
692
740
|
mode,
|
|
693
741
|
code: 'saved',
|
|
694
742
|
params: { name: displayName, mode },
|
|
695
|
-
note: '已保存(' + displayName + ',基础模式 ' + mode + '
|
|
743
|
+
note: '已保存(' + displayName + ',基础模式 ' + mode + ')。下一步组装时会重读已保存的提示词。',
|
|
696
744
|
}
|
|
697
745
|
}
|
|
698
746
|
|
|
@@ -1077,6 +1125,15 @@ export function apply(ctx) {
|
|
|
1077
1125
|
console.error('custom-mode: 初始化 preset 目录时出现意外错误(已忽略): ' + describe(error))
|
|
1078
1126
|
}
|
|
1079
1127
|
|
|
1128
|
+
try {
|
|
1129
|
+
const presetDefault = ensureUnsetDefaultPreset(dshHome(), LEGACY_ID)
|
|
1130
|
+
if (presetDefault.wrote) {
|
|
1131
|
+
console.log('custom-mode: settings.yaml 里没有 agent-presets.default,已写成 ' + LEGACY_ID + '。已有的值不会改。')
|
|
1132
|
+
}
|
|
1133
|
+
} catch (error) {
|
|
1134
|
+
console.error('custom-mode: 没有写 agent-presets.default: ' + describe(error))
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1080
1137
|
try {
|
|
1081
1138
|
ctx.inject(WEB_SERVICES, (scope) => {
|
|
1082
1139
|
// Compatibility guard: this plugin reads host APIs that a future DSH release could
|
package/locales.mjs
CHANGED
|
@@ -87,10 +87,11 @@ export const zh = {
|
|
|
87
87
|
'msg.repaired': '已修复',
|
|
88
88
|
'meta.version': '插件版本',
|
|
89
89
|
'meta.versionHint': '未钉版本时,可能装到较旧的发布。要换版本,按 README 钉版本重装,然后重启。',
|
|
90
|
-
'api.saved': '已保存({name},{mode}
|
|
90
|
+
'api.saved': '已保存({name},{mode})。下一步组装时会重读已保存的提示词。',
|
|
91
|
+
'api.conflict': '磁盘上的提示词已经变了,这次没有写入。先重新读取,再决定要不要覆盖。',
|
|
91
92
|
'api.savedWithExclusiveRows': '已保存({name},{mode})。已关闭不能同时启用的行:{rows}。',
|
|
92
93
|
'api.created': '已创建「{name}」。刷新后,新会话的模式菜单里会出现它。',
|
|
93
|
-
'api.savedPrompt': '已保存系统提示词({name}
|
|
94
|
+
'api.savedPrompt': '已保存系统提示词({name})。下一步组装时会重读这个文件。',
|
|
94
95
|
'api.duplicated': '已从「{from}」复制。两份互不影响。',
|
|
95
96
|
'api.deleted': '已删除「{name}」。正在使用它的会话不受影响。',
|
|
96
97
|
'api.reordered': '顺序已保存。新会话的模式菜单按此排列。',
|
|
@@ -169,7 +170,7 @@ export const zh = {
|
|
|
169
170
|
'rows.hint': '关闭的行,这个助手不会使用。',
|
|
170
171
|
|
|
171
172
|
'prompt.heading': '系统提示词',
|
|
172
|
-
'prompt.hint': '
|
|
173
|
+
'prompt.hint': '保存后,下一步组装模型输入时重读这个文件。',
|
|
173
174
|
|
|
174
175
|
'status.enabled': '已启用',
|
|
175
176
|
'status.disabled': '已停用',
|
|
@@ -186,7 +187,7 @@ export const zh = {
|
|
|
186
187
|
'msg.unsaved': '有未保存的修改',
|
|
187
188
|
'msg.readFailed': '读取失败',
|
|
188
189
|
'msg.saveFailed': '保存失败',
|
|
189
|
-
'msg.saved': '
|
|
190
|
+
'msg.saved': '已保存。下一步组装时会重读这个文件。',
|
|
190
191
|
// 把「一句已翻译的话 + 一个底层细节」拼起来时,中间那个标点也必须**按语言**走 ——
|
|
191
192
|
// 硬编码全角冒号会让英文界面在出错那一刻出现中文标点(issue #7)。
|
|
192
193
|
'status.detail': '{message}:{detail}',
|
|
@@ -342,10 +343,11 @@ export const en = {
|
|
|
342
343
|
'msg.repaired': 'Repaired',
|
|
343
344
|
'meta.version': 'Plugin version',
|
|
344
345
|
'meta.versionHint': 'Without a pinned version, an older release may be installed. To change it, reinstall with the version pin from the README, then restart.',
|
|
345
|
-
'api.saved': 'Saved ({name}, {mode}).
|
|
346
|
+
'api.saved': 'Saved ({name}, {mode}). The next model step rereads the saved prompt.',
|
|
347
|
+
'api.conflict': 'The prompt on disk has changed, so this save was not written. Reload, then decide whether to overwrite.',
|
|
346
348
|
'api.savedWithExclusiveRows': 'Saved ({name}, {mode}). Turned off rows that cannot be on together: {rows}.',
|
|
347
349
|
'api.created': 'Created {name}. Refresh once, then it appears in the mode menu for a new session.',
|
|
348
|
-
'api.savedPrompt': 'System prompt saved ({name}).
|
|
350
|
+
'api.savedPrompt': 'System prompt saved ({name}). The next model step rereads this file.',
|
|
349
351
|
'api.duplicated': 'Copied from {from}. The two are independent from now on.',
|
|
350
352
|
'api.deleted': 'Deleted {name}. Sessions already using it are unchanged.',
|
|
351
353
|
'api.reordered': 'Order saved. New sessions list modes in this order.',
|
|
@@ -424,7 +426,7 @@ export const en = {
|
|
|
424
426
|
'rows.hint': 'A row that is off is not used by this assistant.',
|
|
425
427
|
|
|
426
428
|
'prompt.heading': 'System prompt',
|
|
427
|
-
'prompt.hint': 'After you save, the next model step
|
|
429
|
+
'prompt.hint': 'After you save, the next model step rereads this file.',
|
|
428
430
|
|
|
429
431
|
'status.enabled': 'Enabled',
|
|
430
432
|
'status.disabled': 'Disabled',
|
|
@@ -441,7 +443,7 @@ export const en = {
|
|
|
441
443
|
'msg.unsaved': 'Unsaved changes',
|
|
442
444
|
'msg.readFailed': 'Load failed',
|
|
443
445
|
'msg.saveFailed': 'Save failed',
|
|
444
|
-
'msg.saved': 'Saved.
|
|
446
|
+
'msg.saved': 'Saved. The next model step rereads this file.',
|
|
445
447
|
'status.detail': '{message}: {detail}',
|
|
446
448
|
|
|
447
449
|
// ── row labels ────────────────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-custom-mode",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "Edit a DeepSeek Harness (dsh) mode's system prompt, base mode, and plugin switches.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"author": "bowluna <ai@bowluna.com>",
|
|
7
8
|
"main": "./index.mjs",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": "./index.mjs",
|
|
@@ -23,31 +24,11 @@
|
|
|
23
24
|
"deepseek-harness",
|
|
24
25
|
"deepseek",
|
|
25
26
|
"dsh-plugin",
|
|
26
|
-
"cordis",
|
|
27
27
|
"plugin",
|
|
28
28
|
"agent-preset",
|
|
29
|
-
"agent-mode",
|
|
30
29
|
"custom-mode",
|
|
31
|
-
"multi-mode",
|
|
32
|
-
"assistant",
|
|
33
30
|
"system-prompt",
|
|
34
|
-
"
|
|
35
|
-
"prompt-editor",
|
|
36
|
-
"prompt-engineering",
|
|
37
|
-
"persona",
|
|
38
|
-
"multi-agent",
|
|
39
|
-
"roleplay",
|
|
40
|
-
"chat",
|
|
41
|
-
"settings-page",
|
|
42
|
-
"自定义模式",
|
|
43
|
-
"系统提示词",
|
|
44
|
-
"提示词编辑",
|
|
45
|
-
"多助手",
|
|
46
|
-
"角色扮演",
|
|
47
|
-
"deepseek-desktop",
|
|
48
|
-
"agent-preset",
|
|
49
|
-
"persona-editor",
|
|
50
|
-
"dshpreset"
|
|
31
|
+
"prompt-editor"
|
|
51
32
|
],
|
|
52
33
|
"dsh": {
|
|
53
34
|
"bundle": {
|
|
@@ -69,6 +50,7 @@
|
|
|
69
50
|
"paths.mjs",
|
|
70
51
|
"locales.mjs",
|
|
71
52
|
"journal.mjs",
|
|
53
|
+
"defaults.mjs",
|
|
72
54
|
"cordis.patch.yml",
|
|
73
55
|
"LICENSE",
|
|
74
56
|
"postures.mjs",
|
|
@@ -78,7 +60,14 @@
|
|
|
78
60
|
"scripts": {
|
|
79
61
|
"test": "node test/run.mjs",
|
|
80
62
|
"verify-translation-pairing": "node tools/verify-translation-pairing.mjs",
|
|
81
|
-
"verify-doc-numbers": "node tools/verify-doc-numbers.mjs"
|
|
63
|
+
"verify-doc-numbers": "node tools/verify-doc-numbers.mjs",
|
|
64
|
+
"verify-version-consistency": "node tools/verify-version-consistency.mjs",
|
|
65
|
+
"verify-browser-literals": "node tools/verify-browser-literals.mjs",
|
|
66
|
+
"boot-check": "node tools/boot-check.mjs",
|
|
67
|
+
"union-mount-check": "node tools/union-mount-check.mjs",
|
|
68
|
+
"session-trace": "node tools/session-trace.mjs",
|
|
69
|
+
"browser-verify": "node tools/browser-verify.mjs",
|
|
70
|
+
"upgrade-preflight": "node tools/upgrade-preflight.mjs"
|
|
82
71
|
},
|
|
83
72
|
"peerDependencies": {
|
|
84
73
|
"@deepseek-ai/dsh": ">=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0 || >=0.1.7-alpha.1 <0.2.0-0 || >=0.2.0-0 <0.3.0-0 || >=0.2.1-alpha.1 <0.3.0-0"
|