@memtensor/memos-local-openclaw-plugin 1.0.6-beta.9 → 1.0.7-beta.1

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 (228) hide show
  1. package/index.ts +266 -273
  2. package/openclaw.plugin.json +1 -1
  3. package/package.json +3 -5
  4. package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
  5. package/prebuilds/darwin-x64/better_sqlite3.node +0 -0
  6. package/prebuilds/linux-x64/better_sqlite3.node +0 -0
  7. package/prebuilds/win32-x64/better_sqlite3.node +0 -0
  8. package/scripts/postinstall.cjs +44 -44
  9. package/skill/memos-memory-guide/SKILL.md +25 -2
  10. package/src/context-engine/index.ts +321 -0
  11. package/src/ingest/providers/index.ts +14 -1
  12. package/src/shared/llm-call.ts +14 -1
  13. package/src/update-check.ts +2 -7
  14. package/src/viewer/html.ts +4 -4
  15. package/src/viewer/server.ts +25 -3
  16. package/telemetry.credentials.json +5 -0
  17. package/dist/capture/index.d.ts +0 -26
  18. package/dist/capture/index.d.ts.map +0 -1
  19. package/dist/capture/index.js +0 -283
  20. package/dist/capture/index.js.map +0 -1
  21. package/dist/client/connector.d.ts +0 -34
  22. package/dist/client/connector.d.ts.map +0 -1
  23. package/dist/client/connector.js +0 -381
  24. package/dist/client/connector.js.map +0 -1
  25. package/dist/client/hub.d.ts +0 -61
  26. package/dist/client/hub.d.ts.map +0 -1
  27. package/dist/client/hub.js +0 -174
  28. package/dist/client/hub.js.map +0 -1
  29. package/dist/client/skill-sync.d.ts +0 -36
  30. package/dist/client/skill-sync.d.ts.map +0 -1
  31. package/dist/client/skill-sync.js +0 -226
  32. package/dist/client/skill-sync.js.map +0 -1
  33. package/dist/config.d.ts +0 -5
  34. package/dist/config.d.ts.map +0 -1
  35. package/dist/config.js +0 -171
  36. package/dist/config.js.map +0 -1
  37. package/dist/embedding/index.d.ts +0 -14
  38. package/dist/embedding/index.d.ts.map +0 -1
  39. package/dist/embedding/index.js +0 -105
  40. package/dist/embedding/index.js.map +0 -1
  41. package/dist/embedding/local.d.ts +0 -3
  42. package/dist/embedding/local.d.ts.map +0 -1
  43. package/dist/embedding/local.js +0 -66
  44. package/dist/embedding/local.js.map +0 -1
  45. package/dist/embedding/providers/cohere.d.ts +0 -4
  46. package/dist/embedding/providers/cohere.d.ts.map +0 -1
  47. package/dist/embedding/providers/cohere.js +0 -57
  48. package/dist/embedding/providers/cohere.js.map +0 -1
  49. package/dist/embedding/providers/gemini.d.ts +0 -3
  50. package/dist/embedding/providers/gemini.d.ts.map +0 -1
  51. package/dist/embedding/providers/gemini.js +0 -31
  52. package/dist/embedding/providers/gemini.js.map +0 -1
  53. package/dist/embedding/providers/mistral.d.ts +0 -3
  54. package/dist/embedding/providers/mistral.d.ts.map +0 -1
  55. package/dist/embedding/providers/mistral.js +0 -25
  56. package/dist/embedding/providers/mistral.js.map +0 -1
  57. package/dist/embedding/providers/openai.d.ts +0 -3
  58. package/dist/embedding/providers/openai.d.ts.map +0 -1
  59. package/dist/embedding/providers/openai.js +0 -35
  60. package/dist/embedding/providers/openai.js.map +0 -1
  61. package/dist/embedding/providers/voyage.d.ts +0 -3
  62. package/dist/embedding/providers/voyage.d.ts.map +0 -1
  63. package/dist/embedding/providers/voyage.js +0 -25
  64. package/dist/embedding/providers/voyage.js.map +0 -1
  65. package/dist/hub/auth.d.ts +0 -19
  66. package/dist/hub/auth.d.ts.map +0 -1
  67. package/dist/hub/auth.js +0 -70
  68. package/dist/hub/auth.js.map +0 -1
  69. package/dist/hub/server.d.ts +0 -52
  70. package/dist/hub/server.d.ts.map +0 -1
  71. package/dist/hub/server.js +0 -1197
  72. package/dist/hub/server.js.map +0 -1
  73. package/dist/hub/user-manager.d.ts +0 -40
  74. package/dist/hub/user-manager.d.ts.map +0 -1
  75. package/dist/hub/user-manager.js +0 -153
  76. package/dist/hub/user-manager.js.map +0 -1
  77. package/dist/index.d.ts +0 -46
  78. package/dist/index.d.ts.map +0 -1
  79. package/dist/index.js +0 -82
  80. package/dist/index.js.map +0 -1
  81. package/dist/ingest/chunker.d.ts +0 -15
  82. package/dist/ingest/chunker.d.ts.map +0 -1
  83. package/dist/ingest/chunker.js +0 -192
  84. package/dist/ingest/chunker.js.map +0 -1
  85. package/dist/ingest/dedup.d.ts +0 -19
  86. package/dist/ingest/dedup.d.ts.map +0 -1
  87. package/dist/ingest/dedup.js +0 -50
  88. package/dist/ingest/dedup.js.map +0 -1
  89. package/dist/ingest/providers/anthropic.d.ts +0 -21
  90. package/dist/ingest/providers/anthropic.d.ts.map +0 -1
  91. package/dist/ingest/providers/anthropic.js +0 -314
  92. package/dist/ingest/providers/anthropic.js.map +0 -1
  93. package/dist/ingest/providers/bedrock.d.ts +0 -21
  94. package/dist/ingest/providers/bedrock.d.ts.map +0 -1
  95. package/dist/ingest/providers/bedrock.js +0 -313
  96. package/dist/ingest/providers/bedrock.js.map +0 -1
  97. package/dist/ingest/providers/gemini.d.ts +0 -21
  98. package/dist/ingest/providers/gemini.d.ts.map +0 -1
  99. package/dist/ingest/providers/gemini.js +0 -298
  100. package/dist/ingest/providers/gemini.js.map +0 -1
  101. package/dist/ingest/providers/index.d.ts +0 -68
  102. package/dist/ingest/providers/index.d.ts.map +0 -1
  103. package/dist/ingest/providers/index.js +0 -611
  104. package/dist/ingest/providers/index.js.map +0 -1
  105. package/dist/ingest/providers/openai.d.ts +0 -30
  106. package/dist/ingest/providers/openai.d.ts.map +0 -1
  107. package/dist/ingest/providers/openai.js +0 -387
  108. package/dist/ingest/providers/openai.js.map +0 -1
  109. package/dist/ingest/task-processor.d.ts +0 -91
  110. package/dist/ingest/task-processor.d.ts.map +0 -1
  111. package/dist/ingest/task-processor.js +0 -478
  112. package/dist/ingest/task-processor.js.map +0 -1
  113. package/dist/ingest/worker.d.ts +0 -23
  114. package/dist/ingest/worker.d.ts.map +0 -1
  115. package/dist/ingest/worker.js +0 -255
  116. package/dist/ingest/worker.js.map +0 -1
  117. package/dist/openclaw-api.d.ts +0 -53
  118. package/dist/openclaw-api.d.ts.map +0 -1
  119. package/dist/openclaw-api.js +0 -189
  120. package/dist/openclaw-api.js.map +0 -1
  121. package/dist/recall/engine.d.ts +0 -28
  122. package/dist/recall/engine.d.ts.map +0 -1
  123. package/dist/recall/engine.js +0 -343
  124. package/dist/recall/engine.js.map +0 -1
  125. package/dist/recall/mmr.d.ts +0 -17
  126. package/dist/recall/mmr.d.ts.map +0 -1
  127. package/dist/recall/mmr.js +0 -53
  128. package/dist/recall/mmr.js.map +0 -1
  129. package/dist/recall/recency.d.ts +0 -20
  130. package/dist/recall/recency.d.ts.map +0 -1
  131. package/dist/recall/recency.js +0 -26
  132. package/dist/recall/recency.js.map +0 -1
  133. package/dist/recall/rrf.d.ts +0 -16
  134. package/dist/recall/rrf.d.ts.map +0 -1
  135. package/dist/recall/rrf.js +0 -15
  136. package/dist/recall/rrf.js.map +0 -1
  137. package/dist/shared/llm-call.d.ts +0 -30
  138. package/dist/shared/llm-call.d.ts.map +0 -1
  139. package/dist/shared/llm-call.js +0 -253
  140. package/dist/shared/llm-call.js.map +0 -1
  141. package/dist/sharing/types.contract.d.ts +0 -2
  142. package/dist/sharing/types.contract.d.ts.map +0 -1
  143. package/dist/sharing/types.contract.js +0 -3
  144. package/dist/sharing/types.contract.js.map +0 -1
  145. package/dist/sharing/types.d.ts +0 -80
  146. package/dist/sharing/types.d.ts.map +0 -1
  147. package/dist/sharing/types.js +0 -3
  148. package/dist/sharing/types.js.map +0 -1
  149. package/dist/skill/bundled-memory-guide.d.ts +0 -2
  150. package/dist/skill/bundled-memory-guide.d.ts.map +0 -1
  151. package/dist/skill/bundled-memory-guide.js +0 -45
  152. package/dist/skill/bundled-memory-guide.js.map +0 -1
  153. package/dist/skill/evaluator.d.ts +0 -28
  154. package/dist/skill/evaluator.d.ts.map +0 -1
  155. package/dist/skill/evaluator.js +0 -169
  156. package/dist/skill/evaluator.js.map +0 -1
  157. package/dist/skill/evolver.d.ts +0 -48
  158. package/dist/skill/evolver.d.ts.map +0 -1
  159. package/dist/skill/evolver.js +0 -406
  160. package/dist/skill/evolver.js.map +0 -1
  161. package/dist/skill/generator.d.ts +0 -26
  162. package/dist/skill/generator.d.ts.map +0 -1
  163. package/dist/skill/generator.js +0 -521
  164. package/dist/skill/generator.js.map +0 -1
  165. package/dist/skill/installer.d.ts +0 -42
  166. package/dist/skill/installer.d.ts.map +0 -1
  167. package/dist/skill/installer.js +0 -165
  168. package/dist/skill/installer.js.map +0 -1
  169. package/dist/skill/upgrader.d.ts +0 -19
  170. package/dist/skill/upgrader.d.ts.map +0 -1
  171. package/dist/skill/upgrader.js +0 -366
  172. package/dist/skill/upgrader.js.map +0 -1
  173. package/dist/skill/validator.d.ts +0 -30
  174. package/dist/skill/validator.d.ts.map +0 -1
  175. package/dist/skill/validator.js +0 -272
  176. package/dist/skill/validator.js.map +0 -1
  177. package/dist/storage/ensure-binding.d.ts +0 -12
  178. package/dist/storage/ensure-binding.d.ts.map +0 -1
  179. package/dist/storage/ensure-binding.js +0 -53
  180. package/dist/storage/ensure-binding.js.map +0 -1
  181. package/dist/storage/sqlite.d.ts +0 -649
  182. package/dist/storage/sqlite.d.ts.map +0 -1
  183. package/dist/storage/sqlite.js +0 -2657
  184. package/dist/storage/sqlite.js.map +0 -1
  185. package/dist/storage/vector.d.ts +0 -12
  186. package/dist/storage/vector.d.ts.map +0 -1
  187. package/dist/storage/vector.js +0 -34
  188. package/dist/storage/vector.js.map +0 -1
  189. package/dist/telemetry.d.ts +0 -47
  190. package/dist/telemetry.d.ts.map +0 -1
  191. package/dist/telemetry.js +0 -312
  192. package/dist/telemetry.js.map +0 -1
  193. package/dist/tools/index.d.ts +0 -5
  194. package/dist/tools/index.d.ts.map +0 -1
  195. package/dist/tools/index.js +0 -12
  196. package/dist/tools/index.js.map +0 -1
  197. package/dist/tools/memory-get.d.ts +0 -4
  198. package/dist/tools/memory-get.d.ts.map +0 -1
  199. package/dist/tools/memory-get.js +0 -64
  200. package/dist/tools/memory-get.js.map +0 -1
  201. package/dist/tools/memory-search.d.ts +0 -7
  202. package/dist/tools/memory-search.d.ts.map +0 -1
  203. package/dist/tools/memory-search.js +0 -84
  204. package/dist/tools/memory-search.js.map +0 -1
  205. package/dist/tools/memory-timeline.d.ts +0 -4
  206. package/dist/tools/memory-timeline.d.ts.map +0 -1
  207. package/dist/tools/memory-timeline.js +0 -73
  208. package/dist/tools/memory-timeline.js.map +0 -1
  209. package/dist/tools/network-memory-detail.d.ts +0 -4
  210. package/dist/tools/network-memory-detail.d.ts.map +0 -1
  211. package/dist/tools/network-memory-detail.js +0 -34
  212. package/dist/tools/network-memory-detail.js.map +0 -1
  213. package/dist/types.d.ts +0 -330
  214. package/dist/types.d.ts.map +0 -1
  215. package/dist/types.js +0 -38
  216. package/dist/types.js.map +0 -1
  217. package/dist/update-check.d.ts +0 -21
  218. package/dist/update-check.d.ts.map +0 -1
  219. package/dist/update-check.js +0 -110
  220. package/dist/update-check.js.map +0 -1
  221. package/dist/viewer/html.d.ts +0 -2
  222. package/dist/viewer/html.d.ts.map +0 -1
  223. package/dist/viewer/html.js +0 -9168
  224. package/dist/viewer/html.js.map +0 -1
  225. package/dist/viewer/server.d.ts +0 -205
  226. package/dist/viewer/server.d.ts.map +0 -1
  227. package/dist/viewer/server.js +0 -4876
  228. package/dist/viewer/server.js.map +0 -1
package/index.ts CHANGED
@@ -31,6 +31,18 @@ import { SkillInstaller } from "./src/skill/installer";
31
31
  import { Summarizer } from "./src/ingest/providers";
32
32
  import { MEMORY_GUIDE_SKILL_MD } from "./src/skill/bundled-memory-guide";
33
33
  import { Telemetry } from "./src/telemetry";
34
+ import {
35
+ type AgentMessage as CEAgentMessage,
36
+ type PendingInjection,
37
+ deduplicateHits as ceDeduplicateHits,
38
+ formatMemoryBlock,
39
+ appendMemoryToMessage,
40
+ removeExistingMemoryBlock,
41
+ messageHasMemoryBlock,
42
+ getTextFromMessage,
43
+ insertSyntheticAssistantEntry,
44
+ findTargetAssistantEntry,
45
+ } from "./src/context-engine";
34
46
 
35
47
 
36
48
  /** Remove near-duplicate hits based on summary word overlap (>70%). Keeps first (highest-scored) hit. */
@@ -320,6 +332,214 @@ const memosLocalPlugin = {
320
332
  api.logger.info("memos-local: allowPromptInjection=true, auto-recall enabled");
321
333
  }
322
334
 
335
+ // ─── Context Engine: inject memories into assistant messages ───
336
+ // Memories are wrapped in <relevant-memories> tags which OpenClaw's UI
337
+ // automatically strips from assistant messages, keeping the chat clean.
338
+ // Persisted to the session file so the prompt prefix stays stable for KV cache.
339
+
340
+ let pendingInjection: PendingInjection | null = null;
341
+
342
+ try {
343
+ api.registerContextEngine("memos-local-openclaw-plugin", () => ({
344
+ info: {
345
+ id: "memos-local-openclaw-plugin",
346
+ name: "MemOS Local Memory Context Engine",
347
+ version: "1.0.0",
348
+ },
349
+
350
+ async ingest() {
351
+ return { ingested: false };
352
+ },
353
+
354
+ async assemble(params: {
355
+ sessionId: string;
356
+ sessionKey?: string;
357
+ messages: CEAgentMessage[];
358
+ tokenBudget?: number;
359
+ model?: string;
360
+ prompt?: string;
361
+ }) {
362
+ const { messages, prompt, sessionId, sessionKey } = params;
363
+
364
+ if (!allowPromptInjection || !prompt || prompt.length < 3) {
365
+ return { messages, estimatedTokens: 0 };
366
+ }
367
+
368
+ const recallT0 = performance.now();
369
+ try {
370
+ let query = prompt;
371
+ const senderTag = "Sender (untrusted metadata):";
372
+ const senderPos = query.indexOf(senderTag);
373
+ if (senderPos !== -1) {
374
+ const afterSender = query.slice(senderPos);
375
+ const fenceStart = afterSender.indexOf("```json");
376
+ const fenceEnd = fenceStart >= 0 ? afterSender.indexOf("```\n", fenceStart + 7) : -1;
377
+ if (fenceEnd > 0) {
378
+ query = afterSender.slice(fenceEnd + 4).replace(/^\s*\n/, "").trim();
379
+ } else {
380
+ const firstDblNl = afterSender.indexOf("\n\n");
381
+ if (firstDblNl > 0) {
382
+ query = afterSender.slice(firstDblNl + 2).trim();
383
+ }
384
+ }
385
+ }
386
+ query = stripInboundMetadata(query).replace(/<[^>]+>/g, "").trim();
387
+
388
+ if (query.length < 2) {
389
+ return { messages, estimatedTokens: 0 };
390
+ }
391
+
392
+ ctx.log.debug(`context-engine assemble: query="${query.slice(0, 80)}"`);
393
+
394
+ const recallOwner = [`agent:${currentAgentId}`, "public"];
395
+ const result = await engine.search({ query, maxResults: 10, minScore: 0.45, ownerFilter: recallOwner });
396
+ const filteredHits = ceDeduplicateHits(
397
+ result.hits.filter((h: SearchHit) => h.score >= 0.5),
398
+ );
399
+
400
+ if (filteredHits.length === 0) {
401
+ ctx.log.debug("context-engine assemble: no memory hits");
402
+ return { messages, estimatedTokens: 0 };
403
+ }
404
+
405
+ const memoryBlock = formatMemoryBlock(filteredHits);
406
+ const cloned: CEAgentMessage[] = messages.map((m) => structuredClone(m));
407
+
408
+ let lastAssistantIdx = -1;
409
+ for (let i = cloned.length - 1; i >= 0; i--) {
410
+ if (cloned[i].role === "assistant") {
411
+ lastAssistantIdx = i;
412
+ break;
413
+ }
414
+ }
415
+
416
+ const sk = sessionKey ?? sessionId;
417
+
418
+ if (lastAssistantIdx < 0) {
419
+ const syntheticAssistant: CEAgentMessage = {
420
+ role: "assistant",
421
+ content: [{ type: "text", text: memoryBlock }],
422
+ timestamp: Date.now(),
423
+ stopReason: "end_turn",
424
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 },
425
+ };
426
+ pendingInjection = { sessionKey: sk, memoryBlock, isSynthetic: true };
427
+ ctx.log.info(`context-engine assemble: first turn, injecting synthetic assistant (${filteredHits.length} memories)`);
428
+ return { messages: [...cloned, syntheticAssistant], estimatedTokens: 0 };
429
+ }
430
+
431
+ removeExistingMemoryBlock(cloned[lastAssistantIdx]);
432
+ appendMemoryToMessage(cloned[lastAssistantIdx], memoryBlock);
433
+ pendingInjection = { sessionKey: sk, memoryBlock, isSynthetic: false };
434
+
435
+ const dur = performance.now() - recallT0;
436
+ ctx.log.info(`context-engine assemble: injected ${filteredHits.length} memories into assistant[${lastAssistantIdx}] (${dur.toFixed(0)}ms)`);
437
+ return { messages: cloned, estimatedTokens: 0 };
438
+ } catch (err) {
439
+ ctx.log.warn(`context-engine assemble failed: ${err}`);
440
+ return { messages, estimatedTokens: 0 };
441
+ }
442
+ },
443
+
444
+ async afterTurn() {},
445
+
446
+ async compact(params: any) {
447
+ try {
448
+ const { delegateCompactionToRuntime } = await import("openclaw/plugin-sdk");
449
+ return await delegateCompactionToRuntime(params);
450
+ } catch {
451
+ return { ok: true, compacted: false, reason: "delegateCompactionToRuntime not available" };
452
+ }
453
+ },
454
+
455
+ async maintain(params: {
456
+ sessionId: string;
457
+ sessionKey?: string;
458
+ sessionFile: string;
459
+ runtimeContext?: { rewriteTranscriptEntries?: (req: any) => Promise<any> };
460
+ }) {
461
+ const noChange = { changed: false, bytesFreed: 0, rewrittenEntries: 0 };
462
+
463
+ if (!pendingInjection) return noChange;
464
+
465
+ const sk = params.sessionKey ?? params.sessionId;
466
+ if (pendingInjection.sessionKey !== sk) {
467
+ pendingInjection = null;
468
+ return { ...noChange, reason: "session mismatch" };
469
+ }
470
+
471
+ try {
472
+ if (pendingInjection.isSynthetic) {
473
+ // First turn: INSERT synthetic assistant before existing entries
474
+ const { SessionManager } = await import("@mariozechner/pi-coding-agent");
475
+ const sm = SessionManager.open(params.sessionFile);
476
+ const ok = insertSyntheticAssistantEntry(sm, pendingInjection.memoryBlock);
477
+ pendingInjection = null;
478
+ if (ok) {
479
+ ctx.log.info("context-engine maintain: persisted synthetic assistant message");
480
+ return { changed: true, bytesFreed: 0, rewrittenEntries: 1 };
481
+ }
482
+ return { ...noChange, reason: "empty branch, could not insert synthetic" };
483
+ }
484
+
485
+ // Subsequent turns: REPLACE last assistant entry with memory-injected version
486
+ if (!params.runtimeContext?.rewriteTranscriptEntries) {
487
+ pendingInjection = null;
488
+ return { ...noChange, reason: "rewriteTranscriptEntries not available" };
489
+ }
490
+
491
+ const { SessionManager } = await import("@mariozechner/pi-coding-agent");
492
+ const sm = SessionManager.open(params.sessionFile);
493
+ const branch = sm.getBranch();
494
+ const targetEntry = findTargetAssistantEntry(branch);
495
+
496
+ if (!targetEntry) {
497
+ pendingInjection = null;
498
+ return { ...noChange, reason: "no target assistant entry found" };
499
+ }
500
+
501
+ const modifiedMessage = structuredClone(targetEntry.message!);
502
+ removeExistingMemoryBlock(modifiedMessage as CEAgentMessage);
503
+ appendMemoryToMessage(modifiedMessage as CEAgentMessage, pendingInjection.memoryBlock);
504
+
505
+ const result = await params.runtimeContext.rewriteTranscriptEntries({
506
+ replacements: [{ entryId: targetEntry.id, message: modifiedMessage }],
507
+ });
508
+
509
+ ctx.log.info(`context-engine maintain: persisted memory to assistant entry ${targetEntry.id}`);
510
+ pendingInjection = null;
511
+ return result;
512
+ } catch (err) {
513
+ ctx.log.warn(`context-engine maintain failed: ${err}`);
514
+ pendingInjection = null;
515
+ return { ...noChange, reason: String(err) };
516
+ }
517
+ },
518
+ }));
519
+
520
+ ctx.log.info("memos-local: registered context engine 'memos-local-openclaw-plugin'");
521
+ } catch (err) {
522
+ ctx.log.warn(`memos-local: context engine registration failed (${err}), memory injection will use before_prompt_build fallback`);
523
+ }
524
+
525
+ // ─── Memory Prompt Section: static instructions for the LLM ───
526
+ try {
527
+ api.registerMemoryPromptSection(() => [
528
+ "## Memory System",
529
+ "",
530
+ "Assistant messages in this conversation may contain <relevant-memories> blocks.",
531
+ "These are NOT part of the assistant's original response.",
532
+ "They contain background knowledge and memories relevant to the next user message,",
533
+ "injected by the user's local memory system before each query.",
534
+ "Use them as context to better understand and respond to the following user message.",
535
+ "Do not mention, quote, or repeat these memory blocks in your replies.",
536
+ "",
537
+ ]);
538
+ ctx.log.info("memos-local: registered memory prompt section");
539
+ } catch (err) {
540
+ ctx.log.warn(`memos-local: registerMemoryPromptSection failed: ${err}`);
541
+ }
542
+
323
543
  const trackTool = (toolName: string, fn: (...args: any[]) => Promise<any>) =>
324
544
  async (...args: any[]) => {
325
545
  const t0 = performance.now();
@@ -1401,9 +1621,11 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
1401
1621
  name: "memory_share",
1402
1622
  label: "Share Memory",
1403
1623
  description:
1404
- "Share an existing memory either with local OpenClaw agents, to the Hub team, or to both targets. " +
1405
- "Use this only for an existing chunkId. Use target='agents' for local multi-agent sharing, target='hub' for team sharing, or target='both' for both. " +
1406
- "If you need to create a brand new shared memory instead of exposing an existing one, use memory_write_public.",
1624
+ "Share an existing stored memory (requires a real chunkId from the database) to the Hub team, or to both targets. " +
1625
+ "If you want to share content from the conversation, please first retrieve the memories related to that content to obtain the correct chunkId(s), then proceed with the sharing. " +
1626
+ "target='agents' (default): when retrieved memories would clearly help other agents in the same OpenClaw workspace, you may share proactively without asking the user. " +
1627
+ "target='hub' or 'both': do not share to the team Hub without explicit user consent when the content would benefit collaborators—explain briefly, ask first, and only call hub/both after they agree (Hub must be configured). " +
1628
+ "To create a brand-new shared note with no existing chunk, use memory_write_public.",
1407
1629
  parameters: Type.Object({
1408
1630
  chunkId: Type.String({ description: "Existing local memory chunk ID to share" }),
1409
1631
  target: Type.Optional(Type.String({ description: "Share target: 'agents' (default), 'hub', or 'both'" })),
@@ -1771,7 +1993,9 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
1771
1993
  { name: "network_skill_pull" },
1772
1994
  );
1773
1995
 
1774
- // ─── Auto-recall: inject relevant memories before agent starts ───
1996
+ // ─── Skill auto-recall: inject relevant skills before agent starts ───
1997
+ // Memory injection is handled by the Context Engine above.
1998
+ // This hook only handles skill auto-recall via prependContext.
1775
1999
 
1776
2000
  api.on("before_prompt_build", async (event: { prompt?: string; messages?: unknown[] }, hookCtx?: { agentId?: string; sessionKey?: string }) => {
1777
2001
  if (!allowPromptInjection) return {};
@@ -1779,21 +2003,18 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
1779
2003
 
1780
2004
  const recallAgentId = hookCtx?.agentId ?? "main";
1781
2005
  currentAgentId = recallAgentId;
1782
- const recallOwnerFilter = [`agent:${recallAgentId}`, "public"];
1783
- ctx.log.info(`auto-recall: agentId=${recallAgentId} (from hookCtx)`);
2006
+
2007
+ const skillAutoRecall = ctx.config.skillEvolution?.autoRecallSkills ?? DEFAULTS.skillAutoRecall;
2008
+ if (!skillAutoRecall) return;
1784
2009
 
1785
2010
  const recallT0 = performance.now();
1786
- let recallQuery = "";
1787
2011
 
1788
2012
  try {
1789
- const rawPrompt = event.prompt;
1790
- ctx.log.debug(`auto-recall: rawPrompt="${rawPrompt.slice(0, 300)}"`);
1791
-
1792
- let query = rawPrompt;
2013
+ let query = event.prompt;
1793
2014
  const senderTag = "Sender (untrusted metadata):";
1794
- const senderPos = rawPrompt.indexOf(senderTag);
2015
+ const senderPos = query.indexOf(senderTag);
1795
2016
  if (senderPos !== -1) {
1796
- const afterSender = rawPrompt.slice(senderPos);
2017
+ const afterSender = query.slice(senderPos);
1797
2018
  const fenceStart = afterSender.indexOf("```json");
1798
2019
  const fenceEnd = fenceStart >= 0 ? afterSender.indexOf("```\n", fenceStart + 7) : -1;
1799
2020
  if (fenceEnd > 0) {
@@ -1805,276 +2026,48 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
1805
2026
  }
1806
2027
  }
1807
2028
  }
1808
- query = stripInboundMetadata(query);
1809
- query = query.replace(/<[^>]+>/g, "").trim();
1810
- recallQuery = query;
1811
-
1812
- if (query.length < 2) {
1813
- ctx.log.debug("auto-recall: extracted query too short, skipping");
1814
- return;
1815
- }
1816
- ctx.log.debug(`auto-recall: query="${query.slice(0, 80)}"`);
1817
-
1818
- // ── Phase 1: Local search ∥ Hub search (parallel) ──
1819
- const arLocalP = engine.search({ query, maxResults: 10, minScore: 0.45, ownerFilter: recallOwnerFilter });
1820
- const arHubP = ctx.config?.sharing?.enabled
1821
- ? hubSearchMemories(store, ctx, { query, maxResults: 10, scope: "all" })
1822
- .catch((err: any) => { ctx.log.debug(`auto-recall: hub search failed (${err})`); return { hits: [] as any[], meta: {} }; })
1823
- : Promise.resolve({ hits: [] as any[], meta: {} });
1824
-
1825
- const [result, arHubResult] = await Promise.all([arLocalP, arHubP]);
1826
-
1827
- const localHits = result.hits.filter((h) => h.origin !== "hub-memory");
1828
- const hubLocalHits = result.hits.filter((h) => h.origin === "hub-memory");
1829
- const hubRemoteHits: SearchHit[] = (arHubResult.hits ?? []).map((h: any) => ({
1830
- summary: h.summary,
1831
- original_excerpt: h.excerpt || h.summary,
1832
- ref: { sessionKey: "", chunkId: h.remoteHitId ?? "", turnId: "", seq: 0 },
1833
- score: 0.9,
1834
- taskId: null,
1835
- skillId: null,
1836
- origin: "hub-remote" as const,
1837
- source: { ts: h.source?.ts, role: h.source?.role ?? "assistant", sessionKey: "" },
1838
- ownerName: h.ownerName,
1839
- groupName: h.groupName,
1840
- }));
1841
- const allHubHits = [...hubLocalHits, ...hubRemoteHits];
1842
-
1843
- ctx.log.debug(`auto-recall: local=${localHits.length}, hub-memory=${hubLocalHits.length}, hub-remote=${hubRemoteHits.length}`);
1844
-
1845
- const rawLocalCandidates = localHits.map((h) => ({
1846
- score: h.score, role: h.source.role, summary: h.summary,
1847
- content: (h.original_excerpt ?? "").slice(0, 200), origin: h.origin || "local",
1848
- }));
1849
- const rawHubCandidates = allHubHits.map((h) => ({
1850
- score: h.score, role: h.source.role, summary: h.summary,
1851
- content: (h.original_excerpt ?? "").slice(0, 200), origin: h.origin || "hub-remote",
1852
- ownerName: (h as any).ownerName ?? "", groupName: (h as any).groupName ?? "",
1853
- }));
1854
-
1855
- const allRawHits = [...localHits, ...allHubHits];
1856
-
1857
- if (allRawHits.length === 0) {
1858
- ctx.log.debug("auto-recall: no memory candidates found");
1859
- const dur = performance.now() - recallT0;
1860
- store.recordToolCall("memory_search", dur, true);
1861
- store.recordApiLog("memory_search", { type: "auto_recall", query }, JSON.stringify({
1862
- candidates: rawLocalCandidates, hubCandidates: rawHubCandidates, filtered: [],
1863
- }), dur, true);
1864
-
1865
- const skillAutoRecallEarly = ctx.config.skillEvolution?.autoRecallSkills ?? DEFAULTS.skillAutoRecall;
1866
- if (skillAutoRecallEarly) {
1867
- try {
1868
- const skillLimit = ctx.config.skillEvolution?.autoRecallSkillLimit ?? DEFAULTS.skillAutoRecallLimit;
1869
- const skillHits = await engine.searchSkills(query, "mix" as any, getCurrentOwner());
1870
- const topSkills = skillHits.slice(0, skillLimit);
1871
- if (topSkills.length > 0) {
1872
- const skillLines = topSkills.map((sc, i) => {
1873
- const manifest = skillInstaller.getCompanionManifest(sc.skillId);
1874
- let badge = "";
1875
- if (manifest?.installed) badge = " [installed]";
1876
- else if (manifest?.installMode === "install_recommended") badge = " [has scripts, install recommended]";
1877
- else if (manifest?.hasCompanionFiles) badge = " [has companion files]";
1878
- return `${i + 1}. **${sc.name}**${badge} — ${sc.description.slice(0, 200)}\n → call \`skill_get(skillId="${sc.skillId}")\` for the full guide`;
1879
- });
1880
- const skillContext = "## Relevant skills from past experience\n\n" +
1881
- "No direct memory matches were found, but these skills from past tasks may help:\n\n" +
1882
- skillLines.join("\n\n") +
1883
- "\n\nYou SHOULD call `skill_get` to retrieve the full guide before attempting the task.";
1884
- ctx.log.info(`auto-recall-skill (no-memory path): injecting ${topSkills.length} skill(s)`);
1885
- try { store.recordApiLog("skill_search", { type: "auto_recall_skill", query }, JSON.stringify(topSkills), dur, true); } catch { /* best-effort */ }
1886
- return { prependContext: skillContext };
1887
- }
1888
- } catch (err) {
1889
- ctx.log.debug(`auto-recall-skill (no-memory path): failed: ${err}`);
1890
- }
1891
- }
2029
+ query = stripInboundMetadata(query).replace(/<[^>]+>/g, "").trim();
2030
+ if (query.length < 2) return;
1892
2031
 
1893
- if (query.length > 50) {
1894
- const noRecallHint =
1895
- "## Memory system — ACTION REQUIRED\n\n" +
1896
- "Auto-recall found no results for a long query. " +
1897
- "You MUST call `memory_search` now with a shortened query (2-5 key words) before answering. " +
1898
- "Do NOT skip this step. Do NOT answer without searching first.";
1899
- return { prependContext: noRecallHint };
1900
- }
1901
- return;
1902
- }
2032
+ const skillLimit = ctx.config.skillEvolution?.autoRecallSkillLimit ?? DEFAULTS.skillAutoRecallLimit;
2033
+ const skillCandidateMap = new Map<string, { name: string; description: string; skillId: string; source: string }>();
1903
2034
 
1904
- // ── Phase 2: Merge all → single LLM filter ──
1905
- const mergedForFilter = allRawHits.map((h, i) => ({
1906
- index: i + 1,
1907
- role: h.source.role,
1908
- content: (h.original_excerpt ?? "").slice(0, 300),
1909
- time: h.source.ts ? new Date(h.source.ts).toISOString().slice(0, 16) : "",
1910
- }));
1911
-
1912
- let filteredHits = allRawHits;
1913
- let sufficient = false;
1914
-
1915
- const filterResult = await summarizer.filterRelevant(query, mergedForFilter);
1916
- if (filterResult !== null) {
1917
- sufficient = filterResult.sufficient;
1918
- if (filterResult.relevant.length > 0) {
1919
- const indexSet = new Set(filterResult.relevant);
1920
- filteredHits = allRawHits.filter((_, i) => indexSet.has(i + 1));
1921
- } else {
1922
- const dur = performance.now() - recallT0;
1923
- store.recordToolCall("memory_search", dur, true);
1924
- store.recordApiLog("memory_search", { type: "auto_recall", query }, JSON.stringify({
1925
- candidates: rawLocalCandidates, hubCandidates: rawHubCandidates, filtered: [],
1926
- }), dur, true);
1927
- if (query.length > 50) {
1928
- const noRecallHint =
1929
- "## Memory system — ACTION REQUIRED\n\n" +
1930
- "Auto-recall found no relevant results for a long query. " +
1931
- "You MUST call `memory_search` now with a shortened query (2-5 key words) before answering. " +
1932
- "Do NOT skip this step. Do NOT answer without searching first.";
1933
- return { prependContext: noRecallHint };
2035
+ try {
2036
+ const directSkillHits = await engine.searchSkills(query, "mix" as any, getCurrentOwner());
2037
+ for (const sh of directSkillHits.slice(0, skillLimit + 2)) {
2038
+ if (!skillCandidateMap.has(sh.skillId)) {
2039
+ skillCandidateMap.set(sh.skillId, { name: sh.name, description: sh.description, skillId: sh.skillId, source: "query" });
1934
2040
  }
1935
- return;
1936
2041
  }
2042
+ } catch (err) {
2043
+ ctx.log.debug(`auto-recall-skill: direct search failed: ${err}`);
1937
2044
  }
1938
2045
 
1939
- const beforeDedup = filteredHits.length;
1940
- filteredHits = deduplicateHits(filteredHits);
1941
- ctx.log.debug(`auto-recall: merged ${allRawHits.length} → ${beforeDedup} relevant → ${filteredHits.length} after dedup, sufficient=${sufficient}`);
1942
-
1943
- const lines = filteredHits.map((h, i) => {
1944
- const excerpt = h.original_excerpt;
1945
- const oTag = h.origin === "local-shared" ? " [本机共享]" : h.origin === "hub-memory" ? " [团队缓存]" : "";
1946
- const parts: string[] = [`${i + 1}. [${h.source.role}]${oTag}`];
1947
- if (excerpt) parts.push(` ${excerpt}`);
1948
- parts.push(` chunkId="${h.ref.chunkId}"`);
1949
- if (h.taskId) {
1950
- const task = store.getTask(h.taskId);
1951
- if (task && task.status !== "skipped") {
1952
- parts.push(` task_id="${h.taskId}"`);
1953
- }
1954
- }
1955
- return parts.join("\n");
2046
+ const skillCandidates = [...skillCandidateMap.values()].slice(0, skillLimit);
2047
+ if (skillCandidates.length === 0) return;
2048
+
2049
+ const skillLines = skillCandidates.map((sc, i) => {
2050
+ const manifest = skillInstaller.getCompanionManifest(sc.skillId);
2051
+ let badge = "";
2052
+ if (manifest?.installed) badge = " [installed]";
2053
+ else if (manifest?.installMode === "install_recommended") badge = " [has scripts, install recommended]";
2054
+ else if (manifest?.hasCompanionFiles) badge = " [has companion files]";
2055
+ const action = `call \`skill_get(skillId="${sc.skillId}")\``;
2056
+ return `${i + 1}. **${sc.name}**${badge} — ${sc.description.slice(0, 200)}\n → ${action}`;
1956
2057
  });
2058
+ const skillContext = "## Relevant skills from past experience\n\n" +
2059
+ "The following skills were distilled from similar previous tasks. " +
2060
+ "You SHOULD call `skill_get` to retrieve the full guide before attempting the task.\n\n" +
2061
+ skillLines.join("\n\n");
1957
2062
 
1958
- const hasTask = filteredHits.some((h) => {
1959
- if (!h.taskId) return false;
1960
- const t = store.getTask(h.taskId);
1961
- return t && t.status !== "skipped";
1962
- });
1963
- const tips: string[] = [];
1964
- if (hasTask) {
1965
- tips.push("- A hit has `task_id` → call `task_summary(taskId=\"...\")` to get the full task context (steps, code, results)");
1966
- tips.push("- A task may have a reusable guide → call `skill_get(taskId=\"...\")` to retrieve the experience/skill");
1967
- }
1968
- tips.push("- Need more surrounding dialogue → call `memory_timeline(chunkId=\"...\")` to expand context around a hit");
1969
- const tipsText = "\n\nAvailable follow-up tools:\n" + tips.join("\n");
1970
-
1971
- const contextParts = [
1972
- "## User's conversation history (from memory system)",
1973
- "",
1974
- "IMPORTANT: The following are facts from previous conversations with this user.",
1975
- "You MUST treat these as established knowledge and use them directly when answering.",
1976
- "Do NOT say you don't know or don't have information if the answer is in these memories.",
1977
- "",
1978
- lines.join("\n\n"),
1979
- ];
1980
- if (tipsText) contextParts.push(tipsText);
1981
-
1982
- // ─── Skill auto-recall ───
1983
- const skillAutoRecall = ctx.config.skillEvolution?.autoRecallSkills ?? DEFAULTS.skillAutoRecall;
1984
- const skillLimit = ctx.config.skillEvolution?.autoRecallSkillLimit ?? DEFAULTS.skillAutoRecallLimit;
1985
- let skillSection = "";
1986
-
1987
- if (skillAutoRecall) {
1988
- try {
1989
- const skillCandidateMap = new Map<string, { name: string; description: string; skillId: string; source: string }>();
1990
-
1991
- // Source 1: direct skill search based on user query
1992
- try {
1993
- const directSkillHits = await engine.searchSkills(query, "mix" as any, getCurrentOwner());
1994
- for (const sh of directSkillHits.slice(0, skillLimit + 2)) {
1995
- if (!skillCandidateMap.has(sh.skillId)) {
1996
- skillCandidateMap.set(sh.skillId, { name: sh.name, description: sh.description, skillId: sh.skillId, source: "query" });
1997
- }
1998
- }
1999
- } catch (err) {
2000
- ctx.log.debug(`auto-recall-skill: direct search failed: ${err}`);
2001
- }
2002
-
2003
- // Source 2: skills linked to tasks from memory hits
2004
- const taskIds = new Set<string>();
2005
- for (const h of filteredHits) {
2006
- if (h.taskId) {
2007
- const t = store.getTask(h.taskId);
2008
- if (t && t.status !== "skipped") taskIds.add(h.taskId);
2009
- }
2010
- }
2011
- for (const tid of taskIds) {
2012
- const linked = store.getSkillsByTask(tid);
2013
- for (const rs of linked) {
2014
- if (!skillCandidateMap.has(rs.skill.id)) {
2015
- skillCandidateMap.set(rs.skill.id, { name: rs.skill.name, description: rs.skill.description, skillId: rs.skill.id, source: `task:${tid}` });
2016
- }
2017
- }
2018
- }
2019
-
2020
- const skillCandidates = [...skillCandidateMap.values()].slice(0, skillLimit);
2021
-
2022
- if (skillCandidates.length > 0) {
2023
- const skillLines = skillCandidates.map((sc, i) => {
2024
- const manifest = skillInstaller.getCompanionManifest(sc.skillId);
2025
- let badge = "";
2026
- if (manifest?.installed) badge = " [installed]";
2027
- else if (manifest?.installMode === "install_recommended") badge = " [has scripts, install recommended]";
2028
- else if (manifest?.hasCompanionFiles) badge = " [has companion files]";
2029
- const action = `call \`skill_get(skillId="${sc.skillId}")\``;
2030
- return `${i + 1}. **${sc.name}**${badge} — ${sc.description.slice(0, 200)}\n → ${action}`;
2031
- });
2032
- skillSection = "\n\n## Relevant skills from past experience\n\n" +
2033
- "The following skills were distilled from similar previous tasks. " +
2034
- "You SHOULD call `skill_get` to retrieve the full guide before attempting the task.\n\n" +
2035
- skillLines.join("\n\n");
2036
-
2037
- ctx.log.info(`auto-recall-skill: injecting ${skillCandidates.length} skill(s): ${skillCandidates.map(s => s.name).join(", ")}`);
2038
- try {
2039
- store.recordApiLog("skill_search", { type: "auto_recall_skill", query }, JSON.stringify(skillCandidates), performance.now() - recallT0, true);
2040
- } catch { /* best-effort */ }
2041
- } else {
2042
- ctx.log.debug("auto-recall-skill: no matching skills found");
2043
- }
2044
- } catch (err) {
2045
- ctx.log.debug(`auto-recall-skill: failed: ${err}`);
2046
- }
2047
- }
2048
-
2049
- if (skillSection) contextParts.push(skillSection);
2050
- const context = contextParts.join("\n");
2051
-
2052
- const recallDur = performance.now() - recallT0;
2053
- store.recordToolCall("memory_search", recallDur, true);
2054
- store.recordApiLog("memory_search", { type: "auto_recall", query }, JSON.stringify({
2055
- candidates: rawLocalCandidates,
2056
- hubCandidates: rawHubCandidates,
2057
- filtered: filteredHits.map(h => ({ score: h.score, role: h.source.role, summary: h.summary, content: h.original_excerpt, origin: h.origin || "local" })),
2058
- }), recallDur, true);
2059
- telemetry.trackAutoRecall(filteredHits.length, recallDur);
2060
-
2061
- ctx.log.info(`auto-recall: returning prependContext (${context.length} chars), sufficient=${sufficient}, skills=${skillSection ? "yes" : "no"}`);
2062
-
2063
- if (!sufficient) {
2064
- const searchHint =
2065
- "\n\nIf these memories don't fully answer the question, " +
2066
- "call `memory_search` with a shorter or rephrased query to find more.";
2067
- return { prependContext: context + searchHint };
2068
- }
2063
+ ctx.log.info(`auto-recall-skill: injecting ${skillCandidates.length} skill(s): ${skillCandidates.map(s => s.name).join(", ")}`);
2064
+ try {
2065
+ store.recordApiLog("skill_search", { type: "auto_recall_skill", query }, JSON.stringify(skillCandidates), performance.now() - recallT0, true);
2066
+ } catch { /* best-effort */ }
2069
2067
 
2070
- return {
2071
- prependContext: context,
2072
- };
2068
+ return { prependContext: skillContext };
2073
2069
  } catch (err) {
2074
- const dur = performance.now() - recallT0;
2075
- store.recordToolCall("memory_search", dur, false);
2076
- try { store.recordApiLog("memory_search", { type: "auto_recall", query: recallQuery }, `error: ${String(err)}`, dur, false); } catch (_) { /* best-effort */ }
2077
- ctx.log.warn(`auto-recall failed: ${String(err)}`);
2070
+ ctx.log.warn(`auto-recall-skill failed: ${String(err)}`);
2078
2071
  }
2079
2072
  });
2080
2073
 
@@ -3,7 +3,7 @@
3
3
  "name": "MemOS Local Memory",
4
4
  "description": "Full-write local conversation memory with hybrid search (RRF + MMR + recency), task summarization, skill evolution, and team sharing (Hub-Client). Provides memory_search, memory_get, task_summary, skill_search, task_share, network_skill_pull, network_team_info, memory_viewer for layered retrieval and team collaboration.",
5
5
  "kind": "memory",
6
- "version": "1.0.6-beta.9",
6
+ "version": "1.0.6-beta.11",
7
7
  "skills": [
8
8
  "skill/memos-memory-guide"
9
9
  ],
package/package.json CHANGED
@@ -1,14 +1,12 @@
1
1
  {
2
2
  "name": "@memtensor/memos-local-openclaw-plugin",
3
- "version": "1.0.6-beta.9",
3
+ "version": "1.0.7-beta.1",
4
4
  "description": "MemOS Local memory plugin for OpenClaw — full-write, hybrid-recall, progressive retrieval",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
- "types": "dist/index.d.ts",
8
7
  "files": [
9
8
  "index.ts",
10
9
  "src",
11
- "dist",
12
10
  "skill",
13
11
  "prebuilds",
14
12
  "scripts/native-binding.cjs",
@@ -36,7 +34,7 @@
36
34
  "test:watch": "vitest",
37
35
  "test:accuracy": "tsx scripts/run-accuracy-test.ts",
38
36
  "postinstall": "node scripts/postinstall.cjs",
39
- "prepublishOnly": "npm run build"
37
+ "prepublishOnly": "echo 'Source-only publish — no build needed.'"
40
38
  },
41
39
  "keywords": [
42
40
  "openclaw",
@@ -47,7 +45,7 @@
47
45
  ],
48
46
  "license": "MIT",
49
47
  "engines": {
50
- "node": ">=18.0.0"
48
+ "node": ">=22.0.0"
51
49
  },
52
50
  "dependencies": {
53
51
  "@huggingface/transformers": "^3.8.0",