@memtensor/memos-local-openclaw-plugin 1.0.6-beta.2 → 1.0.6-beta.4
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.
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +39 -1
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +62 -24
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +187 -248
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/scripts/native-binding.cjs +32 -0
- package/scripts/postinstall.cjs +13 -16
- package/src/viewer/html.ts +39 -1
- package/src/viewer/server.ts +51 -8
package/index.ts
CHANGED
|
@@ -340,25 +340,11 @@ const memosLocalPlugin = {
|
|
|
340
340
|
try {
|
|
341
341
|
let outputText: string;
|
|
342
342
|
const det = result?.details;
|
|
343
|
-
if (det && Array.isArray(det.candidates)) {
|
|
343
|
+
if (det && (Array.isArray(det.candidates) || Array.isArray(det.filtered))) {
|
|
344
344
|
outputText = JSON.stringify({
|
|
345
|
-
candidates: det.candidates,
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
} else if (det && det.local && det.hub) {
|
|
349
|
-
const localHits = det.local?.hits ?? [];
|
|
350
|
-
const hubHits = (det.hub?.hits ?? []).map((h: any) => ({
|
|
351
|
-
score: h.score ?? 0,
|
|
352
|
-
role: h.source?.role ?? h.role ?? "assistant",
|
|
353
|
-
summary: h.summary ?? "",
|
|
354
|
-
original_excerpt: h.excerpt ?? h.summary ?? "",
|
|
355
|
-
origin: "hub-remote",
|
|
356
|
-
ownerName: h.ownerName ?? "",
|
|
357
|
-
groupName: h.groupName ?? "",
|
|
358
|
-
}));
|
|
359
|
-
outputText = JSON.stringify({
|
|
360
|
-
candidates: [...localHits, ...hubHits],
|
|
361
|
-
filtered: [...localHits, ...hubHits],
|
|
345
|
+
candidates: det.candidates ?? [],
|
|
346
|
+
hubCandidates: det.hubCandidates ?? [],
|
|
347
|
+
filtered: det.filtered ?? det.hits ?? [],
|
|
362
348
|
});
|
|
363
349
|
} else {
|
|
364
350
|
outputText = result?.content?.[0]?.text ?? JSON.stringify(result ?? "");
|
|
@@ -505,10 +491,22 @@ const memosLocalPlugin = {
|
|
|
505
491
|
const ownerFilter = [`agent:${agentId}`, "public"];
|
|
506
492
|
const effectiveMaxResults = searchLimit;
|
|
507
493
|
ctx.log.debug(`memory_search query="${query}" maxResults=${effectiveMaxResults} minScore=${minScore ?? 0.45} role=${role ?? "all"} owner=agent:${agentId}`);
|
|
508
|
-
const result = await engine.search({ query, maxResults: effectiveMaxResults, minScore, role, ownerFilter });
|
|
509
|
-
ctx.log.debug(`memory_search raw candidates: ${result.hits.length}`);
|
|
510
494
|
|
|
511
|
-
|
|
495
|
+
// ── Phase 1: Local search ∥ Hub search (parallel) ──
|
|
496
|
+
const localSearchP = engine.search({ query, maxResults: effectiveMaxResults, minScore, role, ownerFilter });
|
|
497
|
+
const hubSearchP = searchScope !== "local"
|
|
498
|
+
? hubSearchMemories(store, ctx, { query, maxResults: searchLimit, scope: searchScope as any, hubAddress, userToken })
|
|
499
|
+
.catch(() => ({ hits: [] as any[], meta: { totalCandidates: 0, searchedGroups: [] as string[], includedPublic: searchScope === "all" } }))
|
|
500
|
+
: Promise.resolve(null);
|
|
501
|
+
|
|
502
|
+
const [result, hubResult] = await Promise.all([localSearchP, hubSearchP]);
|
|
503
|
+
ctx.log.debug(`memory_search raw candidates: local=${result.hits.length}, hub=${hubResult?.hits?.length ?? 0}`);
|
|
504
|
+
|
|
505
|
+
// Split local results: pure-local vs hub-memory (Hub role's hub_memories mixed in by RecallEngine)
|
|
506
|
+
const localHits = result.hits.filter((h) => h.origin !== "hub-memory");
|
|
507
|
+
const hubLocalHits = result.hits.filter((h) => h.origin === "hub-memory");
|
|
508
|
+
|
|
509
|
+
const rawLocalCandidates = localHits.map((h) => ({
|
|
512
510
|
chunkId: h.ref.chunkId,
|
|
513
511
|
role: h.source.role,
|
|
514
512
|
score: h.score,
|
|
@@ -517,208 +515,156 @@ const memosLocalPlugin = {
|
|
|
517
515
|
origin: h.origin || "local",
|
|
518
516
|
}));
|
|
519
517
|
|
|
520
|
-
|
|
518
|
+
// Hub remote candidates (from HTTP call) + hub-memory candidates (from RecallEngine for Hub role)
|
|
519
|
+
const hubRemoteHits = hubResult?.hits ?? [];
|
|
520
|
+
const rawHubCandidates = [
|
|
521
|
+
...hubLocalHits.map((h) => ({
|
|
522
|
+
score: h.score,
|
|
523
|
+
role: h.source.role,
|
|
524
|
+
summary: h.summary,
|
|
525
|
+
original_excerpt: (h.original_excerpt ?? "").slice(0, 200),
|
|
526
|
+
origin: "hub-memory" as const,
|
|
527
|
+
ownerName: "",
|
|
528
|
+
groupName: "",
|
|
529
|
+
})),
|
|
530
|
+
...hubRemoteHits.map((h: any) => ({
|
|
531
|
+
score: h.score ?? 0,
|
|
532
|
+
role: h.source?.role ?? h.role ?? "assistant",
|
|
533
|
+
summary: h.summary ?? "",
|
|
534
|
+
original_excerpt: (h.excerpt ?? h.summary ?? "").slice(0, 200),
|
|
535
|
+
origin: "hub-remote" as const,
|
|
536
|
+
ownerName: h.ownerName ?? "",
|
|
537
|
+
groupName: h.groupName ?? "",
|
|
538
|
+
})),
|
|
539
|
+
];
|
|
540
|
+
|
|
541
|
+
if (localHits.length === 0 && rawHubCandidates.length === 0) {
|
|
521
542
|
return {
|
|
522
543
|
content: [{ type: "text", text: result.meta.note ?? "No relevant memories found." }],
|
|
523
|
-
details: { candidates: [], meta: result.meta },
|
|
544
|
+
details: { candidates: rawLocalCandidates, hubCandidates: [], filtered: [], meta: result.meta },
|
|
524
545
|
};
|
|
525
546
|
}
|
|
526
547
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
content: (h.original_excerpt ?? "").slice(0, 300),
|
|
534
|
-
time: h.source.ts ? new Date(h.source.ts).toISOString().slice(0, 16) : "",
|
|
535
|
-
}));
|
|
536
|
-
|
|
537
|
-
const filterResult = await summarizer.filterRelevant(query, candidates);
|
|
538
|
-
if (filterResult !== null) {
|
|
539
|
-
sufficient = filterResult.sufficient;
|
|
540
|
-
if (filterResult.relevant.length > 0) {
|
|
541
|
-
const indexSet = new Set(filterResult.relevant);
|
|
542
|
-
filteredHits = result.hits.filter((_, i) => indexSet.has(i + 1));
|
|
543
|
-
ctx.log.debug(`memory_search LLM filter: ${result.hits.length} → ${filteredHits.length} hits, sufficient=${sufficient}`);
|
|
544
|
-
} else if (searchScope === "local") {
|
|
545
|
-
return {
|
|
546
|
-
content: [{ type: "text", text: "No relevant memories found for this query." }],
|
|
547
|
-
details: { candidates: rawCandidates, filtered: [], meta: result.meta },
|
|
548
|
-
};
|
|
549
|
-
} else {
|
|
550
|
-
filteredHits = [];
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
const beforeDedup = filteredHits.length;
|
|
555
|
-
filteredHits = deduplicateHits(filteredHits);
|
|
556
|
-
ctx.log.debug(`memory_search dedup: ${beforeDedup} → ${filteredHits.length}`);
|
|
557
|
-
|
|
558
|
-
const localDetailsHits = filteredHits.map((h) => {
|
|
559
|
-
let effectiveTaskId = h.taskId;
|
|
560
|
-
if (effectiveTaskId) {
|
|
561
|
-
const t = store.getTask(effectiveTaskId);
|
|
562
|
-
if (t && t.status === "skipped") effectiveTaskId = null;
|
|
563
|
-
}
|
|
564
|
-
return {
|
|
565
|
-
ref: h.ref,
|
|
566
|
-
chunkId: h.ref.chunkId,
|
|
567
|
-
taskId: effectiveTaskId,
|
|
568
|
-
skillId: h.skillId,
|
|
548
|
+
// ── Phase 2: Merge all candidates → single LLM filter ──
|
|
549
|
+
const allHitsForFilter = [...localHits, ...hubLocalHits];
|
|
550
|
+
const hubRemoteForFilter = hubRemoteHits;
|
|
551
|
+
const mergedCandidates = [
|
|
552
|
+
...allHitsForFilter.map((h, i) => ({
|
|
553
|
+
index: i + 1,
|
|
569
554
|
role: h.source.role,
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
555
|
+
content: (h.original_excerpt ?? "").slice(0, 300),
|
|
556
|
+
time: h.source.ts ? new Date(h.source.ts).toISOString().slice(0, 16) : "",
|
|
557
|
+
})),
|
|
558
|
+
...hubRemoteForFilter.map((h: any, i: number) => ({
|
|
559
|
+
index: allHitsForFilter.length + i + 1,
|
|
560
|
+
role: (h.source?.role || "assistant") as string,
|
|
561
|
+
content: (h.summary || h.excerpt || "").slice(0, 300),
|
|
562
|
+
time: h.source?.ts ? new Date(h.source.ts).toISOString().slice(0, 16) : "",
|
|
563
|
+
})),
|
|
564
|
+
];
|
|
565
|
+
|
|
566
|
+
let filteredLocalHits = allHitsForFilter;
|
|
567
|
+
let filteredHubRemoteHits = hubRemoteForFilter;
|
|
568
|
+
let sufficient = false;
|
|
575
569
|
|
|
576
|
-
if (
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
role: h.source.role,
|
|
590
|
-
content: (h.original_excerpt ?? "").slice(0, 300),
|
|
591
|
-
time: h.source.ts ? new Date(h.source.ts).toISOString().slice(0, 16) : "",
|
|
592
|
-
}));
|
|
593
|
-
const mergedCandidates = [...localCandidatesForMerge, ...hubCandidates];
|
|
594
|
-
const mergedFilter = await summarizer.filterRelevant(query, mergedCandidates);
|
|
595
|
-
if (mergedFilter !== null && mergedFilter.relevant.length > 0) {
|
|
596
|
-
const relevantSet = new Set(mergedFilter.relevant);
|
|
597
|
-
const hubStartIdx = filteredHits.length + 1;
|
|
598
|
-
filteredHits = filteredHits.filter((_, i) => relevantSet.has(i + 1));
|
|
599
|
-
filteredHubHits = hub.hits.filter((_, i) => relevantSet.has(hubStartIdx + i));
|
|
600
|
-
ctx.log.debug(`memory_search LLM filter (merged): local ${localCandidatesForMerge.length}→${filteredHits.length}, hub ${hub.hits.length}→${filteredHubHits.length}`);
|
|
570
|
+
if (mergedCandidates.length > 0) {
|
|
571
|
+
const filterResult = await summarizer.filterRelevant(query, mergedCandidates);
|
|
572
|
+
if (filterResult !== null) {
|
|
573
|
+
sufficient = filterResult.sufficient;
|
|
574
|
+
if (filterResult.relevant.length > 0) {
|
|
575
|
+
const relevantSet = new Set(filterResult.relevant);
|
|
576
|
+
const hubStartIdx = allHitsForFilter.length + 1;
|
|
577
|
+
filteredLocalHits = allHitsForFilter.filter((_, i) => relevantSet.has(i + 1));
|
|
578
|
+
filteredHubRemoteHits = hubRemoteForFilter.filter((_: any, i: number) => relevantSet.has(hubStartIdx + i));
|
|
579
|
+
ctx.log.debug(`memory_search LLM filter: merged ${mergedCandidates.length} → local ${filteredLocalHits.length}, hub ${filteredHubRemoteHits.length}`);
|
|
580
|
+
} else {
|
|
581
|
+
filteredLocalHits = [];
|
|
582
|
+
filteredHubRemoteHits = [];
|
|
601
583
|
}
|
|
602
584
|
}
|
|
603
|
-
|
|
604
|
-
const originLabel = (h: SearchHit) => {
|
|
605
|
-
if (h.origin === "hub-memory") return " [团队缓存]";
|
|
606
|
-
if (h.origin === "local-shared") return " [本机共享]";
|
|
607
|
-
return "";
|
|
608
|
-
};
|
|
609
|
-
const localText = filteredHits.length > 0
|
|
610
|
-
? filteredHits.map((h, i) => {
|
|
611
|
-
const excerpt = h.original_excerpt.length > 220 ? h.original_excerpt.slice(0, 217) + "..." : h.original_excerpt;
|
|
612
|
-
return `${i + 1}. [${h.source.role}]${originLabel(h)} ${excerpt}`;
|
|
613
|
-
}).join("\n")
|
|
614
|
-
: "(none)";
|
|
615
|
-
const hubText = filteredHubHits.length > 0
|
|
616
|
-
? filteredHubHits.map((h, i) => `${i + 1}. [${h.ownerName}] [团队] ${h.summary}${h.groupName ? ` (${h.groupName})` : ""}`).join("\n")
|
|
617
|
-
: "(none)";
|
|
618
|
-
|
|
619
|
-
const localDetailsFiltered = filteredHits.map((h) => {
|
|
620
|
-
let effectiveTaskId = h.taskId;
|
|
621
|
-
if (effectiveTaskId) {
|
|
622
|
-
const t = store.getTask(effectiveTaskId);
|
|
623
|
-
if (t && t.status === "skipped") effectiveTaskId = null;
|
|
624
|
-
}
|
|
625
|
-
return {
|
|
626
|
-
ref: h.ref,
|
|
627
|
-
chunkId: h.ref.chunkId,
|
|
628
|
-
taskId: effectiveTaskId,
|
|
629
|
-
skillId: h.skillId,
|
|
630
|
-
role: h.source.role,
|
|
631
|
-
score: h.score,
|
|
632
|
-
summary: h.summary,
|
|
633
|
-
origin: h.origin,
|
|
634
|
-
};
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
return {
|
|
638
|
-
content: [{
|
|
639
|
-
type: "text",
|
|
640
|
-
text: `Local results:\n${localText}\n\nHub results:\n${hubText}`,
|
|
641
|
-
}],
|
|
642
|
-
details: {
|
|
643
|
-
local: { hits: localDetailsFiltered, meta: result.meta },
|
|
644
|
-
hub: { ...hub, hits: filteredHubHits },
|
|
645
|
-
},
|
|
646
|
-
};
|
|
647
585
|
}
|
|
648
586
|
|
|
649
|
-
|
|
587
|
+
const beforeDedup = filteredLocalHits.length;
|
|
588
|
+
filteredLocalHits = deduplicateHits(filteredLocalHits);
|
|
589
|
+
ctx.log.debug(`memory_search dedup: ${beforeDedup} → ${filteredLocalHits.length}`);
|
|
590
|
+
|
|
591
|
+
if (filteredLocalHits.length === 0 && filteredHubRemoteHits.length === 0) {
|
|
650
592
|
return {
|
|
651
593
|
content: [{ type: "text", text: "No relevant memories found for this query." }],
|
|
652
|
-
details: { candidates:
|
|
594
|
+
details: { candidates: rawLocalCandidates, hubCandidates: rawHubCandidates, filtered: [], meta: result.meta },
|
|
653
595
|
};
|
|
654
596
|
}
|
|
655
597
|
|
|
598
|
+
// ── Phase 3: Build response text ──
|
|
656
599
|
const originTag = (o?: string) => {
|
|
657
600
|
if (o === "local-shared") return " [本机共享]";
|
|
658
601
|
if (o === "hub-memory") return " [团队缓存]";
|
|
659
602
|
if (o === "hub-remote") return " [团队]";
|
|
660
603
|
return "";
|
|
661
604
|
};
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
const
|
|
665
|
-
|
|
605
|
+
|
|
606
|
+
const localLines = filteredLocalHits.map((h, i) => {
|
|
607
|
+
const excerpt = h.original_excerpt.length > 220 ? h.original_excerpt.slice(0, 217) + "..." : h.original_excerpt;
|
|
608
|
+
const parts = [`${i + 1}. [${h.source.role}]${originTag(h.origin)} ${excerpt}`];
|
|
666
609
|
parts.push(` chunkId="${h.ref.chunkId}"`);
|
|
667
610
|
if (h.taskId) {
|
|
668
611
|
const task = store.getTask(h.taskId);
|
|
669
|
-
if (task && task.status !== "skipped") {
|
|
670
|
-
parts.push(` task_id="${h.taskId}"`);
|
|
671
|
-
}
|
|
612
|
+
if (task && task.status !== "skipped") parts.push(` task_id="${h.taskId}"`);
|
|
672
613
|
}
|
|
673
614
|
return parts.join("\n");
|
|
674
615
|
});
|
|
675
616
|
|
|
617
|
+
const hubLines = filteredHubRemoteHits.map((h: any, i: number) =>
|
|
618
|
+
`${i + 1}. [${h.ownerName ?? "team"}] [团队] ${h.summary ?? ""}${h.groupName ? ` (${h.groupName})` : ""}`
|
|
619
|
+
);
|
|
620
|
+
|
|
676
621
|
let tipsText = "";
|
|
677
622
|
if (!sufficient) {
|
|
678
|
-
const hasTask =
|
|
623
|
+
const hasTask = filteredLocalHits.some((h) => {
|
|
679
624
|
if (!h.taskId) return false;
|
|
680
625
|
const t = store.getTask(h.taskId);
|
|
681
626
|
return t && t.status !== "skipped";
|
|
682
627
|
});
|
|
683
|
-
|
|
684
628
|
const tips: string[] = [];
|
|
685
629
|
if (hasTask) {
|
|
686
630
|
tips.push("→ call task_summary(taskId) for full task context");
|
|
687
631
|
tips.push("→ call skill_get(taskId=...) if the task has a proven experience guide");
|
|
688
632
|
}
|
|
689
633
|
tips.push("→ call memory_timeline(chunkId) to expand surrounding conversation");
|
|
690
|
-
|
|
691
|
-
if (tips.length > 0) {
|
|
692
|
-
tipsText = "\n\nThese memories may not be enough. You can fetch more context:\n" + tips.join("\n");
|
|
693
|
-
}
|
|
634
|
+
if (tips.length > 0) tipsText = "\n\nThese memories may not be enough. You can fetch more context:\n" + tips.join("\n");
|
|
694
635
|
}
|
|
695
636
|
|
|
637
|
+
const localText = localLines.length > 0 ? localLines.join("\n\n") : "(none)";
|
|
638
|
+
const hubText = hubLines.length > 0 ? hubLines.join("\n") : "(none)";
|
|
639
|
+
const totalFiltered = filteredLocalHits.length + filteredHubRemoteHits.length;
|
|
640
|
+
const responseText = filteredHubRemoteHits.length > 0
|
|
641
|
+
? `Found ${totalFiltered} relevant memories:\n\nLocal results:\n${localText}\n\nHub results:\n${hubText}${tipsText}`
|
|
642
|
+
: `Found ${totalFiltered} relevant memories:\n\n${localText}${tipsText}`;
|
|
643
|
+
|
|
644
|
+
const filteredDetails = [
|
|
645
|
+
...filteredLocalHits.map((h) => {
|
|
646
|
+
let effectiveTaskId = h.taskId;
|
|
647
|
+
if (effectiveTaskId) { const t = store.getTask(effectiveTaskId); if (t && t.status === "skipped") effectiveTaskId = null; }
|
|
648
|
+
return {
|
|
649
|
+
chunkId: h.ref.chunkId, taskId: effectiveTaskId, skillId: h.skillId,
|
|
650
|
+
role: h.source.role, score: h.score, summary: h.summary,
|
|
651
|
+
original_excerpt: (h.original_excerpt ?? "").slice(0, 200), origin: h.origin || "local",
|
|
652
|
+
};
|
|
653
|
+
}),
|
|
654
|
+
...filteredHubRemoteHits.map((h: any) => ({
|
|
655
|
+
chunkId: "", taskId: null, skillId: null,
|
|
656
|
+
role: h.source?.role ?? h.role ?? "assistant", score: h.score ?? 0,
|
|
657
|
+
summary: h.summary ?? "", original_excerpt: (h.excerpt ?? h.summary ?? "").slice(0, 200),
|
|
658
|
+
origin: "hub-remote", ownerName: h.ownerName ?? "", groupName: h.groupName ?? "",
|
|
659
|
+
})),
|
|
660
|
+
];
|
|
661
|
+
|
|
696
662
|
return {
|
|
697
|
-
content: [
|
|
698
|
-
{
|
|
699
|
-
type: "text",
|
|
700
|
-
text: `Found ${filteredHits.length} relevant memories:\n\n${lines.join("\n\n")}${tipsText}`,
|
|
701
|
-
},
|
|
702
|
-
],
|
|
663
|
+
content: [{ type: "text", text: responseText }],
|
|
703
664
|
details: {
|
|
704
|
-
candidates:
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
if (effectiveTaskId) {
|
|
708
|
-
const t = store.getTask(effectiveTaskId);
|
|
709
|
-
if (t && t.status === "skipped") effectiveTaskId = null;
|
|
710
|
-
}
|
|
711
|
-
return {
|
|
712
|
-
chunkId: h.ref.chunkId,
|
|
713
|
-
taskId: effectiveTaskId,
|
|
714
|
-
skillId: h.skillId,
|
|
715
|
-
role: h.source.role,
|
|
716
|
-
score: h.score,
|
|
717
|
-
summary: h.summary,
|
|
718
|
-
original_excerpt: (h.original_excerpt ?? "").slice(0, 200),
|
|
719
|
-
origin: h.origin || "local",
|
|
720
|
-
};
|
|
721
|
-
}),
|
|
665
|
+
candidates: rawLocalCandidates,
|
|
666
|
+
hubCandidates: rawHubCandidates,
|
|
667
|
+
filtered: filteredDetails,
|
|
722
668
|
meta: result.meta,
|
|
723
669
|
},
|
|
724
670
|
};
|
|
@@ -1869,46 +1815,53 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1869
1815
|
}
|
|
1870
1816
|
ctx.log.debug(`auto-recall: query="${query.slice(0, 80)}"`);
|
|
1871
1817
|
|
|
1872
|
-
|
|
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];
|
|
1873
1842
|
|
|
1874
|
-
|
|
1875
|
-
const hubFallback = async (): Promise<SearchHit[]> => {
|
|
1876
|
-
if (!ctx.config?.sharing?.enabled) return [];
|
|
1877
|
-
try {
|
|
1878
|
-
const hubResult = await hubSearchMemories(store, ctx, { query, maxResults: 10, scope: "all" });
|
|
1879
|
-
if (hubResult.hits.length === 0) return [];
|
|
1880
|
-
ctx.log.debug(`auto-recall: hub fallback returned ${hubResult.hits.length} hit(s)`);
|
|
1881
|
-
return hubResult.hits.map((h) => ({
|
|
1882
|
-
summary: h.summary,
|
|
1883
|
-
original_excerpt: h.excerpt || h.summary,
|
|
1884
|
-
ref: { sessionKey: "", chunkId: h.remoteHitId, turnId: "", seq: 0 },
|
|
1885
|
-
score: 0.9,
|
|
1886
|
-
taskId: null,
|
|
1887
|
-
skillId: null,
|
|
1888
|
-
origin: "hub-remote" as const,
|
|
1889
|
-
source: { ts: h.source.ts, role: h.source.role, sessionKey: "" },
|
|
1890
|
-
}));
|
|
1891
|
-
} catch (err) {
|
|
1892
|
-
ctx.log.debug(`auto-recall: hub fallback failed (${err})`);
|
|
1893
|
-
return [];
|
|
1894
|
-
}
|
|
1895
|
-
};
|
|
1843
|
+
ctx.log.debug(`auto-recall: local=${localHits.length}, hub-memory=${hubLocalHits.length}, hub-remote=${hubRemoteHits.length}`);
|
|
1896
1844
|
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
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) {
|
|
1906
1858
|
ctx.log.debug("auto-recall: no memory candidates found");
|
|
1907
1859
|
const dur = performance.now() - recallT0;
|
|
1908
1860
|
store.recordToolCall("memory_search", dur, true);
|
|
1909
|
-
store.recordApiLog("memory_search", { type: "auto_recall", query }, JSON.stringify({
|
|
1861
|
+
store.recordApiLog("memory_search", { type: "auto_recall", query }, JSON.stringify({
|
|
1862
|
+
candidates: rawLocalCandidates, hubCandidates: rawHubCandidates, filtered: [],
|
|
1863
|
+
}), dur, true);
|
|
1910
1864
|
|
|
1911
|
-
// Even without memory hits, try skill recall
|
|
1912
1865
|
const skillAutoRecallEarly = ctx.config.skillEvolution?.autoRecallSkills ?? DEFAULTS.skillAutoRecall;
|
|
1913
1866
|
if (skillAutoRecallEarly) {
|
|
1914
1867
|
try {
|
|
@@ -1948,59 +1901,44 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
1948
1901
|
return;
|
|
1949
1902
|
}
|
|
1950
1903
|
|
|
1951
|
-
|
|
1904
|
+
// ── Phase 2: Merge all → single LLM filter ──
|
|
1905
|
+
const mergedForFilter = allRawHits.map((h, i) => ({
|
|
1952
1906
|
index: i + 1,
|
|
1953
1907
|
role: h.source.role,
|
|
1954
1908
|
content: (h.original_excerpt ?? "").slice(0, 300),
|
|
1955
1909
|
time: h.source.ts ? new Date(h.source.ts).toISOString().slice(0, 16) : "",
|
|
1956
1910
|
}));
|
|
1957
1911
|
|
|
1958
|
-
let filteredHits =
|
|
1912
|
+
let filteredHits = allRawHits;
|
|
1959
1913
|
let sufficient = false;
|
|
1960
1914
|
|
|
1961
|
-
const filterResult = await summarizer.filterRelevant(query,
|
|
1915
|
+
const filterResult = await summarizer.filterRelevant(query, mergedForFilter);
|
|
1962
1916
|
if (filterResult !== null) {
|
|
1963
1917
|
sufficient = filterResult.sufficient;
|
|
1964
1918
|
if (filterResult.relevant.length > 0) {
|
|
1965
1919
|
const indexSet = new Set(filterResult.relevant);
|
|
1966
|
-
filteredHits =
|
|
1920
|
+
filteredHits = allRawHits.filter((_, i) => indexSet.has(i + 1));
|
|
1967
1921
|
} else {
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
const
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
if (query.length > 50) {
|
|
1981
|
-
const noRecallHint =
|
|
1982
|
-
"## Memory system — ACTION REQUIRED\n\n" +
|
|
1983
|
-
"Auto-recall found no relevant results for a long query. " +
|
|
1984
|
-
"You MUST call `memory_search` now with a shortened query (2-5 key words) before answering. " +
|
|
1985
|
-
"Do NOT skip this step. Do NOT answer without searching first.";
|
|
1986
|
-
return { prependContext: noRecallHint };
|
|
1987
|
-
}
|
|
1988
|
-
return;
|
|
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 };
|
|
1989
1934
|
}
|
|
1990
|
-
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
if (!sufficient && filteredHits.length > 0 && ctx.config?.sharing?.enabled) {
|
|
1994
|
-
const hubSupp = await hubFallback();
|
|
1995
|
-
if (hubSupp.length > 0) {
|
|
1996
|
-
ctx.log.debug(`auto-recall: local insufficient, supplementing with ${hubSupp.length} hub hit(s)`);
|
|
1997
|
-
filteredHits.push(...hubSupp);
|
|
1935
|
+
return;
|
|
1998
1936
|
}
|
|
1999
1937
|
}
|
|
2000
1938
|
|
|
2001
1939
|
const beforeDedup = filteredHits.length;
|
|
2002
1940
|
filteredHits = deduplicateHits(filteredHits);
|
|
2003
|
-
ctx.log.debug(`auto-recall: ${
|
|
1941
|
+
ctx.log.debug(`auto-recall: merged ${allRawHits.length} → ${beforeDedup} relevant → ${filteredHits.length} after dedup, sufficient=${sufficient}`);
|
|
2004
1942
|
|
|
2005
1943
|
const lines = filteredHits.map((h, i) => {
|
|
2006
1944
|
const excerpt = h.original_excerpt;
|
|
@@ -2114,8 +2052,9 @@ Groups: ${groupNames.length > 0 ? groupNames.join(", ") : "(none)"}`,
|
|
|
2114
2052
|
const recallDur = performance.now() - recallT0;
|
|
2115
2053
|
store.recordToolCall("memory_search", recallDur, true);
|
|
2116
2054
|
store.recordApiLog("memory_search", { type: "auto_recall", query }, JSON.stringify({
|
|
2117
|
-
candidates:
|
|
2118
|
-
|
|
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" })),
|
|
2119
2058
|
}), recallDur, true);
|
|
2120
2059
|
telemetry.trackAutoRecall(filteredHits.length, recallDur);
|
|
2121
2060
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -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": "0.
|
|
6
|
+
"version": "1.0.6-beta.4",
|
|
7
7
|
"skills": [
|
|
8
8
|
"skill/memos-memory-guide"
|
|
9
9
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memtensor/memos-local-openclaw-plugin",
|
|
3
|
-
"version": "1.0.6-beta.
|
|
3
|
+
"version": "1.0.6-beta.4",
|
|
4
4
|
"description": "MemOS Local memory plugin for OpenClaw — full-write, hybrid-recall, progressive retrieval",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"dist",
|
|
12
12
|
"skill",
|
|
13
13
|
"prebuilds",
|
|
14
|
+
"scripts/native-binding.cjs",
|
|
14
15
|
"scripts/postinstall.cjs",
|
|
15
16
|
"openclaw.plugin.json",
|
|
16
17
|
"telemetry.credentials.json",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function errorMessage(error) {
|
|
4
|
+
if (error && typeof error.message === "string") return error.message;
|
|
5
|
+
return String(error || "Unknown native binding error");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function defaultLoadBinding(bindingPath) {
|
|
9
|
+
process.dlopen({ exports: {} }, bindingPath);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function validateNativeBinding(bindingPath, loadBinding = defaultLoadBinding) {
|
|
13
|
+
if (!bindingPath) {
|
|
14
|
+
return { ok: false, reason: "missing", message: "Native binding path not found" };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
loadBinding(bindingPath);
|
|
19
|
+
return { ok: true, reason: "ok", message: "" };
|
|
20
|
+
} catch (error) {
|
|
21
|
+
const message = errorMessage(error);
|
|
22
|
+
if (/NODE_MODULE_VERSION/.test(message)) {
|
|
23
|
+
return { ok: false, reason: "node-module-version", message };
|
|
24
|
+
}
|
|
25
|
+
return { ok: false, reason: "load-error", message };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
defaultLoadBinding,
|
|
31
|
+
validateNativeBinding,
|
|
32
|
+
};
|