@unblocklabs/unblock-memory 0.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/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +9 -0
- package/dist/src/analysis.d.ts +93 -0
- package/dist/src/analysis.js +406 -0
- package/dist/src/config.d.ts +8 -0
- package/dist/src/config.js +27 -0
- package/dist/src/contracts.d.ts +22 -0
- package/dist/src/contracts.js +1 -0
- package/dist/src/manager.d.ts +49 -0
- package/dist/src/manager.js +346 -0
- package/dist/src/plugin.d.ts +14 -0
- package/dist/src/plugin.js +325 -0
- package/dist/src/runtime.d.ts +24 -0
- package/dist/src/runtime.js +51 -0
- package/dist/src/sources.d.ts +13 -0
- package/dist/src/sources.js +95 -0
- package/openclaw.plugin.json +42 -0
- package/package.json +63 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { MemoryPluginCapability } from "openclaw/plugin-sdk/memory-host-core";
|
|
2
|
+
export type MemoryPluginRuntimeContract = NonNullable<MemoryPluginCapability["runtime"]>;
|
|
3
|
+
type ManagerLookup = Awaited<ReturnType<MemoryPluginRuntimeContract["getMemorySearchManager"]>>;
|
|
4
|
+
export type MemorySearchManagerContract = NonNullable<ManagerLookup["manager"]>;
|
|
5
|
+
export type MemoryProviderStatus = ReturnType<MemorySearchManagerContract["status"]>;
|
|
6
|
+
export type MemorySearchResult = Awaited<ReturnType<MemorySearchManagerContract["search"]>>[number];
|
|
7
|
+
export type MemoryEmbeddingProbeResult = Awaited<ReturnType<MemorySearchManagerContract["probeEmbeddingAvailability"]>>;
|
|
8
|
+
export type MemorySyncParams = Parameters<NonNullable<MemorySearchManagerContract["sync"]>>[0];
|
|
9
|
+
export type MemoryReadResult = {
|
|
10
|
+
status: "ok";
|
|
11
|
+
text: string;
|
|
12
|
+
path: string;
|
|
13
|
+
truncated?: boolean;
|
|
14
|
+
from?: number;
|
|
15
|
+
lines?: number;
|
|
16
|
+
nextFrom?: number;
|
|
17
|
+
} | {
|
|
18
|
+
status: "not_found";
|
|
19
|
+
text: "";
|
|
20
|
+
path: string;
|
|
21
|
+
};
|
|
22
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { QMDStore } from "@unblocklabs/qmd";
|
|
2
|
+
import { type AnalysisRunner, type MemoryAnalysisSummary, type MemoryClusterDetail, type MemoryClusterList, type MemoryReclusterOptions } from "./analysis.js";
|
|
3
|
+
import type { MemoryEmbeddingProbeResult, MemoryProviderStatus, MemoryReadResult, MemorySearchManagerContract, MemorySearchResult, MemorySyncParams } from "./contracts.js";
|
|
4
|
+
import { type ResolvedSource } from "./sources.js";
|
|
5
|
+
export type ManagerStore = Pick<QMDStore, "update" | "embed" | "getStatus" | "listCollections" | "searchLex" | "vsearch" | "get" | "getDocumentBody" | "close">;
|
|
6
|
+
export declare function enableSecureDelete(store: QMDStore): void;
|
|
7
|
+
export declare function cleanupRemovedDocuments(store: QMDStore, changedDocuments?: number): number;
|
|
8
|
+
export declare function pruneStaleCollections(store: QMDStore, configuredCollections: ReadonlySet<string>): Promise<number>;
|
|
9
|
+
export declare function buildReadResult(params: {
|
|
10
|
+
content: string;
|
|
11
|
+
path: string;
|
|
12
|
+
from?: number;
|
|
13
|
+
lines?: number;
|
|
14
|
+
}): MemoryReadResult;
|
|
15
|
+
export declare class QmdMemoryManager implements MemorySearchManagerContract {
|
|
16
|
+
#private;
|
|
17
|
+
constructor(params: {
|
|
18
|
+
dbPath: string;
|
|
19
|
+
workspaceDir: string;
|
|
20
|
+
sources: readonly ResolvedSource[];
|
|
21
|
+
storeFactory?: () => Promise<ManagerStore>;
|
|
22
|
+
analysisExecutable?: string;
|
|
23
|
+
analysisRunner?: AnalysisRunner;
|
|
24
|
+
});
|
|
25
|
+
start(): Promise<void>;
|
|
26
|
+
sync(params?: MemorySyncParams): Promise<void>;
|
|
27
|
+
recluster(options?: MemoryReclusterOptions, signal?: AbortSignal): Promise<MemoryAnalysisSummary>;
|
|
28
|
+
listClusters(limit?: number): Promise<MemoryClusterList>;
|
|
29
|
+
fetchCluster(params: {
|
|
30
|
+
clusterId: string;
|
|
31
|
+
topK?: number;
|
|
32
|
+
}): Promise<MemoryClusterDetail>;
|
|
33
|
+
search(query: string, opts?: {
|
|
34
|
+
maxResults?: number;
|
|
35
|
+
minScore?: number;
|
|
36
|
+
lexicalOnly?: boolean;
|
|
37
|
+
sources?: Array<"memory" | "sessions">;
|
|
38
|
+
signal?: AbortSignal;
|
|
39
|
+
}): Promise<MemorySearchResult[]>;
|
|
40
|
+
readFile(params: {
|
|
41
|
+
relPath: string;
|
|
42
|
+
from?: number;
|
|
43
|
+
lines?: number;
|
|
44
|
+
}): Promise<MemoryReadResult>;
|
|
45
|
+
status(): MemoryProviderStatus;
|
|
46
|
+
probeEmbeddingAvailability(): Promise<MemoryEmbeddingProbeResult>;
|
|
47
|
+
probeVectorAvailability(): Promise<boolean>;
|
|
48
|
+
close(): Promise<void>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import chokidar from "chokidar";
|
|
4
|
+
import { ensureMemoryAnalysisSchema, latestAnalysisRunId, markMemoryAnalysisStale, readAnalysisSummary, readCluster, readClusters, runAnalysisWorker, } from "./analysis.js";
|
|
5
|
+
import { parseSafeVirtualPath } from "./sources.js";
|
|
6
|
+
const DEFAULT_READ_LINES = 120;
|
|
7
|
+
const MAX_READ_CHARS = 12_000;
|
|
8
|
+
const WATCH_DEBOUNCE_MS = 250;
|
|
9
|
+
const qmdModule = import("@unblocklabs/qmd");
|
|
10
|
+
export function enableSecureDelete(store) {
|
|
11
|
+
store.internal.db.exec("PRAGMA secure_delete = ON");
|
|
12
|
+
}
|
|
13
|
+
export function cleanupRemovedDocuments(store, changedDocuments = 0) {
|
|
14
|
+
enableSecureDelete(store);
|
|
15
|
+
const cleaned = changedDocuments +
|
|
16
|
+
store.internal.deleteInactiveDocuments() +
|
|
17
|
+
store.internal.cleanupOrphanedVectors() +
|
|
18
|
+
store.internal.cleanupOrphanedContent();
|
|
19
|
+
if (cleaned > 0) {
|
|
20
|
+
store.internal.db.exec("INSERT INTO documents_fts(documents_fts) VALUES('optimize')");
|
|
21
|
+
store.internal.vacuumDatabase();
|
|
22
|
+
store.internal.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
23
|
+
}
|
|
24
|
+
return cleaned;
|
|
25
|
+
}
|
|
26
|
+
export async function pruneStaleCollections(store, configuredCollections) {
|
|
27
|
+
const staleCollections = (await store.getStatus()).collections
|
|
28
|
+
.map((collection) => collection.name)
|
|
29
|
+
.filter((name) => !configuredCollections.has(name));
|
|
30
|
+
if (staleCollections.length === 0)
|
|
31
|
+
return 0;
|
|
32
|
+
enableSecureDelete(store);
|
|
33
|
+
const deleteDocuments = store.internal.db.prepare("DELETE FROM documents WHERE collection = ?");
|
|
34
|
+
const removed = store.internal.db.transaction(() => {
|
|
35
|
+
let count = 0;
|
|
36
|
+
for (const collection of staleCollections)
|
|
37
|
+
count += deleteDocuments.run(collection).changes;
|
|
38
|
+
return count;
|
|
39
|
+
}).immediate();
|
|
40
|
+
cleanupRemovedDocuments(store, removed);
|
|
41
|
+
return removed;
|
|
42
|
+
}
|
|
43
|
+
export function buildReadResult(params) {
|
|
44
|
+
const fileLines = params.content.split("\n");
|
|
45
|
+
if (fileLines.at(-1) === "")
|
|
46
|
+
fileLines.pop();
|
|
47
|
+
const from = Math.max(1, Math.floor(params.from ?? 1));
|
|
48
|
+
const requestedLines = Math.max(1, Math.floor(params.lines ?? DEFAULT_READ_LINES));
|
|
49
|
+
const selected = fileLines.slice(from - 1, from - 1 + requestedLines);
|
|
50
|
+
let includedLines = selected.length;
|
|
51
|
+
let text = selected.join("\n");
|
|
52
|
+
while (includedLines > 1 && text.length > MAX_READ_CHARS) {
|
|
53
|
+
includedLines -= 1;
|
|
54
|
+
text = selected.slice(0, includedLines).join("\n");
|
|
55
|
+
}
|
|
56
|
+
const hardTruncated = text.length > MAX_READ_CHARS;
|
|
57
|
+
if (hardTruncated)
|
|
58
|
+
text = text.slice(0, MAX_READ_CHARS);
|
|
59
|
+
const moreLinesRemain = from - 1 + includedLines < fileLines.length;
|
|
60
|
+
const truncated = hardTruncated || moreLinesRemain || includedLines < selected.length;
|
|
61
|
+
const nextFrom = hardTruncated ? undefined : truncated ? from + includedLines : undefined;
|
|
62
|
+
if (truncated) {
|
|
63
|
+
text += `\n\n[More content available.${nextFrom ? ` Use from=${nextFrom} to continue.` : ""}]`;
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
status: "ok",
|
|
67
|
+
text,
|
|
68
|
+
path: params.path,
|
|
69
|
+
from,
|
|
70
|
+
lines: includedLines,
|
|
71
|
+
...(truncated ? { truncated: true } : {}),
|
|
72
|
+
...(nextFrom ? { nextFrom } : {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function lineSpan(result) {
|
|
76
|
+
const before = result.body.slice(0, result.chunkPos);
|
|
77
|
+
const startLine = before.split("\n").length;
|
|
78
|
+
const endLine = startLine + Math.max(0, result.bestChunk.split("\n").length - 1);
|
|
79
|
+
return { startLine, endLine };
|
|
80
|
+
}
|
|
81
|
+
function lexicalResult(hit) {
|
|
82
|
+
const body = hit.body ?? hit.title;
|
|
83
|
+
const endLine = Math.max(1, body.split("\n").length);
|
|
84
|
+
return {
|
|
85
|
+
path: hit.filepath,
|
|
86
|
+
startLine: 1,
|
|
87
|
+
endLine,
|
|
88
|
+
score: hit.score,
|
|
89
|
+
textScore: hit.score,
|
|
90
|
+
snippet: body,
|
|
91
|
+
source: "memory",
|
|
92
|
+
citation: `${hit.displayPath}#L1-L${endLine}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export class QmdMemoryManager {
|
|
96
|
+
#dbPath;
|
|
97
|
+
#workspaceDir;
|
|
98
|
+
#sources;
|
|
99
|
+
#storeFactory;
|
|
100
|
+
#analysisExecutable;
|
|
101
|
+
#analysisRunner;
|
|
102
|
+
#store;
|
|
103
|
+
#cleanupRemovedDocuments;
|
|
104
|
+
#operationChain;
|
|
105
|
+
#watcher;
|
|
106
|
+
#watchReady;
|
|
107
|
+
#watchTimer;
|
|
108
|
+
#watchError;
|
|
109
|
+
#closed = false;
|
|
110
|
+
#files = 0;
|
|
111
|
+
#dirty = true;
|
|
112
|
+
constructor(params) {
|
|
113
|
+
this.#dbPath = params.dbPath;
|
|
114
|
+
this.#workspaceDir = params.workspaceDir;
|
|
115
|
+
this.#sources = new Map(params.sources.map((source) => [source.collection, source]));
|
|
116
|
+
this.#storeFactory = params.storeFactory;
|
|
117
|
+
this.#analysisExecutable = params.analysisExecutable;
|
|
118
|
+
this.#analysisRunner = params.analysisRunner ?? runAnalysisWorker;
|
|
119
|
+
}
|
|
120
|
+
async start() {
|
|
121
|
+
this.#startWatcher();
|
|
122
|
+
await this.sync({ reason: "first-use" });
|
|
123
|
+
await this.#watchReady;
|
|
124
|
+
}
|
|
125
|
+
#startWatcher() {
|
|
126
|
+
const paths = [...new Set([...this.#sources.values()].map((source) => source.watchPath))];
|
|
127
|
+
if (paths.length === 0 || this.#watcher)
|
|
128
|
+
return;
|
|
129
|
+
this.#watcher = chokidar.watch(paths, {
|
|
130
|
+
ignoreInitial: true,
|
|
131
|
+
persistent: false,
|
|
132
|
+
ignored: (path, stats) => Boolean(stats && !stats.isDirectory() && !path.toLowerCase().endsWith(".md")),
|
|
133
|
+
});
|
|
134
|
+
this.#watchReady = new Promise((resolve) => {
|
|
135
|
+
this.#watcher?.once("ready", resolve);
|
|
136
|
+
this.#watcher?.once("error", () => resolve());
|
|
137
|
+
});
|
|
138
|
+
this.#watcher.on("error", (error) => {
|
|
139
|
+
this.#watchError = error instanceof Error ? error.message : String(error);
|
|
140
|
+
});
|
|
141
|
+
this.#watcher.on("all", () => {
|
|
142
|
+
if (this.#closed)
|
|
143
|
+
return;
|
|
144
|
+
this.#dirty = true;
|
|
145
|
+
if (this.#watchTimer)
|
|
146
|
+
clearTimeout(this.#watchTimer);
|
|
147
|
+
this.#watchTimer = setTimeout(() => {
|
|
148
|
+
this.#watchTimer = undefined;
|
|
149
|
+
void this.sync({ reason: "watch" }).catch(() => undefined);
|
|
150
|
+
}, WATCH_DEBOUNCE_MS);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async #getStore() {
|
|
154
|
+
if (this.#store)
|
|
155
|
+
return this.#store;
|
|
156
|
+
await mkdir(dirname(this.#dbPath), { recursive: true });
|
|
157
|
+
if (this.#storeFactory) {
|
|
158
|
+
this.#store = await this.#storeFactory();
|
|
159
|
+
const store = this.#store;
|
|
160
|
+
if (store.internal)
|
|
161
|
+
ensureMemoryAnalysisSchema(store.internal.db);
|
|
162
|
+
return this.#store;
|
|
163
|
+
}
|
|
164
|
+
const { createStore } = await qmdModule;
|
|
165
|
+
const store = await createStore({
|
|
166
|
+
dbPath: this.#dbPath,
|
|
167
|
+
config: {
|
|
168
|
+
collections: Object.fromEntries([...this.#sources.values()].map((source) => [
|
|
169
|
+
source.collection,
|
|
170
|
+
{ path: source.root, pattern: source.pattern },
|
|
171
|
+
])),
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
enableSecureDelete(store);
|
|
175
|
+
ensureMemoryAnalysisSchema(store.internal.db);
|
|
176
|
+
const prunedDocuments = await pruneStaleCollections(store, new Set(this.#collectionNames()));
|
|
177
|
+
if (prunedDocuments > 0)
|
|
178
|
+
markMemoryAnalysisStale(store.internal.db);
|
|
179
|
+
this.#cleanupRemovedDocuments = (changedDocuments) => {
|
|
180
|
+
cleanupRemovedDocuments(store, changedDocuments);
|
|
181
|
+
};
|
|
182
|
+
this.#store = store;
|
|
183
|
+
return store;
|
|
184
|
+
}
|
|
185
|
+
#collectionNames() {
|
|
186
|
+
return [...this.#sources.keys()];
|
|
187
|
+
}
|
|
188
|
+
sync(params) {
|
|
189
|
+
const run = async () => {
|
|
190
|
+
const store = await this.#getStore();
|
|
191
|
+
this.#dirty = true;
|
|
192
|
+
const update = await store.update();
|
|
193
|
+
this.#cleanupRemovedDocuments?.(update.updated + update.removed);
|
|
194
|
+
const analysisStore = store;
|
|
195
|
+
const invalidatesAnalysis = update.indexed + update.updated + update.removed > 0 ||
|
|
196
|
+
update.needsEmbedding > 0 ||
|
|
197
|
+
params?.force === true;
|
|
198
|
+
if (invalidatesAnalysis && analysisStore.internal) {
|
|
199
|
+
markMemoryAnalysisStale(analysisStore.internal.db);
|
|
200
|
+
}
|
|
201
|
+
const embed = await store.embed({ force: params?.force, chunkStrategy: "semantic" });
|
|
202
|
+
if (!invalidatesAnalysis && embed.chunksEmbedded > 0 && analysisStore.internal) {
|
|
203
|
+
markMemoryAnalysisStale(analysisStore.internal.db);
|
|
204
|
+
}
|
|
205
|
+
const status = await store.getStatus();
|
|
206
|
+
const collections = await store.listCollections();
|
|
207
|
+
this.#files = collections.reduce((total, collection) => total + collection.active_count, 0);
|
|
208
|
+
this.#dirty = status.needsEmbedding > 0;
|
|
209
|
+
};
|
|
210
|
+
return this.#enqueue(run);
|
|
211
|
+
}
|
|
212
|
+
recluster(options, signal) {
|
|
213
|
+
return this.#enqueue(async () => {
|
|
214
|
+
if (!this.#analysisExecutable) {
|
|
215
|
+
throw new Error("Memory analysis is unavailable: configure analysis.executable with an absolute worker path");
|
|
216
|
+
}
|
|
217
|
+
signal?.throwIfAborted();
|
|
218
|
+
const store = await this.#getAnalysisStore();
|
|
219
|
+
const status = await store.getStatus();
|
|
220
|
+
if (status.needsEmbedding > 0) {
|
|
221
|
+
throw new Error(`Memory analysis requires an up-to-date QMD vector index: ${status.needsEmbedding} chunks need embedding. ` +
|
|
222
|
+
"Run memory sync and retry memory_recluster after embedding finishes.");
|
|
223
|
+
}
|
|
224
|
+
const previousRunId = latestAnalysisRunId(store.internal.db);
|
|
225
|
+
await this.#analysisRunner({
|
|
226
|
+
executable: this.#analysisExecutable,
|
|
227
|
+
dbPath: this.#dbPath,
|
|
228
|
+
options,
|
|
229
|
+
signal,
|
|
230
|
+
});
|
|
231
|
+
const summary = readAnalysisSummary(store.internal.db);
|
|
232
|
+
if (!summary || summary.runId === previousRunId || summary.stale) {
|
|
233
|
+
throw new Error("Memory analysis worker did not produce a new complete analysis run");
|
|
234
|
+
}
|
|
235
|
+
return summary;
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
listClusters(limit) {
|
|
239
|
+
return this.#enqueue(async () => readClusters((await this.#getAnalysisStore()).internal.db, limit));
|
|
240
|
+
}
|
|
241
|
+
fetchCluster(params) {
|
|
242
|
+
return this.#enqueue(async () => readCluster((await this.#getAnalysisStore()).internal.db, params.clusterId, params.topK));
|
|
243
|
+
}
|
|
244
|
+
async #getAnalysisStore() {
|
|
245
|
+
const store = await this.#getStore();
|
|
246
|
+
if (!("internal" in store))
|
|
247
|
+
throw new Error("Memory analysis requires the QMD SQLite store");
|
|
248
|
+
return store;
|
|
249
|
+
}
|
|
250
|
+
#enqueue(run) {
|
|
251
|
+
const result = (this.#operationChain ?? Promise.resolve()).then(run, run);
|
|
252
|
+
this.#operationChain = result.then(() => undefined, () => undefined);
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
async search(query, opts) {
|
|
256
|
+
if (opts?.sources && !opts.sources.includes("memory"))
|
|
257
|
+
return [];
|
|
258
|
+
if (this.#sources.size === 0)
|
|
259
|
+
return [];
|
|
260
|
+
opts?.signal?.throwIfAborted();
|
|
261
|
+
await this.#operationChain;
|
|
262
|
+
const store = await this.#getStore();
|
|
263
|
+
if (opts?.lexicalOnly) {
|
|
264
|
+
const hits = await store.searchLex(query, {
|
|
265
|
+
limit: opts.maxResults ?? 5,
|
|
266
|
+
collection: this.#collectionNames(),
|
|
267
|
+
});
|
|
268
|
+
return hits
|
|
269
|
+
.filter((hit) => hit.score >= (opts.minScore ?? 0))
|
|
270
|
+
.map(lexicalResult);
|
|
271
|
+
}
|
|
272
|
+
const hits = await store.vsearch(query, {
|
|
273
|
+
collection: this.#collectionNames(),
|
|
274
|
+
limit: opts?.maxResults ?? 5,
|
|
275
|
+
minScore: opts?.minScore ?? 0.3,
|
|
276
|
+
});
|
|
277
|
+
return hits.map((hit) => {
|
|
278
|
+
const span = lineSpan(hit);
|
|
279
|
+
return {
|
|
280
|
+
path: hit.file,
|
|
281
|
+
...span,
|
|
282
|
+
score: hit.score,
|
|
283
|
+
vectorScore: hit.score,
|
|
284
|
+
snippet: hit.bestChunk,
|
|
285
|
+
source: "memory",
|
|
286
|
+
citation: `${hit.displayPath}#L${span.startLine}-L${span.endLine}`,
|
|
287
|
+
};
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
async readFile(params) {
|
|
291
|
+
const safe = parseSafeVirtualPath(params.relPath, this.#sources);
|
|
292
|
+
if (!safe)
|
|
293
|
+
return { status: "not_found", text: "", path: params.relPath };
|
|
294
|
+
await this.#operationChain;
|
|
295
|
+
const store = await this.#getStore();
|
|
296
|
+
const doc = await store.get(safe.normalized);
|
|
297
|
+
if ("error" in doc || doc.filepath !== safe.normalized) {
|
|
298
|
+
return { status: "not_found", text: "", path: params.relPath };
|
|
299
|
+
}
|
|
300
|
+
const content = await store.getDocumentBody(safe.normalized);
|
|
301
|
+
if (content === null)
|
|
302
|
+
return { status: "not_found", text: "", path: params.relPath };
|
|
303
|
+
return buildReadResult({
|
|
304
|
+
content,
|
|
305
|
+
path: safe.normalized,
|
|
306
|
+
from: params.from,
|
|
307
|
+
lines: params.lines,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
status() {
|
|
311
|
+
return {
|
|
312
|
+
backend: "builtin",
|
|
313
|
+
provider: "unblock-memory",
|
|
314
|
+
files: this.#files,
|
|
315
|
+
dirty: this.#dirty,
|
|
316
|
+
workspaceDir: this.#workspaceDir,
|
|
317
|
+
dbPath: this.#dbPath,
|
|
318
|
+
sources: ["memory"],
|
|
319
|
+
vector: { enabled: true, available: !this.#dirty },
|
|
320
|
+
custom: {
|
|
321
|
+
paths: [...this.#sources.values()].map((source) => source.configuredPath),
|
|
322
|
+
...(this.#watchError ? { watchError: this.#watchError } : {}),
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
async probeEmbeddingAvailability() {
|
|
327
|
+
await this.#getStore();
|
|
328
|
+
return { ok: true, checked: true, checkedAtMs: Date.now() };
|
|
329
|
+
}
|
|
330
|
+
async probeVectorAvailability() {
|
|
331
|
+
const status = await (await this.#getStore()).getStatus();
|
|
332
|
+
return status.hasVectorIndex;
|
|
333
|
+
}
|
|
334
|
+
async close() {
|
|
335
|
+
this.#closed = true;
|
|
336
|
+
if (this.#watchTimer)
|
|
337
|
+
clearTimeout(this.#watchTimer);
|
|
338
|
+
this.#watchTimer = undefined;
|
|
339
|
+
await this.#watcher?.close();
|
|
340
|
+
this.#watcher = undefined;
|
|
341
|
+
this.#watchReady = undefined;
|
|
342
|
+
await this.#operationChain?.catch(() => undefined);
|
|
343
|
+
await this.#store?.close();
|
|
344
|
+
this.#store = undefined;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
export declare function resolveFlushPlan(params?: {
|
|
3
|
+
cfg?: OpenClawConfig;
|
|
4
|
+
nowMs?: number;
|
|
5
|
+
}): {
|
|
6
|
+
softThresholdTokens: number;
|
|
7
|
+
forceFlushTranscriptBytes: number;
|
|
8
|
+
reserveTokensFloor: number;
|
|
9
|
+
model: string | undefined;
|
|
10
|
+
prompt: string;
|
|
11
|
+
systemPrompt: string;
|
|
12
|
+
relativePath: string;
|
|
13
|
+
} | null;
|
|
14
|
+
export declare function registerUnblockMemory(api: OpenClawPluginApi): void;
|