@xdarkicex/openclaw-memory-libravdb 1.4.6 → 1.4.8
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/HOOK.md +14 -0
- package/README.md +32 -2
- package/dist/cli.d.ts +39 -0
- package/dist/cli.js +208 -0
- package/dist/context-engine.d.ts +56 -0
- package/dist/context-engine.js +125 -0
- package/dist/dream-promotion.d.ts +47 -0
- package/dist/dream-promotion.js +363 -0
- package/dist/dream-routing.d.ts +6 -0
- package/dist/dream-routing.js +31 -0
- package/dist/durable-namespace.d.ts +6 -0
- package/dist/durable-namespace.js +24 -0
- package/dist/grpc-client.d.ts +23 -0
- package/dist/grpc-client.js +104 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +40 -0
- package/dist/lifecycle-hooks.d.ts +4 -0
- package/dist/lifecycle-hooks.js +64 -0
- package/dist/markdown-hash.d.ts +3 -0
- package/dist/markdown-hash.js +82 -0
- package/dist/markdown-ingest.d.ts +43 -0
- package/dist/markdown-ingest.js +464 -0
- package/dist/memory-provider.d.ts +4 -0
- package/dist/memory-provider.js +13 -0
- package/dist/memory-runtime.d.ts +118 -0
- package/dist/memory-runtime.js +217 -0
- package/dist/plugin-runtime.d.ts +28 -0
- package/dist/plugin-runtime.js +127 -0
- package/dist/proto/intelligence_kernel/v1/kernel.proto +378 -0
- package/dist/recall-cache.d.ts +2 -0
- package/dist/recall-cache.js +30 -0
- package/dist/rpc-protobuf-codecs.d.ts +70 -0
- package/dist/rpc-protobuf-codecs.js +77 -0
- package/dist/rpc.d.ts +14 -0
- package/dist/rpc.js +121 -0
- package/dist/sidecar.d.ts +34 -0
- package/dist/sidecar.js +535 -0
- package/dist/types.d.ts +163 -0
- package/dist/types.js +1 -0
- package/docs/contributing.md +14 -13
- package/docs/install.md +7 -9
- package/docs/installation.md +23 -16
- package/docs/uninstall.md +1 -1
- package/index.js +2 -0
- package/openclaw.plugin.json +2 -2
- package/package.json +39 -16
- package/packaging/README.md +0 -71
- package/packaging/homebrew/libravdbd.rb.tmpl +0 -224
- package/packaging/launchd/com.xdarkicex.libravdbd.plist +0 -32
- package/packaging/systemd/libravdbd.service +0 -12
- package/src/cli.ts +0 -299
- package/src/comparison-experiments.ts +0 -128
- package/src/context-engine.ts +0 -1645
- package/src/continuity.ts +0 -93
- package/src/dream-promotion.ts +0 -492
- package/src/dream-routing.ts +0 -40
- package/src/durable-namespace.ts +0 -34
- package/src/index.ts +0 -47
- package/src/lifecycle-hooks.ts +0 -96
- package/src/markdown-hash.ts +0 -104
- package/src/markdown-ingest.ts +0 -627
- package/src/memory-provider.ts +0 -25
- package/src/memory-runtime.ts +0 -283
- package/src/openclaw-plugin-sdk.d.ts +0 -59
- package/src/plugin-runtime.ts +0 -119
- package/src/recall-cache.ts +0 -34
- package/src/recall-utils.ts +0 -131
- package/src/rpc.ts +0 -92
- package/src/scoring.ts +0 -632
- package/src/sidecar.ts +0 -583
- package/src/temporal.ts +0 -1031
- package/src/tokens.ts +0 -52
- package/src/types.ts +0 -278
- package/tsconfig.json +0 -20
- package/tsconfig.tests.json +0 -12
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { RpcGetter } from "./plugin-runtime.js";
|
|
2
|
+
import type { PluginConfig } from "./types.js";
|
|
3
|
+
type MemorySearchParams = {
|
|
4
|
+
query?: string;
|
|
5
|
+
text?: string;
|
|
6
|
+
input?: string;
|
|
7
|
+
q?: string;
|
|
8
|
+
k?: number;
|
|
9
|
+
limit?: number;
|
|
10
|
+
topK?: number;
|
|
11
|
+
userId?: string;
|
|
12
|
+
agentId?: string;
|
|
13
|
+
sessionId?: string;
|
|
14
|
+
sessionKey?: string;
|
|
15
|
+
context?: {
|
|
16
|
+
userId?: string;
|
|
17
|
+
agentId?: string;
|
|
18
|
+
sessionId?: string;
|
|
19
|
+
sessionKey?: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
type MemoryRuntimeStatus = {
|
|
23
|
+
ok?: boolean;
|
|
24
|
+
message?: string;
|
|
25
|
+
turnCount?: number;
|
|
26
|
+
memoryCount?: number;
|
|
27
|
+
gatingThreshold?: number;
|
|
28
|
+
abstractiveReady?: boolean;
|
|
29
|
+
embeddingProfile?: string;
|
|
30
|
+
};
|
|
31
|
+
export declare function buildMemoryRuntimeBridge(getRpc: RpcGetter, cfg: PluginConfig): {
|
|
32
|
+
getMemorySearchManager(params?: {
|
|
33
|
+
agentId?: string;
|
|
34
|
+
purpose?: string;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
manager: {
|
|
37
|
+
search(queryOrParams?: string | MemorySearchParams, opts?: MemorySearchParams): Promise<{
|
|
38
|
+
path: string;
|
|
39
|
+
startLine: number;
|
|
40
|
+
endLine: number;
|
|
41
|
+
score: number;
|
|
42
|
+
snippet: string;
|
|
43
|
+
source: string;
|
|
44
|
+
citation: string;
|
|
45
|
+
}[] | {
|
|
46
|
+
results: never[];
|
|
47
|
+
error: string;
|
|
48
|
+
} | {
|
|
49
|
+
results: {
|
|
50
|
+
content: string;
|
|
51
|
+
id: string;
|
|
52
|
+
score: number;
|
|
53
|
+
text: string;
|
|
54
|
+
metadata: {
|
|
55
|
+
ts?: number;
|
|
56
|
+
sessionId?: string;
|
|
57
|
+
userId?: string;
|
|
58
|
+
role?: string;
|
|
59
|
+
source_doc?: string;
|
|
60
|
+
node_kind?: string;
|
|
61
|
+
ordinal?: number;
|
|
62
|
+
position?: number;
|
|
63
|
+
tier?: number;
|
|
64
|
+
authored?: boolean;
|
|
65
|
+
authority?: number;
|
|
66
|
+
access_count?: number;
|
|
67
|
+
collection?: string;
|
|
68
|
+
hop_targets?: string[] | string;
|
|
69
|
+
token_estimate?: number;
|
|
70
|
+
continuity_tail?: boolean;
|
|
71
|
+
continuity_base?: boolean;
|
|
72
|
+
continuity_bundle_id?: string;
|
|
73
|
+
elevated_guidance?: boolean;
|
|
74
|
+
source_turn_id?: string;
|
|
75
|
+
source_turn_ts?: number;
|
|
76
|
+
provenance_class?: string;
|
|
77
|
+
stability_weight?: number;
|
|
78
|
+
expanded_from_summary?: boolean;
|
|
79
|
+
parent_summary_id?: string;
|
|
80
|
+
expansion_depth?: number;
|
|
81
|
+
cascade_tier?: number;
|
|
82
|
+
[key: string]: unknown;
|
|
83
|
+
};
|
|
84
|
+
finalScore?: number;
|
|
85
|
+
}[];
|
|
86
|
+
error?: undefined;
|
|
87
|
+
}>;
|
|
88
|
+
readFile(params: {
|
|
89
|
+
relPath: string;
|
|
90
|
+
from?: number;
|
|
91
|
+
lines?: number;
|
|
92
|
+
}): Promise<{
|
|
93
|
+
text: string;
|
|
94
|
+
path: string;
|
|
95
|
+
}>;
|
|
96
|
+
ingest(): Promise<{
|
|
97
|
+
ingested: boolean;
|
|
98
|
+
delegatedToContextEngine: boolean;
|
|
99
|
+
}>;
|
|
100
|
+
sync(): Promise<{
|
|
101
|
+
synced: boolean;
|
|
102
|
+
delegatedToContextEngine: boolean;
|
|
103
|
+
}>;
|
|
104
|
+
status(): MemoryRuntimeStatus & Record<string, unknown>;
|
|
105
|
+
probeEmbeddingAvailability(): Promise<{
|
|
106
|
+
error?: string | undefined;
|
|
107
|
+
ok: boolean;
|
|
108
|
+
}>;
|
|
109
|
+
probeVectorAvailability(): Promise<boolean>;
|
|
110
|
+
close(): Promise<void>;
|
|
111
|
+
};
|
|
112
|
+
}>;
|
|
113
|
+
resolveMemoryBackendConfig(): {
|
|
114
|
+
backend: string;
|
|
115
|
+
};
|
|
116
|
+
closeAllMemorySearchManagers(): Promise<void>;
|
|
117
|
+
};
|
|
118
|
+
export {};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { resolveDurableNamespace } from "./durable-namespace.js";
|
|
2
|
+
import { detectDreamQuerySignal, resolveDreamCollection } from "./dream-routing.js";
|
|
3
|
+
export function buildMemoryRuntimeBridge(getRpc, cfg) {
|
|
4
|
+
return {
|
|
5
|
+
async getMemorySearchManager(params = {}) {
|
|
6
|
+
const status = await readStatus(getRpc, params.purpose);
|
|
7
|
+
return {
|
|
8
|
+
manager: createMemorySearchManager(getRpc, cfg, params, status),
|
|
9
|
+
};
|
|
10
|
+
},
|
|
11
|
+
resolveMemoryBackendConfig() {
|
|
12
|
+
// We keep retrieval inside the plugin-side sidecar rather than delegating to
|
|
13
|
+
// OpenClaw's external QMD path.
|
|
14
|
+
return { backend: "builtin" };
|
|
15
|
+
},
|
|
16
|
+
async closeAllMemorySearchManagers() {
|
|
17
|
+
// Context-engine lifecycle cleanup still happens through gateway_stop.
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function createMemorySearchManager(getRpc, cfg, defaults, initialStatus) {
|
|
22
|
+
let cachedStatus = initialStatus;
|
|
23
|
+
return {
|
|
24
|
+
async search(queryOrParams = {}, opts = {}) {
|
|
25
|
+
const legacyCall = typeof queryOrParams === "string";
|
|
26
|
+
const params = legacyCall
|
|
27
|
+
? {
|
|
28
|
+
query: queryOrParams,
|
|
29
|
+
limit: opts.limit ?? opts.k ?? opts.topK,
|
|
30
|
+
sessionId: opts.sessionId,
|
|
31
|
+
sessionKey: opts.sessionKey,
|
|
32
|
+
userId: opts.userId,
|
|
33
|
+
agentId: opts.agentId,
|
|
34
|
+
context: opts.context,
|
|
35
|
+
}
|
|
36
|
+
: queryOrParams;
|
|
37
|
+
const queryText = firstString(params.query, params.text, params.input, params.q);
|
|
38
|
+
if (!queryText) {
|
|
39
|
+
return legacyCall ? { results: [], error: "Missing query text for LibraVDB memory search" } : [];
|
|
40
|
+
}
|
|
41
|
+
const dreamQuery = detectDreamQuerySignal(queryText);
|
|
42
|
+
const sessionId = firstString(params.sessionId, params.context?.sessionId);
|
|
43
|
+
const userId = resolveDurableNamespace({
|
|
44
|
+
userId: firstString(params.userId, params.context?.userId),
|
|
45
|
+
sessionKey: firstString(params.sessionKey, params.context?.sessionKey),
|
|
46
|
+
agentId: firstString(params.agentId, params.context?.agentId, defaults.agentId),
|
|
47
|
+
fallback: sessionId ? `session:${sessionId}` : undefined,
|
|
48
|
+
});
|
|
49
|
+
const k = normalizePositiveInteger(params.k, params.limit, params.topK, cfg.topK, 8);
|
|
50
|
+
const rpc = await getRpc();
|
|
51
|
+
const result = dreamQuery.active
|
|
52
|
+
? await rpc.call("search_text", {
|
|
53
|
+
collection: resolveDreamCollection(userId),
|
|
54
|
+
text: queryText,
|
|
55
|
+
k,
|
|
56
|
+
})
|
|
57
|
+
: await searchResolvedCollections(rpc, cfg, userId, sessionId, queryText, k);
|
|
58
|
+
const legacyResults = result.results.map((item) => ({
|
|
59
|
+
...item,
|
|
60
|
+
content: item.text,
|
|
61
|
+
}));
|
|
62
|
+
if (legacyCall) {
|
|
63
|
+
return { results: legacyResults };
|
|
64
|
+
}
|
|
65
|
+
return result.results.map(toMemorySearchResult);
|
|
66
|
+
},
|
|
67
|
+
async readFile(params) {
|
|
68
|
+
const located = await loadSearchResultText(getRpc, params.relPath);
|
|
69
|
+
const fromLine = Math.max(1, params.from ?? 1);
|
|
70
|
+
const lineCount = Math.max(1, params.lines ?? 200);
|
|
71
|
+
const lines = located.text.split("\n");
|
|
72
|
+
const text = lines.slice(fromLine - 1, fromLine - 1 + lineCount).join("\n");
|
|
73
|
+
return {
|
|
74
|
+
text,
|
|
75
|
+
path: located.path,
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
async ingest() {
|
|
79
|
+
// The plugin already owns per-turn ingest through the context engine.
|
|
80
|
+
return { ingested: false, delegatedToContextEngine: true };
|
|
81
|
+
},
|
|
82
|
+
async sync() {
|
|
83
|
+
cachedStatus = await readStatus(getRpc, defaults.purpose);
|
|
84
|
+
return { synced: true, delegatedToContextEngine: true };
|
|
85
|
+
},
|
|
86
|
+
status() {
|
|
87
|
+
return cachedStatus;
|
|
88
|
+
},
|
|
89
|
+
async probeEmbeddingAvailability() {
|
|
90
|
+
return {
|
|
91
|
+
ok: cachedStatus.ok ?? false,
|
|
92
|
+
...(cachedStatus.ok === false && typeof cachedStatus.message === "string"
|
|
93
|
+
? { error: cachedStatus.message }
|
|
94
|
+
: {}),
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
async probeVectorAvailability() {
|
|
98
|
+
return cachedStatus.ok ?? false;
|
|
99
|
+
},
|
|
100
|
+
async close() {
|
|
101
|
+
// The sidecar connection is shared by the plugin runtime.
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function searchResolvedCollections(rpc, cfg, userId, sessionId, queryText, k) {
|
|
106
|
+
const collections = resolveSearchCollections(cfg, userId, sessionId);
|
|
107
|
+
return collections.length === 1
|
|
108
|
+
? await rpc.call("search_text", {
|
|
109
|
+
collection: collections[0],
|
|
110
|
+
text: queryText,
|
|
111
|
+
k,
|
|
112
|
+
})
|
|
113
|
+
: await rpc.call("search_text_collections", {
|
|
114
|
+
collections,
|
|
115
|
+
text: queryText,
|
|
116
|
+
k,
|
|
117
|
+
excludeByCollection: {},
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function resolveSearchCollections(cfg, userId, sessionId) {
|
|
121
|
+
const collections = [`user:${userId}`, "global"];
|
|
122
|
+
if (!sessionId) {
|
|
123
|
+
return collections;
|
|
124
|
+
}
|
|
125
|
+
if (cfg.useSessionSummarySearchExperiment) {
|
|
126
|
+
collections.unshift(`session_summary:${sessionId}`);
|
|
127
|
+
return collections;
|
|
128
|
+
}
|
|
129
|
+
if (cfg.useSessionRecallProjection) {
|
|
130
|
+
collections.unshift(`session_recall:${sessionId}`);
|
|
131
|
+
return collections;
|
|
132
|
+
}
|
|
133
|
+
collections.unshift(`session:${sessionId}`);
|
|
134
|
+
return collections;
|
|
135
|
+
}
|
|
136
|
+
function firstString(...values) {
|
|
137
|
+
return values.find((value) => typeof value === "string" && value.length > 0);
|
|
138
|
+
}
|
|
139
|
+
function toMemorySearchResult(item) {
|
|
140
|
+
const collection = typeof item.metadata.collection === "string" ? item.metadata.collection : "memory";
|
|
141
|
+
return {
|
|
142
|
+
path: encodeSearchResultPath(collection, item.id),
|
|
143
|
+
startLine: 1,
|
|
144
|
+
endLine: Math.max(1, item.text.split("\n").length),
|
|
145
|
+
score: item.score,
|
|
146
|
+
snippet: item.text,
|
|
147
|
+
source: collection.startsWith("session:") || collection.startsWith("session_") ? "sessions" : "memory",
|
|
148
|
+
citation: `${collection}:${item.id}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
async function loadSearchResultText(getRpc, relPath) {
|
|
152
|
+
const { collection, id } = decodeSearchResultPath(relPath);
|
|
153
|
+
const rpc = await getRpc();
|
|
154
|
+
const result = await rpc.call("list_collection", { collection });
|
|
155
|
+
const item = result.results.find((entry) => entry.id === id);
|
|
156
|
+
if (!item) {
|
|
157
|
+
throw new Error(`LibraVDB memory path not found: ${relPath}`);
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
path: relPath,
|
|
161
|
+
text: item.text,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function encodeSearchResultPath(collection, id) {
|
|
165
|
+
return `${encodeURIComponent(collection)}::${encodeURIComponent(id)}`;
|
|
166
|
+
}
|
|
167
|
+
function decodeSearchResultPath(relPath) {
|
|
168
|
+
const separator = relPath.indexOf("::");
|
|
169
|
+
if (separator <= 0) {
|
|
170
|
+
throw new Error(`Unsupported LibraVDB memory path: ${relPath}`);
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
collection: decodeURIComponent(relPath.slice(0, separator)),
|
|
174
|
+
id: decodeURIComponent(relPath.slice(separator + 2)),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
async function readStatus(getRpc, purpose) {
|
|
178
|
+
try {
|
|
179
|
+
const rpc = await getRpc();
|
|
180
|
+
const status = await rpc.call("status", {});
|
|
181
|
+
return {
|
|
182
|
+
...status,
|
|
183
|
+
backend: "builtin",
|
|
184
|
+
provider: "libravdb",
|
|
185
|
+
model: status.embeddingProfile ?? "unknown",
|
|
186
|
+
ok: status.ok ?? false,
|
|
187
|
+
message: status.message ?? "ok",
|
|
188
|
+
turnCount: status.turnCount ?? 0,
|
|
189
|
+
memoryCount: status.memoryCount ?? 0,
|
|
190
|
+
gatingThreshold: status.gatingThreshold,
|
|
191
|
+
abstractiveReady: status.abstractiveReady ?? false,
|
|
192
|
+
embeddingProfile: status.embeddingProfile ?? "unknown",
|
|
193
|
+
purpose,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
return {
|
|
198
|
+
backend: "builtin",
|
|
199
|
+
provider: "libravdb",
|
|
200
|
+
model: "unknown",
|
|
201
|
+
ok: false,
|
|
202
|
+
message: error instanceof Error && error.message ? error.message : String(error),
|
|
203
|
+
turnCount: 0,
|
|
204
|
+
memoryCount: 0,
|
|
205
|
+
embeddingProfile: "unknown",
|
|
206
|
+
purpose,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function normalizePositiveInteger(...values) {
|
|
211
|
+
for (const value of values) {
|
|
212
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
213
|
+
return Math.max(1, Math.floor(value));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return 8;
|
|
217
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { RpcClient } from "./rpc.js";
|
|
2
|
+
import { GrpcKernelClient } from "./grpc-client.js";
|
|
3
|
+
import type { LoggerLike, PluginConfig } from "./types.js";
|
|
4
|
+
export type RpcGetter = () => Promise<RpcClient>;
|
|
5
|
+
export declare const DEFAULT_RPC_TIMEOUT_MS = 30000;
|
|
6
|
+
export declare const STARTUP_HEALTH_TIMEOUT_MS = 2000;
|
|
7
|
+
export interface LifecycleHint {
|
|
8
|
+
hook: "before_reset" | "session_end";
|
|
9
|
+
reason?: string;
|
|
10
|
+
sessionFile?: string;
|
|
11
|
+
sessionId?: string;
|
|
12
|
+
sessionKey?: string;
|
|
13
|
+
agentId?: string;
|
|
14
|
+
workspaceDir?: string;
|
|
15
|
+
messageCount?: number;
|
|
16
|
+
durationMs?: number;
|
|
17
|
+
transcriptArchived?: boolean;
|
|
18
|
+
nextSessionId?: string;
|
|
19
|
+
nextSessionKey?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PluginRuntime {
|
|
22
|
+
getRpc: RpcGetter;
|
|
23
|
+
getKernel(): GrpcKernelClient | null;
|
|
24
|
+
emitLifecycleHint(hint: LifecycleHint): Promise<void>;
|
|
25
|
+
shutdown(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export declare function createPluginRuntime(cfg: PluginConfig, logger?: LoggerLike): PluginRuntime;
|
|
28
|
+
export declare function enrichStartupError(error: unknown, healthMessage?: string): Error;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { RpcClient } from "./rpc.js";
|
|
2
|
+
import { GrpcKernelClient } from "./grpc-client.js";
|
|
3
|
+
import { daemonProvisioningHint, startSidecar } from "./sidecar.js";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
export const DEFAULT_RPC_TIMEOUT_MS = 30000;
|
|
6
|
+
export const STARTUP_HEALTH_TIMEOUT_MS = 2000;
|
|
7
|
+
export function createPluginRuntime(cfg, logger = console) {
|
|
8
|
+
let started = null;
|
|
9
|
+
let stopped = false;
|
|
10
|
+
let resolvedKernel = null;
|
|
11
|
+
const ensureStarted = async () => {
|
|
12
|
+
if (stopped) {
|
|
13
|
+
throw new Error("LibraVDB plugin runtime has been shut down");
|
|
14
|
+
}
|
|
15
|
+
if (!started) {
|
|
16
|
+
started = (async () => {
|
|
17
|
+
const sidecar = await startSidecar(cfg, logger);
|
|
18
|
+
const rpc = new RpcClient(sidecar.socket, {
|
|
19
|
+
timeoutMs: cfg.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS,
|
|
20
|
+
});
|
|
21
|
+
const health = await rpc.call("health", {}, {
|
|
22
|
+
timeoutMs: STARTUP_HEALTH_TIMEOUT_MS,
|
|
23
|
+
});
|
|
24
|
+
if (!health.ok) {
|
|
25
|
+
try {
|
|
26
|
+
await sidecar.shutdown();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Ignore cleanup failure on startup rejection.
|
|
30
|
+
}
|
|
31
|
+
throw enrichStartupError("LibraVDB daemon failed health check", health.message);
|
|
32
|
+
}
|
|
33
|
+
let kernel = null;
|
|
34
|
+
if (cfg.grpcEndpoint) {
|
|
35
|
+
try {
|
|
36
|
+
const secret = loadSecretFromEnv();
|
|
37
|
+
kernel = new GrpcKernelClient({
|
|
38
|
+
endpoint: cfg.grpcEndpoint,
|
|
39
|
+
secret,
|
|
40
|
+
timeoutMs: cfg.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
logger.warn?.(`LibraVDB: failed to initialize gRPC kernel client: ${formatError(error)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
resolvedKernel = kernel;
|
|
48
|
+
return { rpc, sidecar, kernel };
|
|
49
|
+
})().catch((error) => {
|
|
50
|
+
started = null;
|
|
51
|
+
throw enrichStartupError(error);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return await started;
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
async getRpc() {
|
|
58
|
+
return (await ensureStarted()).rpc;
|
|
59
|
+
},
|
|
60
|
+
getKernel() {
|
|
61
|
+
if (!started)
|
|
62
|
+
return null;
|
|
63
|
+
return resolvedKernel;
|
|
64
|
+
},
|
|
65
|
+
async emitLifecycleHint(hint) {
|
|
66
|
+
try {
|
|
67
|
+
const active = await ensureStarted();
|
|
68
|
+
await active.rpc.call("session_lifecycle_hint", hint);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
logger.warn?.(`LibraVDB lifecycle hint dropped: ${formatError(error)}`);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
async shutdown() {
|
|
75
|
+
stopped = true;
|
|
76
|
+
if (!started) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const active = started;
|
|
80
|
+
started = null;
|
|
81
|
+
const { rpc, sidecar, kernel } = await active;
|
|
82
|
+
try {
|
|
83
|
+
if (kernel)
|
|
84
|
+
kernel.close();
|
|
85
|
+
await rpc.call("flush", {});
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await sidecar.shutdown();
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function loadSecretFromEnv() {
|
|
94
|
+
const secret = process.env.LIBRAVDB_AUTH_SECRET;
|
|
95
|
+
if (secret)
|
|
96
|
+
return secret;
|
|
97
|
+
const path = process.env.LIBRAVDB_AUTH_SECRET_FILE;
|
|
98
|
+
if (path) {
|
|
99
|
+
try {
|
|
100
|
+
return readFileSync(path, "utf8").trim();
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
function formatError(error) {
|
|
109
|
+
if (error instanceof Error && error.message.trim()) {
|
|
110
|
+
return error.message;
|
|
111
|
+
}
|
|
112
|
+
return String(error);
|
|
113
|
+
}
|
|
114
|
+
export function enrichStartupError(error, healthMessage) {
|
|
115
|
+
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
116
|
+
const message = rawMessage.trim() || "LibraVDB daemon startup failed";
|
|
117
|
+
if (message.includes("install and start libravdbd separately") || message.includes("package does not provision the daemon binary")) {
|
|
118
|
+
return error instanceof Error ? error : new Error(message);
|
|
119
|
+
}
|
|
120
|
+
const shouldHint = /health check|daemon unavailable|connection refused|ECONNREFUSED|ENOENT|fallback mode|ONNX Runtime|embedder/i.test(`${message} ${healthMessage ?? ""}`);
|
|
121
|
+
if (!shouldHint) {
|
|
122
|
+
return error instanceof Error ? error : new Error(message);
|
|
123
|
+
}
|
|
124
|
+
const detail = healthMessage?.trim();
|
|
125
|
+
const prefix = detail && !message.includes(detail) ? `${message}: ${detail}` : message;
|
|
126
|
+
return new Error(`${prefix}. ${daemonProvisioningHint()}`);
|
|
127
|
+
}
|