dsh-custom-mode 1.11.1 → 1.11.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.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: 4009df09f4f352850312dbeefb0f4f8729ba1e45
5
- README.zh.md: dc907b21aa6d277d5ad0f91b2c7acd40d3fd3e42
4
+ README.md: 31ff076c2ce3710b0e4597e8091d6edc74e5ecb8
5
+ README.zh.md: 23c1e72f1c796320950b5eead938967c8ec6f062
package/README.md CHANGED
@@ -59,7 +59,7 @@ One command installs everything — the settings-page plugin, and the preset it
59
59
  activation:
60
60
 
61
61
  ```sh
62
- dsh plugin --profile web add dsh-custom-mode@1.11.1 # pin the version to get this one for sure
62
+ dsh plugin --profile web add dsh-custom-mode@1.11.2 # pin the version to get this one for sure
63
63
  # A bare `add dsh-custom-mode` is subject to pnpm's release cooldown (`minimumReleaseAge`, 1 day by
64
64
  # default): for hours after a release it can silently install an OLDER version — measured: a bare
65
65
  # install 38 minutes after 1.3.0 shipped landed on 1.0.3. Check what you got with `npm ls
@@ -118,7 +118,7 @@ Electron shell**, so this plugin's page renders there unchanged. One install pat
118
118
  too and points you at the app, so the two agree instead of one silently working around the other.
119
119
  (A lab simulation of the profile shape can override it with `DSH_ALLOW_DESKTOP_PROFILE=1`.)
120
120
  **The app's own bundled CLI is the exception** — `<install>\resources\runtime\cli\bin\dsh.cmd` is allowed
121
- to operate on that profile (measured on 1.11.1: `add dsh-custom-mode@1.11.1` → `+ dsh-custom-mode 1.11.1`).
121
+ to operate on that profile (measured on 1.11.1: `add dsh-custom-mode@1.11.2` → `+ dsh-custom-mode 1.11.1`).
122
122
  It is a launcher, not a dsh feature: it starts Electron with `ELECTRON_RUN_AS_NODE=1` and runs the
123
123
  desktop-host CLI, so it is the app talking to its own profile rather than something driving from outside.
124
124
  - **Nothing else to do on the preset side**: `$DSH_HOME/.agent-presets/` is product data shared by the
@@ -430,7 +430,7 @@ installing again — your data is not touched:
430
430
 
431
431
  ```sh
432
432
  # the pinned form: what you ask for is what you get
433
- dsh plugin --profile web add dsh-custom-mode@1.11.1
433
+ dsh plugin --profile web add dsh-custom-mode@1.11.2
434
434
  # then restart the DSH process that serves the web profile
435
435
  ```
436
436
 
package/README.zh.md CHANGED
@@ -48,7 +48,7 @@ base mode / plugin switches / multi-assistant / multi-persona。
48
48
  一条命令装完——设置页插件,以及它在首次激活时自动播种的 preset:
49
49
 
50
50
  ```sh
51
- dsh plugin --profile web add dsh-custom-mode@1.11.1 # 钉版本才能确定拿到这一版
51
+ dsh plugin --profile web add dsh-custom-mode@1.11.2 # 钉版本才能确定拿到这一版
52
52
  # 不带版本号会受 pnpm 的发布冷却期影响(`minimumReleaseAge`,默认一天):发布后数小时内按名安装
53
53
  # 可能**静默装到旧版** —— 实测 1.3.0 发布 38 分钟后按名安装装到了 1.0.3。用 profile 里的
54
54
  # `npm ls dsh-custom-mode` 核对实际装到的版本,或像上面那样钉版本。
@@ -98,7 +98,7 @@ dsh `0.1.6-alpha.2` 起有插件管理页:**侧边栏 → 插件 → 添加插
98
98
  `cordis.patch.yml`。所以 `install.sh` 也**拒绝** `--profile desktop` 并指向应用内安装 —— 两边一致,
99
99
  而不是某一方偷偷绕过去。(只想在本机模拟 profile 形状时可设 `DSH_ALLOW_DESKTOP_PROFILE=1`。)
100
100
  **应用自带的捆绑 CLI 是例外**:`<安装目录>\resources\runtime\cli\bin\dsh.cmd` 可以操作那个 profile
101
- (1.11.1 实测:`add dsh-custom-mode@1.11.1` → `+ dsh-custom-mode 1.11.1`)。它是启动器而不是 dsh 的
101
+ (1.11.1 实测:`add dsh-custom-mode@1.11.2` → `+ dsh-custom-mode 1.11.1`)。它是启动器而不是 dsh 的
102
102
  新功能 —— 它用 `ELECTRON_RUN_AS_NODE=1` 起 Electron 去跑 desktop-host 的 CLI,所以那是**应用在跟自己的
103
103
  profile 说话**,不是从外面驱动它。
104
104
  - **预设那一侧不用做任何事**:`$DSH_HOME/.agent-presets/` 是桌面端与 CLI 共享的产品数据,
@@ -330,7 +330,7 @@ MIT
330
330
 
331
331
  ```sh
332
332
  # 钉版本的写法:要哪版就是哪版
333
- dsh plugin --profile web add dsh-custom-mode@1.11.1
333
+ dsh plugin --profile web add dsh-custom-mode@1.11.2
334
334
  # 然后重启为该 profile 提供服务的 DSH 进程
335
335
  ```
336
336
 
package/client.js CHANGED
@@ -61,14 +61,14 @@ try {
61
61
  const ZH = {
62
62
  "nav": "自定义模式",
63
63
  "assistant.heading": "助手",
64
- "assistant.hint": "每个助手就是一个独立模式:有自己的系统提示词、基础模式和插件开关,互不影响。新建会话时在模式选择器里挑一个。",
64
+ "assistant.hint": "每个助手即一个独立模式:独立的系统提示词、基础模式与插件开关。新建会话时在模式选择器中选择。",
65
65
  "assistant.empty": "还没有助手,用下面的输入框新建一个。",
66
66
  "assistant.loadingList": "正在读取助手列表…",
67
67
  "assistant.newPlaceholder": "新助手的名字(例如:写作助手)",
68
68
  "assistant.meta": "标识",
69
69
  "assistant.copyName": "「{name}」副本",
70
70
  "assistant.broken": "组成文件有问题",
71
- "assistant.switchHint": "切换助手不会丢掉未保存的修改:每个助手的草稿各自留着,列表上的「未保存」标记就是它。",
71
+ "assistant.switchHint": "切换助手保留各自的未保存草稿;列表中的「未保存」标记即为此。",
72
72
  "btn.create": "新增助手",
73
73
  "btn.creating": "创建中…",
74
74
  "btn.duplicate": "复制一份",
@@ -86,7 +86,7 @@ try {
86
86
  "msg.createFailed": "创建失败",
87
87
  "msg.deleteFailed": "删除失败",
88
88
  "msg.nameRequired": "请先给新助手起个名字。",
89
- "msg.readOnlyHint": "这一页只管理本工具创建的助手;手写的 preset 不在这里,也不会被改写。",
89
+ "msg.readOnlyHint": "仅管理本插件创建的助手;手写 preset 不计入,也不会被改写。",
90
90
  "msg.reordered": "顺序已保存:新建会话时的模式选择器按这个顺序排列。",
91
91
  "msg.reorderFailed": "调整顺序失败",
92
92
  "msg.imported": "已导入到编辑器(还没有保存):检查后点「保存」。",
@@ -169,7 +169,7 @@ try {
169
169
  "history.label": "改动历史",
170
170
  "history.pick": "选择要载入的版本…",
171
171
  "history.load": "载入这一版",
172
- "history.hint": "每次保存、以及会话内工具或手工改动,都会在这里留一版;载入只改草稿,保存前不落盘。",
172
+ "history.hint": "每次保存、会话内工具改动或手工编辑都会在此留一版;载入只改草稿,保存前不落盘。",
173
173
  "history.by.settings": "设置页保存",
174
174
  "history.by.external": "会话内/手工改动",
175
175
  "msg.loadFailed": "载入这一版失败",
@@ -184,15 +184,15 @@ try {
184
184
  "delete.close": "关闭",
185
185
  "delete.plainConfirm": "确定永久删除「{name}」吗?该操作无法撤销。",
186
186
  "name.heading": "模式名称",
187
- "name.hint": "改名只影响显示(模式选择器与上面的助手列表),内部标识和已有会话不受影响。新建会话即可看到新名称。",
187
+ "name.hint": "仅影响显示名(模式选择器与助手列表);内部标识与已有会话不变,新建会话后生效。",
188
188
  "name.placeholder": "自定义模式",
189
189
  "name.descriptionPlaceholder": "模式描述(显示在模式选择器里,可留空)",
190
190
  "mode.heading": "基础模式",
191
- "mode.hint": "选一个官方模式作为底子,下面再按行微调。注意:底子只决定「行集合」与工具能力 —— 本模式的 persona 行始终替换掉底子那一行(提示词由你编辑,complete: false),底子的提示词语义不会被继承。改完保存后,新建会话即生效,不需要重启。",
191
+ "mode.hint": "底子只决定「行集合」与工具能力:本模式的身份行始终替换底子那一行(提示词由你编辑,complete: false),底子的提示语语义不继承。保存后新建会话即生效,无需重启。",
192
192
  "rows.heading": "插件开关",
193
- "rows.hint": "逐行控制这个模式挂载哪些插件,和官方插件列表一样按行铺开。没拨过的行保持官方默认(含平台判断);你手动拨了就以你的为准。",
193
+ "rows.hint": "逐行控制本模式挂载的插件。未拨动的行保持官方默认(含平台判断);手动拨动后以你的为准。",
194
194
  "prompt.heading": "系统提示词",
195
- "prompt.hint": "这段文本在每个模型调用前重新读取,所以保存后下一步即生效,且只影响使用这个助手的会话。「导入」把文件读进编辑器(未保存前不写入任何东西);「导出」把当前文本存成 .md 文件;「恢复出厂提示词」把出厂模板填回编辑器(同样要保存才写入)。",
195
+ "prompt.hint": "每次模型调用前重新读取:保存后下一步生效,仅影响使用本助手的会话。「导入」读文件进编辑器(保存前不写入);「导出」存为 .md;「恢复出厂提示词」填回出厂模板(同样需保存)。",
196
196
  "status.enabled": "已启用",
197
197
  "status.disabled": "已停用",
198
198
  "status.changed": "已改",
@@ -280,14 +280,14 @@ try {
280
280
  const EN = {
281
281
  "nav": "Custom mode",
282
282
  "assistant.heading": "Assistants",
283
- "assistant.hint": "Each assistant is an independent mode: its own system prompt, base mode and plugin switches, with no effect on the others. Pick one in the mode picker when you start a new session.",
283
+ "assistant.hint": "Each assistant is an independent mode: its own system prompt, base mode and plugin switches. Pick one in the mode picker when you start a new session.",
284
284
  "assistant.empty": "No assistants yet — create one with the field below.",
285
285
  "assistant.loadingList": "Loading assistants…",
286
286
  "assistant.newPlaceholder": "Name of the new assistant (e.g. Writing assistant)",
287
287
  "assistant.meta": "ID",
288
288
  "assistant.copyName": "{name} (copy)",
289
289
  "assistant.broken": "Composition problem",
290
- "assistant.switchHint": "Switching assistants never loses unsaved edits: each one keeps its own draft, and the \"Unsaved\" marker in the list is exactly that.",
290
+ "assistant.switchHint": "Switching assistants keeps each draft; the \"Unsaved\" marker in the list is exactly that.",
291
291
  "btn.create": "New assistant",
292
292
  "btn.creating": "Creating…",
293
293
  "btn.duplicate": "Duplicate",
@@ -305,7 +305,7 @@ try {
305
305
  "msg.createFailed": "Could not create",
306
306
  "msg.deleteFailed": "Could not delete",
307
307
  "msg.nameRequired": "Give the new assistant a name first.",
308
- "msg.readOnlyHint": "This page manages only the assistants this tool created; a hand-written preset is not listed here and is never rewritten.",
308
+ "msg.readOnlyHint": "Manages only the assistants this plugin created; a hand-written preset is not listed and is never rewritten.",
309
309
  "msg.reordered": "Order saved: the mode picker for new sessions follows it.",
310
310
  "msg.reorderFailed": "Could not reorder",
311
311
  "msg.imported": "Imported into the editor (not saved yet) — review it, then click Save.",
@@ -403,11 +403,11 @@ try {
403
403
  "delete.close": "Close",
404
404
  "delete.plainConfirm": "Permanently delete \"{name}\"? This cannot be undone.",
405
405
  "name.heading": "Mode name",
406
- "name.hint": "Renaming changes only what is displayed (the mode picker and the assistant list above); the internal id and existing sessions are unaffected. Start a new session to see it.",
406
+ "name.hint": "Changes the display name only (mode picker and assistant list); the internal id and existing sessions are unaffected. Applies to the next new session.",
407
407
  "name.placeholder": "Custom mode",
408
408
  "name.descriptionPlaceholder": "Mode description (shown in the mode picker, optional)",
409
409
  "mode.heading": "Base mode",
410
- "mode.hint": "Pick an official mode as the base, then adjust it row by row. Note: the base only decides which rows exist — this mode always replaces the base's persona row with its own reader (complete: false), so the base's prompt semantics are not inherited. A save applies to the next new session; no restart needed.",
410
+ "mode.hint": "The base decides the row set and the tool abilities: this mode always replaces the base's persona row with its own reader (complete: false), so the base's prompt semantics are not inherited. A save applies to the next new session; no restart needed.",
411
411
  "rows.heading": "Plugin switches",
412
412
  "rows.hint": "Control row by row which plugins this mode mounts, laid out like the official plugin list. Untouched rows keep the official default (platform conditions included); your manual choice wins.",
413
413
  "prompt.heading": "System prompt",
@@ -699,14 +699,14 @@ try {
699
699
  ".cpfe-h{margin:0 0 2px;font-size:14px;line-height:22px;font-weight:500;color:var(--dsw-alias-label-primary)}",
700
700
  ".cpfe-sub{margin:0 0 8px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
701
701
  // 区块引言:官方是标题下的一行 12px 灰字 —— 没有折叠按钮,也没有那个蓝色圆点。
702
- ".cpfe-hint{display:flex;flex-direction:column;gap:2px;margin:0 0 4px}",
702
+ ".cpfe-hint{display:flex;flex-direction:column;gap:2px;margin:0 0 6px}",
703
703
  ".cpfe-hint-line{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
704
704
  ".cpfe-hint-detail{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
705
705
  // 描述:多行、自适应高度(没有多行输入组件,所以用 textarea + 同一批语义变量)
706
706
  ".cpfe-desc{box-sizing:border-box;min-height:56px;max-height:200px;resize:vertical;padding:8px 12px;border-radius:10px;border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;margin-bottom:8px}",
707
707
  ".cpfe-base-pending{color:var(--dsw-alias-state-warn-primary)}",
708
- ".cpfe-note{display:block;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary)}",
709
- ".cpfe-mono{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
708
+ ".cpfe-note{display:block;margin:8px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
709
+ ".cpfe-mono{display:block;margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
710
710
  ".cpfe-pills{display:flex;flex-wrap:wrap;gap:6px;align-items:center}",
711
711
  ".cpfe-picker{display:flex;align-items:center;gap:8px;margin:2px 0 4px}",
712
712
  // 壳的菜单浮层靠一个独立的 backing 元素上色(实测在 0.1.7-rc.2 + headless 下那块是透明的,
@@ -719,11 +719,11 @@ try {
719
719
  ".cpfe-actions{display:flex;flex-wrap:wrap;gap:var(--g);align-items:center;margin-top:4px}",
720
720
  ".cpfe-newrow{display:flex;gap:var(--g);align-items:center;flex-wrap:wrap;margin-top:10px}",
721
721
  ".cpfe-field{display:flex;width:100%;margin-bottom:8px}",
722
- ".cpfe-row-head{font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary)}",
722
+ ".cpfe-row-head{font-size:14px;line-height:20px;color:var(--dsw-alias-label-primary)}",
723
723
  ".cpfe-row-switch{flex:0 0 auto}",
724
724
  ".cpfe-row-line{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0}",
725
- ".cpfe-row-note{min-height:18px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);overflow:hidden;text-overflow:ellipsis}",
726
- ".cpfe-row-toggle{flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:11px;line-height:1}",
725
+ ".cpfe-row-note{min-height:18px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
726
+ ".cpfe-row-toggle{flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:12px;line-height:1}",
727
727
  ".cpfe-row-toggle:hover{background:var(--dsw-alias-bg-layer-2)}",
728
728
  ".cpfe-row-open{border-color:var(--dsw-alias-border-l2)}",
729
729
  ".cpfe-row-detail{display:flex;flex-direction:column;gap:4px;margin:0 0 12px;padding:0 0 0 16px;border:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}",
@@ -740,9 +740,9 @@ try {
740
740
  ".cpfe-line:last-child{border-bottom:0}",
741
741
  ".cpfe-group{display:flex;flex-direction:column;min-width:0}",
742
742
  ".cpfe-kids{display:flex;flex-direction:column;margin-left:0;padding-left:16px}",
743
- ".cpfe-row{display:flex;gap:12px;align-items:center;box-sizing:border-box;min-height:0;padding:16px 0;border:0;border-radius:0;background:none}",
743
+ ".cpfe-row{display:flex;gap:12px;align-items:center;box-sizing:border-box;min-height:0;padding:12px 0;border:0;border-radius:0;background:none}",
744
744
  ".cpfe-row-meta{display:flex;flex-direction:column;gap:4px;min-width:0;flex:1}",
745
- ".cpfe-row-badges{display:flex;align-items:center;gap:6px;flex-wrap:wrap}",
745
+ ".cpfe-row-badges{display:flex;align-items:center;gap:6px;flex-wrap:wrap;height:18px}",
746
746
  ".cpfe-editor{box-sizing:border-box;width:100%;min-height:240px;resize:vertical;padding:12px;border-radius:10px;border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;line-height:20px}",
747
747
  ".cpfe-bar{display:flex;align-items:center;gap:var(--g);flex-wrap:wrap}",
748
748
  ".cpfe-status{font-size:13px;line-height:20px}",
@@ -751,8 +751,8 @@ try {
751
751
  ".cpfe-dirty{color:var(--dsw-alias-state-warn-primary)}",
752
752
  ".cpfe-danger{color:var(--dsw-alias-state-error-primary)}",
753
753
  ".cpfe-meta-line{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px;min-width:0}",
754
- ".cpfe-version{font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary);white-space:nowrap}",
755
- ".cpfe-path{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
754
+ ".cpfe-version{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);white-space:nowrap}",
755
+ ".cpfe-path{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
756
756
  // ── fallback-path controls (unused when the shell provides the atoms) ──
757
757
  ".cpfe-btn{appearance:none;cursor:pointer;padding:0 14px;height:32px;border-radius:8px;border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}",
758
758
  ".cpfe-btn:disabled{opacity:.5;cursor:default}",
@@ -768,7 +768,7 @@ try {
768
768
  // 视觉上也给一个明确的"此刻不能动"。
769
769
  ".cpfe-busy .cpfe-switch,.cpfe-busy .cpfe-row-toggle,.cpfe-busy .cpfe-input,.cpfe-busy .cpfe-field,.cpfe-busy .cpfe-editor{pointer-events:none;opacity:.65}",
770
770
  ".cpfe-busy .cpfe-editor{cursor:progress}",
771
- ".cpfe-tag{font-size:11px;line-height:16px;padding:0 5px;border-radius:4px;border:.5px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary)}",
771
+ ".cpfe-tag{font-size:12px;line-height:18px;padding:0 5px;border-radius:4px;border:.5px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary)}",
772
772
  ".cpfe-tag-success,.cpfe-tag-info{color:var(--dsw-alias-state-success-primary)}",
773
773
  ".cpfe-tag-warning{color:var(--dsw-alias-state-warn-primary)}",
774
774
  ".cpfe-tag-danger{color:var(--dsw-alias-state-error-primary)}",
package/index.mjs CHANGED
@@ -281,7 +281,9 @@ export function configWarnings(text, prompt, meta) {
281
281
  warnings.push('personaOffWithPrompt')
282
282
  }
283
283
  if (find('custom-prompt-tool')?.disabled === true) warnings.push('toolOff')
284
- if (typeof meta?.description !== 'string' || meta.description.trim() === '') warnings.push('noDescription')
284
+ // **不报「没有描述」**:1.11.2 起模板故意不再播种描述(它是产品数据,壳没法按界面语言本地化 ——
285
+ // 写一种语言在另一种界面里就是外语,两种都写就成了"中英拼接")。默认留空却弹一条 ⚠ 是自相矛盾的,
286
+ // 而且描述框的占位符本来就写着「可留空」。填不填是用户的自由,不该被一直念。
285
287
  if (typeof meta?.name !== 'string' || meta.name.trim() === '') warnings.push('noName')
286
288
  // 两条互斥的路同时开着 ⇒ 平台会把整个模式判 broken 并**从所有选择器里静默丢弃**。
287
289
  // 保存路径已经会自动避让(见 applyRowExclusivity),所以走到这里只可能是手工编辑、直接调 HTTP API,
package/locales.mjs CHANGED
@@ -21,14 +21,14 @@ export const zh = {
21
21
  // ── 助手管理 ──────────────────────────────────────────────────────────────
22
22
  'assistant.heading': '助手',
23
23
  'assistant.hint':
24
- '每个助手就是一个独立模式:有自己的系统提示词、基础模式和插件开关,互不影响。新建会话时在模式选择器里挑一个。',
24
+ '每个助手即一个独立模式:独立的系统提示词、基础模式与插件开关。新建会话时在模式选择器中选择。',
25
25
  'assistant.empty': '还没有助手,用下面的输入框新建一个。',
26
26
  'assistant.loadingList': '正在读取助手列表…',
27
27
  'assistant.newPlaceholder': '新助手的名字(例如:写作助手)',
28
28
  'assistant.meta': '标识',
29
29
  'assistant.copyName': '「{name}」副本',
30
30
  'assistant.broken': '组成文件有问题',
31
- 'assistant.switchHint': '切换助手不会丢掉未保存的修改:每个助手的草稿各自留着,列表上的「未保存」标记就是它。',
31
+ 'assistant.switchHint': '切换助手保留各自的未保存草稿;列表中的「未保存」标记即为此。',
32
32
  'btn.create': '新增助手',
33
33
  'btn.creating': '创建中…',
34
34
  'btn.duplicate': '复制一份',
@@ -46,7 +46,7 @@ export const zh = {
46
46
  'msg.createFailed': '创建失败',
47
47
  'msg.deleteFailed': '删除失败',
48
48
  'msg.nameRequired': '请先给新助手起个名字。',
49
- 'msg.readOnlyHint': '这一页只管理本工具创建的助手;手写的 preset 不在这里,也不会被改写。',
49
+ 'msg.readOnlyHint': '仅管理本插件创建的助手;手写 preset 不计入,也不会被改写。',
50
50
  'msg.reordered': '顺序已保存:新建会话时的模式选择器按这个顺序排列。',
51
51
  'msg.reorderFailed': '调整顺序失败',
52
52
  'msg.imported': '已导入到编辑器(还没有保存):检查后点「保存」。',
@@ -129,7 +129,7 @@ export const zh = {
129
129
  'history.label': '改动历史',
130
130
  'history.pick': '选择要载入的版本…',
131
131
  'history.load': '载入这一版',
132
- 'history.hint': '每次保存、以及会话内工具或手工改动,都会在这里留一版;载入只改草稿,保存前不落盘。',
132
+ 'history.hint': '每次保存、会话内工具改动或手工编辑都会在此留一版;载入只改草稿,保存前不落盘。',
133
133
  'history.by.settings': '设置页保存',
134
134
  'history.by.external': '会话内/手工改动',
135
135
  'msg.loadFailed': '载入这一版失败',
@@ -145,19 +145,19 @@ export const zh = {
145
145
  'delete.plainConfirm': '确定永久删除「{name}」吗?该操作无法撤销。',
146
146
 
147
147
  'name.heading': '模式名称',
148
- 'name.hint': '改名只影响显示(模式选择器与上面的助手列表),内部标识和已有会话不受影响。新建会话即可看到新名称。',
148
+ 'name.hint': '仅影响显示名(模式选择器与助手列表);内部标识与已有会话不变,新建会话后生效。',
149
149
  'name.placeholder': '自定义模式',
150
150
  'name.descriptionPlaceholder': '模式描述(显示在模式选择器里,可留空)',
151
151
 
152
152
  'mode.heading': '基础模式',
153
- 'mode.hint': '选一个官方模式作为底子,下面再按行微调。注意:底子只决定「行集合」与工具能力 —— 本模式的 persona 行始终替换掉底子那一行(提示词由你编辑,complete: false),底子的提示词语义不会被继承。改完保存后,新建会话即生效,不需要重启。',
153
+ 'mode.hint': '底子只决定「行集合」与工具能力:本模式的身份行始终替换底子那一行(提示词由你编辑,complete: false),底子的提示语语义不继承。保存后新建会话即生效,无需重启。',
154
154
 
155
155
  'rows.heading': '插件开关',
156
- 'rows.hint': '逐行控制这个模式挂载哪些插件,和官方插件列表一样按行铺开。没拨过的行保持官方默认(含平台判断);你手动拨了就以你的为准。',
156
+ 'rows.hint': '逐行控制本模式挂载的插件。未拨动的行保持官方默认(含平台判断);手动拨动后以你的为准。',
157
157
 
158
158
  'prompt.heading': '系统提示词',
159
159
  'prompt.hint':
160
- '这段文本在每个模型调用前重新读取,所以保存后下一步即生效,且只影响使用这个助手的会话。「导入」把文件读进编辑器(未保存前不写入任何东西);「导出」把当前文本存成 .md 文件;「恢复出厂提示词」把出厂模板填回编辑器(同样要保存才写入)。',
160
+ '每次模型调用前重新读取:保存后下一步生效,仅影响使用本助手的会话。「导入」读文件进编辑器(保存前不写入);「导出」存为 .md;「恢复出厂提示词」填回出厂模板(同样需保存)。',
161
161
 
162
162
  'status.enabled': '已启用',
163
163
  'status.disabled': '已停用',
@@ -258,14 +258,14 @@ export const en = {
258
258
  // ── assistant management ──────────────────────────────────────────────────
259
259
  'assistant.heading': 'Assistants',
260
260
  'assistant.hint':
261
- 'Each assistant is an independent mode: its own system prompt, base mode and plugin switches, with no effect on the others. Pick one in the mode picker when you start a new session.',
261
+ 'Each assistant is an independent mode: its own system prompt, base mode and plugin switches. Pick one in the mode picker when you start a new session.',
262
262
  'assistant.empty': 'No assistants yet — create one with the field below.',
263
263
  'assistant.loadingList': 'Loading assistants…',
264
264
  'assistant.newPlaceholder': 'Name of the new assistant (e.g. Writing assistant)',
265
265
  'assistant.meta': 'ID',
266
266
  'assistant.copyName': '{name} (copy)',
267
267
  'assistant.broken': 'Composition problem',
268
- 'assistant.switchHint': 'Switching assistants never loses unsaved edits: each one keeps its own draft, and the "Unsaved" marker in the list is exactly that.',
268
+ 'assistant.switchHint': 'Switching assistants keeps each draft; the "Unsaved" marker in the list is exactly that.',
269
269
  'btn.create': 'New assistant',
270
270
  'btn.creating': 'Creating…',
271
271
  'btn.duplicate': 'Duplicate',
@@ -283,7 +283,7 @@ export const en = {
283
283
  'msg.createFailed': 'Could not create',
284
284
  'msg.deleteFailed': 'Could not delete',
285
285
  'msg.nameRequired': 'Give the new assistant a name first.',
286
- 'msg.readOnlyHint': 'This page manages only the assistants this tool created; a hand-written preset is not listed here and is never rewritten.',
286
+ 'msg.readOnlyHint': 'Manages only the assistants this plugin created; a hand-written preset is not listed and is never rewritten.',
287
287
  'msg.reordered': 'Order saved: the mode picker for new sessions follows it.',
288
288
  'msg.reorderFailed': 'Could not reorder',
289
289
  'msg.imported': 'Imported into the editor (not saved yet) — review it, then click Save.',
@@ -383,13 +383,13 @@ export const en = {
383
383
 
384
384
  'name.heading': 'Mode name',
385
385
  'name.hint':
386
- 'Renaming changes only what is displayed (the mode picker and the assistant list above); the internal id and existing sessions are unaffected. Start a new session to see it.',
386
+ 'Changes the display name only (mode picker and assistant list); the internal id and existing sessions are unaffected. Applies to the next new session.',
387
387
  'name.placeholder': 'Custom mode',
388
388
  'name.descriptionPlaceholder': 'Mode description (shown in the mode picker, optional)',
389
389
 
390
390
  'mode.heading': 'Base mode',
391
391
  'mode.hint':
392
- 'Pick an official mode as the base, then adjust it row by row. Note: the base only decides which rows exist — this mode always replaces the base\'s persona row with its own reader (complete: false), so the base\'s prompt semantics are not inherited. A save applies to the next new session; no restart needed.',
392
+ 'The base decides the row set and the tool abilities: this mode always replaces the base\'s persona row with its own reader (complete: false), so the base\'s prompt semantics are not inherited. A save applies to the next new session; no restart needed.',
393
393
 
394
394
  'rows.heading': 'Plugin switches',
395
395
  'rows.hint':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-custom-mode",
3
- "version": "1.11.1",
3
+ "version": "1.11.2",
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",
package/preset/preset.yml CHANGED
@@ -1,2 +1,5 @@
1
+ # 描述**故意留空**:它是产品数据,壳按界面语言渲染自己的出厂预设,但没法本地化我们的。
2
+ # 写一种语言,另一种语言的界面上就是外语;两种都写,就成了"中英拼接"(实测被点名)。
3
+ # 留空的描述是壳本来就支持的状态,且设置页上的「描述」随时可填。
1
4
  name: 自定义模式
2
- description: 在设置页编辑本模式的系统提示词 / Edit this mode's system prompt in Settings.
5
+ description: ""
package/seed.mjs CHANGED
@@ -23,6 +23,7 @@
23
23
 
24
24
  import { existsSync, mkdirSync, readFileSync } from 'node:fs'
25
25
  import { renderComposition } from './composition.mjs'
26
+ import { readPresetMeta, writePresetMeta } from './meta.mjs'
26
27
  import { writeAtomic } from './atomic.mjs'
27
28
  import { dirname, join } from 'node:path'
28
29
  import { fileURLToPath } from 'node:url'
@@ -172,6 +173,49 @@ export function seedPreset(presetDir, sourceDir = packagedPresetDir(), options =
172
173
  return { created, refreshed, kept, errors }
173
174
  }
174
175
 
176
+ /**
177
+ * We shipped a **bilingual** description (`中文 / English`) for three years' worth of installs.
178
+ *
179
+ * It was a workaround with a real cause: the description is **product data** in `preset.yml`, and the shell
180
+ * renders it in whatever language the UI happens to be in — it localises its own shipped presets, but it
181
+ * cannot localise ours. A bilingual line then read as one language inside the other, which is exactly the
182
+ * complaint that came back ("英文是英文中文是中文,不要混杂"): in the mode picker the entry showed
183
+ * `完整编码能力… / Full coding ability…` as a single run-on string, and it was long enough to make that
184
+ * card taller than the shipped ones next to it.
185
+ *
186
+ * So the template no longer ships a description at all: an absent description is a state the shell already
187
+ * supports (it prints its own "no description" placeholder), it cannot be wrong in either language, and the
188
+ * field stays user-editable on the settings page.
189
+ *
190
+ * This migrates installs that still carry **one of our own shipped strings verbatim**. Anything else — a
191
+ * description the user typed, however similar — is left alone: that is the whole contract of this file.
192
+ */
193
+ export const SHIPPED_DESCRIPTIONS = [
194
+ "在设置页编辑本模式的系统提示词 / Edit this mode's system prompt in Settings.",
195
+ '完整编码能力,系统提示词来自 prompt.md,可在设置页随时修改、下一步即生效。 / Full coding ability; the system prompt lives in prompt.md and can be edited in the settings page — it takes effect on the next step.',
196
+ ]
197
+
198
+ /**
199
+ * Blank the description **only** when it is one of {@link SHIPPED_DESCRIPTIONS}.
200
+ *
201
+ * @param {string} presetDir - the installed preset directory.
202
+ * @returns {{migrated: boolean, reason?: string}} what happened.
203
+ */
204
+ export function dropShippedDescription(presetDir) {
205
+ const file = join(presetDir, 'preset.yml')
206
+ if (!existsSync(file)) return { migrated: false, reason: 'no preset.yml' }
207
+ let meta
208
+ try {
209
+ meta = readPresetMeta(presetDir)
210
+ } catch (error) {
211
+ return { migrated: false, reason: describe(error) }
212
+ }
213
+ const current = typeof meta.description === 'string' ? meta.description.trim() : ''
214
+ if (current === '' || SHIPPED_DESCRIPTIONS.includes(current) === false) return { migrated: false, reason: 'user text' }
215
+ const written = writePresetMeta(typeof meta.name === 'string' && meta.name.trim() !== '' ? meta.name : '自定义模式', '', presetDir)
216
+ return written.ok === true ? { migrated: true } : { migrated: false, reason: String(written.code ?? 'writeFailed') }
217
+ }
218
+
175
219
  /**
176
220
  * Seed and report, in the form the host half logs.
177
221
  *
@@ -200,6 +244,14 @@ export function seedPresetWithLog(presetDir, log = console.error, info = console
200
244
  if (result.created.length > 0) {
201
245
  info(`custom-mode: 已播种 preset 到 ${presetDir}(新建 ${result.created.length} 个文件: ${result.created.join(', ')})`)
202
246
  }
247
+ // 只订正**我们自己写进去的**那条描述(见 dropShippedDescription)。
248
+ try {
249
+ if (dropShippedDescription(presetDir).migrated === true) {
250
+ info(`custom-mode: 已清空描述 —— 它还是我们早年播种的那条中英拼接,壳无法按界面语言本地化产品数据`)
251
+ }
252
+ } catch (error) {
253
+ log(`custom-mode: 描述迁移失败(已忽略): ${describe(error)}`)
254
+ }
203
255
  for (const error of result.errors) {
204
256
  log(`custom-mode: preset 播种未完成 —— ${error}`)
205
257
  }