@humanlikememory/human-like-mem 0.3.14 → 0.4.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/LICENSE +190 -190
- package/README.md +70 -70
- package/README_ZH.md +70 -70
- package/index.js +232 -271
- package/openclaw.plugin.json +165 -151
- package/package.json +8 -5
package/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @license Apache-2.0
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const PLUGIN_VERSION = "0.
|
|
9
|
+
const PLUGIN_VERSION = "0.4.0";
|
|
10
10
|
const USER_QUERY_MARKER = "--- User Query ---";
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -35,7 +35,7 @@ function warnMissingApiKey(log) {
|
|
|
35
35
|
Get your API key from: https://human-like.me
|
|
36
36
|
Then set:
|
|
37
37
|
export HUMAN_LIKE_MEM_API_KEY="mp_xxxxxx"
|
|
38
|
-
export HUMAN_LIKE_MEM_BASE_URL="https://
|
|
38
|
+
export HUMAN_LIKE_MEM_BASE_URL="https://human-like.me"
|
|
39
39
|
`;
|
|
40
40
|
if (log?.warn) {
|
|
41
41
|
log.warn(msg);
|
|
@@ -981,257 +981,167 @@ async function flushSession(sessionId, cfg, ctx, log) {
|
|
|
981
981
|
}
|
|
982
982
|
|
|
983
983
|
/**
|
|
984
|
-
* Main plugin export.
|
|
985
|
-
* Supports
|
|
984
|
+
* Main plugin export — object-style with register(api) method.
|
|
985
|
+
* Supports OpenClaw >=2026.2.0 capability registration.
|
|
986
986
|
*/
|
|
987
|
-
export default
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
// config -> hooks style
|
|
994
|
-
return createHooksPlugin(configOrApi);
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
/**
|
|
998
|
-
* Register style plugin (like MemOS)
|
|
999
|
-
*/
|
|
1000
|
-
function registerPlugin(api) {
|
|
1001
|
-
const config = api.pluginConfig || {};
|
|
1002
|
-
const log = api.logger || console;
|
|
987
|
+
export default {
|
|
988
|
+
id: "human-like-mem",
|
|
989
|
+
name: "Human-Like Memory Plugin",
|
|
990
|
+
kind: "memory",
|
|
1003
991
|
|
|
1004
|
-
|
|
992
|
+
register(api) {
|
|
993
|
+
const config = api.pluginConfig || {};
|
|
994
|
+
const log = api.logger || console;
|
|
995
|
+
const cfg = buildConfig(config);
|
|
1005
996
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
if (log?.debug) {
|
|
1010
|
-
log.debug(`[Memory Plugin] recall hook TRIGGERED (register mode)`);
|
|
1011
|
-
log.debug(`[Memory Plugin] recall ctx keys: ${JSON.stringify(ctx ? Object.keys(ctx) : 'null')}`);
|
|
1012
|
-
log.debug(`[Memory Plugin] recall event keys: ${JSON.stringify(event ? Object.keys(event) : 'null')}`);
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
const prompt = event?.prompt || "";
|
|
1016
|
-
if (!prompt || prompt.trim().length < 3) {
|
|
1017
|
-
if (log?.debug) log.debug('[Memory Plugin] Prompt too short, skipping recall');
|
|
1018
|
-
return;
|
|
997
|
+
if (log?.info) {
|
|
998
|
+
log.info(`[Memory Plugin] v${PLUGIN_VERSION} registering as memory slot`);
|
|
1019
999
|
}
|
|
1020
1000
|
|
|
1021
|
-
|
|
1022
|
-
const
|
|
1023
|
-
const
|
|
1024
|
-
if (userContent) {
|
|
1025
|
-
addToSessionCache(sessionId, { role: "user", content: userContent, rawContent: rawUserContent });
|
|
1026
|
-
}
|
|
1001
|
+
// --- Check allowPromptInjection policy ---
|
|
1002
|
+
const pluginEntry = api.config?.plugins?.entries?.[api.id] ?? api.config?.plugins?.entries?.["human-like-mem"];
|
|
1003
|
+
const allowPromptInjection = pluginEntry?.hooks?.allowPromptInjection !== false;
|
|
1027
1004
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1005
|
+
// --- Service lifecycle ---
|
|
1006
|
+
api.registerService({
|
|
1007
|
+
id: "human-like-mem",
|
|
1008
|
+
start: async () => {
|
|
1009
|
+
if (log?.info) log.info(`[Memory Plugin] Service started (v${PLUGIN_VERSION})`);
|
|
1010
|
+
},
|
|
1011
|
+
health: async () => {
|
|
1012
|
+
const hasApiKey = !!(cfg.apiKey || process.env.HUMAN_LIKE_MEM_API_KEY);
|
|
1013
|
+
return {
|
|
1014
|
+
status: hasApiKey ? "ok" : "degraded",
|
|
1015
|
+
message: hasApiKey
|
|
1016
|
+
? `v${PLUGIN_VERSION} ready, ${sessionCache.size} active sessions`
|
|
1017
|
+
: "API key not configured",
|
|
1018
|
+
};
|
|
1019
|
+
},
|
|
1020
|
+
stop: async () => {
|
|
1021
|
+
if (log?.info) log.info(`[Memory Plugin] Service stopping, flushing ${sessionCache.size} sessions`);
|
|
1022
|
+
const flushPromises = [];
|
|
1023
|
+
for (const [sid] of sessionCache) {
|
|
1024
|
+
flushPromises.push(
|
|
1025
|
+
flushSession(sid, cfg, null, log).catch(err => {
|
|
1026
|
+
if (log?.warn) log.warn(`[Memory Plugin] Flush failed for ${sid}: ${err.message}`);
|
|
1027
|
+
})
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
await Promise.allSettled(flushPromises);
|
|
1031
|
+
if (log?.info) log.info(`[Memory Plugin] Service stopped`);
|
|
1032
|
+
},
|
|
1033
|
+
});
|
|
1030
1034
|
|
|
1031
|
-
|
|
1035
|
+
// --- Register memory runtime for status reporting ---
|
|
1036
|
+
if (typeof api.registerMemoryRuntime === "function") {
|
|
1037
|
+
api.registerMemoryRuntime({
|
|
1038
|
+
getMemorySearchManager: async () => ({
|
|
1039
|
+
manager: {
|
|
1040
|
+
async probeVectorAvailability() {
|
|
1041
|
+
return { ok: !!(cfg.apiKey || process.env.HUMAN_LIKE_MEM_API_KEY) };
|
|
1042
|
+
},
|
|
1043
|
+
status() {
|
|
1044
|
+
return {
|
|
1045
|
+
files: sessionCache.size,
|
|
1046
|
+
chunks: 0,
|
|
1047
|
+
dirty: false,
|
|
1048
|
+
sources: ["remote-api"],
|
|
1049
|
+
provider: "human-like-mem",
|
|
1050
|
+
vector: { enabled: true, available: true, provider: "remote" },
|
|
1051
|
+
};
|
|
1052
|
+
},
|
|
1053
|
+
async close() {},
|
|
1054
|
+
},
|
|
1055
|
+
}),
|
|
1056
|
+
resolveMemoryBackendConfig: () => ({
|
|
1057
|
+
store: { driver: "remote", path: "remote://plugin.human-like.me" },
|
|
1058
|
+
}),
|
|
1059
|
+
});
|
|
1060
|
+
if (log?.info) log.info(`[Memory Plugin] Registered memory runtime for status reporting`);
|
|
1061
|
+
}
|
|
1032
1062
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1063
|
+
// --- Recall handler (before_prompt_build only) ---
|
|
1064
|
+
const recallHandler = async (event, ctx) => {
|
|
1065
|
+
if (!cfg.recallEnabled) return;
|
|
1066
|
+
if (!allowPromptInjection) {
|
|
1067
|
+
if (log?.debug) log.debug(`[Memory Plugin] allowPromptInjection=false, auto-recall disabled`);
|
|
1068
|
+
return;
|
|
1037
1069
|
}
|
|
1038
1070
|
|
|
1039
|
-
if (
|
|
1040
|
-
|
|
1041
|
-
|
|
1071
|
+
if (log?.debug) {
|
|
1072
|
+
log.debug(`[Memory Plugin] before_prompt_build TRIGGERED`);
|
|
1073
|
+
log.debug(`[Memory Plugin] recall ctx keys: ${JSON.stringify(ctx ? Object.keys(ctx) : 'null')}`);
|
|
1074
|
+
log.debug(`[Memory Plugin] recall event keys: ${JSON.stringify(event ? Object.keys(event) : 'null')}`);
|
|
1042
1075
|
}
|
|
1043
1076
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1077
|
+
const prompt = event?.prompt || "";
|
|
1078
|
+
if (!prompt || prompt.trim().length < 3) {
|
|
1079
|
+
if (log?.debug) log.debug('[Memory Plugin] Prompt too short, skipping recall');
|
|
1080
|
+
return;
|
|
1046
1081
|
}
|
|
1047
|
-
} catch (error) {
|
|
1048
|
-
if (log?.warn) log.warn(`[Memory Plugin] Memory recall failed: ${error.message}`);
|
|
1049
1082
|
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1083
|
+
const sessionId = resolveSessionId(ctx, event) || `session-${Date.now()}`;
|
|
1084
|
+
const userContent = normalizeUserMessageContent(prompt);
|
|
1085
|
+
const rawUserContent = stripPrependedPrompt(prompt);
|
|
1086
|
+
if (userContent) {
|
|
1087
|
+
addToSessionCache(sessionId, { role: "user", content: userContent, rawContent: rawUserContent });
|
|
1054
1088
|
}
|
|
1055
|
-
}
|
|
1056
|
-
};
|
|
1057
1089
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
if (!event?.success) return;
|
|
1090
|
+
try {
|
|
1091
|
+
const memories = await retrieveMemory(prompt, cfg, ctx, log);
|
|
1061
1092
|
|
|
1062
|
-
|
|
1063
|
-
log.debug(`[Memory Plugin] store hook ctx keys: ${JSON.stringify(ctx ? Object.keys(ctx) : 'null')}`);
|
|
1064
|
-
log.debug(`[Memory Plugin] store hook event keys: ${JSON.stringify(event ? Object.keys(event) : 'null')}`);
|
|
1065
|
-
}
|
|
1093
|
+
let prependContext = "";
|
|
1066
1094
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
const assistantContent = event?.response || event?.result;
|
|
1074
|
-
if (assistantContent) {
|
|
1075
|
-
const content = normalizeAssistantMessageContent(assistantContent);
|
|
1076
|
-
const rawContent = extractText(assistantContent);
|
|
1077
|
-
if (content) {
|
|
1078
|
-
addToSessionCache(sessionId, { role: "assistant", content, rawContent: rawContent || undefined });
|
|
1079
|
-
}
|
|
1080
|
-
} else if (event?.messages?.length) {
|
|
1081
|
-
for (let i = event.messages.length - 1; i >= 0; i--) {
|
|
1082
|
-
if (event.messages[i].role === "assistant") {
|
|
1083
|
-
const content = normalizeAssistantMessageContent(event.messages[i].content);
|
|
1084
|
-
const rawContent = extractText(event.messages[i].content);
|
|
1085
|
-
if (content) {
|
|
1086
|
-
addToSessionCache(sessionId, { role: "assistant", content, rawContent: rawContent || undefined });
|
|
1087
|
-
}
|
|
1088
|
-
break;
|
|
1095
|
+
const upgradeMsg = formatUpgradeNotification();
|
|
1096
|
+
if (upgradeMsg) {
|
|
1097
|
+
prependContext += upgradeMsg + "\n\n";
|
|
1098
|
+
upgradeNotification = null;
|
|
1089
1099
|
}
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
scheduleSessionFlush(sessionId, cfg, ctx, log);
|
|
1094
1100
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
if (log?.info) {
|
|
1099
|
-
log.info(`[Memory Plugin] Reached ${cache.turnCount} turns, flushing session`);
|
|
1101
|
+
if (memories && memories.length > 0) {
|
|
1102
|
+
prependContext += formatMemoriesForContext(memories, { currentTime: Date.now() });
|
|
1103
|
+
if (log?.info) log.info(`[Memory Plugin] Injected ${memories.length} memories`);
|
|
1100
1104
|
}
|
|
1101
|
-
flushSession(sessionId, cfg, ctx, log).catch(err => {
|
|
1102
|
-
if (log?.warn) log.warn(`[Memory Plugin] Async flush failed: ${err.message}`);
|
|
1103
|
-
});
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
};
|
|
1107
|
-
|
|
1108
|
-
const sessionEndHandler = async (event, ctx) => {
|
|
1109
|
-
if (!cfg.addEnabled) return;
|
|
1110
|
-
|
|
1111
|
-
const sessionId = resolveSessionId(ctx, event);
|
|
1112
|
-
if (!sessionId) return;
|
|
1113
|
-
|
|
1114
|
-
if (log?.info) {
|
|
1115
|
-
log.info(`[Memory Plugin] Session ending, flushing cache`);
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
await flushSession(sessionId, cfg, ctx, log);
|
|
1119
|
-
};
|
|
1120
1105
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
}
|
|
1127
|
-
api.on("session_end", sessionEndHandler);
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
/**
|
|
1131
|
-
* Hooks object style plugin
|
|
1132
|
-
*/
|
|
1133
|
-
function createHooksPlugin(config) {
|
|
1134
|
-
const cfg = buildConfig(config);
|
|
1135
|
-
|
|
1136
|
-
const recallHandler = async (event, ctx) => {
|
|
1137
|
-
const log = ctx?.log || console;
|
|
1138
|
-
|
|
1139
|
-
if (log?.debug) {
|
|
1140
|
-
log.debug(`[Memory Plugin] recall hook TRIGGERED`);
|
|
1141
|
-
log.debug(`[Memory Plugin] recall ctx keys: ${JSON.stringify(ctx ? Object.keys(ctx) : 'null')}`);
|
|
1142
|
-
log.debug(`[Memory Plugin] recall event keys: ${JSON.stringify(event ? Object.keys(event) : 'null')}`);
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
if (!cfg.recallEnabled) return;
|
|
1146
|
-
|
|
1147
|
-
const prompt = event?.prompt || "";
|
|
1148
|
-
if (!prompt || prompt.trim().length < 3) {
|
|
1149
|
-
if (log?.debug) log.debug('[Memory Plugin] Prompt too short, skipping recall');
|
|
1150
|
-
return;
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
const sessionId = resolveSessionId(ctx, event) || `session-${Date.now()}`;
|
|
1154
|
-
const userContent = normalizeUserMessageContent(prompt);
|
|
1155
|
-
const rawUserContent = stripPrependedPrompt(prompt);
|
|
1156
|
-
if (userContent) {
|
|
1157
|
-
addToSessionCache(sessionId, { role: "user", content: userContent, rawContent: rawUserContent });
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
try {
|
|
1161
|
-
const memories = await retrieveMemory(prompt, cfg, ctx, log);
|
|
1162
|
-
|
|
1163
|
-
let prependContext = "";
|
|
1106
|
+
if (prependContext) {
|
|
1107
|
+
return { prependContext };
|
|
1108
|
+
}
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
if (log?.warn) log.warn(`[Memory Plugin] Memory recall failed: ${error.message}`);
|
|
1164
1111
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1112
|
+
const upgradeMsg = formatUpgradeNotification();
|
|
1113
|
+
if (upgradeMsg) {
|
|
1114
|
+
upgradeNotification = null;
|
|
1115
|
+
return { prependContext: upgradeMsg };
|
|
1116
|
+
}
|
|
1169
1117
|
}
|
|
1118
|
+
};
|
|
1170
1119
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1120
|
+
// --- Store handler (agent_end only) ---
|
|
1121
|
+
const storeHandler = async (event, ctx) => {
|
|
1122
|
+
if (!cfg.addEnabled) return;
|
|
1123
|
+
if (!event?.success) return;
|
|
1175
1124
|
|
|
1176
|
-
if (
|
|
1177
|
-
|
|
1125
|
+
if (log?.debug) {
|
|
1126
|
+
log.debug(`[Memory Plugin] agent_end store TRIGGERED`);
|
|
1127
|
+
log.debug(`[Memory Plugin] store hook ctx keys: ${JSON.stringify(ctx ? Object.keys(ctx) : 'null')}`);
|
|
1128
|
+
log.debug(`[Memory Plugin] store hook event keys: ${JSON.stringify(event ? Object.keys(event) : 'null')}`);
|
|
1178
1129
|
}
|
|
1179
|
-
} catch (error) {
|
|
1180
|
-
if (log?.warn) log.warn(`[Memory Plugin] Memory recall failed: ${error.message}`);
|
|
1181
1130
|
|
|
1182
|
-
const
|
|
1183
|
-
if (
|
|
1184
|
-
|
|
1185
|
-
return
|
|
1131
|
+
const sessionId = resolveSessionId(ctx, event);
|
|
1132
|
+
if (!sessionId) {
|
|
1133
|
+
if (log?.debug) log.debug('[Memory Plugin] No session ID found in ctx or event, skipping memory cache');
|
|
1134
|
+
return;
|
|
1186
1135
|
}
|
|
1187
|
-
}
|
|
1188
|
-
};
|
|
1189
|
-
|
|
1190
|
-
const storeHandler = async (event, ctx) => {
|
|
1191
|
-
const log = ctx?.log || console;
|
|
1192
|
-
|
|
1193
|
-
if (log?.debug) {
|
|
1194
|
-
log.debug(`[Memory Plugin] store hook ctx keys: ${JSON.stringify(ctx ? Object.keys(ctx) : 'null')}`);
|
|
1195
|
-
log.debug(`[Memory Plugin] store hook event keys: ${JSON.stringify(event ? Object.keys(event) : 'null')}`);
|
|
1196
|
-
if (ctx?.messageProvider) {
|
|
1197
|
-
try {
|
|
1198
|
-
const mpKeys = typeof ctx.messageProvider === 'object' ? Object.keys(ctx.messageProvider) : typeof ctx.messageProvider;
|
|
1199
|
-
log.debug(`[Memory Plugin] messageProvider info: ${JSON.stringify(mpKeys)}`);
|
|
1200
|
-
} catch (_) {}
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
1136
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
return;
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
if (log?.debug) {
|
|
1214
|
-
log.debug(`[Memory Plugin] Resolved sessionId: ${sessionId}`);
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
// If event.messages has full conversation, cache all of them at once
|
|
1218
|
-
if (event?.messages?.length) {
|
|
1219
|
-
const cache = getSessionCache(sessionId);
|
|
1220
|
-
if (cache.messages.length === 0) {
|
|
1221
|
-
for (const msg of event.messages) {
|
|
1222
|
-
if (msg.role === "system") continue;
|
|
1223
|
-
const content = msg.role === "user"
|
|
1224
|
-
? normalizeUserMessageContent(msg.content)
|
|
1225
|
-
: normalizeAssistantMessageContent(msg.content);
|
|
1226
|
-
const rawSource = msg.role === "user"
|
|
1227
|
-
? stripPrependedPrompt(msg.content)
|
|
1228
|
-
: extractText(msg.content);
|
|
1229
|
-
if (content) {
|
|
1230
|
-
addToSessionCache(sessionId, { role: msg.role, content, rawContent: rawSource || undefined });
|
|
1231
|
-
}
|
|
1137
|
+
const assistantContent = event?.response || event?.result;
|
|
1138
|
+
if (assistantContent) {
|
|
1139
|
+
const content = normalizeAssistantMessageContent(assistantContent);
|
|
1140
|
+
const rawContent = extractText(assistantContent);
|
|
1141
|
+
if (content) {
|
|
1142
|
+
addToSessionCache(sessionId, { role: "assistant", content, rawContent: rawContent || undefined });
|
|
1232
1143
|
}
|
|
1233
|
-
} else {
|
|
1234
|
-
// Only add last assistant message
|
|
1144
|
+
} else if (event?.messages?.length) {
|
|
1235
1145
|
for (let i = event.messages.length - 1; i >= 0; i--) {
|
|
1236
1146
|
if (event.messages[i].role === "assistant") {
|
|
1237
1147
|
const content = normalizeAssistantMessageContent(event.messages[i].content);
|
|
@@ -1243,65 +1153,116 @@ function createHooksPlugin(config) {
|
|
|
1243
1153
|
}
|
|
1244
1154
|
}
|
|
1245
1155
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
if (
|
|
1252
|
-
|
|
1156
|
+
|
|
1157
|
+
scheduleSessionFlush(sessionId, cfg, ctx, log);
|
|
1158
|
+
|
|
1159
|
+
const cache = sessionCache.get(sessionId);
|
|
1160
|
+
if (cache && cache.turnCount >= (cfg.minTurnsToStore || 10)) {
|
|
1161
|
+
if (isConversationWorthStoring(cache.messages, cfg)) {
|
|
1162
|
+
if (log?.info) {
|
|
1163
|
+
log.info(`[Memory Plugin] Reached ${cache.turnCount} turns, flushing session`);
|
|
1164
|
+
}
|
|
1165
|
+
flushSession(sessionId, cfg, ctx, log).catch(err => {
|
|
1166
|
+
if (log?.warn) log.warn(`[Memory Plugin] Async flush failed: ${err.message}`);
|
|
1167
|
+
});
|
|
1253
1168
|
}
|
|
1254
1169
|
}
|
|
1255
|
-
}
|
|
1170
|
+
};
|
|
1256
1171
|
|
|
1257
|
-
|
|
1172
|
+
// --- Session end handler ---
|
|
1173
|
+
const sessionEndHandler = async (event, ctx) => {
|
|
1174
|
+
if (!cfg.addEnabled) return;
|
|
1258
1175
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1265
|
-
flushSession(sessionId, cfg, ctx, log).catch(err => {
|
|
1266
|
-
if (log?.warn) log.warn(`[Memory Plugin] Async flush failed: ${err.message}`);
|
|
1267
|
-
});
|
|
1176
|
+
const sessionId = resolveSessionId(ctx, event);
|
|
1177
|
+
if (!sessionId) return;
|
|
1178
|
+
|
|
1179
|
+
if (log?.info) {
|
|
1180
|
+
log.info(`[Memory Plugin] Session ending, flushing cache`);
|
|
1268
1181
|
}
|
|
1269
|
-
}
|
|
1270
|
-
};
|
|
1271
1182
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1183
|
+
await flushSession(sessionId, cfg, ctx, log);
|
|
1184
|
+
};
|
|
1274
1185
|
|
|
1275
|
-
|
|
1186
|
+
// --- Hook registration (single canonical hooks) ---
|
|
1187
|
+
api.on("before_prompt_build", recallHandler);
|
|
1188
|
+
api.on("agent_end", storeHandler);
|
|
1189
|
+
api.on("session_end", sessionEndHandler);
|
|
1190
|
+
|
|
1191
|
+
// --- Agent tools registration ---
|
|
1192
|
+
if (typeof api.registerTool === "function") {
|
|
1193
|
+
api.registerTool({
|
|
1194
|
+
name: "memory_search",
|
|
1195
|
+
label: "Memory Search",
|
|
1196
|
+
description: "Search user's long-term memory for relevant past conversations, knowledge, and preferences. Use this when you need context about the user's history or when they reference past events.",
|
|
1197
|
+
parameters: {
|
|
1198
|
+
type: "object",
|
|
1199
|
+
properties: {
|
|
1200
|
+
query: { type: "string", description: "Search query — keywords or natural language describing what to look for in memory" },
|
|
1201
|
+
limit: { type: "number", description: "Maximum number of results to return (default: 6, max: 20)" },
|
|
1202
|
+
},
|
|
1203
|
+
required: ["query"],
|
|
1204
|
+
},
|
|
1205
|
+
execute: async (_toolCallId, params) => {
|
|
1206
|
+
const { query, limit = 6 } = params;
|
|
1207
|
+
const clampedLimit = Math.min(Math.max(1, limit), 20);
|
|
1208
|
+
if (log?.info) log.info(`[Memory Plugin] Tool memory_search: query="${truncate(query, 50)}", limit=${clampedLimit}`);
|
|
1209
|
+
try {
|
|
1210
|
+
const memories = await retrieveMemory(query, { ...cfg, memoryLimitNumber: clampedLimit }, null, log);
|
|
1211
|
+
if (!memories || memories.length === 0) {
|
|
1212
|
+
return { content: [{ type: "text", text: "No relevant memories found for this query." }] };
|
|
1213
|
+
}
|
|
1214
|
+
const formatted = formatMemoriesForContext(memories, { currentTime: Date.now() });
|
|
1215
|
+
return {
|
|
1216
|
+
content: [{ type: "text", text: formatted }],
|
|
1217
|
+
details: { hits: memories.length, query },
|
|
1218
|
+
};
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
if (log?.warn) log.warn(`[Memory Plugin] Tool memory_search failed: ${error.message}`);
|
|
1221
|
+
return { content: [{ type: "text", text: `Memory search failed: ${error.message}` }], isError: true };
|
|
1222
|
+
}
|
|
1223
|
+
},
|
|
1224
|
+
});
|
|
1276
1225
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1226
|
+
api.registerTool({
|
|
1227
|
+
name: "memory_store",
|
|
1228
|
+
label: "Memory Store",
|
|
1229
|
+
description: "Actively save important information, user preferences, or key decisions to long-term memory. Use this when the user shares something worth remembering for future conversations.",
|
|
1230
|
+
parameters: {
|
|
1231
|
+
type: "object",
|
|
1232
|
+
properties: {
|
|
1233
|
+
content: { type: "string", description: "The information to remember — be specific and include context" },
|
|
1234
|
+
category: { type: "string", description: "Optional category tag (e.g. 'preference', 'fact', 'decision')" },
|
|
1235
|
+
},
|
|
1236
|
+
required: ["content"],
|
|
1237
|
+
},
|
|
1238
|
+
execute: async (_toolCallId, params) => {
|
|
1239
|
+
const { content, category } = params;
|
|
1240
|
+
if (log?.info) log.info(`[Memory Plugin] Tool memory_store: content="${truncate(content, 50)}", category=${category || 'none'}`);
|
|
1241
|
+
try {
|
|
1242
|
+
const messages = [
|
|
1243
|
+
{ role: "user", content: `[Memory Store${category ? ` | ${category}` : ""}] ${content}` },
|
|
1244
|
+
{ role: "assistant", content: `Noted and saved to memory: ${truncate(content, 100)}` },
|
|
1245
|
+
];
|
|
1246
|
+
const result = await addMemory(messages, cfg, null, log);
|
|
1247
|
+
return {
|
|
1248
|
+
content: [{ type: "text", text: `Memory saved successfully: "${truncate(content, 80)}"` }],
|
|
1249
|
+
details: { stored: true, category: category || null },
|
|
1250
|
+
};
|
|
1251
|
+
} catch (error) {
|
|
1252
|
+
if (log?.warn) log.warn(`[Memory Plugin] Tool memory_store failed: ${error.message}`);
|
|
1253
|
+
return { content: [{ type: "text", text: `Memory store failed: ${error.message}` }], isError: true };
|
|
1254
|
+
}
|
|
1255
|
+
},
|
|
1256
|
+
});
|
|
1279
1257
|
|
|
1280
|
-
|
|
1281
|
-
log.info(`[Memory Plugin] Session ending, flushing cache`);
|
|
1258
|
+
if (log?.info) log.info(`[Memory Plugin] Registered 2 agent tools: memory_search, memory_store`);
|
|
1282
1259
|
}
|
|
1283
1260
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
"before_agent_start": recallHandler,
|
|
1290
|
-
"agent_start": recallHandler,
|
|
1291
|
-
"prompt_start": recallHandler,
|
|
1292
|
-
"message_received": recallHandler,
|
|
1293
|
-
"request_pre": recallHandler,
|
|
1294
|
-
"before_recall": recallHandler,
|
|
1295
|
-
|
|
1296
|
-
"agent_end": storeHandler,
|
|
1297
|
-
"prompt_end": storeHandler,
|
|
1298
|
-
"request_post": storeHandler,
|
|
1299
|
-
"message_sent": storeHandler,
|
|
1300
|
-
|
|
1301
|
-
"session_end": sessionEndHandler,
|
|
1302
|
-
},
|
|
1303
|
-
};
|
|
1304
|
-
}
|
|
1261
|
+
if (log?.info) {
|
|
1262
|
+
log.info(`[Memory Plugin] Registration complete: hooks=[before_prompt_build, agent_end, session_end], tools=${typeof api.registerTool === "function" ? 2 : 0}`);
|
|
1263
|
+
}
|
|
1264
|
+
},
|
|
1265
|
+
};
|
|
1305
1266
|
|
|
1306
1267
|
/**
|
|
1307
1268
|
* Build config from various sources
|
|
@@ -1311,7 +1272,7 @@ function buildConfig(config) {
|
|
|
1311
1272
|
const configuredUserId = config?.userId || process.env.HUMAN_LIKE_MEM_USER_ID;
|
|
1312
1273
|
|
|
1313
1274
|
return {
|
|
1314
|
-
baseUrl: config?.baseUrl || process.env.HUMAN_LIKE_MEM_BASE_URL || "https://
|
|
1275
|
+
baseUrl: config?.baseUrl || process.env.HUMAN_LIKE_MEM_BASE_URL || "https://human-like.me",
|
|
1315
1276
|
apiKey: config?.apiKey || process.env.HUMAN_LIKE_MEM_API_KEY,
|
|
1316
1277
|
configuredUserId: configuredUserId,
|
|
1317
1278
|
userId: configuredUserId || "openclaw-user",
|