dsh-custom-mode 1.11.6 → 1.12.1
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 +12 -48
- package/README.zh.md +12 -45
- package/atomic.mjs +31 -1
- package/client.js +243 -100
- package/composition.mjs +39 -5
- package/index.mjs +38 -10
- package/locales.mjs +43 -17
- package/package.json +8 -4
- package/preset/prompt-tool.mjs +4 -4
- package/preset-backend/declarative.mjs +24 -5
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: 76fa2912ffb915b664b12346c9b1b2d5a9ca7637
|
|
5
|
+
README.zh.md: cba5304f70adb125af94b2bd9f24b8b73af50fd8
|
package/README.md
CHANGED
|
@@ -9,11 +9,8 @@ role-play). · **中文**:DeepSeek Harness(dsh)自定义模式插件 —
|
|
|
9
9
|
|
|
10
10
|

|
|
11
11
|
|
|
12
|
-
<!-- community badge row: required in every new repository (see the storefront template in the operator SOP/DSH Plugins folder) -->
|
|
13
|
-
|
|
14
12
|
[](https://github.com/BOWLUNA/dsh-custom-mode/stargazers) [](https://www.npmjs.com/package/dsh-custom-mode) [](https://github.com/BOWLUNA/dsh-custom-mode/actions/workflows/test.yml) [](LICENSE) [](https://github.com/BOWLUNA/dsh-custom-mode#readme)
|
|
15
13
|
|
|
16
|
-
<!-- badge rows: same two-part structure (dark label + coloured value) in both rows; every new repo copies this block -->
|
|
17
14
|
[](https://b23.tv/qJ4Ev0W) [](https://v.douyin.com/VWh0M03Fa4Y/) [](https://xhslink.cn/o/A7QtXmePBBF) [](https://discord.gg/pz97SfAfSy) [](https://github.com/BOWLUNA/dsh-custom-mode/discussions)
|
|
18
15
|
|
|
19
16
|
A custom mode for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh). Its
|
|
@@ -27,15 +24,13 @@ modes ("assistants") can live side by side, each with its own prompt.
|
|
|
27
24
|
Also searched for as: custom mode · custom prompt · system-prompt editor · multi-mode / several assistants ·
|
|
28
25
|
multi-agent · roleplay (RP) / chat personas.
|
|
29
26
|
|
|
30
|
-
**
|
|
31
|
-
neither merely claimed. npm's `latest` and `next` have both pointed at `0.2.0-rc.2` since 2026-09-29, so
|
|
32
|
-
"latest stable" and "latest preview" are one release now; the second leg stays because most installs in
|
|
33
|
-
the field are still on `0.1.7-rc.2`:
|
|
27
|
+
**Unit tests cover three lines. The render gate runs only on `0.2.0-rc.2`.** npm's `latest` and `next` have both pointed at `0.2.0-rc.2` since 2026-09-29:
|
|
34
28
|
|
|
35
29
|
| dsh | role | status |
|
|
36
30
|
| --- | --- | --- |
|
|
37
31
|
| `0.2.0-rc.2` | **newest line** — npm's `latest` *and* `next`, and the line the official desktop app ships (it is version-locked to dsh) | ✅ CI (ubuntu node 20/24 + **windows**) + a real instance: install, composition and the seeded preset verified on this line (the full render gate runs here) |
|
|
38
|
-
| `0.1
|
|
32
|
+
| `0.2.1-alpha.1` | preview line — npm `alpha`. Covered by unit tests; the render gate does not run here | ✅ CI |
|
|
33
|
+
| `0.1.7-rc.2` | previous stable — still inside the declared range, and what most existing installs run | ✅ CI. On this line the page falls back where the shell does not provide atoms; the render gate does not run here |
|
|
39
34
|
|
|
40
35
|
Older builds (`0.1.5-rc.3`, `0.1.6-alpha.*`) use the same mechanisms and remain inside the declared peer
|
|
41
36
|
range, but they no longer get a CI leg of their own.
|
|
@@ -59,7 +54,7 @@ One command installs everything — the settings-page plugin, and the preset it
|
|
|
59
54
|
activation:
|
|
60
55
|
|
|
61
56
|
```sh
|
|
62
|
-
dsh plugin --profile web add dsh-custom-mode@1.
|
|
57
|
+
dsh plugin --profile web add dsh-custom-mode@1.12.1 # pin the version to get this one for sure
|
|
63
58
|
# A bare `add dsh-custom-mode` is subject to pnpm's release cooldown (`minimumReleaseAge`, 1 day by
|
|
64
59
|
# default): for hours after a release it can silently install an OLDER version — measured: a bare
|
|
65
60
|
# install 38 minutes after 1.3.0 shipped landed on 1.0.3. Check what you got with `npm ls
|
|
@@ -118,7 +113,7 @@ Electron shell**, so this plugin's page renders there unchanged. One install pat
|
|
|
118
113
|
too and points you at the app, so the two agree instead of one silently working around the other.
|
|
119
114
|
(A lab simulation of the profile shape can override it with `DSH_ALLOW_DESKTOP_PROFILE=1`.)
|
|
120
115
|
**The app's own bundled CLI is the exception** — `<install>\resources\runtime\cli\bin\dsh.cmd` is allowed
|
|
121
|
-
to operate on that profile (
|
|
116
|
+
to operate on that profile (an older note claimed a pin installed a different version; that note is withdrawn).
|
|
122
117
|
It is a launcher, not a dsh feature: it starts Electron with `ELECTRON_RUN_AS_NODE=1` and runs the
|
|
123
118
|
desktop-host CLI, so it is the app talking to its own profile rather than something driving from outside.
|
|
124
119
|
- **Nothing else to do on the preset side**: `$DSH_HOME/.agent-presets/` is product data shared by the
|
|
@@ -315,13 +310,12 @@ What this plugin supports is declared in `engines.dsh` and the `@deepseek-ai/dsh
|
|
|
315
310
|
`package.json`, and `tools/verify-version-consistency.mjs` (run in CI) asserts that the DSH
|
|
316
311
|
version CI installs and tests falls inside those ranges.
|
|
317
312
|
|
|
318
|
-
**
|
|
319
|
-
since 2026-09-29, and the line the desktop app ships)
|
|
313
|
+
**Three lines are supported: the newest stable line (`0.2.0-rc.2`, which npm's `latest` and `next` both
|
|
314
|
+
point at since 2026-09-29, and the line the desktop app ships), the newest preview line
|
|
315
|
+
(`0.2.1-alpha.1`, which npm's `alpha` points at) and the previous stable (`0.1.7-rc.2` — still inside
|
|
320
316
|
the declared range, and what most existing installs run)** — declared as
|
|
321
|
-
`>=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
|
|
322
|
-
semver
|
|
323
|
-
and runs the whole suite against each — the rendered gate runs on the **newest** line, because that is
|
|
324
|
-
where the shell's own UI changes land first.
|
|
317
|
+
`>=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`.
|
|
318
|
+
The official one-click installer reads `peerDependencies` and **lets prereleases match** (`semver.satisfies(version, range, { includePrerelease: true })` in `dsh-app-boot`). The current stable line `0.2.0-rc.2` and the preview line `0.2.1-alpha.1` both install. The range still names those prereleases because npm and pnpm's default comparison is stricter, so a marketplace using that comparison does not reject the preview line either. CI installs all three lines and runs the full suite on each. The render gate runs only on `0.2.0-rc.2`, where the shell's UI changes land first. Older `0.1.5` / `0.1.6` builds stay inside the peer range and no longer have their own CI leg.
|
|
325
319
|
**The official desktop app (DeepSeek Harness Desktop) is covered too**: it is version-locked to dsh and now
|
|
326
320
|
ships `0.2.0-rc.2`, i.e. the same combination this matrix pins. Install it from inside the app (sidebar →
|
|
327
321
|
Plugins); the CLI is refused for that profile by dsh itself. On `0.2.0-rc.2` measured (2026-09-30, clean
|
|
@@ -393,36 +387,6 @@ what came out of the last one.
|
|
|
393
387
|
|
|
394
388
|
MIT
|
|
395
389
|
|
|
396
|
-
---
|
|
397
|
-
|
|
398
|
-
## Built with
|
|
399
|
-
|
|
400
|
-
| | |
|
|
401
|
-
| --- | --- |
|
|
402
|
-
| Model | DeepSeek V4.1 Flash (`deepseek-v4-flash`, provider `deepseek-official`) |
|
|
403
|
-
| Runtime | DeepSeek Harness **0.2.0-rc.2** (newest line) / **0.1.7-rc.2** (previous stable) — the two supported lines; the token figures below are from an earlier `0.1.6-alpha.2` session |
|
|
404
|
-
| Uncached input | 224,058 tok |
|
|
405
|
-
| Cache reads | 125,638,016 tok |
|
|
406
|
-
| Output | 425,539 tok |
|
|
407
|
-
|
|
408
|
-
The whole project, research and dead ends included, cost the DSH client **126,287,613 tokens** at a
|
|
409
|
-
**99.8%** cache hit rate (cache reads ÷ all input, 125,862,074).
|
|
410
|
-
|
|
411
|
-
### What the review-driven iterations cost
|
|
412
|
-
|
|
413
|
-
Four external reviews turned into releases `1.4.0` … `1.9.0` (the stable-line picker fix, the Windows write
|
|
414
|
-
races, the i18n leaks, the portability fixes, the two UI passes). Measured in the same client:
|
|
415
|
-
|
|
416
|
-
| | |
|
|
417
|
-
| --- | --- |
|
|
418
|
-
| Uncached input | 3,973,904 tok |
|
|
419
|
-
| Cache reads | 711,962,624 tok |
|
|
420
|
-
| Output | 1,559,791 tok |
|
|
421
|
-
|
|
422
|
-
That is **717,496,319 tokens** (3,973,904 + 711,962,624 + 1,559,791) at a **99.4%** cache hit rate
|
|
423
|
-
(711,962,624 ÷ 715,936,528). Counting the first pass as well, the project stands at **843,783,932 tokens**,
|
|
424
|
-
**99.5%** cached (837,600,640 ÷ 841,798,602).
|
|
425
|
-
|
|
426
390
|
## Updating
|
|
427
391
|
|
|
428
392
|
The plugin lives in the profile's `node_modules` and the platform owns that installation, so updating means
|
|
@@ -430,7 +394,7 @@ installing again — your data is not touched:
|
|
|
430
394
|
|
|
431
395
|
```sh
|
|
432
396
|
# the pinned form: what you ask for is what you get
|
|
433
|
-
dsh plugin --profile web add dsh-custom-mode@1.
|
|
397
|
+
dsh plugin --profile web add dsh-custom-mode@1.12.1
|
|
434
398
|
# then restart the DSH process that serves the web profile
|
|
435
399
|
```
|
|
436
400
|
|
|
@@ -439,7 +403,7 @@ dsh plugin --profile web add dsh-custom-mode@1.11.6
|
|
|
439
403
|
hours*. Measured on a clean machine while 1.9.0 was latest: the bare command installed **1.0.1**. Pinning the
|
|
440
404
|
version bypasses the cooldown, which is why the command above carries `@`.
|
|
441
405
|
|
|
442
|
-
**How to know what you are running**: the settings page shows the installed version at the bottom (
|
|
406
|
+
**How to know what you are running**: the settings page shows the installed version at the bottom (Plugin version).
|
|
443
407
|
Compare it with `npm view dsh-custom-mode version`.
|
|
444
408
|
|
|
445
409
|
**What an update does not touch**: `$DSH_HOME/.agent-presets/<your assistants>/` — `prompt.md`, `preset.yml` and
|
package/README.zh.md
CHANGED
|
@@ -8,11 +8,8 @@ base mode / plugin switches / multi-assistant / multi-persona。
|
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
11
|
-
<!-- 社区入口:第二排徽章在每个新仓库里都要加(见 SOP/DSH Plugins/仓库门面模板.md) -->
|
|
12
|
-
|
|
13
11
|
[](https://github.com/BOWLUNA/dsh-custom-mode/stargazers) [](https://www.npmjs.com/package/dsh-custom-mode) [](https://github.com/BOWLUNA/dsh-custom-mode/actions/workflows/test.yml) [](LICENSE) [](https://github.com/BOWLUNA/dsh-custom-mode#readme)
|
|
14
12
|
|
|
15
|
-
<!-- 徽章:两排统一"深色标签 + 彩色值"的两段式;新仓库照抄这一块 -->
|
|
16
13
|
[](https://b23.tv/qJ4Ev0W) [](https://v.douyin.com/VWh0M03Fa4Y/) [](https://xhslink.cn/o/A7QtXmePBBF) [](https://discord.gg/pz97SfAfSy) [](https://github.com/BOWLUNA/dsh-custom-mode/discussions)
|
|
17
14
|
|
|
18
15
|
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)用的自定义模式。它的系统提示词是一个普通文件,可以在 Web 设置页里编辑,**改完下一步模型调用即生效** —— 不用重启,也不用新建会话。
|
|
@@ -22,12 +19,13 @@ base mode / plugin switches / multi-assistant / multi-persona。
|
|
|
22
19
|
常见的叫法:自定义模式 · 自定义提示词 · 系统提示词编辑 · 多助手/多模式 · 多个 Agent 模式 ·
|
|
23
20
|
角色扮演/聊天人格(RP)。
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
**单元测试覆盖三条线,渲染闸门只跑 `0.2.0-rc.2`。** npm 的 `latest` 与 `next` 自 2026-09-29 起都指向 `0.2.0-rc.2`:
|
|
26
23
|
|
|
27
24
|
| dsh | 定位 | 状态 |
|
|
28
25
|
| --- | --- | --- |
|
|
29
26
|
| `0.2.0-rc.2` | **最新线** —— npm 的 `latest` 与 `next` 都指向它,官方桌面端也在这一条线上(与 dsh 同版本号) | ✅ CI(ubuntu node 20/24 + **windows**)+ 真机:这条线上安装、组合树与播种都验过(整道渲染闸门跑在这里)|
|
|
30
|
-
| `0.1
|
|
27
|
+
| `0.2.1-alpha.1` | 预览线 —— npm 的 `alpha` 指向它。单元测试覆盖;渲染闸门不在这条线上 | ✅ CI |
|
|
28
|
+
| `0.1.7-rc.2` | 上一个正式版 —— 仍在声明范围内,现役安装大多在它上面 | ✅ CI。设置页在这条线上会退回壳没提供的控件;渲染闸门不在这里 |
|
|
31
29
|
|
|
32
30
|
更早的线(`0.1.5-rc.3`、`0.1.6-alpha.*`)共用同一套机制,peer 范围仍然接纳,但不再单独占 CI 腿。
|
|
33
31
|
|
|
@@ -48,7 +46,7 @@ base mode / plugin switches / multi-assistant / multi-persona。
|
|
|
48
46
|
一条命令装完——设置页插件,以及它在首次激活时自动播种的 preset:
|
|
49
47
|
|
|
50
48
|
```sh
|
|
51
|
-
dsh plugin --profile web add dsh-custom-mode@1.
|
|
49
|
+
dsh plugin --profile web add dsh-custom-mode@1.12.1 # 钉版本才能确定拿到这一版
|
|
52
50
|
# 不带版本号会受 pnpm 的发布冷却期影响(`minimumReleaseAge`,默认一天):发布后数小时内按名安装
|
|
53
51
|
# 可能**静默装到旧版** —— 实测 1.3.0 发布 38 分钟后按名安装装到了 1.0.3。用 profile 里的
|
|
54
52
|
# `npm ls dsh-custom-mode` 核对实际装到的版本,或像上面那样钉版本。
|
|
@@ -67,7 +65,7 @@ dsh `0.1.6-alpha.2` 起有插件管理页:**侧边栏 → 插件 → 添加插
|
|
|
67
65
|
|
|
68
66
|
| 输入 | 填什么 | 说明 |
|
|
69
67
|
| --- | --- | --- |
|
|
70
|
-
| **包名** | `dsh-custom-mode` |
|
|
68
|
+
| **包名** | `dsh-custom-mode` | 受 pnpm 发布冷却期影响,可能装到旧版。要这一版请钉 `@1.12.1` |
|
|
71
69
|
| **GitHub 仓库地址** | `https://github.com/BOWLUNA/dsh-custom-mode` | 指向**仓库根**即可 |
|
|
72
70
|
| **本地插件目录** | `<你 clone 的路径>` | 就是仓库根 —— 仓库根**本身**就是发布包 |
|
|
73
71
|
|
|
@@ -98,7 +96,7 @@ dsh `0.1.6-alpha.2` 起有插件管理页:**侧边栏 → 插件 → 添加插
|
|
|
98
96
|
`cordis.patch.yml`。所以 `install.sh` 也**拒绝** `--profile desktop` 并指向应用内安装 —— 两边一致,
|
|
99
97
|
而不是某一方偷偷绕过去。(只想在本机模拟 profile 形状时可设 `DSH_ALLOW_DESKTOP_PROFILE=1`。)
|
|
100
98
|
**应用自带的捆绑 CLI 是例外**:`<安装目录>\resources\runtime\cli\bin\dsh.cmd` 可以操作那个 profile
|
|
101
|
-
(1.11.1
|
|
99
|
+
(1.11.1 实测时,命令写的版本和装上的版本对不上,这次记录作废)。它是启动器而不是 dsh 的
|
|
102
100
|
新功能 —— 它用 `ELECTRON_RUN_AS_NODE=1` 起 Electron 去跑 desktop-host 的 CLI,所以那是**应用在跟自己的
|
|
103
101
|
profile 说话**,不是从外面驱动它。
|
|
104
102
|
- **预设那一侧不用做任何事**:`$DSH_HOME/.agent-presets/` 是桌面端与 CLI 共享的产品数据,
|
|
@@ -222,12 +220,11 @@ dsh 的系统提示词通常来自 preset 的 YAML,而官方 `@deepseek-ai/dsh
|
|
|
222
220
|
|
|
223
221
|
## 版本
|
|
224
222
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
这里的策略是"只跟两个最新的":更早的线(`0.1.5-rc.3`、`0.1.6-alpha.*`)仍在 peer 范围内,但不单独占 CI 腿。
|
|
223
|
+
**支持三条线:最新正式线(`0.2.0-rc.2`,npm 的 `latest` 与 `next` 自 2026-09-29 起都指向它,也是桌面端
|
|
224
|
+
所在的那条线)、最新预览线(`0.2.1-alpha.1`,npm 的 `alpha` 指向它)与上一个正式版(`0.1.7-rc.2`,
|
|
225
|
+
仍在声明范围内,现役安装大多在它上面)** —— 声明为
|
|
226
|
+
`>=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`。
|
|
227
|
+
官方一键安装读的是 `peerDependencies`,并且**预发布版参与比较**(`dsh-app-boot` 里的 `semver.satisfies(version, range, { includePrerelease: true })`)。因此当前稳定线 `0.2.0-rc.2` 与预览线 `0.2.1-alpha.1` 都能装上。范围里仍点名预发布版本,是因为 npm / pnpm 的默认比较更严,插件市场走那套语义时也不会把预览线拒掉。CI 对这三条线各装一次并跑完全部测试。渲染闸门只跑 `0.2.0-rc.2`,壳的界面改动先落在这条线上。更早的 `0.1.5` / `0.1.6` 仍在 peer 范围内,不再单独占一条 CI。
|
|
231
228
|
**官方桌面端(DeepSeek Harness Desktop)也在覆盖范围内**:它与 dsh 同版本号,当前即为 `0.2.0-rc.2`,
|
|
232
229
|
正是这条矩阵钉住的组合;在应用内(侧栏 → Plugins)安装即可,CLI 那条路 dsh 自己会拒。
|
|
233
230
|
`0.2.0-rc.2` 实测(2026-09-30,干净的一次性 `DSH_HOME`):`dsh plugin --profile web add dsh-custom-mode`
|
|
@@ -294,43 +291,13 @@ node test/run.mjs # 15 个套件;自己解析出厂 preset 目录(0.1
|
|
|
294
291
|
|
|
295
292
|
MIT
|
|
296
293
|
|
|
297
|
-
---
|
|
298
|
-
|
|
299
|
-
## 构建环境
|
|
300
|
-
|
|
301
|
-
| | |
|
|
302
|
-
| --- | --- |
|
|
303
|
-
| 模型 | DeepSeek V4.1 Flash(`deepseek-v4-flash`,provider `deepseek-official`) |
|
|
304
|
-
| 运行时 | DeepSeek Harness **0.2.0-rc.2**(最新线)/ **0.1.7-rc.2**(上一个正式版)—— 支持的就是这两条线;下面的用量数字来自更早的 `0.1.6-alpha.2` 那一轮 |
|
|
305
|
-
| 未缓存输入 | 224,058 tok |
|
|
306
|
-
| 缓存读取 | 125,638,016 tok |
|
|
307
|
-
| 输出 | 425,539 tok |
|
|
308
|
-
|
|
309
|
-
整个项目(含调研与返工)由 DSH 客户端统计:**126,287,613 tok**,缓存命中率 **99.8%**
|
|
310
|
-
|
|
311
|
-
### 评审驱动的迭代轮次花了多少
|
|
312
|
-
|
|
313
|
-
四份外部审阅变成了 `1.4.0` … `1.9.0` 这些版本(稳定线选择器修复、Windows 写入竞争、i18n 泄漏、可移植性修复、
|
|
314
|
-
两轮 UI 改动)。同一个客户端里的实测:
|
|
315
|
-
|
|
316
|
-
| | |
|
|
317
|
-
| --- | --- |
|
|
318
|
-
| 未缓存输入 | 3,973,904 tok |
|
|
319
|
-
| 缓存读取 | 711,962,624 tok |
|
|
320
|
-
| 输出 | 1,559,791 tok |
|
|
321
|
-
|
|
322
|
-
合计 **717,496,319 tokens**(3,973,904 + 711,962,624 + 1,559,791),缓存命中率 **99.4%**
|
|
323
|
-
(711,962,624 ÷ 715,936,528)。连同第一遍一起,项目累计 **843,783,932 tokens**,缓存命中 **99.5%**
|
|
324
|
-
(837,600,640 ÷ 841,798,602)。
|
|
325
|
-
(缓存读取 ÷ 全部输入 125,862,074)。
|
|
326
|
-
|
|
327
294
|
## 更新
|
|
328
295
|
|
|
329
296
|
插件装在 profile 的 `node_modules` 里,安装由平台负责,所以"更新"就是**再装一次** —— 你的数据不会被碰:
|
|
330
297
|
|
|
331
298
|
```sh
|
|
332
299
|
# 钉版本的写法:要哪版就是哪版
|
|
333
|
-
dsh plugin --profile web add dsh-custom-mode@1.
|
|
300
|
+
dsh plugin --profile web add dsh-custom-mode@1.12.1
|
|
334
301
|
# 然后重启为该 profile 提供服务的 DSH 进程
|
|
335
302
|
```
|
|
336
303
|
|
package/atomic.mjs
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* random temporary name does not help with *that* collision, only with temp-vs-temp ones.
|
|
15
15
|
*/
|
|
16
16
|
import { randomBytes } from 'node:crypto'
|
|
17
|
-
import { existsSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
|
17
|
+
import { existsSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
|
18
|
+
import { join } from 'node:path'
|
|
18
19
|
|
|
19
20
|
/** Synchronous backoff: these write paths are synchronous, so waiting is simpler than async plumbing. */
|
|
20
21
|
function sleepSync(ms) {
|
|
@@ -195,6 +196,35 @@ export function writeAtomicPair(entries) {
|
|
|
195
196
|
* @param {string} text - content to write.
|
|
196
197
|
* @returns {void}
|
|
197
198
|
*/
|
|
199
|
+
/**
|
|
200
|
+
* 掉电落在「旧文件已改名为 .bak、新文件还没装上」时,把唯一的备份移回原名。
|
|
201
|
+
*
|
|
202
|
+
* 多于一份备份时不动:无法判断哪一份是最后的好内容。
|
|
203
|
+
*
|
|
204
|
+
* @param {string} directory - one assistant directory.
|
|
205
|
+
* @returns {void}
|
|
206
|
+
*/
|
|
207
|
+
export function restoreBackups(directory) {
|
|
208
|
+
const names = ['prompt.md', 'agent.cordis.yml', 'preset.yml']
|
|
209
|
+
let entries
|
|
210
|
+
try {
|
|
211
|
+
entries = readdirSync(directory)
|
|
212
|
+
} catch {
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
for (const name of names) {
|
|
216
|
+
const file = join(directory, name)
|
|
217
|
+
if (existsSync(file)) continue
|
|
218
|
+
const backups = entries.filter((entry) => entry.startsWith(name + '.bak-'))
|
|
219
|
+
if (backups.length !== 1) continue
|
|
220
|
+
try {
|
|
221
|
+
renameSync(join(directory, backups[0]), file)
|
|
222
|
+
} catch {
|
|
223
|
+
/* 恢复失败就留着备份,下一次读取再试 */
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
198
228
|
export function writeAtomic(file, text) {
|
|
199
229
|
const temporary = `${file}.tmp-${String(process.pid)}-${randomBytes(4).toString('hex')}`
|
|
200
230
|
try {
|