@max-null/dsh-memory 0.7.0 → 0.7.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.md +12 -0
- package/dist/engine.d.ts +50 -1
- package/dist/engine.js +133 -5
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -55,6 +55,16 @@ npm install @max-null/dsh-memory
|
|
|
55
55
|
|
|
56
56
|
两个根都可用 config 覆盖(`globalRoot` / `projectRoot`)。`memory_list` / `memory_search` 不带 `namespace` 过滤时会同时查两层。旧版双重前缀文件名(`memory_project_memory_project_<hash>.json`)在打开时自动迁移为规范名。
|
|
57
57
|
|
|
58
|
+
## 多实例共存(0.7.1)
|
|
59
|
+
|
|
60
|
+
两个 DSH 实例(例如 DSH web 与 SSiD 桌面壳)可以同时运行、共用同一个 `DSH_HOME`,**记忆不再互相抹掉**。
|
|
61
|
+
|
|
62
|
+
DSH 存储层的写入是「读—改—写全量覆盖」,且官方两个后端都声明不做跨进程协调(`storage-json`:*writer per process and last-write-wins is correct*;`storage-sqlite`:*cross-process coordination is out of scope*)。插件层的处置是**写前重读**(等价于 update 前先 select):每次写入在串行区里先比对存储文件指纹(mtime + size),发现磁盘被别的实例动过就先重载再写。代价是磁盘没变时的一次 `statSync`。
|
|
63
|
+
|
|
64
|
+
残余窗口只剩两个实例在**同一瞬间**写——此时后写者赢;同时写**不同**记忆已不再互相影响。
|
|
65
|
+
|
|
66
|
+
**未覆盖**:`workspace.json`(工作区登记)由 DSH 自己的 workspace 服务写,插件层够不着,仍会被双实例互相覆盖;根因处置在上游——[discussion #6882](https://github.com/deepseek-ai/deepseek-harness/discussions/6882)。当前防线是轮转快照备份(SSiD 侧 `shell/scripts/backup-storages.mjs` + 计划任务)。
|
|
67
|
+
|
|
58
68
|
## 使用流程(写入即生效,人为例外干预)
|
|
59
69
|
|
|
60
70
|
提示词模板库(0.6.0):`prompt_search / prompt_get / prompt_list / prompt_add` 四个工具管理**模板库**——
|
|
@@ -102,6 +112,8 @@ memory_forget → 随时删除(删除始终是人的动作)
|
|
|
102
112
|
|
|
103
113
|
## SSID 系列
|
|
104
114
|
|
|
115
|
+
本插件是 **[SSID(思灵 · Seek Soul in Darkness)](https://github.com/Max-Null/seek-soul-in-darkness)** 全家桶的一员;也可以单独安装到任意 DSH profile——除设置页的记忆面板外不依赖其它同系列插件。
|
|
116
|
+
|
|
105
117
|
## 开发
|
|
106
118
|
|
|
107
119
|
```bash
|
package/dist/engine.d.ts
CHANGED
|
@@ -222,6 +222,10 @@ export declare class MemoryEngine extends Service {
|
|
|
222
222
|
/** backend 只注册一次(registry 重名抛 duplicate;reload 清缓存后不得重复注册)。 */
|
|
223
223
|
private registeredProjectBackends;
|
|
224
224
|
private facility?;
|
|
225
|
+
/** 各存储文件上次被我们确认过的指纹;用于识别「另一个实例写过」。 */
|
|
226
|
+
private readonly storeStamps;
|
|
227
|
+
/** 写入串行链:「刷新—读—改—写」必须整体互斥,否则并发写会在其间穿插。 */
|
|
228
|
+
private writeChain;
|
|
225
229
|
/** 会话启动维护只跑一次的标记(2026-09-15)。 */
|
|
226
230
|
private maintenanceDone;
|
|
227
231
|
constructor(ctx: import('@deepseek-ai/cordis').Context, config?: MemoryConfig);
|
|
@@ -240,7 +244,13 @@ export declare class MemoryEngine extends Service {
|
|
|
240
244
|
/**
|
|
241
245
|
* 强制重载存储:关闭全局域与全部已开工作区域后重开(unit 释放后
|
|
242
246
|
* backend 重读文件),放弃内存缓存。供外部编辑记忆文件后刷新
|
|
243
|
-
* (JsonStorageBackend 打开时加载一次,无 watch——2026-08-19
|
|
247
|
+
* (JsonStorageBackend 打开时加载一次,无 watch——2026-08-19 实测);
|
|
248
|
+
* 多实例写入协调(0.7.1)让它在每次「磁盘被别人改过」时自动触发。
|
|
249
|
+
*
|
|
250
|
+
* **已打开的工作区要重开,而不是一关了之**:注入 provider 是同步回调、只读
|
|
251
|
+
* 已打开的表,关掉就等同于「这个工作区没有常驻记忆」——工作区记忆会从上下文里
|
|
252
|
+
* 静默消失,直到某次工具调用把它重新打开。重开的表同样从磁盘重读,
|
|
253
|
+
* 「放弃内存缓存」这条语义不变,改变的只是「重读完别忘了装回去」。
|
|
244
254
|
*/
|
|
245
255
|
reload(): Promise<void>;
|
|
246
256
|
/**
|
|
@@ -248,7 +258,30 @@ export declare class MemoryEngine extends Service {
|
|
|
248
258
|
* 命中危险内容规则则隔离:`status` 留 `suggested`(天然不注入)**并且**置
|
|
249
259
|
* `quarantined`(不进检索),双保险,等待人工放行或删除。
|
|
250
260
|
*/
|
|
261
|
+
/** 文件指纹:mtime + size(size 兜住同一毫秒内的两次写)。 */
|
|
262
|
+
private static fileStamp;
|
|
263
|
+
/** 全局记忆的存储文件(JsonStorageBackend 的 unit 文件名 = domain 名)。 */
|
|
264
|
+
private globalStoreFile;
|
|
265
|
+
/** 某工作区记忆的存储文件。 */
|
|
266
|
+
private projectStoreFile;
|
|
267
|
+
/** 记录当前指纹,作为「这就是我们造成的状态」的基准(写入完成后调用)。 */
|
|
268
|
+
private noteStoreStamps;
|
|
269
|
+
/**
|
|
270
|
+
* 写前新鲜度门:存储文件被外部改过就重载,避免整份覆盖吃掉对方的写入。
|
|
271
|
+
* 必须在取表**之前**调用——{@link reload} 重开 facility,此前取到的 table 引用会失效。
|
|
272
|
+
*/
|
|
273
|
+
private refreshForWrite;
|
|
274
|
+
/**
|
|
275
|
+
* 一次写入的完整作用域:串行化 → 新鲜度门 → 执行 → 记指纹。
|
|
276
|
+
* 刷新与「读—改—写」必须同处一个互斥区,否则并发写会在两者之间穿插。
|
|
277
|
+
* @param projectCwd - 本次写入涉及的工作区;无则只覆盖全局存储。
|
|
278
|
+
* @param run - 实际的读改写;必须在内部重新取表(刷新后旧引用失效)。
|
|
279
|
+
* @returns `run` 的返回值。
|
|
280
|
+
*/
|
|
281
|
+
private writeScope;
|
|
251
282
|
remember(input: MemoryWrite, projectCwd?: string): Promise<MemoryRecord>;
|
|
283
|
+
/** {@link remember} 的实现体;必须经 `writeScope` 进入(多实例写入协调)。 */
|
|
284
|
+
private rememberInner;
|
|
252
285
|
list(filter?: MemoryFilter, projectCwd?: string): Promise<MemoryRecord[]>;
|
|
253
286
|
/**
|
|
254
287
|
* 关键词检索。每次成功返回的检索都记一条查询日志(⑥ 2026-09-15)——命中与否都记:
|
|
@@ -283,6 +316,8 @@ export declare class MemoryEngine extends Service {
|
|
|
283
316
|
private tableOf;
|
|
284
317
|
/** 命中标记:写回 lastUsedAt 与命中次数,达标即自动升常驻(2026-09-15)。 */
|
|
285
318
|
private markUsed;
|
|
319
|
+
/** {@link markUsed} 的实现体;必须经 `writeScope` 进入(`recordUse` 在此之内)。 */
|
|
320
|
+
private markUsedInner;
|
|
286
321
|
/**
|
|
287
322
|
* 命中记账 + 自动升级(2026-09-15 静默记忆机制):命中次数达阈值即自动打开常驻
|
|
288
323
|
* 注入——「被反复检索命中」是它值得每轮付费的唯一客观证据。统计性字段不 emit,
|
|
@@ -331,6 +366,8 @@ export declare class MemoryEngine extends Service {
|
|
|
331
366
|
* @returns 被撤下的条数(诊断与日志用)。
|
|
332
367
|
*/
|
|
333
368
|
demoteStale(projectCwd?: string, now?: number): Promise<number>;
|
|
369
|
+
/** {@link demoteStale} 的实现体;必须经 `writeScope` 进入。 */
|
|
370
|
+
private demoteStaleInner;
|
|
334
371
|
/**
|
|
335
372
|
* 锚点批量校验(2026-09-15):会话启动时调一次,把失效的条目标成 `stale`。
|
|
336
373
|
*
|
|
@@ -343,6 +380,8 @@ export declare class MemoryEngine extends Service {
|
|
|
343
380
|
* @returns 本次状态发生变化的条数。
|
|
344
381
|
*/
|
|
345
382
|
verifyAnchors(probes: AnchorProbes, projectCwd?: string): Promise<number>;
|
|
383
|
+
/** {@link verifyAnchors} 的实现体;必须经 `writeScope` 进入。 */
|
|
384
|
+
private verifyAnchorsInner;
|
|
346
385
|
/**
|
|
347
386
|
* 旧数据迁移(2026-09-15 静默记忆机制):一次性、幂等,会话启动维护时调用。
|
|
348
387
|
*
|
|
@@ -363,13 +402,21 @@ export declare class MemoryEngine extends Service {
|
|
|
363
402
|
quarantined: number;
|
|
364
403
|
counted: number;
|
|
365
404
|
}>;
|
|
405
|
+
/** {@link migrateLegacy} 的实现体;必须经 `writeScope` 进入。 */
|
|
406
|
+
private migrateLegacyInner;
|
|
366
407
|
forget(id: MemoryId, projectCwd?: string): Promise<boolean>;
|
|
408
|
+
/** {@link forget} 的实现体;必须经 `writeScope` 进入。 */
|
|
409
|
+
private forgetInner;
|
|
367
410
|
setStatus(id: MemoryId, status: MemoryStatus, projectCwd?: string): Promise<MemoryRecord>;
|
|
411
|
+
/** {@link setStatus} 的实现体;必须经 `writeScope` 进入。 */
|
|
412
|
+
private setStatusInner;
|
|
368
413
|
/**
|
|
369
414
|
* 注入维度开关(0.3.0):只改 injected,不动审核状态。供 UI 面板
|
|
370
415
|
* 「常驻注入」开关调用(remote.setInjected)。
|
|
371
416
|
*/
|
|
372
417
|
setInjected(id: MemoryId, injected: boolean, projectCwd?: string): Promise<MemoryRecord>;
|
|
418
|
+
/** {@link setInjected} 的实现体;必须经 `writeScope` 进入。 */
|
|
419
|
+
private setInjectedInner;
|
|
373
420
|
/**
|
|
374
421
|
* 修改一条记忆的内容/关键词(0.3.1 整理记忆用)。2026-09-15 起:改动**不再**
|
|
375
422
|
* 退回待审核——静默机制下记忆由系统与模型自行维护,更新即保持原有审核状态;
|
|
@@ -379,6 +426,8 @@ export declare class MemoryEngine extends Service {
|
|
|
379
426
|
content?: string;
|
|
380
427
|
keywords?: string[];
|
|
381
428
|
}, projectCwd?: string): Promise<MemoryRecord>;
|
|
429
|
+
/** {@link update} 的实现体;必须经 `writeScope` 进入。 */
|
|
430
|
+
private updateInner;
|
|
382
431
|
/**
|
|
383
432
|
* 预热某工作区的 project 表(异步打开并缓存)。system prompt 的
|
|
384
433
|
* `memory:recall` provider 是同步回调——只读已打开的表;预热让
|
package/dist/engine.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* is always created `suggested` and becomes effective only through `setStatus`.
|
|
6
6
|
*/
|
|
7
7
|
import { randomUUID } from 'node:crypto';
|
|
8
|
-
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { existsSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { Service } from '@deepseek-ai/cordis';
|
|
@@ -170,6 +170,10 @@ export class MemoryEngine extends Service {
|
|
|
170
170
|
/** backend 只注册一次(registry 重名抛 duplicate;reload 清缓存后不得重复注册)。 */
|
|
171
171
|
registeredProjectBackends = new Set();
|
|
172
172
|
facility;
|
|
173
|
+
/** 各存储文件上次被我们确认过的指纹;用于识别「另一个实例写过」。 */
|
|
174
|
+
storeStamps = new Map();
|
|
175
|
+
/** 写入串行链:「刷新—读—改—写」必须整体互斥,否则并发写会在其间穿插。 */
|
|
176
|
+
writeChain = Promise.resolve();
|
|
173
177
|
/** 会话启动维护只跑一次的标记(2026-09-15)。 */
|
|
174
178
|
maintenanceDone = false;
|
|
175
179
|
constructor(ctx, config = {}) {
|
|
@@ -258,22 +262,114 @@ export class MemoryEngine extends Service {
|
|
|
258
262
|
/**
|
|
259
263
|
* 强制重载存储:关闭全局域与全部已开工作区域后重开(unit 释放后
|
|
260
264
|
* backend 重读文件),放弃内存缓存。供外部编辑记忆文件后刷新
|
|
261
|
-
* (JsonStorageBackend 打开时加载一次,无 watch——2026-08-19
|
|
265
|
+
* (JsonStorageBackend 打开时加载一次,无 watch——2026-08-19 实测);
|
|
266
|
+
* 多实例写入协调(0.7.1)让它在每次「磁盘被别人改过」时自动触发。
|
|
267
|
+
*
|
|
268
|
+
* **已打开的工作区要重开,而不是一关了之**:注入 provider 是同步回调、只读
|
|
269
|
+
* 已打开的表,关掉就等同于「这个工作区没有常驻记忆」——工作区记忆会从上下文里
|
|
270
|
+
* 静默消失,直到某次工具调用把它重新打开。重开的表同样从磁盘重读,
|
|
271
|
+
* 「放弃内存缓存」这条语义不变,改变的只是「重读完别忘了装回去」。
|
|
262
272
|
*/
|
|
263
273
|
async reload() {
|
|
274
|
+
const opened = [...this.projectTables.keys()];
|
|
264
275
|
await this.facility?.closeAll();
|
|
265
276
|
for (const facility of this.projectFacilities.values())
|
|
266
277
|
await facility.closeAll();
|
|
267
278
|
this.projectTables.clear();
|
|
268
279
|
this.projectFacilities.clear();
|
|
269
280
|
await this.openGlobalFacility();
|
|
281
|
+
for (const key of opened) {
|
|
282
|
+
// 重开失败 = 该工作区退回「未打开」(本轮注入缺这部分,下次访问重试),
|
|
283
|
+
// 不该让触发重载的那一整次写入跟着失败。
|
|
284
|
+
await this.projectTableFor(key).catch(() => undefined);
|
|
285
|
+
}
|
|
270
286
|
}
|
|
271
287
|
/**
|
|
272
288
|
* Create one record. 低危内容直接 `approved`(静默生效,2026-09-15 静默记忆机制);
|
|
273
289
|
* 命中危险内容规则则隔离:`status` 留 `suggested`(天然不注入)**并且**置
|
|
274
290
|
* `quarantined`(不进检索),双保险,等待人工放行或删除。
|
|
275
291
|
*/
|
|
292
|
+
// ── 多实例写入协调(2026-09-17)────────────────────────────────────────
|
|
293
|
+
//
|
|
294
|
+
// 另一个 DSH 实例(web 与 SSiD 同时运行)共用同一个 DSH_HOME 时,它写的记忆只落在
|
|
295
|
+
// 磁盘上;我们的内存态若比磁盘旧,下一次整份覆盖就会把对方的新记忆抹掉。DSH 的
|
|
296
|
+
// 存储层明确不做跨进程协调(storage-json「writer per process and last-write-wins
|
|
297
|
+
// is correct」、storage-sqlite「cross-process coordination is out of scope」),
|
|
298
|
+
// 所以协调放在这里:开写前比对存储文件指纹,变过就重载——等价于「update 前先
|
|
299
|
+
// select」。残余窗口只剩两个实例真正同时写,而记忆写入是低频操作。
|
|
300
|
+
/** 文件指纹:mtime + size(size 兜住同一毫秒内的两次写)。 */
|
|
301
|
+
static fileStamp(file) {
|
|
302
|
+
try {
|
|
303
|
+
const stat = statSync(file);
|
|
304
|
+
return `${stat.mtimeMs}:${stat.size}`;
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
return 'missing';
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/** 全局记忆的存储文件(JsonStorageBackend 的 unit 文件名 = domain 名)。 */
|
|
311
|
+
globalStoreFile() {
|
|
312
|
+
return join(this.config.globalRoot ?? globalRoot(), 'memory.json');
|
|
313
|
+
}
|
|
314
|
+
/** 某工作区记忆的存储文件。 */
|
|
315
|
+
projectStoreFile(projectCwd) {
|
|
316
|
+
const key = join(projectCwd);
|
|
317
|
+
return join(this.projectRootFor(key), `${this.projectBackendName(key)}.json`);
|
|
318
|
+
}
|
|
319
|
+
/** 记录当前指纹,作为「这就是我们造成的状态」的基准(写入完成后调用)。 */
|
|
320
|
+
noteStoreStamps(projectCwd) {
|
|
321
|
+
const global = this.globalStoreFile();
|
|
322
|
+
this.storeStamps.set(global, MemoryEngine.fileStamp(global));
|
|
323
|
+
const scopes = new Set(this.projectTables.keys());
|
|
324
|
+
if (projectCwd !== undefined && projectCwd !== '')
|
|
325
|
+
scopes.add(join(projectCwd));
|
|
326
|
+
for (const key of scopes) {
|
|
327
|
+
const file = this.projectStoreFile(key);
|
|
328
|
+
this.storeStamps.set(file, MemoryEngine.fileStamp(file));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* 写前新鲜度门:存储文件被外部改过就重载,避免整份覆盖吃掉对方的写入。
|
|
333
|
+
* 必须在取表**之前**调用——{@link reload} 重开 facility,此前取到的 table 引用会失效。
|
|
334
|
+
*/
|
|
335
|
+
async refreshForWrite(projectCwd) {
|
|
336
|
+
if (this.storeStamps.size === 0) {
|
|
337
|
+
this.noteStoreStamps(projectCwd);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const files = [this.globalStoreFile()];
|
|
341
|
+
if (projectCwd !== undefined && projectCwd !== '')
|
|
342
|
+
files.push(this.projectStoreFile(projectCwd));
|
|
343
|
+
for (const file of files) {
|
|
344
|
+
if (MemoryEngine.fileStamp(file) === this.storeStamps.get(file))
|
|
345
|
+
continue;
|
|
346
|
+
await this.reload();
|
|
347
|
+
this.noteStoreStamps(projectCwd);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* 一次写入的完整作用域:串行化 → 新鲜度门 → 执行 → 记指纹。
|
|
353
|
+
* 刷新与「读—改—写」必须同处一个互斥区,否则并发写会在两者之间穿插。
|
|
354
|
+
* @param projectCwd - 本次写入涉及的工作区;无则只覆盖全局存储。
|
|
355
|
+
* @param run - 实际的读改写;必须在内部重新取表(刷新后旧引用失效)。
|
|
356
|
+
* @returns `run` 的返回值。
|
|
357
|
+
*/
|
|
358
|
+
writeScope(projectCwd, run) {
|
|
359
|
+
const task = this.writeChain.then(async () => {
|
|
360
|
+
await this.refreshForWrite(projectCwd);
|
|
361
|
+
const result = await run();
|
|
362
|
+
this.noteStoreStamps(projectCwd);
|
|
363
|
+
return result;
|
|
364
|
+
});
|
|
365
|
+
this.writeChain = task.then(() => undefined, () => undefined);
|
|
366
|
+
return task;
|
|
367
|
+
}
|
|
276
368
|
async remember(input, projectCwd) {
|
|
369
|
+
return this.writeScope(projectCwd, () => this.rememberInner(input, projectCwd));
|
|
370
|
+
}
|
|
371
|
+
/** {@link remember} 的实现体;必须经 `writeScope` 进入(多实例写入协调)。 */
|
|
372
|
+
async rememberInner(input, projectCwd) {
|
|
277
373
|
const namespace = input.namespace ?? 'global';
|
|
278
374
|
const table = namespace === 'project'
|
|
279
375
|
? await this.projectTableFor(projectCwd)
|
|
@@ -479,13 +575,17 @@ export class MemoryEngine extends Service {
|
|
|
479
575
|
}
|
|
480
576
|
/** 命中标记:写回 lastUsedAt 与命中次数,达标即自动升常驻(2026-09-15)。 */
|
|
481
577
|
async markUsed(ids, projectCwd) {
|
|
578
|
+
return this.writeScope(projectCwd, () => this.markUsedInner(ids, projectCwd));
|
|
579
|
+
}
|
|
580
|
+
/** {@link markUsed} 的实现体;必须经 `writeScope` 进入(`recordUse` 在此之内)。 */
|
|
581
|
+
async markUsedInner(ids, projectCwd) {
|
|
482
582
|
if (ids.length === 0)
|
|
483
583
|
return;
|
|
484
584
|
const now = Date.now();
|
|
485
585
|
const global = this.requireTable('global');
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
586
|
+
// 取表走异步版:`writeScope` 可能在本次进入前刚 `reload()`(外部实例改过存储),
|
|
587
|
+
// 那时同步缓存已被清空——继续读缓存会把 project 侧的命中静默漏记。
|
|
588
|
+
const project = await this.projectTableFor(projectCwd);
|
|
489
589
|
for (const id of ids) {
|
|
490
590
|
const globalBlock = global.get(id);
|
|
491
591
|
if (globalBlock !== undefined) {
|
|
@@ -625,6 +725,10 @@ export class MemoryEngine extends Service {
|
|
|
625
725
|
* @returns 被撤下的条数(诊断与日志用)。
|
|
626
726
|
*/
|
|
627
727
|
async demoteStale(projectCwd, now = Date.now()) {
|
|
728
|
+
return this.writeScope(projectCwd, () => this.demoteStaleInner(projectCwd, now));
|
|
729
|
+
}
|
|
730
|
+
/** {@link demoteStale} 的实现体;必须经 `writeScope` 进入。 */
|
|
731
|
+
async demoteStaleInner(projectCwd, now = Date.now()) {
|
|
628
732
|
const threshold = now - AUTO_DEMOTE_DAYS * 24 * 60 * 60 * 1000;
|
|
629
733
|
const tables = [this.requireTable('global')];
|
|
630
734
|
if (projectCwd !== undefined && projectCwd !== '') {
|
|
@@ -664,6 +768,10 @@ export class MemoryEngine extends Service {
|
|
|
664
768
|
* @returns 本次状态发生变化的条数。
|
|
665
769
|
*/
|
|
666
770
|
async verifyAnchors(probes, projectCwd) {
|
|
771
|
+
return this.writeScope(projectCwd, () => this.verifyAnchorsInner(probes, projectCwd));
|
|
772
|
+
}
|
|
773
|
+
/** {@link verifyAnchors} 的实现体;必须经 `writeScope` 进入。 */
|
|
774
|
+
async verifyAnchorsInner(probes, projectCwd) {
|
|
667
775
|
const tables = [this.requireTable('global')];
|
|
668
776
|
if (projectCwd !== undefined && projectCwd !== '') {
|
|
669
777
|
const project = await this.projectTableFor(projectCwd);
|
|
@@ -713,6 +821,10 @@ export class MemoryEngine extends Service {
|
|
|
713
821
|
* @returns 迁移统计(放行 / 隔离 / 回填计数)。
|
|
714
822
|
*/
|
|
715
823
|
async migrateLegacy(projectCwd) {
|
|
824
|
+
return this.writeScope(projectCwd, () => this.migrateLegacyInner(projectCwd));
|
|
825
|
+
}
|
|
826
|
+
/** {@link migrateLegacy} 的实现体;必须经 `writeScope` 进入。 */
|
|
827
|
+
async migrateLegacyInner(projectCwd) {
|
|
716
828
|
const tables = [this.requireTable('global')];
|
|
717
829
|
if (projectCwd !== undefined && projectCwd !== '') {
|
|
718
830
|
const project = await this.projectTableFor(projectCwd);
|
|
@@ -778,6 +890,10 @@ export class MemoryEngine extends Service {
|
|
|
778
890
|
return stats;
|
|
779
891
|
}
|
|
780
892
|
async forget(id, projectCwd) {
|
|
893
|
+
return this.writeScope(projectCwd, () => this.forgetInner(id, projectCwd));
|
|
894
|
+
}
|
|
895
|
+
/** {@link forget} 的实现体;必须经 `writeScope` 进入。 */
|
|
896
|
+
async forgetInner(id, projectCwd) {
|
|
781
897
|
if (await this.requireTable('global').delete(id)) {
|
|
782
898
|
this.ctx.emit('memory/changed', { operation: 'forgotten', id });
|
|
783
899
|
return true;
|
|
@@ -790,6 +906,10 @@ export class MemoryEngine extends Service {
|
|
|
790
906
|
return false;
|
|
791
907
|
}
|
|
792
908
|
async setStatus(id, status, projectCwd) {
|
|
909
|
+
return this.writeScope(projectCwd, () => this.setStatusInner(id, status, projectCwd));
|
|
910
|
+
}
|
|
911
|
+
/** {@link setStatus} 的实现体;必须经 `writeScope` 进入。 */
|
|
912
|
+
async setStatusInner(id, status, projectCwd) {
|
|
793
913
|
const global = this.requireTable('global').get(id);
|
|
794
914
|
if (global !== undefined) {
|
|
795
915
|
const updated = {
|
|
@@ -830,6 +950,10 @@ export class MemoryEngine extends Service {
|
|
|
830
950
|
* 「常驻注入」开关调用(remote.setInjected)。
|
|
831
951
|
*/
|
|
832
952
|
async setInjected(id, injected, projectCwd) {
|
|
953
|
+
return this.writeScope(projectCwd, () => this.setInjectedInner(id, injected, projectCwd));
|
|
954
|
+
}
|
|
955
|
+
/** {@link setInjected} 的实现体;必须经 `writeScope` 进入。 */
|
|
956
|
+
async setInjectedInner(id, injected, projectCwd) {
|
|
833
957
|
const locate = async () => {
|
|
834
958
|
const global = this.requireTable('global').get(id);
|
|
835
959
|
if (global !== undefined)
|
|
@@ -872,6 +996,10 @@ export class MemoryEngine extends Service {
|
|
|
872
996
|
* `injected` 保留原值。若新内容命中危险规则则转为隔离(与写入路径同一判定)。
|
|
873
997
|
*/
|
|
874
998
|
async update(id, patch, projectCwd) {
|
|
999
|
+
return this.writeScope(projectCwd, () => this.updateInner(id, patch, projectCwd));
|
|
1000
|
+
}
|
|
1001
|
+
/** {@link update} 的实现体;必须经 `writeScope` 进入。 */
|
|
1002
|
+
async updateInner(id, patch, projectCwd) {
|
|
875
1003
|
const applyPatch = (block) => {
|
|
876
1004
|
const normalized = normalizeBlock(block);
|
|
877
1005
|
const content = patch.content ?? block.content;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@max-null/dsh-memory",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Cross-session plaintext memory plugin for the DeepSeek Harness — deterministic BM25 recall, writes take effect immediately, signal-driven injection, no embeddings",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
"client.js",
|
|
23
23
|
"cordis.patch.yml",
|
|
24
24
|
"README.md",
|
|
25
|
-
"LICENSE",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"docs/shots"
|
|
27
|
+
],
|
|
26
28
|
"dsh": {
|
|
27
29
|
"bundle": {
|
|
28
30
|
"patch": "./cordis.patch.yml"
|