dsh-engram 0.3.6 → 0.3.7
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.md +70 -0
- package/README.zh.md +60 -0
- package/docs/RELEASE-NOTES.TEMPLATE.md +68 -0
- package/lib/api.js +70 -2
- package/lib/client.js +34 -25
- package/lib/index.js +1 -1
- package/lib/settings.js +43 -23
- package/package.json +17 -13
- package/scripts/dsh-engram.mjs +0 -0
- package/scripts/gen-release-notes.mjs +64 -0
- package/scripts/prepublish-check.mjs +4 -4
- package/scripts/setup-links.mjs +21 -11
package/README.md
CHANGED
|
@@ -238,6 +238,76 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
|
|
|
238
238
|
-d "{\"payload\": $(cat engram-backup.json), \"mode\": \"merge\"}"
|
|
239
239
|
```
|
|
240
240
|
|
|
241
|
+
## DSH compatibility
|
|
242
|
+
|
|
243
|
+
> **Short version.** Every **published** release (`0.2.0` … `0.3.6`) supports
|
|
244
|
+
> the **old** DSH API graph (`0.1.0-rc.7` / `0.1.1`, peers `^0.1.0-rc.7`)
|
|
245
|
+
> **only**. DSH `0.1.2-alpha.2` and newer are **not** compatible with those
|
|
246
|
+
> releases — the settings/client API family broke there. New-DSH support
|
|
247
|
+
> **starts with `0.3.7`**, the first release of the new API generation
|
|
248
|
+
> (described below).
|
|
249
|
+
|
|
250
|
+
| dsh-engram release | Status | DSH support | Declared via |
|
|
251
|
+
| --- | --- | --- | --- |
|
|
252
|
+
| `0.2.0` … `0.3.6` (npm, GitHub v0.3.3–v0.3.6) | published | **old DSH only** — `0.1.0-rc.7` / `0.1.0-rc.8` / `0.1.1-rc.1` / `0.1.1-rc.2` | peers `@deepseek-ai/dsh-*@^0.1.0-rc.7` |
|
|
253
|
+
| `0.3.7` (npm, GitHub v0.3.7) | published | **new DSH only** — `>=0.1.2-alpha.2 <0.2.0-0` | `engines.dsh` + peers `>=0.1.2-alpha.2 <0.2.0-0` |
|
|
254
|
+
|
|
255
|
+
### Where the API family broke — `0.1.2-alpha.2`
|
|
256
|
+
|
|
257
|
+
`0.1.2-alpha.2` is the first version of the new API family and the first that
|
|
258
|
+
**breaks** the released plugin versions:
|
|
259
|
+
|
|
260
|
+
- `@deepseek-ai/dsh-settings` **removed** `settingsNamespace()` and
|
|
261
|
+
`installSettingsSection()` — the exact imports every release ≤ `0.3.6` uses —
|
|
262
|
+
and replaced them with `settings.installSection`. The old host half fails to
|
|
263
|
+
load on `0.1.2-alpha.2+`.
|
|
264
|
+
- The web client half swapped its inject set: `@deepseek-ai/dsh-client-runtime`
|
|
265
|
+
no longer exists in the new family (replaced by `dsh-api-remotes` plus
|
|
266
|
+
`dsh-client-locale` / `-conversation` / `-renderer` / `-ui-settings-plugins`),
|
|
267
|
+
and the settings card used to talk through `ctx.remote.settings`.
|
|
268
|
+
- Supporting bumps at the same boundary: `@deepseek-ai/cordis` `^4.0.1 → ^4.0.2`,
|
|
269
|
+
`@deepseek-ai/schemastery` `^3.18.1 → ^3.18.2`, and `@deepseek-ai/dsh` stopped
|
|
270
|
+
shipping `config/agent-presets` in the package (moved to `dsh.configTrees`).
|
|
271
|
+
|
|
272
|
+
The web config card no longer depends on any of this: since `0.3.7` it reads
|
|
273
|
+
and writes the `dsh-engram` settings namespace through the plugin's **own**
|
|
274
|
+
HTTP route (`/api/dsh-engram/settings`, same-origin fetch like the memory
|
|
275
|
+
viewer), so the card is generation-agnostic on both sides of the
|
|
276
|
+
`0.1.2-alpha.2` boundary.
|
|
277
|
+
|
|
278
|
+
So: keep DSH on `0.1.1-rc.2` or older while running a release ≤ `0.3.6`, and
|
|
279
|
+
upgrade the plugin to `>=0.3.7` before moving DSH to `0.1.2-alpha.2`.
|
|
280
|
+
|
|
281
|
+
### Current release — `0.3.7`
|
|
282
|
+
|
|
283
|
+
This release declares support for DSH `>=0.1.2-alpha.2 <0.2.0-0` and was fully
|
|
284
|
+
validated against the `dsh-v0.1.5-alpha.1` source and web runtime from the local
|
|
285
|
+
`deepseek-harness` checkout.
|
|
286
|
+
|
|
287
|
+
| Component | Supported range |
|
|
288
|
+
| --- | --- |
|
|
289
|
+
| DSH runtime / host / client package family | `>=0.1.2-alpha.2 <0.2.0-0` |
|
|
290
|
+
| `@deepseek-ai/cordis` | `^4.0.2` |
|
|
291
|
+
| `@deepseek-ai/schemastery` | `^3.18.2` |
|
|
292
|
+
| Node.js | `>=22.19.0` |
|
|
293
|
+
|
|
294
|
+
`0.1.2-alpha.2` is the minimum version for this API family: host settings use
|
|
295
|
+
`settings.installSection`, and the browser settings card reads and writes
|
|
296
|
+
through the plugin's own HTTP route (`/api/dsh-engram/settings`, see Route B
|
|
297
|
+
above). Host import and settings-registration smoke checks pass on
|
|
298
|
+
`0.1.2-alpha.2` and `0.1.3-alpha.2`; the full regression suite is validated
|
|
299
|
+
on `0.1.5-alpha.1` (real-machine GET/PUT/conflict/reset/persist-across-restart
|
|
300
|
+
all verified on `dsh 0.1.5-rc.1` with an isolated profile). The older
|
|
301
|
+
`0.1.0-rc.7` and `0.1.1` API graphs are outside this release's compatibility
|
|
302
|
+
range.
|
|
303
|
+
|
|
304
|
+
To install the DSH version used for validation:
|
|
305
|
+
|
|
306
|
+
```sh
|
|
307
|
+
npm install --global @deepseek-ai/dsh@0.1.5-alpha.1
|
|
308
|
+
dsh --version
|
|
309
|
+
```
|
|
310
|
+
|
|
241
311
|
## Install
|
|
242
312
|
|
|
243
313
|
```sh
|
package/README.zh.md
CHANGED
|
@@ -149,6 +149,66 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
|
|
|
149
149
|
-d "{\"payload\": $(cat engram-backup.json), \"mode\": \"merge\"}"
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
+
## 兼容性
|
|
153
|
+
|
|
154
|
+
> **一句话结论**:所有**已真正发布**的版本(`0.2.0` … `0.3.6`)**只支持旧版
|
|
155
|
+
> DSH**(`0.1.0-rc.7` / `0.1.1` API 图,peers `^0.1.0-rc.7`)。DSH
|
|
156
|
+
> `0.1.2-alpha.2` 及更新版本与它们**不兼容**——settings/client API 族在该版本
|
|
157
|
+
> 断裂。新版 DSH 支持**自 `0.3.7` 起**提供——它是新 API 代际的第一个发布(见下文)。
|
|
158
|
+
|
|
159
|
+
| dsh-engram 发布版本 | 状态 | 支持的 DSH | 声明方式 |
|
|
160
|
+
| --- | --- | --- | --- |
|
|
161
|
+
| `0.2.0` … `0.3.6`(npm 全量 + GitHub v0.3.3–v0.3.6) | 已发布 | **仅旧版** —— `0.1.0-rc.7` / `0.1.0-rc.8` / `0.1.1-rc.1` / `0.1.1-rc.2` | peers `@deepseek-ai/dsh-*@^0.1.0-rc.7` |
|
|
162
|
+
| `0.3.7`(npm + GitHub v0.3.7) | 已发布 | **仅新版** —— `>=0.1.2-alpha.2 <0.2.0-0` | `engines.dsh` + peers 同范围 |
|
|
163
|
+
|
|
164
|
+
### API 族断裂点:`0.1.2-alpha.2`
|
|
165
|
+
|
|
166
|
+
`0.1.2-alpha.2` 是新 API 族的第一个版本,也是第一个**让已发布插件失效**的版本:
|
|
167
|
+
|
|
168
|
+
- `@deepseek-ai/dsh-settings` **移除了** `settingsNamespace()` 与
|
|
169
|
+
`installSettingsSection()`(≤ `0.3.6` 的每个发布都直接 import 这两个符号),
|
|
170
|
+
改为 `settings.installSection`——旧宿主端在 `0.1.2-alpha.2+` 上直接加载失败。
|
|
171
|
+
- Web 客户端注入集整体更换:`@deepseek-ai/dsh-client-runtime` 在新代际**已不存在**
|
|
172
|
+
(被 `dsh-api-remotes` + `dsh-client-locale` / `-conversation` / `-renderer` /
|
|
173
|
+
`-ui-settings-plugins` 取代),设置卡此前改走 `ctx.remote.settings`。
|
|
174
|
+
- 同一边界的伴随升级:`@deepseek-ai/cordis` `^4.0.1 → ^4.0.2`、
|
|
175
|
+
`@deepseek-ai/schemastery` `^3.18.1 → ^3.18.2`、`@deepseek-ai/dsh` 不再随包
|
|
176
|
+
发布 `config/agent-presets`(改为 `dsh.configTrees` 机制)。
|
|
177
|
+
|
|
178
|
+
**web 设置卡已不依赖上面任何一项**:自 `0.3.7` 起,配置卡通过插件**自有**
|
|
179
|
+
HTTP 路由(`/api/dsh-engram/settings`,与记忆 viewer 同样的同源 fetch)读写
|
|
180
|
+
`dsh-engram` settings namespace,因此在 `0.1.2-alpha.2` 分界线两侧都是代际无关的。
|
|
181
|
+
|
|
182
|
+
所以:跑 ≤ `0.3.6` 的已发布版本时请把 DSH 保持在 `0.1.1-rc.2` 或更旧;升级
|
|
183
|
+
DSH 到 `0.1.2-alpha.2` 之前,先升级插件到 `>=0.3.7`。
|
|
184
|
+
|
|
185
|
+
### 当前发布版本 —— `0.3.7`
|
|
186
|
+
|
|
187
|
+
当前发布版本声明适配 DSH `>=0.1.2-alpha.2 <0.2.0-0`,并以本地
|
|
188
|
+
`deepseek-harness` 的 `dsh-v0.1.5-alpha.1` 代码和 Web 运行时完成完整验证。
|
|
189
|
+
|
|
190
|
+
| 组件 | 适配版本 |
|
|
191
|
+
| --- | --- |
|
|
192
|
+
| DSH runtime / host / client 包族 | `>=0.1.2-alpha.2 <0.2.0-0` |
|
|
193
|
+
| `@deepseek-ai/cordis` | `^4.0.2` |
|
|
194
|
+
| `@deepseek-ai/schemastery` | `^3.18.2` |
|
|
195
|
+
| Node.js | `>=22.19.0` |
|
|
196
|
+
|
|
197
|
+
`0.1.2-alpha.2` 是这组 API 的最低版本:宿主设置注册使用
|
|
198
|
+
`settings.installSection`,浏览器端设置卡通过插件自有 HTTP 路由
|
|
199
|
+
(`/api/dsh-engram/settings`,见上文 Route B)读写。
|
|
200
|
+
`0.1.2-alpha.2` 和 `0.1.3-alpha.2` 已完成宿主导入与设置注册 smoke 验证;
|
|
201
|
+
`0.1.5-alpha.1` 已完成完整回归(并在 `dsh 0.1.5-rc.1` 隔离 profile 上真机验证
|
|
202
|
+
设置 GET/PUT/冲突/复位/跨重启持久化全通)。旧的 `0.1.0-rc.7` 与 `0.1.1` API 图不在
|
|
203
|
+
本发布版本的兼容范围内。
|
|
204
|
+
|
|
205
|
+
如需按验证版本安装 DSH:
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
npm install --global @deepseek-ai/dsh@0.1.5-alpha.1
|
|
209
|
+
dsh --version
|
|
210
|
+
```
|
|
211
|
+
|
|
152
212
|
## 安装
|
|
153
213
|
|
|
154
214
|
```sh
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Release Notes 模板(每次发布必用)
|
|
2
|
+
|
|
3
|
+
每个 GitHub Release 正文都应包含以下区块。**「DSH 版本支持」一节为必填**,
|
|
4
|
+
其余按发布内容填写。版本支持信息从 `package.json` 读取,避免手写漂移:
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
# 直接生成「DSH 版本支持」markdown 段(读 package.json 的 engines.dsh + peers)
|
|
8
|
+
npm run dsh-engram:release-notes
|
|
9
|
+
# 或
|
|
10
|
+
node scripts/gen-release-notes.mjs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 模板正文(复制后填空)
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
## dsh-engram v<版本号>
|
|
19
|
+
|
|
20
|
+
<一句话:自上一个版本以来的主题>
|
|
21
|
+
|
|
22
|
+
### 功能
|
|
23
|
+
- ...
|
|
24
|
+
|
|
25
|
+
### 修复
|
|
26
|
+
- ...
|
|
27
|
+
|
|
28
|
+
### 其他
|
|
29
|
+
- ...
|
|
30
|
+
|
|
31
|
+
## DSH 版本支持(必填)
|
|
32
|
+
|
|
33
|
+
- **支持的 DSH 范围**:`<复制自 package.json engines.dsh,如 >=0.1.2-alpha.2 <0.2.0-0>`
|
|
34
|
+
- **验证版本**:`<如 dsh-v0.1.5-alpha.1>`(本地 deepseek-harness checkout)
|
|
35
|
+
- **最后验证日期**:`<YYYY-MM-DD>`
|
|
36
|
+
- **安装方式已验证**:`<如 dsh plugin --profile web add dsh-engram>`
|
|
37
|
+
- **peer 声明**:`<复制自 package.json peerDependencies 中的 @deepseek-ai/dsh-* 范围>`
|
|
38
|
+
|
|
39
|
+
> ⚠️ 新旧分界:DSH `0.1.2-alpha.2` 起 settings/client API 族大改
|
|
40
|
+
> (`@deepseek-ai/dsh-settings` 移除 `settingsNamespace`/`installSettingsSection`,
|
|
41
|
+
> 改 `settings.installSection`;client 注入集从 `dsh-client-runtime` 换到
|
|
42
|
+
> `dsh-api-remotes` 等;cordis `^4.0.2`、schemastery `^3.18.2`)。
|
|
43
|
+
> 若本版本仍声明 peers `^0.1.0-rc.7`,说明它**只支持旧版 DSH**
|
|
44
|
+
> (≤ `0.1.1-rc.2`),正文必须写明「仅支持旧版 DSH」并用上面第一段替换当前示例。
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 历史版本口径(补注用,legacy releases 说明)
|
|
50
|
+
|
|
51
|
+
已发布版本 `0.2.0` … `0.3.6` 的 Release/README 如果缺失 DSH 兼容说明,统一补这
|
|
52
|
+
一段:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
## DSH 版本支持
|
|
56
|
+
|
|
57
|
+
- **支持的 DSH 范围**:仅旧版 —— `0.1.0-rc.7` / `0.1.0-rc.8` / `0.1.1-rc.1` / `0.1.1-rc.2`
|
|
58
|
+
- **peer 声明**:`@deepseek-ai/dsh-*@^0.1.0-rc.7`
|
|
59
|
+
- ⚠️ DSH `>=0.1.2-alpha.2` 与本版本**不兼容**(settings/client API 族在该版本断裂)。
|
|
60
|
+
如需新版 DSH,请升级插件到 `master` 的下一个发布。
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## dsh-market 兼容信息格式参考
|
|
64
|
+
|
|
65
|
+
- 包清单的版本兼容字段是 **semver 范围**:`package.json` / `dsh.plugin.json` 的
|
|
66
|
+
`engines.dsh`(如 `">=0.1.5-alpha.2"` 或 `"0.1.0-rc.6 || 0.1.2-rc.1"`)。
|
|
67
|
+
- 市场侧 README 公约(`## Compatibility` 段):
|
|
68
|
+
`DSH 版本` · `最后验证日期` · `安装方式已验证`。
|
package/lib/api.js
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
* wears the loopback-only trust fence (copied from the SSH family): these
|
|
6
6
|
* routes expose the on-disk memory store, so a LAN-exposed dsh web must not
|
|
7
7
|
* serve them. All reads go straight at the opened storage-domain handle
|
|
8
|
-
* (synchronous after the first open); the
|
|
9
|
-
* go through the same store methods the
|
|
8
|
+
* (synchronous after the first open); the mutations (archive / delete, and
|
|
9
|
+
* the settings card's PUT /settings) go through the same store methods the
|
|
10
|
+
* agent tools use (settings writes through the `service.settings` seam).
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { uuid, shortId } from "./util.js";
|
|
13
14
|
import { collectToolCounts, collectUsageStats } from "./usage.js";
|
|
15
|
+
import { SETTINGS_NS } from "./settings.js";
|
|
14
16
|
|
|
15
17
|
/** Route family prefix (kept in sync with the client half). */
|
|
16
18
|
export const API_PREFIX = "/api/dsh-engram";
|
|
@@ -541,6 +543,71 @@ export function makeEngramRoutes(service) {
|
|
|
541
543
|
writeJson(res, 200, { items: list });
|
|
542
544
|
};
|
|
543
545
|
|
|
546
|
+
/** Settings card transport — read the `dsh-engram` namespace through the
|
|
547
|
+
* host settings service (see settings.js for the normalized `service.settings`
|
|
548
|
+
* seam). Same-origin fetch, like every other route in this family; this is
|
|
549
|
+
* what lets the web config card be DSH-generation-agnostic (no
|
|
550
|
+
* connection/remote RPC), at the cost of the same loopback/trustedHosts
|
|
551
|
+
* fence the memory viewer already wears. */
|
|
552
|
+
const getSettings = (_req, res) => {
|
|
553
|
+
const settings = service.settings;
|
|
554
|
+
if (!settings) {
|
|
555
|
+
writeError(res, 503, "settings service not available");
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
try {
|
|
559
|
+
// dsh-settings `describe()` returns a BARE array of namespace descriptors
|
|
560
|
+
// (in registration order); older adapters may wrap it as {namespaces:[...]}.
|
|
561
|
+
// Normalize both onto the HTTP contract {namespaces:[...]} the client uses.
|
|
562
|
+
const payload = settings.describe();
|
|
563
|
+
const namespaces = Array.isArray(payload) ? payload : (payload?.namespaces ?? []);
|
|
564
|
+
const view = namespaces.find((n) => n && n.ns === SETTINGS_NS);
|
|
565
|
+
writeJson(res, 200, view ? { namespaces: [view] } : { namespaces: [] });
|
|
566
|
+
} catch (error) {
|
|
567
|
+
writeError(res, 500, String(error?.message ?? error));
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
/** Settings card transport — mutate the `dsh-engram` namespace. Accepts
|
|
572
|
+
* either path ops (`ops: [{op:"set"|"unset", path:[...], value?}]`, the
|
|
573
|
+
* exact shape the card used through the RPC) or a plain partial patch
|
|
574
|
+
* (`patch: object`). The provider validates and persists through the same
|
|
575
|
+
* document the GUI card always wrote to. */
|
|
576
|
+
const putSettings = async (req, res) => {
|
|
577
|
+
const settings = service.settings;
|
|
578
|
+
if (!settings) {
|
|
579
|
+
writeError(res, 503, "settings service not available");
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const body = await readJsonBody(req);
|
|
583
|
+
if (body.error) return writeError(res, 400, body.error);
|
|
584
|
+
const { ops, patch, expectedRevision } = body.value ?? {};
|
|
585
|
+
if (!Array.isArray(ops) && !(patch !== null && typeof patch === "object")) {
|
|
586
|
+
return writeError(res, 400, "body must provide ops (array of {op,path[,value]}) or patch (object)");
|
|
587
|
+
}
|
|
588
|
+
try {
|
|
589
|
+
if (Array.isArray(ops)) {
|
|
590
|
+
await settings.mutate(SETTINGS_NS, ops, expectedRevision);
|
|
591
|
+
} else {
|
|
592
|
+
await settings.update(SETTINGS_NS, patch, expectedRevision);
|
|
593
|
+
}
|
|
594
|
+
writeJson(res, 200, { ok: true });
|
|
595
|
+
} catch (error) {
|
|
596
|
+
if (error?.code === "SETTINGS_CONFLICT" || error?.name === "SettingsConflictError") {
|
|
597
|
+
writeJson(res, 409, {
|
|
598
|
+
error: {
|
|
599
|
+
code: "SETTINGS_CONFLICT",
|
|
600
|
+
message: String(error?.message ?? "settings moved since read"),
|
|
601
|
+
expected: error?.expected,
|
|
602
|
+
actual: error?.actual,
|
|
603
|
+
},
|
|
604
|
+
});
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
writeError(res, 400, String(error?.message ?? error));
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
|
|
544
611
|
const make = (method, path, handler) => ({
|
|
545
612
|
kind: "exact",
|
|
546
613
|
path: `${API_PREFIX}${path}`,
|
|
@@ -585,6 +652,7 @@ export function makeEngramRoutes(service) {
|
|
|
585
652
|
make("GET", "/model", model),
|
|
586
653
|
make("GET", "/preview", preview),
|
|
587
654
|
make("GET", "/config", readConfig),
|
|
655
|
+
makeUnion("/settings", { GET: getSettings, PUT: putSettings }),
|
|
588
656
|
make("POST", "/memories/archive", archive),
|
|
589
657
|
make("POST", "/memories/delete", remove),
|
|
590
658
|
make("POST", "/tasks/close", closeTask),
|
package/lib/client.js
CHANGED
|
@@ -153,6 +153,34 @@ var EngramApi = class {
|
|
|
153
153
|
})
|
|
154
154
|
);
|
|
155
155
|
}
|
|
156
|
+
/** Settings card transport — read the `dsh-engram` namespace through our
|
|
157
|
+
* own HTTP API instead of the host connection/remote RPC. */
|
|
158
|
+
async getSettings() {
|
|
159
|
+
return readJson(await fetch(`${API_PREFIX}/settings`));
|
|
160
|
+
}
|
|
161
|
+
/** Settings card transport — persist one or more path ops into the
|
|
162
|
+
* `dsh-engram` namespace. Non-2xx surfaces the server's message. */
|
|
163
|
+
async updateSettings(ops, expectedRevision) {
|
|
164
|
+
const response = await fetch(`${API_PREFIX}/settings`, {
|
|
165
|
+
method: "PUT",
|
|
166
|
+
headers: { "content-type": "application/json" },
|
|
167
|
+
body: JSON.stringify({
|
|
168
|
+
ops,
|
|
169
|
+
...expectedRevision !== void 0 ? { expectedRevision } : {}
|
|
170
|
+
})
|
|
171
|
+
});
|
|
172
|
+
let body = null;
|
|
173
|
+
try {
|
|
174
|
+
body = await response.json();
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
const err = body;
|
|
179
|
+
const message = typeof err?.error === "string" ? err.error : typeof err?.error === "object" && err.error !== null && typeof err.error.message === "string" ? err.error.message : `HTTP ${response.status}`;
|
|
180
|
+
throw new EngramApiError(message);
|
|
181
|
+
}
|
|
182
|
+
return body ?? { ok: true };
|
|
183
|
+
}
|
|
156
184
|
};
|
|
157
185
|
|
|
158
186
|
// client/src/EngramTaskDock.tsx
|
|
@@ -3824,14 +3852,7 @@ var EngramScopeImpl = class {
|
|
|
3824
3852
|
this.snapshot = LOADING;
|
|
3825
3853
|
this.notify();
|
|
3826
3854
|
try {
|
|
3827
|
-
const
|
|
3828
|
-
const result = response?.result;
|
|
3829
|
-
if (!result || result.ok !== true) {
|
|
3830
|
-
const err = result && "error" in result ? result.error : { code: "settings.describe", message: "unreachable" };
|
|
3831
|
-
this.fail(`settings.describe failed: ${String(err?.code ?? err)}: ${String(err?.message ?? "")}`);
|
|
3832
|
-
return;
|
|
3833
|
-
}
|
|
3834
|
-
const payload = result.value;
|
|
3855
|
+
const payload = await this.api.getSettings();
|
|
3835
3856
|
const view = payload.namespaces?.find((n) => n.ns === this.ns);
|
|
3836
3857
|
if (!view) {
|
|
3837
3858
|
this.fail(`the '${this.ns}' settings namespace is not served by this host.`);
|
|
@@ -3845,27 +3866,16 @@ var EngramScopeImpl = class {
|
|
|
3845
3866
|
base: view.base ?? view.value,
|
|
3846
3867
|
user: view.user,
|
|
3847
3868
|
revision: view.revision ?? 0,
|
|
3848
|
-
writable: this.opts.writable !== false &&
|
|
3869
|
+
writable: this.opts.writable !== false && applied
|
|
3849
3870
|
};
|
|
3850
3871
|
this.notify();
|
|
3851
3872
|
} catch (error) {
|
|
3852
|
-
this.fail(`settings
|
|
3873
|
+
this.fail(`settings fetch failed: ${String(error instanceof Error ? error.message : error)}`);
|
|
3853
3874
|
}
|
|
3854
3875
|
}
|
|
3855
3876
|
async mutate(op) {
|
|
3856
3877
|
const revision = this.snapshot.revision;
|
|
3857
|
-
|
|
3858
|
-
ns: this.ns,
|
|
3859
|
-
ops: [op],
|
|
3860
|
-
...revision ? { expectedRevision: revision } : {}
|
|
3861
|
-
});
|
|
3862
|
-
const result = response?.result;
|
|
3863
|
-
if (!result || result.ok !== true) {
|
|
3864
|
-
const err = result && "error" in result ? result.error : { code: "settings.mutate", message: "unreachable" };
|
|
3865
|
-
throw new Error(`settings.mutate failed: ${String(err?.code ?? err)}: ${String(err?.message ?? "")}`);
|
|
3866
|
-
}
|
|
3867
|
-
const view = result.value;
|
|
3868
|
-
if (view) this.snapshot = { ...this.snapshot, revision: view.revision ?? revision };
|
|
3878
|
+
await this.api.updateSettings([op], revision > 0 ? revision : void 0);
|
|
3869
3879
|
}
|
|
3870
3880
|
async set(key, value) {
|
|
3871
3881
|
await this.mutate({ op: "set", path: [String(key)], value });
|
|
@@ -4924,7 +4934,7 @@ var en = {
|
|
|
4924
4934
|
refresh: "Refresh",
|
|
4925
4935
|
error: "Load failed"
|
|
4926
4936
|
};
|
|
4927
|
-
var inject = ["slots", "locale", "
|
|
4937
|
+
var inject = ["slots", "locale", "sessions", "workspaces"];
|
|
4928
4938
|
function apply(ctx) {
|
|
4929
4939
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-engram: dictionaries");
|
|
4930
4940
|
const api = new EngramApi();
|
|
@@ -4965,8 +4975,7 @@ function apply(ctx) {
|
|
|
4965
4975
|
} catch (error) {
|
|
4966
4976
|
console.warn("[dsh-engram] settings.section registration failed:", error);
|
|
4967
4977
|
}
|
|
4968
|
-
const
|
|
4969
|
-
const scope = new EngramScopeImpl(connection.api, "dsh-engram");
|
|
4978
|
+
const scope = new EngramScopeImpl(api, "dsh-engram");
|
|
4970
4979
|
const cardInjected = () => ({ scope });
|
|
4971
4980
|
try {
|
|
4972
4981
|
ctx.slots.inject(
|
package/lib/index.js
CHANGED
|
@@ -258,7 +258,7 @@ export function apply(ctx, config) {
|
|
|
258
258
|
|
|
259
259
|
// User settings (web GUI card): merges stored values onto the live config.
|
|
260
260
|
// Non-blocking — when the settings service is absent this never runs.
|
|
261
|
-
installEngramSettings(ctx, resolved, resolved);
|
|
261
|
+
installEngramSettings(ctx, resolved, resolved, service);
|
|
262
262
|
|
|
263
263
|
// Web API for the GUI: deferred on the webServer service, so headless
|
|
264
264
|
// profiles keep running the plugin without it. `register` returns the
|
package/lib/settings.js
CHANGED
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import z from "@deepseek-ai/schemastery";
|
|
15
|
-
import { settingsNamespace, installSettingsSection } from "@deepseek-ai/dsh-settings";
|
|
16
15
|
|
|
17
16
|
/** Namespace join key — must match the client card's `key`. */
|
|
18
17
|
export const SETTINGS_NS_NAME = "dsh-engram";
|
|
19
|
-
|
|
18
|
+
// dsh-settings 0.1.5 exposes the namespace as a validated string at the
|
|
19
|
+
// provider boundary; the legacy branding helper was removed.
|
|
20
|
+
export const SETTINGS_NS = SETTINGS_NS_NAME;
|
|
20
21
|
|
|
21
22
|
/** Fields the GUI exposes (a curated subset of the plugin DEFAULTS). */
|
|
22
23
|
export const SETTINGS_KEYS = [
|
|
@@ -86,30 +87,49 @@ export function makeSettingsSchema(defaults) {
|
|
|
86
87
|
/**
|
|
87
88
|
* Wire the settings section into `ctx` and keep `live` (the plugin's mutable
|
|
88
89
|
* config object) merged with what the user stored. Called from `apply()`.
|
|
90
|
+
*
|
|
91
|
+
* When a `service` object is supplied, a normalized `service.settings` seam
|
|
92
|
+
* ({ describe, mutate, update }) is exposed for the web API — this is the
|
|
93
|
+
* single host-side adapter point: both the current and the legacy DSH
|
|
94
|
+
* settings APIs reduce onto the same three calls, so `/api/dsh-engram/settings`
|
|
95
|
+
* never needs to know which generation it is talking to.
|
|
89
96
|
*/
|
|
90
|
-
export function installEngramSettings(ctx, defaults, live) {
|
|
97
|
+
export function installEngramSettings(ctx, defaults, live, service) {
|
|
91
98
|
const base = settingsBaseFrom(defaults);
|
|
92
99
|
const schema = makeSettingsSchema(defaults);
|
|
93
100
|
let source = () => base;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
101
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
102
|
+
// dsh-settings 0.1.5 moved the optional-consumer wiring onto the provider
|
|
103
|
+
// instance. Keeping the registration on the scoped context preserves the
|
|
104
|
+
// provider attach/detach lifecycle while avoiding a removed helper export.
|
|
105
|
+
const provider = settingsCtx.settings;
|
|
106
|
+
if (service) {
|
|
107
|
+
service.settings = {
|
|
108
|
+
describe: () => provider.describe({ redactSecrets: true }),
|
|
109
|
+
mutate: (ns, ops, expectedRevision) => provider.mutate(ns, ops, expectedRevision),
|
|
110
|
+
update: (ns, patch, expectedRevision) => provider.update(ns, patch, expectedRevision),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
provider.installSection(ctx, SETTINGS_NS, schema, base, {
|
|
114
|
+
setSource(current) {
|
|
115
|
+
source = current;
|
|
116
|
+
},
|
|
117
|
+
onChange() {
|
|
118
|
+
let value;
|
|
119
|
+
try {
|
|
120
|
+
value = source();
|
|
121
|
+
} catch {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (value === null || typeof value !== "object") return;
|
|
125
|
+
for (const key of SETTINGS_KEYS) {
|
|
126
|
+
if (value[key] !== void 0 && live[key] !== value[key]) live[key] = value[key];
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
validate(value) {
|
|
130
|
+
// The schema already constrains every exposed field; refuse nothing here.
|
|
131
|
+
void value;
|
|
132
|
+
},
|
|
133
|
+
});
|
|
114
134
|
});
|
|
115
135
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-engram",
|
|
3
3
|
"description": "Minimalist long-term memory for DeepSeek Harness, distilling symbolic-index + pi-esr ideas: zero-LLM auto-capture, a symbolic [ENGRAM] index with progressive disclosure, and an ESR-lite evidence-closure protocol (esr_task / esr_node / esr_close / esr_link). Real agent-behaviour telemetry (usage rollup + /stats) and a deterministic offline recall benchmark (npm run eval). Hot path is model-free; storage on ctx.storageDomain; web memory viewer, observability panel and config card through DSH's native settings slots.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.7",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"LICENSE"
|
|
34
34
|
],
|
|
35
35
|
"engines": {
|
|
36
|
-
"node": ">=22.19.0"
|
|
36
|
+
"node": ">=22.19.0",
|
|
37
|
+
"dsh": ">=0.1.2-alpha.2 <0.2.0-0"
|
|
37
38
|
},
|
|
38
39
|
"scripts": {
|
|
39
40
|
"setup": "node scripts/setup-links.mjs",
|
|
@@ -47,7 +48,8 @@
|
|
|
47
48
|
"web-compaction:enable": "node scripts/dsh-engram.mjs enable",
|
|
48
49
|
"web-compaction:revert": "node scripts/dsh-engram.mjs revert",
|
|
49
50
|
"dsh-engram": "node scripts/dsh-engram.mjs",
|
|
50
|
-
"dsh-engram:doctor": "node scripts/dsh-engram.mjs doctor"
|
|
51
|
+
"dsh-engram:doctor": "node scripts/dsh-engram.mjs doctor",
|
|
52
|
+
"release-notes": "node scripts/gen-release-notes.mjs"
|
|
51
53
|
},
|
|
52
54
|
"publishConfig": {
|
|
53
55
|
"access": "public"
|
|
@@ -59,9 +61,11 @@
|
|
|
59
61
|
"client": {
|
|
60
62
|
"platform": "web",
|
|
61
63
|
"inject": [
|
|
62
|
-
"@deepseek-ai/dsh-client-
|
|
63
|
-
"@deepseek-ai/dsh-client-
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-
|
|
64
|
+
"@deepseek-ai/dsh-client-locale",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins"
|
|
65
69
|
]
|
|
66
70
|
}
|
|
67
71
|
},
|
|
@@ -69,13 +73,13 @@
|
|
|
69
73
|
"zod": "^4.4.3"
|
|
70
74
|
},
|
|
71
75
|
"peerDependencies": {
|
|
72
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
73
|
-
"@deepseek-ai/dsh-storage-domain": "
|
|
74
|
-
"@deepseek-ai/dsh-settings": "
|
|
75
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
76
|
-
"@deepseek-ai/dsh-compaction": "
|
|
77
|
-
"@deepseek-ai/dsh-compaction-basic": "
|
|
78
|
-
"@deepseek-ai/dsh-llm": "
|
|
76
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
77
|
+
"@deepseek-ai/dsh-storage-domain": ">=0.1.2-alpha.2 <0.2.0-0",
|
|
78
|
+
"@deepseek-ai/dsh-settings": ">=0.1.2-alpha.2 <0.2.0-0",
|
|
79
|
+
"@deepseek-ai/schemastery": "^3.18.2",
|
|
80
|
+
"@deepseek-ai/dsh-compaction": ">=0.1.2-alpha.2 <0.2.0-0",
|
|
81
|
+
"@deepseek-ai/dsh-compaction-basic": ">=0.1.2-alpha.2 <0.2.0-0",
|
|
82
|
+
"@deepseek-ai/dsh-llm": ">=0.1.2-alpha.2 <0.2.0-0"
|
|
79
83
|
},
|
|
80
84
|
"peerDependenciesMeta": {
|
|
81
85
|
"@deepseek-ai/cordis": {
|
package/scripts/dsh-engram.mjs
CHANGED
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* dsh-engram release-note helper.
|
|
4
|
+
*
|
|
5
|
+
* Prints the required "DSH 版本支持" markdown stanza for a release note by
|
|
6
|
+
* reading the single source of truth in package.json (engines.dsh + the
|
|
7
|
+
* @deepseek-ai/dsh-* peers), so the declared compatibility can never drift
|
|
8
|
+
* from what the package.json actually claims.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node scripts/gen-release-notes.mjs # print the stanza
|
|
12
|
+
* node scripts/gen-release-notes.mjs --watch # also echo engines + peers
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { dirname, resolve } from "node:path";
|
|
18
|
+
|
|
19
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
20
|
+
const pkg = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8"));
|
|
21
|
+
|
|
22
|
+
const engines = pkg.engines ?? {};
|
|
23
|
+
const peers = pkg.peerDependencies ?? {};
|
|
24
|
+
|
|
25
|
+
const DSH_PEER_PREFIX = "@deepseek-ai/dsh-";
|
|
26
|
+
const dshPeers = Object.entries(peers)
|
|
27
|
+
.filter(([name]) => name.startsWith(DSH_PEER_PREFIX))
|
|
28
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
29
|
+
|
|
30
|
+
const range = engines.dsh;
|
|
31
|
+
const legacyOld =
|
|
32
|
+
!range && dshPeers.every(([, r]) => r.includes("0.1.0-rc.7"));
|
|
33
|
+
const supportLine = legacyOld
|
|
34
|
+
? "仅旧版 —— `0.1.0-rc.7` / `0.1.0-rc.8` / `0.1.1-rc.1` / `0.1.1-rc.2`"
|
|
35
|
+
: range ?? "(package.json 未声明 engines.dsh —— 补注历史版本时按上一行规则填写)";
|
|
36
|
+
|
|
37
|
+
const peerLine = dshPeers.length
|
|
38
|
+
? dshPeers.map(([n, r]) => ` - \`${n}\`: \`${r}\``).join("\n")
|
|
39
|
+
: " - (无 @deepseek-ai/dsh-* peer 声明)";
|
|
40
|
+
|
|
41
|
+
const body = `## DSH 版本支持
|
|
42
|
+
|
|
43
|
+
- **支持的 DSH 范围**:${supportLine}
|
|
44
|
+
- **peer 声明**:
|
|
45
|
+
${peerLine}
|
|
46
|
+
${
|
|
47
|
+
legacyOld
|
|
48
|
+
? "- ⚠️ DSH `>=0.1.2-alpha.2` 与本版本**不兼容**(settings/client API 族在该版本断裂:`@deepseek-ai/dsh-settings` 移除 `settingsNamespace`/`installSettingsSection`,改 `settings.installSection`;client 注入集换到 `dsh-api-remotes` 等;cordis `^4.0.2`、schemastery `^3.18.2`)。如需新版 DSH,请升级插件到 `master` 的下一个发布。"
|
|
49
|
+
: range
|
|
50
|
+
? `- **验证版本**:<如 dsh-v0.1.5-alpha.1>(本地 deepseek-harness checkout)
|
|
51
|
+
- **最后验证日期**:<YYYY-MM-DD>
|
|
52
|
+
- **安装方式已验证**:<如 dsh plugin --profile web add dsh-engram>`
|
|
53
|
+
: ""
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
if (process.argv.includes("--watch")) {
|
|
58
|
+
console.log(`engines.dsh = ${range ?? "<未声明>"}`);
|
|
59
|
+
console.log(`engines.node = ${engines.node ?? "<未声明>"}`);
|
|
60
|
+
console.log(`@deepseek-ai/cordis peer = ${peers["@deepseek-ai/cordis"] ?? "<未声明>"}`);
|
|
61
|
+
console.log(`@deepseek-ai/schemastery peer = ${peers["@deepseek-ai/schemastery"] ?? "<未声明>"}`);
|
|
62
|
+
console.log("---");
|
|
63
|
+
}
|
|
64
|
+
process.stdout.write(body);
|
|
@@ -23,10 +23,10 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
23
23
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
24
24
|
const tmp = mkdtempSync(join(tmpdir(), "dsh-engram-pack-"));
|
|
25
25
|
const PEERS = [
|
|
26
|
-
"@deepseek-ai/cordis@^4.0.
|
|
27
|
-
"@deepseek-ai/dsh-settings
|
|
28
|
-
"@deepseek-ai/dsh-storage-domain
|
|
29
|
-
"@deepseek-ai/schemastery@^3.18.
|
|
26
|
+
"@deepseek-ai/cordis@^4.0.2",
|
|
27
|
+
"@deepseek-ai/dsh-settings@>=0.1.2-alpha.2 <0.2.0-0",
|
|
28
|
+
"@deepseek-ai/dsh-storage-domain@>=0.1.2-alpha.2 <0.2.0-0",
|
|
29
|
+
"@deepseek-ai/schemastery@^3.18.2",
|
|
30
30
|
];
|
|
31
31
|
|
|
32
32
|
function sh(cmd, args, cwd) {
|
package/scripts/setup-links.mjs
CHANGED
|
@@ -93,6 +93,11 @@ function findStoreZod(harness) {
|
|
|
93
93
|
function buildPlan(harness) {
|
|
94
94
|
const zodDir = findStoreZod(harness);
|
|
95
95
|
const plan = [
|
|
96
|
+
{
|
|
97
|
+
label: "@deepseek-ai/cordis",
|
|
98
|
+
mount: join(SCOPE, "cordis"),
|
|
99
|
+
target: join(harness, "vendor", "cordis"),
|
|
100
|
+
},
|
|
96
101
|
{
|
|
97
102
|
label: "zod",
|
|
98
103
|
mount: join(NODE_MODULES, "zod"),
|
|
@@ -141,7 +146,9 @@ function buildPlan(harness) {
|
|
|
141
146
|
function isInstalled(mount, expected) {
|
|
142
147
|
if (existsSync(mount)) {
|
|
143
148
|
try {
|
|
144
|
-
|
|
149
|
+
// readlinkSync() returns a relative target for pnpm-created links; resolve
|
|
150
|
+
// it from the mount's parent, rather than from the process cwd.
|
|
151
|
+
return resolve(dirname(mount), readlinkSync(mount)) === resolve(expected);
|
|
145
152
|
} catch {
|
|
146
153
|
/* not a symlink — fall through */
|
|
147
154
|
}
|
|
@@ -185,24 +192,27 @@ function ensureLink(item) {
|
|
|
185
192
|
return;
|
|
186
193
|
}
|
|
187
194
|
// 允许替换:符号链接直接 unlink;pnpm 预建的空目录用 rmdir 清掉。
|
|
195
|
+
let removed = false;
|
|
188
196
|
try {
|
|
189
197
|
unlinkSync(mount);
|
|
190
198
|
console.log(` ~ ${label}: 替换旧链接`);
|
|
191
|
-
|
|
199
|
+
removed = true;
|
|
192
200
|
} catch {
|
|
193
201
|
/* not a symlink */
|
|
194
202
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
203
|
+
if (!removed) {
|
|
204
|
+
try {
|
|
205
|
+
if (readdirSync(mount).length === 0) {
|
|
206
|
+
rmdirSync(mount);
|
|
207
|
+
console.log(` ~ ${label}: 移除空目录后重建`);
|
|
208
|
+
} else {
|
|
209
|
+
console.log(` ! ${label}: ${mount} 是含文件的真实目录,无法自动替换,请手动清理后重试`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
} catch {
|
|
213
|
+
console.log(` ! ${label}: ${mount} 无法自动替换,请手动清理后重试`);
|
|
201
214
|
return;
|
|
202
215
|
}
|
|
203
|
-
} catch {
|
|
204
|
-
console.log(` ! ${label}: ${mount} 无法自动替换,请手动清理后重试`);
|
|
205
|
-
return;
|
|
206
216
|
}
|
|
207
217
|
}
|
|
208
218
|
if (CHECK_ONLY) {
|