@max-null/dsh-memory 0.2.2 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client.js +549 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +283 -0
- package/dist/engine.d.ts +59 -14
- package/dist/engine.js +217 -42
- package/dist/index.js +78 -26
- package/dist/remote.d.ts +21 -6
- package/dist/remote.js +39 -10
- package/dist/routes.d.ts +11 -0
- package/dist/routes.js +211 -0
- package/dist/self.d.ts +1 -0
- package/dist/self.js +26 -0
- package/package.json +16 -4
package/dist/engine.js
CHANGED
|
@@ -18,7 +18,8 @@ export function MemoryId(id) {
|
|
|
18
18
|
}
|
|
19
19
|
const blockSchema = z.object({
|
|
20
20
|
namespace: z.enum(['global', 'project']),
|
|
21
|
-
status: z.enum(['suggested', 'auto', 'suggest']),
|
|
21
|
+
status: z.enum(['suggested', 'approved', 'auto', 'suggest']),
|
|
22
|
+
injected: z.boolean().optional(),
|
|
22
23
|
content: z.string(),
|
|
23
24
|
keywords: z.array(z.string()),
|
|
24
25
|
createdAt: z.number(),
|
|
@@ -32,8 +33,30 @@ function memorySpec(name) {
|
|
|
32
33
|
tables: { blocks: domainTable(blockSchema) },
|
|
33
34
|
});
|
|
34
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* 旧 schema 迁移(0.3.0,设计文档「迁移规则」):
|
|
38
|
+
* - 旧 `auto` → approved + injected:true(行为不变:仍常驻注入)
|
|
39
|
+
* - 旧 `suggest` → suggested + injected:false
|
|
40
|
+
* - 缺 injected 的 approved → injected:false(新写路径兜底)
|
|
41
|
+
* - 其余 → suggested + injected:false
|
|
42
|
+
*/
|
|
43
|
+
function normalizeBlock(block) {
|
|
44
|
+
if (block.status === 'auto')
|
|
45
|
+
return { status: 'approved', injected: true };
|
|
46
|
+
if (block.status === 'suggest')
|
|
47
|
+
return { status: 'suggested', injected: false };
|
|
48
|
+
return { status: block.status, injected: block.injected ?? false };
|
|
49
|
+
}
|
|
35
50
|
function toRecord(id, block) {
|
|
36
|
-
return {
|
|
51
|
+
return {
|
|
52
|
+
id: MemoryId(id),
|
|
53
|
+
namespace: block.namespace,
|
|
54
|
+
...normalizeBlock(block),
|
|
55
|
+
content: block.content,
|
|
56
|
+
keywords: block.keywords,
|
|
57
|
+
createdAt: block.createdAt,
|
|
58
|
+
updatedAt: block.updatedAt,
|
|
59
|
+
};
|
|
37
60
|
}
|
|
38
61
|
/** Harness-home root for `global` memories. */
|
|
39
62
|
function globalRoot() {
|
|
@@ -45,13 +68,20 @@ function projectRoot() {
|
|
|
45
68
|
}
|
|
46
69
|
/**
|
|
47
70
|
* Cross-session plaintext memory over the storage hub, split by namespace:
|
|
48
|
-
* `global` lives in the harness home, `project` in the
|
|
71
|
+
* `global` lives in the harness home, `project` in the session's workspace
|
|
72
|
+
* folder (`<workspace>/.dsh/storages`, follows the repository) — 0.3.4:
|
|
73
|
+
* project memory routes by the CALLER's workspace cwd (工具/面板按当前会话
|
|
74
|
+
* 工作区路由), not the process launch dir.
|
|
49
75
|
*/
|
|
50
76
|
export class MemoryEngine extends Service {
|
|
51
77
|
config;
|
|
52
78
|
static inject = ['storage'];
|
|
53
79
|
globalTable;
|
|
54
|
-
|
|
80
|
+
/** project 域按工作区 cwd 懒打开 + 缓存(多会话并发各工作区独立)。 */
|
|
81
|
+
projectTables = new Map();
|
|
82
|
+
projectFacilities = new Map();
|
|
83
|
+
/** backend 只注册一次(registry 重名抛 duplicate;reload 清缓存后不得重复注册)。 */
|
|
84
|
+
registeredProjectBackends = new Set();
|
|
55
85
|
facility;
|
|
56
86
|
constructor(ctx, config = {}) {
|
|
57
87
|
super(ctx, 'memory');
|
|
@@ -61,42 +91,84 @@ export class MemoryEngine extends Service {
|
|
|
61
91
|
// backend 只注册一次:registry 对重名注册抛 duplicate-backend
|
|
62
92
|
// (storage/tests/registry.spec 实测),reload 不得重复注册。
|
|
63
93
|
const globalBackend = new JsonStorageBackend(this.config.globalRoot ?? globalRoot());
|
|
64
|
-
const projectBackend = new JsonStorageBackend(this.config.projectRoot ?? projectRoot());
|
|
65
94
|
this.ctx.storage.backend.register('memory-global', globalBackend);
|
|
66
|
-
this.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
});
|
|
95
|
+
await this.openGlobalFacility();
|
|
96
|
+
this.ctx.effect(() => () => {
|
|
97
|
+
void this.facility?.closeAll();
|
|
98
|
+
for (const facility of this.projectFacilities.values())
|
|
99
|
+
void facility.closeAll();
|
|
100
|
+
}, 'memory.domainsClose');
|
|
101
|
+
}
|
|
102
|
+
/** 打开全局存储域(init 与 reload 共用;backend 复用已注册实例)。 */
|
|
103
|
+
async openGlobalFacility() {
|
|
104
|
+
const facility = new DomainFacility(this.ctx, { backend: 'memory-global', routes: {} });
|
|
76
105
|
const globalDomain = await facility.open(memorySpec('memory'));
|
|
77
|
-
const projectDomain = await facility.open(memorySpec('memory_project'));
|
|
78
106
|
this.facility = facility;
|
|
79
107
|
this.globalTable = globalDomain.table('blocks');
|
|
80
|
-
|
|
108
|
+
}
|
|
109
|
+
/** 工作区 project 存储根(<workspace>/.dsh/storages,随 git 分享)。 */
|
|
110
|
+
projectRootFor(cwd) {
|
|
111
|
+
return join(cwd, '.dsh', 'storages');
|
|
112
|
+
}
|
|
113
|
+
/** 稳定 backend 名(registry 重名抛错,按 cwd hash 唯一化;只允许 [a-z0-9_],domain 名校验)。 */
|
|
114
|
+
projectBackendName(cwd) {
|
|
115
|
+
let h = 5381;
|
|
116
|
+
const text = cwd.toLowerCase(); // Windows 路径大小写不敏感
|
|
117
|
+
for (let i = 0; i < text.length; i++)
|
|
118
|
+
h = ((h << 5) + h + text.charCodeAt(i)) | 0;
|
|
119
|
+
return `memory_project_${Math.abs(h).toString(36)}`;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 按工作区 cwd 取 project 表(懒打开 + 缓存)。无 cwd(未选工作区)
|
|
123
|
+
* 返回 undefined——调用方(工具/面板)按此跳过 project 部分。
|
|
124
|
+
*/
|
|
125
|
+
async projectTableFor(projectCwd) {
|
|
126
|
+
if (projectCwd === undefined || projectCwd === '')
|
|
127
|
+
return undefined;
|
|
128
|
+
const key = join(projectCwd); // 规范化(Windows 大小写/尾斜杠)
|
|
129
|
+
const cached = this.projectTables.get(key);
|
|
130
|
+
if (cached !== undefined)
|
|
131
|
+
return cached;
|
|
132
|
+
const backendName = this.projectBackendName(key);
|
|
133
|
+
if (!this.registeredProjectBackends.has(backendName)) {
|
|
134
|
+
const backend = new JsonStorageBackend(this.projectRootFor(key));
|
|
135
|
+
this.ctx.storage.backend.register(backendName, backend);
|
|
136
|
+
this.registeredProjectBackends.add(backendName);
|
|
137
|
+
}
|
|
138
|
+
const facility = new DomainFacility(this.ctx, { backend: backendName, routes: {} });
|
|
139
|
+
const domain = await facility.open(memorySpec(`memory_project_${backendName}`));
|
|
140
|
+
const table = domain.table('blocks');
|
|
141
|
+
this.projectFacilities.set(key, facility);
|
|
142
|
+
this.projectTables.set(key, table);
|
|
143
|
+
return table;
|
|
81
144
|
}
|
|
82
145
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
146
|
+
* 强制重载存储:关闭全局域与全部已开工作区域后重开(unit 释放后
|
|
147
|
+
* backend 重读文件),放弃内存缓存。供外部编辑记忆文件后刷新
|
|
148
|
+
* (JsonStorageBackend 打开时加载一次,无 watch——2026-08-19 实测)。
|
|
86
149
|
*/
|
|
87
150
|
async reload() {
|
|
88
151
|
await this.facility?.closeAll();
|
|
89
|
-
|
|
152
|
+
for (const facility of this.projectFacilities.values())
|
|
153
|
+
await facility.closeAll();
|
|
154
|
+
this.projectTables.clear();
|
|
155
|
+
this.projectFacilities.clear();
|
|
156
|
+
await this.openGlobalFacility();
|
|
90
157
|
}
|
|
91
158
|
/** Create one record in `suggested` status — never self-promoting. */
|
|
92
|
-
async remember(input) {
|
|
159
|
+
async remember(input, projectCwd) {
|
|
93
160
|
const namespace = input.namespace ?? 'global';
|
|
94
|
-
const table =
|
|
161
|
+
const table = namespace === 'project'
|
|
162
|
+
? await this.projectTableFor(projectCwd)
|
|
163
|
+
: this.tableFor('global');
|
|
164
|
+
if (table === undefined)
|
|
165
|
+
throw new Error('cannot write project memory without a workspace cwd');
|
|
95
166
|
const id = randomUUID();
|
|
96
167
|
const now = Date.now();
|
|
97
168
|
const block = {
|
|
98
169
|
namespace,
|
|
99
170
|
status: 'suggested',
|
|
171
|
+
injected: false,
|
|
100
172
|
content: input.content,
|
|
101
173
|
keywords: (input.keywords ?? []).map(keyword => keyword.toLowerCase()),
|
|
102
174
|
createdAt: now,
|
|
@@ -107,12 +179,13 @@ export class MemoryEngine extends Service {
|
|
|
107
179
|
this.ctx.emit('memory/changed', { operation: 'remembered', record });
|
|
108
180
|
return record;
|
|
109
181
|
}
|
|
110
|
-
list(filter) {
|
|
111
|
-
const records = this.allRecords(filter?.namespace);
|
|
112
|
-
return records.filter(record => (filter?.status === undefined || record.status === filter.status)
|
|
182
|
+
async list(filter, projectCwd) {
|
|
183
|
+
const records = await this.allRecords(filter?.namespace, projectCwd);
|
|
184
|
+
return records.filter(record => (filter?.status === undefined || record.status === filter.status)
|
|
185
|
+
&& (filter?.injected === undefined || record.injected === filter.injected));
|
|
113
186
|
}
|
|
114
|
-
search(query, filter) {
|
|
115
|
-
const records = this.list(filter);
|
|
187
|
+
async search(query, filter, projectCwd) {
|
|
188
|
+
const records = await this.list(filter, projectCwd);
|
|
116
189
|
const docs = records.map(record => `${record.content} ${record.keywords.join(' ')}`);
|
|
117
190
|
const scores = bm25Scores(query, docs);
|
|
118
191
|
return records
|
|
@@ -120,44 +193,146 @@ export class MemoryEngine extends Service {
|
|
|
120
193
|
.filter(hit => hit.score > 0)
|
|
121
194
|
.sort((left, right) => right.score - left.score);
|
|
122
195
|
}
|
|
123
|
-
async forget(id) {
|
|
196
|
+
async forget(id, projectCwd) {
|
|
124
197
|
if (await this.requireTable('global').delete(id)) {
|
|
125
198
|
this.ctx.emit('memory/changed', { operation: 'forgotten', id });
|
|
126
199
|
return true;
|
|
127
200
|
}
|
|
128
|
-
|
|
201
|
+
const project = await this.projectTableFor(projectCwd);
|
|
202
|
+
if (project !== undefined && await project.delete(id)) {
|
|
129
203
|
this.ctx.emit('memory/changed', { operation: 'forgotten', id });
|
|
130
204
|
return true;
|
|
131
205
|
}
|
|
132
206
|
return false;
|
|
133
207
|
}
|
|
134
|
-
async setStatus(id, status) {
|
|
208
|
+
async setStatus(id, status, projectCwd) {
|
|
135
209
|
const global = this.requireTable('global').get(id);
|
|
136
210
|
if (global !== undefined) {
|
|
137
|
-
const updated = {
|
|
211
|
+
const updated = {
|
|
212
|
+
...global,
|
|
213
|
+
status,
|
|
214
|
+
// 旧数据可能缺 injected,写回时补全(读时迁移值)
|
|
215
|
+
injected: normalizeBlock(global).injected,
|
|
216
|
+
updatedAt: Date.now(),
|
|
217
|
+
};
|
|
138
218
|
await this.requireTable('global').put(id, updated);
|
|
139
219
|
const record = toRecord(id, updated);
|
|
140
220
|
this.ctx.emit('memory/changed', { operation: 'status', id, status });
|
|
141
221
|
return record;
|
|
142
222
|
}
|
|
143
|
-
const project = this.
|
|
223
|
+
const project = await this.projectTableFor(projectCwd);
|
|
224
|
+
if (project !== undefined) {
|
|
225
|
+
const block = project.get(id);
|
|
226
|
+
if (block !== undefined) {
|
|
227
|
+
const updated = {
|
|
228
|
+
...block,
|
|
229
|
+
status,
|
|
230
|
+
injected: normalizeBlock(block).injected,
|
|
231
|
+
updatedAt: Date.now(),
|
|
232
|
+
};
|
|
233
|
+
await project.put(id, updated);
|
|
234
|
+
const record = toRecord(id, updated);
|
|
235
|
+
this.ctx.emit('memory/changed', { operation: 'status', id, status });
|
|
236
|
+
return record;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
throw new Error(`cannot set status of unknown memory '${id}'`);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* 注入维度开关(0.3.0):只改 injected,不动审核状态。供 UI 面板
|
|
243
|
+
* 「常驻注入」开关调用(remote.setInjected)。
|
|
244
|
+
*/
|
|
245
|
+
async setInjected(id, injected, projectCwd) {
|
|
246
|
+
const global = this.requireTable('global').get(id);
|
|
247
|
+
if (global !== undefined) {
|
|
248
|
+
const updated = {
|
|
249
|
+
...global,
|
|
250
|
+
status: normalizeBlock(global).status,
|
|
251
|
+
injected,
|
|
252
|
+
updatedAt: Date.now(),
|
|
253
|
+
};
|
|
254
|
+
await this.requireTable('global').put(id, updated);
|
|
255
|
+
const record = toRecord(id, updated);
|
|
256
|
+
this.ctx.emit('memory/changed', { operation: 'injected', id, injected });
|
|
257
|
+
return record;
|
|
258
|
+
}
|
|
259
|
+
const project = await this.projectTableFor(projectCwd);
|
|
144
260
|
if (project !== undefined) {
|
|
145
|
-
const
|
|
146
|
-
|
|
261
|
+
const block = project.get(id);
|
|
262
|
+
if (block !== undefined) {
|
|
263
|
+
const updated = {
|
|
264
|
+
...block,
|
|
265
|
+
status: normalizeBlock(block).status,
|
|
266
|
+
injected,
|
|
267
|
+
updatedAt: Date.now(),
|
|
268
|
+
};
|
|
269
|
+
await project.put(id, updated);
|
|
270
|
+
const record = toRecord(id, updated);
|
|
271
|
+
this.ctx.emit('memory/changed', { operation: 'injected', id, injected });
|
|
272
|
+
return record;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
throw new Error(`cannot set injected of unknown memory '${id}'`);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* 修改一条记忆的内容/关键词(0.3.1 整理记忆用)。内容被模型改动后
|
|
279
|
+
* 必须重新人工审核:status 重置为 suggested(自然停止注入——注入仅对
|
|
280
|
+
* approved 生效);injected 保留原值(审核通过后注入开关原样恢复)。
|
|
281
|
+
*/
|
|
282
|
+
async update(id, patch, projectCwd) {
|
|
283
|
+
const applyPatch = (block) => {
|
|
284
|
+
const normalized = normalizeBlock(block);
|
|
285
|
+
return {
|
|
286
|
+
...block,
|
|
287
|
+
status: 'suggested',
|
|
288
|
+
injected: normalized.injected,
|
|
289
|
+
...(patch.content === undefined ? {} : { content: patch.content }),
|
|
290
|
+
...(patch.keywords === undefined ? {} : { keywords: patch.keywords.map(keyword => keyword.toLowerCase()) }),
|
|
291
|
+
updatedAt: Date.now(),
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
const global = this.requireTable('global').get(id);
|
|
295
|
+
if (global !== undefined) {
|
|
296
|
+
const updated = applyPatch(global);
|
|
297
|
+
await this.requireTable('global').put(id, updated);
|
|
147
298
|
const record = toRecord(id, updated);
|
|
148
|
-
this.ctx.emit('memory/changed', { operation: 'status', id, status });
|
|
299
|
+
this.ctx.emit('memory/changed', { operation: 'status', id, status: 'suggested' });
|
|
149
300
|
return record;
|
|
150
301
|
}
|
|
151
|
-
|
|
302
|
+
const project = await this.projectTableFor(projectCwd);
|
|
303
|
+
if (project !== undefined) {
|
|
304
|
+
const block = project.get(id);
|
|
305
|
+
if (block !== undefined) {
|
|
306
|
+
const updated = applyPatch(block);
|
|
307
|
+
await project.put(id, updated);
|
|
308
|
+
const record = toRecord(id, updated);
|
|
309
|
+
this.ctx.emit('memory/changed', { operation: 'status', id, status: 'suggested' });
|
|
310
|
+
return record;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
throw new Error(`cannot update unknown memory '${id}'`);
|
|
152
314
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
315
|
+
/**
|
|
316
|
+
* 注入专用(0.3.4):systemPrompt context 是同步回调、无会话 cwd 可及,
|
|
317
|
+
* 只返回 global 的 approved+injected(工作区记忆走检索/面板)。
|
|
318
|
+
*/
|
|
319
|
+
recallRecords() {
|
|
320
|
+
return this.recordsOf(this.requireTable('global'))
|
|
321
|
+
.filter(record => record.status === 'approved' && record.injected);
|
|
322
|
+
}
|
|
323
|
+
async allRecords(namespace, projectCwd) {
|
|
324
|
+
if (namespace === 'project') {
|
|
325
|
+
const project = await this.projectTableFor(projectCwd);
|
|
326
|
+
if (project === undefined)
|
|
327
|
+
return [];
|
|
328
|
+
return this.recordsOf(project);
|
|
329
|
+
}
|
|
156
330
|
if (namespace === 'global')
|
|
157
331
|
return this.recordsOf(this.requireTable('global'));
|
|
332
|
+
const project = await this.projectTableFor(projectCwd);
|
|
158
333
|
return [
|
|
159
334
|
...this.recordsOf(this.requireTable('global')),
|
|
160
|
-
...this.recordsOf(
|
|
335
|
+
...(project === undefined ? [] : this.recordsOf(project)),
|
|
161
336
|
];
|
|
162
337
|
}
|
|
163
338
|
recordsOf(table) {
|
|
@@ -167,7 +342,7 @@ export class MemoryEngine extends Service {
|
|
|
167
342
|
return this.requireTable(namespace);
|
|
168
343
|
}
|
|
169
344
|
requireTable(namespace) {
|
|
170
|
-
const table = namespace === 'global' ? this.globalTable :
|
|
345
|
+
const table = namespace === 'global' ? this.globalTable : undefined;
|
|
171
346
|
if (table === undefined)
|
|
172
347
|
throw new Error('memory engine is not started yet');
|
|
173
348
|
return table;
|
package/dist/index.js
CHANGED
|
@@ -7,18 +7,21 @@
|
|
|
7
7
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
8
8
|
import { MemoryEngine } from "./engine.js";
|
|
9
9
|
import { MemoryGateway } from "./remote.js";
|
|
10
|
+
import { mountMemoryApi } from "./routes.js";
|
|
11
|
+
import { SELF_DESCRIPTION } from "./self.js";
|
|
10
12
|
export { MemoryEngine } from "./engine.js";
|
|
11
13
|
export { MemoryId } from "./engine.js";
|
|
12
14
|
export { bm25Scores, tokenize } from "./bm25.js";
|
|
13
15
|
export const name = 'dsh-memory';
|
|
14
|
-
export const inject = ['storage', 'systemPrompt', 'tools'];
|
|
16
|
+
export const inject = ['storage', 'systemPrompt', 'tools', 'webServer', 'webRuntime'];
|
|
15
17
|
const RECORD_SCHEMA = {
|
|
16
18
|
type: 'object',
|
|
17
19
|
additionalProperties: false,
|
|
18
20
|
properties: {
|
|
19
21
|
id: { type: 'string', required: true },
|
|
20
22
|
namespace: { type: 'string', required: true, enum: ['global', 'project'] },
|
|
21
|
-
status: { type: 'string', required: true, enum: ['suggested', '
|
|
23
|
+
status: { type: 'string', required: true, enum: ['suggested', 'approved'] },
|
|
24
|
+
injected: { type: 'boolean', required: true },
|
|
22
25
|
content: { type: 'string', required: true },
|
|
23
26
|
keywords: { type: 'array', required: true, items: { type: 'string' } },
|
|
24
27
|
createdAt: { type: 'number', required: true },
|
|
@@ -38,6 +41,7 @@ function recordValue(record) {
|
|
|
38
41
|
id: String(record.id),
|
|
39
42
|
namespace: record.namespace,
|
|
40
43
|
status: record.status,
|
|
44
|
+
injected: record.injected,
|
|
41
45
|
content: record.content,
|
|
42
46
|
keywords: record.keywords,
|
|
43
47
|
createdAt: record.createdAt,
|
|
@@ -47,17 +51,34 @@ function recordValue(record) {
|
|
|
47
51
|
function hitValue(hit) {
|
|
48
52
|
return { record: recordValue(hit.record), score: hit.score };
|
|
49
53
|
}
|
|
54
|
+
// 0.3.0:审核语义双语(host 侧 GUIDANCE 无法跟随 DSH locale 动态切换,
|
|
55
|
+
// 采用中英双语都写、模型自取——设计文档「风险与注意」)。
|
|
50
56
|
const GUIDANCE = 'Use memory tools for cross-session preferences, habits, and project conventions. '
|
|
57
|
+
+ '记忆工具用于跨会话的偏好、习惯与项目约定。'
|
|
51
58
|
+ 'memory_save always records a suggestion (`suggested`) and never makes it effective itself — '
|
|
52
|
-
+ 'a human
|
|
53
|
-
+ '
|
|
59
|
+
+ 'a human approves it. memory_save 永远只写入建议(`suggested`),不会自行生效——需人工审核通过。'
|
|
60
|
+
+ 'Approval only marks the content reviewed; whether it is injected every turn is a separate '
|
|
61
|
+
+ 'human-controlled switch (`injected`). 审核通过只代表内容被认可;是否每轮常驻注入由独立的'
|
|
62
|
+
+ '人工开关(`injected`)控制。'
|
|
63
|
+
+ 'When earlier context may be relevant, call memory_search to recall it — reviewable memories '
|
|
64
|
+
+ '(`suggested`) are searchable too. 相关历史上下文可用 memory_search 检索——待审核的记忆也可检索。'
|
|
65
|
+
+ 'Every memory is plaintext and inspectable with memory_list; memory_forget removes one. '
|
|
66
|
+
+ '所有记忆均为明文,可用 memory_list 查看;memory_forget 删除一条。';
|
|
67
|
+
// 0.3.4:注入只注入 global 的 approved+injected(systemPrompt context 是
|
|
68
|
+
// 同步回调、无会话 cwd 可及);工作区(project)记忆按会话 cwd 路由,靠
|
|
69
|
+
// memory_search / 面板访问。
|
|
54
70
|
function recallText(memory) {
|
|
55
|
-
const auto = memory.
|
|
71
|
+
const auto = memory.recallRecords();
|
|
56
72
|
if (auto.length === 0)
|
|
57
73
|
return '';
|
|
58
74
|
const lines = auto.map(record => `- [memory:${String(record.id)}] ${record.content}`);
|
|
59
75
|
return `Remembered preferences and conventions — apply these:\n${lines.join('\n')}`;
|
|
60
76
|
}
|
|
77
|
+
/** 从工具执行上下文取调用会话的工作区 cwd(0.3.4 工作区路由)。 */
|
|
78
|
+
function execProjectCwd(exec) {
|
|
79
|
+
return exec
|
|
80
|
+
?.agent?.session?.header?.cwd;
|
|
81
|
+
}
|
|
61
82
|
function renderJson(value) {
|
|
62
83
|
return [{ type: 'text', text: JSON.stringify(value) }];
|
|
63
84
|
}
|
|
@@ -67,6 +88,8 @@ function present(title, kind, rawInput) {
|
|
|
67
88
|
export async function apply(ctx, config) {
|
|
68
89
|
await ctx.plugin(MemoryEngine, config);
|
|
69
90
|
await ctx.plugin(MemoryGateway);
|
|
91
|
+
// 0.3.6:自带面板数据通道(HTTP,独立包无需 Typert 构建产物)
|
|
92
|
+
mountMemoryApi(ctx);
|
|
70
93
|
const memory = ctx.get('memory');
|
|
71
94
|
if (memory === undefined)
|
|
72
95
|
throw new Error('memory engine failed to register');
|
|
@@ -75,6 +98,13 @@ export async function apply(ctx, config) {
|
|
|
75
98
|
order: 115,
|
|
76
99
|
text: GUIDANCE,
|
|
77
100
|
});
|
|
101
|
+
// 0.3.2:记忆机制自述——常驻注入(不落用户存储),随发版更新。
|
|
102
|
+
// order 49 < recall(50):机制说明在具体记忆内容之前。
|
|
103
|
+
ctx.systemPrompt.context({
|
|
104
|
+
name: 'memory:self',
|
|
105
|
+
order: 49,
|
|
106
|
+
text: SELF_DESCRIPTION,
|
|
107
|
+
});
|
|
78
108
|
ctx.systemPrompt.context({
|
|
79
109
|
name: 'memory:recall',
|
|
80
110
|
order: 50,
|
|
@@ -92,51 +122,53 @@ export async function apply(ctx, config) {
|
|
|
92
122
|
schema: RECORD_SCHEMA,
|
|
93
123
|
render: (_args, value) => renderJson(value),
|
|
94
124
|
},
|
|
95
|
-
execute(args,
|
|
125
|
+
execute(args, exec) {
|
|
96
126
|
return memory.remember({
|
|
97
127
|
content: args.content,
|
|
98
128
|
...args.namespace === undefined ? {} : { namespace: args.namespace },
|
|
99
129
|
...args.keywords === undefined ? {} : { keywords: args.keywords },
|
|
100
|
-
}).then(recordValue);
|
|
130
|
+
}, execProjectCwd(exec)).then(recordValue);
|
|
101
131
|
},
|
|
102
132
|
presentCall: args => present('Save memory', 'other', args.content),
|
|
103
133
|
}));
|
|
104
134
|
ctx.tools.register(defineTool({
|
|
105
135
|
name: 'memory_list',
|
|
106
|
-
description: 'List every stored memory, optionally filtered by namespace or
|
|
136
|
+
description: 'List every stored memory, optionally filtered by namespace, status, or injected switch. Every memory is plaintext and inspectable. 列出全部记忆,可按 namespace/status/injected 过滤;均为明文可查。',
|
|
107
137
|
parameters: {
|
|
108
|
-
namespace: { type: 'string', enum: ['global', 'project'], description: 'Restrict to one namespace.' },
|
|
109
|
-
status: { type: 'string', enum: ['suggested', '
|
|
138
|
+
namespace: { type: 'string', enum: ['global', 'project'], description: 'Restrict to one namespace. 限定单个命名空间.' },
|
|
139
|
+
status: { type: 'string', enum: ['suggested', 'approved'], description: 'Restrict to one review status. 限定审核状态(suggested=待审核 / approved=已审核).' },
|
|
140
|
+
injected: { type: 'boolean', description: 'Restrict by the persistent-injection switch. 按常驻注入开关过滤.' },
|
|
110
141
|
},
|
|
111
142
|
output: {
|
|
112
143
|
schema: { type: 'array', items: RECORD_SCHEMA },
|
|
113
144
|
render: (_args, value) => renderJson(value),
|
|
114
145
|
},
|
|
115
|
-
execute(args,
|
|
116
|
-
return
|
|
146
|
+
execute(args, exec) {
|
|
147
|
+
return memory.list({
|
|
117
148
|
...args.namespace === undefined ? {} : { namespace: args.namespace },
|
|
118
149
|
...args.status === undefined ? {} : { status: args.status },
|
|
119
|
-
|
|
150
|
+
...args.injected === undefined ? {} : { injected: args.injected },
|
|
151
|
+
}, execProjectCwd(exec)).then(records => records.map(recordValue));
|
|
120
152
|
},
|
|
121
153
|
presentCall: () => present('List memories', 'read'),
|
|
122
154
|
}));
|
|
123
155
|
ctx.tools.register(defineTool({
|
|
124
156
|
name: 'memory_search',
|
|
125
|
-
description: 'Recall stored memories by keyword. Deterministic literal matching — a miss means no stored term matched the query.',
|
|
157
|
+
description: 'Recall stored memories by keyword. Deterministic literal matching — a miss means no stored term matched the query. 按关键词检索记忆(含待审核条目)。',
|
|
126
158
|
parameters: {
|
|
127
|
-
query: { type: 'string', required: true, description: 'Keyword query.' },
|
|
128
|
-
namespace: { type: 'string', enum: ['global', 'project'], description: 'Restrict to one namespace.' },
|
|
129
|
-
status: { type: 'string', enum: ['suggested', '
|
|
159
|
+
query: { type: 'string', required: true, description: 'Keyword query. 关键词查询.' },
|
|
160
|
+
namespace: { type: 'string', enum: ['global', 'project'], description: 'Restrict to one namespace. 限定单个命名空间.' },
|
|
161
|
+
status: { type: 'string', enum: ['suggested', 'approved'], description: 'Restrict to one review status. 限定审核状态.' },
|
|
130
162
|
},
|
|
131
163
|
output: {
|
|
132
164
|
schema: { type: 'array', items: HIT_SCHEMA },
|
|
133
165
|
render: (_args, value) => renderJson(value),
|
|
134
166
|
},
|
|
135
|
-
execute(args,
|
|
136
|
-
return
|
|
167
|
+
execute(args, exec) {
|
|
168
|
+
return memory.search(args.query, {
|
|
137
169
|
...args.namespace === undefined ? {} : { namespace: args.namespace },
|
|
138
170
|
...args.status === undefined ? {} : { status: args.status },
|
|
139
|
-
}).map(hitValue));
|
|
171
|
+
}, execProjectCwd(exec)).then(hits => hits.map(hitValue));
|
|
140
172
|
},
|
|
141
173
|
presentCall: args => present('Search memory', 'read', args.query),
|
|
142
174
|
}));
|
|
@@ -150,23 +182,43 @@ export async function apply(ctx, config) {
|
|
|
150
182
|
schema: { type: 'object', additionalProperties: false, properties: { deleted: { type: 'boolean', required: true } } },
|
|
151
183
|
render: (_args, value) => renderJson(value),
|
|
152
184
|
},
|
|
153
|
-
execute(args,
|
|
154
|
-
return memory.forget(args.id).then(deleted => ({ deleted }));
|
|
185
|
+
execute(args, exec) {
|
|
186
|
+
return memory.forget(args.id, execProjectCwd(exec)).then(deleted => ({ deleted }));
|
|
155
187
|
},
|
|
156
188
|
presentCall: args => present('Forget memory', 'other', args.id),
|
|
157
189
|
}));
|
|
190
|
+
ctx.tools.register(defineTool({
|
|
191
|
+
name: 'memory_update',
|
|
192
|
+
description: 'Update the content or keywords of one stored memory (e.g. correcting stale facts). The record is re-marked `suggested` — the human must review it again before it is approved/injected again. 修改一条记忆的内容或关键词(如修正过时信息)。改动后该记忆重置为待审核(suggested),需人工再次审核;常驻注入开关保留原值(审核通过后恢复)。',
|
|
193
|
+
parameters: {
|
|
194
|
+
id: { type: 'string', required: true, description: 'Exact memory id from memory_list. 记忆 id(来自 memory_list).' },
|
|
195
|
+
content: { type: 'string', description: 'New content; omit to keep current. 新内容;省略则保留现有内容.' },
|
|
196
|
+
keywords: { type: 'array', items: { type: 'string' }, description: 'New keywords; omit to keep current. 新关键词;省略则保留现有.' },
|
|
197
|
+
},
|
|
198
|
+
output: {
|
|
199
|
+
schema: RECORD_SCHEMA,
|
|
200
|
+
render: (_args, value) => renderJson(value),
|
|
201
|
+
},
|
|
202
|
+
execute(args, exec) {
|
|
203
|
+
return memory.update(args.id, {
|
|
204
|
+
...args.content === undefined ? {} : { content: args.content },
|
|
205
|
+
...args.keywords === undefined ? {} : { keywords: args.keywords },
|
|
206
|
+
}, execProjectCwd(exec)).then(recordValue);
|
|
207
|
+
},
|
|
208
|
+
presentCall: args => present('Update memory', 'other', args.id),
|
|
209
|
+
}));
|
|
158
210
|
ctx.tools.register(defineTool({
|
|
159
211
|
name: 'memory_confirm',
|
|
160
|
-
description: '
|
|
212
|
+
description: 'Approve a suggested memory so it is marked human-reviewed (`approved`). Approval does NOT enable persistent injection — whether a memory is injected every turn is a separate human-controlled switch (`injected`). Only call this when the human explicitly asks to approve a memory; never self-promote a suggestion. 将待审核记忆标记为已审核(approved)。审核通过不改变注入状态——是否每轮常驻注入由独立的人工开关(injected)控制。仅在用户明确要求审核某条记忆时调用;模型不得自我提升。',
|
|
161
213
|
parameters: {
|
|
162
|
-
id: { type: 'string', required: true, description: 'Exact memory id from memory_list.' },
|
|
214
|
+
id: { type: 'string', required: true, description: 'Exact memory id from memory_list. 记忆 id(来自 memory_list).' },
|
|
163
215
|
},
|
|
164
216
|
output: {
|
|
165
217
|
schema: RECORD_SCHEMA,
|
|
166
218
|
render: (_args, value) => renderJson(value),
|
|
167
219
|
},
|
|
168
|
-
execute(args,
|
|
169
|
-
return memory.setStatus(args.id, '
|
|
220
|
+
execute(args, exec) {
|
|
221
|
+
return memory.setStatus(args.id, 'approved', execProjectCwd(exec)).then(recordValue);
|
|
170
222
|
},
|
|
171
223
|
presentCall: args => present('Confirm memory', 'other', args.id),
|
|
172
224
|
}));
|
package/dist/remote.d.ts
CHANGED
|
@@ -13,13 +13,28 @@ import type { MemoryFilter, MemoryHit, MemoryRecord } from './engine.ts';
|
|
|
13
13
|
export declare class MemoryGateway extends TypertRemoteService {
|
|
14
14
|
static inject: string[];
|
|
15
15
|
constructor(ctx: Context);
|
|
16
|
-
/** List every stored memory, optionally filtered by namespace
|
|
17
|
-
list(filter?: MemoryFilter): MemoryRecord[]
|
|
16
|
+
/** List every stored memory, optionally filtered by namespace/status/injected and workspace cwd. */
|
|
17
|
+
list(filter?: MemoryFilter, cwd?: string): Promise<MemoryRecord[]>;
|
|
18
18
|
/** Recall memories by keyword; deterministic literal matching. */
|
|
19
|
-
search(query: string, filter?: MemoryFilter): MemoryHit[]
|
|
20
|
-
/**
|
|
21
|
-
confirm(id: string): Promise<MemoryRecord>;
|
|
19
|
+
search(query: string, filter?: MemoryFilter, cwd?: string): Promise<MemoryHit[]>;
|
|
20
|
+
/** Approve a suggested memory (review status `suggested` → `approved`); does not change injection. */
|
|
21
|
+
confirm(id: string, cwd?: string): Promise<MemoryRecord>;
|
|
22
|
+
/**
|
|
23
|
+
* Toggle the persistent-injection switch (`injected`) — the UI panel's
|
|
24
|
+
* 「常驻注入」 switch; does not change the review status.
|
|
25
|
+
*/
|
|
26
|
+
setInjected(id: string, injected: boolean, cwd?: string): Promise<MemoryRecord>;
|
|
22
27
|
/** Delete one stored memory by id. */
|
|
23
|
-
forget(id: string): Promise<boolean>;
|
|
28
|
+
forget(id: string, cwd?: string): Promise<boolean>;
|
|
29
|
+
/** Force-reload storage files (external edits; JsonStorageBackend has no watch). */
|
|
30
|
+
reload(cwd?: string): Promise<MemoryRecord[]>;
|
|
31
|
+
/**
|
|
32
|
+
* 注入预览(0.3.5):开发者查看当前注入到 system prompt 的记忆内容
|
|
33
|
+
* ——self 自述 + 实际注入的 global approved+injected 记忆。
|
|
34
|
+
*/
|
|
35
|
+
injectionPreview(): {
|
|
36
|
+
self: string;
|
|
37
|
+
injected: MemoryRecord[];
|
|
38
|
+
};
|
|
24
39
|
}
|
|
25
40
|
export default MemoryGateway;
|