@memtensor/memos-local-openclaw-plugin 1.0.4-beta.6 → 1.0.4-beta.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.
Files changed (143) hide show
  1. package/README.md +38 -21
  2. package/dist/capture/index.d.ts +1 -1
  3. package/dist/capture/index.d.ts.map +1 -1
  4. package/dist/capture/index.js +29 -3
  5. package/dist/capture/index.js.map +1 -1
  6. package/dist/client/connector.d.ts +29 -0
  7. package/dist/client/connector.d.ts.map +1 -0
  8. package/dist/client/connector.js +231 -0
  9. package/dist/client/connector.js.map +1 -0
  10. package/dist/client/hub.d.ts +61 -0
  11. package/dist/client/hub.d.ts.map +1 -0
  12. package/dist/client/hub.js +170 -0
  13. package/dist/client/hub.js.map +1 -0
  14. package/dist/client/skill-sync.d.ts +36 -0
  15. package/dist/client/skill-sync.d.ts.map +1 -0
  16. package/dist/client/skill-sync.js +226 -0
  17. package/dist/client/skill-sync.js.map +1 -0
  18. package/dist/config.d.ts +2 -1
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +70 -3
  21. package/dist/config.js.map +1 -1
  22. package/dist/embedding/index.d.ts +4 -2
  23. package/dist/embedding/index.d.ts.map +1 -1
  24. package/dist/embedding/index.js +17 -1
  25. package/dist/embedding/index.js.map +1 -1
  26. package/dist/hub/auth.d.ts +19 -0
  27. package/dist/hub/auth.d.ts.map +1 -0
  28. package/dist/hub/auth.js +70 -0
  29. package/dist/hub/auth.js.map +1 -0
  30. package/dist/hub/server.d.ts +48 -0
  31. package/dist/hub/server.d.ts.map +1 -0
  32. package/dist/hub/server.js +922 -0
  33. package/dist/hub/server.js.map +1 -0
  34. package/dist/hub/user-manager.d.ts +31 -0
  35. package/dist/hub/user-manager.d.ts.map +1 -0
  36. package/dist/hub/user-manager.js +129 -0
  37. package/dist/hub/user-manager.js.map +1 -0
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +8 -4
  41. package/dist/index.js.map +1 -1
  42. package/dist/ingest/providers/index.d.ts +10 -2
  43. package/dist/ingest/providers/index.d.ts.map +1 -1
  44. package/dist/ingest/providers/index.js +203 -6
  45. package/dist/ingest/providers/index.js.map +1 -1
  46. package/dist/ingest/providers/openai.d.ts +1 -0
  47. package/dist/ingest/providers/openai.d.ts.map +1 -1
  48. package/dist/ingest/providers/openai.js +1 -0
  49. package/dist/ingest/providers/openai.js.map +1 -1
  50. package/dist/ingest/task-processor.js +1 -1
  51. package/dist/ingest/task-processor.js.map +1 -1
  52. package/dist/openclaw-api.d.ts +53 -0
  53. package/dist/openclaw-api.d.ts.map +1 -0
  54. package/dist/openclaw-api.js +189 -0
  55. package/dist/openclaw-api.js.map +1 -0
  56. package/dist/recall/engine.js +1 -1
  57. package/dist/recall/engine.js.map +1 -1
  58. package/dist/shared/llm-call.d.ts +4 -1
  59. package/dist/shared/llm-call.d.ts.map +1 -1
  60. package/dist/shared/llm-call.js +14 -1
  61. package/dist/shared/llm-call.js.map +1 -1
  62. package/dist/sharing/types.contract.d.ts +2 -0
  63. package/dist/sharing/types.contract.d.ts.map +1 -0
  64. package/dist/sharing/types.contract.js +3 -0
  65. package/dist/sharing/types.contract.js.map +1 -0
  66. package/dist/sharing/types.d.ts +80 -0
  67. package/dist/sharing/types.d.ts.map +1 -0
  68. package/dist/sharing/types.js +3 -0
  69. package/dist/sharing/types.js.map +1 -0
  70. package/dist/skill/evaluator.d.ts.map +1 -1
  71. package/dist/skill/evaluator.js +2 -2
  72. package/dist/skill/evaluator.js.map +1 -1
  73. package/dist/skill/generator.d.ts.map +1 -1
  74. package/dist/skill/generator.js +4 -4
  75. package/dist/skill/generator.js.map +1 -1
  76. package/dist/skill/upgrader.js +1 -1
  77. package/dist/skill/upgrader.js.map +1 -1
  78. package/dist/skill/validator.js +1 -1
  79. package/dist/skill/validator.js.map +1 -1
  80. package/dist/storage/ensure-binding.d.ts.map +1 -1
  81. package/dist/storage/ensure-binding.js +3 -1
  82. package/dist/storage/ensure-binding.js.map +1 -1
  83. package/dist/storage/sqlite.d.ts +332 -1
  84. package/dist/storage/sqlite.d.ts.map +1 -1
  85. package/dist/storage/sqlite.js +913 -4
  86. package/dist/storage/sqlite.js.map +1 -1
  87. package/dist/tools/index.d.ts +1 -0
  88. package/dist/tools/index.d.ts.map +1 -1
  89. package/dist/tools/index.js +3 -1
  90. package/dist/tools/index.js.map +1 -1
  91. package/dist/tools/memory-search.d.ts +5 -2
  92. package/dist/tools/memory-search.d.ts.map +1 -1
  93. package/dist/tools/memory-search.js +50 -7
  94. package/dist/tools/memory-search.js.map +1 -1
  95. package/dist/tools/network-memory-detail.d.ts +4 -0
  96. package/dist/tools/network-memory-detail.d.ts.map +1 -0
  97. package/dist/tools/network-memory-detail.js +34 -0
  98. package/dist/tools/network-memory-detail.js.map +1 -0
  99. package/dist/types.d.ts +48 -2
  100. package/dist/types.d.ts.map +1 -1
  101. package/dist/types.js.map +1 -1
  102. package/dist/viewer/html.d.ts.map +1 -1
  103. package/dist/viewer/html.js +4299 -511
  104. package/dist/viewer/html.js.map +1 -1
  105. package/dist/viewer/server.d.ts +65 -0
  106. package/dist/viewer/server.d.ts.map +1 -1
  107. package/dist/viewer/server.js +1844 -90
  108. package/dist/viewer/server.js.map +1 -1
  109. package/index.ts +767 -41
  110. package/openclaw.plugin.json +3 -2
  111. package/package.json +3 -3
  112. package/scripts/postinstall.cjs +282 -45
  113. package/skill/memos-memory-guide/SKILL.md +82 -20
  114. package/src/capture/index.ts +30 -2
  115. package/src/client/connector.ts +225 -0
  116. package/src/client/hub.ts +207 -0
  117. package/src/client/skill-sync.ts +216 -0
  118. package/src/config.ts +92 -3
  119. package/src/embedding/index.ts +21 -1
  120. package/src/hub/auth.ts +78 -0
  121. package/src/hub/server.ts +906 -0
  122. package/src/hub/user-manager.ts +143 -0
  123. package/src/index.ts +13 -5
  124. package/src/ingest/providers/index.ts +240 -6
  125. package/src/ingest/providers/openai.ts +1 -1
  126. package/src/ingest/task-processor.ts +1 -1
  127. package/src/openclaw-api.ts +287 -0
  128. package/src/recall/engine.ts +1 -1
  129. package/src/shared/llm-call.ts +18 -2
  130. package/src/sharing/types.contract.ts +40 -0
  131. package/src/sharing/types.ts +102 -0
  132. package/src/skill/evaluator.ts +3 -2
  133. package/src/skill/generator.ts +6 -4
  134. package/src/skill/upgrader.ts +1 -1
  135. package/src/skill/validator.ts +1 -1
  136. package/src/storage/ensure-binding.ts +3 -1
  137. package/src/storage/sqlite.ts +1164 -4
  138. package/src/tools/index.ts +1 -0
  139. package/src/tools/memory-search.ts +58 -8
  140. package/src/tools/network-memory-detail.ts +34 -0
  141. package/src/types.ts +43 -2
  142. package/src/viewer/html.ts +4299 -511
  143. package/src/viewer/server.ts +1688 -73
@@ -3,7 +3,7 @@
3
3
  "name": "MemOS Local Memory",
4
4
  "description": "Full-write local conversation memory with hybrid search (RRF + MMR + recency). Provides memory_search, memory_get, task_summary, memory_timeline, memory_viewer for layered retrieval.",
5
5
  "kind": "memory",
6
- "version": "0.1.11",
6
+ "version": "0.1.12",
7
7
  "skills": [
8
8
  "skill/memos-memory-guide"
9
9
  ],
@@ -32,5 +32,6 @@
32
32
  "Memory Viewer will be available at http://127.0.0.1:18799",
33
33
  "If better-sqlite3 fails to build, ensure you have C++ build tools: xcode-select --install (macOS) or build-essential (Linux)"
34
34
  ]
35
- }
35
+ },
36
+ "extensions": ["./index.ts"]
36
37
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@memtensor/memos-local-openclaw-plugin",
3
- "version": "1.0.4-beta.6",
4
- "description": "MemOS Local memory plugin for OpenClaw \u2014 full-write, hybrid-recall, progressive retrieval",
3
+ "version": "1.0.4-beta.8",
4
+ "description": "MemOS Local memory plugin for OpenClaw full-write, hybrid-recall, progressive retrieval",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
7
  "types": "dist/index.d.ts",
@@ -64,4 +64,4 @@
64
64
  "typescript": "^5.7.0",
65
65
  "vitest": "^2.1.0"
66
66
  }
67
- }
67
+ }
@@ -380,58 +380,30 @@ function sqliteBindingsExist() {
380
380
 
381
381
  if (sqliteBindingsExist()) {
382
382
  ok("better-sqlite3 is ready.");
383
- console.log(`
384
- ${GREEN}${BOLD} ┌──────────────────────────────────────────────────┐
385
- │ ✔ Setup complete! │
386
- │ │
387
- │ Restart gateway: │
388
- │ ${CYAN}openclaw gateway stop && openclaw gateway start${GREEN} │
389
- └──────────────────────────────────────────────────┘${RESET}
390
- `);
391
- process.exit(0);
392
383
  } else {
393
384
  warn("better-sqlite3 native bindings not found in plugin dir.");
394
385
  log(`Searched in: ${DIM}${sqliteModulePath}/build/${RESET}`);
395
386
  log("Running: npm rebuild better-sqlite3 (may take 30-60s)...");
396
- }
397
-
398
- const startMs = Date.now();
399
-
400
- const result = spawnSync("npm", ["rebuild", "better-sqlite3"], {
401
- cwd: pluginDir,
402
- stdio: "pipe",
403
- shell: true,
404
- timeout: 180_000,
405
- });
406
387
 
407
- const elapsed = ((Date.now() - startMs) / 1000).toFixed(1);
408
- const stdout = (result.stdout || "").toString().trim();
409
- const stderr = (result.stderr || "").toString().trim();
410
-
411
- if (stdout) log(`rebuild output: ${DIM}${stdout.slice(0, 500)}${RESET}`);
412
- if (stderr) warn(`rebuild stderr: ${DIM}${stderr.slice(0, 500)}${RESET}`);
388
+ const startMs = Date.now();
389
+ const result = spawnSync("npm", ["rebuild", "better-sqlite3"], {
390
+ cwd: pluginDir,
391
+ stdio: "pipe",
392
+ shell: true,
393
+ timeout: 180_000,
394
+ });
395
+ const elapsed = ((Date.now() - startMs) / 1000).toFixed(1);
396
+ const stdout = (result.stdout || "").toString().trim();
397
+ const stderr = (result.stderr || "").toString().trim();
398
+ if (stdout) log(`rebuild output: ${DIM}${stdout.slice(0, 500)}${RESET}`);
399
+ if (stderr) warn(`rebuild stderr: ${DIM}${stderr.slice(0, 500)}${RESET}`);
413
400
 
414
- if (result.status === 0) {
415
- if (sqliteBindingsExist()) {
401
+ if (result.status === 0 && sqliteBindingsExist()) {
416
402
  ok(`better-sqlite3 rebuilt successfully (${elapsed}s).`);
417
- console.log(`
418
- ${GREEN}${BOLD} ┌──────────────────────────────────────────────────┐
419
- │ ✔ Setup complete! │
420
- │ │
421
- │ Restart gateway: │
422
- │ ${CYAN}openclaw gateway stop && openclaw gateway start${GREEN} │
423
- └──────────────────────────────────────────────────┘${RESET}
424
- `);
425
- process.exit(0);
426
403
  } else {
427
- fail(`Rebuild completed but bindings still missing (${elapsed}s).`);
428
- fail(`Looked in: ${sqliteModulePath}/build/`);
429
- }
430
- } else {
431
- fail(`Rebuild failed with exit code ${result.status} (${elapsed}s).`);
432
- }
433
-
434
- console.log(`
404
+ if (result.status !== 0) fail(`Rebuild failed with exit code ${result.status} (${elapsed}s).`);
405
+ else { fail(`Rebuild completed but bindings still missing (${elapsed}s).`); fail(`Looked in: ${sqliteModulePath}/build/`); }
406
+ console.log(`
435
407
  ${YELLOW}${BOLD} ╔══════════════════════════════════════════════════════════════╗
436
408
  ║ ✖ better-sqlite3 native module build failed ║
437
409
  ╠══════════════════════════════════════════════════════════════╣${RESET}
@@ -452,5 +424,270 @@ ${YELLOW} ║${RESET} ${GREEN}openclaw gateway stop && openclaw gateway start$
452
424
  ${YELLOW} ║${RESET} ${YELLOW}║${RESET}
453
425
  ${YELLOW}${BOLD} ╚══════════════════════════════════════════════════════════════╝${RESET}
454
426
  `);
427
+ }
428
+ }
429
+
430
+ /* ═══════════════════════════════════════════════════════════
431
+ * Phase 3: Interactive LAN Sharing Setup
432
+ * ═══════════════════════════════════════════════════════════ */
433
+
434
+ const rlMod = require("readline");
435
+ const os = require("os");
436
+ const crypto = require("crypto");
437
+
438
+ function getLocalIPs() {
439
+ const nets = os.networkInterfaces();
440
+ const results = [];
441
+ for (const name of Object.keys(nets)) {
442
+ for (const net of nets[name]) {
443
+ if (net.family === "IPv4" && !net.internal) {
444
+ results.push({ name, address: net.address });
445
+ }
446
+ }
447
+ }
448
+ return results;
449
+ }
450
+
451
+ function generateTeamToken() {
452
+ return crypto.randomBytes(18).toString("base64url");
453
+ }
454
+
455
+ function createPrompt() {
456
+ const rl = rlMod.createInterface({ input: process.stdin, output: process.stdout });
457
+ return {
458
+ ask(q) { return new Promise((resolve) => rl.question(q, (a) => resolve(a.trim()))); },
459
+ close() { rl.close(); },
460
+ };
461
+ }
462
+
463
+ async function setupSharingWizard() {
464
+ if (!process.stdin.isTTY) {
465
+ log("Non-interactive environment, skipping sharing setup wizard.");
466
+ return;
467
+ }
468
+ if (process.env.MEMOS_SKIP_SETUP === "1") {
469
+ log("MEMOS_SKIP_SETUP=1, skipping sharing setup.");
470
+ return;
471
+ }
472
+
473
+ const home = process.env.HOME || process.env.USERPROFILE || "";
474
+ const cfgPath = path.join(home, ".openclaw", "openclaw.json");
475
+ if (!fs.existsSync(cfgPath)) {
476
+ log("~/.openclaw/openclaw.json not found, skipping sharing setup.");
477
+ return;
478
+ }
479
+
480
+ let cfg;
481
+ try {
482
+ cfg = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
483
+ } catch (e) {
484
+ warn(`Cannot parse openclaw.json: ${e.message}`);
485
+ return;
486
+ }
487
+
488
+ const pluginEntry = cfg?.plugins?.entries?.["memos-local-openclaw-plugin"];
489
+ const existingSharing = pluginEntry?.config?.sharing;
490
+
491
+ if (existingSharing?.enabled) {
492
+ const roleLabel = existingSharing.role === "hub" ? "Hub (团队中心)" : "Client (团队成员)";
493
+ log(`已检测到共享配置: 角色 = ${BOLD}${roleLabel}${RESET}`);
494
+ const prompt = createPrompt();
495
+ const ans = await prompt.ask(` 是否重新配置?/ Reconfigure? (y/N) > `);
496
+ prompt.close();
497
+ if (ans.toLowerCase() !== "y") {
498
+ ok("保留现有共享配置。");
499
+ return;
500
+ }
501
+ }
502
+
503
+ phase(3, "局域网共享设置 / LAN Sharing Setup");
504
+
505
+ const prompt = createPrompt();
506
+
507
+ const enableAns = await prompt.ask(` 是否启用局域网记忆共享?/ Enable LAN sharing? (y/N) > `);
508
+ if (enableAns.toLowerCase() !== "y") {
509
+ prompt.close();
510
+ log("未启用共享。你可以稍后在 openclaw.json 中手动配置。");
511
+ return;
512
+ }
513
+
514
+ console.log(`
515
+ ${BOLD}请选择你的角色 / Choose your role:${RESET}
516
+ ${GREEN}1)${RESET} 创建团队 (Hub) — 成为团队管理员,其他人连接你
517
+ ${GREEN}2)${RESET} 加入团队 (Client) — 连接到已有的 Hub
518
+ `);
519
+
520
+ const roleAns = await prompt.ask(` 请输入 1 或 2 / Enter 1 or 2 > `);
521
+
522
+ let sharingConfig;
523
+
524
+ if (roleAns === "1") {
525
+ console.log(`\n ${CYAN}${BOLD}── Hub 设置 / Hub Setup ──${RESET}\n`);
526
+
527
+ const teamName = (await prompt.ask(` 团队名称 / Team name (默认: My Team) > `)) || "My Team";
528
+ const portStr = (await prompt.ask(` Hub 端口 / Hub port (默认: 18800) > `)) || "18800";
529
+ const port = parseInt(portStr, 10) || 18800;
530
+ const teamToken = generateTeamToken();
531
+
532
+ sharingConfig = {
533
+ enabled: true,
534
+ role: "hub",
535
+ hub: { port, teamName, teamToken },
536
+ };
537
+
538
+ const localIPs = getLocalIPs();
539
+ const displayIP = localIPs.length > 0 ? localIPs[0].address : "<your-ip>";
540
+
541
+ console.log(`
542
+ ${GREEN}${BOLD} ┌────────────────────────────────────────────────────────────┐
543
+ │ ✔ Hub 配置完成!/ Hub configured! │
544
+ │ │
545
+ │ 请将以下信息分享给团队成员: │
546
+ │ Share this info with your team: │
547
+ │ │
548
+ │ ${CYAN}Hub 地址 / Address : ${displayIP}:${port}${GREEN}
549
+ │ ${CYAN}Team Token : ${teamToken}${GREEN}
550
+ │ │
551
+ │ 团队成员安装插件时选择 "加入团队" 并输入以上信息。 │
552
+ └────────────────────────────────────────────────────────────┘${RESET}
553
+ `);
554
+
555
+ if (localIPs.length > 1) {
556
+ log("检测到多个网络接口 / Multiple network interfaces:");
557
+ for (const ip of localIPs) {
558
+ log(` ${ip.name}: ${BOLD}${ip.address}:${port}${RESET}`);
559
+ }
560
+ }
561
+
562
+ } else if (roleAns === "2") {
563
+ console.log(`\n ${CYAN}${BOLD}── 加入团队 / Join Team ──${RESET}\n`);
564
+
565
+ const hubAddress = await prompt.ask(` Hub 地址 / Hub address (如 192.168.1.100:18800) > `);
566
+ if (!hubAddress) {
567
+ prompt.close();
568
+ warn("Hub 地址不能为空,跳过配置。");
569
+ return;
570
+ }
571
+
572
+ const teamToken = await prompt.ask(` Team Token (由 Hub 创建者提供 / from Hub creator) > `);
573
+ if (!teamToken) {
574
+ prompt.close();
575
+ warn("Team Token 不能为空,跳过配置。");
576
+ return;
577
+ }
578
+
579
+ const username = (await prompt.ask(` 你的用户名 / Your username (默认: ${os.userInfo().username}) > `)) || os.userInfo().username;
455
580
 
456
- process.exit(0);
581
+ const hubUrl = /^https?:\/\//i.test(hubAddress.trim()) ? hubAddress.trim() : `http://${hubAddress.trim()}`;
582
+ log(`正在加入团队 / Joining team at: ${BOLD}${hubUrl}${RESET} ...`);
583
+
584
+ let userToken = "";
585
+ let joinOk = false;
586
+
587
+ try {
588
+ const http = require("http");
589
+ const https = require("https");
590
+ const joinResult = await new Promise((resolve, reject) => {
591
+ const postData = JSON.stringify({ teamToken, username, deviceName: os.hostname() });
592
+ const url = new URL(`${hubUrl}/api/v1/hub/join`);
593
+ const mod = url.protocol === "https:" ? https : http;
594
+ const reqObj = mod.request({
595
+ hostname: url.hostname, port: url.port, path: url.pathname,
596
+ method: "POST",
597
+ headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(postData) },
598
+ timeout: 8000,
599
+ }, (resp) => {
600
+ let data = "";
601
+ resp.on("data", (c) => { data += c; });
602
+ resp.on("end", () => {
603
+ try { resolve({ status: resp.statusCode, body: JSON.parse(data) }); }
604
+ catch { resolve({ status: resp.statusCode, body: data }); }
605
+ });
606
+ });
607
+ reqObj.on("error", reject);
608
+ reqObj.on("timeout", () => { reqObj.destroy(); reject(new Error("timeout")); });
609
+ reqObj.write(postData);
610
+ reqObj.end();
611
+ });
612
+
613
+ if (joinResult.status === 200 && joinResult.body.userToken) {
614
+ userToken = joinResult.body.userToken;
615
+ joinOk = true;
616
+ ok(`加入成功!/ Joined successfully! 用户: ${BOLD}${username}${RESET}`);
617
+ } else if (joinResult.status === 403) {
618
+ prompt.close();
619
+ fail("Team Token 无效 / Invalid Team Token");
620
+ return;
621
+ } else {
622
+ warn(`Hub 返回 / Hub responded: ${joinResult.status} ${JSON.stringify(joinResult.body)}`);
623
+ log("配置将被保存,gateway 启动时会用 Team Token 自动重试加入。");
624
+ }
625
+ } catch (e) {
626
+ warn(`无法连接 Hub / Cannot reach Hub: ${e.message}`);
627
+ log("配置将被保存,gateway 启动时会用 Team Token 自动重试加入。");
628
+ }
629
+
630
+ sharingConfig = {
631
+ enabled: true,
632
+ role: "client",
633
+ client: { hubAddress, teamToken },
634
+ };
635
+ if (userToken) sharingConfig.client.userToken = userToken;
636
+
637
+ const statusMsg = joinOk
638
+ ? `已加入团队,重启 gateway 即生效`
639
+ : `Hub 暂不可达,gateway 启动时会自动加入`;
640
+ console.log(`
641
+ ${GREEN}${BOLD} ┌────────────────────────────────────────────────────────────┐
642
+ │ ✔ Client 配置完成!/ Client configured! │
643
+ │ ${CYAN}Hub: ${hubAddress}${GREEN}
644
+ │ ${CYAN}${statusMsg}${GREEN}
645
+ └────────────────────────────────────────────────────────────┘${RESET}
646
+ `);
647
+
648
+ } else {
649
+ prompt.close();
650
+ warn(`无效选择 "${roleAns}",跳过配置。你可以稍后在 openclaw.json 中手动配置。`);
651
+ return;
652
+ }
653
+
654
+ prompt.close();
655
+
656
+ try {
657
+ if (!cfg.plugins) cfg.plugins = {};
658
+ if (!cfg.plugins.entries) cfg.plugins.entries = {};
659
+ if (!cfg.plugins.entries["memos-local-openclaw-plugin"]) {
660
+ cfg.plugins.entries["memos-local-openclaw-plugin"] = { enabled: true };
661
+ }
662
+ const entry = cfg.plugins.entries["memos-local-openclaw-plugin"];
663
+ if (!entry.config) entry.config = {};
664
+ entry.config.sharing = sharingConfig;
665
+
666
+ const backup = cfgPath + ".bak-" + Date.now();
667
+ fs.copyFileSync(cfgPath, backup);
668
+ fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
669
+ ok(`配置已写入 / Config saved: ${DIM}~/.openclaw/openclaw.json${RESET}`);
670
+ log(`备份 / Backup: ${DIM}${backup}${RESET}`);
671
+ } catch (e) {
672
+ fail(`写入配置失败 / Config write failed: ${e.message}`);
673
+ warn("请手动编辑 ~/.openclaw/openclaw.json 添加 sharing 配置。");
674
+ }
675
+ }
676
+
677
+ (async () => {
678
+ try {
679
+ await setupSharingWizard();
680
+ } catch (e) {
681
+ warn(`Setup wizard error: ${e.message}`);
682
+ }
683
+
684
+ console.log(`
685
+ ${GREEN}${BOLD} ┌──────────────────────────────────────────────────┐
686
+ │ ✔ Setup complete! │
687
+ │ │
688
+ │ Restart gateway: │
689
+ │ ${CYAN}openclaw gateway stop && openclaw gateway start${GREEN} │
690
+ └──────────────────────────────────────────────────┘${RESET}
691
+ `);
692
+ process.exit(0);
693
+ })();
@@ -1,17 +1,22 @@
1
1
  ---
2
2
  name: memos-memory-guide
3
- description: "Use the MemOS Local memory system to search and use the user's past conversations. Use this skill whenever the user refers to past chats, their own preferences or history, or when you need to answer from prior context. When auto-recall returns nothing (long or unclear user query), generate your own short search query and call memory_search. Available tools: memory_search, memory_get, memory_write_public, task_summary, skill_get, skill_search, skill_install, skill_publish, skill_unpublish, memory_timeline, memory_viewer."
3
+ description: "Use the MemOS Local memory system to search and use the user's past conversations. Use this skill whenever the user refers to past chats, their own preferences or history, or when you need to answer from prior context. When auto-recall returns nothing (long or unclear user query), generate your own short search query and call memory_search. Available tools: memory_search, memory_get, memory_write_public, memory_share, memory_unshare, task_summary, skill_get, skill_search, skill_install, skill_publish, skill_unpublish, network_memory_detail, network_skill_pull, network_team_info, memory_timeline, memory_viewer."
4
4
  ---
5
5
 
6
6
  # MemOS Local Memory — Agent Guide
7
7
 
8
- This skill describes how to use the MemOS memory tools so you can reliably search and use the user's long-term conversation history, share knowledge across agents, and discover public skills.
8
+ This skill describes how to use the MemOS memory tools so you can reliably search and use the user's long-term conversation history, query team-shared data, share tasks, and discover or pull reusable skills.
9
+
10
+ Two sharing planes exist and must not be confused:
11
+
12
+ - **Local agent sharing:** visible to agents in the same OpenClaw workspace only.
13
+ - **Team sharing:** visible to teammates through the configured team server.
9
14
 
10
15
  ## How memory is provided each turn
11
16
 
12
17
  - **Automatic recall (hook):** At the start of each turn, the system runs a memory search using the user's current message and injects relevant past memories into your context. You do not need to call any tool for that.
13
18
  - **When that is not enough:** If the user's message is very long, vague, or the automatic search returns **no memories**, you should **generate your own short, focused query** and call `memory_search` yourself.
14
- - **Memory isolation:** Each agent can only see its own memories and memories marked as `public`. Other agents' private memories are invisible to you.
19
+ - **Memory isolation:** Each agent can only see its own local private memories and local `public` memories. Team-shared data only appears when you search with `scope="group"` or `scope="all"`.
15
20
 
16
21
  ## Tools — what they do and when to call
17
22
 
@@ -24,9 +29,10 @@ This skill describes how to use the MemOS memory tools so you can reliably searc
24
29
  - You need to search with a different angle (e.g. filter by `role='user'`).
25
30
  - **Parameters:**
26
31
  - `query` (string, **required**) — Natural language search query.
27
- - `maxResults` (number, optional) — Max results, default 20, max 20.
28
- - `minScore` (number, optional) — Minimum score 0–1, default 0.45, floor 0.35.
29
- - `role` (string, optional) — Filter by role: `'user'`, `'assistant'`, or `'tool'`. Use `'user'` to find what the user said.
32
+ - `scope` (string, optional) — `'local'` (default) for current agent + local shared memories, or `'group'` / `'all'` to include team-shared memories.
33
+ - `maxResults` (number, optional) — Increase when the first search is too narrow.
34
+ - `minScore` (number, optional) — Lower slightly if recall is too strict.
35
+ - `role` (string, optional) — Filter local results by `'user'`, `'assistant'`, `'tool'`, or `'system'`.
30
36
 
31
37
  ### memory_get
32
38
 
@@ -38,12 +44,32 @@ This skill describes how to use the MemOS memory tools so you can reliably searc
38
44
 
39
45
  ### memory_write_public
40
46
 
41
- - **What it does:** Write a piece of information to public memory. Public memories are visible to all agents during `memory_search`. Use for shared knowledge, team decisions, or cross-agent coordination information.
42
- - **When to call:** In multi-agent or collaborative scenarios, when you have persistent information useful to everyone (e.g. shared decisions, conventions, configurations, workflows). Do not write session-only or purely private content.
47
+ - **What it does:** Create a brand new local shared memory. These memories are visible to all agents in the same OpenClaw workspace during `memory_search`. This does **not** publish anything to the team server.
48
+ - **When to call:** In multi-agent or collaborative scenarios, when you want to create a new persistent shared note from scratch (e.g. shared decisions, conventions, configurations, workflows). Do not use it if you already have a specific memory chunk to expose.
43
49
  - **Parameters:**
44
- - `content` (string, **required**) — The content to write to public memory.
50
+ - `content` (string, **required**) — The content to write to local shared memory.
45
51
  - `summary` (string, optional) — Short summary of the content.
46
52
 
53
+ ### memory_share
54
+
55
+ - **What it does:** Share an existing memory either with local OpenClaw agents, to the team, or to both.
56
+ - **When to call:** You already have a useful memory chunk and want to expose it beyond the current agent.
57
+ - **Do not use when:** You are creating a new shared note from scratch. In that case use `memory_write_public`.
58
+ - **Parameters:**
59
+ - `chunkId` (string, **required**) — Existing memory chunk ID.
60
+ - `target` (string, optional) — `'agents'` (default), `'hub'`, or `'both'`.
61
+ - `visibility` (string, optional) — Team visibility when target includes team: `'public'` (default) or `'group'`.
62
+ - `groupId` (string, optional) — Optional team group ID when `visibility='group'`.
63
+
64
+ ### memory_unshare
65
+
66
+ - **What it does:** Remove an existing memory from local agent sharing, team sharing, or both.
67
+ - **When to call:** A memory should no longer be visible outside the current agent or should be removed from the team.
68
+ - **Parameters:**
69
+ - `chunkId` (string, **required**) — Existing memory chunk ID.
70
+ - `target` (string, optional) — `'agents'`, `'hub'`, or `'all'` (default).
71
+ - `privateOwner` (string, optional) — Rare fallback only for older public memories that have no recorded original owner.
72
+
47
73
  ### task_summary
48
74
 
49
75
  - **What it does:** Get the detailed summary of a complete task: title, status, narrative summary, and related skills. Use when `memory_search` returns a hit with a `task_id` and you need the full story. Preserves critical information: URLs, file paths, commands, error codes, step-by-step instructions.
@@ -62,11 +88,11 @@ This skill describes how to use the MemOS memory tools so you can reliably searc
62
88
 
63
89
  ### skill_search
64
90
 
65
- - **What it does:** Search available skills by natural language. Searches your own skills, public skills, or both controlled by the `scope` parameter.
91
+ - **What it does:** Search available skills by natural language. Searches your own skills, local shared skills, or both. It can also include team skills.
66
92
  - **When to call:** The current task requires a capability or guide you don't have. Use `skill_search` to find one first; after finding it, use `skill_get` to read it, then `skill_install` to load it for future turns.
67
93
  - **Parameters:**
68
94
  - `query` (string, **required**) — Natural language description of the needed skill.
69
- - `scope` (string, optional) — Search scope: `'mix'` (default, self + public), `'self'` (own only), `'public'` (public only).
95
+ - `scope` (string, optional) — `'mix'` (default, self + local shared), `'self'`, `'public'` (local shared only), or `'group'` / `'all'` to include team results.
70
96
 
71
97
  ### skill_install
72
98
 
@@ -77,17 +103,47 @@ This skill describes how to use the MemOS memory tools so you can reliably searc
77
103
 
78
104
  ### skill_publish
79
105
 
80
- - **What it does:** Make a skill public so other agents can discover and install it via `skill_search`.
81
- - **When to call:** You have a useful skill that other agents could benefit from, and you want to share it.
106
+ - **What it does:** Share a skill with local agents, or publish it to the team.
107
+ - **When to call:** You have a useful skill that other agents or your team could benefit from.
82
108
  - **Parameters:**
83
109
  - `skillId` (string, **required**) — The skill ID to publish.
110
+ - `target` (string, optional) — `'agents'` (default) or `'hub'`.
111
+ - `visibility` (string, optional) — When `target='hub'`, use `'public'` (default) or `'group'`.
112
+ - `groupId` (string, optional) — Optional team group ID when `target='hub'` and `visibility='group'`.
113
+ - `scope` (string, optional) — Backward-compatible alias for old calls. Prefer `target` + `visibility` in new calls.
84
114
 
85
115
  ### skill_unpublish
86
116
 
87
- - **What it does:** Make a skill private again. Other agents will no longer be able to discover it.
117
+ - **What it does:** Stop local agent sharing, remove a team-published copy, or do both.
88
118
  - **When to call:** You want to stop sharing a previously published skill.
89
119
  - **Parameters:**
90
120
  - `skillId` (string, **required**) — The skill ID to unpublish.
121
+ - `target` (string, optional) — `'agents'` (default), `'hub'`, or `'all'`.
122
+
123
+ ### network_memory_detail
124
+
125
+ - **What it does:** Fetches the full content behind a team search hit.
126
+ - **When to call:** A `memory_search` result came from the team and you need the full shared memory content.
127
+ - **Parameters:** `remoteHitId`.
128
+
129
+ ### task_share / task_unshare
130
+
131
+ - **What they do:** Share a local task to the team, or remove it later.
132
+ - **When to call:** A task is valuable to your group or to the whole team and should be discoverable via shared search.
133
+ - **Parameters:** `taskId`, plus sharing visibility/scope when required.
134
+
135
+ ### network_skill_pull
136
+
137
+ - **What it does:** Pulls a team-shared skill bundle down into local storage.
138
+ - **When to call:** `skill_search` found a useful team skill and you want to use it locally or offline.
139
+ - **Parameters:** `skillId`.
140
+
141
+ ### network_team_info
142
+
143
+ - **What it does:** Returns current team server connection information, user, role, and groups.
144
+ - **When to call:** You need to confirm whether team sharing is configured or which groups the current client belongs to.
145
+ - **Call this first before:** `memory_share(... target='hub'|'both')`, `memory_unshare(... target='hub'|'all')`, `task_share`, `task_unshare`, `skill_publish(... target='hub')`, `skill_unpublish(... target='hub'|'all')`, or `network_skill_pull`.
146
+ - **Parameters:** none.
91
147
 
92
148
  ### memory_timeline
93
149
 
@@ -123,13 +179,19 @@ This skill describes how to use the MemOS memory tools so you can reliably searc
123
179
  6. **You need a capability/guide that you don't have**
124
180
  → Call `skill_search(query="...", scope="mix")` to discover available skills.
125
181
 
126
- 7. **You have shared knowledge useful to all agents**
127
- → Call `memory_write_public(content="...")` to persist it in public memory.
182
+ 7. **You have new shared knowledge useful to all local agents**
183
+ → Call `memory_write_public(content="...")`.
184
+
185
+ 8. **You already have an existing memory chunk and want to expose or hide it**
186
+ → Call `memory_share(chunkId="...", target="agents|hub|both")` or `memory_unshare(chunkId="...", target="agents|hub|all")`.
187
+
188
+ 9. **You are about to do anything team-sharing-related**
189
+ → Call `network_team_info()` first if team server availability is uncertain.
128
190
 
129
- 8. **You want to share/stop sharing a skill with other agents**
130
- Call `skill_publish(skillId="...")` or `skill_unpublish(skillId="...")`.
191
+ 10. **You want to share/stop sharing a skill with local agents or team**
192
+ Prefer `skill_publish(skillId="...", target="agents|hub", visibility=...)` and `skill_unpublish(skillId="...", target="agents|hub|all")`.
131
193
 
132
- 9. **User asks where to see or manage their memories**
194
+ 11. **User asks where to see or manage their memories**
133
195
  → Call `memory_viewer()` and share the URL.
134
196
 
135
197
  ## Writing good search queries
@@ -144,6 +206,6 @@ This skill describes how to use the MemOS memory tools so you can reliably searc
144
206
  Each memory is tagged with an `owner` (e.g. `agent:main`, `agent:sales-bot`). This is handled **automatically** — you do not need to pass any owner parameter.
145
207
 
146
208
  - **Your memories:** All tools (`memory_search`, `memory_get`, `memory_timeline`) automatically scope queries to your agent's own memories.
147
- - **Public memories:** Memories marked as `public` are visible to all agents. Use `memory_write_public` to write shared knowledge.
209
+ - **Local shared memories:** Memories marked as local shared are visible to all agents in the same OpenClaw workspace. Use `memory_write_public` to create them, or `memory_share(target='agents')` to expose an existing chunk.
148
210
  - **Cross-agent isolation:** You cannot see memories owned by other agents (unless they are public).
149
211
  - **How it works:** The system identifies your agent ID from the OpenClaw runtime context and applies owner filtering automatically on every search, recall, and retrieval.
@@ -33,6 +33,9 @@ const SENTINEL_FAST_RE = new RegExp(
33
33
  const ENVELOPE_PREFIX_RE =
34
34
  /^\s*\[(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(?::\d{2})?\s+[A-Z]{3}[+-]\d{1,2}\]\s*/;
35
35
 
36
+ const ENVELOPE_EXTRACT_RE =
37
+ /^\s*\[(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s+(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}(?::\d{2})?)\s+([A-Z]{3}[+-]\d{1,2})\]/;
38
+
36
39
  /**
37
40
  * Extract writable messages from a conversation turn.
38
41
  *
@@ -47,9 +50,11 @@ export function captureMessages(
47
50
  evidenceTag: string,
48
51
  log: Logger,
49
52
  owner?: string,
53
+ userSearchTime?: number,
50
54
  ): ConversationMessage[] {
51
55
  const now = Date.now();
52
56
  const result: ConversationMessage[] = [];
57
+ let lastTimestamp = 0;
53
58
 
54
59
  for (const msg of messages) {
55
60
  const role = msg.role as Role;
@@ -75,10 +80,19 @@ export function captureMessages(
75
80
  }
76
81
  if (!content.trim()) continue;
77
82
 
83
+ let ts: number;
84
+ if (role === "user" && userSearchTime && userSearchTime > 0) {
85
+ ts = userSearchTime;
86
+ } else {
87
+ ts = now;
88
+ }
89
+ if (ts <= lastTimestamp) ts = lastTimestamp + 1;
90
+ lastTimestamp = ts;
91
+
78
92
  result.push({
79
93
  role,
80
94
  content,
81
- timestamp: now,
95
+ timestamp: ts,
82
96
  turnId,
83
97
  sessionKey,
84
98
  toolName: role === "tool" ? msg.toolName : undefined,
@@ -150,13 +164,27 @@ export function stripInboundMetadata(text: string): string {
150
164
  return stripEnvelopePrefix(result.join("\n")).trim();
151
165
  }
152
166
 
153
- /** Strip <think…>…</think blocks emitted by DeepSeek-style reasoning models. */
167
+ /** Strip <think…>…</think> blocks emitted by DeepSeek-style reasoning models. */
154
168
  const THINKING_TAG_RE = /<think[\s>][\s\S]*?<\/think>\s*/gi;
155
169
 
156
170
  function stripThinkingTags(text: string): string {
157
171
  return text.replace(THINKING_TAG_RE, "");
158
172
  }
159
173
 
174
+ function extractEnvelopeTimestamp(text: string): number | null {
175
+ const m = ENVELOPE_EXTRACT_RE.exec(text);
176
+ if (!m) return null;
177
+ const [, date, time, tz] = m;
178
+ const timeStr = time.includes(":") && time.split(":").length === 3 ? time : time + ":00";
179
+ const offsetMatch = tz.match(/([+-])(\d{1,2})$/);
180
+ const offsetStr = offsetMatch
181
+ ? `${offsetMatch[1]}${offsetMatch[2].padStart(2, "0")}:00`
182
+ : "+00:00";
183
+ const iso = `${date}T${timeStr}${offsetStr}`;
184
+ const ts = new Date(iso).getTime();
185
+ return Number.isNaN(ts) ? null : ts;
186
+ }
187
+
160
188
  function stripEnvelopePrefix(text: string): string {
161
189
  return text.replace(ENVELOPE_PREFIX_RE, "");
162
190
  }