dsh-custom-mode 1.9.7 → 1.9.9
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 +58 -13
- package/composition.mjs +22 -5
- package/index.mjs +25 -3
- package/locales.mjs +6 -2
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -170,7 +170,7 @@ try {
|
|
|
170
170
|
"msg.exported": "已导出为文件。",
|
|
171
171
|
"msg.exportFailed": "导出失败",
|
|
172
172
|
"delete.title": "永久删除这个助手?",
|
|
173
|
-
"delete.description": "
|
|
173
|
+
"delete.description": "这会删除磁盘上的模式目录,连同它的系统提示词一起消失,无法撤销。正在使用它的会话不受影响;新建会话时它不再出现在选择器里。({id})",
|
|
174
174
|
"delete.acknowledge": "我明白这个助手的提示词会被永久删除",
|
|
175
175
|
"delete.confirm": "永久删除",
|
|
176
176
|
"delete.close": "关闭",
|
|
@@ -200,6 +200,7 @@ try {
|
|
|
200
200
|
"msg.readFailed": "读取失败",
|
|
201
201
|
"msg.saveFailed": "保存失败",
|
|
202
202
|
"msg.saved": "已保存。新建会话即生效,当前会话保持原配置。",
|
|
203
|
+
"status.detail": "{message}:{detail}",
|
|
203
204
|
"row.persona.label": "身份(系统提示词)",
|
|
204
205
|
"row.persona.note": "提示词注入点;关掉后本模式用回部署默认身份",
|
|
205
206
|
"row.custom-prompt-tool.label": "custom_prompt 工具",
|
|
@@ -377,7 +378,7 @@ try {
|
|
|
377
378
|
"msg.exported": "Exported to a file.",
|
|
378
379
|
"msg.exportFailed": "Export failed",
|
|
379
380
|
"delete.title": "Delete this assistant permanently?",
|
|
380
|
-
"delete.description": "This removes the mode directory from disk, system prompt included, and cannot be undone. Sessions already using it keep running; new sessions no longer offer it.",
|
|
381
|
+
"delete.description": "This removes the mode directory from disk, system prompt included, and cannot be undone. Sessions already using it keep running; new sessions no longer offer it. ({id})",
|
|
381
382
|
"delete.acknowledge": "I understand this assistant's prompt will be deleted permanently",
|
|
382
383
|
"delete.confirm": "Delete permanently",
|
|
383
384
|
"delete.close": "Close",
|
|
@@ -407,6 +408,7 @@ try {
|
|
|
407
408
|
"msg.readFailed": "Load failed",
|
|
408
409
|
"msg.saveFailed": "Save failed",
|
|
409
410
|
"msg.saved": "Saved. A new session picks it up; the current one keeps its configuration.",
|
|
411
|
+
"status.detail": "{message}: {detail}",
|
|
410
412
|
"row.persona.label": "Identity (system prompt)",
|
|
411
413
|
"row.persona.note": "Where the prompt is injected; turning it off falls back to the deployment identity",
|
|
412
414
|
"row.custom-prompt-tool.label": "custom_prompt tool",
|
|
@@ -681,6 +683,13 @@ try {
|
|
|
681
683
|
".cpfe-btn-ghost{border-color:transparent;background:transparent}",
|
|
682
684
|
".cpfe-input{box-sizing:border-box;width:100%;height:34px;padding:0 12px;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}",
|
|
683
685
|
".cpfe-switch{display:inline-flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;line-height:19px;color:var(--dsw-alias-label-primary)}",
|
|
686
|
+
// 忙碌期间禁掉草稿类控件的交互(issue #6)。
|
|
687
|
+
// 状态层已经拦住了改动(`update()` 在 busy 时直接返回),但对**受控组件**来说,
|
|
688
|
+
// 光拦状态还不够:DOM 会先显示用户敲进去的字/翻过去的开关,等下一次重渲染才被拉回来 ——
|
|
689
|
+
// 那就是"我明明改了,它自己弹回去了"。`pointer-events` 让点击根本到不了控件,
|
|
690
|
+
// 视觉上也给一个明确的"此刻不能动"。
|
|
691
|
+
".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}",
|
|
692
|
+
".cpfe-busy .cpfe-editor{cursor:progress}",
|
|
684
693
|
".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)}",
|
|
685
694
|
".cpfe-tag-success,.cpfe-tag-info{color:var(--dsw-alias-state-success-primary)}",
|
|
686
695
|
".cpfe-tag-warning{color:var(--dsw-alias-state-warn-primary)}",
|
|
@@ -1038,6 +1047,19 @@ try {
|
|
|
1038
1047
|
const result = await postJson(ROUTES.repair, { id: selected })
|
|
1039
1048
|
if (result !== null && result.ok === true) {
|
|
1040
1049
|
setStatus(apiText(result, "msg.repaired"))
|
|
1050
|
+
// ★ 把修复折进**草稿**(issue #8)。
|
|
1051
|
+
//
|
|
1052
|
+
// 修复在磁盘上是生效的,但草稿里那几行仍然是"启用"。而 `reload()` 对脏草稿是
|
|
1053
|
+
// **保留**的(`open()` 的 `!sameDraft(...)` 保护),所以草稿不会自己更新;
|
|
1054
|
+
// 于是下一次保存会按草稿重渲染,把刚修好的行原样打开 —— 模式再次从所有选择器里消失。
|
|
1055
|
+
// 实测(2026-09-21):修复后磁盘上 ghost-row 已关闭,保存一次就又被打开了。
|
|
1056
|
+
const repairedIds = Array.isArray(result.params?.repairedIds) ? result.params.repairedIds : []
|
|
1057
|
+
if (repairedIds.length > 0 && draft !== null) {
|
|
1058
|
+
// 草稿里的 `overrides` 是"显式**启用**"语义,所以关闭 = false。
|
|
1059
|
+
const nextOverrides = { ...draft.overrides }
|
|
1060
|
+
for (const rowId of repairedIds) nextOverrides[rowId] = false
|
|
1061
|
+
update({ overrides: nextOverrides }, { force: true })
|
|
1062
|
+
}
|
|
1041
1063
|
await reload()
|
|
1042
1064
|
} else {
|
|
1043
1065
|
setFailed(true)
|
|
@@ -1183,7 +1205,7 @@ try {
|
|
|
1183
1205
|
} catch (error) {
|
|
1184
1206
|
if (alive) {
|
|
1185
1207
|
setFailed(true)
|
|
1186
|
-
setStatus(t("
|
|
1208
|
+
setStatus(fillPlaceholders(t("status.detail"), { message: t("msg.readFailed"), detail: describeError(error) }))
|
|
1187
1209
|
}
|
|
1188
1210
|
} finally {
|
|
1189
1211
|
if (alive) setBusy(false)
|
|
@@ -1195,7 +1217,20 @@ try {
|
|
|
1195
1217
|
}, [])
|
|
1196
1218
|
|
|
1197
1219
|
/** Change one field of the selected assistant's draft. */
|
|
1198
|
-
const update = (patch) => {
|
|
1220
|
+
const update = (patch, options) => {
|
|
1221
|
+
// ★ 忙碌期间**不接受**对草稿的修改(issue #6)。
|
|
1222
|
+
//
|
|
1223
|
+
// 为什么必须在这里拦:保存成功后(下面 `save()` 里)会用服务器归一化的结果
|
|
1224
|
+
// `value: normalized` **无条件**覆盖草稿。于是往返期间敲进去的字、翻过的行开关都会
|
|
1225
|
+
// 凭空消失,而页面还显示 "Saved" —— 用户丢的是手写的系统提示词。
|
|
1226
|
+
//
|
|
1227
|
+
// 拦在这一层而不是逐个控件加 `disabled`,是因为它一处覆盖**所有**草稿字段
|
|
1228
|
+
// (name / description / prompt / overrides),漏掉任何一个都会重新打开那条丢失路径。
|
|
1229
|
+
// 视觉上控件仍可点,但点了不会改状态,所以不会出现"开关翻过去又弹回来"这种骗人的反馈。
|
|
1230
|
+
//
|
|
1231
|
+
// `{ force: true }` 是给**内部**调用用的(例如「按本线修复」把结果折进草稿):
|
|
1232
|
+
// 那不是用户输入,不受这条守护约束。
|
|
1233
|
+
if (busy === true && options?.force !== true) return
|
|
1199
1234
|
setEntries((previous) => {
|
|
1200
1235
|
const current = previous[selected]
|
|
1201
1236
|
if (current === undefined) return previous
|
|
@@ -1215,7 +1250,7 @@ try {
|
|
|
1215
1250
|
await open(id)
|
|
1216
1251
|
} catch (error) {
|
|
1217
1252
|
setFailed(true)
|
|
1218
|
-
setStatus(t("
|
|
1253
|
+
setStatus(fillPlaceholders(t("status.detail"), { message: t("msg.readFailed"), detail: describeError(error) }))
|
|
1219
1254
|
} finally {
|
|
1220
1255
|
setBusy(false)
|
|
1221
1256
|
}
|
|
@@ -1247,7 +1282,7 @@ try {
|
|
|
1247
1282
|
}
|
|
1248
1283
|
} catch (error) {
|
|
1249
1284
|
setFailed(true)
|
|
1250
|
-
setStatus(t("
|
|
1285
|
+
setStatus(fillPlaceholders(t("status.detail"), { message: t("msg.createFailed"), detail: describeError(error) }))
|
|
1251
1286
|
} finally {
|
|
1252
1287
|
setBusy(false)
|
|
1253
1288
|
}
|
|
@@ -1275,7 +1310,7 @@ try {
|
|
|
1275
1310
|
}
|
|
1276
1311
|
} catch (error) {
|
|
1277
1312
|
setFailed(true)
|
|
1278
|
-
setStatus(t("
|
|
1313
|
+
setStatus(fillPlaceholders(t("status.detail"), { message: t("msg.reorderFailed"), detail: describe(error) }))
|
|
1279
1314
|
} finally {
|
|
1280
1315
|
setBusy(false)
|
|
1281
1316
|
}
|
|
@@ -1308,7 +1343,7 @@ try {
|
|
|
1308
1343
|
}
|
|
1309
1344
|
update({ prompt: payload.text })
|
|
1310
1345
|
} catch (error) {
|
|
1311
|
-
setStatus(t("
|
|
1346
|
+
setStatus(fillPlaceholders(t("status.detail"), { message: t("msg.loadFailed"), detail: describeError(error) }))
|
|
1312
1347
|
}
|
|
1313
1348
|
}
|
|
1314
1349
|
|
|
@@ -1327,7 +1362,7 @@ try {
|
|
|
1327
1362
|
setStatus(t("msg.exported"))
|
|
1328
1363
|
} catch (error) {
|
|
1329
1364
|
setFailed(true)
|
|
1330
|
-
setStatus(t("
|
|
1365
|
+
setStatus(fillPlaceholders(t("status.detail"), { message: t("msg.exportFailed"), detail: describe(error) }))
|
|
1331
1366
|
}
|
|
1332
1367
|
}
|
|
1333
1368
|
|
|
@@ -1389,7 +1424,7 @@ try {
|
|
|
1389
1424
|
}
|
|
1390
1425
|
} catch (error) {
|
|
1391
1426
|
setFailed(true)
|
|
1392
|
-
setStatus(t("
|
|
1427
|
+
setStatus(fillPlaceholders(t("status.detail"), { message: t("msg.deleteFailed"), detail: describeError(error) }))
|
|
1393
1428
|
} finally {
|
|
1394
1429
|
setBusy(false)
|
|
1395
1430
|
}
|
|
@@ -1452,7 +1487,7 @@ try {
|
|
|
1452
1487
|
}
|
|
1453
1488
|
} catch (error) {
|
|
1454
1489
|
setFailed(true)
|
|
1455
|
-
setStatus(t("
|
|
1490
|
+
setStatus(fillPlaceholders(t("status.detail"), { message: t("msg.saveFailed"), detail: describeError(error) }))
|
|
1456
1491
|
} finally {
|
|
1457
1492
|
setBusy(false)
|
|
1458
1493
|
}
|
|
@@ -1569,6 +1604,9 @@ try {
|
|
|
1569
1604
|
className: "cpfe-field cpfe-desc",
|
|
1570
1605
|
value: draft.description,
|
|
1571
1606
|
rows: 2,
|
|
1607
|
+
// 忙碌期间只读(issue #6)。**描述框在 DOM 里排在提示词编辑器前面**,
|
|
1608
|
+
// 所以只给 `.cpfe-editor` 加 readOnly 是不够的 —— 实测就是这么漏掉的。
|
|
1609
|
+
readOnly: busy === true,
|
|
1572
1610
|
placeholder: t("name.descriptionPlaceholder"),
|
|
1573
1611
|
"aria-label": t("name.descriptionPlaceholder"),
|
|
1574
1612
|
onChange: (event) => update({ description: event.target.value }),
|
|
@@ -1757,6 +1795,11 @@ try {
|
|
|
1757
1795
|
className: "cpfe-editor",
|
|
1758
1796
|
value: draft.prompt,
|
|
1759
1797
|
spellCheck: false,
|
|
1798
|
+
// 忙碌期间只读(issue #6):`update()` 那一层已经会丢弃改动,这里再给一个
|
|
1799
|
+
// **看得见**的信号 —— 否则用户会以为自己在编辑,而字根本没进去。
|
|
1800
|
+
// 用 readOnly 而不是 disabled:仍然可以选中/复制,只是改不了。
|
|
1801
|
+
readOnly: busy === true,
|
|
1802
|
+
"aria-busy": busy === true,
|
|
1760
1803
|
"aria-label": t("prompt.heading"),
|
|
1761
1804
|
onChange: (event) => update({ prompt: event.target.value }),
|
|
1762
1805
|
}),
|
|
@@ -1802,7 +1845,9 @@ try {
|
|
|
1802
1845
|
|
|
1803
1846
|
return react.createElement(
|
|
1804
1847
|
"div",
|
|
1805
|
-
|
|
1848
|
+
// `cpfe-busy` 是 issue #6 的兜底:面板里**所有**会改草稿的控件在忙碌期间一律不可交互。
|
|
1849
|
+
// 逐个控件加 `disabled` 会漏(本轮就漏了行开关与描述框),一个根类 + 一条 CSS 反而漏不掉。
|
|
1850
|
+
{ className: busy === true ? "cpfe cpfe-busy" : "cpfe" },
|
|
1806
1851
|
assistantList,
|
|
1807
1852
|
react.createElement("p", { className: "cpfe-note" }, t("assistant.switchHint")),
|
|
1808
1853
|
// 配了却不生效的项:主动点名,而不是让用户对着"我明明写了"发呆。
|
|
@@ -1878,7 +1923,7 @@ try {
|
|
|
1878
1923
|
? react.createElement(A.RiskConfirmation, {
|
|
1879
1924
|
open: deleteOpen,
|
|
1880
1925
|
title: t("delete.title"),
|
|
1881
|
-
description: t("delete.description")
|
|
1926
|
+
description: fillPlaceholders(t("delete.description"), { id: draft.id }),
|
|
1882
1927
|
acknowledgeLabel: t("delete.acknowledge"),
|
|
1883
1928
|
cancelLabel: t("btn.cancel"),
|
|
1884
1929
|
closeLabel: t("delete.close"),
|
package/composition.mjs
CHANGED
|
@@ -593,8 +593,15 @@ export function disableRowsInPlace(text, ids) {
|
|
|
593
593
|
if (wanted.length === 0) return text
|
|
594
594
|
// `applyLevel` 把 Map 的值直接交给 `setDisabled(...)`,所以值是**关闭**布尔(true = 关闭)。
|
|
595
595
|
const off = new Map(wanted.map((id) => [id, true]))
|
|
596
|
-
|
|
597
|
-
|
|
596
|
+
// **只跑顶级这一遍。** 它已经通过 applyLevel 的 `isGroup` 分支递归进每个分组,
|
|
597
|
+
// 所以原先跟在后面的那次扁平扫描(`applyLevel(top, false, …)`)纯属重复 —— 而且有害:
|
|
598
|
+
// 它以「下一个 4 空格 `- id:`」界定片段,于是**分组的最后一个子行会吞掉后续的顶层内容**
|
|
599
|
+
// (包括下一个分组的 `name:` 头),`ownKeyIndent` 随即算出缩进 2,把 `disabled` 写进了
|
|
600
|
+
// **下一个分组**。实测(2026-09-21,4 个模式 × 每个行 id 全扫):连带损害 7 例
|
|
601
|
+
// (关 plan-mode 会连带关掉整个 compaction 分组;关 tool-result-pruner 会连带关掉整个
|
|
602
|
+
// delegation 分组)、行数 Δ=+2 的结构异常 6 例、以及若干"目标行根本没被改到"的空操作。
|
|
603
|
+
// 删掉这一遍之后:连带 0 例、结构异常 0 例,正确生效 81 → 87。
|
|
604
|
+
return applyLevel(text, true, off, false, false)
|
|
598
605
|
}
|
|
599
606
|
|
|
600
607
|
export function unresolvableRows(text) {
|
|
@@ -613,15 +620,25 @@ export function unresolvableRows(text) {
|
|
|
613
620
|
const row = /^( {0,4})- id: (.+?)\s*$/.exec(lines[index])
|
|
614
621
|
if (row === null) continue
|
|
615
622
|
const indent = row[1].length
|
|
623
|
+
// 这一行**自己的键**所在的列(`- id:` 再进两格)。只有这一列上的 `name:` / `disabled:`
|
|
624
|
+
// 才属于它 —— 否则会读到子树里的键。
|
|
625
|
+
const keyIndent = indent + 2
|
|
616
626
|
let name
|
|
617
627
|
let disabled = false
|
|
618
628
|
for (let next = index + 1; next < lines.length; next += 1) {
|
|
619
629
|
const line = lines[next]
|
|
620
|
-
if (line.trim()
|
|
621
|
-
|
|
630
|
+
if (line.trim() === '') continue
|
|
631
|
+
const column = line.search(/\S/)
|
|
632
|
+
if (column <= indent) break
|
|
633
|
+
// **跳过比自己深的行**(子行的键、子行的键的子行……)。
|
|
634
|
+
// 原先这里没有这一层过滤,于是顶层分组会一路走完整个子树,`name` 被**最后一个子行**覆盖:
|
|
635
|
+
// 一个健康的分组因此被报成"解析不了"(外部实测:`grp` 被报成坏行,name 取自 child-last),
|
|
636
|
+
// 后果是页面点名它、启动日志喊它、而"按本线修复"会**关掉整个分组**。
|
|
637
|
+
if (column !== keyIndent) continue
|
|
638
|
+
if (/^name: ['"]?(.+?)['"]?\s*$/.test(line.slice(keyIndent))) name = /^name: ['"]?(.+?)['"]?\s*$/.exec(line.slice(keyIndent))[1]
|
|
622
639
|
// 平台条件行(`disabled: !!js …`)必须**求值**判定:只看有没有字面 `true` 会把"本平台已启用"的行
|
|
623
640
|
// 误判成关闭,也会把"本平台本来就关闭"的行(如 Windows 专用的 pwsh)误报成"无法解析"。
|
|
624
|
-
const flag =
|
|
641
|
+
const flag = /^disabled:\s*(.+?)\s*$/.exec(line.slice(keyIndent))
|
|
625
642
|
if (flag !== null) {
|
|
626
643
|
// 字面量直接读,`!!js` 一类交给求值器(它对字面量不做布尔化)。
|
|
627
644
|
const raw = flag[1].replace(/^['"]|['"]$/g, '')
|
package/index.mjs
CHANGED
|
@@ -281,7 +281,19 @@ export function readState(rows, id, options = {}) {
|
|
|
281
281
|
const meta = readPresetMeta(directory)
|
|
282
282
|
// 页面要打开时顺便对账:磁盘上的文本若与日志末条不同,说明它在设置页之外被改过
|
|
283
283
|
// (会话内的 custom_prompt 工具、手工编辑、别处同步)—— 补记一条 external,于是"被改过"看得见。
|
|
284
|
-
|
|
284
|
+
//
|
|
285
|
+
// **必须包起来**:这是一次"写透"(recordPrompt → writeAtomic),而日志路径本身可能是坏的
|
|
286
|
+
// (被换成目录、被 chmod 000、一次 sudo 跑 dsh 留下的 root 属主)。1.9.7 给 readEntries 加的
|
|
287
|
+
// 守卫只覆盖了**读**那一半,于是同一个损坏场景会在这里重新把整个 GET state 打成 500 ——
|
|
288
|
+
// 页面打不开这个助手,用户既看不到也改不了自己的提示词(外部实测:EISDIR on rename)。
|
|
289
|
+
// 留痕失败降级为"这一版没记上",与 saveState 里已经做过的处置一致。
|
|
290
|
+
if (prompt.ok === true) {
|
|
291
|
+
try {
|
|
292
|
+
recordExternalChange(directory, prompt.text)
|
|
293
|
+
} catch {
|
|
294
|
+
/* 审计留痕失败不影响读取 */
|
|
295
|
+
}
|
|
296
|
+
}
|
|
285
297
|
return {
|
|
286
298
|
ok: true,
|
|
287
299
|
id,
|
|
@@ -507,7 +519,14 @@ export function createAssistant(rows, input, templateDir = packagedPresetDir())
|
|
|
507
519
|
// 显示名(`rows` 里的 name)优先于内部 id —— 与删除一致。
|
|
508
520
|
const fromRow = rows.find((item) => item !== null && typeof item === 'object' && item.id === from)
|
|
509
521
|
if (typeof fromRow?.name === 'string' && fromRow.name.trim() !== '') fromDisplayName = fromRow.name
|
|
510
|
-
|
|
522
|
+
// 与 readState 同一条纪律:**坏的源文件要给类型化错误,不能把异常抛到路由层**
|
|
523
|
+
// (抛上去就变成 500 internalError,页面只能显示 Node 的原始错误串)。
|
|
524
|
+
let text
|
|
525
|
+
try {
|
|
526
|
+
text = readFileSync(fromComposition, 'utf8')
|
|
527
|
+
} catch (error) {
|
|
528
|
+
return { ok: false, code: 'compositionMissing', params: { path: fromComposition, detail: describe(error) }, error: '读不到源助手的组成文件:' + describe(error) }
|
|
529
|
+
}
|
|
511
530
|
const sourceMode = modeOf(text)
|
|
512
531
|
const sourcePrompt = readPrompt(fromDir)
|
|
513
532
|
source = {
|
|
@@ -600,7 +619,10 @@ export function repairComposition(rows, input) {
|
|
|
600
619
|
ok: true,
|
|
601
620
|
id,
|
|
602
621
|
code: 'repaired',
|
|
603
|
-
|
|
622
|
+
// `ids` 是给人看的一串;`repairedIds` 是给**页面**用的数组 —— 页面必须把这几行写进它自己的
|
|
623
|
+
// 草稿,否则下一次保存会按草稿重渲染,把刚才的修复原样撤销(issue #8 实测:
|
|
624
|
+
// 修复写进了磁盘,但草稿里那几行仍是启用的,于是保存后 ghost 行又回来了)。
|
|
625
|
+
params: { count: bad.length, ids, repairedIds: bad.map((row) => row.id) },
|
|
604
626
|
note: '已按本机这条 dsh 线关闭 ' + String(bad.length) + ' 个无法解析的行(' + ids + ')。现在这个模式能重新出现在选择器里。',
|
|
605
627
|
}
|
|
606
628
|
}
|
package/locales.mjs
CHANGED
|
@@ -129,7 +129,7 @@ export const zh = {
|
|
|
129
129
|
'msg.exported': '已导出为文件。',
|
|
130
130
|
'msg.exportFailed': '导出失败',
|
|
131
131
|
'delete.title': '永久删除这个助手?',
|
|
132
|
-
'delete.description': '
|
|
132
|
+
'delete.description': '这会删除磁盘上的模式目录,连同它的系统提示词一起消失,无法撤销。正在使用它的会话不受影响;新建会话时它不再出现在选择器里。({id})',
|
|
133
133
|
'delete.acknowledge': '我明白这个助手的提示词会被永久删除',
|
|
134
134
|
'delete.confirm': '永久删除',
|
|
135
135
|
'delete.close': '关闭',
|
|
@@ -168,6 +168,9 @@ export const zh = {
|
|
|
168
168
|
'msg.readFailed': '读取失败',
|
|
169
169
|
'msg.saveFailed': '保存失败',
|
|
170
170
|
'msg.saved': '已保存。新建会话即生效,当前会话保持原配置。',
|
|
171
|
+
// 把「一句已翻译的话 + 一个底层细节」拼起来时,中间那个标点也必须**按语言**走 ——
|
|
172
|
+
// 硬编码全角冒号会让英文界面在出错那一刻出现中文标点(issue #7)。
|
|
173
|
+
'status.detail': '{message}:{detail}',
|
|
171
174
|
|
|
172
175
|
// ── row labels ────────────────────────────────────────────────────────────
|
|
173
176
|
'row.persona.label': '身份(系统提示词)',
|
|
@@ -352,7 +355,7 @@ export const en = {
|
|
|
352
355
|
'msg.exported': 'Exported to a file.',
|
|
353
356
|
'msg.exportFailed': 'Export failed',
|
|
354
357
|
'delete.title': 'Delete this assistant permanently?',
|
|
355
|
-
'delete.description': 'This removes the mode directory from disk, system prompt included, and cannot be undone. Sessions already using it keep running; new sessions no longer offer it.',
|
|
358
|
+
'delete.description': 'This removes the mode directory from disk, system prompt included, and cannot be undone. Sessions already using it keep running; new sessions no longer offer it. ({id})',
|
|
356
359
|
'delete.acknowledge': 'I understand this assistant\'s prompt will be deleted permanently',
|
|
357
360
|
'delete.confirm': 'Delete permanently',
|
|
358
361
|
'delete.close': 'Close',
|
|
@@ -394,6 +397,7 @@ export const en = {
|
|
|
394
397
|
'msg.readFailed': 'Load failed',
|
|
395
398
|
'msg.saveFailed': 'Save failed',
|
|
396
399
|
'msg.saved': 'Saved. A new session picks it up; the current one keeps its configuration.',
|
|
400
|
+
'status.detail': '{message}: {detail}',
|
|
397
401
|
|
|
398
402
|
// ── row labels ────────────────────────────────────────────────────────────
|
|
399
403
|
'row.persona.label': 'Identity (system prompt)',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-custom-mode",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.9",
|
|
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",
|