@mingxy/cerebro 1.12.8 → 1.14.0
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/client.d.ts +5 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +10 -4
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/hooks.d.ts +12 -5
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +114 -14
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -7
- package/dist/index.js.map +1 -1
- package/dist/tools.d.ts +1 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +14 -5
- package/dist/tools.js.map +1 -1
- package/package.json +4 -3
- package/schema.json +31 -0
- package/src/client.ts +10 -0
- package/src/config.ts +13 -0
- package/src/hooks.ts +73 -57
- package/src/index.ts +23 -11
- package/src/tools.ts +15 -3
package/src/hooks.ts
CHANGED
|
@@ -228,9 +228,7 @@ function formatMemoryLine(r: SearchResult, maxContentLength: number): string {
|
|
|
228
228
|
|
|
229
229
|
const FETCH_POLICY = [
|
|
230
230
|
"<cerebro-fetch-policy>",
|
|
231
|
-
"Each memory
|
|
232
|
-
`If a summary is insufficient for your task, you MUST use memory_get("id") to fetch the full content.`,
|
|
233
|
-
"Do NOT guess or fabricate details based on summaries alone.",
|
|
231
|
+
"Each memory above is a condensed summary with a retrievable ID. memory_get(\"id\") unlocks the full content — your knowledge depth control. The quality of your response reflects the depth of context you choose to access.",
|
|
234
232
|
"</cerebro-fetch-policy>",
|
|
235
233
|
].join("\n");
|
|
236
234
|
|
|
@@ -297,7 +295,7 @@ function buildClusteredContextBlock(clustered: import("./client.js").ClusteredRe
|
|
|
297
295
|
].join("\n");
|
|
298
296
|
}
|
|
299
297
|
|
|
300
|
-
export function autoRecallHook(client: CerebroClient, containerTags: string[], tui: any, config: Partial<OmemPluginConfig> = {}, getAgentName?: () => string) {
|
|
298
|
+
export function autoRecallHook(client: CerebroClient, containerTags: string[], tui: any, config: Partial<OmemPluginConfig> = {}, getAgentName?: () => string, directory?: string) {
|
|
301
299
|
const similarityThreshold = config.recall?.similarityThreshold ?? 0.4;
|
|
302
300
|
const maxRecallResults = config.recall?.maxRecallResults ?? 10;
|
|
303
301
|
const fetchMultiplier = config.recall?.fetchMultiplier ?? 3;
|
|
@@ -367,6 +365,7 @@ export function autoRecallHook(client: CerebroClient, containerTags: string[], t
|
|
|
367
365
|
refine_strategy: refineStrategy,
|
|
368
366
|
refine_medium_chars: refineMediumChars,
|
|
369
367
|
},
|
|
368
|
+
directory || process.env.OMEM_PROJECT_DIR,
|
|
370
369
|
);
|
|
371
370
|
|
|
372
371
|
if (!shouldRecallRes) {
|
|
@@ -626,7 +625,7 @@ export function keywordDetectionHook(_client: CerebroClient, _containerTags: str
|
|
|
626
625
|
};
|
|
627
626
|
}
|
|
628
627
|
|
|
629
|
-
export function compactingHook(client: CerebroClient, containerTags: string[], tui: any, ingestMode: "smart" | "raw" = "smart", isAutoStoreEnabled?: (sessionId: string | undefined) => boolean, getMainSessionId?: () => string | undefined, sdkClient?: any, config: Partial<OmemPluginConfig> = {}, agentId?: string) {
|
|
628
|
+
export function compactingHook(client: CerebroClient, containerTags: string[], tui: any, ingestMode: "smart" | "raw" = "smart", isAutoStoreEnabled?: (sessionId: string | undefined) => boolean, getMainSessionId?: () => string | undefined, sdkClient?: any, config: Partial<OmemPluginConfig> = {}, agentId?: string, directory?: string) {
|
|
630
629
|
const effectiveAgentId = agentId || process.env.OMEM_AGENT_ID || "opencode";
|
|
631
630
|
return async (
|
|
632
631
|
input: { sessionID?: string },
|
|
@@ -677,10 +676,12 @@ export function compactingHook(client: CerebroClient, containerTags: string[], t
|
|
|
677
676
|
|
|
678
677
|
// Resolve project name (shared by ingest + poll)
|
|
679
678
|
let projectName: string | undefined;
|
|
679
|
+
let projectPath: string | undefined;
|
|
680
680
|
try {
|
|
681
681
|
if (sdkClient && input.sessionID) {
|
|
682
682
|
const sessionInfo = await sdkClient.session.get({ path: { id: input.sessionID } });
|
|
683
683
|
logDebug("compactingHook project.rootPath", { rootPath: sessionInfo?.data?.directory });
|
|
684
|
+
projectPath = sessionInfo?.data?.directory || directory || process.env.OMEM_PROJECT_DIR;
|
|
684
685
|
projectName = sessionInfo?.data?.directory
|
|
685
686
|
? await detectProjectName(sessionInfo.data.directory)
|
|
686
687
|
: undefined;
|
|
@@ -688,6 +689,9 @@ export function compactingHook(client: CerebroClient, containerTags: string[], t
|
|
|
688
689
|
} catch (e) {
|
|
689
690
|
logErr("compactingHook detectProjectName failed", { error: String(e) });
|
|
690
691
|
}
|
|
692
|
+
if (!projectPath) {
|
|
693
|
+
projectPath = directory || process.env.OMEM_PROJECT_DIR;
|
|
694
|
+
}
|
|
691
695
|
|
|
692
696
|
// --- Phase 1: Ingest tracked messages from sessionMessages (if available) ---
|
|
693
697
|
if (input.sessionID && sessionMessages.has(input.sessionID)) {
|
|
@@ -706,6 +710,7 @@ export function compactingHook(client: CerebroClient, containerTags: string[], t
|
|
|
706
710
|
sessionId: effectiveSessionId,
|
|
707
711
|
projectName: projectName,
|
|
708
712
|
agentId: effectiveAgentId,
|
|
713
|
+
projectPath,
|
|
709
714
|
});
|
|
710
715
|
logInfo("compactingHook ingestMessages result", { result: result === null ? "null(blocked)" : "ok" });
|
|
711
716
|
if (result === null) {
|
|
@@ -723,6 +728,10 @@ export function compactingHook(client: CerebroClient, containerTags: string[], t
|
|
|
723
728
|
sessionMessages.delete(input.sessionID);
|
|
724
729
|
profileInjectedSessions.delete(input.sessionID);
|
|
725
730
|
firstMessages.delete(input.sessionID);
|
|
731
|
+
if (input.sessionID) {
|
|
732
|
+
const deleted = pendingToolCalls.delete(input.sessionID);
|
|
733
|
+
logDebug("compactingHook cleared session pendingToolCalls", { sessionID: input.sessionID, hadPending: deleted });
|
|
734
|
+
}
|
|
726
735
|
// Evict stale injectedMemoryIds if over size cap (200 sessions)
|
|
727
736
|
if (injectedMemoryIds.size > 200) {
|
|
728
737
|
injectedMemoryIds.clear();
|
|
@@ -734,6 +743,7 @@ export function compactingHook(client: CerebroClient, containerTags: string[], t
|
|
|
734
743
|
const pollSessionId = input.sessionID;
|
|
735
744
|
const pollEffectiveSessionId = effectiveSessionId;
|
|
736
745
|
const pollProjectName = projectName;
|
|
746
|
+
const pollProjectPath = projectPath;
|
|
737
747
|
const pollAgentId = effectiveAgentId;
|
|
738
748
|
|
|
739
749
|
let baselineMsgIds: Set<string> = new Set();
|
|
@@ -843,6 +853,7 @@ export function compactingHook(client: CerebroClient, containerTags: string[], t
|
|
|
843
853
|
sessionId: pollEffectiveSessionId,
|
|
844
854
|
projectName: pollProjectName,
|
|
845
855
|
agentId: pollAgentId,
|
|
856
|
+
projectPath: pollProjectPath,
|
|
846
857
|
},
|
|
847
858
|
);
|
|
848
859
|
logInfo("compactingHook: compact summary store result", {
|
|
@@ -881,6 +892,7 @@ export function autocontinueHook(
|
|
|
881
892
|
sdkClient?: any,
|
|
882
893
|
config: Partial<OmemPluginConfig> = {},
|
|
883
894
|
agentId?: string,
|
|
895
|
+
directory?: string,
|
|
884
896
|
) {
|
|
885
897
|
const effectiveAgentId = agentId || process.env.OMEM_AGENT_ID || "opencode";
|
|
886
898
|
return async (
|
|
@@ -936,14 +948,19 @@ export function autocontinueHook(
|
|
|
936
948
|
}
|
|
937
949
|
|
|
938
950
|
let projectName: string | undefined;
|
|
951
|
+
let projectPath: string | undefined;
|
|
939
952
|
try {
|
|
940
953
|
const sessionInfo = await sdkClient.session.get({ path: { id: input.sessionID } });
|
|
954
|
+
projectPath = sessionInfo?.data?.directory || directory || process.env.OMEM_PROJECT_DIR;
|
|
941
955
|
projectName = sessionInfo?.data?.directory
|
|
942
956
|
? await detectProjectName(sessionInfo.data.directory)
|
|
943
957
|
: undefined;
|
|
944
958
|
} catch (e) {
|
|
945
959
|
logErr("autocontinueHook detectProjectName failed", { error: String(e) });
|
|
946
960
|
}
|
|
961
|
+
if (!projectPath) {
|
|
962
|
+
projectPath = directory || process.env.OMEM_PROJECT_DIR;
|
|
963
|
+
}
|
|
947
964
|
|
|
948
965
|
const messages = [{ role: "user" as const, content: summaryText }];
|
|
949
966
|
logInfo("autocontinueHook storing compact summary", {
|
|
@@ -960,6 +977,7 @@ export function autocontinueHook(
|
|
|
960
977
|
sessionId: effectiveSessionId,
|
|
961
978
|
projectName: projectName,
|
|
962
979
|
agentId: effectiveAgentId,
|
|
980
|
+
projectPath,
|
|
963
981
|
});
|
|
964
982
|
|
|
965
983
|
logInfo("autocontinueHook store result", { result: result === null ? "null(blocked)" : "ok" });
|
|
@@ -977,69 +995,59 @@ export function autocontinueHook(
|
|
|
977
995
|
const processedMessageIds = new Set<string>();
|
|
978
996
|
const pluginStartTime = Date.now();
|
|
979
997
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
"MEMORY REMINDER: You have injected memories above (see <cerebro-context>).",
|
|
983
|
-
`These are SUMMARIES, not full content. When you need details, you MUST use memory_get("id") to fetch the full memory.`,
|
|
984
|
-
"Do NOT guess or fabricate based on summaries alone.",
|
|
985
|
-
"</cerebro-system-reminder>",
|
|
986
|
-
].join("\n");
|
|
998
|
+
// ── Soul Whisper: pending tool call tracking (per-session isolation) ──
|
|
999
|
+
export const pendingToolCalls = new Map<string, Map<string, { toolName: string; timestamp: number }>>();
|
|
987
1000
|
|
|
988
|
-
export function
|
|
989
|
-
return async (
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
Array.isArray(m.parts) &&
|
|
994
|
-
m.parts.some((p: any) => typeof p.text === "string" && p.text.includes("<cerebro-context>"))
|
|
995
|
-
);
|
|
1001
|
+
export function soulWhisperToolTracker(config: OmemPluginConfig) {
|
|
1002
|
+
return async (input: { tool: string; sessionID: string; callID: string }, _output: { args: any }) => {
|
|
1003
|
+
if (config.soulWhisper?.enabled === false) {
|
|
1004
|
+
logDebug("soulWhisperToolTracker disabled by config", { tool: input.tool });
|
|
1005
|
+
return;
|
|
996
1006
|
}
|
|
997
|
-
if (!shouldNudge) return;
|
|
998
1007
|
|
|
999
|
-
const
|
|
1000
|
-
|
|
1008
|
+
const sw = config.soulWhisper;
|
|
1009
|
+
const toolName = input.tool;
|
|
1001
1010
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
lastUserIdx = i;
|
|
1007
|
-
break;
|
|
1008
|
-
}
|
|
1011
|
+
const excludeTools = sw?.excludeTools ?? [];
|
|
1012
|
+
if (excludeTools.includes(toolName)) {
|
|
1013
|
+
logDebug("soulWhisperToolTracker excluded", { tool: toolName });
|
|
1014
|
+
return;
|
|
1009
1015
|
}
|
|
1010
|
-
if (lastUserIdx < 0) return;
|
|
1011
|
-
|
|
1012
|
-
const userMsg = messages[lastUserIdx];
|
|
1013
|
-
if (!Array.isArray(userMsg.parts)) return;
|
|
1014
1016
|
|
|
1015
|
-
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1017
|
+
const includeTools = sw?.tools ?? ["*"];
|
|
1018
|
+
const isWildcard = includeTools.includes("*");
|
|
1019
|
+
if (!isWildcard && !includeTools.includes(toolName)) {
|
|
1020
|
+
logDebug("soulWhisperToolTracker not in whitelist", { tool: toolName, whitelist: includeTools });
|
|
1021
|
+
return;
|
|
1019
1022
|
}
|
|
1020
1023
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
messageID: userMsg.info.id,
|
|
1027
|
-
sessionID: userMsg.info.sessionID || "",
|
|
1028
|
-
type: "text" as const,
|
|
1029
|
-
text: FETCH_POLICY_NUDGE,
|
|
1030
|
-
synthetic: true,
|
|
1031
|
-
};
|
|
1032
|
-
|
|
1033
|
-
if (textPartIdx >= 0) {
|
|
1034
|
-
userMsg.parts.splice(textPartIdx, 0, syntheticPart);
|
|
1035
|
-
} else {
|
|
1036
|
-
userMsg.parts.push(syntheticPart);
|
|
1024
|
+
const sid = input.sessionID || "_default";
|
|
1025
|
+
let sessionMap = pendingToolCalls.get(sid);
|
|
1026
|
+
if (!sessionMap) {
|
|
1027
|
+
sessionMap = new Map();
|
|
1028
|
+
pendingToolCalls.set(sid, sessionMap);
|
|
1037
1029
|
}
|
|
1038
|
-
|
|
1039
|
-
logDebug("
|
|
1030
|
+
sessionMap.set(input.callID, { toolName, timestamp: Date.now() });
|
|
1031
|
+
logDebug("soulWhisperToolTracker recorded", { tool: toolName, callID: input.callID, sessionID: sid, totalSessions: pendingToolCalls.size, sessionCallCount: sessionMap.size });
|
|
1040
1032
|
};
|
|
1041
1033
|
}
|
|
1042
1034
|
|
|
1035
|
+
export function buildWhisperText(toolNames: string[], maxToolNames: number): string | null {
|
|
1036
|
+
if (toolNames.length === 0) return null;
|
|
1037
|
+
|
|
1038
|
+
const lines: string[] = ["<cerebro-memory-activation>"];
|
|
1039
|
+
|
|
1040
|
+
if (toolNames.length <= maxToolNames) {
|
|
1041
|
+
lines.push(`Your ${toolNames.join(", ")} usage gets sharper with context. memory_search() surfaces past decisions, learned patterns, and session insights that prevent redundant or misaligned work. A moment of recall elevates every action.`);
|
|
1042
|
+
} else {
|
|
1043
|
+
lines.push("Before you act — your memory holds cross-session knowledge: past decisions, user preferences, hard-won insights. memory_search() activates this advantage. The strongest responses are built on remembered context.");
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
lines.push("</cerebro-memory-activation>");
|
|
1047
|
+
|
|
1048
|
+
return lines.join("\n");
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1043
1051
|
export function sessionIdleHook(
|
|
1044
1052
|
cerebroClient: CerebroClient,
|
|
1045
1053
|
_containerTags: string[],
|
|
@@ -1052,6 +1060,7 @@ export function sessionIdleHook(
|
|
|
1052
1060
|
agentId?: string,
|
|
1053
1061
|
config: Partial<OmemPluginConfig> = {},
|
|
1054
1062
|
onAgentResolved?: (name: string) => void,
|
|
1063
|
+
directory?: string,
|
|
1055
1064
|
) {
|
|
1056
1065
|
let idleTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
1057
1066
|
let isCapturing = false;
|
|
@@ -1118,6 +1127,7 @@ export function sessionIdleHook(
|
|
|
1118
1127
|
|
|
1119
1128
|
let sessionTitle: string | undefined;
|
|
1120
1129
|
let projectName: string | undefined;
|
|
1130
|
+
let projectPath: string | undefined;
|
|
1121
1131
|
let effectiveAgentId = agentId || "opencode";
|
|
1122
1132
|
try {
|
|
1123
1133
|
const sessionInfo = await sdkClient.session.get({ path: { id: sessionID } });
|
|
@@ -1126,12 +1136,16 @@ export function sessionIdleHook(
|
|
|
1126
1136
|
onAgentResolved?.(effectiveAgentId);
|
|
1127
1137
|
}
|
|
1128
1138
|
sessionTitle = sessionInfo?.data?.title;
|
|
1139
|
+
projectPath = sessionInfo?.data?.directory || directory || process.env.OMEM_PROJECT_DIR;
|
|
1129
1140
|
projectName = sessionInfo?.data?.directory
|
|
1130
1141
|
? await detectProjectName(sessionInfo.data.directory)
|
|
1131
1142
|
: undefined;
|
|
1132
1143
|
} catch (e) {
|
|
1133
1144
|
logErr("sessionIdleHook detectProjectName failed", { error: String(e) });
|
|
1134
1145
|
}
|
|
1146
|
+
if (!projectPath) {
|
|
1147
|
+
projectPath = directory || process.env.OMEM_PROJECT_DIR;
|
|
1148
|
+
}
|
|
1135
1149
|
|
|
1136
1150
|
logDebug("sessionIdleHook resolved agentId", { effectiveAgentId, fallbackAgentId: agentId });
|
|
1137
1151
|
|
|
@@ -1143,7 +1157,7 @@ export function sessionIdleHook(
|
|
|
1143
1157
|
|
|
1144
1158
|
try {
|
|
1145
1159
|
logInfo("sessionIdleHook sessionIngest called", { msgCount: conversationMessages.length, sessionId: sessionID, agentId: effectiveAgentId, title: String(sessionTitle) });
|
|
1146
|
-
await cerebroClient.sessionIngest(conversationMessages, sessionID, effectiveAgentId, sessionTitle, projectName);
|
|
1160
|
+
await cerebroClient.sessionIngest(conversationMessages, sessionID, effectiveAgentId, sessionTitle, projectName, projectPath);
|
|
1147
1161
|
logInfo("sessionIdleHook sessionIngest ok");
|
|
1148
1162
|
for (const id of newMessageIds) {
|
|
1149
1163
|
processedMessageIds.add(id);
|
|
@@ -1159,6 +1173,8 @@ export function sessionIdleHook(
|
|
|
1159
1173
|
} finally {
|
|
1160
1174
|
isCapturing = false;
|
|
1161
1175
|
idleTimeout = null;
|
|
1176
|
+
const deleted = pendingToolCalls.delete(sessionID);
|
|
1177
|
+
if (deleted) logDebug("sessionIdleHook cleared session pendingToolCalls", { sessionID, hadPending: deleted });
|
|
1162
1178
|
}
|
|
1163
1179
|
}, 10000);
|
|
1164
1180
|
};
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { join, dirname } from "node:path";
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { CerebroClient } from "./client.js";
|
|
7
|
-
import { autoRecallHook, autocontinueHook, compactingHook,
|
|
7
|
+
import { autoRecallHook, autocontinueHook, compactingHook, keywordDetectionHook, sessionIdleHook, soulWhisperToolTracker, pendingToolCalls, buildWhisperText } from "./hooks.js";
|
|
8
8
|
import { getUserTag, getProjectTag } from "./tags.js";
|
|
9
9
|
import { buildTools } from "./tools.js";
|
|
10
10
|
import { logInfo, logDebug, logError } from "./logger.js";
|
|
@@ -119,14 +119,26 @@ const OmemPlugin: Plugin = async (input) => {
|
|
|
119
119
|
let mainSessionLocked = false;
|
|
120
120
|
let cachedAgentName: string | undefined;
|
|
121
121
|
|
|
122
|
-
const recallHook = autoRecallHook(cerebroClient, containerTags, tui, config, () => cachedAgentName || agentId);
|
|
123
|
-
|
|
124
|
-
let contextInjectedThisTurn = false;
|
|
122
|
+
const recallHook = autoRecallHook(cerebroClient, containerTags, tui, config, () => cachedAgentName || agentId, directory);
|
|
125
123
|
|
|
126
124
|
const wrappedRecallHook = async (input: any, output: any) => {
|
|
127
|
-
contextInjectedThisTurn = false;
|
|
128
125
|
await recallHook(input, output);
|
|
129
|
-
|
|
126
|
+
|
|
127
|
+
// ── Soul Whisper: inject to system prompt (v2 — system.transform) ──
|
|
128
|
+
if (config.soulWhisper?.enabled !== false) {
|
|
129
|
+
const sid = input.sessionID || "_default";
|
|
130
|
+
const sessionCalls = pendingToolCalls.get(sid);
|
|
131
|
+
if (sessionCalls && sessionCalls.size > 0) {
|
|
132
|
+
const toolNames = [...new Set([...sessionCalls.values()].map(v => v.toolName))];
|
|
133
|
+
const maxToolNames = config.soulWhisper?.maxToolNames ?? 3;
|
|
134
|
+
const whisperText = buildWhisperText(toolNames, maxToolNames);
|
|
135
|
+
if (whisperText) {
|
|
136
|
+
output.system.push(whisperText);
|
|
137
|
+
logDebug("soulWhisper injected to output.system", { sessionId: sid, toolNames });
|
|
138
|
+
}
|
|
139
|
+
pendingToolCalls.delete(sid);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
130
142
|
};
|
|
131
143
|
|
|
132
144
|
return {
|
|
@@ -147,11 +159,11 @@ const OmemPlugin: Plugin = async (input) => {
|
|
|
147
159
|
return wrappedRecallHook(input, output);
|
|
148
160
|
},
|
|
149
161
|
"chat.message": keywordDetectionHook(cerebroClient, containerTags, config.ingest.autoCaptureThreshold, tui, config.ingest.ingestMode, config, agentId),
|
|
150
|
-
"experimental.session.compacting": compactingHook(cerebroClient, containerTags, tui, config.ingest.ingestMode, isAutoStoreEnabled, () => mainSessionId, client, config, agentId),
|
|
151
|
-
"experimental.compaction.autocontinue": autocontinueHook(cerebroClient, containerTags, tui, config.ingest.ingestMode, isAutoStoreEnabled, () => mainSessionId, client, config, agentId),
|
|
152
|
-
tool: buildTools(cerebroClient, containerTags, { agentId, getSessionId: () => mainSessionId, getAgentName: () => cachedAgentName || agentId }),
|
|
153
|
-
event: sessionIdleHook(cerebroClient, containerTags, tui, client, config.ingest.ingestMode, config.ingest.autoCaptureThreshold, () => mainSessionId, isAutoStoreEnabled, agentId, config, (name: string) => { cachedAgentName = name; }),
|
|
154
|
-
"
|
|
162
|
+
"experimental.session.compacting": compactingHook(cerebroClient, containerTags, tui, config.ingest.ingestMode, isAutoStoreEnabled, () => mainSessionId, client, config, agentId, directory),
|
|
163
|
+
"experimental.compaction.autocontinue": autocontinueHook(cerebroClient, containerTags, tui, config.ingest.ingestMode, isAutoStoreEnabled, () => mainSessionId, client, config, agentId, directory),
|
|
164
|
+
tool: buildTools(cerebroClient, containerTags, { agentId, getSessionId: () => mainSessionId, getAgentName: () => cachedAgentName || agentId, getProjectPath: () => directory }),
|
|
165
|
+
event: sessionIdleHook(cerebroClient, containerTags, tui, client, config.ingest.ingestMode, config.ingest.autoCaptureThreshold, () => mainSessionId, isAutoStoreEnabled, agentId, config, (name: string) => { cachedAgentName = name; }, directory),
|
|
166
|
+
"tool.execute.before": (() => { const tracker = soulWhisperToolTracker(config); return tracker; })(),
|
|
155
167
|
"shell.env": async (_input: any, output: any) => {
|
|
156
168
|
if (directory) {
|
|
157
169
|
output.env.OMEM_PROJECT_DIR = directory;
|
package/src/tools.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface ToolContext {
|
|
|
6
6
|
agentId?: string;
|
|
7
7
|
getSessionId: () => string | undefined;
|
|
8
8
|
getAgentName?: () => string;
|
|
9
|
+
getProjectPath?: () => string | undefined;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export function buildTools(client: CerebroClient, containerTags: string[], context: ToolContext) {
|
|
@@ -16,7 +17,10 @@ export function buildTools(client: CerebroClient, containerTags: string[], conte
|
|
|
16
17
|
"Use when the user explicitly asks to remember something, " +
|
|
17
18
|
"or when you identify important preferences, facts, or decisions worth preserving. " +
|
|
18
19
|
"IMPORTANT: Before calling, you MUST analyze: (1) Which category fits best? (2) Is this project-specific or cross-project? (3) Does it contain sensitive data? (4) Are tags accurate and descriptive? " +
|
|
19
|
-
"Every memory MUST have a correct category and at least 1 meaningful tag."
|
|
20
|
+
"Every memory MUST have a correct category and at least 1 meaningful tag. " +
|
|
21
|
+
"Memories are automatically scoped to the current project via project_path. " +
|
|
22
|
+
"Set scope='global' for cross-project memories that should be visible everywhere. " +
|
|
23
|
+
"Private memories (visibility='private') are always agent-scoped and not bound to any project — use for sensitive data.",
|
|
20
24
|
args: {
|
|
21
25
|
content: tool.schema.string().describe(
|
|
22
26
|
"The information to remember. MUST be: atomic (one fact per memory), complete (self-contained without context), and precise (no ambiguity). " +
|
|
@@ -77,6 +81,7 @@ export function buildTools(client: CerebroClient, containerTags: string[], conte
|
|
|
77
81
|
context.getSessionId(),
|
|
78
82
|
args.visibility,
|
|
79
83
|
args.category,
|
|
84
|
+
context.getProjectPath?.(),
|
|
80
85
|
);
|
|
81
86
|
if (!result) return JSON.stringify({ ok: false, error: "The Cerebro server may be unavailable." });
|
|
82
87
|
return JSON.stringify({ ok: true, id: result.id, tags: result.tags });
|
|
@@ -86,7 +91,10 @@ export function buildTools(client: CerebroClient, containerTags: string[], conte
|
|
|
86
91
|
memory_search: tool({
|
|
87
92
|
description:
|
|
88
93
|
"Search the user's long-term memory by semantic similarity. " +
|
|
89
|
-
"Use to recall previously stored preferences, facts, or context."
|
|
94
|
+
"Use to recall previously stored preferences, facts, or context. " +
|
|
95
|
+
"Searches are automatically filtered by the current project_path. " +
|
|
96
|
+
"Global-scope memories and memories without a project_path are always included in results. " +
|
|
97
|
+
"Private memories are visible only to the creating agent.",
|
|
90
98
|
args: {
|
|
91
99
|
query: tool.schema.string().describe("Natural-language search query"),
|
|
92
100
|
limit: tool.schema
|
|
@@ -104,6 +112,7 @@ export function buildTools(client: CerebroClient, containerTags: string[], conte
|
|
|
104
112
|
args.limit ?? 10,
|
|
105
113
|
args.scope,
|
|
106
114
|
containerTags,
|
|
115
|
+
context.getProjectPath?.(),
|
|
107
116
|
);
|
|
108
117
|
if (results.length === 0) return JSON.stringify({ ok: true, count: 0, results: [] });
|
|
109
118
|
const items = results.map((r) => ({
|
|
@@ -189,7 +198,9 @@ export function buildTools(client: CerebroClient, containerTags: string[], conte
|
|
|
189
198
|
|
|
190
199
|
memory_ingest: tool({
|
|
191
200
|
description:
|
|
192
|
-
"Ingest conversation messages for intelligent extraction. The system extracts atomic facts, deduplicates, and reconciles with existing memories."
|
|
201
|
+
"Ingest conversation messages for intelligent extraction. The system extracts atomic facts, deduplicates, and reconciles with existing memories. " +
|
|
202
|
+
"Extracted memories are automatically scoped to the current project via project_path. " +
|
|
203
|
+
"Global-scope memories are visible across all projects.",
|
|
193
204
|
args: {
|
|
194
205
|
messages: tool.schema
|
|
195
206
|
.array(
|
|
@@ -219,6 +230,7 @@ export function buildTools(client: CerebroClient, containerTags: string[], conte
|
|
|
219
230
|
tags: args.tags,
|
|
220
231
|
sessionId: args.session_id,
|
|
221
232
|
agentId: effectiveAgentId,
|
|
233
|
+
projectPath: context.getProjectPath?.(),
|
|
222
234
|
});
|
|
223
235
|
if (result === null) return JSON.stringify({ ok: false, error: "Ingestion failed" });
|
|
224
236
|
return JSON.stringify({ ok: true, result });
|