@lacneu/openclaw-knowledge 3.1.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.
@@ -0,0 +1,33 @@
1
+ // Gemini embedding client for query vectors.
2
+ //
3
+ // We deliberately hit the *native* `embedContent` endpoint rather than the
4
+ // OpenAI-compatible one, because the OpenAI endpoint does not support
5
+ // multimodal inputs and we want to stay in the same embedding space as the
6
+ // n8n ingestion pipeline (which uses the native endpoint with images/audio).
7
+ const GEMINI_EMBED_URL = "https://generativelanguage.googleapis.com/v1beta/" +
8
+ "models/gemini-embedding-2-preview:embedContent";
9
+ /**
10
+ * Embed a text query via Gemini Embedding 2 Preview.
11
+ * Uses the same model as n8n document ingestion so that query vectors and
12
+ * stored chunks live in the same 3072-dimensional space.
13
+ *
14
+ * @throws Error on any non-OK HTTP response, with the first 200 chars of the
15
+ * error body for debugging.
16
+ */
17
+ export async function embedQuery(text, geminiApiKey) {
18
+ const url = `${GEMINI_EMBED_URL}?key=${geminiApiKey}`;
19
+ const resp = await fetch(url, {
20
+ method: "POST",
21
+ headers: { "Content-Type": "application/json" },
22
+ body: JSON.stringify({
23
+ content: { parts: [{ text }] },
24
+ }),
25
+ });
26
+ if (!resp.ok) {
27
+ const body = await resp.text();
28
+ throw new Error(`Gemini embedding failed (${resp.status}): ${body.slice(0, 200)}`);
29
+ }
30
+ const data = (await resp.json());
31
+ return data.embedding.values;
32
+ }
33
+ //# sourceMappingURL=embeddings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embeddings.js","sourceRoot":"","sources":["../src/embeddings.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,EAAE;AACF,2EAA2E;AAC3E,sEAAsE;AACtE,2EAA2E;AAC3E,6EAA6E;AAE7E,MAAM,gBAAgB,GACpB,mDAAmD;IACnD,gDAAgD,CAAC;AAMnD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAY,EACZ,YAAoB;IAEpB,MAAM,GAAG,GAAG,GAAG,gBAAgB,QAAQ,YAAY,EAAE,CAAC;IAEtD,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC5B,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE;SAC/B,CAAC;KACH,CAAC,CAAC;IAEH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,4BAA4B,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAClE,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAA4B,CAAC;IAC5D,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type { OpenClawPluginApi, PluginLogger } from "openclaw/plugin-sdk/plugin-entry";
2
+ import type { BeforePromptBuildEvent, BeforePromptBuildResult, PgPoolLike, ResolvedKnowledgeConfig } from "./types.js";
3
+ export { resolveEnv, resolveConfig } from "./config.js";
4
+ export { embedQuery } from "./embeddings.js";
5
+ export { searchCollection, formatPgvectorResults } from "./pgvector.js";
6
+ export { queryLightRAG, truncateLightRAG, formatLightRAGResults } from "./lightrag.js";
7
+ export type { BeforePromptBuildEvent, BeforePromptBuildResult, KnowledgePluginConfig, LightRAGQueryMode, PgPoolLike, PgvectorResult, PgvectorRow, PromptContentPart, PromptMessage, ResolvedKnowledgeConfig, } from "./types.js";
8
+ interface HookHandlerDeps {
9
+ config: ResolvedKnowledgeConfig;
10
+ pool: PgPoolLike | null;
11
+ logger: PluginLogger;
12
+ }
13
+ /**
14
+ * Build the `before_prompt_build` handler bound to a specific plugin state.
15
+ * Kept as a pure factory so the handler can be unit-tested with fake deps.
16
+ */
17
+ export declare function createBeforePromptBuildHandler(deps: HookHandlerDeps): (event: BeforePromptBuildEvent) => Promise<BeforePromptBuildResult | undefined>;
18
+ /**
19
+ * Register the plugin against a minimal shape-compatible subset of the
20
+ * OpenClaw plugin API. Returns nothing; side effects are setting a hook and
21
+ * logging the initial status.
22
+ */
23
+ export declare function registerKnowledgePlugin(api: OpenClawPluginApi): void;
24
+ declare const _default: {
25
+ id: string;
26
+ name: string;
27
+ description: string;
28
+ configSchema: import("openclaw/plugin-sdk/plugin-entry").OpenClawPluginConfigSchema;
29
+ register: NonNullable<import("openclaw/plugin-sdk/plugin-entry").OpenClawPluginDefinition["register"]>;
30
+ } & Pick<import("openclaw/plugin-sdk/plugin-entry").OpenClawPluginDefinition, "kind" | "reload" | "nodeHostCommands" | "securityAuditCollectors">;
31
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,239 @@
1
+ // openclaw-knowledge — Multi-source knowledge plugin for OpenClaw
2
+ //
3
+ // Queries two knowledge sources in parallel and injects relevant context
4
+ // into the agent's system prompt via `appendSystemContext`:
5
+ // 1. PostgreSQL pgvector — semantic vector search on document embeddings
6
+ // 2. LightRAG — knowledge graph with entity/relation multi-hop search
7
+ //
8
+ // Hook: before_prompt_build (requires OpenClaw >= v2026.3.7)
9
+ // Depends on: pg (node-postgres)
10
+ //
11
+ // This is the canonical entry point for the plugin. Helpers live in sibling
12
+ // modules (`config.ts`, `embeddings.ts`, `pgvector.ts`, `lightrag.ts`) so the
13
+ // business logic can be unit-tested without instantiating the full SDK.
14
+ import pg from "pg";
15
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
16
+ import { resolveConfig } from "./config.js";
17
+ import { embedQuery } from "./embeddings.js";
18
+ import { searchCollection, formatPgvectorResults } from "./pgvector.js";
19
+ import { queryLightRAG, formatLightRAGResults } from "./lightrag.js";
20
+ // Re-export helpers so the test suite can import them directly without
21
+ // duplicating imports from every submodule.
22
+ export { resolveEnv, resolveConfig } from "./config.js";
23
+ export { embedQuery } from "./embeddings.js";
24
+ export { searchCollection, formatPgvectorResults } from "./pgvector.js";
25
+ export { queryLightRAG, truncateLightRAG, formatLightRAGResults } from "./lightrag.js";
26
+ // ---------------------------------------------------------------------------
27
+ // Hook handler factory
28
+ //
29
+ // Extracted from `register` so tests can exercise the handler directly
30
+ // without mocking the full plugin API surface.
31
+ // ---------------------------------------------------------------------------
32
+ const MAX_CONSECUTIVE_ERRORS = 3;
33
+ const COOLDOWN_MS = 5 * 60 * 1000;
34
+ const MIN_QUERY_LENGTH = 3;
35
+ /**
36
+ * Build the `before_prompt_build` handler bound to a specific plugin state.
37
+ * Kept as a pure factory so the handler can be unit-tested with fake deps.
38
+ */
39
+ export function createBeforePromptBuildHandler(deps) {
40
+ const { config, pool, logger } = deps;
41
+ // Per-instance state: consecutive failure counter and cooldown deadline.
42
+ // Closed-over so two registrations of the hook never share state.
43
+ let consecutiveErrors = 0;
44
+ let cooldownUntil = 0;
45
+ return async function beforePromptBuild(event) {
46
+ if (!config.enabled)
47
+ return undefined;
48
+ // Cooldown after repeated failures: skip silently until the deadline
49
+ // passes, then reset the counter and resume normal operation.
50
+ if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
51
+ if (Date.now() < cooldownUntil)
52
+ return undefined;
53
+ consecutiveErrors = 0;
54
+ logger.info("openclaw-knowledge: resuming after cooldown");
55
+ }
56
+ const query = extractQueryFromMessages(event.messages);
57
+ if (!query || query.trim().length < MIN_QUERY_LENGTH)
58
+ return undefined;
59
+ try {
60
+ const tasks = [];
61
+ if (config.pgvectorEnabled && pool) {
62
+ tasks.push(runPgvectorSource(pool, query, config));
63
+ }
64
+ if (config.lightragEnabled) {
65
+ tasks.push(runLightRAGSource(query, config));
66
+ }
67
+ const settled = await Promise.allSettled(tasks);
68
+ const sections = [];
69
+ let failedSources = 0;
70
+ for (const result of settled) {
71
+ if (result.status === "rejected") {
72
+ failedSources++;
73
+ const reason = result.reason;
74
+ logger.error(`openclaw-knowledge: source failed — ${reason?.message ?? String(result.reason)}`);
75
+ continue;
76
+ }
77
+ const section = renderSection(result.value, config, logger);
78
+ if (section)
79
+ sections.push(section);
80
+ }
81
+ // If every source we launched failed, treat the turn as a failure for
82
+ // cooldown tracking. A partial failure is fine — the other source's
83
+ // context is better than nothing.
84
+ if (failedSources > 0 && failedSources === tasks.length) {
85
+ consecutiveErrors++;
86
+ if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
87
+ cooldownUntil = Date.now() + COOLDOWN_MS;
88
+ logger.error(`openclaw-knowledge: ${consecutiveErrors} consecutive errors — cooling down 5 min`);
89
+ }
90
+ return undefined;
91
+ }
92
+ consecutiveErrors = 0;
93
+ if (sections.length === 0)
94
+ return undefined;
95
+ return {
96
+ appendSystemContext: [
97
+ "",
98
+ "## Relevant Knowledge Base",
99
+ "Use this information to answer the user's question accurately.",
100
+ "Always cite the source document name when using this information.",
101
+ "",
102
+ ...sections,
103
+ ].join("\n"),
104
+ };
105
+ }
106
+ catch (err) {
107
+ // Catch-all: an unexpected crash must never propagate to the agent.
108
+ consecutiveErrors++;
109
+ const message = err instanceof Error ? err.message : String(err);
110
+ if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
111
+ cooldownUntil = Date.now() + COOLDOWN_MS;
112
+ logger.error(`openclaw-knowledge: ${consecutiveErrors} consecutive errors — cooling down 5 min: ${message}`);
113
+ }
114
+ else {
115
+ logger.error(`openclaw-knowledge: ${message}`);
116
+ }
117
+ return undefined;
118
+ }
119
+ };
120
+ }
121
+ /**
122
+ * Extract the most recent user message text. OpenClaw surfaces two content
123
+ * shapes: a plain string, or an array of typed content parts (multi-modal).
124
+ */
125
+ function extractQueryFromMessages(messages) {
126
+ if (!Array.isArray(messages) || messages.length === 0)
127
+ return "";
128
+ for (let i = messages.length - 1; i >= 0; i--) {
129
+ const msg = messages[i];
130
+ if (!msg || msg.role !== "user")
131
+ continue;
132
+ if (typeof msg.content === "string") {
133
+ return msg.content;
134
+ }
135
+ if (Array.isArray(msg.content)) {
136
+ return msg.content
137
+ .filter((p) => p.type === "text" && typeof p.text === "string")
138
+ .map((p) => p.text)
139
+ .join(" ");
140
+ }
141
+ return "";
142
+ }
143
+ return "";
144
+ }
145
+ async function runPgvectorSource(pool, query, config) {
146
+ const vector = await embedQuery(query, config.geminiApiKey);
147
+ const searches = config.collections.map((col) => searchCollection(pool, col, vector, config.topK, config.scoreThreshold));
148
+ const allResults = (await Promise.all(searches)).flat();
149
+ allResults.sort((a, b) => b.score - a.score);
150
+ return { source: "pgvector", data: allResults };
151
+ }
152
+ async function runLightRAGSource(query, config) {
153
+ const context = await queryLightRAG(config.lightragUrl, config.lightragApiKey, query, config.lightragQueryMode);
154
+ return { source: "lightrag", data: context };
155
+ }
156
+ function renderSection(result, config, logger) {
157
+ if (result.source === "pgvector") {
158
+ const formatted = formatPgvectorResults(result.data, config.maxInjectChars);
159
+ if (!formatted)
160
+ return null;
161
+ const topScore = result.data[0]?.score?.toFixed(2) ?? "n/a";
162
+ logger.info(`openclaw-knowledge: pgvector — ${result.data.length} result(s) (top: ${topScore})`);
163
+ return "### Document Search Results (pgvector)\n" + formatted;
164
+ }
165
+ if (result.source === "lightrag") {
166
+ const formatted = formatLightRAGResults(result.data, config.lightragMaxChars);
167
+ if (!formatted)
168
+ return null;
169
+ logger.info(`openclaw-knowledge: LightRAG — ${formatted.truncated.length}/${formatted.originalLength} chars (truncated from ${formatted.originalLength})`);
170
+ return "### Knowledge Graph Context (LightRAG)\n" + formatted.truncated;
171
+ }
172
+ return null;
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // Plugin registration helper
176
+ //
177
+ // Exposed so tests can exercise the full wiring (including api.on) without
178
+ // going through `definePluginEntry`, which is tied to the SDK runtime.
179
+ // ---------------------------------------------------------------------------
180
+ /**
181
+ * Register the plugin against a minimal shape-compatible subset of the
182
+ * OpenClaw plugin API. Returns nothing; side effects are setting a hook and
183
+ * logging the initial status.
184
+ */
185
+ export function registerKnowledgePlugin(api) {
186
+ const rawConfig = (api.pluginConfig ?? {});
187
+ const config = resolveConfig(rawConfig);
188
+ if (!config.pgvectorEnabled && !config.lightragEnabled) {
189
+ api.logger.warn("openclaw-knowledge: neither pgvector nor LightRAG configured — plugin disabled");
190
+ return;
191
+ }
192
+ // Only instantiate the pg pool when pgvector is actually in play. Booting
193
+ // a pool with no valid connection string would keep the plugin disabled
194
+ // anyway and leak sockets on hot-reload.
195
+ let pool = null;
196
+ if (config.pgvectorEnabled) {
197
+ const realPool = new pg.Pool({
198
+ connectionString: config.postgresUrl,
199
+ max: 3,
200
+ idleTimeoutMillis: 30000,
201
+ });
202
+ realPool.on("error", (err) => {
203
+ api.logger.error(`openclaw-knowledge: pool error — ${err.message}`);
204
+ });
205
+ pool = realPool;
206
+ }
207
+ const sources = [];
208
+ if (config.pgvectorEnabled) {
209
+ sources.push(`pgvector (${config.collections.join(", ")})`);
210
+ }
211
+ if (config.lightragEnabled) {
212
+ sources.push(`LightRAG (${config.lightragQueryMode})`);
213
+ }
214
+ api.logger.info(`openclaw-knowledge: ready — sources: ${sources.join(" + ")}`);
215
+ const handler = createBeforePromptBuildHandler({
216
+ config,
217
+ pool,
218
+ logger: api.logger,
219
+ });
220
+ // The SDK's `api.on<K>` signature is strongly typed per hook name, so we
221
+ // use a cast here to bridge our structural handler type with the precise
222
+ // `PluginHookHandlerMap["before_prompt_build"]` expected signature.
223
+ // The handler itself is fully type-safe on its own contract (see
224
+ // {@link createBeforePromptBuildHandler}).
225
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
226
+ api.on("before_prompt_build", handler);
227
+ }
228
+ // ---------------------------------------------------------------------------
229
+ // Canonical plugin entry
230
+ // ---------------------------------------------------------------------------
231
+ export default definePluginEntry({
232
+ id: "openclaw-knowledge",
233
+ name: "Knowledge Base",
234
+ description: "Multi-source knowledge search for OpenClaw (pgvector + LightRAG)",
235
+ register(api) {
236
+ registerKnowledgePlugin(api);
237
+ },
238
+ });
239
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { LightRAGQueryMode } from "./types.js";
2
+ /**
3
+ * Query a LightRAG server for context relevant to `query`.
4
+ *
5
+ * Modes:
6
+ * - `naive` — simple vector similarity on chunks
7
+ * - `local` — entity-neighbourhood traversal
8
+ * - `global` — community summaries
9
+ * - `hybrid` — local + global (recommended default)
10
+ *
11
+ * @throws Error on any non-OK HTTP response, with the first 200 chars of the
12
+ * error body for debugging.
13
+ */
14
+ export declare function queryLightRAG(url: string, apiKey: string, query: string, mode?: LightRAGQueryMode): Promise<string>;
15
+ /**
16
+ * Truncate text to `maxChars` without cutting mid-sentence when possible.
17
+ * Falls back to a raw character cut if no sentence boundary is found in the
18
+ * second half of the allowed window.
19
+ */
20
+ export declare function truncateLightRAG(text: string, maxChars: number): string;
21
+ /**
22
+ * Convenience wrapper used by the hook handler: trim the context, truncate
23
+ * it to the configured budget, and return `null` if nothing remains.
24
+ */
25
+ export declare function formatLightRAGResults(rawContext: string, maxChars: number): {
26
+ truncated: string;
27
+ originalLength: number;
28
+ } | null;
@@ -0,0 +1,69 @@
1
+ // LightRAG query client.
2
+ //
3
+ // LightRAG is a knowledge graph server built on Neo4j + a vector store. We
4
+ // call its `/query` endpoint with `only_need_context=true` so it returns the
5
+ // assembled context text WITHOUT running its own LLM synthesis — we only need
6
+ // the raw context to feed back into OpenClaw's agent.
7
+ /**
8
+ * Query a LightRAG server for context relevant to `query`.
9
+ *
10
+ * Modes:
11
+ * - `naive` — simple vector similarity on chunks
12
+ * - `local` — entity-neighbourhood traversal
13
+ * - `global` — community summaries
14
+ * - `hybrid` — local + global (recommended default)
15
+ *
16
+ * @throws Error on any non-OK HTTP response, with the first 200 chars of the
17
+ * error body for debugging.
18
+ */
19
+ export async function queryLightRAG(url, apiKey, query, mode = "hybrid") {
20
+ const headers = {
21
+ "Content-Type": "application/json",
22
+ };
23
+ if (apiKey)
24
+ headers["X-API-Key"] = apiKey;
25
+ const resp = await fetch(`${url}/query`, {
26
+ method: "POST",
27
+ headers,
28
+ body: JSON.stringify({
29
+ query,
30
+ mode,
31
+ only_need_context: true,
32
+ stream: false,
33
+ }),
34
+ });
35
+ if (!resp.ok) {
36
+ const body = await resp.text();
37
+ throw new Error(`LightRAG query failed (${resp.status}): ${body.slice(0, 200)}`);
38
+ }
39
+ const data = (await resp.json());
40
+ return data.response ?? "";
41
+ }
42
+ /**
43
+ * Truncate text to `maxChars` without cutting mid-sentence when possible.
44
+ * Falls back to a raw character cut if no sentence boundary is found in the
45
+ * second half of the allowed window.
46
+ */
47
+ export function truncateLightRAG(text, maxChars) {
48
+ if (text.length <= maxChars)
49
+ return text;
50
+ const truncated = text.slice(0, maxChars);
51
+ const lastPeriod = truncated.lastIndexOf(".");
52
+ return lastPeriod > maxChars * 0.5
53
+ ? truncated.slice(0, lastPeriod + 1)
54
+ : truncated;
55
+ }
56
+ /**
57
+ * Convenience wrapper used by the hook handler: trim the context, truncate
58
+ * it to the configured budget, and return `null` if nothing remains.
59
+ */
60
+ export function formatLightRAGResults(rawContext, maxChars) {
61
+ const context = rawContext.trim();
62
+ if (context.length === 0)
63
+ return null;
64
+ return {
65
+ truncated: truncateLightRAG(context, maxChars),
66
+ originalLength: context.length,
67
+ };
68
+ }
69
+ //# sourceMappingURL=lightrag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lightrag.js","sourceRoot":"","sources":["../src/lightrag.ts"],"names":[],"mappings":"AAAA,yBAAyB;AACzB,EAAE;AACF,2EAA2E;AAC3E,6EAA6E;AAC7E,8EAA8E;AAC9E,sDAAsD;AAQtD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAW,EACX,MAAc,EACd,KAAa,EACb,OAA0B,QAAQ;IAElC,MAAM,OAAO,GAA2B;QACtC,cAAc,EAAE,kBAAkB;KACnC,CAAC;IACF,IAAI,MAAM;QAAE,OAAO,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC;IAE1C,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,GAAG,QAAQ,EAAE;QACvC,MAAM,EAAE,MAAM;QACd,OAAO;QACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,KAAK;YACL,IAAI;YACJ,iBAAiB,EAAE,IAAI;YACvB,MAAM,EAAE,KAAK;SACd,CAAC;KACH,CAAC,CAAC;IAEH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,0BAA0B,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAChE,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAA4B,CAAC;IAC5D,OAAO,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,QAAgB;IAC7D,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ;QAAE,OAAO,IAAI,CAAC;IAEzC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC9C,OAAO,UAAU,GAAG,QAAQ,GAAG,GAAG;QAChC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC;QACpC,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAkB,EAClB,QAAgB;IAEhB,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEtC,OAAO;QACL,SAAS,EAAE,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC;QAC9C,cAAc,EAAE,OAAO,CAAC,MAAM;KAC/B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { PgPoolLike, PgvectorResult } from "./types.js";
2
+ /**
3
+ * Search a single collection in `knowledge_vectors` using cosine similarity.
4
+ *
5
+ * Score filtering is performed in JS after the query rather than in SQL so
6
+ * the HNSW index can handle `ORDER BY ... <=> ... LIMIT $3` efficiently
7
+ * (a WHERE clause on the computed score would defeat the index).
8
+ *
9
+ * On any database error we swallow the exception and return an empty array —
10
+ * the plugin must never block the agent, so a DB hiccup degrades gracefully.
11
+ */
12
+ export declare function searchCollection(pool: PgPoolLike, collection: string, vector: number[], topK: number, scoreThreshold: number): Promise<PgvectorResult[]>;
13
+ /**
14
+ * Format pgvector results for injection into the system prompt.
15
+ * Respects a character budget so we never blow the context window with a
16
+ * single oversized chunk — entries are appended whole until the budget is hit.
17
+ *
18
+ * Returns `null` when there is nothing useful to inject so the caller can
19
+ * easily skip empty sections.
20
+ */
21
+ export declare function formatPgvectorResults(results: PgvectorResult[], maxChars: number): string | null;
@@ -0,0 +1,81 @@
1
+ // PostgreSQL / pgvector search helpers.
2
+ //
3
+ // The HNSW index for our 3072-dimensional embeddings is built on
4
+ // `halfvec(3072)` because pgvector's HNSW implementation caps at 2000 dims
5
+ // for the native `vector` type. Both the column cast and the query parameter
6
+ // cast must match, otherwise the planner falls back to a sequential scan.
7
+ const SEARCH_SQL = `SELECT file_name, mime_type, text, file_id, source, owner,
8
+ chunk_index, total_chunks, timestamp_start, timestamp_end,
9
+ embedded_at,
10
+ 1 - (embedding::halfvec(3072) <=> $1::halfvec(3072)) AS score
11
+ FROM knowledge_vectors
12
+ WHERE collection = $2
13
+ ORDER BY embedding::halfvec(3072) <=> $1::halfvec(3072)
14
+ LIMIT $3`;
15
+ /**
16
+ * Search a single collection in `knowledge_vectors` using cosine similarity.
17
+ *
18
+ * Score filtering is performed in JS after the query rather than in SQL so
19
+ * the HNSW index can handle `ORDER BY ... <=> ... LIMIT $3` efficiently
20
+ * (a WHERE clause on the computed score would defeat the index).
21
+ *
22
+ * On any database error we swallow the exception and return an empty array —
23
+ * the plugin must never block the agent, so a DB hiccup degrades gracefully.
24
+ */
25
+ export async function searchCollection(pool, collection, vector, topK, scoreThreshold) {
26
+ const vectorStr = `[${vector.join(",")}]`;
27
+ try {
28
+ const result = await pool.query(SEARCH_SQL, [vectorStr, collection, topK]);
29
+ // pg returns numeric columns as strings by default, so parse the score.
30
+ return result.rows
31
+ .map((row) => ({
32
+ collection,
33
+ score: parseFloat(row.score),
34
+ file_name: row.file_name ?? null,
35
+ mime_type: row.mime_type ?? null,
36
+ text: row.text ?? null,
37
+ file_id: row.file_id ?? null,
38
+ source: row.source ?? null,
39
+ owner: row.owner ?? null,
40
+ chunk_index: row.chunk_index ?? null,
41
+ total_chunks: row.total_chunks ?? null,
42
+ timestamp_start: row.timestamp_start ?? null,
43
+ timestamp_end: row.timestamp_end ?? null,
44
+ }))
45
+ .filter((row) => row.score >= scoreThreshold);
46
+ }
47
+ catch {
48
+ return [];
49
+ }
50
+ }
51
+ /**
52
+ * Format pgvector results for injection into the system prompt.
53
+ * Respects a character budget so we never blow the context window with a
54
+ * single oversized chunk — entries are appended whole until the budget is hit.
55
+ *
56
+ * Returns `null` when there is nothing useful to inject so the caller can
57
+ * easily skip empty sections.
58
+ */
59
+ export function formatPgvectorResults(results, maxChars) {
60
+ if (results.length === 0)
61
+ return null;
62
+ let output = "";
63
+ for (const r of results) {
64
+ const lines = [
65
+ `[${r.collection}] ${r.file_name ?? "unknown"} (score: ${r.score.toFixed(2)})`,
66
+ ];
67
+ if (r.timestamp_start) {
68
+ lines.push(`Segment: ${r.timestamp_start} - ${r.timestamp_end ?? ""}`);
69
+ }
70
+ if (r.text) {
71
+ lines.push(`Content: ${r.text}`);
72
+ }
73
+ lines.push(""); // blank line separator between entries
74
+ const entry = lines.join("\n");
75
+ if (output.length + entry.length > maxChars)
76
+ break;
77
+ output += entry;
78
+ }
79
+ return output;
80
+ }
81
+ //# sourceMappingURL=pgvector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pgvector.js","sourceRoot":"","sources":["../src/pgvector.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,EAAE;AACF,iEAAiE;AACjE,2EAA2E;AAC3E,6EAA6E;AAC7E,0EAA0E;AAI1E,MAAM,UAAU,GAAG;;;;;;;gBAOH,CAAC;AAEjB;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAgB,EAChB,UAAkB,EAClB,MAAgB,EAChB,IAAY,EACZ,cAAsB;IAEtB,MAAM,SAAS,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAE1C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC;QAE3E,wEAAwE;QACxE,OAAO,MAAM,CAAC,IAAI;aACf,GAAG,CAAC,CAAC,GAAgB,EAAkB,EAAE,CAAC,CAAC;YAC1C,UAAU;YACV,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;YAC5B,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,IAAI;YAChC,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,IAAI;YAChC,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,IAAI;YACtB,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,IAAI;YAC5B,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,IAAI;YAC1B,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,IAAI;YACxB,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,IAAI;YACpC,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,IAAI;YACtC,eAAe,EAAE,GAAG,CAAC,eAAe,IAAI,IAAI;YAC5C,aAAa,EAAE,GAAG,CAAC,aAAa,IAAI,IAAI;SACzC,CAAC,CAAC;aACF,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,IAAI,cAAc,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAyB,EACzB,QAAgB;IAEhB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEtC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,KAAK,GAAa;YACtB,IAAI,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,SAAS,IAAI,SAAS,YAAY,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;SAC/E,CAAC;QAEF,IAAI,CAAC,CAAC,eAAe,EAAE,CAAC;YACtB,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,eAAe,MAAM,CAAC,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC,CAAC;QACzE,CAAC;QAED,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;YACX,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACnC,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,uCAAuC;QACvD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE/B,IAAI,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,QAAQ;YAAE,MAAM;QACnD,MAAM,IAAI,KAAK,CAAC;IAClB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Runtime configuration as it appears in `plugins.entries.openclaw-knowledge.config`.
3
+ * All fields are optional — defaults are applied in {@link resolveConfig}.
4
+ */
5
+ export interface KnowledgePluginConfig {
6
+ enabled?: boolean;
7
+ geminiApiKey?: string;
8
+ postgresUrl?: string;
9
+ collections?: string[];
10
+ topK?: number;
11
+ scoreThreshold?: number;
12
+ maxInjectChars?: number;
13
+ pgvectorEnabled?: boolean;
14
+ lightragUrl?: string;
15
+ lightragApiKey?: string;
16
+ lightragQueryMode?: LightRAGQueryMode;
17
+ lightragMaxChars?: number;
18
+ lightragEnabled?: boolean;
19
+ }
20
+ export type LightRAGQueryMode = "naive" | "local" | "global" | "hybrid";
21
+ /**
22
+ * Fully resolved plugin configuration after defaults, env substitution, and
23
+ * derivation of the pgvector/lightrag enabled flags from presence of secrets.
24
+ */
25
+ export interface ResolvedKnowledgeConfig {
26
+ enabled: boolean;
27
+ geminiApiKey: string;
28
+ postgresUrl: string;
29
+ collections: string[];
30
+ topK: number;
31
+ scoreThreshold: number;
32
+ maxInjectChars: number;
33
+ pgvectorEnabled: boolean;
34
+ lightragUrl: string;
35
+ lightragApiKey: string;
36
+ lightragQueryMode: LightRAGQueryMode;
37
+ lightragMaxChars: number;
38
+ lightragEnabled: boolean;
39
+ }
40
+ /**
41
+ * One search hit from the PostgreSQL `knowledge_vectors` table, after score
42
+ * parsing and filtering.
43
+ */
44
+ export interface PgvectorResult {
45
+ collection: string;
46
+ score: number;
47
+ file_name: string | null;
48
+ mime_type: string | null;
49
+ text: string | null;
50
+ file_id: string | null;
51
+ source: string | null;
52
+ owner: string | null;
53
+ chunk_index: number | null;
54
+ total_chunks: number | null;
55
+ timestamp_start: string | null;
56
+ timestamp_end: string | null;
57
+ }
58
+ /**
59
+ * Minimal `pg.Pool` surface that {@link searchCollection} actually uses.
60
+ * Declared locally so helpers can be unit-tested without a real database
61
+ * and without pulling `@types/pg` into the test graph.
62
+ */
63
+ export interface PgPoolLike {
64
+ query(sql: string, params: unknown[]): Promise<{
65
+ rows: PgvectorRow[];
66
+ }>;
67
+ }
68
+ /**
69
+ * Raw row shape returned by the pgvector SQL query. `score` comes back as a
70
+ * string because pg returns numeric values as strings by default.
71
+ */
72
+ export interface PgvectorRow {
73
+ file_name?: string | null;
74
+ mime_type?: string | null;
75
+ text?: string | null;
76
+ file_id?: string | null;
77
+ source?: string | null;
78
+ owner?: string | null;
79
+ chunk_index?: number | null;
80
+ total_chunks?: number | null;
81
+ timestamp_start?: string | null;
82
+ timestamp_end?: string | null;
83
+ embedded_at?: string | null;
84
+ score: string;
85
+ }
86
+ /**
87
+ * Shape of the `before_prompt_build` event payload as consumed by this plugin.
88
+ * We only rely on `messages`; the SDK may add other fields that we ignore.
89
+ */
90
+ export interface BeforePromptBuildEvent {
91
+ messages?: PromptMessage[];
92
+ }
93
+ export interface PromptMessage {
94
+ role?: string;
95
+ content?: string | PromptContentPart[];
96
+ }
97
+ export interface PromptContentPart {
98
+ type?: string;
99
+ text?: string;
100
+ }
101
+ /**
102
+ * Return value honoured by OpenClaw when a `before_prompt_build` handler wants
103
+ * to append extra text to the agent's system prompt.
104
+ */
105
+ export interface BeforePromptBuildResult {
106
+ appendSystemContext: string;
107
+ }
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ // Type definitions for the openclaw-knowledge plugin.
2
+ //
3
+ // Kept separate from the entry point so that tests and helper modules can
4
+ // import them without pulling in the full plugin registration code.
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,EAAE;AACF,0EAA0E;AAC1E,oEAAoE"}