memory-braid 0.4.6 → 0.4.7
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/package.json +1 -1
- package/src/index.ts +177 -2
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -63,6 +63,107 @@ function resolveScopeFromHookContext(ctx: {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
function extractHookMessageText(content: unknown): string {
|
|
67
|
+
if (typeof content === "string") {
|
|
68
|
+
return normalizeWhitespace(content);
|
|
69
|
+
}
|
|
70
|
+
if (!Array.isArray(content)) {
|
|
71
|
+
return "";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const parts: string[] = [];
|
|
75
|
+
for (const block of content) {
|
|
76
|
+
if (!block || typeof block !== "object") {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const item = block as { type?: unknown; text?: unknown };
|
|
80
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
81
|
+
const normalized = normalizeWhitespace(item.text);
|
|
82
|
+
if (normalized) {
|
|
83
|
+
parts.push(normalized);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return parts.join(" ");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function normalizeHookMessages(messages: unknown[]): Array<{ role: string; text: string }> {
|
|
91
|
+
const out: Array<{ role: string; text: string }> = [];
|
|
92
|
+
for (const entry of messages) {
|
|
93
|
+
if (!entry || typeof entry !== "object") {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const direct = entry as { role?: unknown; content?: unknown };
|
|
98
|
+
if (typeof direct.role === "string") {
|
|
99
|
+
const text = extractHookMessageText(direct.content);
|
|
100
|
+
if (text) {
|
|
101
|
+
out.push({ role: direct.role, text });
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const wrapped = entry as { message?: { role?: unknown; content?: unknown } };
|
|
107
|
+
if (wrapped.message && typeof wrapped.message.role === "string") {
|
|
108
|
+
const text = extractHookMessageText(wrapped.message.content);
|
|
109
|
+
if (text) {
|
|
110
|
+
out.push({ role: wrapped.message.role, text });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function resolveLatestUserTurnSignature(messages?: unknown[]): string | undefined {
|
|
118
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const normalized = normalizeHookMessages(messages);
|
|
123
|
+
for (let i = normalized.length - 1; i >= 0; i -= 1) {
|
|
124
|
+
const message = normalized[i];
|
|
125
|
+
if (!message || message.role !== "user") {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const hashSource = normalizeForHash(message.text);
|
|
129
|
+
if (!hashSource) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
return `${i}:${sha256(hashSource)}`;
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function resolvePromptTurnSignature(prompt: string): string | undefined {
|
|
138
|
+
const normalized = normalizeForHash(prompt);
|
|
139
|
+
if (!normalized) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
return `prompt:${sha256(normalized)}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function resolveRunScopeKey(ctx: { agentId?: string; sessionKey?: string }): string {
|
|
146
|
+
const agentId = (ctx.agentId ?? "main").trim() || "main";
|
|
147
|
+
const sessionKey = (ctx.sessionKey ?? "main").trim() || "main";
|
|
148
|
+
return `${agentId}|${sessionKey}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isExcludedAutoMemorySession(sessionKey?: string): boolean {
|
|
152
|
+
const normalized = (sessionKey ?? "").trim().toLowerCase();
|
|
153
|
+
if (!normalized) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
return (
|
|
157
|
+
normalized.startsWith("cron:") ||
|
|
158
|
+
normalized.includes(":cron:") ||
|
|
159
|
+
normalized.includes(":subagent:") ||
|
|
160
|
+
normalized.startsWith("subagent:") ||
|
|
161
|
+
normalized.includes(":acp:") ||
|
|
162
|
+
normalized.startsWith("acp:") ||
|
|
163
|
+
normalized.startsWith("temp:")
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
66
167
|
function formatRelevantMemories(results: MemoryBraidResult[], maxChars = 600): string {
|
|
67
168
|
const lines = results.map((entry, index) => {
|
|
68
169
|
const sourceLabel = entry.source === "local" ? "local" : "mem0";
|
|
@@ -944,6 +1045,8 @@ const memoryBraidPlugin = {
|
|
|
944
1045
|
const entityExtraction = new EntityExtractionManager(cfg.entityExtraction, log, {
|
|
945
1046
|
stateDir: initialStateDir,
|
|
946
1047
|
});
|
|
1048
|
+
const recallSeenByScope = new Map<string, string>();
|
|
1049
|
+
const captureSeenByScope = new Map<string, string>();
|
|
947
1050
|
|
|
948
1051
|
let lifecycleTimer: NodeJS.Timeout | null = null;
|
|
949
1052
|
let statePaths: StatePaths | null = null;
|
|
@@ -1231,10 +1334,48 @@ const memoryBraidPlugin = {
|
|
|
1231
1334
|
|
|
1232
1335
|
api.on("before_agent_start", async (event, ctx) => {
|
|
1233
1336
|
const runId = log.newRunId();
|
|
1337
|
+
const scope = resolveScopeFromHookContext(ctx);
|
|
1338
|
+
if (isExcludedAutoMemorySession(ctx.sessionKey)) {
|
|
1339
|
+
log.debug("memory_braid.search.skip", {
|
|
1340
|
+
runId,
|
|
1341
|
+
reason: "session_scope_excluded",
|
|
1342
|
+
workspaceHash: scope.workspaceHash,
|
|
1343
|
+
agentId: scope.agentId,
|
|
1344
|
+
sessionKey: scope.sessionKey,
|
|
1345
|
+
});
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1234
1349
|
const recallQuery = sanitizeRecallQuery(event.prompt);
|
|
1235
1350
|
if (!recallQuery) {
|
|
1236
1351
|
return;
|
|
1237
1352
|
}
|
|
1353
|
+
const scopeKey = resolveRunScopeKey(ctx);
|
|
1354
|
+
const userTurnSignature =
|
|
1355
|
+
resolveLatestUserTurnSignature(event.messages) ?? resolvePromptTurnSignature(recallQuery);
|
|
1356
|
+
if (!userTurnSignature) {
|
|
1357
|
+
log.debug("memory_braid.search.skip", {
|
|
1358
|
+
runId,
|
|
1359
|
+
reason: "no_user_turn_signature",
|
|
1360
|
+
workspaceHash: scope.workspaceHash,
|
|
1361
|
+
agentId: scope.agentId,
|
|
1362
|
+
sessionKey: scope.sessionKey,
|
|
1363
|
+
});
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
const previousSignature = recallSeenByScope.get(scopeKey);
|
|
1367
|
+
if (previousSignature === userTurnSignature) {
|
|
1368
|
+
log.debug("memory_braid.search.skip", {
|
|
1369
|
+
runId,
|
|
1370
|
+
reason: "no_new_user_turn",
|
|
1371
|
+
workspaceHash: scope.workspaceHash,
|
|
1372
|
+
agentId: scope.agentId,
|
|
1373
|
+
sessionKey: scope.sessionKey,
|
|
1374
|
+
});
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
recallSeenByScope.set(scopeKey, userTurnSignature);
|
|
1378
|
+
|
|
1238
1379
|
const toolCtx: OpenClawPluginToolContext = {
|
|
1239
1380
|
config: api.config,
|
|
1240
1381
|
workspaceDir: ctx.workspaceDir,
|
|
@@ -1264,7 +1405,6 @@ const memoryBraidPlugin = {
|
|
|
1264
1405
|
limit: cfg.recall.injectTopK,
|
|
1265
1406
|
});
|
|
1266
1407
|
if (selected.injected.length === 0) {
|
|
1267
|
-
const scope = resolveScopeFromHookContext(ctx);
|
|
1268
1408
|
log.debug("memory_braid.search.inject", {
|
|
1269
1409
|
runId,
|
|
1270
1410
|
agentId: scope.agentId,
|
|
@@ -1281,7 +1421,6 @@ const memoryBraidPlugin = {
|
|
|
1281
1421
|
}
|
|
1282
1422
|
|
|
1283
1423
|
const prependContext = formatRelevantMemories(selected.injected, cfg.debug.maxSnippetChars);
|
|
1284
|
-
const scope = resolveScopeFromHookContext(ctx);
|
|
1285
1424
|
log.debug("memory_braid.search.inject", {
|
|
1286
1425
|
runId,
|
|
1287
1426
|
agentId: scope.agentId,
|
|
@@ -1306,6 +1445,42 @@ const memoryBraidPlugin = {
|
|
|
1306
1445
|
}
|
|
1307
1446
|
const runId = log.newRunId();
|
|
1308
1447
|
const scope = resolveScopeFromHookContext(ctx);
|
|
1448
|
+
if (isExcludedAutoMemorySession(ctx.sessionKey)) {
|
|
1449
|
+
log.debug("memory_braid.capture.skip", {
|
|
1450
|
+
runId,
|
|
1451
|
+
reason: "session_scope_excluded",
|
|
1452
|
+
workspaceHash: scope.workspaceHash,
|
|
1453
|
+
agentId: scope.agentId,
|
|
1454
|
+
sessionKey: scope.sessionKey,
|
|
1455
|
+
});
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
const scopeKey = resolveRunScopeKey(ctx);
|
|
1460
|
+
const userTurnSignature = resolveLatestUserTurnSignature(event.messages);
|
|
1461
|
+
if (!userTurnSignature) {
|
|
1462
|
+
log.debug("memory_braid.capture.skip", {
|
|
1463
|
+
runId,
|
|
1464
|
+
reason: "no_user_turn_signature",
|
|
1465
|
+
workspaceHash: scope.workspaceHash,
|
|
1466
|
+
agentId: scope.agentId,
|
|
1467
|
+
sessionKey: scope.sessionKey,
|
|
1468
|
+
});
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
const previousSignature = captureSeenByScope.get(scopeKey);
|
|
1472
|
+
if (previousSignature === userTurnSignature) {
|
|
1473
|
+
log.debug("memory_braid.capture.skip", {
|
|
1474
|
+
runId,
|
|
1475
|
+
reason: "no_new_user_turn",
|
|
1476
|
+
workspaceHash: scope.workspaceHash,
|
|
1477
|
+
agentId: scope.agentId,
|
|
1478
|
+
sessionKey: scope.sessionKey,
|
|
1479
|
+
});
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
captureSeenByScope.set(scopeKey, userTurnSignature);
|
|
1483
|
+
|
|
1309
1484
|
const candidates = await extractCandidates({
|
|
1310
1485
|
messages: event.messages,
|
|
1311
1486
|
cfg,
|