@evermind-ai/openclaw-plugin 1.1.0 → 1.3.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/src/compaction.js DELETED
@@ -1,85 +0,0 @@
1
- /**
2
- * Compaction Handler Module
3
- * Handles session compaction evaluation and participation
4
- */
5
-
6
- /**
7
- * @typedef {import("./types.js").EverMemOSConfig} EverMemOSConfig
8
- * @typedef {import("./types.js").Logger} Logger
9
- */
10
-
11
- /**
12
- * Compaction evaluation parameters
13
- * @typedef {Object} CompactionEvalParams
14
- * @property {Array} messages
15
- * @property {number} tokenCount
16
- * @property {number} turnCount
17
- * @property {number} lastAssembleTime
18
- */
19
-
20
- /**
21
- * Compaction decision result
22
- * @typedef {Object} CompactionDecision
23
- * @property {boolean} shouldCompact
24
- * @property {string} reason
25
- * @property {string} [memoryStrategy]
26
- */
27
-
28
- /**
29
- * Handles session compaction evaluation and participation
30
- * Evaluates when to compact the conversation context
31
- */
32
- export class CompactionHandler {
33
- /**
34
- * @param {EverMemOSConfig} cfg
35
- * @param {Logger} logger
36
- */
37
- constructor(cfg, logger) {
38
- this.cfg = cfg;
39
- this.log = logger;
40
-
41
- // Configurable thresholds
42
- this.compactThresholdTokens = 8000;
43
- this.compactThresholdTurns = 10;
44
- }
45
-
46
- /**
47
- * Evaluate whether session should be compacted
48
- * @param {CompactionEvalParams} params
49
- * @returns {Promise<CompactionDecision>}
50
- */
51
- async evaluate({ messages, tokenCount, turnCount, lastAssembleTime }) {
52
- // Compact if token count exceeds threshold
53
- if (tokenCount > this.compactThresholdTokens) {
54
- return {
55
- shouldCompact: true,
56
- reason: `token count (${tokenCount}) exceeds threshold (${this.compactThresholdTokens})`,
57
- memoryStrategy: "consolidate_to_long_term",
58
- };
59
- }
60
-
61
- // Compact if turn count exceeds threshold
62
- if (turnCount > this.compactThresholdTurns) {
63
- return {
64
- shouldCompact: true,
65
- reason: `turn count (${turnCount}) exceeds threshold (${this.compactThresholdTurns})`,
66
- memoryStrategy: "consolidate_to_long_term",
67
- };
68
- }
69
-
70
- // Compact if no recent memory assembly (stale session)
71
- const timeSinceAssemble = Date.now() - lastAssembleTime;
72
- if (lastAssembleTime > 0 && timeSinceAssemble > 30 * 60 * 1000) { // 30 minutes
73
- return {
74
- shouldCompact: true,
75
- reason: `session inactive for ${Math.round(timeSinceAssemble / 60000)} minutes`,
76
- memoryStrategy: "consolidate_to_long_term",
77
- };
78
- }
79
-
80
- return {
81
- shouldCompact: false,
82
- reason: "token and turn counts within acceptable range",
83
- };
84
- }
85
- }
@@ -1,283 +0,0 @@
1
- /**
2
- * EverMemOS ContextEngine Implementation for OpenClaw 3.8+
3
- * Implements full-lifecycle memory management: bootstrap, assemble, afterTurn, compact, subagent
4
- */
5
-
6
- import { resolveConfig } from "./config.js";
7
- import { toText, isSessionResetPrompt, collectMessages } from "./message-utils.js";
8
- import { ContextAssembler } from "./assembler.js";
9
- import { LifecycleManager } from "./lifecycle.js";
10
- import { CompactionHandler } from "./compaction.js";
11
- import { SubagentTracker } from "./subagent.js";
12
-
13
- /**
14
- * @typedef {import("./types.js").EverMemOSConfig} EverMemOSConfig
15
- * @typedef {import("./types.js").Logger} Logger
16
- * @typedef {import("./types.js").BootstrapContext} BootstrapContext
17
- * @typedef {import("./types.js").AssembleContext} AssembleContext
18
- * @typedef {import("./types.js").AssembleResult} AssembleResult
19
- * @typedef {import("./types.js").AfterTurnContext} AfterTurnContext
20
- * @typedef {import("./types.js").CompactContext} CompactContext
21
- * @typedef {import("./types.js").CompactResult} CompactResult
22
- * @typedef {import("./types.js").PrepareSubagentContext} PrepareSubagentContext
23
- * @typedef {import("./types.js").PrepareSubagentResult} PrepareSubagentResult
24
- * @typedef {import("./types.js").SubagentEndedContext} SubagentEndedContext
25
- */
26
-
27
- /**
28
- * EverMemOS ContextEngine implementation for OpenClaw 3.8+
29
- * Implements full-lifecycle memory management: bootstrap, assemble, afterTurn, compact, subagent
30
- */
31
- export class EverMemOSContextEngine {
32
- /**
33
- * @param {EverMemOSConfig} config
34
- * @param {Logger} logger
35
- */
36
- constructor(config, logger = console) {
37
- this.cfg = resolveConfig(config);
38
- this.log = logger;
39
-
40
- // Session state tracking
41
- this.turnCount = 0;
42
- this.lastAssembleTime = 0;
43
- this.pendingFlush = false;
44
-
45
- // Submodules
46
- this.assembler = new ContextAssembler(this.cfg, this.log);
47
- this.lifecycle = new LifecycleManager(this.cfg, this.log);
48
- this.compaction = new CompactionHandler(this.cfg, this.log);
49
- this.subagentTracker = new SubagentTracker(this.cfg, this.log);
50
- }
51
-
52
- /**
53
- * Bootstrap: Called once when plugin loads. Initialize connection and verify backend health.
54
- * @param {BootstrapContext} ctx
55
- * @returns {Promise<void>}
56
- */
57
- async bootstrap(ctx) {
58
- this.log("[evermemos] bootstrap: initializing EverMemOS connection");
59
- try {
60
- const result = await this._healthCheck();
61
- if (result?.status === "ok") {
62
- this.log("[evermemos] bootstrap: backend healthy");
63
- } else {
64
- this.log.warn("[evermemos] bootstrap: backend unhealthy, will degrade gracefully");
65
- }
66
- } catch (err) {
67
- this.log.warn(`[evermemos] bootstrap: health check failed: ${err.message}`);
68
- }
69
- }
70
-
71
- /**
72
- * Assemble: Build context from memories before agent processes the prompt.
73
- * This replaces the old before_agent_start hook with query-aware assembly.
74
- * @param {AssembleContext} ctx
75
- * @returns {Promise<AssembleResult>}
76
- */
77
- async assemble(ctx) {
78
- const { prompt, messages } = ctx;
79
- const query = toText(prompt);
80
-
81
- // Detect session reset (/new, /reset)
82
- if (isSessionResetPrompt(query)) {
83
- this.log("[evermemos] assemble: session reset detected, skipping search");
84
- this.pendingFlush = true;
85
- return { context: "", metadata: { skipped: "session_reset" } };
86
- }
87
-
88
- if (!query || query.length < 3) {
89
- return { context: "", metadata: { skipped: "empty_query" } };
90
- }
91
-
92
- try {
93
- this.lastAssembleTime = Date.now();
94
- const result = await this.assembler.assemble(query, messages, this.turnCount);
95
- this.log(`[evermemos] assemble: retrieved ${result.memoryCount} memories`);
96
- return {
97
- context: result.context,
98
- metadata: {
99
- memoryCount: result.memoryCount,
100
- retrieveMethod: this.cfg.retrieveMethod,
101
- turnCount: this.turnCount,
102
- },
103
- };
104
- } catch (err) {
105
- this.log.warn(`[evermemos] assemble: ${err.message}`);
106
- return { context: "", metadata: { error: err.message } };
107
- }
108
- }
109
-
110
- /**
111
- * AfterTurn: Called after each agent turn. Extract memories promptly.
112
- * This enables timely extraction instead of waiting for agent_end.
113
- * @param {AfterTurnContext} ctx
114
- * @returns {Promise<void>}
115
- */
116
- async afterTurn(ctx) {
117
- const { messages, success } = ctx;
118
- this.turnCount++;
119
-
120
- if (!success || !messages?.length) {
121
- this.log(`[evermemos] afterTurn: turn ${this.turnCount} skipped (no success/messages)`);
122
- return;
123
- }
124
-
125
- try {
126
- const collected = collectMessages(messages);
127
- if (!collected.length) {
128
- this.log(`[evermemos] afterTurn: turn ${this.turnCount} - no messages to save`);
129
- return;
130
- }
131
-
132
- await this.lifecycle.ingestTurn({
133
- userId: this.cfg.userId,
134
- groupId: this.cfg.groupId,
135
- messages: collected,
136
- turnCount: this.turnCount,
137
- flush: this.pendingFlush,
138
- });
139
-
140
- this.log(`[evermemos] afterTurn: turn ${this.turnCount} - saved ${collected.length} messages`);
141
-
142
- // Consume flush flag after successful save
143
- if (this.pendingFlush) {
144
- this.pendingFlush = false;
145
- this.log("[evermemos] afterTurn: flush flag consumed");
146
- }
147
- } catch (err) {
148
- this.log.warn(`[evermemos] afterTurn: ${err.message}`);
149
- }
150
- }
151
-
152
- /**
153
- * Compact: Participate in session compaction decision and memory consolidation.
154
- * @param {CompactContext} ctx
155
- * @returns {Promise<CompactResult>}
156
- */
157
- async compact(ctx) {
158
- const { messages, tokenCount } = ctx;
159
-
160
- this.log(`[evermemos] compact: evaluating compaction (tokens: ${tokenCount}, turns: ${this.turnCount})`);
161
-
162
- try {
163
- const decision = await this.compaction.evaluate({
164
- messages,
165
- tokenCount,
166
- turnCount: this.turnCount,
167
- lastAssembleTime: this.lastAssembleTime,
168
- });
169
-
170
- if (decision.shouldCompact) {
171
- this.log(`[evermemos] compact: compaction recommended - ${decision.reason}`);
172
- return {
173
- shouldCompact: true,
174
- reason: decision.reason,
175
- metadata: {
176
- turnCount: this.turnCount,
177
- memoryStrategy: decision.memoryStrategy,
178
- },
179
- };
180
- }
181
-
182
- this.log("[evermemos] compact: no compaction needed");
183
- return { shouldCompact: false, reason: decision.reason };
184
- } catch (err) {
185
- this.log.warn(`[evermemos] compact: ${err.message}`);
186
- return { shouldCompact: false, reason: "error evaluating compaction" };
187
- }
188
- }
189
-
190
- /**
191
- * PrepareSubagentSpawn: Called before spawning a subagent.
192
- * @param {PrepareSubagentContext} ctx
193
- * @returns {Promise<PrepareSubagentResult>}
194
- */
195
- async prepareSubagentSpawn(ctx) {
196
- const { subagentId, parentMessages } = ctx;
197
-
198
- this.log(`[evermemos] prepareSubagentSpawn: tracking subagent ${subagentId}`);
199
-
200
- // Register the subagent
201
- this.subagentTracker.register(subagentId, {
202
- subagentType: ctx.subagentType,
203
- parentTurnCount: this.turnCount,
204
- });
205
-
206
- try {
207
- // Collect relevant memories for the subagent
208
- const query = toText(ctx.prompt);
209
- const context = query
210
- ? await this.assembler.assembleForSubagent(query)
211
- : "";
212
-
213
- return {
214
- prependContext: context || "",
215
- metadata: {
216
- subagentId,
217
- parentTurnCount: this.turnCount,
218
- },
219
- };
220
- } catch (err) {
221
- this.log.warn(`[evermemos] prepareSubagentSpawn: ${err.message}`);
222
- return { prependContext: "", metadata: {} };
223
- }
224
- }
225
-
226
- /**
227
- * OnSubagentEnded: Called after a subagent completes.
228
- * @param {SubagentEndedContext} ctx
229
- * @returns {Promise<void>}
230
- */
231
- async onSubagentEnded(ctx) {
232
- const { subagentId, messages, success } = ctx;
233
-
234
- this.log(`[evermemos] onSubagentEnded: subagent ${subagentId} ended, success=${success}`);
235
-
236
- // Unregister the subagent
237
- this.subagentTracker.unregister(subagentId);
238
-
239
- try {
240
- if (success && messages?.length) {
241
- const collected = collectMessages(messages);
242
- if (collected.length) {
243
- await this.lifecycle.ingestTurn({
244
- userId: this.cfg.userId,
245
- groupId: this.cfg.groupId,
246
- messages: collected,
247
- turnCount: this.turnCount,
248
- flush: false,
249
- });
250
- this.log(`[evermemos] onSubagentEnded: saved ${collected.length} messages from subagent`);
251
- }
252
- }
253
- } catch (err) {
254
- this.log.warn(`[evermemos] onSubagentEnded: ${err.message}`);
255
- }
256
- }
257
-
258
- /**
259
- * Internal: Verify backend health
260
- * @private
261
- * @returns {Promise<{status: string}|null>}
262
- */
263
- async _healthCheck() {
264
- const { serverUrl } = this.cfg;
265
- try {
266
- const controller = new AbortController();
267
- const timeout = setTimeout(() => controller.abort(), 5000);
268
-
269
- const response = await fetch(`${serverUrl}/api/v0/health`, {
270
- method: "GET",
271
- signal: controller.signal,
272
- });
273
- clearTimeout(timeout);
274
-
275
- if (response.ok) {
276
- return await response.json();
277
- }
278
- return { status: "error", httpStatus: response.status };
279
- } catch {
280
- return { status: "error" };
281
- }
282
- }
283
- }
package/src/lifecycle.js DELETED
@@ -1,65 +0,0 @@
1
- /**
2
- * Lifecycle Manager Module
3
- * Handles turn-level memory ingestion
4
- */
5
-
6
- import { saveMemories } from "./memory-api.js";
7
-
8
- /**
9
- * @typedef {import("./types.js").EverMemOSConfig} EverMemOSConfig
10
- * @typedef {import("./types.js").Logger} Logger
11
- */
12
-
13
- /**
14
- * Turn ingestion parameters
15
- * @typedef {Object} IngestTurnParams
16
- * @property {string} userId
17
- * @property {string} groupId
18
- * @property {Array} messages
19
- * @property {number} turnCount
20
- * @property {boolean} flush
21
- */
22
-
23
- /**
24
- * Handles turn-level memory ingestion
25
- * Extracts and saves memories after each conversation turn
26
- */
27
- export class LifecycleManager {
28
- /**
29
- * @param {EverMemOSConfig} cfg
30
- * @param {Logger} logger
31
- */
32
- constructor(cfg, logger) {
33
- this.cfg = cfg;
34
- this.log = logger;
35
- }
36
-
37
- /**
38
- * Ingest messages from a single turn
39
- * @param {IngestTurnParams} params
40
- * @returns {Promise<void>}
41
- */
42
- async ingestTurn({ userId, groupId, messages, turnCount, flush }) {
43
- await saveMemories(this.cfg, {
44
- userId,
45
- groupId,
46
- messages,
47
- flush,
48
- });
49
- }
50
-
51
- /**
52
- * Flush pending memories to storage
53
- * Called when session ends or reset is detected
54
- * @returns {Promise<void>}
55
- */
56
- async flush() {
57
- // Force save with flush=true
58
- await saveMemories(this.cfg, {
59
- userId: this.cfg.userId,
60
- groupId: this.cfg.groupId,
61
- messages: [],
62
- flush: true,
63
- });
64
- }
65
- }