@icex-labs/openclaw-memory-engine 4.0.0 → 4.1.1
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/index.js +27 -0
- package/lib/auto-capture.js +89 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -32,6 +32,7 @@ import { analyzePatterns, formatReflection } from "./lib/reflection.js";
|
|
|
32
32
|
import { runQualityPass, formatQualityReport } from "./lib/quality.js";
|
|
33
33
|
import { migrateFromJsonl } from "./lib/store-sqlite.js";
|
|
34
34
|
import { generateDashboard } from "./lib/dashboard.js";
|
|
35
|
+
import { captureMessage } from "./lib/auto-capture.js";
|
|
35
36
|
|
|
36
37
|
import { readFileSync } from "node:fs";
|
|
37
38
|
|
|
@@ -131,6 +132,32 @@ export default definePluginEntry({
|
|
|
131
132
|
} catch { /* ignore startup errors */ }
|
|
132
133
|
}, 10000); // delay 10s after gateway start
|
|
133
134
|
|
|
135
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
136
|
+
// Auto-capture hooks: passively store facts from conversations
|
|
137
|
+
// No reliance on agent calling tools — memory happens automatically
|
|
138
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
139
|
+
|
|
140
|
+
api.registerHook("message:received", (event) => {
|
|
141
|
+
try {
|
|
142
|
+
const ctx = event.context;
|
|
143
|
+
if (!ctx?.content) return;
|
|
144
|
+
const agentId = extractAgentId(event.sessionKey);
|
|
145
|
+
const wsDir = resolveWorkspace({ agentId });
|
|
146
|
+
captureMessage(wsDir, ctx.content, "user-message");
|
|
147
|
+
} catch { /* don't break message flow */ }
|
|
148
|
+
}, { name: "memory-engine-capture-received", description: "Auto-capture facts from incoming messages" });
|
|
149
|
+
|
|
150
|
+
api.registerHook("message:sent", (event) => {
|
|
151
|
+
try {
|
|
152
|
+
const ctx = event.context;
|
|
153
|
+
if (!ctx?.content || !ctx?.success) return;
|
|
154
|
+
if (ctx.content.length < 50) return;
|
|
155
|
+
const agentId = extractAgentId(event.sessionKey);
|
|
156
|
+
const wsDir = resolveWorkspace({ agentId });
|
|
157
|
+
captureMessage(wsDir, ctx.content, "agent-reply");
|
|
158
|
+
} catch { /* don't break message flow */ }
|
|
159
|
+
}, { name: "memory-engine-capture-sent", description: "Auto-capture facts from agent replies" });
|
|
160
|
+
|
|
134
161
|
// ─── core_memory_read ───
|
|
135
162
|
api.registerTool(withAgent((agentId) => ({
|
|
136
163
|
name: "core_memory_read",
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-capture: hook into message:received and message:sent events
|
|
3
|
+
* to automatically extract and store facts in archival memory.
|
|
4
|
+
*
|
|
5
|
+
* No reliance on the agent calling tools — memory happens passively.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { appendRecord } from "./archival.js";
|
|
9
|
+
import { indexEmbedding } from "./embedding.js";
|
|
10
|
+
import { extractTriples, addTriple } from "./graph.js";
|
|
11
|
+
import { resolveWorkspace } from "./paths.js";
|
|
12
|
+
|
|
13
|
+
// Minimum message length to consider for fact extraction
|
|
14
|
+
const MIN_LENGTH = 20;
|
|
15
|
+
|
|
16
|
+
// Skip patterns — don't store these as facts
|
|
17
|
+
const SKIP_PATTERNS = [
|
|
18
|
+
/^(hi|hello|hey|ok|thanks|good morning|good night|早|晚安|你好|嗯|好的|谢谢)/i,
|
|
19
|
+
/^HEARTBEAT_OK$/,
|
|
20
|
+
/^\//, // slash commands
|
|
21
|
+
/^(yes|no|yeah|nah|sure|maybe)$/i,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// High-value content patterns — always store these
|
|
25
|
+
const HIGH_VALUE_PATTERNS = [
|
|
26
|
+
/\b(decided|decision|plan|scheduled|booked|bought|sold|paid|签|买|卖|预约|决定)\b/i,
|
|
27
|
+
/\b(doctor|lawyer|immigration|IRCC|IBKR|account|password|address|phone|email)\b/i,
|
|
28
|
+
/\b(remember|don't forget|提醒|记住|别忘)\b/i,
|
|
29
|
+
/\$\d{2,}/, // dollar amounts
|
|
30
|
+
/\b\d{4}-\d{2}-\d{2}\b/, // dates
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Entity inference (same as quality.js but lightweight)
|
|
34
|
+
const ENTITY_PATTERNS = [
|
|
35
|
+
[/\b(IBKR|invest|portfolio|HELOC|mortgage|bank|\$\d{3,})/i, "finance"],
|
|
36
|
+
[/\b(immigration|PR|IRCC|CBSA|visa|lawyer|律师)/i, "immigration"],
|
|
37
|
+
[/\b(doctor|医生|hospital|health|medication|药)/i, "health"],
|
|
38
|
+
[/\b(car|vehicle|Escalade|GX550|ES350|Tesla|tire|车)/i, "vehicles"],
|
|
39
|
+
[/\b(school|homework|exam|swimming|lesson|学校|课)/i, "education"],
|
|
40
|
+
[/\b(deploy|k3d|ArgoCD|kubectl|CI|cluster)/i, "infrastructure"],
|
|
41
|
+
[/\b(quant|trading|backtest|signal|strategy)/i, "quant"],
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
function inferEntity(text) {
|
|
45
|
+
for (const [pat, name] of ENTITY_PATTERNS) {
|
|
46
|
+
if (pat.test(text)) return name;
|
|
47
|
+
}
|
|
48
|
+
return "conversation";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function shouldCapture(content) {
|
|
52
|
+
if (!content || content.length < MIN_LENGTH) return false;
|
|
53
|
+
if (SKIP_PATTERNS.some((p) => p.test(content.trim()))) return false;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isHighValue(content) {
|
|
58
|
+
return HIGH_VALUE_PATTERNS.some((p) => p.test(content));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Process an incoming or outgoing message and auto-store if valuable.
|
|
63
|
+
*/
|
|
64
|
+
export function captureMessage(ws, content, source = "auto-capture") {
|
|
65
|
+
if (!shouldCapture(content)) return null;
|
|
66
|
+
|
|
67
|
+
const importance = isHighValue(content) ? 7 : 4;
|
|
68
|
+
const entity = inferEntity(content);
|
|
69
|
+
|
|
70
|
+
// Trim very long messages to first 500 chars
|
|
71
|
+
const trimmed = content.length > 500 ? content.slice(0, 497) + "..." : content;
|
|
72
|
+
|
|
73
|
+
const record = appendRecord(ws, {
|
|
74
|
+
content: trimmed,
|
|
75
|
+
entity,
|
|
76
|
+
tags: [source],
|
|
77
|
+
importance,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Background: index embedding + extract graph triples
|
|
81
|
+
indexEmbedding(ws, record).catch(() => {});
|
|
82
|
+
|
|
83
|
+
const triples = extractTriples(trimmed);
|
|
84
|
+
for (const t of triples) {
|
|
85
|
+
addTriple(ws, t.s, t.r, t.o, record.id);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return record;
|
|
89
|
+
}
|
package/package.json
CHANGED