@yottameta/yotta-memory 0.2.10 → 0.4.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.
@@ -1,22 +1,44 @@
1
1
  #!/usr/bin/env node
2
2
  // yotta-memory(元忆): 有权限边界的文件式智能体记忆 CLI(零依赖)
3
- // v0.2 增强:① index.json 索引+TF 打分 ② 质量分+盖棺分(access_count/last_accessed)③ 读取分区(scope/owner)
3
+ // v0.4.0 新增:lan(Windows 计划任务开机自启 serve)/ init --dir(显式指定位置)/ serve --stdio(本地零进程模式)/ MCP 工具补 reindex/export/import
4
+ // v0.4.2 新增:iam/whoami 智能体身份自注册(agents.json 唯一性)+ 自我档案 PREF + 私密记忆须声明 owner
4
5
  'use strict';
5
6
 
6
7
  const fs = require('fs');
7
8
  const path = require('path');
8
9
  const os = require('os');
10
+ const crypto = require('crypto');
11
+ const http = require('http');
12
+ const child_process = require('child_process');
9
13
 
10
- const VERSION = '0.2.9';
14
+ const VERSION = '0.4.2';
11
15
  const TYPES = ['FACT', 'PREF', 'BOUND', 'COMMIT'];
12
16
  const TYPE_DIRS = { FACT: 'facts', PREF: 'prefs', BOUND: 'bounds', COMMIT: 'commits' };
13
17
  const ARCHIVE_DIR = '.archive';
14
18
  const INDEX_FILE = 'index.json';
15
19
  const PRIVATE_TYPES = ['PREF', 'BOUND', 'COMMIT'];
16
20
  const FIELD_ORDER = ['type', 'subject', 'statement', 'confidence', 'created', 'updated', 'tags', 'immutable', 'scope', 'owner', 'access_count', 'last_accessed'];
21
+ const CONFIG_FILE = 'config.json';
22
+ const SERVER_SUBDIR = '.server';
23
+ const TOKENS_FILE = 'tokens.json';
24
+ const AGENTS_FILE = 'agents.json';
17
25
 
26
+ // ---- 全局配置(记忆库位置持久化,固定 ~/.yottamemory/config.json)----
27
+ function configPath() {
28
+ return path.join(os.homedir(), '.yottamemory', CONFIG_FILE);
29
+ }
30
+ function loadConfig() {
31
+ try { return JSON.parse(fs.readFileSync(configPath(), 'utf8')) || {}; } catch (e) { return {}; }
32
+ }
33
+ function saveConfig(cfg) {
34
+ fs.mkdirSync(path.dirname(configPath()), { recursive: true });
35
+ fs.writeFileSync(configPath(), JSON.stringify(cfg, null, 2), 'utf8');
36
+ }
18
37
  function userRoot() {
19
- return process.env.YOTTA_MEMORY_HOME || path.join(os.homedir(), '.yottamemory');
38
+ if (process.env.YOTTA_MEMORY_HOME) return process.env.YOTTA_MEMORY_HOME;
39
+ const cfg = loadConfig();
40
+ if (cfg.memory_home) return cfg.memory_home;
41
+ return path.join(os.homedir(), '.yottamemory');
20
42
  }
21
43
  function projectRoot() {
22
44
  return path.join(process.cwd(), '.yottamemory');
@@ -249,11 +271,7 @@ function vitality(meta) {
249
271
  }
250
272
  function loadGrants(root) {
251
273
  const fp = path.join(root, 'grants.json');
252
- try {
253
- return JSON.parse(fs.readFileSync(fp, 'utf8')) || {};
254
- } catch (e) {
255
- return {};
256
- }
274
+ try { return JSON.parse(fs.readFileSync(fp, 'utf8')) || {}; } catch (e) { return {}; }
257
275
  }
258
276
  function hasGrant(userAgent, ownerAgent) {
259
277
  if (!userAgent || !ownerAgent) return false;
@@ -269,35 +287,35 @@ function hasGrant(userAgent, ownerAgent) {
269
287
  function classifyRead(entry, agent, ownerFilter, unsafe) {
270
288
  if (entry.scope === 'public') return 'read';
271
289
  const owner = entry.owner || '';
272
- // 无归属的 private:所有 agent 视为自身私密(默认行为保留)
273
290
  if (!owner) return 'read';
274
- // 归属等于当前 agent 或(显式 --owner 等于当前 agent):自身授权
275
291
  if (agent && owner === agent) return 'read';
276
292
  if (ownerFilter && owner === ownerFilter && agent && ownerFilter === agent) return 'read';
277
- // 越界读:需要 --unsafe / identity=user / grant 授权,否则拒绝
278
293
  if (unsafe || String(ownerFilter).toLowerCase() === 'user' || String(agent && '' + agent).toLowerCase() === 'user') return 'read';
279
294
  if (hasGrant(agent, owner)) return 'read';
280
295
  return 'denied';
281
296
  }
282
297
 
283
- // ---- 命令 ----
284
- function cmdInit(opts) {
285
- const root = opts.project ? projectRoot() : userRoot();
298
+ // ---- 命令 core(CLI 与 MCP 共用;返回 { error, exitCode, text },不 process.exit)----
299
+ function initCore(opts) {
300
+ const root = opts.dir ? path.resolve(String(opts.dir)) : (opts.project ? projectRoot() : userRoot());
286
301
  ensureInit(root);
287
- console.log('已初始化记忆库: ' + root);
302
+ return { error: false, text: '已初始化记忆库: ' + root };
288
303
  }
289
-
290
- function cmdRemember(type, subject, statement, opts) {
304
+ function rememberCore(type, subject, statement, opts) {
291
305
  const root = userRoot();
292
306
  ensureInit(root);
293
307
  const t = String(type).toUpperCase();
294
- const dir = path.join(root, typeDir(t));
308
+ if (!TYPE_DIRS[t]) return { error: true, text: '未知记忆类型: ' + type + '(可用: ' + TYPES.join(' / ') + ')' };
309
+ const dir = path.join(root, TYPE_DIRS[t]);
295
310
  const stmt = String(statement || '').trim();
296
311
  const subj = String(subject || '').trim();
297
- if (!stmt) { console.error('statement 不能为空'); process.exit(2); }
298
- if (!subj) { console.error('subject 不能为空'); process.exit(2); }
312
+ if (!stmt) return { error: true, text: 'statement 不能为空' };
313
+ if (!subj) return { error: true, text: 'subject 不能为空' };
299
314
  const owner = opts.owner || currentAgent();
300
315
  const scope = opts.scope || defaultScope(t);
316
+ if (scope === 'private' && !owner) {
317
+ return { error: true, text: '私密记忆必须声明归属智能体:请设环境变量 YOTTA_AGENT_ID(或 AGENT_ID)、传 --owner <id>,或先 yotta-memory whoami / iam 登记唯一身份。公共记忆(FACT)不受影响。' };
318
+ }
301
319
  if (fs.existsSync(dir)) {
302
320
  for (const f of fs.readdirSync(dir)) {
303
321
  const fp = path.join(dir, f);
@@ -310,8 +328,7 @@ function cmdRemember(type, subject, statement, opts) {
310
328
  if (scope && !meta.scope) patch.scope = scope;
311
329
  rewriteFrontmatter(fp, patch);
312
330
  upsertIndexEntry(root, readEntry(fp, root));
313
- console.log('已更新: ' + fp);
314
- return;
331
+ return { error: false, text: '已更新: ' + fp };
315
332
  }
316
333
  }
317
334
  }
@@ -325,22 +342,17 @@ function cmdRemember(type, subject, statement, opts) {
325
342
  };
326
343
  fs.writeFileSync(file, frontmatterToText(rec, stmt), 'utf8');
327
344
  upsertIndexEntry(root, readEntry(file, root));
328
- console.log('已记录: ' + file);
345
+ return { error: false, text: '已记录: ' + file };
329
346
  }
330
-
331
- function cmdRecall(query, opts) {
347
+ function recallCore(query, opts) {
332
348
  const roots = [projectRoot(), userRoot()].filter(function (r) { return fs.existsSync(r); });
333
- if (!roots.length) {
334
- console.log('记忆库不存在,请先运行: yotta-memory init');
335
- return;
336
- }
349
+ if (!roots.length) return { error: false, exitCode: 0, text: '记忆库不存在,请先运行: yotta-memory init' };
337
350
  const limit = opts.limit || 50;
338
351
  const onlyType = opts.type ? String(opts.type).toUpperCase() : null;
339
352
  const q = query ? String(query).toLowerCase() : '';
340
353
  const agent = opts.agent || currentAgent();
341
354
  const ownerFilter = opts.owner || '';
342
355
  const allSafe = !!opts.unsafe;
343
- // 是否显式发起跨智能体读取(--all 或 --owner <其它agent>):只有此时越界读才报错/警告,默认 recall 保持原有静默跳过
344
356
  const explicitCross = !!opts.all || (!!ownerFilter && ownerFilter.toLowerCase() !== 'user' && ownerFilter !== agent);
345
357
  const hits = [];
346
358
  let deniedCount = 0;
@@ -375,12 +387,9 @@ function cmdRecall(query, opts) {
375
387
  const shown = hits.slice(0, limit);
376
388
  if (!shown.length) {
377
389
  if (deniedCount > 0 && explicitCross) {
378
- console.log('检测到 ' + deniedCount + ' 条越界访问已被拒绝。');
379
- console.log('如需读取其它智能体私密记忆,请加 --unsafe(用户显式授权)或 --owner user。');
380
- process.exit(3);
390
+ return { error: false, exitCode: 3, text: '检测到 ' + deniedCount + ' 条越界访问已被拒绝。\n如需读取其它智能体私密记忆,请加 --unsafe(用户显式授权)或 --owner user。' };
381
391
  }
382
- console.log('无匹配记忆。');
383
- return;
392
+ return { error: false, exitCode: 0, text: '无匹配记忆。' };
384
393
  }
385
394
  const touchRel = shown.filter(function (h) { return !h.entry.immutable; }).map(function (h) { return h.entry.file; });
386
395
  if (touchRel.length) {
@@ -389,17 +398,17 @@ function cmdRecall(query, opts) {
389
398
  touchIndex(root, touchRel);
390
399
  }
391
400
  }
392
- console.log('共 ' + shown.length + ' 条记忆(' + (hits.length > limit ? '前 ' + limit + ' 条' : '全部') + '):');
401
+ const lines = ['共 ' + shown.length + ' 条记忆(' + (hits.length > limit ? '前 ' + limit + ' 条' : '全部') + '):'];
393
402
  for (const h of shown) {
394
- console.log('[' + h.entry.type + '] ' + h.entry.subject + ': ' + h.entry.statement);
395
- console.log(' ' + path.join(h.root, h.entry.file));
403
+ lines.push('[' + h.entry.type + '] ' + h.entry.subject + ': ' + h.entry.statement);
404
+ lines.push(' ' + path.join(h.root, h.entry.file));
396
405
  }
397
406
  if (deniedCount > 0 && explicitCross) {
398
- console.log('\n[警告] 本次检索共拒绝 ' + deniedCount + ' 条越界访问(其它智能体私密记忆,未授权不展示)。如需读取请加 --unsafe 或 --owner user。');
407
+ lines.push('\n[警告] 本次检索共拒绝 ' + deniedCount + ' 条越界访问(其它智能体私密记忆,未授权不展示)。如需读取请加 --unsafe 或 --owner user。');
399
408
  }
409
+ return { error: false, exitCode: 0, text: lines.join('\n') };
400
410
  }
401
-
402
- function cmdForget(fileRef) {
411
+ function forgetCore(fileRef) {
403
412
  const roots = [projectRoot(), userRoot()].filter(function (r) { return fs.existsSync(r); });
404
413
  const ref = String(fileRef || '').replace(/\\/g, '/');
405
414
  const slash = ref.indexOf('/');
@@ -430,17 +439,16 @@ function cmdForget(fileRef) {
430
439
  }
431
440
  if (target) break;
432
441
  }
433
- if (!target) { console.error('未找到记忆文件: ' + fileRef); process.exit(2); }
442
+ if (!target) return { error: true, text: '未找到记忆文件: ' + fileRef };
434
443
  fs.unlinkSync(target);
435
444
  if (targetRoot) removeIndexEntry(targetRoot, targetRel);
436
- console.log('已删除: ' + target);
445
+ return { error: false, text: '已删除: ' + target };
437
446
  }
438
-
439
- function cmdArchive(opts) {
447
+ function archiveCore(opts) {
440
448
  const days = opts.days || 180;
441
449
  const threshold = (opts.threshold !== undefined && opts.threshold !== null) ? opts.threshold : 0.4;
442
450
  const root = userRoot();
443
- if (!fs.existsSync(root)) { console.log('记忆库不存在。'); return; }
451
+ if (!fs.existsSync(root)) return { error: false, text: '记忆库不存在。' };
444
452
  const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
445
453
  let moved = 0;
446
454
  const movedFiles = [];
@@ -465,9 +473,33 @@ function cmdArchive(opts) {
465
473
  }
466
474
  }
467
475
  if (movedFiles.length) removeIndexEntries(root, movedFiles);
468
- console.log('已归档 ' + moved + ' 条旧记忆到 ' + path.join(root, ARCHIVE_DIR));
476
+ return { error: false, text: '已归档 ' + moved + ' 条旧记忆到 ' + path.join(root, ARCHIVE_DIR) };
469
477
  }
470
478
 
479
+ // ---- 命令包装(CLI 入口)----
480
+ function cmdInit(opts) {
481
+ const r = initCore(opts);
482
+ console.log(r.text);
483
+ }
484
+ function cmdRemember(type, subject, statement, opts) {
485
+ const r = rememberCore(type, subject, statement, opts);
486
+ console.log(r.text);
487
+ if (r.error) process.exit(2);
488
+ }
489
+ function cmdRecall(query, opts) {
490
+ const r = recallCore(query, opts);
491
+ console.log(r.text);
492
+ if (r.exitCode) process.exit(r.exitCode);
493
+ }
494
+ function cmdForget(fileRef) {
495
+ const r = forgetCore(fileRef);
496
+ console.log(r.text);
497
+ if (r.error) process.exit(2);
498
+ }
499
+ function cmdArchive(opts) {
500
+ const r = archiveCore(opts);
501
+ console.log(r.text);
502
+ }
471
503
  function cmdReindex() {
472
504
  const roots = [projectRoot(), userRoot()].filter(function (r) { return fs.existsSync(r); });
473
505
  if (!roots.length) { console.log('记忆库不存在。'); return; }
@@ -476,7 +508,6 @@ function cmdReindex() {
476
508
  console.log('已重建索引 ' + root + '(' + n + ' 条)');
477
509
  }
478
510
  }
479
-
480
511
  function collectAll(root) {
481
512
  const out = [];
482
513
  for (const t of Object.keys(TYPE_DIRS)) {
@@ -491,23 +522,26 @@ function collectAll(root) {
491
522
  }
492
523
  return out;
493
524
  }
494
-
495
- function cmdExport(outPath) {
496
- const root = userRoot();
497
- if (!fs.existsSync(root)) { console.log('记忆库不存在。'); return; }
498
- const data = { exported: today(), version: VERSION, memories: collectAll(root) };
499
- const target = outPath || 'yottamemory-export-' + today() + '.json';
525
+ function exportCore(root, outPath) {
526
+ const data = { format: 'yottamemory', version: 1, exported: today(), memories: collectAll(root) };
527
+ const target = outPath || path.join(root, 'yottamemory-export-' + today() + '.json');
500
528
  fs.writeFileSync(target, JSON.stringify(data, null, 2), 'utf8');
501
- console.log('已导出 ' + data.memories.length + ' 条记忆 -> ' + target);
529
+ return { error: false, text: '已导出 ' + data.memories.length + ' 条记忆 -> ' + target };
502
530
  }
503
-
504
- function cmdImport(src) {
505
- if (!src || !fs.existsSync(src)) { console.error('请提供 JSON 文件路径'); process.exit(2); }
506
- const data = JSON.parse(fs.readFileSync(src, 'utf8'));
507
- if (!data.memories || !Array.isArray(data.memories)) { console.error('JSON 格式不正确(缺少 memories 数组)'); process.exit(2); }
508
- const root = userRoot();
531
+ function importCore(root, src) {
532
+ if (!src) return { error: true, text: '请提供 JSON 文件路径' };
533
+ let fp = String(src);
534
+ if (!fs.existsSync(fp)) {
535
+ const alt = path.resolve(root, fp);
536
+ if (fs.existsSync(alt)) fp = alt;
537
+ }
538
+ if (!fs.existsSync(fp)) return { error: true, text: 'JSON 文件不存在: ' + src };
539
+ let data;
540
+ try { data = JSON.parse(fs.readFileSync(fp, 'utf8')); }
541
+ catch (e) { return { error: true, text: 'JSON 解析失败: ' + (e && e.message ? e.message : String(e)) }; }
542
+ if (!data.memories || !Array.isArray(data.memories)) return { error: true, text: 'JSON 格式不正确(缺少 memories 数组)' };
509
543
  ensureInit(root);
510
- let n = 0;
544
+ let m = 0;
511
545
  for (const item of data.memories) {
512
546
  const meta = item.meta || {};
513
547
  const t = (meta.type || 'FACT').toUpperCase();
@@ -532,32 +566,438 @@ function cmdImport(src) {
532
566
  };
533
567
  fs.writeFileSync(file, frontmatterToText(rec, rec.statement), 'utf8');
534
568
  upsertIndexEntry(root, readEntry(file, root));
535
- n++;
569
+ m++;
570
+ }
571
+ return { error: false, text: '已导入 ' + m + ' 条记忆 -> ' + root };
572
+ }
573
+ function cmdExport(outPath) {
574
+ const r = exportCore(userRoot(), outPath || 'yottamemory-export-' + today() + '.json');
575
+ console.log(r.text);
576
+ }
577
+ function cmdImport(src) {
578
+ const r = importCore(userRoot(), src);
579
+ console.log(r.text);
580
+ if (r.error) process.exit(2);
581
+ }
582
+
583
+ // ---- token 管理(每智能体一个,登记 <记忆库>/.server/tokens.json)----
584
+ function serverDir(root) { return path.join(root, SERVER_SUBDIR); }
585
+ function tokensPath(root) { return path.join(serverDir(root), TOKENS_FILE); }
586
+ function loadTokens(root) {
587
+ try { return JSON.parse(fs.readFileSync(tokensPath(root), 'utf8')) || {}; } catch (e) { return {}; }
588
+ }
589
+ function saveTokens(root, data) {
590
+ fs.mkdirSync(serverDir(root), { recursive: true });
591
+ fs.writeFileSync(tokensPath(root), JSON.stringify(data, null, 2), 'utf8');
592
+ try { fs.chmodSync(tokensPath(root), 0o600); } catch (e) {}
593
+ }
594
+ function cmdTokenNew(agentId, opts) {
595
+ const root = userRoot();
596
+ ensureInit(root);
597
+ if (!agentId) { console.error('请指定 --agent <id>'); process.exit(2); }
598
+ const data = loadTokens(root);
599
+ data.version = 1;
600
+ data.tokens = data.tokens || {};
601
+ const conflict = identityConflict(root, agentId);
602
+ if (conflict && !opts.force) {
603
+ console.error("错误: 智能体 ID '" + agentId + "' " + conflict + '。每个 AI 智能体的 ID 必须唯一;确认是同一智能体后加 --force 覆盖。');
604
+ process.exit(2);
536
605
  }
537
- console.log('已导入 ' + n + ' 条记忆 -> ' + root);
606
+ const token = 'ytm_' + crypto.randomBytes(16).toString('hex');
607
+ data.tokens[agentId] = { token: token, created: today() };
608
+ saveTokens(root, data);
609
+ console.log(token);
610
+ }
611
+ function cmdTokenList() {
612
+ const root = userRoot();
613
+ const data = loadTokens(root);
614
+ const map = data.tokens || {};
615
+ const ids = Object.keys(map);
616
+ if (!ids.length) { console.log('暂无已登记 token(yotta-memory token new --agent <id> 生成)'); return; }
617
+ console.log('已登记智能体:');
618
+ for (const id of ids) console.log(' ' + id + ' (创建于 ' + map[id].created + ')');
619
+ }
620
+ function cmdTokenRevoke(agentId) {
621
+ const root = userRoot();
622
+ if (!agentId) { console.error('请指定 --agent <id>'); process.exit(2); }
623
+ const data = loadTokens(root);
624
+ data.tokens = data.tokens || {};
625
+ if (data.tokens[agentId]) { delete data.tokens[agentId]; saveTokens(root, data); console.log('已吊销: ' + agentId); }
626
+ else { console.log('未找到已登记智能体: ' + agentId); }
538
627
  }
539
628
 
629
+
630
+ // ---- 智能体身份登记(agents.json)+ 自我档案 ----
631
+ function agentsPath(root) { return path.join(root, AGENTS_FILE); }
632
+ function loadAgents(root) {
633
+ try { return JSON.parse(fs.readFileSync(agentsPath(root), 'utf8')) || {}; } catch (e) { return {}; }
634
+ }
635
+ function saveAgents(root, data) {
636
+ fs.mkdirSync(root, { recursive: true });
637
+ fs.writeFileSync(agentsPath(root), JSON.stringify(data, null, 2), 'utf8');
638
+ }
639
+ const SELF_PROFILE_SUBJECT = '自我接入档案';
640
+ function findSelfProfile(root, agentId) {
641
+ const dir = path.join(root, TYPE_DIRS.PREF);
642
+ if (!fs.existsSync(dir)) return '';
643
+ for (const f of fs.readdirSync(dir)) {
644
+ const fp = path.join(dir, f);
645
+ if (!fs.statSync(fp).isFile()) continue;
646
+ const meta = parseFrontmatter(fs.readFileSync(fp, 'utf8')).meta;
647
+ if (meta.subject === SELF_PROFILE_SUBJECT && (meta.owner || '') === agentId) return f;
648
+ }
649
+ return '';
650
+ }
651
+ function selfProfileBody(agentId, root, extra) {
652
+ const lines = [];
653
+ lines.push('agent_id: ' + agentId);
654
+ lines.push('host: ' + os.hostname());
655
+ lines.push('memory_home: ' + root);
656
+ lines.push('mcp_mode: ' + (extra.mcpMode || 'stdio'));
657
+ if (extra.engineUrl) lines.push('engine_url: ' + extra.engineUrl);
658
+ if (extra.token) lines.push('token: ' + extra.token);
659
+ return lines.join('; ');
660
+ }
661
+ function writeSelfProfile(root, agentId, extra) {
662
+ const dir = path.join(root, TYPE_DIRS.PREF);
663
+ const seq = nextSeq(dir);
664
+ const file = path.join(dir, today() + '-' + seq + '.md');
665
+ const body = selfProfileBody(agentId, root, extra || {});
666
+ const rec = {
667
+ type: 'PREF', subject: SELF_PROFILE_SUBJECT, statement: body,
668
+ confidence: 1.0, created: today(), updated: today(),
669
+ tags: ['自我档案'], immutable: false,
670
+ scope: 'private', owner: agentId, access_count: 0, last_accessed: '',
671
+ };
672
+ fs.writeFileSync(file, frontmatterToText(rec, body), 'utf8');
673
+ upsertIndexEntry(root, readEntry(file, root));
674
+ return file;
675
+ }
676
+ function identityConflict(root, agentId) {
677
+ const agents = loadAgents(root).agents || {};
678
+ const tokens = loadTokens(root).tokens || {};
679
+ const host = os.hostname();
680
+ const a = agents[agentId];
681
+ if (a && a.host && a.host !== host) return '已被智能体身份登记占用(host=' + a.host + ')';
682
+ if (tokens[agentId]) return '已被远端 token 登记占用(.server/tokens.json)';
683
+ return '';
684
+ }
685
+ function cmdIam(agentId, opts) {
686
+ const root = userRoot();
687
+ ensureInit(root);
688
+ if (!agentId) { console.error('请指定 <id>:yotta-memory iam <id> [--force]'); process.exit(2); }
689
+ const conflict = identityConflict(root, agentId);
690
+ if (conflict && !opts.force) {
691
+ console.error("错误: ID '" + agentId + "' " + conflict + '。每个 AI 智能体的 ID 必须唯一。请换一个唯一 ID,或确认是同一智能体后加 --force 覆盖。');
692
+ process.exit(2);
693
+ }
694
+ const data = loadAgents(root);
695
+ data.version = 1;
696
+ data.agents = data.agents || {};
697
+ const host = os.hostname();
698
+ const existed = data.agents[agentId];
699
+ data.agents[agentId] = { host: host, created: (existed && existed.created) || today() };
700
+ saveAgents(root, data);
701
+ const file = writeSelfProfile(root, agentId, { mcpMode: 'stdio' });
702
+ console.log('已登记智能体身份: ' + agentId + '(host=' + host + ',' + (conflict ? '--force 覆盖' : '新建') + ')');
703
+ console.log('已写入自我档案: ' + file);
704
+ console.log('本机免 token:以后用 whoami 确认身份;远端接入需 token new --agent ' + agentId);
705
+ }
706
+ function cmdWhoami() {
707
+ const root = userRoot();
708
+ const id = currentAgent();
709
+ if (!id) {
710
+ console.log('当前未声明智能体身份(YOTTA_AGENT_ID / AGENT_ID 未设置,也未传 --agent)。');
711
+ console.log('本机:请在该智能体的 MCP 配置 env 设 YOTTA_AGENT_ID=<唯一ID>,然后 yotta-memory iam <id> 登记;或 CLI 每次带 --agent <id> / --owner <id>。');
712
+ console.log('远端:X-Agent-Id 请求头 + token(token new --agent <id>)。');
713
+ return;
714
+ }
715
+ const agents = loadAgents(root).agents || {};
716
+ const tokens = loadTokens(root).tokens || {};
717
+ let reg = '未登记';
718
+ if (agents[id]) reg = 'agents.json 已登记(host=' + agents[id].host + ')';
719
+ else if (tokens[id]) reg = 'tokens.json 已登记(远端)';
720
+ const profile = findSelfProfile(root, id);
721
+ console.log('当前智能体身份: ' + id);
722
+ console.log('登记状态: ' + reg);
723
+ console.log('自我档案: ' + (profile ? profile : '未写入(请执行 yotta-memory iam ' + id + ')'));
724
+ if (!agents[id] && !tokens[id]) console.log('提示: 请先 yotta-memory iam ' + id + ' 登记唯一身份并落自我档案,再写私密记忆。');
725
+ }
726
+
727
+ // ---- config 命令 ----
728
+ function cmdConfigSet(key, value) {
729
+ if (key !== 'memory_home') { console.error('未知配置项: ' + key + '(可用: memory_home)'); process.exit(2); }
730
+ if (!value) { console.error('缺少值: config set memory_home <目录>'); process.exit(2); }
731
+ const cfg = loadConfig();
732
+ cfg.memory_home = value;
733
+ saveConfig(cfg);
734
+ console.log('已记住记忆库位置: ' + value);
735
+ }
736
+ function cmdConfigGet() {
737
+ const cfg = loadConfig();
738
+ console.log('memory_home: ' + (cfg.memory_home || '(未设置,默认 ~/.yottamemory)'));
739
+ console.log('当前生效用户级位置: ' + userRoot());
740
+ if (cfg.serve && Object.keys(cfg.serve).length) console.log('serve: ' + JSON.stringify(cfg.serve));
741
+ }
742
+
743
+ // ---- MCP serve(局域网 streamable HTTP 记忆引擎,零依赖)----
744
+ function mcpTools() {
745
+ return [
746
+ { name: 'remember', description: '写入一条记忆。参数 type(FACT/PREF/BOUND/COMMIT)、subject、statement 必填;owner 可选,默认当前智能体', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'FACT/PREF/BOUND/COMMIT' }, subject: { type: 'string' }, statement: { type: 'string' }, owner: { type: 'string' } }, required: ['type', 'subject', 'statement'] } },
747
+ { name: 'recall', description: '检索记忆。query 可选;type 可选;limit 可选(默认 20)。只返回当前智能体可读记忆', inputSchema: { type: 'object', properties: { query: { type: 'string' }, type: { type: 'string' }, limit: { type: 'number' } } } },
748
+ { name: 'search', description: '检索记忆(同 recall)。query 可选;type 可选;limit 可选(默认 20)', inputSchema: { type: 'object', properties: { query: { type: 'string' }, type: { type: 'string' }, limit: { type: 'number' } } } },
749
+ { name: 'forget', description: '删除一条记忆。file 为记忆文件路径(如 facts/2026-08-24-0001.md 或文件名)', inputSchema: { type: 'object', properties: { file: { type: 'string' } }, required: ['file'] } },
750
+ { name: 'archive', description: '归档旧记忆。days 默认 180;threshold 默认 0.4', inputSchema: { type: 'object', properties: { days: { type: 'number' }, threshold: { type: 'number' } } } },
751
+ { name: 'reindex', description: '重建索引(手动改 .md 后校正;扫描 facts/prefs/bounds/commits 四目录)', inputSchema: { type: 'object', properties: {} } },
752
+ { name: 'export', description: '导出全部记忆到引擎主机上的 JSON 文件。out 可选(默认 <记忆库>/yottamemory-export-<日期>.json)', inputSchema: { type: 'object', properties: { out: { type: 'string' } } } },
753
+ { name: 'import', description: '从引擎主机上的 JSON 文件导入记忆。src 为文件路径(可绝对路径,或相对记忆库目录)', inputSchema: { type: 'object', properties: { src: { type: 'string' } }, required: ['src'] } },
754
+ { name: 'agent_info', description: '查看当前智能体身份与登记状态(远端读经 token 校验的 X-Agent-Id;本机读 YOTTA_AGENT_ID)。开工先确认「我是谁」,禁止从记忆里抄别人的 ID', inputSchema: { type: 'object', properties: {} } },
755
+ ];
756
+ }
757
+ function callTool(name, args, ctx) {
758
+ const agent = ctx.agent || '';
759
+ try {
760
+ if (name === 'remember') {
761
+ const r = rememberCore(String(args.type || ''), String(args.subject || ''), String(args.statement || ''), { owner: args.owner || agent });
762
+ return { text: r.text, error: r.error };
763
+ }
764
+ if (name === 'recall' || name === 'search') {
765
+ const r = recallCore(args.query ? String(args.query) : null, { limit: args.limit || 20, type: args.type ? String(args.type) : null, agent: agent });
766
+ return { text: r.text, error: r.error };
767
+ }
768
+ if (name === 'forget') {
769
+ const r = forgetCore(String(args.file || ''));
770
+ return { text: r.text, error: r.error };
771
+ }
772
+ if (name === 'archive') {
773
+ const r = archiveCore({ days: args.days, threshold: args.threshold });
774
+ return { text: r.text, error: r.error };
775
+ }
776
+ if (name === 'reindex') {
777
+ const root = userRoot();
778
+ if (!fs.existsSync(root)) return { text: '记忆库不存在。', error: false };
779
+ const cnt = buildIndex(root).length;
780
+ return { text: '已重建索引 ' + root + '(' + cnt + ' 条)', error: false };
781
+ }
782
+ if (name === 'export') {
783
+ const r = exportCore(userRoot(), args.out ? String(args.out) : null);
784
+ return { text: r.text, error: r.error };
785
+ }
786
+ if (name === 'import') {
787
+ const r = importCore(userRoot(), String(args.src || ''));
788
+ return { text: r.text, error: r.error };
789
+ }
790
+ if (name === 'agent_info') {
791
+ const root = userRoot();
792
+ const id = agent || '';
793
+ if (!id) return { text: '当前未声明智能体身份(无 X-Agent-Id / YOTTA_AGENT_ID)。本机请在 MCP 配置 env 设 YOTTA_AGENT_ID=<唯一ID> 并 iam 登记。', error: false };
794
+ const agents = loadAgents(root).agents || {};
795
+ const tokens = loadTokens(root).tokens || {};
796
+ let reg = '未登记';
797
+ if (agents[id]) reg = 'agents.json 已登记(host=' + agents[id].host + ')';
798
+ else if (tokens[id]) reg = 'tokens.json 已登记(远端)';
799
+ const profile = findSelfProfile(root, id);
800
+ return { text: '当前智能体身份: ' + id + '\n登记状态: ' + reg + '\n自我档案: ' + (profile || '未写入(本地引擎主机执行 yotta-memory iam ' + id + ')'), error: false };
801
+ }
802
+ return { text: '未知工具: ' + name, error: true };
803
+ } catch (e) {
804
+ return { text: '错误: ' + (e && e.message ? e.message : String(e)), error: true };
805
+ }
806
+ }
807
+ function handleMessage(msg, ctx) {
808
+ if (!msg || msg.jsonrpc !== '2.0') return { jsonrpc: '2.0', id: msg && msg.id, error: { code: -32600, message: 'invalid request' } };
809
+ const id = msg.id;
810
+ if (id === undefined || id === null) return null;
811
+ const method = msg.method || '';
812
+ const params = msg.params || {};
813
+ if (method === 'initialize') {
814
+ return { jsonrpc: '2.0', id: id, result: { protocolVersion: '2025-03-26', capabilities: { tools: {} }, serverInfo: { name: 'yotta-memory', version: VERSION } } };
815
+ }
816
+ if (method === 'ping') return { jsonrpc: '2.0', id: id, result: {} };
817
+ if (method === 'tools/list') return { jsonrpc: '2.0', id: id, result: { tools: mcpTools() } };
818
+ if (method === 'tools/call') {
819
+ const out = callTool(params.name || '', params.arguments || {}, ctx);
820
+ return { jsonrpc: '2.0', id: id, result: { content: [{ type: 'text', text: out.text }], isError: !!out.error } };
821
+ }
822
+ return { jsonrpc: '2.0', id: id, error: { code: -32601, message: 'Method not found: ' + method } };
823
+ }
824
+ function cmdServe(opts) {
825
+ if (opts.stdio) { cmdServeStdio(); return; }
826
+ const host = opts.host || '0.0.0.0';
827
+ const port = opts.port || 8787;
828
+ const noAuth = !!opts.noAuth;
829
+ const root = userRoot();
830
+ ensureInit(root);
831
+ function authorize(req) {
832
+ if (noAuth) return { agent: String(req.headers['x-agent-id'] || '') };
833
+ const auth = req.headers['authorization'] || '';
834
+ const m = /^Bearer\s+(.+)$/i.exec(auth);
835
+ if (!m) return null;
836
+ const token = m[1].trim();
837
+ const agent = String(req.headers['x-agent-id'] || '');
838
+ const tokenMap = (loadTokens(root).tokens) || {};
839
+ if (tokenMap[agent] && tokenMap[agent].token === token) return { agent: agent };
840
+ return null;
841
+ }
842
+ const server = http.createServer(function (req, res) {
843
+ let pathname = '/';
844
+ try { pathname = new URL(req.url, 'http://' + (req.headers.host || 'localhost')).pathname; } catch (e) {}
845
+ if (pathname !== '/mcp') { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('not found'); return; }
846
+ const auth = authorize(req);
847
+ if (!auth) {
848
+ res.writeHead(401, { 'Content-Type': 'application/json' });
849
+ res.end(JSON.stringify({ jsonrpc: '2.0', error: { code: -32001, message: 'unauthorized: 需要有效 Bearer token 与 X-Agent-Id' } }));
850
+ return;
851
+ }
852
+ if (req.method === 'GET') {
853
+ res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
854
+ res.write('event: endpoint\ndata: /mcp\n\n');
855
+ const iv = setInterval(function () { res.write(': keep-alive\n\n'); }, 15000);
856
+ req.on('close', function () { clearInterval(iv); });
857
+ return;
858
+ }
859
+ if (req.method === 'POST') {
860
+ let body = '';
861
+ req.on('data', function (c) { body += c; });
862
+ req.on('end', function () {
863
+ let msg;
864
+ try { msg = JSON.parse(body); } catch (e) {
865
+ res.writeHead(400, { 'Content-Type': 'application/json' });
866
+ res.end(JSON.stringify({ jsonrpc: '2.0', error: { code: -32700, message: 'parse error' } }));
867
+ return;
868
+ }
869
+ const resp = handleMessage(msg, auth);
870
+ if (resp === null) { res.writeHead(204); res.end(); return; }
871
+ res.writeHead(200, { 'Content-Type': 'application/json' });
872
+ res.end(JSON.stringify(resp));
873
+ });
874
+ return;
875
+ }
876
+ res.writeHead(405, { 'Content-Type': 'text/plain' }); res.end('method not allowed');
877
+ });
878
+ server.listen(port, host, function () {
879
+ console.log('yotta-memory 记忆引擎已启动(v' + VERSION + ')');
880
+ console.log('URL: http://' + host + ':' + port + '/mcp');
881
+ console.log('记忆库: ' + root);
882
+ if (noAuth) console.log('鉴权: 已关闭(--no-auth,仅限可信内网)');
883
+ else console.log('鉴权: Bearer token + X-Agent-Id(yotta-memory token new --agent <id> 生成)');
884
+ console.log('按 Ctrl+C 停止');
885
+ });
886
+ }
887
+
888
+ // ---- stdio 本地零进程模式(照灵魂盘:客户端按需拉起 CLI)----
889
+ function cmdServeStdio() {
890
+ const root = userRoot();
891
+ ensureInit(root);
892
+ const ctx = { agent: currentAgent() };
893
+ let buf = '';
894
+ process.stdin.setEncoding('utf8');
895
+ process.stdin.on('data', function (chunk) {
896
+ buf += chunk;
897
+ let idx;
898
+ while ((idx = buf.indexOf('\n')) !== -1) {
899
+ const line = buf.slice(0, idx).trim();
900
+ buf = buf.slice(idx + 1);
901
+ if (!line) continue;
902
+ let msg;
903
+ try { msg = JSON.parse(line); } catch (e) { continue; }
904
+ const resp = handleMessage(msg, ctx);
905
+ if (resp !== null) process.stdout.write(JSON.stringify(resp) + '\n');
906
+ }
907
+ });
908
+ process.stdin.on('end', function () { process.exit(0); });
909
+ }
910
+
911
+ // ---- lan 命令(Windows 计划任务管理开机自启 serve)----
912
+ const LAN_TASK_NAME = 'YottaMemoryServe';
913
+ function lanTaskRunCmd(opts) {
914
+ const host = opts.host || '0.0.0.0';
915
+ const port = opts.port || 8787;
916
+ return '"' + process.execPath + '" "' + __filename + '" serve --host ' + host + ' --port ' + port;
917
+ }
918
+ function cmdLanEnable(opts) {
919
+ if (process.platform !== 'win32') {
920
+ console.error('lan 命令当前仅支持 Windows(计划任务);本机平台: ' + process.platform);
921
+ process.exit(2);
922
+ }
923
+ const trigger = opts.onstart ? 'onstart' : 'onlogon';
924
+ const tr = lanTaskRunCmd(opts);
925
+ try {
926
+ child_process.execFileSync('schtasks', ['/create', '/tn', LAN_TASK_NAME, '/tr', tr, '/sc', trigger, '/f'], { stdio: 'inherit' });
927
+ } catch (e) {
928
+ console.error('注册计划任务失败(ONSTART 需要管理员权限;ONLOGON 一般不需要;如仍失败请用管理员终端执行): ' + String((e && e.stderr) || (e && e.message) || e).split(/\r?\n/)[0]);
929
+ process.exit(1);
930
+ }
931
+ console.log('已注册开机自启: 计划任务 ' + LAN_TASK_NAME + '(触发器 ' + trigger + ')');
932
+ console.log('运行命令: ' + tr);
933
+ }
934
+ function cmdLanDisable() {
935
+ if (process.platform !== 'win32') {
936
+ console.error('lan 命令当前仅支持 Windows(计划任务);本机平台: ' + process.platform);
937
+ process.exit(2);
938
+ }
939
+ try {
940
+ child_process.execFileSync('schtasks', ['/delete', '/tn', LAN_TASK_NAME, '/f'], { stdio: ['ignore', 'pipe', 'ignore'] });
941
+ } catch (e) {
942
+ const msg = String((e && e.stderr) || (e && e.message) || e);
943
+ if (msg.indexOf('cannot find the path') !== -1 || msg.indexOf('没有找到') !== -1) {
944
+ console.log('未启用: 计划任务 ' + LAN_TASK_NAME + ' 不存在,无需移除');
945
+ return;
946
+ }
947
+ console.error('移除计划任务失败: ' + msg.split(/\r?\n/)[0]);
948
+ process.exit(1);
949
+ }
950
+ console.log('已移除开机自启计划任务 ' + LAN_TASK_NAME);
951
+ }
952
+ function cmdLanStatus() {
953
+ if (process.platform !== 'win32') {
954
+ console.log('lan 命令当前仅支持 Windows(计划任务);本机平台: ' + process.platform);
955
+ return;
956
+ }
957
+ try {
958
+ const out = child_process.execFileSync('schtasks', ['/query', '/tn', LAN_TASK_NAME, '/fo', 'LIST', '/v'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
959
+ console.log('已启用: 计划任务 ' + LAN_TASK_NAME + ' 存在');
960
+ const re = /^(状态|Status)\s*:\s*(.+)$/;
961
+ for (const line of String(out).split(/\r?\n/)) {
962
+ const m = re.exec(line.trim());
963
+ if (m) console.log(m[1] + ': ' + m[2]);
964
+ }
965
+ } catch (e) {
966
+ console.log('未启用: 计划任务 ' + LAN_TASK_NAME + ' 不存在(可用 yotta-memory lan enable 注册)');
967
+ }
968
+ }
969
+
970
+ // ---- usage / main ----
540
971
  function usage() {
541
972
  console.log([
542
973
  'yotta-memory v' + VERSION + ' — 元忆:有权限边界的文件式智能体记忆',
543
974
  '',
544
975
  '用法:',
545
- ' yotta-memory init [--project] 初始化记忆库(默认用户级 ~/.yottamemory)',
976
+ ' yotta-memory init [--project] [--dir <目录>] 初始化记忆库(默认用户级;--dir 显式指定位置)',
977
+ ' yotta-memory config set memory_home <目录> 持久记住记忆库位置',
978
+ ' yotta-memory config get 查看当前配置与生效位置',
546
979
  ' yotta-memory remember <type> <subject> <statement> [--owner <id>] 写入记忆',
547
- ' yotta-memory recall [关键词] [--type T] [--limit N] [--agent <id>] [--owner <id>] [--all] [--unsafe] 检索(索引+TF,读取分区;--unsafe 允许越界读其它智能体私密)',
980
+ ' yotta-memory recall [关键词] [--type T] [--limit N] [--agent <id>] [--owner <id>] [--all] [--unsafe] 检索(索引+TF,读取分区)',
548
981
  ' yotta-memory forget <文件或文件名> 删除一条记忆',
549
982
  ' yotta-memory archive [--days 180] [--threshold 0.4] 归档(盖棺分+年龄)',
550
- ' yotta-memory reindex 重建索引(手动改文件后用)',
983
+ ' yotta-memory reindex 重建索引',
551
984
  ' yotta-memory export [--out 文件.json] 导出全部记忆',
552
985
  ' yotta-memory import <文件.json> 导入记忆',
986
+ ' yotta-memory whoami 查看当前智能体身份与登记状态(读 YOTTA_AGENT_ID / X-Agent-Id,不猜不默认)',
987
+ ' yotta-memory iam <id> [--force] 登记本智能体唯一身份并自动落自我档案(agents.json;ID 必须唯一)',
988
+ ' yotta-memory token new --agent <id> [--force] 生成/重置某智能体访问 token(同 ID 已被其它来源占用需 --force 覆盖)',
989
+ ' yotta-memory token list 列出已登记智能体',
990
+ ' yotta-memory token revoke --agent <id> 吊销某智能体 token',
991
+ ' yotta-memory serve [--host 0.0.0.0] [--port 8787] [--no-auth] [--stdio] 启动 MCP 记忆引擎(streamable HTTP;--stdio 本地零进程模式)',
992
+ ' yotta-memory lan enable [--onstart] | disable | status 开机自启管理(Windows 计划任务,默认 ONLOGON)',
553
993
  ' yotta-memory --version 版本',
554
994
  '',
555
995
  '类型: FACT(公共共享) / PREF(偏好) / BOUND(边界) / COMMIT(承诺)',
556
- '环境变量: YOTTA_MEMORY_HOME 覆盖用户级目录; YOTTA_AGENT_ID/AGENT_ID 作为当前 agent 标识(读取分区)',
996
+ '环境变量: YOTTA_MEMORY_HOME 临时覆盖用户级位置; YOTTA_AGENT_ID/AGENT_ID 当前 agent 标识(本机声明身份用;私密记忆必须有 owner)',
997
+ '远端接入: MCP url http://<主机IP>:8787/mcp;请求头 Authorization: Bearer <token> + X-Agent-Id: <id>',
557
998
  '',
558
999
  ].join('\n'));
559
1000
  }
560
-
561
1001
  function main() {
562
1002
  const args = process.argv.slice(2);
563
1003
  if (!args.length) { usage(); return; }
@@ -566,12 +1006,16 @@ function main() {
566
1006
  if (first === '--help' || first === '-h') { usage(); return; }
567
1007
  const opts = {};
568
1008
  const positional = [];
569
- const valueOpts = new Set(['--type', '--limit', '--days', '--out', '--owner', '--agent', '--threshold', '--scope']);
1009
+ const valueOpts = new Set(['--type', '--limit', '--days', '--out', '--owner', '--agent', '--threshold', '--scope', '--host', '--port', '--dir']);
570
1010
  for (let i = 1; i < args.length; i++) {
571
1011
  const a = args[i];
572
1012
  if (a === '--project') opts.project = true;
573
1013
  else if (a === '--all') opts.all = true;
574
1014
  else if (a === '--unsafe') opts.unsafe = true;
1015
+ else if (a === '--no-auth') opts.noAuth = true;
1016
+ else if (a === '--stdio') opts.stdio = true;
1017
+ else if (a === '--onstart') opts.onstart = true;
1018
+ else if (a === '--force') opts.force = true;
575
1019
  else if (valueOpts.has(a)) {
576
1020
  const v = args[++i];
577
1021
  if (a === '--type') opts.type = v;
@@ -582,6 +1026,9 @@ function main() {
582
1026
  else if (a === '--owner') opts.owner = v;
583
1027
  else if (a === '--agent') opts.agent = v;
584
1028
  else if (a === '--scope') opts.scope = v;
1029
+ else if (a === '--host') opts.host = v;
1030
+ else if (a === '--port') opts.port = parseInt(v, 10) || 8787;
1031
+ else if (a === '--dir') opts.dir = v;
585
1032
  } else if (a.startsWith('--')) {
586
1033
  console.error('未知选项: ' + a);
587
1034
  process.exit(2);
@@ -591,6 +1038,15 @@ function main() {
591
1038
  }
592
1039
  switch (first) {
593
1040
  case 'init': cmdInit(opts); break;
1041
+ case 'whoami': cmdWhoami(); break;
1042
+ case 'iam': cmdIam(positional[0], opts); break;
1043
+ case 'config': {
1044
+ const sub = positional[0];
1045
+ if (sub === 'set') cmdConfigSet(positional[1], positional[2]);
1046
+ else if (sub === 'get') cmdConfigGet();
1047
+ else { console.error('config 子命令: set memory_home <目录> / get'); process.exit(2); }
1048
+ break;
1049
+ }
594
1050
  case 'remember': cmdRemember(positional[0], positional[1], positional[2], opts); break;
595
1051
  case 'recall': cmdRecall(positional[0] || null, opts); break;
596
1052
  case 'forget': cmdForget(positional[0]); break;
@@ -598,6 +1054,23 @@ function main() {
598
1054
  case 'reindex': cmdReindex(); break;
599
1055
  case 'export': cmdExport(opts.out); break;
600
1056
  case 'import': cmdImport(positional[0]); break;
1057
+ case 'token': {
1058
+ const sub = positional[0];
1059
+ if (sub === 'new') cmdTokenNew(opts.agent, opts);
1060
+ else if (sub === 'list') cmdTokenList();
1061
+ else if (sub === 'revoke') cmdTokenRevoke(opts.agent);
1062
+ else { console.error('token 子命令: new --agent <id> / list / revoke --agent <id>'); process.exit(2); }
1063
+ break;
1064
+ }
1065
+ case 'serve': cmdServe(opts); break;
1066
+ case 'lan': {
1067
+ const sub = positional[0];
1068
+ if (sub === 'enable') cmdLanEnable(opts);
1069
+ else if (sub === 'disable') cmdLanDisable();
1070
+ else if (sub === 'status') cmdLanStatus();
1071
+ else { console.error('lan 子命令: enable [--onstart] / disable / status'); process.exit(2); }
1072
+ break;
1073
+ }
601
1074
  default:
602
1075
  console.error('未知命令: ' + first);
603
1076
  usage();
@@ -605,4 +1078,20 @@ function main() {
605
1078
  }
606
1079
  }
607
1080
 
608
- main();
1081
+ if (require.main === module) main();
1082
+ module.exports = {
1083
+ VERSION: VERSION,
1084
+ userRoot: userRoot,
1085
+ projectRoot: projectRoot,
1086
+ rememberCore: rememberCore,
1087
+ recallCore: recallCore,
1088
+ forgetCore: forgetCore,
1089
+ archiveCore: archiveCore,
1090
+ mcpTools: mcpTools,
1091
+ callTool: callTool,
1092
+ handleMessage: handleMessage,
1093
+ loadConfig: loadConfig,
1094
+ saveConfig: saveConfig,
1095
+ loadTokens: loadTokens,
1096
+ saveTokens: saveTokens,
1097
+ };