@cognee/cognee-openclaw 2026.3.0 → 2026.3.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/README.md +201 -38
- package/dist/index.d.ts +8 -142
- package/dist/index.js +17 -813
- package/dist/index.js.map +1 -1
- package/dist/src/client.d.ts +74 -0
- package/dist/src/client.js +302 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/config.d.ts +21 -0
- package/dist/src/config.js +89 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/files.d.ts +6 -0
- package/dist/src/files.js +60 -0
- package/dist/src/files.js.map +1 -0
- package/dist/src/persistence.d.ts +17 -0
- package/dist/src/persistence.js +107 -0
- package/dist/src/persistence.js.map +1 -0
- package/dist/src/plugin.d.ts +9 -0
- package/dist/src/plugin.js +510 -0
- package/dist/src/plugin.js.map +1 -0
- package/dist/src/scope.d.ts +20 -0
- package/dist/src/scope.js +122 -0
- package/dist/src/scope.js.map +1 -0
- package/dist/src/sync.d.ts +18 -0
- package/dist/src/sync.js +219 -0
- package/dist/src/sync.js.map +1 -0
- package/dist/src/types.d.ts +80 -0
- package/dist/src/types.js +5 -0
- package/dist/src/types.js.map +1 -0
- package/openclaw.plugin.json +114 -13
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -1,816 +1,20 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
5
1
|
// ---------------------------------------------------------------------------
|
|
6
|
-
//
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
const DEFAULT_BASE_URL = "http://localhost:8000";
|
|
9
|
-
const DEFAULT_DATASET_NAME = "openclaw";
|
|
10
|
-
const DEFAULT_SEARCH_TYPE = "GRAPH_COMPLETION";
|
|
11
|
-
const DEFAULT_SEARCH_PROMPT = "";
|
|
12
|
-
const DEFAULT_DELETE_MODE = "soft";
|
|
13
|
-
const DEFAULT_MAX_RESULTS = 6;
|
|
14
|
-
const DEFAULT_MIN_SCORE = 0;
|
|
15
|
-
const DEFAULT_MAX_TOKENS = 512;
|
|
16
|
-
const DEFAULT_AUTO_RECALL = true;
|
|
17
|
-
const DEFAULT_AUTO_INDEX = true;
|
|
18
|
-
const DEFAULT_AUTO_COGNIFY = true;
|
|
19
|
-
const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
|
|
20
|
-
const DEFAULT_INGESTION_TIMEOUT_MS = 300_000; // 5 min for add/update (ingestion is slow)
|
|
21
|
-
const MAX_RETRIES = 2;
|
|
22
|
-
const RETRY_BASE_DELAY_MS = 3_000;
|
|
23
|
-
const STATE_PATH = join(homedir(), ".openclaw", "memory", "cognee", "datasets.json");
|
|
24
|
-
const SYNC_INDEX_PATH = join(homedir(), ".openclaw", "memory", "cognee", "sync-index.json");
|
|
25
|
-
/** Glob patterns for memory files, relative to workspace root. */
|
|
26
|
-
const MEMORY_FILE_PATTERNS = ["MEMORY.md", "memory"];
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// Helpers
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
function resolveEnvVars(value) {
|
|
31
|
-
return value.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
|
|
32
|
-
const envValue = process.env[envVar];
|
|
33
|
-
if (!envValue) {
|
|
34
|
-
throw new Error(`Environment variable ${envVar} is not set`);
|
|
35
|
-
}
|
|
36
|
-
return envValue;
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
function hashText(value) {
|
|
40
|
-
return createHash("sha256").update(value).digest("hex");
|
|
41
|
-
}
|
|
42
|
-
function resolveConfig(rawConfig) {
|
|
43
|
-
const raw = rawConfig && typeof rawConfig === "object" && !Array.isArray(rawConfig)
|
|
44
|
-
? rawConfig
|
|
45
|
-
: {};
|
|
46
|
-
const baseUrl = raw.baseUrl?.trim() || DEFAULT_BASE_URL;
|
|
47
|
-
const datasetName = raw.datasetName?.trim() || DEFAULT_DATASET_NAME;
|
|
48
|
-
const searchType = raw.searchType || DEFAULT_SEARCH_TYPE;
|
|
49
|
-
const searchPrompt = raw.searchPrompt || DEFAULT_SEARCH_PROMPT;
|
|
50
|
-
const deleteMode = raw.deleteMode === "hard" ? "hard" : DEFAULT_DELETE_MODE;
|
|
51
|
-
const maxResults = typeof raw.maxResults === "number" ? raw.maxResults : DEFAULT_MAX_RESULTS;
|
|
52
|
-
const minScore = typeof raw.minScore === "number" ? raw.minScore : DEFAULT_MIN_SCORE;
|
|
53
|
-
const maxTokens = typeof raw.maxTokens === "number" ? raw.maxTokens : DEFAULT_MAX_TOKENS;
|
|
54
|
-
const autoRecall = typeof raw.autoRecall === "boolean" ? raw.autoRecall : DEFAULT_AUTO_RECALL;
|
|
55
|
-
const autoIndex = typeof raw.autoIndex === "boolean" ? raw.autoIndex : DEFAULT_AUTO_INDEX;
|
|
56
|
-
const autoCognify = typeof raw.autoCognify === "boolean" ? raw.autoCognify : DEFAULT_AUTO_COGNIFY;
|
|
57
|
-
const requestTimeoutMs = typeof raw.requestTimeoutMs === "number" ? raw.requestTimeoutMs : DEFAULT_REQUEST_TIMEOUT_MS;
|
|
58
|
-
const ingestionTimeoutMs = typeof raw.ingestionTimeoutMs === "number" ? raw.ingestionTimeoutMs : DEFAULT_INGESTION_TIMEOUT_MS;
|
|
59
|
-
const apiKey = raw.apiKey && raw.apiKey.length > 0
|
|
60
|
-
? resolveEnvVars(raw.apiKey)
|
|
61
|
-
: process.env.COGNEE_API_KEY || "";
|
|
62
|
-
const username = raw.username?.trim() || process.env.COGNEE_USERNAME || "";
|
|
63
|
-
const password = raw.password?.trim() || process.env.COGNEE_PASSWORD || "";
|
|
64
|
-
return {
|
|
65
|
-
baseUrl,
|
|
66
|
-
apiKey,
|
|
67
|
-
username,
|
|
68
|
-
password,
|
|
69
|
-
datasetName,
|
|
70
|
-
searchType,
|
|
71
|
-
searchPrompt,
|
|
72
|
-
deleteMode,
|
|
73
|
-
maxResults,
|
|
74
|
-
minScore,
|
|
75
|
-
maxTokens,
|
|
76
|
-
autoRecall,
|
|
77
|
-
autoIndex,
|
|
78
|
-
autoCognify,
|
|
79
|
-
requestTimeoutMs,
|
|
80
|
-
ingestionTimeoutMs,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
// Persistence — dataset state & sync index
|
|
85
|
-
// ---------------------------------------------------------------------------
|
|
86
|
-
async function loadDatasetState() {
|
|
87
|
-
try {
|
|
88
|
-
const raw = await fs.readFile(STATE_PATH, "utf-8");
|
|
89
|
-
const parsed = JSON.parse(raw);
|
|
90
|
-
if (!parsed || typeof parsed !== "object")
|
|
91
|
-
return {};
|
|
92
|
-
return parsed;
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
if (error.code === "ENOENT")
|
|
96
|
-
return {};
|
|
97
|
-
throw error;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
async function saveDatasetState(state) {
|
|
101
|
-
await fs.mkdir(dirname(STATE_PATH), { recursive: true });
|
|
102
|
-
await fs.writeFile(STATE_PATH, JSON.stringify(state, null, 2), "utf-8");
|
|
103
|
-
}
|
|
104
|
-
async function loadSyncIndex() {
|
|
105
|
-
try {
|
|
106
|
-
const raw = await fs.readFile(SYNC_INDEX_PATH, "utf-8");
|
|
107
|
-
const parsed = JSON.parse(raw);
|
|
108
|
-
if (!parsed || typeof parsed !== "object") {
|
|
109
|
-
return { entries: {} };
|
|
110
|
-
}
|
|
111
|
-
const record = parsed;
|
|
112
|
-
record.entries ??= {};
|
|
113
|
-
return record;
|
|
114
|
-
}
|
|
115
|
-
catch (error) {
|
|
116
|
-
if (error.code === "ENOENT") {
|
|
117
|
-
return { entries: {} };
|
|
118
|
-
}
|
|
119
|
-
throw error;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
async function saveSyncIndex(state) {
|
|
123
|
-
await fs.mkdir(dirname(SYNC_INDEX_PATH), { recursive: true });
|
|
124
|
-
await fs.writeFile(SYNC_INDEX_PATH, JSON.stringify(state, null, 2), "utf-8");
|
|
125
|
-
}
|
|
126
|
-
// ---------------------------------------------------------------------------
|
|
127
|
-
// File collection — scan workspace for memory markdown files
|
|
128
|
-
// ---------------------------------------------------------------------------
|
|
129
|
-
async function collectMemoryFiles(workspaceDir) {
|
|
130
|
-
const files = [];
|
|
131
|
-
for (const pattern of MEMORY_FILE_PATTERNS) {
|
|
132
|
-
const target = resolve(workspaceDir, pattern);
|
|
133
|
-
try {
|
|
134
|
-
const stat = await fs.stat(target);
|
|
135
|
-
if (stat.isFile() && target.endsWith(".md")) {
|
|
136
|
-
const content = await fs.readFile(target, "utf-8");
|
|
137
|
-
files.push({
|
|
138
|
-
path: relative(workspaceDir, target),
|
|
139
|
-
absPath: target,
|
|
140
|
-
content,
|
|
141
|
-
hash: hashText(content),
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
else if (stat.isDirectory()) {
|
|
145
|
-
const entries = await scanDir(target, workspaceDir);
|
|
146
|
-
files.push(...entries);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
catch (error) {
|
|
150
|
-
if (error.code !== "ENOENT") {
|
|
151
|
-
throw error;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return files;
|
|
156
|
-
}
|
|
157
|
-
async function scanDir(dir, workspaceDir) {
|
|
158
|
-
const files = [];
|
|
159
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
160
|
-
for (const entry of entries) {
|
|
161
|
-
const absPath = join(dir, entry.name);
|
|
162
|
-
if (entry.isDirectory()) {
|
|
163
|
-
const nested = await scanDir(absPath, workspaceDir);
|
|
164
|
-
files.push(...nested);
|
|
165
|
-
}
|
|
166
|
-
else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
167
|
-
const content = await fs.readFile(absPath, "utf-8");
|
|
168
|
-
files.push({
|
|
169
|
-
path: relative(workspaceDir, absPath),
|
|
170
|
-
absPath,
|
|
171
|
-
content,
|
|
172
|
-
hash: hashText(content),
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return files;
|
|
177
|
-
}
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
// Cognee HTTP client
|
|
180
|
-
// ---------------------------------------------------------------------------
|
|
181
|
-
class CogneeClient {
|
|
182
|
-
baseUrl;
|
|
183
|
-
apiKey;
|
|
184
|
-
username;
|
|
185
|
-
password;
|
|
186
|
-
timeoutMs;
|
|
187
|
-
ingestionTimeoutMs;
|
|
188
|
-
authToken;
|
|
189
|
-
loginPromise;
|
|
190
|
-
constructor(baseUrl, apiKey, username, password, timeoutMs = 30_000, ingestionTimeoutMs = DEFAULT_INGESTION_TIMEOUT_MS) {
|
|
191
|
-
this.baseUrl = baseUrl;
|
|
192
|
-
this.apiKey = apiKey;
|
|
193
|
-
this.username = username;
|
|
194
|
-
this.password = password;
|
|
195
|
-
this.timeoutMs = timeoutMs;
|
|
196
|
-
this.ingestionTimeoutMs = ingestionTimeoutMs;
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Authenticate with Cognee via /api/v1/auth/login.
|
|
200
|
-
* Falls back to default local dev credentials when none are configured.
|
|
201
|
-
*/
|
|
202
|
-
async login() {
|
|
203
|
-
const user = this.username || "default_user@example.com";
|
|
204
|
-
const pass = this.password || "default_password";
|
|
205
|
-
const controller = new AbortController();
|
|
206
|
-
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
207
|
-
try {
|
|
208
|
-
const response = await fetch(`${this.baseUrl}/api/v1/auth/login`, {
|
|
209
|
-
method: "POST",
|
|
210
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
211
|
-
body: new URLSearchParams({ username: user, password: pass }),
|
|
212
|
-
signal: controller.signal,
|
|
213
|
-
});
|
|
214
|
-
if (!response.ok) {
|
|
215
|
-
const errorText = await response.text();
|
|
216
|
-
throw new Error(`Cognee login failed (${response.status}): ${errorText}`);
|
|
217
|
-
}
|
|
218
|
-
const data = (await response.json());
|
|
219
|
-
this.authToken = data.access_token ?? data.token;
|
|
220
|
-
if (!this.authToken) {
|
|
221
|
-
throw new Error("Cognee login succeeded but no token in response");
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
finally {
|
|
225
|
-
clearTimeout(timeout);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Ensure the client is authenticated (login once, reuse token).
|
|
230
|
-
*/
|
|
231
|
-
async ensureAuth() {
|
|
232
|
-
if (this.authToken || this.apiKey)
|
|
233
|
-
return;
|
|
234
|
-
if (!this.loginPromise) {
|
|
235
|
-
this.loginPromise = this.login().catch((err) => {
|
|
236
|
-
this.loginPromise = undefined;
|
|
237
|
-
throw err;
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
return this.loginPromise;
|
|
241
|
-
}
|
|
242
|
-
buildHeaders() {
|
|
243
|
-
if (this.apiKey) {
|
|
244
|
-
return {
|
|
245
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
246
|
-
"X-Api-Key": this.apiKey,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
if (this.authToken) {
|
|
250
|
-
return { Authorization: `Bearer ${this.authToken}` };
|
|
251
|
-
}
|
|
252
|
-
return {};
|
|
253
|
-
}
|
|
254
|
-
async fetchJson(path, init, timeoutMs = this.timeoutMs, retries = MAX_RETRIES) {
|
|
255
|
-
await this.ensureAuth();
|
|
256
|
-
let lastError;
|
|
257
|
-
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
258
|
-
if (attempt > 0) {
|
|
259
|
-
const delay = RETRY_BASE_DELAY_MS * 2 ** (attempt - 1);
|
|
260
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
261
|
-
}
|
|
262
|
-
const controller = new AbortController();
|
|
263
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
264
|
-
try {
|
|
265
|
-
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
266
|
-
...init,
|
|
267
|
-
headers: { ...this.buildHeaders(), ...init.headers },
|
|
268
|
-
signal: controller.signal,
|
|
269
|
-
});
|
|
270
|
-
// On 401, try re-login once and retry
|
|
271
|
-
if (response.status === 401 && !this.apiKey) {
|
|
272
|
-
clearTimeout(timer);
|
|
273
|
-
this.authToken = undefined;
|
|
274
|
-
this.loginPromise = undefined;
|
|
275
|
-
await this.ensureAuth();
|
|
276
|
-
const retryController = new AbortController();
|
|
277
|
-
const retryTimeout = setTimeout(() => retryController.abort(), timeoutMs);
|
|
278
|
-
try {
|
|
279
|
-
const retryResponse = await fetch(`${this.baseUrl}${path}`, {
|
|
280
|
-
...init,
|
|
281
|
-
headers: { ...this.buildHeaders(), ...init.headers },
|
|
282
|
-
signal: retryController.signal,
|
|
283
|
-
});
|
|
284
|
-
if (!retryResponse.ok) {
|
|
285
|
-
const errorText = await retryResponse.text();
|
|
286
|
-
throw new Error(`Cognee request failed (${retryResponse.status}): ${errorText}`);
|
|
287
|
-
}
|
|
288
|
-
return (await retryResponse.json());
|
|
289
|
-
}
|
|
290
|
-
finally {
|
|
291
|
-
clearTimeout(retryTimeout);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
if (!response.ok) {
|
|
295
|
-
const errorText = await response.text();
|
|
296
|
-
throw new Error(`Cognee request failed (${response.status}): ${errorText}`);
|
|
297
|
-
}
|
|
298
|
-
return (await response.json());
|
|
299
|
-
}
|
|
300
|
-
catch (error) {
|
|
301
|
-
clearTimeout(timer);
|
|
302
|
-
const isTimeout = error instanceof DOMException ||
|
|
303
|
-
(error instanceof Error && error.name === "AbortError");
|
|
304
|
-
if (isTimeout && attempt < retries) {
|
|
305
|
-
lastError = error;
|
|
306
|
-
continue;
|
|
307
|
-
}
|
|
308
|
-
throw error;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
throw lastError;
|
|
312
|
-
}
|
|
313
|
-
async add(params) {
|
|
314
|
-
const formData = new FormData();
|
|
315
|
-
const hash8 = createHash("sha256").update(params.data).digest("hex").slice(0, 8);
|
|
316
|
-
const fileName = `openclaw-memory-${hash8}.txt`;
|
|
317
|
-
formData.append("data", new Blob([params.data], { type: "text/plain" }), fileName);
|
|
318
|
-
formData.append("datasetName", params.datasetName);
|
|
319
|
-
if (params.datasetId) {
|
|
320
|
-
formData.append("datasetId", params.datasetId);
|
|
321
|
-
}
|
|
322
|
-
const data = await this.fetchJson("/api/v1/add", { method: "POST", body: formData }, this.ingestionTimeoutMs);
|
|
323
|
-
let dataId = this.extractDataId(data.data_id ?? data.data_ingestion_info);
|
|
324
|
-
if (!dataId && data.dataset_id) {
|
|
325
|
-
dataId = await this.resolveDataIdFromDataset(data.dataset_id, fileName);
|
|
326
|
-
}
|
|
327
|
-
if (!dataId) {
|
|
328
|
-
console.warn("cognee-openclaw: add response missing data_id and dataset lookup failed", JSON.stringify({
|
|
329
|
-
keys: Object.keys(data),
|
|
330
|
-
data_id: data.data_id ?? null,
|
|
331
|
-
data_ingestion_info: data.data_ingestion_info ?? null,
|
|
332
|
-
}, null, 2));
|
|
333
|
-
}
|
|
334
|
-
return {
|
|
335
|
-
datasetId: data.dataset_id,
|
|
336
|
-
datasetName: data.dataset_name,
|
|
337
|
-
dataId,
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
async update(params) {
|
|
341
|
-
const query = new URLSearchParams({
|
|
342
|
-
data_id: params.dataId,
|
|
343
|
-
dataset_id: params.datasetId,
|
|
344
|
-
});
|
|
345
|
-
const formData = new FormData();
|
|
346
|
-
const hash8 = createHash("sha256").update(params.data).digest("hex").slice(0, 8);
|
|
347
|
-
const fileName = `openclaw-memory-${hash8}.txt`;
|
|
348
|
-
formData.append("data", new Blob([params.data], { type: "text/plain" }), fileName);
|
|
349
|
-
const data = await this.fetchJson(`/api/v1/update?${query.toString()}`, { method: "PATCH", body: formData }, this.ingestionTimeoutMs);
|
|
350
|
-
let dataId = this.extractDataId(data.data_id ?? data.data_ingestion_info);
|
|
351
|
-
if (!dataId) {
|
|
352
|
-
dataId = await this.resolveDataIdFromDataset(params.datasetId, fileName);
|
|
353
|
-
}
|
|
354
|
-
return {
|
|
355
|
-
datasetId: data.dataset_id,
|
|
356
|
-
datasetName: data.dataset_name,
|
|
357
|
-
dataId,
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Query the dataset's data items and find a matching entry by filename.
|
|
362
|
-
* Used as fallback when add/update responses don't include a usable data_id.
|
|
363
|
-
*/
|
|
364
|
-
async resolveDataIdFromDataset(datasetId, fileName) {
|
|
365
|
-
try {
|
|
366
|
-
const items = await this.fetchJson(`/api/v1/datasets/${datasetId}/data`, {
|
|
367
|
-
method: "GET",
|
|
368
|
-
});
|
|
369
|
-
if (!Array.isArray(items))
|
|
370
|
-
return undefined;
|
|
371
|
-
const match = items.find((item) => item.name === fileName.replace(/\.txt$/, ""));
|
|
372
|
-
return match?.id;
|
|
373
|
-
}
|
|
374
|
-
catch {
|
|
375
|
-
return undefined;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
async delete(params) {
|
|
379
|
-
try {
|
|
380
|
-
const query = new URLSearchParams({
|
|
381
|
-
data_id: params.dataId,
|
|
382
|
-
dataset_id: params.datasetId,
|
|
383
|
-
mode: params.mode ?? "soft",
|
|
384
|
-
});
|
|
385
|
-
await this.fetchJson(`/api/v1/delete?${query.toString()}`, {
|
|
386
|
-
method: "DELETE",
|
|
387
|
-
});
|
|
388
|
-
return {
|
|
389
|
-
datasetId: params.datasetId,
|
|
390
|
-
dataId: params.dataId,
|
|
391
|
-
deleted: true,
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
catch (error) {
|
|
395
|
-
return {
|
|
396
|
-
datasetId: params.datasetId,
|
|
397
|
-
dataId: params.dataId,
|
|
398
|
-
deleted: false,
|
|
399
|
-
error: error instanceof Error ? error.message : String(error),
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
async cognify(params = {}) {
|
|
404
|
-
return this.fetchJson("/api/v1/cognify", {
|
|
405
|
-
method: "POST",
|
|
406
|
-
headers: { "Content-Type": "application/json" },
|
|
407
|
-
body: JSON.stringify({ datasetIds: params.datasetIds, runInBackground: true }),
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
async search(params) {
|
|
411
|
-
const data = await this.fetchJson("/api/v1/search", {
|
|
412
|
-
method: "POST",
|
|
413
|
-
headers: { "Content-Type": "application/json" },
|
|
414
|
-
body: JSON.stringify({
|
|
415
|
-
query: params.queryText,
|
|
416
|
-
searchType: params.searchType,
|
|
417
|
-
datasetIds: params.datasetIds,
|
|
418
|
-
max_tokens: params.maxTokens,
|
|
419
|
-
...(params.searchPrompt ? { systemPrompt: params.searchPrompt } : {}),
|
|
420
|
-
}),
|
|
421
|
-
});
|
|
422
|
-
return this.normalizeSearchResults(data);
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Normalize Cognee search response to consistent format.
|
|
426
|
-
* Cognee returns a direct array of strings: ["answer text here"]
|
|
427
|
-
* We convert to: [{ id, text, score }]
|
|
428
|
-
*/
|
|
429
|
-
normalizeSearchResults(data) {
|
|
430
|
-
// Handle direct array (Cognee's actual format)
|
|
431
|
-
if (Array.isArray(data)) {
|
|
432
|
-
return data.map((item, index) => {
|
|
433
|
-
if (typeof item === "string") {
|
|
434
|
-
return { id: `result-${index}`, text: item, score: 1 };
|
|
435
|
-
}
|
|
436
|
-
if (item && typeof item === "object") {
|
|
437
|
-
const record = item;
|
|
438
|
-
return {
|
|
439
|
-
id: typeof record.id === "string" ? record.id : `result-${index}`,
|
|
440
|
-
text: typeof record.text === "string" ? record.text : JSON.stringify(record),
|
|
441
|
-
score: typeof record.score === "number" ? record.score : 1,
|
|
442
|
-
metadata: record.metadata,
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
return { id: `result-${index}`, text: String(item), score: 1 };
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
// Handle wrapped format { results: [...] }
|
|
449
|
-
if (data && typeof data === "object" && "results" in data) {
|
|
450
|
-
return this.normalizeSearchResults(data.results);
|
|
451
|
-
}
|
|
452
|
-
return [];
|
|
453
|
-
}
|
|
454
|
-
extractDataId(value) {
|
|
455
|
-
if (!value)
|
|
456
|
-
return undefined;
|
|
457
|
-
if (typeof value === "string")
|
|
458
|
-
return value;
|
|
459
|
-
if (Array.isArray(value)) {
|
|
460
|
-
for (const entry of value) {
|
|
461
|
-
const id = this.extractDataId(entry);
|
|
462
|
-
if (id)
|
|
463
|
-
return id;
|
|
464
|
-
}
|
|
465
|
-
return undefined;
|
|
466
|
-
}
|
|
467
|
-
if (typeof value !== "object")
|
|
468
|
-
return undefined;
|
|
469
|
-
const record = value;
|
|
470
|
-
if (typeof record.data_id === "string")
|
|
471
|
-
return record.data_id;
|
|
472
|
-
return this.extractDataId(record.data_ingestion_info);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
// ---------------------------------------------------------------------------
|
|
476
|
-
// Unified sync logic
|
|
477
|
-
//
|
|
478
|
-
// For each memory file:
|
|
479
|
-
// - New file (no sync index entry) → add + cognify
|
|
480
|
-
// - Changed file with dataId → update (no re-cognify)
|
|
481
|
-
// - Changed file without dataId → add + cognify
|
|
482
|
-
// - Unchanged file → skip
|
|
483
|
-
// - Deleted file (in index, not on disk) → delete + cognify
|
|
2
|
+
// @cognee/cognee-openclaw — main entry point
|
|
484
3
|
//
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
if (existing?.dataId && datasetId) {
|
|
502
|
-
try {
|
|
503
|
-
const updateResponse = await client.update({
|
|
504
|
-
dataId: existing.dataId,
|
|
505
|
-
datasetId,
|
|
506
|
-
data: dataWithMetadata,
|
|
507
|
-
});
|
|
508
|
-
const newDataId = updateResponse.dataId;
|
|
509
|
-
if (!newDataId) {
|
|
510
|
-
logger.warn?.(`cognee-openclaw: update for ${file.path} succeeded but could not resolve new data_id`);
|
|
511
|
-
}
|
|
512
|
-
syncIndex.entries[file.path] = { hash: file.hash, dataId: newDataId };
|
|
513
|
-
syncIndex.datasetId = datasetId;
|
|
514
|
-
syncIndex.datasetName = cfg.datasetName;
|
|
515
|
-
result.updated++;
|
|
516
|
-
logger.info?.(`cognee-openclaw: updated ${file.path}`);
|
|
517
|
-
continue; // Success, move to next file
|
|
518
|
-
}
|
|
519
|
-
catch (updateError) {
|
|
520
|
-
// If update fails (404/409 - document not found), fall back to add
|
|
521
|
-
const errorMsg = updateError instanceof Error ? updateError.message : String(updateError);
|
|
522
|
-
if (errorMsg.includes("404") || errorMsg.includes("409") || errorMsg.includes("not found")) {
|
|
523
|
-
logger.info?.(`cognee-openclaw: update failed for ${file.path}, falling back to add`);
|
|
524
|
-
// Clear the stale dataId and fall through to add
|
|
525
|
-
delete existing.dataId;
|
|
526
|
-
}
|
|
527
|
-
else {
|
|
528
|
-
throw updateError; // Re-throw other errors
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
// New file, or changed file without dataId, or update failed → add
|
|
533
|
-
const response = await client.add({
|
|
534
|
-
data: dataWithMetadata,
|
|
535
|
-
datasetName: cfg.datasetName,
|
|
536
|
-
datasetId,
|
|
537
|
-
});
|
|
538
|
-
if (response.datasetId && response.datasetId !== datasetId) {
|
|
539
|
-
datasetId = response.datasetId;
|
|
540
|
-
// Persist dataset ID mapping
|
|
541
|
-
const state = await loadDatasetState();
|
|
542
|
-
state[cfg.datasetName] = response.datasetId;
|
|
543
|
-
await saveDatasetState(state);
|
|
544
|
-
}
|
|
545
|
-
syncIndex.entries[file.path] = {
|
|
546
|
-
hash: file.hash,
|
|
547
|
-
dataId: response.dataId,
|
|
548
|
-
};
|
|
549
|
-
syncIndex.datasetId = datasetId;
|
|
550
|
-
syncIndex.datasetName = cfg.datasetName;
|
|
551
|
-
needsCognify = true;
|
|
552
|
-
result.added++;
|
|
553
|
-
logger.info?.(`cognee-openclaw: added ${file.path}`);
|
|
554
|
-
}
|
|
555
|
-
catch (error) {
|
|
556
|
-
result.errors++;
|
|
557
|
-
logger.warn?.(`cognee-openclaw: failed to sync ${file.path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
// Handle deletions: remove from Cognee any files no longer present
|
|
561
|
-
const currentPaths = new Set(fullFiles.map(f => f.path));
|
|
562
|
-
for (const [path, entry] of Object.entries(syncIndex.entries)) {
|
|
563
|
-
if (!currentPaths.has(path) && entry.dataId && datasetId) {
|
|
564
|
-
const deleteResult = await client.delete({
|
|
565
|
-
dataId: entry.dataId,
|
|
566
|
-
datasetId,
|
|
567
|
-
mode: cfg.deleteMode,
|
|
568
|
-
});
|
|
569
|
-
if (deleteResult.deleted) {
|
|
570
|
-
result.deleted++;
|
|
571
|
-
delete syncIndex.entries[path];
|
|
572
|
-
logger.info?.(`cognee-openclaw: deleted ${path}`);
|
|
573
|
-
}
|
|
574
|
-
else {
|
|
575
|
-
const isNotFound = deleteResult.error && (deleteResult.error.includes("404") || deleteResult.error.includes("409") || deleteResult.error.includes("not found"));
|
|
576
|
-
if (isNotFound) {
|
|
577
|
-
result.deleted++;
|
|
578
|
-
delete syncIndex.entries[path];
|
|
579
|
-
logger.info?.(`cognee-openclaw: deleted ${path} (already removed from Cognee)`);
|
|
580
|
-
}
|
|
581
|
-
else {
|
|
582
|
-
result.errors++;
|
|
583
|
-
logger.warn?.(`cognee-openclaw: failed to delete ${path}${deleteResult.error ? `: ${deleteResult.error}` : ""}`);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
// Cognify only after adds (updates re-process inline; hard-deletes clean up graph nodes inline)
|
|
589
|
-
if (needsCognify && cfg.autoCognify && datasetId) {
|
|
590
|
-
try {
|
|
591
|
-
await client.cognify({ datasetIds: [datasetId] });
|
|
592
|
-
logger.info?.("cognee-openclaw: cognify dispatched");
|
|
593
|
-
}
|
|
594
|
-
catch (error) {
|
|
595
|
-
logger.warn?.(`cognee-openclaw: cognify failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
// Save sync index to disk
|
|
599
|
-
await saveSyncIndex(syncIndex);
|
|
600
|
-
return { ...result, datasetId };
|
|
601
|
-
}
|
|
602
|
-
// ---------------------------------------------------------------------------
|
|
603
|
-
// Plugin registration
|
|
604
|
-
// ---------------------------------------------------------------------------
|
|
605
|
-
const memoryCogneePlugin = {
|
|
606
|
-
id: "cognee-openclaw",
|
|
607
|
-
name: "Memory (Cognee)",
|
|
608
|
-
description: "Cognee-backed memory: indexes workspace memory files, auto-recalls before agent runs",
|
|
609
|
-
kind: "memory",
|
|
610
|
-
register(api) {
|
|
611
|
-
const cfg = resolveConfig(api.pluginConfig);
|
|
612
|
-
const client = new CogneeClient(cfg.baseUrl, cfg.apiKey, cfg.username, cfg.password, cfg.requestTimeoutMs, cfg.ingestionTimeoutMs);
|
|
613
|
-
let datasetId;
|
|
614
|
-
let syncIndex = { entries: {} };
|
|
615
|
-
let syncIndexReady = false;
|
|
616
|
-
let resolvedWorkspaceDir; // Set by service/CLI, used by hooks
|
|
617
|
-
// Load persisted state on startup
|
|
618
|
-
const stateReady = Promise.all([
|
|
619
|
-
loadDatasetState()
|
|
620
|
-
.then((state) => {
|
|
621
|
-
datasetId = state[cfg.datasetName];
|
|
622
|
-
})
|
|
623
|
-
.catch((error) => {
|
|
624
|
-
api.logger.warn?.(`cognee-openclaw: failed to load dataset state: ${String(error)}`);
|
|
625
|
-
}),
|
|
626
|
-
loadSyncIndex()
|
|
627
|
-
.then((state) => {
|
|
628
|
-
syncIndex = state;
|
|
629
|
-
syncIndexReady = true;
|
|
630
|
-
if (!datasetId && state.datasetId && state.datasetName === cfg.datasetName) {
|
|
631
|
-
datasetId = state.datasetId;
|
|
632
|
-
}
|
|
633
|
-
})
|
|
634
|
-
.catch((error) => {
|
|
635
|
-
api.logger.warn?.(`cognee-openclaw: failed to load sync index: ${String(error)}`);
|
|
636
|
-
}),
|
|
637
|
-
]);
|
|
638
|
-
// Helper: run sync with a given workspace dir
|
|
639
|
-
async function runSync(workspaceDir, logger) {
|
|
640
|
-
await stateReady;
|
|
641
|
-
const files = await collectMemoryFiles(workspaceDir);
|
|
642
|
-
if (files.length === 0) {
|
|
643
|
-
logger.info?.("cognee-openclaw: no memory files found");
|
|
644
|
-
return { added: 0, updated: 0, skipped: 0, errors: 0, deleted: 0 };
|
|
645
|
-
}
|
|
646
|
-
logger.info?.(`cognee-openclaw: found ${files.length} memory file(s), syncing...`);
|
|
647
|
-
const result = await syncFiles(client, files, files, syncIndex, cfg, logger);
|
|
648
|
-
if (result.datasetId) {
|
|
649
|
-
datasetId = result.datasetId;
|
|
650
|
-
}
|
|
651
|
-
return result;
|
|
652
|
-
}
|
|
653
|
-
// ------------------------------------------------------------------
|
|
654
|
-
// CLI: openclaw cognee index / openclaw cognee status
|
|
655
|
-
// ------------------------------------------------------------------
|
|
656
|
-
api.registerCli((ctx) => {
|
|
657
|
-
const cognee = ctx.program.command("cognee").description("Cognee memory management");
|
|
658
|
-
const resolvedWorkspaceDir = ctx.workspaceDir || process.cwd();
|
|
659
|
-
cognee
|
|
660
|
-
.command("index")
|
|
661
|
-
.description("Sync memory files to Cognee (add new, update changed, skip unchanged)")
|
|
662
|
-
.action(async () => {
|
|
663
|
-
const result = await runSync(resolvedWorkspaceDir, ctx.logger);
|
|
664
|
-
const summary = `Sync complete: ${result.added} added, ${result.updated} updated, ${result.deleted} deleted, ${result.skipped} unchanged, ${result.errors} errors`;
|
|
665
|
-
ctx.logger.info?.(summary);
|
|
666
|
-
console.log(summary);
|
|
667
|
-
});
|
|
668
|
-
cognee
|
|
669
|
-
.command("status")
|
|
670
|
-
.description("Show Cognee sync state (files indexed, dataset info)")
|
|
671
|
-
.action(async () => {
|
|
672
|
-
await stateReady;
|
|
673
|
-
const entryCount = Object.keys(syncIndex.entries).length;
|
|
674
|
-
const entriesWithDataId = Object.values(syncIndex.entries).filter((e) => e.dataId).length;
|
|
675
|
-
const files = await collectMemoryFiles(resolvedWorkspaceDir);
|
|
676
|
-
let dirty = 0;
|
|
677
|
-
let newCount = 0;
|
|
678
|
-
for (const file of files) {
|
|
679
|
-
const existing = syncIndex.entries[file.path];
|
|
680
|
-
if (!existing) {
|
|
681
|
-
newCount++;
|
|
682
|
-
}
|
|
683
|
-
else if (existing.hash !== file.hash) {
|
|
684
|
-
dirty++;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
const lines = [
|
|
688
|
-
`Dataset: ${syncIndex.datasetName ?? cfg.datasetName}`,
|
|
689
|
-
`Dataset ID: ${datasetId ?? syncIndex.datasetId ?? "(not set)"}`,
|
|
690
|
-
`Indexed files: ${entryCount} (${entriesWithDataId} with data ID)`,
|
|
691
|
-
`Workspace files: ${files.length}`,
|
|
692
|
-
`New (unindexed): ${newCount}`,
|
|
693
|
-
`Changed (dirty): ${dirty}`,
|
|
694
|
-
`Sync index: ${SYNC_INDEX_PATH}`,
|
|
695
|
-
];
|
|
696
|
-
console.log(lines.join("\n"));
|
|
697
|
-
});
|
|
698
|
-
}, { commands: ["cognee"] });
|
|
699
|
-
// ------------------------------------------------------------------
|
|
700
|
-
// Auto-sync on startup
|
|
701
|
-
// ------------------------------------------------------------------
|
|
702
|
-
if (cfg.autoIndex) {
|
|
703
|
-
api.registerService({
|
|
704
|
-
id: "cognee-auto-sync",
|
|
705
|
-
async start(ctx) {
|
|
706
|
-
// Store workspace dir for use in hooks
|
|
707
|
-
resolvedWorkspaceDir = ctx.workspaceDir || process.cwd();
|
|
708
|
-
try {
|
|
709
|
-
const result = await runSync(resolvedWorkspaceDir, ctx.logger);
|
|
710
|
-
ctx.logger.info?.(`cognee-openclaw: auto-sync complete: ${result.added} added, ${result.updated} updated, ${result.deleted} deleted, ${result.skipped} unchanged`);
|
|
711
|
-
}
|
|
712
|
-
catch (error) {
|
|
713
|
-
ctx.logger.warn?.(`cognee-openclaw: auto-sync failed: ${String(error)}`);
|
|
714
|
-
}
|
|
715
|
-
},
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
// ------------------------------------------------------------------
|
|
719
|
-
// Auto-recall: inject memories before each agent run
|
|
720
|
-
// ------------------------------------------------------------------
|
|
721
|
-
if (cfg.autoRecall) {
|
|
722
|
-
api.on("before_agent_start", async (event, ctx) => {
|
|
723
|
-
// Wait for state to load (fixes race condition on first agent run)
|
|
724
|
-
await stateReady;
|
|
725
|
-
if (!event.prompt || event.prompt.length < 5) {
|
|
726
|
-
api.logger.debug?.("cognee-openclaw: skipping recall (prompt too short)");
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
if (!datasetId) {
|
|
730
|
-
api.logger.debug?.("cognee-openclaw: skipping recall (no datasetId)");
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
try {
|
|
734
|
-
const results = await client.search({
|
|
735
|
-
queryText: event.prompt,
|
|
736
|
-
searchType: cfg.searchType,
|
|
737
|
-
datasetIds: [datasetId],
|
|
738
|
-
searchPrompt: cfg.searchPrompt,
|
|
739
|
-
maxTokens: cfg.maxTokens,
|
|
740
|
-
});
|
|
741
|
-
const filtered = results
|
|
742
|
-
.filter((result) => result.score >= cfg.minScore)
|
|
743
|
-
.slice(0, cfg.maxResults);
|
|
744
|
-
if (filtered.length === 0) {
|
|
745
|
-
api.logger.debug?.("cognee-openclaw: search returned no results above minScore");
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
const payload = JSON.stringify(filtered.map((result) => ({
|
|
749
|
-
id: result.id,
|
|
750
|
-
score: result.score,
|
|
751
|
-
text: result.text,
|
|
752
|
-
metadata: result.metadata,
|
|
753
|
-
})), null, 2);
|
|
754
|
-
api.logger.info?.(`cognee-openclaw: injecting ${filtered.length} memories for session ${ctx.sessionKey ?? "unknown"}`);
|
|
755
|
-
return {
|
|
756
|
-
prependContext: `<cognee_memories>\nRelevant memories:\n${payload}\n</cognee_memories>`,
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
catch (error) {
|
|
760
|
-
api.logger.warn?.(`cognee-openclaw: recall failed: ${String(error)}`);
|
|
761
|
-
}
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
// ------------------------------------------------------------------
|
|
765
|
-
// Post-agent sync: detect file changes and sync to Cognee
|
|
766
|
-
// ------------------------------------------------------------------
|
|
767
|
-
if (cfg.autoIndex) {
|
|
768
|
-
api.on("agent_end", async (event, ctx) => {
|
|
769
|
-
// Only sync if the agent succeeded
|
|
770
|
-
if (!event.success)
|
|
771
|
-
return;
|
|
772
|
-
await stateReady;
|
|
773
|
-
// Reload sync index from disk to pick up changes made by CLI or other processes
|
|
774
|
-
try {
|
|
775
|
-
const freshIndex = await loadSyncIndex();
|
|
776
|
-
syncIndex.entries = freshIndex.entries;
|
|
777
|
-
if (freshIndex.datasetId)
|
|
778
|
-
syncIndex.datasetId = freshIndex.datasetId;
|
|
779
|
-
if (freshIndex.datasetName)
|
|
780
|
-
syncIndex.datasetName = freshIndex.datasetName;
|
|
781
|
-
}
|
|
782
|
-
catch {
|
|
783
|
-
// Fall through with existing in-memory index
|
|
784
|
-
}
|
|
785
|
-
// Need workspace dir to find memory files
|
|
786
|
-
const workspaceDir = resolvedWorkspaceDir || process.cwd();
|
|
787
|
-
try {
|
|
788
|
-
// Collect current files and find changed ones
|
|
789
|
-
const files = await collectMemoryFiles(workspaceDir);
|
|
790
|
-
const changedFiles = files.filter((f) => {
|
|
791
|
-
const existing = syncIndex.entries[f.path];
|
|
792
|
-
return !existing || existing.hash !== f.hash;
|
|
793
|
-
});
|
|
794
|
-
// Check for deletions: files tracked in the sync index but no longer on disk
|
|
795
|
-
const currentPaths = new Set(files.map(f => f.path));
|
|
796
|
-
const hasDeletedFiles = Object.keys(syncIndex.entries).some(p => !currentPaths.has(p));
|
|
797
|
-
if (changedFiles.length === 0 && !hasDeletedFiles)
|
|
798
|
-
return;
|
|
799
|
-
api.logger.info?.(`cognee-openclaw: detected ${changedFiles.length} changed file(s)${hasDeletedFiles ? " + deletions" : ""}, syncing...`);
|
|
800
|
-
const result = await syncFiles(client, changedFiles, files, syncIndex, cfg, api.logger);
|
|
801
|
-
if (result.datasetId) {
|
|
802
|
-
datasetId = result.datasetId;
|
|
803
|
-
}
|
|
804
|
-
api.logger.info?.(`cognee-openclaw: post-agent sync: ${result.added} added, ${result.updated} updated, ${result.deleted} deleted`);
|
|
805
|
-
}
|
|
806
|
-
catch (error) {
|
|
807
|
-
api.logger.warn?.(`cognee-openclaw: post-agent sync failed: ${String(error)}`);
|
|
808
|
-
}
|
|
809
|
-
});
|
|
810
|
-
}
|
|
811
|
-
},
|
|
812
|
-
};
|
|
813
|
-
export default memoryCogneePlugin;
|
|
814
|
-
// Exports for testing
|
|
815
|
-
export { CogneeClient, syncFiles };
|
|
4
|
+
// Fix #1: The plugin is now split into focused modules under src/.
|
|
5
|
+
// This file is a thin re-export barrel for the public API.
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
export { default } from "./src/plugin.js";
|
|
8
|
+
// Client (shared with skills plugin — Fix #2)
|
|
9
|
+
export { CogneeHttpClient } from "./src/client.js";
|
|
10
|
+
// Sync logic
|
|
11
|
+
export { syncFiles, syncFilesScoped } from "./src/sync.js";
|
|
12
|
+
// Scope utilities
|
|
13
|
+
export { matchGlob, routeFileToScope, datasetNameForScope, isMultiScopeEnabled } from "./src/scope.js";
|
|
14
|
+
// Config
|
|
15
|
+
export { resolveConfig } from "./src/config.js";
|
|
16
|
+
// Files
|
|
17
|
+
export { collectMemoryFiles, hashText } from "./src/files.js";
|
|
18
|
+
// Persistence
|
|
19
|
+
export { loadDatasetState, saveDatasetState, loadSyncIndex, saveSyncIndex, loadScopedSyncIndexes, saveScopedSyncIndexes, migrateLegacyIndex, } from "./src/persistence.js";
|
|
816
20
|
//# sourceMappingURL=index.js.map
|