memtap 3.2.0 → 4.0.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.ts +71 -189
- package/openclaw.plugin.json +24 -6
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -38,6 +38,9 @@ interface MemTapConfig {
|
|
|
38
38
|
apiKey?: string;
|
|
39
39
|
agentId?: string;
|
|
40
40
|
autoCapture?: boolean;
|
|
41
|
+
captureEnabled?: boolean;
|
|
42
|
+
captureMinLength?: number;
|
|
43
|
+
debug?: boolean;
|
|
41
44
|
bulletinOnBoot?: boolean;
|
|
42
45
|
bulletinTopics?: string[];
|
|
43
46
|
llmUrl?: string;
|
|
@@ -2023,207 +2026,86 @@ ${memoryContext}
|
|
|
2023
2026
|
}
|
|
2024
2027
|
);
|
|
2025
2028
|
|
|
2026
|
-
// ── Hook:
|
|
2029
|
+
// ── Hook: Server-Side Capture (message_completed) ──────────────────────────
|
|
2030
|
+
// v4.0.1: Extraction moved to server. Plugin posts user+assistant conversations to /capture.
|
|
2031
|
+
// We buffer user messages per conversation, then capture the pair when the assistant replies.
|
|
2027
2032
|
|
|
2033
|
+
const pendingUserMessages = new Map<string, { content: string; channel?: string; timestamp: string }>();
|
|
2034
|
+
|
|
2035
|
+
// Capture user's inbound message (store for pairing with assistant reply)
|
|
2028
2036
|
api.registerHook(
|
|
2029
|
-
'
|
|
2037
|
+
'message:preprocessed',
|
|
2030
2038
|
async (event: any) => {
|
|
2031
2039
|
const cfg = getConfig(api);
|
|
2032
|
-
if (!cfg.autoCapture) return;
|
|
2033
|
-
|
|
2034
|
-
const
|
|
2035
|
-
|
|
2036
|
-
if (content
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
(
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
// Attention-gated encoding - only encode if attention allows
|
|
2046
|
-
if (context && !shouldEncodeMemory(
|
|
2047
|
-
content,
|
|
2048
|
-
context.attentionLevel,
|
|
2049
|
-
context.emotionalContext,
|
|
2050
|
-
workingMemory?.cognitiveLoad || 0
|
|
2051
|
-
)) {
|
|
2052
|
-
return; // Skip encoding due to low attention/high cognitive load
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
const memories = await llmExtract(cfg, content);
|
|
2056
|
-
if (!memories.length) return;
|
|
2057
|
-
|
|
2058
|
-
let stored = 0;
|
|
2059
|
-
|
|
2060
|
-
for (const mem of memories) {
|
|
2061
|
-
if (!mem.content || mem.content.length < 10) continue;
|
|
2062
|
-
|
|
2063
|
-
try {
|
|
2064
|
-
// Calculate emotional weighting
|
|
2065
|
-
const memoryType = mem.type || 'fact';
|
|
2066
|
-
const emotionalWeight = EMOTIONAL_WEIGHTS[memoryType] || 1.0;
|
|
2067
|
-
const contextualWeight = context?.emotionalContext === 'excited' ? 1.2 :
|
|
2068
|
-
context?.emotionalContext === 'positive' ? 1.1 :
|
|
2069
|
-
context?.emotionalContext === 'negative' ? 1.15 : 1.0;
|
|
2070
|
-
|
|
2071
|
-
const finalImportance = (mem.importance ?? 5) * emotionalWeight * contextualWeight;
|
|
2072
|
-
|
|
2073
|
-
// Determine category (from LLM extraction or fallback)
|
|
2074
|
-
const memCategory = MEMORY_CATEGORIES.includes(mem.category as any)
|
|
2075
|
-
? mem.category
|
|
2076
|
-
: (memoryType === 'preference' ? 'preferences' :
|
|
2077
|
-
memoryType === 'decision' || memoryType === 'goal' || memoryType === 'task' ? 'project' :
|
|
2078
|
-
'technical');
|
|
2079
|
-
|
|
2080
|
-
// Enhanced memory with neuromimetic features
|
|
2081
|
-
const enhancedMem: Record<string, any> = {
|
|
2082
|
-
content: mem.content,
|
|
2083
|
-
type: memoryType,
|
|
2084
|
-
agent: currentAgent,
|
|
2085
|
-
importance: storeImportance(Math.min(10, finalImportance)),
|
|
2086
|
-
category: memCategory,
|
|
2087
|
-
tags: [
|
|
2088
|
-
...(mem.tags || []),
|
|
2089
|
-
'auto-captured',
|
|
2090
|
-
'neuromimetic',
|
|
2091
|
-
`category:${memCategory}`,
|
|
2092
|
-
...(context?.dominantTopic ? [`topic:${context.dominantTopic}`] : []),
|
|
2093
|
-
`engagement:${context?.userEngagement || 'unknown'}`,
|
|
2094
|
-
`attention:${context?.attentionLevel || 'unknown'}`,
|
|
2095
|
-
`emotion:${context?.emotionalContext || 'neutral'}`
|
|
2096
|
-
],
|
|
2097
|
-
source: 'plugin:neuromimetic-capture-v3.0',
|
|
2098
|
-
conversationContext: {
|
|
2099
|
-
dominantTopic: context?.dominantTopic,
|
|
2100
|
-
engagement: context?.userEngagement,
|
|
2101
|
-
queryCount: context?.memoryQueryCount || 0,
|
|
2102
|
-
attentionLevel: context?.attentionLevel,
|
|
2103
|
-
emotionalContext: context?.emotionalContext
|
|
2104
|
-
},
|
|
2105
|
-
neuromimeticData: {
|
|
2106
|
-
emotionalIntensity: contextualWeight,
|
|
2107
|
-
attentionStrength: context?.attentionLevel === 'flow' ? 1.0 :
|
|
2108
|
-
context?.attentionLevel === 'focused' ? 0.8 : 0.5,
|
|
2109
|
-
consolidationScore: 0.1, // Will increase with dream cycles
|
|
2110
|
-
retrievalCount: 0,
|
|
2111
|
-
lastAccess: Date.now()
|
|
2112
|
-
}
|
|
2113
|
-
};
|
|
2114
|
-
|
|
2115
|
-
// Create episodic memory entry
|
|
2116
|
-
if (context && memoryType === 'event') {
|
|
2117
|
-
const episodic = createEpisodicMemory(currentAgent, mem.content, context);
|
|
2118
|
-
const episodics = episodicMemories.get(currentAgent) || [];
|
|
2119
|
-
episodics.push(episodic);
|
|
2120
|
-
episodicMemories.set(currentAgent, episodics.slice(-100)); // Keep last 100
|
|
2121
|
-
}
|
|
2122
|
-
|
|
2123
|
-
await bbFetch(cfg, `${baseUrl(cfg)}/memories`, {
|
|
2124
|
-
method: 'POST',
|
|
2125
|
-
body: JSON.stringify(enhancedMem),
|
|
2126
|
-
});
|
|
2127
|
-
stored++;
|
|
2128
|
-
|
|
2129
|
-
// Cross-session memory sharing for MemTap agents
|
|
2130
|
-
if (currentAgent.includes('memtap') && mem.type === 'decision') {
|
|
2131
|
-
try {
|
|
2132
|
-
// Share important decisions across MemTap agent ecosystem
|
|
2133
|
-
const crossSessionMem = {
|
|
2134
|
-
...enhancedMem,
|
|
2135
|
-
agent: 'memtap-shared',
|
|
2136
|
-
tags: [...enhancedMem.tags, 'cross-session', `from:${currentAgent}`]
|
|
2137
|
-
};
|
|
2138
|
-
|
|
2139
|
-
await bbFetch(cfg, `${baseUrl(cfg)}/memories`, {
|
|
2140
|
-
method: 'POST',
|
|
2141
|
-
body: JSON.stringify(crossSessionMem)
|
|
2142
|
-
});
|
|
2143
|
-
} catch { /* cross-session sharing failed, not critical */ }
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
|
-
} catch { /* skip individual failures */ }
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
|
-
if (stored > 0) {
|
|
2150
|
-
// Update user profile with successful capture
|
|
2151
|
-
const profile = getUserProfile(currentAgent);
|
|
2152
|
-
profile.successfulRecalls += stored; // Treat captures as successes
|
|
2153
|
-
userProfiles.set(currentAgent, profile);
|
|
2154
|
-
|
|
2155
|
-
logger.info?.(`[memtap] Auto-captured ${stored} enhanced memories with context`) ??
|
|
2156
|
-
console.log(`[memtap] Auto-captured ${stored} enhanced memories with context`);
|
|
2157
|
-
}
|
|
2040
|
+
if (!cfg.captureEnabled && !cfg.autoCapture) return;
|
|
2041
|
+
|
|
2042
|
+
const ctx = event || event?.context || {};
|
|
2043
|
+
const content = ctx.content || ctx.bodyForAgent || ctx.body || '';
|
|
2044
|
+
if (!content || typeof content !== 'string') return;
|
|
2045
|
+
if (content === 'NO_REPLY' || content === 'HEARTBEAT_OK') return;
|
|
2046
|
+
|
|
2047
|
+
const conversationId = ctx.conversationId || ctx.from || ctx.to || 'default';
|
|
2048
|
+
pendingUserMessages.set(conversationId, {
|
|
2049
|
+
content,
|
|
2050
|
+
channel: ctx.channelId || ctx.provider,
|
|
2051
|
+
timestamp: new Date().toISOString(),
|
|
2052
|
+
});
|
|
2158
2053
|
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
for (const memId of surfaced.memoryIds) {
|
|
2168
|
-
// Check if the memory ID or content from proactive surfacing appears in the response
|
|
2169
|
-
if (contentLower.includes(memId.toLowerCase())) {
|
|
2170
|
-
usedIds.push(memId);
|
|
2171
|
-
}
|
|
2172
|
-
}
|
|
2054
|
+
// Clean up old pending entries after 10 min
|
|
2055
|
+
setTimeout(() => pendingUserMessages.delete(conversationId), 10 * 60 * 1000);
|
|
2056
|
+
},
|
|
2057
|
+
{
|
|
2058
|
+
name: 'memtap.capture-inbound',
|
|
2059
|
+
description: 'Buffer inbound user messages for capture pairing',
|
|
2060
|
+
}
|
|
2061
|
+
);
|
|
2173
2062
|
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
// Check if key phrases from the memory appear in the response
|
|
2181
|
-
const keyPhrases = memContent.split(/\s+/).filter((w: string) => w.length > 5).slice(0, 5);
|
|
2182
|
-
const matchCount = keyPhrases.filter((phrase: string) => contentLower.includes(phrase)).length;
|
|
2183
|
-
if (matchCount >= 2) {
|
|
2184
|
-
usedIds.push(memId);
|
|
2185
|
-
}
|
|
2186
|
-
} catch { /* skip */ }
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2063
|
+
// Capture assistant's outbound reply (pair with buffered user message, then POST to /capture)
|
|
2064
|
+
api.registerHook(
|
|
2065
|
+
'message:sent',
|
|
2066
|
+
async (event: any) => {
|
|
2067
|
+
const cfg = getConfig(api);
|
|
2068
|
+
if (!cfg.captureEnabled && !cfg.autoCapture) return;
|
|
2189
2069
|
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
body: JSON.stringify({
|
|
2197
|
-
boost: FORGETTING_CURVE.retrievalStrengthening * 1.5, // Extra boost for proactive hit
|
|
2198
|
-
lastAccess: Date.now(),
|
|
2199
|
-
attentionLevel: 'proactive-hit',
|
|
2200
|
-
reinforcement: {
|
|
2201
|
-
proactiveHit: true,
|
|
2202
|
-
adaptiveDecayRate: Math.max(0.001, (cfg.decayRate || FORGETTING_CURVE.baseDecayRate) * 0.5)
|
|
2203
|
-
}
|
|
2204
|
-
})
|
|
2205
|
-
});
|
|
2206
|
-
} catch { /* reinforcement failed, not critical */ }
|
|
2207
|
-
}
|
|
2070
|
+
const ctx = event || event?.context || {};
|
|
2071
|
+
const assistantContent = ctx.content || '';
|
|
2072
|
+
if (!assistantContent || typeof assistantContent !== 'string') return;
|
|
2073
|
+
if (assistantContent === 'NO_REPLY' || assistantContent === 'HEARTBEAT_OK') return;
|
|
2074
|
+
if (assistantContent.length < (cfg.captureMinLength || 80)) return;
|
|
2075
|
+
if (ctx.success === false) return;
|
|
2208
2076
|
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2077
|
+
const conversationId = ctx.conversationId || ctx.to || ctx.groupId || 'default';
|
|
2078
|
+
const buffered = pendingUserMessages.get(conversationId);
|
|
2079
|
+
const userMessage = buffered?.content;
|
|
2212
2080
|
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
}
|
|
2216
|
-
} catch { /* proactive feedback loop failed, not critical */ }
|
|
2081
|
+
// Clear buffer
|
|
2082
|
+
if (buffered) pendingUserMessages.delete(conversationId);
|
|
2217
2083
|
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2084
|
+
try {
|
|
2085
|
+
await bbFetch(cfg, `${baseUrl(cfg)}/capture`, {
|
|
2086
|
+
method: 'POST',
|
|
2087
|
+
body: JSON.stringify({
|
|
2088
|
+
agent: agentId(cfg, api),
|
|
2089
|
+
conversation: {
|
|
2090
|
+
userMessage,
|
|
2091
|
+
assistantMessage: assistantContent,
|
|
2092
|
+
context: {
|
|
2093
|
+
channel: ctx.channelId,
|
|
2094
|
+
conversationId,
|
|
2095
|
+
timestamp: new Date().toISOString(),
|
|
2096
|
+
},
|
|
2097
|
+
},
|
|
2098
|
+
}),
|
|
2099
|
+
});
|
|
2100
|
+
if (cfg.debug) console.log('[memtap] capture posted');
|
|
2101
|
+
} catch (err) {
|
|
2102
|
+
// Silent fail — capture is non-critical
|
|
2103
|
+
if (cfg.debug) console.error('[memtap] capture failed:', err);
|
|
2104
|
+
}
|
|
2223
2105
|
},
|
|
2224
2106
|
{
|
|
2225
|
-
name: 'memtap.
|
|
2226
|
-
description: '
|
|
2107
|
+
name: 'memtap.server-capture',
|
|
2108
|
+
description: 'Server-side memory extraction via /v1/capture',
|
|
2227
2109
|
}
|
|
2228
2110
|
);
|
|
2229
2111
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
"id": "memtap",
|
|
3
3
|
"name": "MemTap",
|
|
4
4
|
"kind": "memory",
|
|
5
|
-
"version": "
|
|
6
|
-
"description": "Graph-based long-term memory for OpenClaw agents
|
|
5
|
+
"version": "4.0.1",
|
|
6
|
+
"description": "Graph-based long-term memory for OpenClaw agents \u2014 semantic recall, GraphRAG, entity management, decision tracking, neural auto-capture, anomaly detection, consolidation, profiles, and adaptive decay.",
|
|
7
7
|
"defaultConfig": {
|
|
8
8
|
"serverUrl": "https://api.memtap.ai",
|
|
9
9
|
"autoCapture": true,
|
|
10
|
+
"captureEnabled": true,
|
|
11
|
+
"captureMinLength": 80,
|
|
10
12
|
"bulletinOnBoot": true
|
|
11
13
|
},
|
|
12
14
|
"configSchema": {
|
|
@@ -28,7 +30,19 @@
|
|
|
28
30
|
},
|
|
29
31
|
"autoCapture": {
|
|
30
32
|
"type": "boolean",
|
|
31
|
-
"description": "Automatically extract memories from conversations"
|
|
33
|
+
"description": "Automatically extract memories from conversations (alias for captureEnabled)"
|
|
34
|
+
},
|
|
35
|
+
"captureEnabled": {
|
|
36
|
+
"type": "boolean",
|
|
37
|
+
"description": "Enable server-side auto-capture of memories from conversations (default true)"
|
|
38
|
+
},
|
|
39
|
+
"captureMinLength": {
|
|
40
|
+
"type": "number",
|
|
41
|
+
"description": "Minimum message length to trigger capture (default 80 chars)"
|
|
42
|
+
},
|
|
43
|
+
"debug": {
|
|
44
|
+
"type": "boolean",
|
|
45
|
+
"description": "Enable debug logging for capture and other operations"
|
|
32
46
|
},
|
|
33
47
|
"bulletinOnBoot": {
|
|
34
48
|
"type": "boolean",
|
|
@@ -72,12 +86,16 @@
|
|
|
72
86
|
"properties": {
|
|
73
87
|
"include": {
|
|
74
88
|
"type": "array",
|
|
75
|
-
"items": {
|
|
89
|
+
"items": {
|
|
90
|
+
"type": "string"
|
|
91
|
+
},
|
|
76
92
|
"description": "Patterns to prioritize in recall (boost matching memories)"
|
|
77
93
|
},
|
|
78
94
|
"exclude": {
|
|
79
95
|
"type": "array",
|
|
80
|
-
"items": {
|
|
96
|
+
"items": {
|
|
97
|
+
"type": "string"
|
|
98
|
+
},
|
|
81
99
|
"description": "Patterns to exclude from recall results"
|
|
82
100
|
}
|
|
83
101
|
}
|
|
@@ -137,4 +155,4 @@
|
|
|
137
155
|
"helpText": "Include/exclude patterns for memory recall filtering"
|
|
138
156
|
}
|
|
139
157
|
}
|
|
140
|
-
}
|
|
158
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memtap",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "MemTap — Graph-based long-term memory plugin for OpenClaw agents. Knowledge graph with semantic recall, GraphRAG, entity management, decision tracking, and auto-capture.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openclaw",
|