dsh-custom-mode 2.0.1 → 2.1.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 +2 -2
- package/README.zh.md +3 -3
- package/client.js +5 -1
- package/index.mjs +52 -5
- package/locales.mjs +2 -0
- package/package.json +1 -1
package/README.i18n.yaml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# 双语配对一致性记录:两侧在「上次确认一致」时的 git blob 哈希。
|
|
2
2
|
# 两份文档权威相同——改完任意一侧,请把另一侧也改掉,然后重新记录:
|
|
3
3
|
# node tools/verify-translation-pairing.mjs --write
|
|
4
|
-
README.md:
|
|
5
|
-
README.zh.md:
|
|
4
|
+
README.md: d70fd341a9be167188f2e4d6117f93517d6221de
|
|
5
|
+
README.zh.md: aa63c0c8050a3832e9eab670fe77e0ba4dcabbb7
|
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ One command installs everything — the settings-page plugin, and the preset it
|
|
|
56
56
|
activation:
|
|
57
57
|
|
|
58
58
|
```sh
|
|
59
|
-
dsh plugin --profile web add dsh-custom-mode@2.0
|
|
59
|
+
dsh plugin --profile web add dsh-custom-mode@2.1.0 # pin the version to get this one for sure
|
|
60
60
|
# A bare `add dsh-custom-mode` is subject to pnpm's release cooldown (`minimumReleaseAge`, 1 day by
|
|
61
61
|
# default): for hours after a release it can silently install an OLDER version — measured: a bare
|
|
62
62
|
# install 38 minutes after 1.3.0 shipped landed on 1.0.3. Check what you got with `npm ls
|
|
@@ -396,7 +396,7 @@ installing again — your data is not touched:
|
|
|
396
396
|
|
|
397
397
|
```sh
|
|
398
398
|
# the pinned form: what you ask for is what you get
|
|
399
|
-
dsh plugin --profile web add dsh-custom-mode@2.0
|
|
399
|
+
dsh plugin --profile web add dsh-custom-mode@2.1.0
|
|
400
400
|
# then restart the DSH process that serves the web profile
|
|
401
401
|
```
|
|
402
402
|
|
package/README.zh.md
CHANGED
|
@@ -49,7 +49,7 @@ base mode / plugin switches / multi-assistant / multi-persona。
|
|
|
49
49
|
一条命令装完——设置页插件,以及它在首次激活时自动播种的 preset:
|
|
50
50
|
|
|
51
51
|
```sh
|
|
52
|
-
dsh plugin --profile web add dsh-custom-mode@2.0
|
|
52
|
+
dsh plugin --profile web add dsh-custom-mode@2.1.0 # 钉版本才能确定拿到这一版
|
|
53
53
|
# 不带版本号会受 pnpm 的发布冷却期影响(`minimumReleaseAge`,默认一天):发布后数小时内按名安装
|
|
54
54
|
# 可能**静默装到旧版** —— 实测 1.3.0 发布 38 分钟后按名安装装到了 1.0.3。用 profile 里的
|
|
55
55
|
# `npm ls dsh-custom-mode` 核对实际装到的版本,或像上面那样钉版本。
|
|
@@ -68,7 +68,7 @@ dsh `0.1.6-alpha.2` 起有插件管理页:**侧边栏 → 插件 → 添加插
|
|
|
68
68
|
|
|
69
69
|
| 输入 | 填什么 | 说明 |
|
|
70
70
|
| --- | --- | --- |
|
|
71
|
-
| **包名** | `dsh-custom-mode` | 受 pnpm 发布冷却期影响,可能装到旧版。要这一版请钉 `@2.0
|
|
71
|
+
| **包名** | `dsh-custom-mode` | 受 pnpm 发布冷却期影响,可能装到旧版。要这一版请钉 `@2.1.0` |
|
|
72
72
|
| **GitHub 仓库地址** | `https://github.com/BOWLUNA/dsh-custom-mode` | 指向**仓库根**即可 |
|
|
73
73
|
| **本地插件目录** | `<你 clone 的路径>` | 就是仓库根 —— 仓库根**本身**就是发布包 |
|
|
74
74
|
|
|
@@ -300,7 +300,7 @@ MIT
|
|
|
300
300
|
|
|
301
301
|
```sh
|
|
302
302
|
# 钉版本的写法:要哪版就是哪版
|
|
303
|
-
dsh plugin --profile web add dsh-custom-mode@2.0
|
|
303
|
+
dsh plugin --profile web add dsh-custom-mode@2.1.0
|
|
304
304
|
# 然后重启为该 profile 提供服务的 DSH 进程
|
|
305
305
|
```
|
|
306
306
|
|
package/client.js
CHANGED
|
@@ -127,6 +127,7 @@ try {
|
|
|
127
127
|
"meta.version": "插件版本",
|
|
128
128
|
"meta.versionHint": "未钉版本时,可能装到较旧的发布。要换版本,按 README 钉版本重装,然后重启。",
|
|
129
129
|
"api.saved": "已保存({name},{mode})。新会话使用新配置,当前会话不变。",
|
|
130
|
+
"api.conflict": "磁盘上的提示词已经变了,这次没有写入。先重新读取,再决定要不要覆盖。",
|
|
130
131
|
"api.savedWithExclusiveRows": "已保存({name},{mode})。已关闭不能同时启用的行:{rows}。",
|
|
131
132
|
"api.created": "已创建「{name}」。刷新后,新会话的模式菜单里会出现它。",
|
|
132
133
|
"api.savedPrompt": "已保存系统提示词({name})。下一步模型调用即生效。",
|
|
@@ -360,6 +361,7 @@ try {
|
|
|
360
361
|
"meta.version": "Plugin version",
|
|
361
362
|
"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.",
|
|
362
363
|
"api.saved": "Saved ({name}, {mode}). New sessions use it. The current session does not change.",
|
|
364
|
+
"api.conflict": "The prompt on disk has changed, so this save was not written. Reload, then decide whether to overwrite.",
|
|
363
365
|
"api.savedWithExclusiveRows": "Saved ({name}, {mode}). Turned off rows that cannot be on together: {rows}.",
|
|
364
366
|
"api.created": "Created {name}. Refresh once, then it appears in the mode menu for a new session.",
|
|
365
367
|
"api.savedPrompt": "System prompt saved ({name}). It applies on the next model step.",
|
|
@@ -791,7 +793,7 @@ try {
|
|
|
791
793
|
".cpfe-kids{display:flex;flex-direction:column;margin-left:0;padding-left:16px}",
|
|
792
794
|
".cpfe-row-meta{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}",
|
|
793
795
|
".cpfe-row-badges{display:flex;align-items:center;gap:4px;flex:0 1 auto;min-width:0}",
|
|
794
|
-
".cpfe-row{display:flex;gap:12px;align-items:center;box-sizing:border-box;min-height:
|
|
796
|
+
".cpfe-row{display:flex;gap:12px;align-items:center;box-sizing:border-box;min-height:74px;padding:16px 0;border:0;border-radius:0;background:none}",
|
|
795
797
|
".cpfe-editor{box-sizing:border-box;width:100%;min-height:120px;resize:vertical;padding:12px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font-family:var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);font-size:13px;line-height:20px}",
|
|
796
798
|
".cpfe-bar{display:flex;align-items:center;gap:var(--g);flex-wrap:wrap;margin-top:8px}",
|
|
797
799
|
".cpfe-disclosure{appearance:none;display:inline-flex;align-items:center;gap:6px;margin:8px 0 0;padding:0;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;font-weight:400;cursor:pointer}",
|
|
@@ -1661,6 +1663,7 @@ try {
|
|
|
1661
1663
|
const save = async () => {
|
|
1662
1664
|
setBusy(true)
|
|
1663
1665
|
try {
|
|
1666
|
+
const newest = Array.isArray(draft.history) && draft.history.length > 0 ? draft.history[0].n : undefined
|
|
1664
1667
|
const result = await postJson(ROUTES.state, {
|
|
1665
1668
|
id: draft.id,
|
|
1666
1669
|
mode: draft.mode,
|
|
@@ -1668,6 +1671,7 @@ try {
|
|
|
1668
1671
|
prompt: draft.prompt,
|
|
1669
1672
|
name: draft.name,
|
|
1670
1673
|
description: draft.description,
|
|
1674
|
+
...(typeof newest === "number" ? { baseN: newest } : {}),
|
|
1671
1675
|
})
|
|
1672
1676
|
if (result !== null && result !== undefined && result.ok === true) {
|
|
1673
1677
|
setFailed(false)
|
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,
|
|
@@ -492,6 +494,38 @@ export function readList(rows) {
|
|
|
492
494
|
*/
|
|
493
495
|
const writeChains = new Map()
|
|
494
496
|
|
|
497
|
+
/**
|
|
498
|
+
* 打开页面之后、按下保存之前,`prompt.md` 可能已被会话工具或手工改过。
|
|
499
|
+
*
|
|
500
|
+
* 先把磁盘上的那一版记进历史。页面带了 JSON 数字 `baseN`,且那个版本不是磁盘上这一版时,
|
|
501
|
+
* 拒绝写入:磁盘保持外部文本。不带 `baseN` 的旧客户端仍覆盖,但原文已经在历史里。
|
|
502
|
+
* 数字字符串不算 `baseN`。留痕自己失败时不挡住这次保存。
|
|
503
|
+
*
|
|
504
|
+
* @param {string} directory
|
|
505
|
+
* @param {string} prompt - the text this save wants to write.
|
|
506
|
+
* @param {unknown} baseN
|
|
507
|
+
* @returns {object|null} a `conflict` result, or null when the write may proceed.
|
|
508
|
+
*/
|
|
509
|
+
function refuseStalePrompt(directory, prompt, baseN) {
|
|
510
|
+
try {
|
|
511
|
+
const diskPrompt = readPrompt(directory)
|
|
512
|
+
if (diskPrompt.ok !== true || diskPrompt.text === prompt) return null
|
|
513
|
+
recordExternalChange(directory, diskPrompt.text)
|
|
514
|
+
if (typeof baseN === 'number' && readVersion(directory, baseN) !== diskPrompt.text) {
|
|
515
|
+
return {
|
|
516
|
+
ok: false,
|
|
517
|
+
code: 'conflict',
|
|
518
|
+
params: { baseN },
|
|
519
|
+
error: '磁盘上的提示词已经变了,这次保存没有写入。',
|
|
520
|
+
note: '磁盘上的提示词已经变了,这次保存没有写入。',
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
} catch {
|
|
524
|
+
/* 审计留痕失败不挡住这次保存 */
|
|
525
|
+
}
|
|
526
|
+
return null
|
|
527
|
+
}
|
|
528
|
+
|
|
495
529
|
function serializedWrite(key, work) {
|
|
496
530
|
const previous = writeChains.get(key) ?? Promise.resolve()
|
|
497
531
|
const next = previous.then(work, work)
|
|
@@ -509,12 +543,13 @@ function serializedWrite(key, work) {
|
|
|
509
543
|
* 的行集合也就不变,这是所有选项里破坏性最小的一个。
|
|
510
544
|
*
|
|
511
545
|
* @param {string} directory - the assistant's preset directory.
|
|
512
|
-
* @param {{id: string, mode: string, prompt: string, name: string, description: string, displayName?: string}} input
|
|
546
|
+
* @param {{id: string, mode: string, prompt: string, name: string, description: string, displayName?: string, baseN?: number}} input
|
|
513
547
|
* - 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).
|
|
548
|
+
* reports (the disk name when the request omitted one — issue #9). `baseN` is the same stale-save
|
|
549
|
+
* token as {@link saveState}: a JSON number only.
|
|
515
550
|
* @returns {object} the shape `saveState` returns, with `code: 'savedPromptOnly'`.
|
|
516
551
|
*/
|
|
517
|
-
export function savePromptOnly(directory, { id, mode, prompt, name, description, displayName }) {
|
|
552
|
+
export function savePromptOnly(directory, { id, mode, prompt, name, description, displayName, baseN }) {
|
|
518
553
|
// 与正常保存同一条纪律:提示词与 preset.yml 一起换名,失败则两个都不动。
|
|
519
554
|
let metaText = null
|
|
520
555
|
if (name !== '') {
|
|
@@ -522,6 +557,8 @@ export function savePromptOnly(directory, { id, mode, prompt, name, description,
|
|
|
522
557
|
if (rendered.ok !== true) return rendered
|
|
523
558
|
metaText = rendered.text
|
|
524
559
|
}
|
|
560
|
+
const conflict = refuseStalePrompt(directory, prompt, baseN)
|
|
561
|
+
if (conflict !== null) return conflict
|
|
525
562
|
try {
|
|
526
563
|
writeAtomicPair([
|
|
527
564
|
[promptFile(directory), prompt],
|
|
@@ -618,7 +655,15 @@ export function saveState(rows, input) {
|
|
|
618
655
|
// 还想改开关时明确拒绝,而不是悄悄丢掉用户的意图。
|
|
619
656
|
if (isBaseCompositionUnavailable(error)) {
|
|
620
657
|
if (overrides.size === 0 && existsSync(compositionFile(directory))) {
|
|
621
|
-
return savePromptOnly(directory, {
|
|
658
|
+
return savePromptOnly(directory, {
|
|
659
|
+
id,
|
|
660
|
+
mode,
|
|
661
|
+
prompt,
|
|
662
|
+
name,
|
|
663
|
+
description,
|
|
664
|
+
displayName: effectiveName,
|
|
665
|
+
baseN: input !== null && typeof input === 'object' ? input.baseN : undefined,
|
|
666
|
+
})
|
|
622
667
|
}
|
|
623
668
|
return {
|
|
624
669
|
ok: false,
|
|
@@ -650,6 +695,8 @@ export function saveState(rows, input) {
|
|
|
650
695
|
if (rendered.ok !== true) return rendered
|
|
651
696
|
metaText = rendered.text
|
|
652
697
|
}
|
|
698
|
+
const conflict = refuseStalePrompt(directory, prompt, input !== null && typeof input === 'object' ? input.baseN : undefined)
|
|
699
|
+
if (conflict !== null) return conflict
|
|
653
700
|
try {
|
|
654
701
|
writeAtomicPair([
|
|
655
702
|
[compositionFile(directory), composition],
|
package/locales.mjs
CHANGED
|
@@ -88,6 +88,7 @@ export const zh = {
|
|
|
88
88
|
'meta.version': '插件版本',
|
|
89
89
|
'meta.versionHint': '未钉版本时,可能装到较旧的发布。要换版本,按 README 钉版本重装,然后重启。',
|
|
90
90
|
'api.saved': '已保存({name},{mode})。新会话使用新配置,当前会话不变。',
|
|
91
|
+
'api.conflict': '磁盘上的提示词已经变了,这次没有写入。先重新读取,再决定要不要覆盖。',
|
|
91
92
|
'api.savedWithExclusiveRows': '已保存({name},{mode})。已关闭不能同时启用的行:{rows}。',
|
|
92
93
|
'api.created': '已创建「{name}」。刷新后,新会话的模式菜单里会出现它。',
|
|
93
94
|
'api.savedPrompt': '已保存系统提示词({name})。下一步模型调用即生效。',
|
|
@@ -343,6 +344,7 @@ export const en = {
|
|
|
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
346
|
'api.saved': 'Saved ({name}, {mode}). New sessions use it. The current session does not change.',
|
|
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
350
|
'api.savedPrompt': 'System prompt saved ({name}). It applies on the next model step.',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-custom-mode",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.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), pick its base mode, switch plugin rows one by one, and keep several assistants side by side — coding, chat or roleplay (RP) personas (multi-mode / multi-agent).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|