@xdarkicex/openclaw-memory-libravdb 1.5.4 → 1.6.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.
@@ -7,6 +7,8 @@ export interface IngestQueueOptions {
7
7
  retryBaseDelayMs: number;
8
8
  /** Max retries per chunk. */
9
9
  maxRetries: number;
10
+ /** Called after each chunk is accepted so scan-level state stays current. */
11
+ onChunkFeedback?: (feedback: IngestFeedback) => void;
10
12
  }
11
13
  interface IngestMarkdownDocumentParams {
12
14
  sourceDoc: string;
@@ -20,19 +22,40 @@ interface IngestMarkdownDocumentParams {
20
22
  fileHash: string;
21
23
  sourceSize: number;
22
24
  sourceMtimeMs: number;
25
+ sourceCtimeMs: number;
23
26
  ingestVersion: number;
24
27
  hashBackend: string;
25
28
  };
26
29
  mode?: IngestMode;
27
30
  }
31
+ interface IngestFeedback {
32
+ queueDepth: number;
33
+ queueCapacity: number;
34
+ acceptMore: boolean;
35
+ retryAfterMs: number;
36
+ processingTimeUs: number;
37
+ nodesAccepted: number;
38
+ nodesRejected: number;
39
+ tokensIngested: number;
40
+ tokenBurstLimit: number;
41
+ walDepth?: number;
42
+ walCapacity?: number;
43
+ }
44
+ interface IngestMarkdownDocumentResponse {
45
+ ok: boolean;
46
+ feedback?: IngestFeedback;
47
+ }
28
48
  export declare class IngestQueue {
29
49
  private readonly queue;
30
- private readonly rpcCall;
50
+ private readonly ingestDocument;
51
+ private readonly deleteDocument;
31
52
  private readonly logger;
32
53
  private readonly options;
33
54
  private running;
34
- constructor(rpcCall: <T>(method: string, params: unknown) => Promise<T>, logger: LoggerLike, options?: Partial<IngestQueueOptions>);
35
- enqueueIngest(sourceDoc: string, text: string, baseParams: Omit<IngestMarkdownDocumentParams, "sourceDoc" | "text" | "mode">): Promise<void>;
55
+ constructor(ingestDocument: (params: IngestMarkdownDocumentParams) => Promise<IngestMarkdownDocumentResponse>, deleteDocument: (params: {
56
+ sourceDoc: string;
57
+ }) => Promise<unknown>, logger: LoggerLike, options?: Partial<IngestQueueOptions>);
58
+ enqueueIngest(sourceDoc: string, text: string, baseParams: Omit<IngestMarkdownDocumentParams, "sourceDoc" | "text" | "mode">, maxChunkTokens?: number): Promise<IngestFeedback | undefined>;
36
59
  private ingestWithRetry;
37
60
  enqueueDelete(sourceDoc: string): Promise<void>;
38
61
  }
@@ -6,12 +6,14 @@ const DEFAULT_OPTIONS = {
6
6
  };
7
7
  export class IngestQueue {
8
8
  queue = [];
9
- rpcCall;
9
+ ingestDocument;
10
+ deleteDocument;
10
11
  logger;
11
12
  options;
12
13
  running = false;
13
- constructor(rpcCall, logger, options = {}) {
14
- this.rpcCall = rpcCall;
14
+ constructor(ingestDocument, deleteDocument, logger, options = {}) {
15
+ this.ingestDocument = ingestDocument;
16
+ this.deleteDocument = deleteDocument;
15
17
  this.logger = logger;
16
18
  this.options = { ...DEFAULT_OPTIONS, ...options };
17
19
  if (!(this.options.chunkTokens > 0)) {
@@ -19,43 +21,62 @@ export class IngestQueue {
19
21
  this.options.chunkTokens = DEFAULT_OPTIONS.chunkTokens;
20
22
  }
21
23
  }
22
- async enqueueIngest(sourceDoc, text, baseParams) {
24
+ async enqueueIngest(sourceDoc, text, baseParams, maxChunkTokens) {
23
25
  if (this.options.chunkTokens === Infinity) {
24
- // Retry-only mode: send full text as single chunk
25
- return this.ingestWithRetry({
26
+ const resp = await this.ingestWithRetry({
26
27
  ...baseParams,
27
28
  sourceDoc,
28
29
  text,
29
30
  mode: IngestMode.REPLACE,
30
31
  });
32
+ return resp.feedback;
31
33
  }
32
- const chunks = splitIntoChunks(text, this.options.chunkTokens);
33
- if (chunks.length === 1) {
34
- return this.ingestWithRetry({
35
- ...baseParams,
36
- sourceDoc,
37
- text: chunks[0].text,
38
- mode: IngestMode.REPLACE,
39
- });
40
- }
41
- // Multiple chunks: clear the source once, then append the remaining chunks.
42
- // Sending REPLACE last deletes the earlier chunks from the same source_doc.
43
- for (let i = 0; i < chunks.length; i++) {
44
- const isFirst = i === 0;
34
+ let currentLimit = maxChunkTokens && maxChunkTokens > 0 ? maxChunkTokens : this.options.chunkTokens;
35
+ let offset = 0;
36
+ let isFirst = true;
37
+ let lastFeedback;
38
+ while (offset < text.length) {
39
+ const remainingText = text.slice(offset);
40
+ const chunks = splitIntoChunks(remainingText, currentLimit);
41
+ const chunkText = chunks[0].text;
45
42
  const chunkParams = {
46
43
  ...baseParams,
47
44
  sourceDoc,
48
- text: chunks[i].text,
45
+ text: chunkText,
49
46
  mode: isFirst ? IngestMode.REPLACE : IngestMode.APPEND,
50
47
  };
51
- await this.ingestWithRetry(chunkParams);
48
+ const resp = await this.ingestWithRetry(chunkParams);
49
+ lastFeedback = resp.feedback;
50
+ if (lastFeedback &&
51
+ lastFeedback.nodesAccepted === 0 &&
52
+ lastFeedback.tokenBurstLimit &&
53
+ lastFeedback.tokenBurstLimit > 0 &&
54
+ lastFeedback.tokenBurstLimit < currentLimit) {
55
+ currentLimit = lastFeedback.tokenBurstLimit;
56
+ continue;
57
+ }
58
+ if (lastFeedback && lastFeedback.nodesAccepted === 0) {
59
+ this.logger.warn?.(`[ingest-queue] Chunk permanently rejected for ${sourceDoc} ` +
60
+ `at offset=${offset} length=${chunkText.length} ` +
61
+ `tokenBurstLimit=${lastFeedback.tokenBurstLimit ?? "unset"}`);
62
+ }
63
+ if (this.options.onChunkFeedback && lastFeedback) {
64
+ this.options.onChunkFeedback(lastFeedback);
65
+ }
66
+ offset += chunkText.length;
67
+ isFirst = false;
68
+ if (lastFeedback && !lastFeedback.acceptMore && offset < text.length) {
69
+ const delay = lastFeedback.retryAfterMs || 1000;
70
+ await new Promise((resolve) => setTimeout(resolve, delay));
71
+ }
52
72
  }
73
+ return lastFeedback;
53
74
  }
54
75
  async ingestWithRetry(params) {
55
- await withRetry(() => this.rpcCall("ingest_markdown_document", params), this.options.maxRetries, this.options.retryBaseDelayMs, this.logger, `ingest_markdown_document(${params.sourceDoc})`);
76
+ return withRetry(() => this.ingestDocument(params), this.options.maxRetries, this.options.retryBaseDelayMs, this.logger, `ingest_markdown_document(${params.sourceDoc})`);
56
77
  }
57
78
  async enqueueDelete(sourceDoc) {
58
- await withRetry(() => this.rpcCall("delete_authored_document", { sourceDoc }), this.options.maxRetries, this.options.retryBaseDelayMs, this.logger, `delete_authored_document(${sourceDoc})`);
79
+ await withRetry(() => this.deleteDocument({ sourceDoc }), this.options.maxRetries, this.options.retryBaseDelayMs, this.logger, `delete_authored_document(${sourceDoc})`);
59
80
  }
60
81
  }
61
82
  function splitIntoChunks(text, maxTokens) {
@@ -0,0 +1,59 @@
1
+ import type { Interceptor } from "@connectrpc/connect";
2
+ import type { PartialMessage } from "@bufbuild/protobuf";
3
+ import type { AfterTurnKernelRequest, AfterTurnKernelResponse, AssembleContextInternalRequest, AssembleContextInternalResponse, BootstrapSessionKernelRequest, BootstrapSessionKernelResponse, CompactSessionRequest, CompactSessionResponse, DeleteAuthoredDocumentRequest, DeleteAuthoredDocumentResponse, DreamPromotionResponse, ExportMemoryRequest, ExportMemoryResponse, FlushNamespaceRequest, FlushNamespaceResponse, FlushRequest, FlushResponse, HealthRequest, HealthResponse, IngestMarkdownDocumentRequest, IngestMarkdownDocumentResponse, IngestMessageKernelRequest, IngestMessageKernelResponse, ListCollectionRequest, ListCollectionResponse, ListLifecycleJournalRequest, ListLifecycleJournalResponse, MarkMemorySupersededRequest, MarkMemorySupersededResponse, MemoryStatusRequest, MemoryStatusResponse, PromoteDreamEntriesRequest, RankCandidatesRequest, RankCandidatesResponse, RebuildIndexRequest, RebuildIndexResponse, ReindexAuthoredDocumentRequest, ReindexAuthoredDocumentResponse, SearchTextCollectionsRequest, SearchTextRequest, SearchTextResponse, SessionLifecycleHintRequest, SessionLifecycleHintResponse } from "@xdarkicex/libravdb-contracts";
4
+ export interface LibravDBClientOptions {
5
+ endpoint?: string;
6
+ secret?: string;
7
+ timeoutMs?: number;
8
+ tlsCaPath?: string;
9
+ tlsMode?: "auto" | "tls" | "insecure";
10
+ tlsClientCertPath?: string;
11
+ tlsClientKeyPath?: string;
12
+ }
13
+ export declare function resolveClientEndpoint(configuredEndpoint?: string): string;
14
+ interface RpcMutex {
15
+ current: Promise<void>;
16
+ lock(): Promise<() => void>;
17
+ }
18
+ export interface AuthInterceptorState {
19
+ readonly secret: string | undefined;
20
+ nonceHex: string | undefined;
21
+ bootstrap(): Promise<void>;
22
+ readonly rpcMutex: RpcMutex;
23
+ }
24
+ export declare function createAuthInterceptor(state: AuthInterceptorState): Interceptor;
25
+ export declare class LibravDBClient {
26
+ private client;
27
+ private readonly secret;
28
+ private nonceHex;
29
+ private closed;
30
+ constructor(options?: LibravDBClientOptions);
31
+ bootstrapHandshake(): Promise<void>;
32
+ private guardOpen;
33
+ health(req?: PartialMessage<HealthRequest>): Promise<HealthResponse>;
34
+ status(req?: PartialMessage<MemoryStatusRequest>): Promise<MemoryStatusResponse>;
35
+ flush(req?: PartialMessage<FlushRequest>): Promise<FlushResponse>;
36
+ sessionLifecycleHint(req: PartialMessage<SessionLifecycleHintRequest>): Promise<SessionLifecycleHintResponse>;
37
+ listLifecycleJournal(req: PartialMessage<ListLifecycleJournalRequest>): Promise<ListLifecycleJournalResponse>;
38
+ ingestMarkdownDocument(req: PartialMessage<IngestMarkdownDocumentRequest>): Promise<IngestMarkdownDocumentResponse>;
39
+ promoteDreamEntries(req: PartialMessage<PromoteDreamEntriesRequest>): Promise<DreamPromotionResponse>;
40
+ reindexAuthoredDocument(req: PartialMessage<ReindexAuthoredDocumentRequest>): Promise<ReindexAuthoredDocumentResponse>;
41
+ deleteAuthoredDocument(req: PartialMessage<DeleteAuthoredDocumentRequest>): Promise<DeleteAuthoredDocumentResponse>;
42
+ markMemorySuperseded(req: PartialMessage<MarkMemorySupersededRequest>): Promise<MarkMemorySupersededResponse>;
43
+ searchText(req: PartialMessage<SearchTextRequest>): Promise<SearchTextResponse>;
44
+ searchTextCollections(req: PartialMessage<SearchTextCollectionsRequest>): Promise<SearchTextResponse>;
45
+ listCollection(req: PartialMessage<ListCollectionRequest>): Promise<ListCollectionResponse>;
46
+ exportMemory(req: PartialMessage<ExportMemoryRequest>): Promise<ExportMemoryResponse>;
47
+ flushNamespace(req: PartialMessage<FlushNamespaceRequest>): Promise<FlushNamespaceResponse>;
48
+ rebuildIndex(req: PartialMessage<RebuildIndexRequest>, opts?: {
49
+ timeoutMs?: number;
50
+ }): Promise<RebuildIndexResponse>;
51
+ bootstrapSessionKernel(req: PartialMessage<BootstrapSessionKernelRequest>): Promise<BootstrapSessionKernelResponse>;
52
+ ingestMessageKernel(req: PartialMessage<IngestMessageKernelRequest>): Promise<IngestMessageKernelResponse>;
53
+ afterTurnKernel(req: PartialMessage<AfterTurnKernelRequest>): Promise<AfterTurnKernelResponse>;
54
+ assembleContextInternal(req: PartialMessage<AssembleContextInternalRequest>): Promise<AssembleContextInternalResponse>;
55
+ compactSession(req: PartialMessage<CompactSessionRequest>): Promise<CompactSessionResponse>;
56
+ rankCandidates(req: PartialMessage<RankCandidatesRequest>): Promise<RankCandidatesResponse>;
57
+ close(): void;
58
+ }
59
+ export {};
@@ -0,0 +1,296 @@
1
+ import { createPromiseClient } from "@connectrpc/connect";
2
+ import { createGrpcTransport } from "@connectrpc/connect-node";
3
+ import { LibravDB } from "@xdarkicex/libravdb-contracts/client";
4
+ import { createHmac } from "node:crypto";
5
+ import fs from "node:fs";
6
+ import net from "node:net";
7
+ import os from "node:os";
8
+ import path from "node:path";
9
+ export function resolveClientEndpoint(configuredEndpoint) {
10
+ if (configuredEndpoint && configuredEndpoint !== "auto")
11
+ return configuredEndpoint;
12
+ if (process.env.LIBRAVDB_GRPC_ENDPOINT)
13
+ return process.env.LIBRAVDB_GRPC_ENDPOINT;
14
+ if (process.platform === "win32")
15
+ return "tcp:127.0.0.1:37421";
16
+ const sockName = "libravdb.sock";
17
+ const candidateDirs = [
18
+ path.join(os.homedir(), ".libravdbd", "run"),
19
+ "/opt/homebrew/var/libravdbd/run",
20
+ "/usr/local/var/libravdbd/run",
21
+ ];
22
+ for (const dir of candidateDirs) {
23
+ const fullPath = path.join(dir, sockName);
24
+ if (fs.existsSync(fullPath))
25
+ return `unix:${fullPath}`;
26
+ }
27
+ return `unix:${path.join(os.homedir(), ".libravdbd", "run", sockName)}`;
28
+ }
29
+ function createRpcMutex() {
30
+ return {
31
+ current: Promise.resolve(),
32
+ async lock() {
33
+ let release;
34
+ const p = new Promise(r => release = r);
35
+ const prev = this.current;
36
+ this.current = prev.then(() => p);
37
+ await prev;
38
+ return release;
39
+ }
40
+ };
41
+ }
42
+ export function createAuthInterceptor(state) {
43
+ return (next) => async (req) => {
44
+ // Health does not participate in the nonce chain — bypass the
45
+ // mutex entirely so recovery can call Health without deadlocking.
46
+ if (req.method.name === "Health") {
47
+ return next(req);
48
+ }
49
+ const release = await state.rpcMutex.lock();
50
+ try {
51
+ // Lost the nonce? Recover inside the lock so queued requests
52
+ // wait for the chain to be restored instead of failing spuriously.
53
+ if (state.secret && !state.nonceHex) {
54
+ await state.bootstrap();
55
+ if (!state.nonceHex) {
56
+ throw new Error("LibraVDB: bootstrap handshake did not return a nonce");
57
+ }
58
+ }
59
+ if (state.secret && state.nonceHex) {
60
+ const hmac = createHmac("sha256", state.secret);
61
+ hmac.update(state.nonceHex);
62
+ req.header.set("x-libravdb-nonce", state.nonceHex);
63
+ req.header.set("x-libravdb-auth", hmac.digest("hex"));
64
+ }
65
+ let res;
66
+ try {
67
+ res = await next(req);
68
+ }
69
+ catch (error) {
70
+ if (state.secret && state.nonceHex) {
71
+ state.nonceHex = undefined;
72
+ }
73
+ throw error;
74
+ }
75
+ if (state.secret) {
76
+ const nextNonce = res.header.get("x-libravdb-nonce") || res.trailer.get("x-libravdb-nonce");
77
+ if (nextNonce) {
78
+ state.nonceHex = nextNonce;
79
+ }
80
+ else {
81
+ state.nonceHex = undefined;
82
+ }
83
+ }
84
+ return res;
85
+ }
86
+ finally {
87
+ release();
88
+ }
89
+ };
90
+ }
91
+ export class LibravDBClient {
92
+ client;
93
+ secret;
94
+ nonceHex;
95
+ closed = false;
96
+ constructor(options = {}) {
97
+ this.secret = options.secret ?? loadSecretFromEnv();
98
+ const rawEndpoint = resolveClientEndpoint(options.endpoint);
99
+ const isUnix = rawEndpoint.startsWith("unix:");
100
+ const socketPath = isUnix ? rawEndpoint.slice(5) : undefined;
101
+ const credMode = resolveCredentialMode(rawEndpoint, options.tlsMode);
102
+ const isInsecure = isUnix || credMode === "insecure";
103
+ const targetUrl = isUnix
104
+ ? "http://localhost"
105
+ : rawEndpoint.replace(/^tcp:/, isInsecure ? "http://" : "https://");
106
+ let rootCerts = null;
107
+ let clientKey = null;
108
+ let clientCert = null;
109
+ if (!isInsecure && options.tlsCaPath) {
110
+ rootCerts = fs.readFileSync(options.tlsCaPath);
111
+ }
112
+ if (options.tlsClientCertPath && options.tlsClientKeyPath) {
113
+ clientCert = fs.readFileSync(options.tlsClientCertPath);
114
+ clientKey = fs.readFileSync(options.tlsClientKeyPath);
115
+ }
116
+ const rpcMutex = createRpcMutex();
117
+ const self = this;
118
+ const authInterceptor = createAuthInterceptor({
119
+ secret: this.secret,
120
+ get nonceHex() { return self.nonceHex; },
121
+ set nonceHex(v) { self.nonceHex = v; },
122
+ bootstrap: () => self.bootstrapHandshake(),
123
+ rpcMutex,
124
+ });
125
+ const transport = createGrpcTransport({
126
+ baseUrl: targetUrl,
127
+ httpVersion: "2",
128
+ nodeOptions: isUnix
129
+ ? { createConnection: () => net.connect(socketPath) }
130
+ : {
131
+ ...(rootCerts ? { ca: rootCerts } : {}),
132
+ ...(clientKey ? { key: clientKey } : {}),
133
+ ...(clientCert ? { cert: clientCert } : {}),
134
+ ...(isInsecure ? { rejectUnauthorized: false } : {}),
135
+ },
136
+ defaultTimeoutMs: options.timeoutMs ?? 30000,
137
+ interceptors: [authInterceptor],
138
+ });
139
+ this.client = createPromiseClient(LibravDB, transport);
140
+ }
141
+ async bootstrapHandshake() {
142
+ this.guardOpen();
143
+ try {
144
+ await this.client.health({ service: "" }, {
145
+ onHeader: (headers) => {
146
+ const nonce = headers.get("x-libravdb-nonce");
147
+ if (nonce)
148
+ this.nonceHex = nonce;
149
+ },
150
+ });
151
+ }
152
+ catch (error) {
153
+ throw new Error(`LibraVDB: failed to handshake with daemon: ${error instanceof Error ? error.message : String(error)}`);
154
+ }
155
+ }
156
+ guardOpen() {
157
+ if (this.closed) {
158
+ throw new Error("LibravDB client is closed");
159
+ }
160
+ }
161
+ // ── Session lifecycle ────────────────────────────────────────────
162
+ async health(req = {}) {
163
+ this.guardOpen();
164
+ return this.client.health(req);
165
+ }
166
+ async status(req = {}) {
167
+ this.guardOpen();
168
+ return this.client.status(req);
169
+ }
170
+ async flush(req = {}) {
171
+ this.guardOpen();
172
+ return this.client.flush(req);
173
+ }
174
+ async sessionLifecycleHint(req) {
175
+ this.guardOpen();
176
+ return this.client.sessionLifecycleHint(req);
177
+ }
178
+ async listLifecycleJournal(req) {
179
+ this.guardOpen();
180
+ return this.client.listLifecycleJournal(req);
181
+ }
182
+ // ── Ingest ───────────────────────────────────────────────────────
183
+ async ingestMarkdownDocument(req) {
184
+ this.guardOpen();
185
+ return this.client.ingestMarkdownDocument(req);
186
+ }
187
+ async promoteDreamEntries(req) {
188
+ this.guardOpen();
189
+ return this.client.promoteDreamEntries(req);
190
+ }
191
+ async reindexAuthoredDocument(req) {
192
+ this.guardOpen();
193
+ return this.client.reindexAuthoredDocument(req);
194
+ }
195
+ async deleteAuthoredDocument(req) {
196
+ this.guardOpen();
197
+ return this.client.deleteAuthoredDocument(req);
198
+ }
199
+ async markMemorySuperseded(req) {
200
+ this.guardOpen();
201
+ return this.client.markMemorySuperseded(req);
202
+ }
203
+ // ── Search / query ───────────────────────────────────────────────
204
+ async searchText(req) {
205
+ this.guardOpen();
206
+ return this.client.searchText(req);
207
+ }
208
+ async searchTextCollections(req) {
209
+ this.guardOpen();
210
+ return this.client.searchTextCollections(req);
211
+ }
212
+ async listCollection(req) {
213
+ this.guardOpen();
214
+ return this.client.listCollection(req);
215
+ }
216
+ // ── Memory ───────────────────────────────────────────────────────
217
+ async exportMemory(req) {
218
+ this.guardOpen();
219
+ return this.client.exportMemory(req);
220
+ }
221
+ async flushNamespace(req) {
222
+ this.guardOpen();
223
+ return this.client.flushNamespace(req);
224
+ }
225
+ // ── Index ────────────────────────────────────────────────────────
226
+ async rebuildIndex(req, opts) {
227
+ this.guardOpen();
228
+ return this.client.rebuildIndex(req, opts);
229
+ }
230
+ // ── Kernel ───────────────────────────────────────────────────────
231
+ async bootstrapSessionKernel(req) {
232
+ this.guardOpen();
233
+ return this.client.bootstrapSessionKernel(req);
234
+ }
235
+ async ingestMessageKernel(req) {
236
+ this.guardOpen();
237
+ return this.client.ingestMessageKernel(req);
238
+ }
239
+ async afterTurnKernel(req) {
240
+ this.guardOpen();
241
+ return this.client.afterTurnKernel(req);
242
+ }
243
+ async assembleContextInternal(req) {
244
+ this.guardOpen();
245
+ return this.client.assembleContextInternal(req);
246
+ }
247
+ async compactSession(req) {
248
+ this.guardOpen();
249
+ return this.client.compactSession(req);
250
+ }
251
+ async rankCandidates(req) {
252
+ this.guardOpen();
253
+ return this.client.rankCandidates(req);
254
+ }
255
+ close() {
256
+ this.closed = true;
257
+ }
258
+ }
259
+ function resolveCredentialMode(endpoint, tlsMode) {
260
+ if (tlsMode === "tls")
261
+ return "tls";
262
+ if (tlsMode === "insecure")
263
+ return "insecure";
264
+ const target = endpoint.startsWith("tcp:") ? endpoint.slice(4) : endpoint;
265
+ if (target.startsWith("unix:"))
266
+ return "insecure";
267
+ const host = extractHost(target);
268
+ const normalized = host.toLowerCase();
269
+ return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1"
270
+ ? "insecure"
271
+ : "tls";
272
+ }
273
+ function extractHost(target) {
274
+ const withoutDns = target.startsWith("dns:///") ? target.slice("dns:///".length) : target;
275
+ if (withoutDns.startsWith("[")) {
276
+ const close = withoutDns.indexOf("]");
277
+ return close > 0 ? withoutDns.slice(1, close) : withoutDns;
278
+ }
279
+ const sep = withoutDns.lastIndexOf(":");
280
+ return sep > 0 ? withoutDns.slice(0, sep) : withoutDns;
281
+ }
282
+ function loadSecretFromEnv() {
283
+ const secret = process.env.LIBRAVDB_AUTH_SECRET;
284
+ if (secret)
285
+ return secret;
286
+ const secretPath = process.env.LIBRAVDB_AUTH_SECRET_FILE;
287
+ if (secretPath) {
288
+ try {
289
+ return fs.readFileSync(secretPath, "utf8").trim();
290
+ }
291
+ catch {
292
+ return undefined;
293
+ }
294
+ }
295
+ return undefined;
296
+ }
@@ -1,11 +1,8 @@
1
1
  import type { LoggerLike, PluginConfig } from "./types.js";
2
+ import type { ClientGetter } from "./plugin-runtime.js";
2
3
  type Disposable = {
3
4
  close(): void;
4
5
  };
5
- interface RpcLike {
6
- call<T>(method: string, params: unknown): Promise<T>;
7
- }
8
- type RpcGetterLike = () => Promise<RpcLike>;
9
6
  interface FsDirentLike {
10
7
  name: string;
11
8
  isDirectory(): boolean;
@@ -14,14 +11,22 @@ interface FsDirentLike {
14
11
  interface FsWatcherLike extends Disposable {
15
12
  on(event: "error", handler: (error: Error) => void): void;
16
13
  }
14
+ interface FsReadStream {
15
+ read(buffer: Uint8Array): Promise<{
16
+ bytesRead: number;
17
+ }>;
18
+ close(): Promise<void>;
19
+ }
17
20
  interface FsApi {
18
21
  readdir(dir: string): Promise<FsDirentLike[]>;
19
22
  readFile(file: string): Promise<Uint8Array>;
20
23
  stat(file: string): Promise<{
21
24
  size: number;
22
25
  mtimeMs: number;
26
+ ctimeMs: number;
23
27
  }>;
24
28
  watch(dir: string, onChange: (event: string, filename: string | Buffer | null) => void): FsWatcherLike;
29
+ openReadStream(file: string): Promise<FsReadStream>;
25
30
  }
26
31
  export interface MarkdownSourceAdapter {
27
32
  kind: string;
@@ -39,5 +44,5 @@ export interface MarkdownIngestionSnapshot {
39
44
  size: number;
40
45
  mtimeMs: number;
41
46
  }
42
- export declare function createMarkdownIngestionHandle(cfg: PluginConfig, getRpc: RpcGetterLike, logger?: LoggerLike, fsApi?: FsApi): MarkdownIngestionHandle;
47
+ export declare function createMarkdownIngestionHandle(cfg: PluginConfig, getClient: ClientGetter, logger?: LoggerLike, fsApi?: FsApi): MarkdownIngestionHandle;
43
48
  export {};