@mingxy/cerebro 1.12.8 → 1.13.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 +123 -11
- package/src/index.ts +8 -7
- package/src/tools.ts +15 -3
package/src/hooks.ts
CHANGED
|
@@ -297,7 +297,7 @@ function buildClusteredContextBlock(clustered: import("./client.js").ClusteredRe
|
|
|
297
297
|
].join("\n");
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
export function autoRecallHook(client: CerebroClient, containerTags: string[], tui: any, config: Partial<OmemPluginConfig> = {}, getAgentName?: () => string) {
|
|
300
|
+
export function autoRecallHook(client: CerebroClient, containerTags: string[], tui: any, config: Partial<OmemPluginConfig> = {}, getAgentName?: () => string, directory?: string) {
|
|
301
301
|
const similarityThreshold = config.recall?.similarityThreshold ?? 0.4;
|
|
302
302
|
const maxRecallResults = config.recall?.maxRecallResults ?? 10;
|
|
303
303
|
const fetchMultiplier = config.recall?.fetchMultiplier ?? 3;
|
|
@@ -367,6 +367,7 @@ export function autoRecallHook(client: CerebroClient, containerTags: string[], t
|
|
|
367
367
|
refine_strategy: refineStrategy,
|
|
368
368
|
refine_medium_chars: refineMediumChars,
|
|
369
369
|
},
|
|
370
|
+
directory || process.env.OMEM_PROJECT_DIR,
|
|
370
371
|
);
|
|
371
372
|
|
|
372
373
|
if (!shouldRecallRes) {
|
|
@@ -626,7 +627,7 @@ export function keywordDetectionHook(_client: CerebroClient, _containerTags: str
|
|
|
626
627
|
};
|
|
627
628
|
}
|
|
628
629
|
|
|
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) {
|
|
630
|
+
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
631
|
const effectiveAgentId = agentId || process.env.OMEM_AGENT_ID || "opencode";
|
|
631
632
|
return async (
|
|
632
633
|
input: { sessionID?: string },
|
|
@@ -677,10 +678,12 @@ export function compactingHook(client: CerebroClient, containerTags: string[], t
|
|
|
677
678
|
|
|
678
679
|
// Resolve project name (shared by ingest + poll)
|
|
679
680
|
let projectName: string | undefined;
|
|
681
|
+
let projectPath: string | undefined;
|
|
680
682
|
try {
|
|
681
683
|
if (sdkClient && input.sessionID) {
|
|
682
684
|
const sessionInfo = await sdkClient.session.get({ path: { id: input.sessionID } });
|
|
683
685
|
logDebug("compactingHook project.rootPath", { rootPath: sessionInfo?.data?.directory });
|
|
686
|
+
projectPath = sessionInfo?.data?.directory || directory || process.env.OMEM_PROJECT_DIR;
|
|
684
687
|
projectName = sessionInfo?.data?.directory
|
|
685
688
|
? await detectProjectName(sessionInfo.data.directory)
|
|
686
689
|
: undefined;
|
|
@@ -688,6 +691,9 @@ export function compactingHook(client: CerebroClient, containerTags: string[], t
|
|
|
688
691
|
} catch (e) {
|
|
689
692
|
logErr("compactingHook detectProjectName failed", { error: String(e) });
|
|
690
693
|
}
|
|
694
|
+
if (!projectPath) {
|
|
695
|
+
projectPath = directory || process.env.OMEM_PROJECT_DIR;
|
|
696
|
+
}
|
|
691
697
|
|
|
692
698
|
// --- Phase 1: Ingest tracked messages from sessionMessages (if available) ---
|
|
693
699
|
if (input.sessionID && sessionMessages.has(input.sessionID)) {
|
|
@@ -706,6 +712,7 @@ export function compactingHook(client: CerebroClient, containerTags: string[], t
|
|
|
706
712
|
sessionId: effectiveSessionId,
|
|
707
713
|
projectName: projectName,
|
|
708
714
|
agentId: effectiveAgentId,
|
|
715
|
+
projectPath,
|
|
709
716
|
});
|
|
710
717
|
logInfo("compactingHook ingestMessages result", { result: result === null ? "null(blocked)" : "ok" });
|
|
711
718
|
if (result === null) {
|
|
@@ -723,6 +730,10 @@ export function compactingHook(client: CerebroClient, containerTags: string[], t
|
|
|
723
730
|
sessionMessages.delete(input.sessionID);
|
|
724
731
|
profileInjectedSessions.delete(input.sessionID);
|
|
725
732
|
firstMessages.delete(input.sessionID);
|
|
733
|
+
if (input.sessionID) {
|
|
734
|
+
const deleted = pendingToolCalls.delete(input.sessionID);
|
|
735
|
+
logDebug("compactingHook cleared session pendingToolCalls", { sessionID: input.sessionID, hadPending: deleted });
|
|
736
|
+
}
|
|
726
737
|
// Evict stale injectedMemoryIds if over size cap (200 sessions)
|
|
727
738
|
if (injectedMemoryIds.size > 200) {
|
|
728
739
|
injectedMemoryIds.clear();
|
|
@@ -734,6 +745,7 @@ export function compactingHook(client: CerebroClient, containerTags: string[], t
|
|
|
734
745
|
const pollSessionId = input.sessionID;
|
|
735
746
|
const pollEffectiveSessionId = effectiveSessionId;
|
|
736
747
|
const pollProjectName = projectName;
|
|
748
|
+
const pollProjectPath = projectPath;
|
|
737
749
|
const pollAgentId = effectiveAgentId;
|
|
738
750
|
|
|
739
751
|
let baselineMsgIds: Set<string> = new Set();
|
|
@@ -843,6 +855,7 @@ export function compactingHook(client: CerebroClient, containerTags: string[], t
|
|
|
843
855
|
sessionId: pollEffectiveSessionId,
|
|
844
856
|
projectName: pollProjectName,
|
|
845
857
|
agentId: pollAgentId,
|
|
858
|
+
projectPath: pollProjectPath,
|
|
846
859
|
},
|
|
847
860
|
);
|
|
848
861
|
logInfo("compactingHook: compact summary store result", {
|
|
@@ -881,6 +894,7 @@ export function autocontinueHook(
|
|
|
881
894
|
sdkClient?: any,
|
|
882
895
|
config: Partial<OmemPluginConfig> = {},
|
|
883
896
|
agentId?: string,
|
|
897
|
+
directory?: string,
|
|
884
898
|
) {
|
|
885
899
|
const effectiveAgentId = agentId || process.env.OMEM_AGENT_ID || "opencode";
|
|
886
900
|
return async (
|
|
@@ -936,14 +950,19 @@ export function autocontinueHook(
|
|
|
936
950
|
}
|
|
937
951
|
|
|
938
952
|
let projectName: string | undefined;
|
|
953
|
+
let projectPath: string | undefined;
|
|
939
954
|
try {
|
|
940
955
|
const sessionInfo = await sdkClient.session.get({ path: { id: input.sessionID } });
|
|
956
|
+
projectPath = sessionInfo?.data?.directory || directory || process.env.OMEM_PROJECT_DIR;
|
|
941
957
|
projectName = sessionInfo?.data?.directory
|
|
942
958
|
? await detectProjectName(sessionInfo.data.directory)
|
|
943
959
|
: undefined;
|
|
944
960
|
} catch (e) {
|
|
945
961
|
logErr("autocontinueHook detectProjectName failed", { error: String(e) });
|
|
946
962
|
}
|
|
963
|
+
if (!projectPath) {
|
|
964
|
+
projectPath = directory || process.env.OMEM_PROJECT_DIR;
|
|
965
|
+
}
|
|
947
966
|
|
|
948
967
|
const messages = [{ role: "user" as const, content: summaryText }];
|
|
949
968
|
logInfo("autocontinueHook storing compact summary", {
|
|
@@ -960,6 +979,7 @@ export function autocontinueHook(
|
|
|
960
979
|
sessionId: effectiveSessionId,
|
|
961
980
|
projectName: projectName,
|
|
962
981
|
agentId: effectiveAgentId,
|
|
982
|
+
projectPath,
|
|
963
983
|
});
|
|
964
984
|
|
|
965
985
|
logInfo("autocontinueHook store result", { result: result === null ? "null(blocked)" : "ok" });
|
|
@@ -977,6 +997,69 @@ export function autocontinueHook(
|
|
|
977
997
|
const processedMessageIds = new Set<string>();
|
|
978
998
|
const pluginStartTime = Date.now();
|
|
979
999
|
|
|
1000
|
+
// ── Soul Whisper: pending tool call tracking (per-session isolation) ──
|
|
1001
|
+
const pendingToolCalls = new Map<string, Map<string, { toolName: string; timestamp: number }>>();
|
|
1002
|
+
|
|
1003
|
+
export function soulWhisperToolTracker(config: OmemPluginConfig) {
|
|
1004
|
+
return async (input: { tool: string; sessionID: string; callID: string }, _output: { args: any }) => {
|
|
1005
|
+
if (config.soulWhisper?.enabled === false) {
|
|
1006
|
+
logDebug("soulWhisperToolTracker disabled by config", { tool: input.tool });
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const sw = config.soulWhisper;
|
|
1011
|
+
const toolName = input.tool;
|
|
1012
|
+
|
|
1013
|
+
const excludeTools = sw?.excludeTools ?? [];
|
|
1014
|
+
if (excludeTools.includes(toolName)) {
|
|
1015
|
+
logDebug("soulWhisperToolTracker excluded", { tool: toolName });
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const includeTools = sw?.tools ?? ["*"];
|
|
1020
|
+
const isWildcard = includeTools.includes("*");
|
|
1021
|
+
if (!isWildcard && !includeTools.includes(toolName)) {
|
|
1022
|
+
logDebug("soulWhisperToolTracker not in whitelist", { tool: toolName, whitelist: includeTools });
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const sid = input.sessionID || "_default";
|
|
1027
|
+
let sessionMap = pendingToolCalls.get(sid);
|
|
1028
|
+
if (!sessionMap) {
|
|
1029
|
+
sessionMap = new Map();
|
|
1030
|
+
pendingToolCalls.set(sid, sessionMap);
|
|
1031
|
+
}
|
|
1032
|
+
sessionMap.set(input.callID, { toolName, timestamp: Date.now() });
|
|
1033
|
+
logDebug("soulWhisperToolTracker recorded", { tool: toolName, callID: input.callID, sessionID: sid, totalSessions: pendingToolCalls.size, sessionCallCount: sessionMap.size });
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function buildWhisperText(toolNames: string[], maxToolNames: number): string | null {
|
|
1038
|
+
if (toolNames.length === 0) return null;
|
|
1039
|
+
|
|
1040
|
+
const lines: string[] = ["<cerebro-system-reminder>"];
|
|
1041
|
+
|
|
1042
|
+
if (toolNames.length <= maxToolNames) {
|
|
1043
|
+
lines.push(`SOUL WHISPER — Detected tool call: ${toolNames.join(", ")}.`);
|
|
1044
|
+
lines.push("Your memories (cerebro-context) may contain:");
|
|
1045
|
+
lines.push("- Exact commands or flags someone already tested");
|
|
1046
|
+
lines.push("- File paths or configurations that worked before");
|
|
1047
|
+
lines.push("- Solutions to errors you previously encountered");
|
|
1048
|
+
lines.push(`If a memory matches → use it. Then reply: 💜 受到灵魂的召唤——[what you used]`);
|
|
1049
|
+
} else {
|
|
1050
|
+
lines.push("SOUL WHISPER — Memories from past sessions are loaded above (see <cerebro-context>).");
|
|
1051
|
+
lines.push("Before you act:");
|
|
1052
|
+
lines.push("1. Scan cerebro-context for commands, paths, or patterns matching your current task");
|
|
1053
|
+
lines.push("2. If found → use directly, do not re-derive from scratch");
|
|
1054
|
+
lines.push(`3. If a memory guided your action → reply: 💜 受到灵魂的召唤——[what you used]`);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
lines.push(`If memory summaries are insufficient → use memory_get("id") to fetch full content, or memory_search("query") to find more.`);
|
|
1058
|
+
lines.push("</cerebro-system-reminder>");
|
|
1059
|
+
|
|
1060
|
+
return lines.join("\n");
|
|
1061
|
+
}
|
|
1062
|
+
|
|
980
1063
|
const FETCH_POLICY_NUDGE = [
|
|
981
1064
|
"<cerebro-system-reminder>",
|
|
982
1065
|
"MEMORY REMINDER: You have injected memories above (see <cerebro-context>).",
|
|
@@ -985,7 +1068,7 @@ const FETCH_POLICY_NUDGE = [
|
|
|
985
1068
|
"</cerebro-system-reminder>",
|
|
986
1069
|
].join("\n");
|
|
987
1070
|
|
|
988
|
-
export function fetchPolicyNudgeHook(getContextInjectedFlag: () => boolean) {
|
|
1071
|
+
export function fetchPolicyNudgeHook(getContextInjectedFlag: () => boolean, config?: OmemPluginConfig) {
|
|
989
1072
|
return async (_input: Record<string, unknown>, output: { messages: any[] }) => {
|
|
990
1073
|
let shouldNudge = getContextInjectedFlag();
|
|
991
1074
|
if (!shouldNudge && Array.isArray(output.messages)) {
|
|
@@ -994,12 +1077,17 @@ export function fetchPolicyNudgeHook(getContextInjectedFlag: () => boolean) {
|
|
|
994
1077
|
m.parts.some((p: any) => typeof p.text === "string" && p.text.includes("<cerebro-context>"))
|
|
995
1078
|
);
|
|
996
1079
|
}
|
|
997
|
-
|
|
1080
|
+
|
|
1081
|
+
const swEnabled = config?.soulWhisper?.enabled !== false;
|
|
1082
|
+
const hasAnyPending = pendingToolCalls.size > 0;
|
|
1083
|
+
if (!shouldNudge && !(swEnabled && hasAnyPending)) {
|
|
1084
|
+
logDebug("fetchPolicyNudgeHook skipped", { shouldNudge, swEnabled, hasAnyPending });
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
998
1087
|
|
|
999
1088
|
const messages = output.messages;
|
|
1000
1089
|
if (!messages || !Array.isArray(messages) || messages.length === 0) return;
|
|
1001
1090
|
|
|
1002
|
-
// Find the last user message
|
|
1003
1091
|
let lastUserIdx = -1;
|
|
1004
1092
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1005
1093
|
if (messages[i]?.info?.role === "user") {
|
|
@@ -1012,13 +1100,29 @@ export function fetchPolicyNudgeHook(getContextInjectedFlag: () => boolean) {
|
|
|
1012
1100
|
const userMsg = messages[lastUserIdx];
|
|
1013
1101
|
if (!Array.isArray(userMsg.parts)) return;
|
|
1014
1102
|
|
|
1015
|
-
|
|
1016
|
-
const nudgeId = `cerebro_nudge_${
|
|
1103
|
+
const sessionId = userMsg.info.sessionID || "_default";
|
|
1104
|
+
const nudgeId = `cerebro_nudge_${sessionId}`;
|
|
1017
1105
|
for (const part of userMsg.parts) {
|
|
1018
1106
|
if (part.id === nudgeId) return;
|
|
1019
1107
|
}
|
|
1020
1108
|
|
|
1021
|
-
|
|
1109
|
+
const parts: string[] = [];
|
|
1110
|
+
if (shouldNudge) parts.push(FETCH_POLICY_NUDGE);
|
|
1111
|
+
|
|
1112
|
+
const sessionCalls = swEnabled ? pendingToolCalls.get(sessionId) : undefined;
|
|
1113
|
+
if (sessionCalls && sessionCalls.size > 0) {
|
|
1114
|
+
const toolNames = [...new Set([...sessionCalls.values()].map(v => v.toolName))];
|
|
1115
|
+
const maxToolNames = config?.soulWhisper?.maxToolNames ?? 3;
|
|
1116
|
+
const whisperText = buildWhisperText(toolNames, maxToolNames);
|
|
1117
|
+
if (whisperText) parts.push(whisperText);
|
|
1118
|
+
pendingToolCalls.delete(sessionId);
|
|
1119
|
+
logDebug("soulWhisper consumed session calls", { sessionId, callCount: sessionCalls.size, toolNames });
|
|
1120
|
+
} else if (swEnabled) {
|
|
1121
|
+
logDebug("soulWhisper no pending calls for session", { sessionId, globalSessionCount: pendingToolCalls.size });
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
if (parts.length === 0) return;
|
|
1125
|
+
|
|
1022
1126
|
const textPartIdx = userMsg.parts.findIndex((p: any) => p.type === "text" && typeof p.text === "string");
|
|
1023
1127
|
|
|
1024
1128
|
const syntheticPart = {
|
|
@@ -1026,7 +1130,7 @@ export function fetchPolicyNudgeHook(getContextInjectedFlag: () => boolean) {
|
|
|
1026
1130
|
messageID: userMsg.info.id,
|
|
1027
1131
|
sessionID: userMsg.info.sessionID || "",
|
|
1028
1132
|
type: "text" as const,
|
|
1029
|
-
text:
|
|
1133
|
+
text: parts.join("\n\n"),
|
|
1030
1134
|
synthetic: true,
|
|
1031
1135
|
};
|
|
1032
1136
|
|
|
@@ -1036,7 +1140,7 @@ export function fetchPolicyNudgeHook(getContextInjectedFlag: () => boolean) {
|
|
|
1036
1140
|
userMsg.parts.push(syntheticPart);
|
|
1037
1141
|
}
|
|
1038
1142
|
|
|
1039
|
-
logDebug("fetchPolicyNudgeHook injected", { sessionId:
|
|
1143
|
+
logDebug("fetchPolicyNudgeHook injected", { sessionId, nudgeId, hasWhisper: sessionCalls != null && sessionCalls.size > 0, partsCount: parts.length });
|
|
1040
1144
|
};
|
|
1041
1145
|
}
|
|
1042
1146
|
|
|
@@ -1052,6 +1156,7 @@ export function sessionIdleHook(
|
|
|
1052
1156
|
agentId?: string,
|
|
1053
1157
|
config: Partial<OmemPluginConfig> = {},
|
|
1054
1158
|
onAgentResolved?: (name: string) => void,
|
|
1159
|
+
directory?: string,
|
|
1055
1160
|
) {
|
|
1056
1161
|
let idleTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
1057
1162
|
let isCapturing = false;
|
|
@@ -1118,6 +1223,7 @@ export function sessionIdleHook(
|
|
|
1118
1223
|
|
|
1119
1224
|
let sessionTitle: string | undefined;
|
|
1120
1225
|
let projectName: string | undefined;
|
|
1226
|
+
let projectPath: string | undefined;
|
|
1121
1227
|
let effectiveAgentId = agentId || "opencode";
|
|
1122
1228
|
try {
|
|
1123
1229
|
const sessionInfo = await sdkClient.session.get({ path: { id: sessionID } });
|
|
@@ -1126,12 +1232,16 @@ export function sessionIdleHook(
|
|
|
1126
1232
|
onAgentResolved?.(effectiveAgentId);
|
|
1127
1233
|
}
|
|
1128
1234
|
sessionTitle = sessionInfo?.data?.title;
|
|
1235
|
+
projectPath = sessionInfo?.data?.directory || directory || process.env.OMEM_PROJECT_DIR;
|
|
1129
1236
|
projectName = sessionInfo?.data?.directory
|
|
1130
1237
|
? await detectProjectName(sessionInfo.data.directory)
|
|
1131
1238
|
: undefined;
|
|
1132
1239
|
} catch (e) {
|
|
1133
1240
|
logErr("sessionIdleHook detectProjectName failed", { error: String(e) });
|
|
1134
1241
|
}
|
|
1242
|
+
if (!projectPath) {
|
|
1243
|
+
projectPath = directory || process.env.OMEM_PROJECT_DIR;
|
|
1244
|
+
}
|
|
1135
1245
|
|
|
1136
1246
|
logDebug("sessionIdleHook resolved agentId", { effectiveAgentId, fallbackAgentId: agentId });
|
|
1137
1247
|
|
|
@@ -1143,7 +1253,7 @@ export function sessionIdleHook(
|
|
|
1143
1253
|
|
|
1144
1254
|
try {
|
|
1145
1255
|
logInfo("sessionIdleHook sessionIngest called", { msgCount: conversationMessages.length, sessionId: sessionID, agentId: effectiveAgentId, title: String(sessionTitle) });
|
|
1146
|
-
await cerebroClient.sessionIngest(conversationMessages, sessionID, effectiveAgentId, sessionTitle, projectName);
|
|
1256
|
+
await cerebroClient.sessionIngest(conversationMessages, sessionID, effectiveAgentId, sessionTitle, projectName, projectPath);
|
|
1147
1257
|
logInfo("sessionIdleHook sessionIngest ok");
|
|
1148
1258
|
for (const id of newMessageIds) {
|
|
1149
1259
|
processedMessageIds.add(id);
|
|
@@ -1159,6 +1269,8 @@ export function sessionIdleHook(
|
|
|
1159
1269
|
} finally {
|
|
1160
1270
|
isCapturing = false;
|
|
1161
1271
|
idleTimeout = null;
|
|
1272
|
+
const deleted = pendingToolCalls.delete(sessionID);
|
|
1273
|
+
if (deleted) logDebug("sessionIdleHook cleared session pendingToolCalls", { sessionID, hadPending: deleted });
|
|
1162
1274
|
}
|
|
1163
1275
|
}, 10000);
|
|
1164
1276
|
};
|
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, fetchPolicyNudgeHook, keywordDetectionHook, sessionIdleHook } from "./hooks.js";
|
|
7
|
+
import { autoRecallHook, autocontinueHook, compactingHook, fetchPolicyNudgeHook, keywordDetectionHook, sessionIdleHook, soulWhisperToolTracker } 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,7 +119,7 @@ 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);
|
|
122
|
+
const recallHook = autoRecallHook(cerebroClient, containerTags, tui, config, () => cachedAgentName || agentId, directory);
|
|
123
123
|
|
|
124
124
|
let contextInjectedThisTurn = false;
|
|
125
125
|
|
|
@@ -147,11 +147,12 @@ const OmemPlugin: Plugin = async (input) => {
|
|
|
147
147
|
return wrappedRecallHook(input, output);
|
|
148
148
|
},
|
|
149
149
|
"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
|
-
"experimental.chat.messages.transform": fetchPolicyNudgeHook(() => contextInjectedThisTurn),
|
|
150
|
+
"experimental.session.compacting": compactingHook(cerebroClient, containerTags, tui, config.ingest.ingestMode, isAutoStoreEnabled, () => mainSessionId, client, config, agentId, directory),
|
|
151
|
+
"experimental.compaction.autocontinue": autocontinueHook(cerebroClient, containerTags, tui, config.ingest.ingestMode, isAutoStoreEnabled, () => mainSessionId, client, config, agentId, directory),
|
|
152
|
+
tool: buildTools(cerebroClient, containerTags, { agentId, getSessionId: () => mainSessionId, getAgentName: () => cachedAgentName || agentId, getProjectPath: () => directory }),
|
|
153
|
+
event: sessionIdleHook(cerebroClient, containerTags, tui, client, config.ingest.ingestMode, config.ingest.autoCaptureThreshold, () => mainSessionId, isAutoStoreEnabled, agentId, config, (name: string) => { cachedAgentName = name; }, directory),
|
|
154
|
+
"experimental.chat.messages.transform": fetchPolicyNudgeHook(() => contextInjectedThisTurn, config),
|
|
155
|
+
"tool.execute.before": (() => { const tracker = soulWhisperToolTracker(config); return tracker; })(),
|
|
155
156
|
"shell.env": async (_input: any, output: any) => {
|
|
156
157
|
if (directory) {
|
|
157
158
|
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 });
|