@mingxy/cerebro 1.15.4 → 1.15.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +285 -213
- package/dist/hooks.js.map +1 -1
- package/package.json +1 -1
- package/src/hooks.ts +322 -244
package/dist/hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
|
package/dist/hooks.js
CHANGED
|
@@ -164,6 +164,8 @@ const sessionMessages = new Map();
|
|
|
164
164
|
const profileInjectedSessions = new Map();
|
|
165
165
|
const injectedSessions = new Set();
|
|
166
166
|
const compactingSummaryCooldown = new Map();
|
|
167
|
+
// Per-session async cache for fire-and-forget recall results
|
|
168
|
+
const recallCache = new Map();
|
|
167
169
|
function hashString(str) {
|
|
168
170
|
let hash = 0;
|
|
169
171
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -564,6 +566,28 @@ export function autoRecallHook(client, containerTags, tui, config = {}, getAgent
|
|
|
564
566
|
}
|
|
565
567
|
};
|
|
566
568
|
}
|
|
569
|
+
function buildProfileBlock(profile) {
|
|
570
|
+
const prefs = (profile?.static_facts ?? [])
|
|
571
|
+
.filter((sf) => {
|
|
572
|
+
const t = sf.tags ?? [];
|
|
573
|
+
return t.includes("preferences");
|
|
574
|
+
})
|
|
575
|
+
.map((sf) => sf.l2_content ?? sf.content ?? "")
|
|
576
|
+
.filter(Boolean);
|
|
577
|
+
const profileLines = prefs.length > 0
|
|
578
|
+
? prefs.map((c) => ` · ${c}`).join("\n")
|
|
579
|
+
: " · (preferences queuing, will populate on next refresh)";
|
|
580
|
+
const block = [
|
|
581
|
+
"<cerebro-profile>",
|
|
582
|
+
profileLines,
|
|
583
|
+
"</cerebro-profile>",
|
|
584
|
+
].join("\n");
|
|
585
|
+
const p = profile;
|
|
586
|
+
const dynamicCount = p?.dynamic_context?.length ?? 0;
|
|
587
|
+
const staticCount = p?.static_facts?.length ?? 0;
|
|
588
|
+
const countText = `Dynamic(${dynamicCount}) · Static(${staticCount})`;
|
|
589
|
+
return { block, countText };
|
|
590
|
+
}
|
|
567
591
|
export function memoryInjectionHook(client, containerTags, tui, config = {}, getAgentName, directory) {
|
|
568
592
|
const similarityThreshold = config.recall?.similarityThreshold ?? 0.4;
|
|
569
593
|
const maxRecallResults = config.recall?.maxRecallResults ?? 10;
|
|
@@ -590,43 +614,6 @@ export function memoryInjectionHook(client, containerTags, tui, config = {}, get
|
|
|
590
614
|
logDebug("memoryInjectionHook start", { sessionId: input.sessionID, agentId, policy, isSaveKeyword, similarityThreshold, maxRecallResults });
|
|
591
615
|
const messages = sessionMessages.get(input.sessionID) ?? [];
|
|
592
616
|
const userMessages = messages.filter((m) => m.role === "user");
|
|
593
|
-
// --- Profile Fetch ---
|
|
594
|
-
const profile = await client.getProfile();
|
|
595
|
-
let profileInjected = false;
|
|
596
|
-
let profileCountText = "";
|
|
597
|
-
let profileBlock = "";
|
|
598
|
-
const lastInjected = profileInjectedSessions.get(input.sessionID);
|
|
599
|
-
const ttlExpired = !lastInjected || (Date.now() - lastInjected > 30 * 60 * 1000);
|
|
600
|
-
const profileIsFirstInjection = !lastInjected;
|
|
601
|
-
if (profile && ttlExpired) {
|
|
602
|
-
const prefs = (profile?.static_facts ?? [])
|
|
603
|
-
.filter((sf) => {
|
|
604
|
-
const t = sf.tags ?? [];
|
|
605
|
-
return t.includes("preferences");
|
|
606
|
-
})
|
|
607
|
-
.map((sf) => sf.l2_content ?? sf.content ?? "")
|
|
608
|
-
.filter(Boolean);
|
|
609
|
-
const profileLines = prefs.length > 0
|
|
610
|
-
? prefs.map((c) => ` · ${c}`).join("\n")
|
|
611
|
-
: " · (preferences queuing, will populate on next refresh)";
|
|
612
|
-
profileBlock = [
|
|
613
|
-
"<cerebro-profile>",
|
|
614
|
-
profileLines,
|
|
615
|
-
"</cerebro-profile>",
|
|
616
|
-
].join("\n");
|
|
617
|
-
profileInjected = true;
|
|
618
|
-
profileInjectedSessions.set(input.sessionID, Date.now());
|
|
619
|
-
const p = profile;
|
|
620
|
-
const dynamicCount = p?.dynamic_context?.length ?? 0;
|
|
621
|
-
const staticCount = p?.static_facts?.length ?? 0;
|
|
622
|
-
profileCountText = `Dynamic(${dynamicCount}) · Static(${staticCount})`;
|
|
623
|
-
if (profileIsFirstInjection) {
|
|
624
|
-
logDebug("memoryInjectionHook profile ready (first)", { dynamicCount, staticCount });
|
|
625
|
-
}
|
|
626
|
-
else {
|
|
627
|
-
logDebug("memoryInjectionHook profile ready (TTL)", { dynamicCount, staticCount });
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
617
|
if (userMessages.length === 0) {
|
|
631
618
|
logDebug("memoryInjectionHook skipped: no user messages in session (post-compacting?)", { sessionId: input.sessionID });
|
|
632
619
|
return;
|
|
@@ -634,7 +621,7 @@ export function memoryInjectionHook(client, containerTags, tui, config = {}, get
|
|
|
634
621
|
const rawQuery = userMessages[userMessages.length - 1]?.content || firstMessages.get(input.sessionID) || "";
|
|
635
622
|
const query_text = extractUserRequest(rawQuery);
|
|
636
623
|
if (!query_text) {
|
|
637
|
-
logDebug("memoryInjectionHook filtered system injection
|
|
624
|
+
logDebug("memoryInjectionHook filtered system injection", { rawQueryPrefix: rawQuery.slice(0, 60) });
|
|
638
625
|
return;
|
|
639
626
|
}
|
|
640
627
|
const last_query_text = userMessages.length >= 2 ? userMessages[userMessages.length - 2].content : undefined;
|
|
@@ -645,28 +632,239 @@ export function memoryInjectionHook(client, containerTags, tui, config = {}, get
|
|
|
645
632
|
return stripped.length > 200 ? stripped.slice(0, 200) : stripped;
|
|
646
633
|
})
|
|
647
634
|
: undefined;
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
635
|
+
// ========== Phase A: synchronous path (zero await) ==========
|
|
636
|
+
const cached = recallCache.get(input.sessionID);
|
|
637
|
+
let profileBlock = "";
|
|
638
|
+
let profileInjected = false;
|
|
639
|
+
let profileCountText = "";
|
|
640
|
+
if (cached) {
|
|
641
|
+
// Phase A: 只读 profileBlock,不更新 TTL(TTL 管理完全由 Phase B 负责)
|
|
642
|
+
if (cached.profileBlock) {
|
|
643
|
+
profileBlock = cached.profileBlock;
|
|
644
|
+
profileInjected = true;
|
|
645
|
+
profileCountText = cached.profileData?.countText ?? "";
|
|
646
|
+
}
|
|
647
|
+
const shouldRecallRes = cached.recallResult;
|
|
648
|
+
if (!shouldRecallRes.should_recall) {
|
|
649
|
+
const partsToInject = [];
|
|
650
|
+
if (profileBlock)
|
|
651
|
+
partsToInject.push(profileBlock);
|
|
652
|
+
if (partsToInject.length > 0) {
|
|
653
|
+
const injectText = partsToInject.join("\n\n");
|
|
654
|
+
const contextPart = {
|
|
655
|
+
id: `prt_cerebro-context-${Date.now()}`,
|
|
656
|
+
sessionID: input.sessionID,
|
|
657
|
+
messageID: output.message.id,
|
|
658
|
+
type: "text",
|
|
659
|
+
text: injectText,
|
|
660
|
+
synthetic: true,
|
|
661
|
+
};
|
|
662
|
+
output.parts.unshift(contextPart);
|
|
663
|
+
logDebug("memoryInjectionHook profile injected from cache (no-recall)", { sessionId: input.sessionID });
|
|
664
|
+
}
|
|
665
|
+
injectedSessions.add(input.sessionID);
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
const results = shouldRecallRes.memories ?? [];
|
|
669
|
+
const clustered = shouldRecallRes.clustered;
|
|
670
|
+
const existingIds = injectedMemoryIds.get(input.sessionID) ?? new Set();
|
|
671
|
+
const newResults = results.filter((r) => !existingIds.has(r.memory.id));
|
|
672
|
+
logDebug("memoryInjectionHook dedup (cached)", { totalResults: results.length, existingCount: existingIds.size, newCount: newResults.length });
|
|
673
|
+
if (newResults.length === 0) {
|
|
674
|
+
const partsToInject = [];
|
|
675
|
+
if (profileBlock)
|
|
676
|
+
partsToInject.push(profileBlock);
|
|
677
|
+
if (partsToInject.length > 0) {
|
|
678
|
+
const injectText = partsToInject.join("\n\n");
|
|
679
|
+
const contextPart = {
|
|
680
|
+
id: `prt_cerebro-context-${Date.now()}`,
|
|
681
|
+
sessionID: input.sessionID,
|
|
682
|
+
messageID: output.message.id,
|
|
683
|
+
type: "text",
|
|
684
|
+
text: injectText,
|
|
685
|
+
synthetic: true,
|
|
686
|
+
};
|
|
687
|
+
output.parts.unshift(contextPart);
|
|
688
|
+
logDebug("memoryInjectionHook profile injected from cache (dedup)", { sessionId: input.sessionID });
|
|
689
|
+
}
|
|
690
|
+
injectedSessions.add(input.sessionID);
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
const profileChars = profileInjected ? profileBlock.length : 0;
|
|
694
|
+
const budgetRemaining = maxContentChars - profileChars;
|
|
695
|
+
const itemCount = clustered
|
|
696
|
+
? (clustered.cluster_summaries.length + clustered.standalone_memories.length)
|
|
697
|
+
: newResults.length;
|
|
698
|
+
const dynamicMaxContentLength = itemCount > 0
|
|
699
|
+
? Math.min(maxContentLength, Math.max(MIN_ITEM_CONTENT_CHARS, Math.floor(budgetRemaining / itemCount)))
|
|
700
|
+
: maxContentLength;
|
|
701
|
+
const block = clustered
|
|
702
|
+
? buildClusteredContextBlock(clustered, dynamicMaxContentLength)
|
|
703
|
+
: buildContextBlock(newResults, dynamicMaxContentLength);
|
|
704
|
+
const partsToInject = [];
|
|
705
|
+
if (block)
|
|
706
|
+
partsToInject.push(block);
|
|
707
|
+
if (block)
|
|
708
|
+
partsToInject.push(FETCH_POLICY);
|
|
709
|
+
if (profileBlock)
|
|
710
|
+
partsToInject.push(profileBlock);
|
|
711
|
+
if (isSaveKeyword)
|
|
712
|
+
partsToInject.push(KEYWORD_NUDGE);
|
|
713
|
+
if (partsToInject.length > 0) {
|
|
714
|
+
const injectText = partsToInject.join("\n\n");
|
|
715
|
+
const contextPart = {
|
|
716
|
+
id: `prt_cerebro-context-${Date.now()}`,
|
|
717
|
+
sessionID: input.sessionID,
|
|
718
|
+
messageID: output.message.id,
|
|
719
|
+
type: "text",
|
|
720
|
+
text: injectText,
|
|
721
|
+
synthetic: true,
|
|
722
|
+
};
|
|
723
|
+
output.parts.unshift(contextPart);
|
|
724
|
+
logDebug("memoryInjectionHook block injected from cache", {
|
|
725
|
+
sessionId: input.sessionID,
|
|
726
|
+
injectTextLen: injectText.length,
|
|
727
|
+
blockPreview: block?.slice(0, 200),
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
injectedSessions.add(input.sessionID);
|
|
731
|
+
if (isSaveKeyword) {
|
|
732
|
+
saveKeywordDetectedSessions.delete(input.sessionID);
|
|
733
|
+
}
|
|
734
|
+
const newIds = newResults.map((r) => r.memory.id);
|
|
735
|
+
injectedMemoryIds.set(input.sessionID, new Set([...existingIds, ...newIds]));
|
|
736
|
+
const memDynamic = newResults.filter((r) => r.memory.memory_type === "fact" || r.memory.memory_type === "event").length;
|
|
737
|
+
const memStatic = newResults.filter((r) => r.memory.memory_type === "pinned" || r.memory.memory_type === "preference").length;
|
|
738
|
+
const memOther = newResults.length - memDynamic - memStatic;
|
|
739
|
+
let memCountMsg = "";
|
|
740
|
+
if (memDynamic > 0)
|
|
741
|
+
memCountMsg += `Dynamic(${memDynamic}) `;
|
|
742
|
+
if (memStatic > 0)
|
|
743
|
+
memCountMsg += `Static(${memStatic}) `;
|
|
744
|
+
if (memOther > 0)
|
|
745
|
+
memCountMsg += `Other(${memOther}) `;
|
|
746
|
+
const categories = categorize(newResults);
|
|
747
|
+
const catSummary = Array.from(categories.entries())
|
|
748
|
+
.map(([label, items]) => `${label}(${items.length})`)
|
|
749
|
+
.join(" · ");
|
|
750
|
+
let toastTitle;
|
|
751
|
+
let toastMessage;
|
|
752
|
+
if (clustered) {
|
|
753
|
+
const clusterCount = clustered.cluster_summaries.length;
|
|
754
|
+
const standaloneCount = clustered.standalone_memories.length;
|
|
755
|
+
toastTitle = `🧠 Context Injected · ${clusterCount} 主题簇${standaloneCount > 0 ? ` · ${standaloneCount} 补充` : ""}`;
|
|
756
|
+
toastMessage = profileInjected
|
|
757
|
+
? `Profile: ${profileCountText} · 聚合记忆展示`
|
|
758
|
+
: `聚合记忆展示`;
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
toastTitle = `🧠 Context Injected · ${newResults.length} fragments`;
|
|
762
|
+
toastMessage = profileInjected
|
|
763
|
+
? `Profile: ${profileCountText} · Memories: ${memCountMsg.trim()}${catSummary ? ` · ${catSummary}` : ""}`
|
|
764
|
+
: `${memCountMsg.trim()}${catSummary ? ` · ${catSummary}` : ""}`;
|
|
765
|
+
}
|
|
766
|
+
showToast(tui, toastTitle, toastMessage, "success", toastDelayMs);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
logDebug("memoryInjectionHook cache hit, injection complete", { sessionId: input.sessionID });
|
|
661
770
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
const
|
|
669
|
-
|
|
771
|
+
else {
|
|
772
|
+
logDebug("memoryInjectionHook cache miss, first message in session", { sessionId: input.sessionID });
|
|
773
|
+
}
|
|
774
|
+
// ========== Phase B: fire-and-forget async fetch for NEXT round ==========
|
|
775
|
+
const bgSessionId = input.sessionID;
|
|
776
|
+
const bgQueryText = query_text;
|
|
777
|
+
const bgLastQueryText = last_query_text;
|
|
778
|
+
const bgConversationContext = conversationContext;
|
|
779
|
+
const bgProjectTags = projectTags.length > 0 ? projectTags : undefined;
|
|
780
|
+
const bgDirectory = directory || process.env.OMEM_PROJECT_DIR;
|
|
781
|
+
Promise.allSettled([
|
|
782
|
+
client.getProfile(),
|
|
783
|
+
client.shouldRecall(bgQueryText, bgLastQueryText, bgSessionId, similarityThreshold, maxRecallResults, bgProjectTags, bgConversationContext && bgConversationContext.length > 0 ? bgConversationContext : undefined, {
|
|
784
|
+
fetch_multiplier: fetchMultiplier,
|
|
785
|
+
topk_cap_multiplier: topkCapMultiplier,
|
|
786
|
+
mmr_jaccard_threshold: mmrJaccardThreshold,
|
|
787
|
+
mmr_penalty_factor: mmrPenaltyFactor,
|
|
788
|
+
phase2_multiplier: phase2Multiplier,
|
|
789
|
+
llm_max_eval: llmMaxEval,
|
|
790
|
+
refine_strategy: refineStrategy,
|
|
791
|
+
refine_medium_chars: refineMediumChars,
|
|
792
|
+
}, bgDirectory),
|
|
793
|
+
])
|
|
794
|
+
.then(([profileRes, recallRes]) => {
|
|
795
|
+
if (recallRes.status === 'rejected') {
|
|
796
|
+
logErr("memoryInjectionHook shouldRecall failed", { error: String(recallRes.reason) });
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const profile = profileRes.status === 'fulfilled' ? profileRes.value : null;
|
|
800
|
+
const shouldRecallRes = recallRes.value;
|
|
801
|
+
if (!shouldRecallRes) {
|
|
802
|
+
showToast(tui, "🧠 Cerebro Service Unavailable", "Unable to reach memory API · check connection", "error", toastDelayMs);
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
logDebug("memoryInjectionHook background fetch complete", {
|
|
806
|
+
sessionId: bgSessionId,
|
|
807
|
+
shouldRecall: shouldRecallRes.should_recall,
|
|
808
|
+
confidence: shouldRecallRes.confidence,
|
|
809
|
+
memCount: shouldRecallRes.memories?.length ?? 0,
|
|
810
|
+
});
|
|
811
|
+
if (shouldRecallRes.should_recall && !Array.isArray(shouldRecallRes.memories)) {
|
|
812
|
+
logErr("memoryInjectionHook shouldRecall returned incomplete data", {
|
|
813
|
+
shouldRecall: shouldRecallRes.should_recall,
|
|
814
|
+
hasMemories: !!shouldRecallRes.memories,
|
|
815
|
+
});
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
let bgProfileBlock = "";
|
|
819
|
+
let bgProfileCountText = "";
|
|
820
|
+
let bgProfileInjected = false;
|
|
821
|
+
if (profile) {
|
|
822
|
+
const lastInjected = profileInjectedSessions.get(bgSessionId);
|
|
823
|
+
const ttlExpired = !lastInjected || (Date.now() - lastInjected > 30 * 60 * 1000);
|
|
824
|
+
if (ttlExpired) {
|
|
825
|
+
const built = buildProfileBlock(profile);
|
|
826
|
+
if (built) {
|
|
827
|
+
bgProfileBlock = built.block;
|
|
828
|
+
bgProfileCountText = built.countText;
|
|
829
|
+
bgProfileInjected = true;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
recallCache.set(bgSessionId, {
|
|
834
|
+
profileBlock: bgProfileBlock,
|
|
835
|
+
recallResult: shouldRecallRes,
|
|
836
|
+
profileData: { countText: bgProfileCountText },
|
|
837
|
+
timestamp: Date.now(),
|
|
838
|
+
});
|
|
839
|
+
if (recallCache.size > 50) {
|
|
840
|
+
let oldestKey = null;
|
|
841
|
+
let oldestTime = Infinity;
|
|
842
|
+
for (const [k, v] of recallCache) {
|
|
843
|
+
if (v.timestamp < oldestTime) {
|
|
844
|
+
oldestTime = v.timestamp;
|
|
845
|
+
oldestKey = k;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (oldestKey)
|
|
849
|
+
recallCache.delete(oldestKey);
|
|
850
|
+
}
|
|
851
|
+
if (shouldRecallRes.should_recall) {
|
|
852
|
+
const results = shouldRecallRes.memories ?? [];
|
|
853
|
+
const existingIds = injectedMemoryIds.get(bgSessionId) ?? new Set();
|
|
854
|
+
const newResults = results.filter((r) => !existingIds.has(r.memory.id));
|
|
855
|
+
if (newResults.length > 0) {
|
|
856
|
+
const newIds = newResults.map((r) => r.memory.id);
|
|
857
|
+
injectedMemoryIds.set(bgSessionId, new Set([...existingIds, ...newIds]));
|
|
858
|
+
}
|
|
859
|
+
const storedMemoryIds = shouldRecallRes.memories?.map((r) => r.memory.id) ?? [];
|
|
860
|
+
const storedDiscardedIds = shouldRecallRes.discarded?.map((d) => d.memory_id) ?? [];
|
|
861
|
+
const maxScore = storedMemoryIds.length > 0
|
|
862
|
+
? Math.max(...(shouldRecallRes.memories?.map((r) => r.score) ?? [0]))
|
|
863
|
+
: 0;
|
|
864
|
+
const bgBlock = shouldRecallRes.clustered
|
|
865
|
+
? buildClusteredContextBlock(shouldRecallRes.clustered, maxContentLength)
|
|
866
|
+
: buildContextBlock(newResults, maxContentLength);
|
|
867
|
+
const bgInjectedContent = bgBlock ?? undefined;
|
|
670
868
|
const items = [
|
|
671
869
|
...(shouldRecallRes.memories?.map((r) => ({
|
|
672
870
|
memory_id: r.memory.id,
|
|
@@ -683,169 +881,40 @@ export function memoryInjectionHook(client, containerTags, tui, config = {}, get
|
|
|
683
881
|
is_kept: false,
|
|
684
882
|
})) ?? []),
|
|
685
883
|
];
|
|
686
|
-
|
|
687
|
-
session_id:
|
|
884
|
+
client.createRecallEvent({
|
|
885
|
+
session_id: bgSessionId,
|
|
688
886
|
recall_type: "auto",
|
|
689
|
-
query_text,
|
|
887
|
+
query_text: bgQueryText,
|
|
690
888
|
max_score: maxScore,
|
|
691
889
|
llm_confidence: shouldRecallRes.confidence ?? 0,
|
|
692
|
-
profile_injected:
|
|
693
|
-
kept_count:
|
|
694
|
-
discarded_count:
|
|
695
|
-
injected_count:
|
|
696
|
-
profile_content:
|
|
697
|
-
injected_content:
|
|
890
|
+
profile_injected: bgProfileInjected,
|
|
891
|
+
kept_count: storedMemoryIds.length,
|
|
892
|
+
discarded_count: storedDiscardedIds.length,
|
|
893
|
+
injected_count: newResults.length,
|
|
894
|
+
profile_content: bgProfileInjected && bgProfileBlock ? bgProfileBlock : undefined,
|
|
895
|
+
injected_content: bgInjectedContent,
|
|
698
896
|
items: items.length > 0 ? items : undefined,
|
|
897
|
+
}).catch((e) => {
|
|
898
|
+
logErr("memoryInjectionHook background createRecallEvent failed", { error: String(e) });
|
|
699
899
|
});
|
|
700
|
-
return result?.event_id;
|
|
701
|
-
}
|
|
702
|
-
catch (e) {
|
|
703
|
-
logErr("memoryInjectionHook createRecallEvent failed", { error: String(e) });
|
|
704
|
-
return undefined;
|
|
705
|
-
}
|
|
706
|
-
};
|
|
707
|
-
// --- no-recall path: inject profile only ---
|
|
708
|
-
if (!shouldRecallRes.should_recall) {
|
|
709
|
-
const partsToInject = [];
|
|
710
|
-
if (profileBlock)
|
|
711
|
-
partsToInject.push(profileBlock);
|
|
712
|
-
if (partsToInject.length > 0) {
|
|
713
|
-
const injectText = partsToInject.join("\n\n");
|
|
714
|
-
const contextPart = {
|
|
715
|
-
id: `prt_cerebro-context-${Date.now()}`,
|
|
716
|
-
sessionID: input.sessionID,
|
|
717
|
-
messageID: output.message.id,
|
|
718
|
-
type: "text",
|
|
719
|
-
text: injectText,
|
|
720
|
-
synthetic: true,
|
|
721
|
-
};
|
|
722
|
-
output.parts.unshift(contextPart);
|
|
723
|
-
logDebug("memoryInjectionHook profile injected (no-recall path)", { sessionId: input.sessionID });
|
|
724
900
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
// --- dedup path: inject profile only ---
|
|
738
|
-
if (newResults.length === 0) {
|
|
739
|
-
const partsToInject = [];
|
|
740
|
-
if (profileBlock)
|
|
741
|
-
partsToInject.push(profileBlock);
|
|
742
|
-
if (partsToInject.length > 0) {
|
|
743
|
-
const injectText = partsToInject.join("\n\n");
|
|
744
|
-
const contextPart = {
|
|
745
|
-
id: `prt_cerebro-context-${Date.now()}`,
|
|
746
|
-
sessionID: input.sessionID,
|
|
747
|
-
messageID: output.message.id,
|
|
748
|
-
type: "text",
|
|
749
|
-
text: injectText,
|
|
750
|
-
synthetic: true,
|
|
751
|
-
};
|
|
752
|
-
output.parts.unshift(contextPart);
|
|
753
|
-
logDebug("memoryInjectionHook profile injected (dedup path)", { sessionId: input.sessionID });
|
|
901
|
+
})
|
|
902
|
+
.catch((err) => {
|
|
903
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
904
|
+
logErr("memoryInjectionHook background fetch failed", { error: errMsg });
|
|
905
|
+
if (errMsg.includes("[cerebro]")) {
|
|
906
|
+
const cleanMsg = errMsg.replace(/^\[cerebro\]\s*/, "");
|
|
907
|
+
if (cleanMsg.startsWith("500")) {
|
|
908
|
+
showToast(tui, "🧠 Cerebro Server Error", cleanMsg.substring(0, 200), "error");
|
|
909
|
+
}
|
|
910
|
+
else if (cleanMsg.includes("timed out")) {
|
|
911
|
+
showToast(tui, "🧠 Cerebro Service Timeout", cleanMsg.substring(0, 100), "error");
|
|
912
|
+
}
|
|
754
913
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
showToast(tui, "👨 Profile Injected", `${profileCountText} · all memories already injected`, "success", toastDelayMs);
|
|
914
|
+
else if (errMsg.includes("fetch") || errMsg.includes("network")) {
|
|
915
|
+
showToast(tui, "🧠 Cerebro Service Unavailable", "Network error · check API connection", "error");
|
|
758
916
|
}
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
// --- Token Budget Calculation ---
|
|
762
|
-
const profileChars = profileInjected ? profileBlock.length : 0;
|
|
763
|
-
const budgetRemaining = maxContentChars - profileChars;
|
|
764
|
-
if (budgetRemaining < 0) {
|
|
765
|
-
logDebug("memoryInjectionHook budget overflow", { profileChars, maxContentChars, deficit: -budgetRemaining });
|
|
766
|
-
}
|
|
767
|
-
const itemCount = clustered
|
|
768
|
-
? (clustered.cluster_summaries.length + clustered.standalone_memories.length)
|
|
769
|
-
: newResults.length;
|
|
770
|
-
const dynamicMaxContentLength = itemCount > 0
|
|
771
|
-
? Math.min(maxContentLength, Math.max(MIN_ITEM_CONTENT_CHARS, Math.floor(budgetRemaining / itemCount)))
|
|
772
|
-
: maxContentLength;
|
|
773
|
-
logDebug("memoryInjectionHook budget", {
|
|
774
|
-
maxContentChars, profileChars, budgetRemaining, itemCount,
|
|
775
|
-
configuredMax: maxContentLength, dynamicMax: dynamicMaxContentLength,
|
|
776
917
|
});
|
|
777
|
-
const block = clustered
|
|
778
|
-
? buildClusteredContextBlock(clustered, dynamicMaxContentLength)
|
|
779
|
-
: buildContextBlock(newResults, dynamicMaxContentLength);
|
|
780
|
-
// ★★★ Core change: inject via output.parts.unshift + synthetic:true ★★★
|
|
781
|
-
const partsToInject = [];
|
|
782
|
-
if (profileBlock)
|
|
783
|
-
partsToInject.push(profileBlock);
|
|
784
|
-
if (block)
|
|
785
|
-
partsToInject.push(block);
|
|
786
|
-
if (block)
|
|
787
|
-
partsToInject.push(FETCH_POLICY);
|
|
788
|
-
if (isSaveKeyword)
|
|
789
|
-
partsToInject.push(KEYWORD_NUDGE);
|
|
790
|
-
if (partsToInject.length > 0) {
|
|
791
|
-
const injectText = partsToInject.join("\n\n");
|
|
792
|
-
const contextPart = {
|
|
793
|
-
id: `prt_cerebro-context-${Date.now()}`,
|
|
794
|
-
sessionID: input.sessionID,
|
|
795
|
-
messageID: output.message.id,
|
|
796
|
-
type: "text",
|
|
797
|
-
text: injectText,
|
|
798
|
-
synthetic: true,
|
|
799
|
-
};
|
|
800
|
-
output.parts.unshift(contextPart);
|
|
801
|
-
logDebug("memoryInjectionHook block injected to output.parts", {
|
|
802
|
-
sessionId: input.sessionID,
|
|
803
|
-
injectTextLen: injectText.length,
|
|
804
|
-
blockPreview: block?.slice(0, 200),
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
else {
|
|
808
|
-
logDebug("memoryInjectionHook no content to inject", { sessionId: input.sessionID });
|
|
809
|
-
}
|
|
810
|
-
injectedSessions.add(input.sessionID);
|
|
811
|
-
if (isSaveKeyword) {
|
|
812
|
-
saveKeywordDetectedSessions.delete(input.sessionID);
|
|
813
|
-
}
|
|
814
|
-
const newIds = newResults.map((r) => r.memory.id);
|
|
815
|
-
injectedMemoryIds.set(input.sessionID, new Set([...existingIds, ...newIds]));
|
|
816
|
-
logDebug("memoryInjectionHook injection complete", { newIds: newIds.length, clustered: !!clustered, sessionId: input.sessionID });
|
|
817
|
-
await createEventAndReturn(newResults.length, storedMemoryIds.length, storedDiscardedIds.length, block || undefined);
|
|
818
|
-
const memDynamic = newResults.filter((r) => r.memory.memory_type === "fact" || r.memory.memory_type === "event").length;
|
|
819
|
-
const memStatic = newResults.filter((r) => r.memory.memory_type === "pinned" || r.memory.memory_type === "preference").length;
|
|
820
|
-
const memOther = newResults.length - memDynamic - memStatic;
|
|
821
|
-
let memCountMsg = "";
|
|
822
|
-
if (memDynamic > 0)
|
|
823
|
-
memCountMsg += `Dynamic(${memDynamic}) `;
|
|
824
|
-
if (memStatic > 0)
|
|
825
|
-
memCountMsg += `Static(${memStatic}) `;
|
|
826
|
-
if (memOther > 0)
|
|
827
|
-
memCountMsg += `Other(${memOther}) `;
|
|
828
|
-
const categories = categorize(newResults);
|
|
829
|
-
const catSummary = Array.from(categories.entries())
|
|
830
|
-
.map(([label, items]) => `${label}(${items.length})`)
|
|
831
|
-
.join(" · ");
|
|
832
|
-
let toastTitle;
|
|
833
|
-
let toastMessage;
|
|
834
|
-
if (clustered) {
|
|
835
|
-
const clusterCount = clustered.cluster_summaries.length;
|
|
836
|
-
const standaloneCount = clustered.standalone_memories.length;
|
|
837
|
-
toastTitle = `🧠 Context Injected · ${clusterCount} 主题簇${standaloneCount > 0 ? ` · ${standaloneCount} 补充` : ""}`;
|
|
838
|
-
toastMessage = profileInjected
|
|
839
|
-
? `Profile: ${profileCountText} · 聚合记忆展示`
|
|
840
|
-
: `聚合记忆展示`;
|
|
841
|
-
}
|
|
842
|
-
else {
|
|
843
|
-
toastTitle = `🧠 Context Injected · ${newResults.length} fragments`;
|
|
844
|
-
toastMessage = profileInjected
|
|
845
|
-
? `Profile: ${profileCountText} · Memories: ${memCountMsg.trim()}${catSummary ? ` · ${catSummary}` : ""}`
|
|
846
|
-
: `${memCountMsg.trim()}${catSummary ? ` · ${catSummary}` : ""}`;
|
|
847
|
-
}
|
|
848
|
-
showToast(tui, toastTitle, toastMessage, "success", toastDelayMs);
|
|
849
918
|
}
|
|
850
919
|
catch (err) {
|
|
851
920
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -995,6 +1064,7 @@ export function compactingHook(client, containerTags, tui, ingestMode = "smart",
|
|
|
995
1064
|
if (input.sessionID) {
|
|
996
1065
|
sessionMessages.delete(input.sessionID);
|
|
997
1066
|
profileInjectedSessions.delete(input.sessionID);
|
|
1067
|
+
recallCache.delete(input.sessionID);
|
|
998
1068
|
firstMessages.delete(input.sessionID);
|
|
999
1069
|
}
|
|
1000
1070
|
return;
|
|
@@ -1024,6 +1094,7 @@ export function compactingHook(client, containerTags, tui, ingestMode = "smart",
|
|
|
1024
1094
|
if (isAutoStoreEnabled && !isAutoStoreEnabled(input.sessionID)) {
|
|
1025
1095
|
sessionMessages.delete(input.sessionID);
|
|
1026
1096
|
profileInjectedSessions.delete(input.sessionID);
|
|
1097
|
+
recallCache.delete(input.sessionID);
|
|
1027
1098
|
firstMessages.delete(input.sessionID);
|
|
1028
1099
|
}
|
|
1029
1100
|
else {
|
|
@@ -1057,6 +1128,7 @@ export function compactingHook(client, containerTags, tui, ingestMode = "smart",
|
|
|
1057
1128
|
sessionMessages.delete(input.sessionID);
|
|
1058
1129
|
injectedSessions.delete(input.sessionID);
|
|
1059
1130
|
profileInjectedSessions.delete(input.sessionID);
|
|
1131
|
+
recallCache.delete(input.sessionID);
|
|
1060
1132
|
firstMessages.delete(input.sessionID);
|
|
1061
1133
|
if (input.sessionID) {
|
|
1062
1134
|
const deleted = pendingToolCalls.delete(input.sessionID);
|