@max-null/dsh-memory 0.7.2 → 0.8.0
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 +4 -1
- package/dist/engine.d.ts +41 -9
- package/dist/engine.js +68 -15
- package/dist/index.js +33 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,15 +74,18 @@ md 文件是唯一事实源(`~/.dsh/prompt-library/*.md` 为 global;`<worksp
|
|
|
74
74
|
|
|
75
75
|
```
|
|
76
76
|
模型 memory_save → status: approved,立即生效;命中密钥/凭据规则则隔离(不进检索也不进注入)
|
|
77
|
+
给 injected 则同时钉住常驻(0.8.0),不吃下面两条自动规则
|
|
77
78
|
memory_search → 关键词/语义召回(只搜记忆;模板走 prompt_search 通道,不会混进候选池)
|
|
78
79
|
命中累计 2 次 → injected: true(自动打开常驻:global + 当前会话工作区,摘要化 + 预算截断)
|
|
79
|
-
30 天未再命中 →
|
|
80
|
+
30 天未再命中 → 自动撤下常驻(只撤自动开的;人工或模型显式动过的开关双向豁免)
|
|
80
81
|
人(面板 / 开关) → 钉住 / 删除 / 放行隔离记录 / 回滚
|
|
81
82
|
memory_forget → 随时删除(删除始终是人的动作)
|
|
82
83
|
```
|
|
83
84
|
|
|
84
85
|
两条自动规则撑起淘汰机制:**反复被检索命中**是它值得每轮付费的证据,**长期不再被命中**则自动退出常驻。两者都不删除任何内容——记忆不会无限累积,也不会被系统自行清空。
|
|
85
86
|
|
|
87
|
+
自动规则对**低频但关键**的记忆(长期约定、判据、委托)够不着:它们不会被反复检索,够不到阈值;勉强够到也会被 30 天撤下。这类走显式路径——在 `memory_save` / `memory_update` 里给 `injected` 即钉住,写的是与面板开关同一套语义(`injectedAuto: false`),此后不受两条自动规则影响。未审核与已隔离的记录不接受注入。
|
|
88
|
+
|
|
86
89
|
记忆还可声明**有效性锚点**(环境变量 / 工具清单 / 插件版本):所绑的值变化后,这条记忆被标记 `stale` 并撤下常驻,检索结果里也带出失效提示——环境变了,旧结论就不再被当成仍然正确。
|
|
87
90
|
|
|
88
91
|
模型写入不需要人点头,但**人始终能看见并推翻**:面板按「隔离区 / 常驻 / 冷数据」分组,被隔离的记录必须由人放行才会重新进入检索。
|
package/dist/engine.d.ts
CHANGED
|
@@ -94,6 +94,12 @@ export interface MemoryWrite {
|
|
|
94
94
|
* 失效。只用于「随环境变化的事实」(某工具版本下的行为、某环境变量决定的配置)。
|
|
95
95
|
*/
|
|
96
96
|
anchor?: MemoryAnchor;
|
|
97
|
+
/**
|
|
98
|
+
* 常驻注入(可选,2026-09-18):显式指定这条记忆是否每轮进上下文。**给值即接管**——
|
|
99
|
+
* 与 {@link MemoryEngine.setInjected} 同一套语义(写 `injectedAuto: false`,此后不受自动
|
|
100
|
+
* 升降影响)。省略则维持缺省行为:不注入,且**保留**「被反复命中即自动升常驻」的资格。
|
|
101
|
+
*/
|
|
102
|
+
injected?: boolean;
|
|
97
103
|
}
|
|
98
104
|
export interface MemoryFilter {
|
|
99
105
|
namespace?: MemoryNamespace;
|
|
@@ -104,6 +110,16 @@ export interface MemoryFilter {
|
|
|
104
110
|
/** 记录类别过滤(0.6.0);缺省不过滤(工具层显式传 kind='fact' 保持旧行为)。 */
|
|
105
111
|
kind?: MemoryKind;
|
|
106
112
|
}
|
|
113
|
+
/** {@link MemoryEngine.update} 的补丁:省略的字段保持原值(`injected` 省略时连 `injectedAuto` 一起保持)。 */
|
|
114
|
+
export interface MemoryPatch {
|
|
115
|
+
content?: string;
|
|
116
|
+
keywords?: string[];
|
|
117
|
+
/**
|
|
118
|
+
* 常驻注入(2026-09-18):给值即接管,与 {@link MemoryEngine.setInjected} 同义(写
|
|
119
|
+
* `injectedAuto: false`,此后不受自动升降影响);省略则保持原值。
|
|
120
|
+
*/
|
|
121
|
+
injected?: boolean;
|
|
122
|
+
}
|
|
107
123
|
export interface MemoryHit {
|
|
108
124
|
record: MemoryRecord;
|
|
109
125
|
score: number;
|
|
@@ -218,6 +234,8 @@ export declare class MemoryEngine extends Service {
|
|
|
218
234
|
private globalTable?;
|
|
219
235
|
/** project 域按工作区 cwd 懒打开 + 缓存(多会话并发各工作区独立)。 */
|
|
220
236
|
private projectTables;
|
|
237
|
+
/** 进行中的工作区打开(key → promise):并发调用复用同一次,见 {@link projectTableFor}。 */
|
|
238
|
+
private readonly projectOpenings;
|
|
221
239
|
private projectFacilities;
|
|
222
240
|
/** backend 只注册一次(registry 重名抛 duplicate;reload 清缓存后不得重复注册)。 */
|
|
223
241
|
private registeredProjectBackends;
|
|
@@ -236,11 +254,24 @@ export declare class MemoryEngine extends Service {
|
|
|
236
254
|
private projectRootFor;
|
|
237
255
|
/** 稳定 backend 名(registry 重名抛错,按 cwd hash 唯一化;只允许 [a-z0-9_],domain 名校验)。 */
|
|
238
256
|
private projectBackendName;
|
|
257
|
+
/**
|
|
258
|
+
* 工作区表是否已经打开(同步判断)。组装路径用它决定「要不要等一次预热」——
|
|
259
|
+
* 表已打开是常态,那时这条路径不该付出任何异步代价。
|
|
260
|
+
*/
|
|
261
|
+
projectOpen(projectCwd?: string): boolean;
|
|
239
262
|
/**
|
|
240
263
|
* 按工作区 cwd 取 project 表(懒打开 + 缓存)。无 cwd(未选工作区)
|
|
241
264
|
* 返回 undefined——调用方(工具/面板)按此跳过 project 部分。
|
|
265
|
+
*
|
|
266
|
+
* **打开是异步的,调用方却会并发**:预热是 fire-and-forget,注入路径、工具路径、
|
|
267
|
+
* 面板路径各来一次。底层 unit 句柄只允许一个,两次并发打开会让后到者以
|
|
268
|
+
* `unit '…' is already open; a unit has exactly one live handle` 失败——而调用方
|
|
269
|
+
* 普遍把预热失败当无碍吞掉,于是表现为「这一轮工作区记忆缺席」。所以这里复用
|
|
270
|
+
* 进行中的那次打开。
|
|
242
271
|
*/
|
|
243
272
|
private projectTableFor;
|
|
273
|
+
/** {@link projectTableFor} 的实际打开步骤;并发去重由调用方负责。 */
|
|
274
|
+
private openProjectTable;
|
|
244
275
|
/**
|
|
245
276
|
* 强制重载存储:关闭全局域与全部已开工作区域后重开(unit 释放后
|
|
246
277
|
* backend 重读文件),放弃内存缓存。供外部编辑记忆文件后刷新
|
|
@@ -411,21 +442,22 @@ export declare class MemoryEngine extends Service {
|
|
|
411
442
|
/** {@link setStatus} 的实现体;必须经 `writeScope` 进入。 */
|
|
412
443
|
private setStatusInner;
|
|
413
444
|
/**
|
|
414
|
-
* 注入维度开关(0.3.0):只改 injected,不动审核状态。供 UI
|
|
415
|
-
*
|
|
445
|
+
* 注入维度开关(0.3.0):只改 injected,不动审核状态。供 UI 面板「常驻注入」开关调用
|
|
446
|
+
* (remote.setInjected)。2026-09-18 起模型侧有等价路径——`memory_save` / `memory_update`
|
|
447
|
+
* 的 `injected` 参数,经 {@link remember} / {@link update} 写同一套 `injectedAuto: false`
|
|
448
|
+
* 语义。差异只在越界处理:本方法对隔离记录**抛错**(用户明确要开,该被告知),
|
|
449
|
+
* 而写入/更新路径**静默降级为不注入**(那是顺带设置,不该中断整次写入)。
|
|
416
450
|
*/
|
|
417
451
|
setInjected(id: MemoryId, injected: boolean, projectCwd?: string): Promise<MemoryRecord>;
|
|
418
452
|
/** {@link setInjected} 的实现体;必须经 `writeScope` 进入。 */
|
|
419
453
|
private setInjectedInner;
|
|
420
454
|
/**
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
* `injected`
|
|
455
|
+
* 修改一条记忆的内容/关键词/注入位(0.3.1 整理记忆用)。2026-09-15 起:改动**不再**
|
|
456
|
+
* 退回待审核——静默机制下记忆由系统与模型自行维护,更新即保持原有审核状态。2026-09-18
|
|
457
|
+
* 起可改 `injected`:**省略则保留原值**(连 `injectedAuto` 一起不动),给值即接管。若新
|
|
458
|
+
* 内容命中危险规则则转为隔离(与写入路径同一判定),并连带撤下注入。
|
|
424
459
|
*/
|
|
425
|
-
update(id: MemoryId, patch:
|
|
426
|
-
content?: string;
|
|
427
|
-
keywords?: string[];
|
|
428
|
-
}, projectCwd?: string): Promise<MemoryRecord>;
|
|
460
|
+
update(id: MemoryId, patch: MemoryPatch, projectCwd?: string): Promise<MemoryRecord>;
|
|
429
461
|
/** {@link update} 的实现体;必须经 `writeScope` 进入。 */
|
|
430
462
|
private updateInner;
|
|
431
463
|
/**
|
package/dist/engine.js
CHANGED
|
@@ -166,6 +166,8 @@ export class MemoryEngine extends Service {
|
|
|
166
166
|
globalTable;
|
|
167
167
|
/** project 域按工作区 cwd 懒打开 + 缓存(多会话并发各工作区独立)。 */
|
|
168
168
|
projectTables = new Map();
|
|
169
|
+
/** 进行中的工作区打开(key → promise):并发调用复用同一次,见 {@link projectTableFor}。 */
|
|
170
|
+
projectOpenings = new Map();
|
|
169
171
|
projectFacilities = new Map();
|
|
170
172
|
/** backend 只注册一次(registry 重名抛 duplicate;reload 清缓存后不得重复注册)。 */
|
|
171
173
|
registeredProjectBackends = new Set();
|
|
@@ -211,9 +213,24 @@ export class MemoryEngine extends Service {
|
|
|
211
213
|
h = ((h << 5) + h + text.charCodeAt(i)) | 0;
|
|
212
214
|
return `memory_project_${Math.abs(h).toString(36)}`;
|
|
213
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* 工作区表是否已经打开(同步判断)。组装路径用它决定「要不要等一次预热」——
|
|
218
|
+
* 表已打开是常态,那时这条路径不该付出任何异步代价。
|
|
219
|
+
*/
|
|
220
|
+
projectOpen(projectCwd) {
|
|
221
|
+
if (projectCwd === undefined || projectCwd === '')
|
|
222
|
+
return false;
|
|
223
|
+
return this.projectTables.has(join(projectCwd));
|
|
224
|
+
}
|
|
214
225
|
/**
|
|
215
226
|
* 按工作区 cwd 取 project 表(懒打开 + 缓存)。无 cwd(未选工作区)
|
|
216
227
|
* 返回 undefined——调用方(工具/面板)按此跳过 project 部分。
|
|
228
|
+
*
|
|
229
|
+
* **打开是异步的,调用方却会并发**:预热是 fire-and-forget,注入路径、工具路径、
|
|
230
|
+
* 面板路径各来一次。底层 unit 句柄只允许一个,两次并发打开会让后到者以
|
|
231
|
+
* `unit '…' is already open; a unit has exactly one live handle` 失败——而调用方
|
|
232
|
+
* 普遍把预热失败当无碍吞掉,于是表现为「这一轮工作区记忆缺席」。所以这里复用
|
|
233
|
+
* 进行中的那次打开。
|
|
217
234
|
*/
|
|
218
235
|
async projectTableFor(projectCwd) {
|
|
219
236
|
if (projectCwd === undefined || projectCwd === '')
|
|
@@ -222,6 +239,20 @@ export class MemoryEngine extends Service {
|
|
|
222
239
|
const cached = this.projectTables.get(key);
|
|
223
240
|
if (cached !== undefined)
|
|
224
241
|
return cached;
|
|
242
|
+
const inFlight = this.projectOpenings.get(key);
|
|
243
|
+
if (inFlight !== undefined)
|
|
244
|
+
return await inFlight;
|
|
245
|
+
const opening = this.openProjectTable(key);
|
|
246
|
+
this.projectOpenings.set(key, opening);
|
|
247
|
+
try {
|
|
248
|
+
return await opening;
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
this.projectOpenings.delete(key);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/** {@link projectTableFor} 的实际打开步骤;并发去重由调用方负责。 */
|
|
255
|
+
async openProjectTable(key) {
|
|
225
256
|
const backendName = this.projectBackendName(key);
|
|
226
257
|
// 0.5.2:旧实现 domain 名多一层 memory_project_ 前缀(文件名双重前缀,
|
|
227
258
|
// 且 unit 文件头 name 同为旧名)。打开前统一迁移到规范名:
|
|
@@ -271,6 +302,9 @@ export class MemoryEngine extends Service {
|
|
|
271
302
|
* 「放弃内存缓存」这条语义不变,改变的只是「重读完别忘了装回去」。
|
|
272
303
|
*/
|
|
273
304
|
async reload() {
|
|
305
|
+
// 进行中的打开先落地:否则它们会在下面的 clear 之后把表塞回已清空的映射,
|
|
306
|
+
// 留下「表在映射里但 facility 已关」的半死状态。
|
|
307
|
+
await Promise.allSettled([...this.projectOpenings.values()]);
|
|
274
308
|
const opened = [...this.projectTables.keys()];
|
|
275
309
|
await this.facility?.closeAll();
|
|
276
310
|
for (const facility of this.projectFacilities.values())
|
|
@@ -383,13 +417,16 @@ export class MemoryEngine extends Service {
|
|
|
383
417
|
const block = {
|
|
384
418
|
namespace,
|
|
385
419
|
status: verdict.quarantined ? 'suggested' : 'approved',
|
|
386
|
-
|
|
420
|
+
// 常驻注入(2026-09-18):显式给值即接管;隔离内容一律不注入
|
|
421
|
+
injected: verdict.quarantined ? false : input.injected ?? false,
|
|
387
422
|
content: input.content,
|
|
388
423
|
keywords: (input.keywords ?? []).map(keyword => keyword.toLowerCase()),
|
|
389
424
|
createdAt: now,
|
|
390
425
|
updatedAt: now,
|
|
391
426
|
// ⑧ 来源(2026-09-15):缺省 'agent'——写入这条记忆的就是主模型
|
|
392
427
|
source: input.source ?? 'agent',
|
|
428
|
+
// 显式指定注入即接管:此后既不被自动降级,也不被自动升级(同 setInjected,2026-09-18)
|
|
429
|
+
...input.injected === undefined ? {} : { injectedAuto: false },
|
|
393
430
|
// 有效性锚点(可选):值变了这条记忆会被标 stale,不再被当成仍然正确
|
|
394
431
|
...input.anchor === undefined ? {} : { anchor: input.anchor },
|
|
395
432
|
...verdict.quarantined
|
|
@@ -946,8 +983,11 @@ export class MemoryEngine extends Service {
|
|
|
946
983
|
throw new Error(`cannot set status of unknown memory '${id}'`);
|
|
947
984
|
}
|
|
948
985
|
/**
|
|
949
|
-
* 注入维度开关(0.3.0):只改 injected,不动审核状态。供 UI
|
|
950
|
-
*
|
|
986
|
+
* 注入维度开关(0.3.0):只改 injected,不动审核状态。供 UI 面板「常驻注入」开关调用
|
|
987
|
+
* (remote.setInjected)。2026-09-18 起模型侧有等价路径——`memory_save` / `memory_update`
|
|
988
|
+
* 的 `injected` 参数,经 {@link remember} / {@link update} 写同一套 `injectedAuto: false`
|
|
989
|
+
* 语义。差异只在越界处理:本方法对隔离记录**抛错**(用户明确要开,该被告知),
|
|
990
|
+
* 而写入/更新路径**静默降级为不注入**(那是顺带设置,不该中断整次写入)。
|
|
951
991
|
*/
|
|
952
992
|
async setInjected(id, injected, projectCwd) {
|
|
953
993
|
return this.writeScope(projectCwd, () => this.setInjectedInner(id, injected, projectCwd));
|
|
@@ -991,9 +1031,10 @@ export class MemoryEngine extends Service {
|
|
|
991
1031
|
return record;
|
|
992
1032
|
}
|
|
993
1033
|
/**
|
|
994
|
-
*
|
|
995
|
-
*
|
|
996
|
-
* `injected`
|
|
1034
|
+
* 修改一条记忆的内容/关键词/注入位(0.3.1 整理记忆用)。2026-09-15 起:改动**不再**
|
|
1035
|
+
* 退回待审核——静默机制下记忆由系统与模型自行维护,更新即保持原有审核状态。2026-09-18
|
|
1036
|
+
* 起可改 `injected`:**省略则保留原值**(连 `injectedAuto` 一起不动),给值即接管。若新
|
|
1037
|
+
* 内容命中危险规则则转为隔离(与写入路径同一判定),并连带撤下注入。
|
|
997
1038
|
*/
|
|
998
1039
|
async update(id, patch, projectCwd) {
|
|
999
1040
|
return this.writeScope(projectCwd, () => this.updateInner(id, patch, projectCwd));
|
|
@@ -1007,11 +1048,16 @@ export class MemoryEngine extends Service {
|
|
|
1007
1048
|
? block.keywords
|
|
1008
1049
|
: patch.keywords.map(keyword => keyword.toLowerCase());
|
|
1009
1050
|
const verdict = detectSensitive(content, ...keywords);
|
|
1051
|
+
// 命中危险规则:退回 suggested(不注入)+ 隔离(不检索)双保险;否则保持原状态
|
|
1052
|
+
const status = verdict.quarantined ? 'suggested' : normalized.status;
|
|
1053
|
+
// 常驻注入(2026-09-18):显式给值即接管;未审核或已隔离一律不注入(与自动升级同一门槛)
|
|
1054
|
+
const injected = status === 'approved' ? patch.injected ?? normalized.injected : false;
|
|
1010
1055
|
return {
|
|
1011
1056
|
...block,
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1057
|
+
status,
|
|
1058
|
+
injected,
|
|
1059
|
+
// 显式改注入即视为有意决定:此后不受自动升降影响(省略时连 injectedAuto 一起保持)
|
|
1060
|
+
...patch.injected === undefined ? {} : { injectedAuto: false },
|
|
1015
1061
|
...verdict.quarantined
|
|
1016
1062
|
? { quarantined: true, ...verdict.reason === undefined ? {} : { quarantineReason: verdict.reason } }
|
|
1017
1063
|
: { quarantined: false, quarantineReason: undefined },
|
|
@@ -1020,13 +1066,22 @@ export class MemoryEngine extends Service {
|
|
|
1020
1066
|
updatedAt: Date.now(),
|
|
1021
1067
|
};
|
|
1022
1068
|
};
|
|
1069
|
+
// 注入位变化单独发一条事件:客户端与面板据此刷新「常驻」列,只发 status 会漏掉它
|
|
1070
|
+
const publish = (previous, updated) => {
|
|
1071
|
+
const record = toRecord(id, updated);
|
|
1072
|
+
this.ctx.emit('memory/changed', { operation: 'status', id, status: normalizeBlock(updated).status });
|
|
1073
|
+
const wasInjected = normalizeBlock(previous).injected;
|
|
1074
|
+
const isInjected = normalizeBlock(updated).injected;
|
|
1075
|
+
if (isInjected !== wasInjected) {
|
|
1076
|
+
this.ctx.emit('memory/changed', { operation: 'injected', id, injected: isInjected });
|
|
1077
|
+
}
|
|
1078
|
+
return record;
|
|
1079
|
+
};
|
|
1023
1080
|
const global = this.requireTable('global').get(id);
|
|
1024
1081
|
if (global !== undefined) {
|
|
1025
1082
|
const updated = applyPatch(global);
|
|
1026
1083
|
await this.requireTable('global').put(id, updated);
|
|
1027
|
-
|
|
1028
|
-
this.ctx.emit('memory/changed', { operation: 'status', id, status: normalizeBlock(updated).status });
|
|
1029
|
-
return record;
|
|
1084
|
+
return publish(global, updated);
|
|
1030
1085
|
}
|
|
1031
1086
|
const project = await this.projectTableFor(projectCwd);
|
|
1032
1087
|
if (project !== undefined) {
|
|
@@ -1034,9 +1089,7 @@ export class MemoryEngine extends Service {
|
|
|
1034
1089
|
if (block !== undefined) {
|
|
1035
1090
|
const updated = applyPatch(block);
|
|
1036
1091
|
await project.put(id, updated);
|
|
1037
|
-
|
|
1038
|
-
this.ctx.emit('memory/changed', { operation: 'status', id, status: normalizeBlock(updated).status });
|
|
1039
|
-
return record;
|
|
1092
|
+
return publish(block, updated);
|
|
1040
1093
|
}
|
|
1041
1094
|
}
|
|
1042
1095
|
throw new Error(`cannot update unknown memory '${id}'`);
|
package/dist/index.js
CHANGED
|
@@ -159,6 +159,8 @@ export async function apply(ctx, config) {
|
|
|
159
159
|
order: 49,
|
|
160
160
|
text: SELF_DESCRIPTION,
|
|
161
161
|
});
|
|
162
|
+
const injectionBudget = config?.injectionBudget ?? DEFAULT_INJECTION_BUDGET;
|
|
163
|
+
const summaryChars = config?.summaryChars ?? DEFAULT_SUMMARY_CHARS;
|
|
162
164
|
ctx.systemPrompt.context({
|
|
163
165
|
name: 'memory:recall',
|
|
164
166
|
order: 50,
|
|
@@ -166,11 +168,34 @@ export async function apply(ctx, config) {
|
|
|
166
168
|
const cwd = assemblyProjectCwd(assembly);
|
|
167
169
|
if (cwd !== undefined)
|
|
168
170
|
void memory.ensureProjectOpen(cwd).catch(() => { });
|
|
169
|
-
|
|
170
|
-
const summaryChars = config?.summaryChars ?? DEFAULT_SUMMARY_CHARS;
|
|
171
|
-
return recallText(memory, cwd, budget, summaryChars);
|
|
171
|
+
return recallText(memory, cwd, injectionBudget, summaryChars);
|
|
172
172
|
},
|
|
173
173
|
});
|
|
174
|
+
// 上面那条 provider 是**同步**的(`SystemPrompt.context` 的 `text` 类型就是
|
|
175
|
+
// `string | ((context) => string)`),而工作区表是异步打开的:重启后首次组装时
|
|
176
|
+
// 它还没打开,工作区记忆会整整缺席一轮(等预热完成,第二轮才回来)。
|
|
177
|
+
// `system-prompt/assemble` 是异步 waterfall、文档写明「返回值权威」,在这里补一次。
|
|
178
|
+
//
|
|
179
|
+
// **不能拿「此刻表是否打开」当作「provider 渲染时表是否打开」的判据**:provider 里那次
|
|
180
|
+
// 预热的 Promise 可能在 `next()` 的 await 期间就完成了,于是这里看到「已打开」而跳过,
|
|
181
|
+
// 而 provider 渲染时它还是空的。所以这里总是重渲染一遍——`renderInjection` 是纯函数、
|
|
182
|
+
// 读的是内存表,代价可忽略;文本没变就原样返回,不动 downstream 看到的对象。
|
|
183
|
+
ctx.on('system-prompt/assemble', async (assembly, context, next) => {
|
|
184
|
+
const assembled = await next();
|
|
185
|
+
const cwd = assemblyProjectCwd(context);
|
|
186
|
+
if (cwd === undefined || cwd === '')
|
|
187
|
+
return assembled;
|
|
188
|
+
if (!memory.projectOpen(cwd))
|
|
189
|
+
await memory.ensureProjectOpen(cwd).catch(() => undefined);
|
|
190
|
+
const text = recallText(memory, cwd, injectionBudget, summaryChars);
|
|
191
|
+
const current = assembled.contexts.find(entry => entry.name === 'memory:recall');
|
|
192
|
+
if (current === undefined || current.text === text)
|
|
193
|
+
return assembled;
|
|
194
|
+
return {
|
|
195
|
+
...assembled,
|
|
196
|
+
contexts: assembled.contexts.map(entry => entry.name === 'memory:recall' ? { ...entry, text } : entry),
|
|
197
|
+
};
|
|
198
|
+
});
|
|
174
199
|
// 会话创建即预热对应工作区的 project 表(0.5.1):让首轮模型请求就
|
|
175
200
|
// 能注入工作区常驻记忆(provider 同步只读已打开的表)。
|
|
176
201
|
// session/created 事件由 dsh-session 提供(可选依赖:无 session 服务
|
|
@@ -188,6 +213,7 @@ export async function apply(ctx, config) {
|
|
|
188
213
|
content: { type: 'string', required: true, description: 'Plaintext memory content.' },
|
|
189
214
|
namespace: { type: 'string', enum: ['global', 'project'], description: 'Where it applies; defaults to global.' },
|
|
190
215
|
keywords: { type: 'array', items: { type: 'string' }, description: 'Explicit searchable anchors for memory_search. Give several angles (synonyms, abbreviations, zh/en) or it will not be found later. 多角度关键词(同义词、缩写、中英),否则将来检索不到它。' },
|
|
216
|
+
injected: { type: 'boolean', description: 'Set true to make this memory resident in every turn\'s context, as a deliberate decision that no longer auto-expires. Reserve it for rules, standing agreements and judgement criteria — the kind that must apply even when nobody thinks to search for them. Facts, references and case notes stay search-only (omit it). 设为 true 让这条记忆每轮常驻上下文,且视为有意决定、不再被自动淘汰。只用于规则、长期约定与判据——那些「没人想起来搜也必须生效」的记忆;事实、参考与案例留作按需检索(省略即可)。' },
|
|
191
217
|
anchor: {
|
|
192
218
|
type: 'object',
|
|
193
219
|
additionalProperties: false,
|
|
@@ -208,6 +234,7 @@ export async function apply(ctx, config) {
|
|
|
208
234
|
content: args.content,
|
|
209
235
|
...args.namespace === undefined ? {} : { namespace: args.namespace },
|
|
210
236
|
...args.keywords === undefined ? {} : { keywords: args.keywords },
|
|
237
|
+
...args.injected === undefined ? {} : { injected: args.injected },
|
|
211
238
|
// 参数已由 tool schema 校验,这里的窄化只为过类型
|
|
212
239
|
...args.anchor === undefined
|
|
213
240
|
? {}
|
|
@@ -406,11 +433,12 @@ export async function apply(ctx, config) {
|
|
|
406
433
|
}));
|
|
407
434
|
ctx.tools.register(defineTool({
|
|
408
435
|
name: 'memory_update',
|
|
409
|
-
description: 'Update the content or
|
|
436
|
+
description: 'Update the content, keywords or persistent-injection switch of one stored memory (e.g. correcting stale facts). The record keeps its review status; content that looks like a credential quarantines it instead. 修改一条记忆的内容、关键词或常驻注入开关(如修正过时信息)。改动后记忆保持原有审核状态;若新内容命中危险规则则转为隔离。',
|
|
410
437
|
parameters: {
|
|
411
438
|
id: { type: 'string', required: true, description: 'Exact memory id from memory_list. 记忆 id(来自 memory_list).' },
|
|
412
439
|
content: { type: 'string', description: 'New content; omit to keep current. 新内容;省略则保留现有内容.' },
|
|
413
440
|
keywords: { type: 'array', items: { type: 'string' }, description: 'New keywords; omit to keep current. 新关键词;省略则保留现有.' },
|
|
441
|
+
injected: { type: 'boolean', description: 'Set or clear the persistent-injection switch; omit to keep the current value. Setting it records a deliberate decision — the memory stops being auto-promoted or auto-demoted. See memory_save for what deserves residency. 设置或清除常驻注入开关;省略则保持原值。给值即视为有意决定——此后不受自动升降影响。何时该常驻见 memory_save。' },
|
|
414
442
|
},
|
|
415
443
|
output: {
|
|
416
444
|
schema: RECORD_SCHEMA,
|
|
@@ -420,6 +448,7 @@ export async function apply(ctx, config) {
|
|
|
420
448
|
return memory.update(args.id, {
|
|
421
449
|
...args.content === undefined ? {} : { content: args.content },
|
|
422
450
|
...args.keywords === undefined ? {} : { keywords: args.keywords },
|
|
451
|
+
...args.injected === undefined ? {} : { injected: args.injected },
|
|
423
452
|
}, execProjectCwd(exec)).then(recordValue);
|
|
424
453
|
},
|
|
425
454
|
presentCall: args => present('Update memory', 'other', args.id),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@max-null/dsh-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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": {
|