cortico-world-memory 0.1.3 → 0.1.5

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/world.ts +38 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortico-world-memory",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "description": "Cortico World 扩展:五层记忆(事实库/知识库/反思/人格/档案)+ 三级记忆底座 + Hybrid Recall 向量混合检索(移植自 E:/qq_bot 的记忆系统与 memory_server 向量 sidecar,qq_bot 源码零改动)。",
6
6
  "license": "MIT",
package/src/world.ts CHANGED
@@ -31,6 +31,41 @@ function isInboundUser(e: EventEnvelope): boolean {
31
31
  return true;
32
32
  }
33
33
 
34
+ /**
35
+ * 自动捕获的记忆域(scope):优先按「发送者 QQ 号」,做到私聊↔群聊按人统一——
36
+ * 同一个人无论是在私聊还是群聊发言,记忆都沉淀到同一个 `qq:<号>` 域,无缝衔接。
37
+ * 仅在事件确实来自 QQ 世界(e.source==='qqbot',qq-better 注入 meta.user=QQ号)时才用 qq: 前缀,
38
+ * 避免把其它世界的数字 user 误标成 qq:;QQ 没启动则无 qq 事件,自然退化为其它域(世界无关)。
39
+ * 非 QQ 来源退化为「来源:user」(若有 meta.user)→ 会话地址(senderKey)→ 来源(source)→ main。
40
+ */
41
+ function scopeOfEvent(e: EventEnvelope): string {
42
+ const meta = (e.meta ?? {}) as Record<string, any>;
43
+ const src = typeof e.source === 'string' ? e.source : '';
44
+ const isQQ = src === 'qqbot';
45
+
46
+ // 1. QQ 来源:用发送者 QQ 号做按人统一域(私聊↔群聊无缝衔接)
47
+ if (isQQ) {
48
+ const u = meta.user;
49
+ if (typeof u === 'number' && Number.isFinite(u)) return `qq:${u}`;
50
+ if (typeof u === 'string' && /^\d{4,}$/.test(u.trim())) return `qq:${u.trim()}`;
51
+ const s = meta.sender;
52
+ if (typeof s === 'string') {
53
+ const m = s.match(/QQ[::]\s*(\d{4,})/);
54
+ if (m) return `qq:${m[1]}`;
55
+ }
56
+ }
57
+
58
+ // 2. 其它世界:若提供了 meta.user,按「来源:user」隔离(不同世界用户号避免撞车)
59
+ const u = meta.user;
60
+ if (typeof u === 'number' && Number.isFinite(u)) return `${src || 'user'}:${u}`;
61
+ if (typeof u === 'string' && u.trim()) return `${src || 'user'}:${u.trim()}`;
62
+
63
+ // 3. 退化:会话地址 → 来源 → main
64
+ if (typeof e.senderKey === 'string' && e.senderKey.trim()) return e.senderKey.trim();
65
+ if (src) return src;
66
+ return 'main';
67
+ }
68
+
34
69
  export class MemoryWorld implements World {
35
70
  readonly id = 'memory';
36
71
  private host: WorldHost | null = null;
@@ -160,10 +195,8 @@ export class MemoryWorld implements World {
160
195
  if (!isInboundUser(e)) continue;
161
196
  const text = cleanCaptureText(e.text ?? '');
162
197
  if (text.length < (this.cfg.captureMinLength ?? 2)) continue;
163
- // scope 取对话地址(senderKey,如群号/私聊号);没有则按来源(source)分域——QQ 没开也不报错。
164
- const scope = (typeof e.senderKey === 'string' && e.senderKey.trim())
165
- ? e.senderKey.trim()
166
- : (typeof e.source === 'string' && e.source ? e.source : 'main');
198
+ // scope 优先按发送者 QQ 号(私聊↔群聊按人统一);非 QQ 来源退化为会话地址/来源。
199
+ const scope = scopeOfEvent(e);
167
200
  this.facts!.add(scope, [text]);
168
201
  const vec = this.cfg.embeddingProvider === 'off' ? null : await this.embedder!.embed(text);
169
202
  this.vectors!.add(scope, text, vec, this.cfg.maxVectorsPerScope);
@@ -225,7 +258,7 @@ export class MemoryWorld implements World {
225
258
  this.vectors!.add(scope, text, this.cfg.embeddingProvider === 'off' ? null : await this.embedder!.embed(text), this.cfg.maxVectorsPerScope);
226
259
  return added ? `OK 已记住(${scope}): ${text.slice(0, 80)}` : `OK 已存在,跳过重复(${scope})`;
227
260
  }),
228
- mem_recall: mk('mem_recall', '按语义+关键词混合检索长期记忆(事实库全文 + BM25+向量+RRF 融合),找旧账/回忆约定时用。scope 留空或填 main = 跨全部记忆域(含自动捕获的对话);填具体 scope 只搜该域。', { query: str('查询'), top_k: num('条数'), scope: str('记忆域(留空=全部)') }, ['query'], ['read'],
261
+ mem_recall: mk('mem_recall', '按语义+关键词混合检索长期记忆(事实库全文 + BM25+向量+RRF 融合),找旧账/回忆约定时用。scope 留空或填 main = 跨全部记忆域(含自动捕获的对话);填具体 scope 只搜该域;可填某人的 qq:<QQ号> 来检索 TA 在私聊与群聊里统一沉淀的记忆(私聊↔群聊无缝衔接)。', { query: str('查询'), top_k: num('条数'), scope: str('记忆域(留空=全部)') }, ['query'], ['read'],
229
262
  async (a) => {
230
263
  const scope = scopeOf(a);
231
264
  const topK = Math.min(20, Number(a.top_k) || this.cfg.vectorTopK);