@downcity/plugins 1.0.252 → 1.0.254

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.
Files changed (67) hide show
  1. package/bin/memory/Action.d.ts +27 -36
  2. package/bin/memory/Action.d.ts.map +1 -1
  3. package/bin/memory/Action.js +71 -311
  4. package/bin/memory/Action.js.map +1 -1
  5. package/bin/memory/Index.d.ts +3 -1
  6. package/bin/memory/Index.d.ts.map +1 -1
  7. package/bin/memory/Index.js +3 -1
  8. package/bin/memory/Index.js.map +1 -1
  9. package/bin/memory/MemoryPlugin.d.ts +20 -37
  10. package/bin/memory/MemoryPlugin.d.ts.map +1 -1
  11. package/bin/memory/MemoryPlugin.js +231 -233
  12. package/bin/memory/MemoryPlugin.js.map +1 -1
  13. package/bin/memory/adapters/FileMemoryStorageAdapter.d.ts +43 -0
  14. package/bin/memory/adapters/FileMemoryStorageAdapter.d.ts.map +1 -0
  15. package/bin/memory/adapters/FileMemoryStorageAdapter.js +163 -0
  16. package/bin/memory/adapters/FileMemoryStorageAdapter.js.map +1 -0
  17. package/bin/memory/providers/BuiltinMemoryProvider.d.ts +63 -0
  18. package/bin/memory/providers/BuiltinMemoryProvider.d.ts.map +1 -0
  19. package/bin/memory/providers/BuiltinMemoryProvider.js +772 -0
  20. package/bin/memory/providers/BuiltinMemoryProvider.js.map +1 -0
  21. package/bin/memory/runtime/SystemProvider.d.ts +7 -12
  22. package/bin/memory/runtime/SystemProvider.d.ts.map +1 -1
  23. package/bin/memory/runtime/SystemProvider.js +30 -89
  24. package/bin/memory/runtime/SystemProvider.js.map +1 -1
  25. package/bin/memory/types/BuiltinMemoryProvider.d.ts +88 -0
  26. package/bin/memory/types/BuiltinMemoryProvider.d.ts.map +1 -0
  27. package/bin/memory/types/BuiltinMemoryProvider.js +9 -0
  28. package/bin/memory/types/BuiltinMemoryProvider.js.map +1 -0
  29. package/bin/memory/types/Memory.d.ts +249 -408
  30. package/bin/memory/types/Memory.d.ts.map +1 -1
  31. package/bin/memory/types/Memory.js +4 -4
  32. package/bin/memory/types/MemoryStorage.d.ts +35 -0
  33. package/bin/memory/types/MemoryStorage.d.ts.map +1 -0
  34. package/bin/memory/types/MemoryStorage.js +10 -0
  35. package/bin/memory/types/MemoryStorage.js.map +1 -0
  36. package/bin/memory.d.ts +6 -2
  37. package/bin/memory.d.ts.map +1 -1
  38. package/bin/memory.js +3 -1
  39. package/bin/memory.js.map +1 -1
  40. package/package.json +4 -3
  41. package/scripts/memory-plugin.test.mjs +227 -0
  42. package/scripts/plugin-subpaths.test.mjs +8 -0
  43. package/src/memory/Action.ts +106 -377
  44. package/src/memory/Index.ts +6 -1
  45. package/src/memory/MemoryPlugin.ts +254 -246
  46. package/src/memory/adapters/FileMemoryStorageAdapter.ts +197 -0
  47. package/src/memory/providers/BuiltinMemoryProvider.ts +905 -0
  48. package/src/memory/runtime/SystemProvider.ts +32 -95
  49. package/src/memory/types/BuiltinMemoryProvider.ts +121 -0
  50. package/src/memory/types/Memory.ts +322 -456
  51. package/src/memory/types/MemoryStorage.ts +44 -0
  52. package/src/memory.ts +48 -24
  53. package/bin/memory/runtime/Search.d.ts +0 -19
  54. package/bin/memory/runtime/Search.d.ts.map +0 -1
  55. package/bin/memory/runtime/Search.js +0 -262
  56. package/bin/memory/runtime/Search.js.map +0 -1
  57. package/bin/memory/runtime/Store.d.ts +0 -42
  58. package/bin/memory/runtime/Store.d.ts.map +0 -1
  59. package/bin/memory/runtime/Store.js +0 -94
  60. package/bin/memory/runtime/Store.js.map +0 -1
  61. package/bin/memory/runtime/Writer.d.ts +0 -61
  62. package/bin/memory/runtime/Writer.d.ts.map +0 -1
  63. package/bin/memory/runtime/Writer.js +0 -274
  64. package/bin/memory/runtime/Writer.js.map +0 -1
  65. package/src/memory/runtime/Search.ts +0 -327
  66. package/src/memory/runtime/Store.ts +0 -158
  67. package/src/memory/runtime/Writer.ts +0 -351
@@ -0,0 +1,772 @@
1
+ /**
2
+ * BuiltinMemoryProvider:Downcity 默认的本地长期记忆实现。
3
+ *
4
+ * 关键点(中文)
5
+ * - Provider 负责 Memory 领域语义,底层 Storage Adapter 只负责文本持久化。
6
+ * - memory_id、citation 与 scope 均为逻辑协议,不暴露 Adapter 的物理位置。
7
+ * - 当前召回使用确定性文本扫描;以后可在 Provider 内替换索引而不改变 Plugin API。
8
+ */
9
+ import { randomUUID } from "node:crypto";
10
+ const DEFAULT_MAX_RESULTS = 6;
11
+ const DEFAULT_MIN_SCORE = 0.35;
12
+ const DEFAULT_MAX_CONTEXT_CHARS = 4_000;
13
+ const SNIPPET_MAX_CHARS = 700;
14
+ const CHUNK_MAX_CHARS = 1_600;
15
+ const CHUNK_OVERLAP_CHARS = 240;
16
+ const INDEX_MEMORY_ID = "wiki/index";
17
+ /** 限制数值到给定闭区间。 */
18
+ function clamp_number(value, minimum, maximum) {
19
+ if (!Number.isFinite(value))
20
+ return minimum;
21
+ return Math.max(minimum, Math.min(maximum, value));
22
+ }
23
+ /** 生成用于文件实现内部组织的稳定 slug。 */
24
+ function slugify(value) {
25
+ const text = String(value || "")
26
+ .trim()
27
+ .toLowerCase()
28
+ .replace(/[^\p{L}\p{N}]+/gu, "-")
29
+ .replace(/^-+|-+$/g, "")
30
+ .slice(0, 80);
31
+ return text || "inbox";
32
+ }
33
+ /** 规范化 Provider 公开 memory_id。 */
34
+ function normalize_memory_id(input) {
35
+ const memory_id = String(input || "")
36
+ .replace(/\\/g, "/")
37
+ .replace(/^memory:\/\/builtin\//, "")
38
+ .replace(/^\/+/, "")
39
+ .replace(/\.md$/i, "")
40
+ .trim();
41
+ if (!memory_id)
42
+ throw new Error("memory_id is required");
43
+ const segments = memory_id.split("/");
44
+ if (segments.some((segment) => !/^[a-z0-9][a-z0-9_-]*$/u.test(segment))) {
45
+ throw new Error(`Invalid memory_id: ${input}`);
46
+ }
47
+ if (segments[0] !== "wiki" && segments[0] !== "evidence") {
48
+ throw new Error(`Unsupported Builtin memory_id: ${input}`);
49
+ }
50
+ return segments.join("/");
51
+ }
52
+ /** 把公开 memory_id 映射为 Storage Adapter 内部 key。 */
53
+ function memory_id_to_key(memory_id) {
54
+ return `${normalize_memory_id(memory_id)}.md`;
55
+ }
56
+ /** 把 Storage Adapter 内部 key 映射为公开 memory_id。 */
57
+ function key_to_memory_id(key) {
58
+ return normalize_memory_id(String(key || "").replace(/\.md$/i, ""));
59
+ }
60
+ /** 创建 Provider 逻辑 citation。 */
61
+ function create_citation(memory_id, start_line, end_line) {
62
+ const base = `memory://builtin/${normalize_memory_id(memory_id)}`;
63
+ if (!start_line)
64
+ return base;
65
+ return end_line && end_line !== start_line
66
+ ? `${base}#L${start_line}-L${end_line}`
67
+ : `${base}#L${start_line}`;
68
+ }
69
+ /** 去除 Markdown frontmatter,并保留内容行语义。 */
70
+ function strip_frontmatter(content) {
71
+ const normalized = String(content || "").replace(/\r\n/g, "\n");
72
+ if (!normalized.startsWith("---\n"))
73
+ return normalized.trim();
74
+ const end_index = normalized.indexOf("\n---\n", 4);
75
+ return end_index < 0 ? normalized.trim() : normalized.slice(end_index + 5).trim();
76
+ }
77
+ /** 从简化 frontmatter 中读取单个 JSON 字段。 */
78
+ function read_frontmatter_json(content, key) {
79
+ const normalized = String(content || "").replace(/\r\n/g, "\n");
80
+ if (!normalized.startsWith("---\n"))
81
+ return undefined;
82
+ const end_index = normalized.indexOf("\n---\n", 4);
83
+ if (end_index < 0)
84
+ return undefined;
85
+ const prefix = `${key}:`;
86
+ const line = normalized.slice(4, end_index)
87
+ .split("\n")
88
+ .find((item) => item.startsWith(prefix));
89
+ if (!line)
90
+ return undefined;
91
+ const raw_value = line.slice(prefix.length).trim();
92
+ try {
93
+ return JSON.parse(raw_value);
94
+ }
95
+ catch {
96
+ return raw_value;
97
+ }
98
+ }
99
+ /** 从 Provider Markdown 读取稳定元数据。 */
100
+ function parse_metadata(content, fallback_type) {
101
+ const raw_type = read_frontmatter_json(content, "memory_type");
102
+ const allowed_types = new Set([
103
+ "fact",
104
+ "preference",
105
+ "decision",
106
+ "episode",
107
+ "procedure",
108
+ "document",
109
+ ]);
110
+ const memory_type = typeof raw_type === "string" && allowed_types.has(raw_type)
111
+ ? raw_type
112
+ : fallback_type;
113
+ const raw_observed_at = read_frontmatter_json(content, "observed_at");
114
+ const raw_source_refs = read_frontmatter_json(content, "source_refs");
115
+ const source_refs = Array.isArray(raw_source_refs)
116
+ ? raw_source_refs.flatMap((value) => {
117
+ if (!value || typeof value !== "object" || Array.isArray(value))
118
+ return [];
119
+ const record = value;
120
+ const source_id = String(record.source_id || "").trim();
121
+ const source_type = String(record.source_type || "").trim();
122
+ if (!source_id || !source_type)
123
+ return [];
124
+ const label = String(record.label || "").trim();
125
+ return [{
126
+ source_id,
127
+ source_type,
128
+ ...(label ? { label } : {}),
129
+ }];
130
+ })
131
+ : [];
132
+ const title = read_frontmatter_json(content, "title");
133
+ return {
134
+ ...(typeof title === "string" && title.trim() ? { title: title.trim() } : {}),
135
+ memory_type,
136
+ observed_at: typeof raw_observed_at === "string" && raw_observed_at.trim()
137
+ ? raw_observed_at.trim()
138
+ : new Date(0).toISOString(),
139
+ source_refs,
140
+ };
141
+ }
142
+ /** 生成 Builtin Provider 使用的 Markdown 记录。 */
143
+ function create_markdown_record(input) {
144
+ const tags = input.tags?.map((tag) => String(tag || "").trim()).filter(Boolean) ?? [];
145
+ return [
146
+ "---",
147
+ `title: ${JSON.stringify(input.title)}`,
148
+ `memory_type: ${JSON.stringify(input.memory_type)}`,
149
+ `observed_at: ${JSON.stringify(input.observed_at || new Date().toISOString())}`,
150
+ `source_refs: ${JSON.stringify(input.source_refs || [])}`,
151
+ `tags: ${JSON.stringify(tags)}`,
152
+ "---",
153
+ "",
154
+ String(input.content || "").trim(),
155
+ "",
156
+ ].join("\n");
157
+ }
158
+ /** 把 Storage 条目转换为领域记录。 */
159
+ function storage_entry_to_record(entry, scope) {
160
+ const memory_id = key_to_memory_id(entry.key);
161
+ const is_evidence = memory_id.startsWith("evidence/");
162
+ const metadata = parse_metadata(entry.content, is_evidence ? "episode" : "document");
163
+ return {
164
+ memory_id,
165
+ memory_type: metadata.memory_type,
166
+ scope: { agent_id: scope.agent_id },
167
+ content: strip_frontmatter(entry.content),
168
+ observed_at: metadata.observed_at,
169
+ source_refs: metadata.source_refs,
170
+ citation: create_citation(memory_id),
171
+ ...(metadata.title ? { metadata: { title: metadata.title } } : {}),
172
+ };
173
+ }
174
+ /** 把查询文本拆成有界 token。 */
175
+ function tokenize_query(raw) {
176
+ return String(raw || "")
177
+ .toLowerCase()
178
+ .replace(/[^\p{L}\p{N}_-]+/gu, " ")
179
+ .split(/\s+/)
180
+ .map((item) => item.trim())
181
+ .filter(Boolean)
182
+ .slice(0, 16);
183
+ }
184
+ /** 计算片段的确定性覆盖率和密度分数。 */
185
+ function score_chunk(text, tokens) {
186
+ if (tokens.length === 0)
187
+ return 0;
188
+ const normalized = String(text || "").toLowerCase();
189
+ let matched_tokens = 0;
190
+ let total_hits = 0;
191
+ for (const token of tokens) {
192
+ let hits = 0;
193
+ let start_index = 0;
194
+ while (start_index < normalized.length) {
195
+ const found_index = normalized.indexOf(token, start_index);
196
+ if (found_index < 0)
197
+ break;
198
+ hits += 1;
199
+ start_index = found_index + token.length;
200
+ }
201
+ if (hits > 0) {
202
+ matched_tokens += 1;
203
+ total_hits += Math.min(hits, 4);
204
+ }
205
+ }
206
+ if (matched_tokens === 0)
207
+ return 0;
208
+ const coverage = matched_tokens / tokens.length;
209
+ const density = Math.min(total_hits, tokens.length * 3) / (tokens.length * 3);
210
+ return Number((coverage * 0.75 + density * 0.25).toFixed(4));
211
+ }
212
+ /** 把完整记录切分成带行号的有界片段。 */
213
+ function chunk_memory(memory) {
214
+ const lines = memory.content.replace(/\r\n/g, "\n").split("\n");
215
+ const chunks = [];
216
+ let bucket = [];
217
+ let character_count = 0;
218
+ const flush = () => {
219
+ const text = bucket.map((item) => item.line).join("\n").trim();
220
+ if (!text || bucket.length === 0)
221
+ return;
222
+ chunks.push({
223
+ memory,
224
+ start_line: bucket[0]?.line_number ?? 1,
225
+ end_line: bucket[bucket.length - 1]?.line_number ?? 1,
226
+ text,
227
+ });
228
+ };
229
+ const carry_overlap = () => {
230
+ let size = 0;
231
+ const next = [];
232
+ for (let index = bucket.length - 1; index >= 0; index -= 1) {
233
+ const row = bucket[index];
234
+ if (!row)
235
+ continue;
236
+ size += row.line.length + 1;
237
+ next.unshift(row);
238
+ if (size >= CHUNK_OVERLAP_CHARS)
239
+ break;
240
+ }
241
+ bucket = next;
242
+ character_count = size;
243
+ };
244
+ for (let index = 0; index < lines.length; index += 1) {
245
+ const line = lines[index] || "";
246
+ const row_size = line.length + 1;
247
+ if (bucket.length > 0 && character_count + row_size > CHUNK_MAX_CHARS) {
248
+ flush();
249
+ carry_overlap();
250
+ }
251
+ bucket.push({ line, line_number: index + 1 });
252
+ character_count += row_size;
253
+ }
254
+ flush();
255
+ return chunks;
256
+ }
257
+ /** 读取 handler 的 digest 输出。 */
258
+ function normalize_digest_output(output) {
259
+ return typeof output === "string"
260
+ ? {
261
+ projections: [{
262
+ memory_id: "wiki/session-digests",
263
+ title: "Session Digests",
264
+ content: output,
265
+ tags: ["memory", "digest"],
266
+ }],
267
+ }
268
+ : output;
269
+ }
270
+ /** 读取 handler 的 revise 输出。 */
271
+ function normalize_revise_output(output, fallback_memory_id) {
272
+ return typeof output === "string"
273
+ ? { memory_id: fallback_memory_id, content: output }
274
+ : output;
275
+ }
276
+ /** Downcity 默认的 provider-neutral Memory 实现。 */
277
+ export class BuiltinMemoryProvider {
278
+ /** 当前 Provider 稳定名称。 */
279
+ name = "builtin";
280
+ /** 当前 Provider 支持的完整能力。 */
281
+ capabilities = Object.freeze({
282
+ remember: true,
283
+ recall: true,
284
+ read: true,
285
+ revise: true,
286
+ forget: true,
287
+ digest: true,
288
+ system_context: true,
289
+ });
290
+ /** 当前 Provider 已创建的唯一低层存储 Adapter。 */
291
+ storage;
292
+ /** 当前 Provider 可选使用的延迟 Storage Adapter 工厂。 */
293
+ create_storage;
294
+ /** 当前 Provider 可选使用的 Session 提炼处理器。 */
295
+ digest_handler;
296
+ /** 当前 Provider 可选使用的内容修订处理器。 */
297
+ revise_handler;
298
+ /** 当前 Provider 已初始化的 Agent 运行身份。 */
299
+ runtime;
300
+ constructor(options) {
301
+ const has_storage = Boolean(options?.storage);
302
+ const has_factory = typeof options?.create_storage === "function";
303
+ if (has_storage === has_factory) {
304
+ throw new Error("BuiltinMemoryProvider requires exactly one storage or create_storage");
305
+ }
306
+ this.storage = options.storage;
307
+ this.create_storage = options.create_storage;
308
+ this.digest_handler = options.digest;
309
+ this.revise_handler = options.revise;
310
+ }
311
+ /** 初始化 Adapter 和默认索引投影。 */
312
+ async initialize(input) {
313
+ const agent_id = String(input.agent_id || "").trim();
314
+ if (!agent_id)
315
+ throw new Error("BuiltinMemoryProvider requires agent_id");
316
+ if (this.runtime && this.runtime.agent_id !== agent_id) {
317
+ throw new Error("BuiltinMemoryProvider is already bound to another Agent");
318
+ }
319
+ const created_storage = !this.storage;
320
+ const storage = this.storage ?? await this.create_storage?.({ agent_id });
321
+ if (!storage)
322
+ throw new Error("BuiltinMemoryProvider storage factory returned no Adapter");
323
+ this.storage = storage;
324
+ try {
325
+ await storage.initialize();
326
+ if (!await storage.has(memory_id_to_key(INDEX_MEMORY_ID))) {
327
+ await this.write_projection({
328
+ memory_id: INDEX_MEMORY_ID,
329
+ title: "Memory Index",
330
+ content: "Long-term memories are available through MemoryPlugin recall and read actions.",
331
+ tags: ["memory", "index"],
332
+ }, [], "document");
333
+ }
334
+ }
335
+ catch (error) {
336
+ if (created_storage) {
337
+ await storage.dispose().catch(() => undefined);
338
+ this.storage = undefined;
339
+ }
340
+ throw error;
341
+ }
342
+ this.runtime = { agent_id };
343
+ }
344
+ /** 返回 Provider 状态与可重建统计。 */
345
+ async status() {
346
+ this.require_runtime();
347
+ const [wiki_entries, evidence_entries] = await Promise.all([
348
+ this.active_storage.list("wiki"),
349
+ this.active_storage.list("evidence"),
350
+ ]);
351
+ const scope = this.create_runtime_scope();
352
+ const chunk_count = [...wiki_entries, ...evidence_entries]
353
+ .map((entry) => storage_entry_to_record(entry, scope))
354
+ .reduce((count, memory) => count + chunk_memory(memory).length, 0);
355
+ return {
356
+ provider: this.name,
357
+ state: "ready",
358
+ capabilities: this.capabilities,
359
+ details: {
360
+ storage_adapter: this.active_storage.name,
361
+ memories: wiki_entries.length,
362
+ evidence: evidence_entries.length,
363
+ chunks: chunk_count,
364
+ },
365
+ };
366
+ }
367
+ /** 使用确定性扫描召回记忆,底层存储形态对调用方不可见。 */
368
+ async recall(input) {
369
+ this.assert_scope(input.scope);
370
+ const query = String(input.query || "").trim();
371
+ if (!query)
372
+ return { provider: this.name, items: [] };
373
+ const tokens = tokenize_query(query);
374
+ if (tokens.length === 0)
375
+ return { provider: this.name, items: [] };
376
+ const entries = [
377
+ ...await this.active_storage.list("wiki"),
378
+ ...(input.include_evidence ? await this.active_storage.list("evidence") : []),
379
+ ];
380
+ const max_results = Math.floor(clamp_number(Number(input.max_results ?? DEFAULT_MAX_RESULTS), 1, 20));
381
+ const min_score = clamp_number(Number(input.min_score ?? DEFAULT_MIN_SCORE), 0, 1);
382
+ const items = entries
383
+ .map((entry) => storage_entry_to_record(entry, this.create_runtime_scope()))
384
+ .flatMap((memory) => chunk_memory(memory))
385
+ .map((chunk) => {
386
+ const score = score_chunk(chunk.text, tokens);
387
+ const citation = create_citation(chunk.memory.memory_id, chunk.start_line, chunk.end_line);
388
+ return {
389
+ memory: { ...chunk.memory, citation },
390
+ score,
391
+ snippet: chunk.text.length <= SNIPPET_MAX_CHARS
392
+ ? chunk.text
393
+ : chunk.text.slice(0, SNIPPET_MAX_CHARS),
394
+ };
395
+ })
396
+ .filter((item) => item.score >= min_score)
397
+ .sort((left, right) => {
398
+ if (right.score !== left.score)
399
+ return right.score - left.score;
400
+ return left.memory.memory_id.localeCompare(right.memory.memory_id);
401
+ })
402
+ .slice(0, max_results);
403
+ return { provider: this.name, items };
404
+ }
405
+ /** 按 memory_id 精确读取并应用可选行预算。 */
406
+ async read(input) {
407
+ this.assert_scope(input.scope);
408
+ const memory_id = normalize_memory_id(input.memory_id);
409
+ const content = await this.active_storage.read(memory_id_to_key(memory_id));
410
+ if (content === null)
411
+ return { memory_id, memory: null };
412
+ const base = storage_entry_to_record({
413
+ key: memory_id_to_key(memory_id),
414
+ content,
415
+ }, this.create_runtime_scope());
416
+ const from_line = input.from_line
417
+ ? Math.max(1, Math.floor(input.from_line))
418
+ : undefined;
419
+ const line_count = input.line_count
420
+ ? Math.max(1, Math.floor(input.line_count))
421
+ : undefined;
422
+ if (!from_line && !line_count)
423
+ return { memory_id, memory: base };
424
+ const lines = base.content.split("\n");
425
+ const start = from_line ?? 1;
426
+ const count = line_count ?? lines.length;
427
+ const end = Math.min(lines.length, start + count - 1);
428
+ return {
429
+ memory_id,
430
+ memory: {
431
+ ...base,
432
+ content: lines.slice(start - 1, end).join("\n"),
433
+ citation: create_citation(memory_id, start, end),
434
+ },
435
+ };
436
+ }
437
+ /** 保存原始证据并形成或更新长期记忆。 */
438
+ async remember(input) {
439
+ this.assert_scope(input.scope);
440
+ const content = String(input.content || "").trim();
441
+ if (!content)
442
+ throw new Error("Memory remember requires content");
443
+ const evidence_id = `evidence/manual/${new Date().toISOString().slice(0, 10)}/${randomUUID()}`;
444
+ await this.write_evidence(evidence_id, content, input.scope, input.source || "manual");
445
+ const memory_id = `wiki/${slugify(input.topic || "inbox")}`;
446
+ const existing = await this.active_storage.read(memory_id_to_key(memory_id));
447
+ const source_refs = [{
448
+ source_id: evidence_id,
449
+ source_type: "manual",
450
+ ...(input.source ? { label: input.source } : {}),
451
+ }];
452
+ if (this.revise_handler) {
453
+ const revised = normalize_revise_output(await this.revise_handler({
454
+ memory_id,
455
+ current_content: existing ? strip_frontmatter(existing) : "",
456
+ instruction: "Integrate the new evidence, deduplicate it, and keep the memory concise.",
457
+ evidence: content,
458
+ }), memory_id);
459
+ const target_memory_id = normalize_memory_id(revised.memory_id || memory_id);
460
+ await this.write_projection({
461
+ memory_id: target_memory_id,
462
+ title: input.topic || "Memory Inbox",
463
+ content: revised.content,
464
+ }, source_refs, input.memory_type || "fact");
465
+ return {
466
+ memory_id: target_memory_id,
467
+ evidence_id,
468
+ mode: existing ? "updated" : "created",
469
+ ...(revised.summary ? { summary: revised.summary } : {}),
470
+ };
471
+ }
472
+ await this.append_projection({
473
+ memory_id,
474
+ title: input.topic || "Memory Inbox",
475
+ content,
476
+ source_refs,
477
+ memory_type: input.memory_type || "fact",
478
+ });
479
+ return {
480
+ memory_id,
481
+ evidence_id,
482
+ mode: existing ? "updated" : "created",
483
+ };
484
+ }
485
+ /** 保存 Session 证据,并通过可选 handler 形成长期投影。 */
486
+ async digest(input) {
487
+ this.assert_scope(input.scope);
488
+ const session_id = String(input.session_id || "").trim();
489
+ if (!session_id)
490
+ throw new Error("Memory digest requires session_id");
491
+ const transcript = String(input.transcript || "").trim();
492
+ if (!transcript)
493
+ throw new Error("Memory digest requires transcript content");
494
+ const evidence_id = `evidence/session/${slugify(session_id)}/${randomUUID()}`;
495
+ await this.write_evidence(evidence_id, transcript, input.scope, `session:${session_id}`);
496
+ const source_refs = [{
497
+ source_id: evidence_id,
498
+ source_type: "session",
499
+ label: session_id,
500
+ }];
501
+ if (this.digest_handler) {
502
+ const index_content = await this.active_storage.read(memory_id_to_key(INDEX_MEMORY_ID));
503
+ const output = normalize_digest_output(await this.digest_handler({
504
+ source_text: transcript,
505
+ source_id: evidence_id,
506
+ session_id,
507
+ current_index: index_content ? strip_frontmatter(index_content) : "",
508
+ }));
509
+ const memory_ids = [];
510
+ for (const projection of output.projections) {
511
+ const memory_id = await this.write_projection(projection, source_refs, "episode");
512
+ memory_ids.push(memory_id);
513
+ }
514
+ return {
515
+ memory_ids,
516
+ evidence_id,
517
+ message_count: input.message_count,
518
+ mode: "projected",
519
+ ...(output.summary ? { summary: output.summary } : {}),
520
+ };
521
+ }
522
+ const memory_id = "wiki/session-digests";
523
+ await this.append_projection({
524
+ memory_id,
525
+ title: "Session Digests",
526
+ content: transcript,
527
+ source_refs,
528
+ memory_type: "episode",
529
+ });
530
+ return {
531
+ memory_ids: [memory_id],
532
+ evidence_id,
533
+ message_count: input.message_count,
534
+ mode: "archived",
535
+ };
536
+ }
537
+ /** 修订既有记忆,并在无 handler 时使用可审计追加语义。 */
538
+ async revise(input) {
539
+ this.assert_scope(input.scope);
540
+ const memory_id = normalize_memory_id(input.memory_id);
541
+ const instruction = String(input.instruction || "").trim();
542
+ if (!instruction)
543
+ throw new Error("Memory revise requires instruction");
544
+ const evidence = String(input.evidence || "").trim();
545
+ const existing = await this.active_storage.read(memory_id_to_key(memory_id));
546
+ if (existing === null)
547
+ throw new Error(`Memory not found: ${memory_id}`);
548
+ const metadata = parse_metadata(existing, "document");
549
+ const evidence_id = evidence
550
+ ? `evidence/manual/${new Date().toISOString().slice(0, 10)}/${randomUUID()}`
551
+ : undefined;
552
+ const source_refs = [...metadata.source_refs];
553
+ if (evidence_id) {
554
+ await this.write_evidence(evidence_id, evidence, input.scope, `revision:${memory_id}`);
555
+ source_refs.push({
556
+ source_id: evidence_id,
557
+ source_type: "manual",
558
+ label: `revision:${memory_id}`,
559
+ });
560
+ }
561
+ if (this.revise_handler) {
562
+ const revised = normalize_revise_output(await this.revise_handler({
563
+ memory_id,
564
+ current_content: strip_frontmatter(existing),
565
+ instruction,
566
+ evidence,
567
+ }), memory_id);
568
+ const target_memory_id = normalize_memory_id(revised.memory_id || memory_id);
569
+ await this.write_projection({
570
+ memory_id: target_memory_id,
571
+ title: String(metadata.title || target_memory_id),
572
+ content: revised.content,
573
+ }, source_refs, metadata.memory_type);
574
+ return {
575
+ memory_id: target_memory_id,
576
+ ...(evidence_id ? { evidence_id } : {}),
577
+ mode: "revised",
578
+ ...(revised.summary ? { summary: revised.summary } : {}),
579
+ };
580
+ }
581
+ const addition = [
582
+ `## ${new Date().toISOString()}`,
583
+ "",
584
+ `Instruction: ${instruction}`,
585
+ "",
586
+ evidence || "(no evidence)",
587
+ "",
588
+ ].join("\n");
589
+ await this.active_storage.write(memory_id_to_key(memory_id), create_markdown_record({
590
+ title: metadata.title || memory_id,
591
+ content: `${strip_frontmatter(existing)}\n\n${addition}`,
592
+ memory_type: metadata.memory_type,
593
+ source_refs,
594
+ tags: ["memory"],
595
+ observed_at: metadata.observed_at,
596
+ }));
597
+ return {
598
+ memory_id,
599
+ ...(evidence_id ? { evidence_id } : {}),
600
+ mode: "appended",
601
+ };
602
+ }
603
+ /** 删除当前 Provider 中的指定记忆。 */
604
+ async forget(input) {
605
+ this.assert_scope(input.scope);
606
+ const memory_id = normalize_memory_id(input.memory_id);
607
+ const key = memory_id_to_key(memory_id);
608
+ const forgotten = await this.active_storage.has(key);
609
+ await this.active_storage.delete(key);
610
+ return { memory_id, forgotten };
611
+ }
612
+ /** 从稳定候选投影中生成有界 system context。 */
613
+ async system_context(input) {
614
+ this.assert_scope(input.scope);
615
+ const max_items = Math.max(0, Math.floor(input.max_items));
616
+ const max_chars = Math.max(0, Math.floor(input.max_chars || DEFAULT_MAX_CONTEXT_CHARS));
617
+ if (max_items === 0 || max_chars === 0)
618
+ return { items: [] };
619
+ const candidates = [
620
+ "wiki/user-preferences",
621
+ "wiki/project-overview",
622
+ "wiki/rules",
623
+ INDEX_MEMORY_ID,
624
+ ];
625
+ const items = [];
626
+ let remaining_chars = max_chars;
627
+ for (const memory_id of candidates) {
628
+ const content = await this.active_storage.read(memory_id_to_key(memory_id));
629
+ if (!content)
630
+ continue;
631
+ const stable_lines = strip_frontmatter(content)
632
+ .split("\n")
633
+ .map((line) => line.trim().replace(/^[-*]\s+/, ""))
634
+ .filter((line) => line && !line.startsWith("#"))
635
+ .slice(0, 3)
636
+ .join("\n");
637
+ if (!stable_lines)
638
+ continue;
639
+ const bounded_content = stable_lines.slice(0, remaining_chars);
640
+ if (!bounded_content)
641
+ break;
642
+ items.push({
643
+ memory_id,
644
+ content: bounded_content,
645
+ citation: create_citation(memory_id),
646
+ });
647
+ remaining_chars -= bounded_content.length;
648
+ if (items.length >= max_items || remaining_chars <= 0)
649
+ break;
650
+ }
651
+ return { items };
652
+ }
653
+ /** 释放底层 Adapter 并关闭当前绑定。 */
654
+ async dispose() {
655
+ try {
656
+ await this.storage?.dispose();
657
+ }
658
+ finally {
659
+ if (this.create_storage)
660
+ this.storage = undefined;
661
+ this.runtime = undefined;
662
+ }
663
+ }
664
+ /** 返回当前已创建的唯一 Storage Adapter。 */
665
+ get active_storage() {
666
+ if (!this.storage)
667
+ throw new Error("BuiltinMemoryProvider storage is not initialized");
668
+ return this.storage;
669
+ }
670
+ /** 创建当前 Runtime 的最小 Agent scope。 */
671
+ create_runtime_scope() {
672
+ const runtime = this.require_runtime();
673
+ return { agent_id: runtime.agent_id };
674
+ }
675
+ /** 校验调用作用域属于当前已初始化 Agent。 */
676
+ assert_scope(scope) {
677
+ const runtime = this.require_runtime();
678
+ if (String(scope.agent_id || "").trim() !== runtime.agent_id) {
679
+ throw new Error("Memory scope agent_id does not match initialized Provider");
680
+ }
681
+ }
682
+ /** 返回已初始化 Runtime,否则拒绝隐式回退。 */
683
+ require_runtime() {
684
+ if (!this.runtime)
685
+ throw new Error("BuiltinMemoryProvider is not initialized");
686
+ return this.runtime;
687
+ }
688
+ /** 保存一条 Provider 内部证据记录。 */
689
+ async write_evidence(evidence_id, content, scope, label) {
690
+ const normalized_id = normalize_memory_id(evidence_id);
691
+ await this.active_storage.write(memory_id_to_key(normalized_id), create_markdown_record({
692
+ title: label,
693
+ content,
694
+ memory_type: "episode",
695
+ source_refs: [{
696
+ source_id: normalized_id,
697
+ source_type: label.startsWith("session:") ? "session" : "manual",
698
+ label,
699
+ }],
700
+ tags: ["memory", "evidence", scope.agent_id],
701
+ }));
702
+ }
703
+ /** 创建或替换一条长期记忆投影。 */
704
+ async write_projection(projection, source_refs, memory_type) {
705
+ const memory_id = normalize_memory_id(projection.memory_id || `wiki/${slugify(projection.title || "inbox")}`);
706
+ if (!memory_id.startsWith("wiki/")) {
707
+ throw new Error(`Builtin projection must use wiki memory_id: ${memory_id}`);
708
+ }
709
+ const content = String(projection.content || "").trim();
710
+ if (!content)
711
+ throw new Error(`Builtin projection requires content: ${memory_id}`);
712
+ const key = memory_id_to_key(memory_id);
713
+ const existing = await this.active_storage.read(key);
714
+ const existing_source_refs = existing
715
+ ? parse_metadata(existing, memory_type).source_refs
716
+ : [];
717
+ const merged_source_refs = [...existing_source_refs];
718
+ for (const source_ref of source_refs) {
719
+ if (merged_source_refs.some((item) => item.source_id === source_ref.source_id))
720
+ continue;
721
+ merged_source_refs.push(source_ref);
722
+ }
723
+ await this.active_storage.write(key, create_markdown_record({
724
+ title: String(projection.title || memory_id).trim(),
725
+ content,
726
+ memory_type,
727
+ source_refs: merged_source_refs,
728
+ tags: projection.tags || ["memory"],
729
+ }));
730
+ return memory_id;
731
+ }
732
+ /** 以确定性方式向一条长期记忆投影追加内容。 */
733
+ async append_projection(input) {
734
+ const memory_id = normalize_memory_id(input.memory_id);
735
+ const key = memory_id_to_key(memory_id);
736
+ const existing = await this.active_storage.read(key);
737
+ if (existing === null) {
738
+ await this.write_projection({
739
+ memory_id,
740
+ title: input.title,
741
+ content: input.content,
742
+ }, input.source_refs, input.memory_type);
743
+ return;
744
+ }
745
+ const metadata = parse_metadata(existing, input.memory_type);
746
+ const combined_source_refs = [...metadata.source_refs];
747
+ for (const source_ref of input.source_refs) {
748
+ if (combined_source_refs.some((item) => item.source_id === source_ref.source_id))
749
+ continue;
750
+ combined_source_refs.push(source_ref);
751
+ }
752
+ const addition = [
753
+ `## ${new Date().toISOString()}`,
754
+ "",
755
+ String(input.content || "").trim(),
756
+ "",
757
+ `Sources: ${input.source_refs.map((source) => source.source_id).join(", ")}`,
758
+ "",
759
+ ].join("\n");
760
+ await this.active_storage.write(key, create_markdown_record({
761
+ title: metadata.title || input.title,
762
+ content: `${strip_frontmatter(existing)}\n\n${addition}`,
763
+ memory_type: metadata.memory_type,
764
+ source_refs: combined_source_refs,
765
+ tags: ["memory"],
766
+ observed_at: metadata.observed_at === new Date(0).toISOString()
767
+ ? new Date().toISOString()
768
+ : metadata.observed_at,
769
+ }));
770
+ }
771
+ }
772
+ //# sourceMappingURL=BuiltinMemoryProvider.js.map