metainsight-context-engine 0.0.8 → 0.0.9

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/engine.ts ADDED
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Cloud Context Engine
3
+ *
4
+ * A lightweight ContextEngine implementation whose primary responsibilities are:
5
+ * 1. **Bootstrap** — Eagerly initialize COS and trigger initial local memory sync.
6
+ * 2. **Local Memory Sync** — Periodically sync local memory files (MEMORY.md,
7
+ * daily logs, config) to cloud storage via `afterTurn`.
8
+ *
9
+ * All other ContextEngine lifecycle methods are no-ops / pass-throughs:
10
+ * - `assemble` — pass-through (runtime handles history via `limitHistoryTurns`,
11
+ * memory recall is handled by `before_prompt_build` hook in index.ts).
12
+ * - `compact` — no-op (owns compaction flag to prevent legacy compactor).
13
+ * - `ingest` / `ingestBatch` — no-ops.
14
+ *
15
+ * By setting `ownsCompaction: true`, the runtime will bypass the legacy
16
+ * `compactEmbeddedPiSessionDirect()` and delegate entirely to our `compact()`.
17
+ *
18
+ * The engine receives a `clientFactory` function that returns a `CosOperations`
19
+ * instance asynchronously — this allows lazy COS bootstrap (bucket/dataset/binding
20
+ * initialization happens on first use, not at plugin registration time).
21
+ */
22
+
23
+ import type { AgentMessage } from '@mariozechner/pi-agent-core';
24
+ import type {
25
+ AssembleResult,
26
+ BootstrapResult,
27
+ CompactResult,
28
+ ContextEngine,
29
+ ContextEngineInfo,
30
+ ContextEngineRuntimeContext,
31
+ IngestBatchResult,
32
+ IngestResult,
33
+ } from 'openclaw/plugin-sdk/core';
34
+
35
+ import type { CosOperations } from './cos-operations.js';
36
+ import { syncLocalMemoryToCloud, type LocalMemorySyncConfig } from './local-memory-sync.js';
37
+
38
+ // ============================================================================
39
+ // Config
40
+ // ============================================================================
41
+
42
+ export interface CloudEngineConfig {
43
+ localMemorySync: LocalMemorySyncConfig;
44
+ /** When false, local memory files (MEMORY.md / daily logs) are NOT synced to cloud. Default: true */
45
+ localMemorySyncEnabled: boolean;
46
+ }
47
+
48
+ // ============================================================================
49
+ // Logger (subset used by the engine)
50
+ // ============================================================================
51
+
52
+ interface Logger {
53
+ info: (...args: unknown[]) => void;
54
+ warn: (...args: unknown[]) => void;
55
+ }
56
+
57
+ // ============================================================================
58
+ // Constants
59
+ // ============================================================================
60
+
61
+ /** Run local memory sync every N turns (to pick up file changes). */
62
+ const LOCAL_MEMORY_SYNC_INTERVAL = 5;
63
+
64
+ // ============================================================================
65
+ // Implementation
66
+ // ============================================================================
67
+
68
+ export class CloudContextEngine implements ContextEngine {
69
+ readonly info: ContextEngineInfo = {
70
+ id: 'metainsight-context-engine',
71
+ name: 'MetaInsight Context Engine',
72
+ version: '1.0.0',
73
+ ownsCompaction: true,
74
+ };
75
+
76
+ private readonly clientFactory: () => Promise<CosOperations>;
77
+ private cachedClient: CosOperations | null = null;
78
+ private readonly config: CloudEngineConfig;
79
+ private readonly logger: Logger;
80
+ /** Counter for incremental local memory sync (every N turns). */
81
+ private turnsSinceLastSync = 0;
82
+
83
+ constructor(clientFactory: () => Promise<CosOperations>, config: CloudEngineConfig, logger: Logger) {
84
+ this.clientFactory = clientFactory;
85
+ this.config = config;
86
+ this.logger = logger;
87
+ }
88
+
89
+ /**
90
+ * Lazily initialize and cache the CosOperations instance (triggers COS bootstrap on first call).
91
+ */
92
+ private async getClient(): Promise<CosOperations> {
93
+ if (!this.cachedClient) {
94
+ this.cachedClient = await this.clientFactory();
95
+ }
96
+ return this.cachedClient;
97
+ }
98
+
99
+ // ==========================================================================
100
+ // bootstrap — session initialization
101
+ // ==========================================================================
102
+
103
+ async bootstrap(params: {
104
+ sessionId: string;
105
+ sessionKey?: string;
106
+ sessionFile: string;
107
+ }): Promise<BootstrapResult> {
108
+ // Eagerly trigger COS bootstrap so errors surface early
109
+ try {
110
+ await this.getClient();
111
+ } catch (err) {
112
+ this.logger.warn(`cloud-engine: COS bootstrap failed during session init: ${String(err)}`);
113
+ }
114
+
115
+ // Sync local memory files to cloud (non-blocking background task)
116
+ if (this.config.localMemorySyncEnabled && this.config.localMemorySync.enabled) {
117
+ void this.runLocalMemorySync(params.sessionFile);
118
+ }
119
+
120
+ return { bootstrapped: true };
121
+ }
122
+
123
+ // ==========================================================================
124
+ // ingest — no-op (conversation memory upload disabled)
125
+ // ==========================================================================
126
+
127
+ async ingest(_params: {
128
+ sessionId: string;
129
+ sessionKey?: string;
130
+ message: AgentMessage;
131
+ isHeartbeat?: boolean;
132
+ }): Promise<IngestResult> {
133
+ return { ingested: false };
134
+ }
135
+
136
+ // ==========================================================================
137
+ // ingestBatch — no-op (conversation memory upload disabled)
138
+ // ==========================================================================
139
+
140
+ async ingestBatch(_params: {
141
+ sessionId: string;
142
+ sessionKey?: string;
143
+ messages: AgentMessage[];
144
+ isHeartbeat?: boolean;
145
+ }): Promise<IngestBatchResult> {
146
+ return { ingestedCount: 0 };
147
+ }
148
+
149
+ // ==========================================================================
150
+ // assemble — pass-through (memory recall handled by before_prompt_build hook)
151
+ // ==========================================================================
152
+
153
+ async assemble(params: {
154
+ sessionId: string;
155
+ sessionKey?: string;
156
+ messages: AgentMessage[];
157
+ tokenBudget?: number;
158
+ }): Promise<AssembleResult> {
159
+ // Pass-through: runtime handles history limiting via limitHistoryTurns(),
160
+ // and memory recall is handled by the before_prompt_build hook in index.ts
161
+ // which has full control over the system prompt.
162
+ return {
163
+ messages: params.messages,
164
+ estimatedTokens: 0,
165
+ };
166
+ }
167
+
168
+ // ==========================================================================
169
+ // compact — no-op (owns compaction to prevent legacy compactor from running)
170
+ // ==========================================================================
171
+
172
+ async compact(params: {
173
+ sessionId: string;
174
+ sessionKey?: string;
175
+ sessionFile: string;
176
+ tokenBudget?: number;
177
+ force?: boolean;
178
+ currentTokenCount?: number;
179
+ compactionTarget?: 'budget' | 'threshold';
180
+ customInstructions?: string;
181
+ runtimeContext?: ContextEngineRuntimeContext;
182
+ }): Promise<CompactResult> {
183
+ return {
184
+ ok: true,
185
+ compacted: true,
186
+ reason: 'cloud-engine: compaction handled by runtime limitHistoryTurns()',
187
+ result: {
188
+ tokensBefore: params.currentTokenCount ?? 0,
189
+ tokensAfter: 0,
190
+ },
191
+ };
192
+ }
193
+
194
+ // ==========================================================================
195
+ // afterTurn — periodic local memory sync only
196
+ // ==========================================================================
197
+
198
+ async afterTurn(params: {
199
+ sessionId: string;
200
+ sessionKey?: string;
201
+ sessionFile: string;
202
+ messages: AgentMessage[];
203
+ prePromptMessageCount: number;
204
+ autoCompactionSummary?: string;
205
+ isHeartbeat?: boolean;
206
+ tokenBudget?: number;
207
+ runtimeContext?: ContextEngineRuntimeContext;
208
+ }): Promise<void> {
209
+ if (params.isHeartbeat) {
210
+ return;
211
+ }
212
+
213
+ // Run local memory sync periodically (every N turns) to pick up file changes
214
+ if (this.config.localMemorySyncEnabled && this.config.localMemorySync.enabled) {
215
+ this.turnsSinceLastSync += 1;
216
+ if (this.turnsSinceLastSync >= LOCAL_MEMORY_SYNC_INTERVAL) {
217
+ this.turnsSinceLastSync = 0;
218
+ void this.runLocalMemorySync(params.sessionFile);
219
+ }
220
+ }
221
+ }
222
+
223
+ // ==========================================================================
224
+ // dispose
225
+ // ==========================================================================
226
+
227
+ async dispose(): Promise<void> {
228
+ this.cachedClient = null;
229
+ }
230
+
231
+ // ==========================================================================
232
+ // Private helpers
233
+ // ==========================================================================
234
+
235
+ /**
236
+ * Run local memory sync in the background (non-blocking).
237
+ * Uploads MEMORY.md, daily logs, and config to cloud storage.
238
+ */
239
+ private async runLocalMemorySync(sessionFile: string): Promise<void> {
240
+ try {
241
+ const client = await this.getClient();
242
+ const result = await syncLocalMemoryToCloud(
243
+ client,
244
+ sessionFile,
245
+ this.config.localMemorySync,
246
+ this.logger,
247
+ );
248
+
249
+ if (result.uploaded > 0) {
250
+ this.logger.info(
251
+ `cloud-engine: local memory sync — uploaded=${result.uploaded}, skipped=${result.skipped}`,
252
+ );
253
+ }
254
+ } catch (err) {
255
+ this.logger.warn(`cloud-engine: local memory sync failed: ${String(err)}`);
256
+ }
257
+ }
258
+
259
+ }