@ontos-ai/knowhere-claw 0.2.7 → 0.2.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/dist/agent-hooks.js +21 -1
- package/dist/index.js +2 -1
- package/dist/kg-service.d.ts +1 -0
- package/dist/kg-service.js +56 -23
- package/dist/tools.js +74 -4
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/knowhere_memory/SKILL.md +32 -7
package/dist/agent-hooks.js
CHANGED
|
@@ -10,10 +10,30 @@ const KNOWHERE_PROMPT_CONTEXT = [
|
|
|
10
10
|
"- If the file is in the cloud (e.g. Feishu Drive), first obtain the download URL via the appropriate channel tool, then use the `url` parameter.",
|
|
11
11
|
"- Refer to your **knowhere_memory** skill for the complete step-by-step workflow.",
|
|
12
12
|
"",
|
|
13
|
+
"### ⚠️ Feishu / Lark Cloud Files",
|
|
14
|
+
"**Never** pass a raw `open.feishu.cn` or `feishu.cn/drive/file/...` URL directly to `knowhere_ingest_document`.",
|
|
15
|
+
"These URLs require authentication and will redirect to a login page, causing Knowhere to parse HTML instead of the actual document.",
|
|
16
|
+
"Instead:",
|
|
17
|
+
"1. Use `feishu_drive` (action: `download`) or equivalents to obtain an **authenticated temporary download URL**.",
|
|
18
|
+
"2. Then pass that authenticated URL to `knowhere_ingest_document(url: ...)`.",
|
|
19
|
+
"",
|
|
20
|
+
"### Empty File Rejection",
|
|
21
|
+
"If a parsed result contains 0 usable chunks, it will be **automatically rejected** and not stored.",
|
|
22
|
+
"This typically means the source file was corrupt, empty, or required authentication that was not provided.",
|
|
23
|
+
"",
|
|
13
24
|
"### Knowledge Retrieval",
|
|
14
25
|
"When answering questions about documents or the knowledge base:",
|
|
15
26
|
"- ✅ Use `knowhere_get_map`, `knowhere_get_structure`, `knowhere_read_chunks`, `knowhere_kg_query`",
|
|
16
|
-
"- ❌ Do NOT use `exec` or shell commands to read files inside `~/.knowhere/`"
|
|
27
|
+
"- ❌ Do NOT use `exec` or shell commands to read files inside `~/.knowhere/`",
|
|
28
|
+
"",
|
|
29
|
+
"### 📷 Image Delivery",
|
|
30
|
+
"**`knowhere_read_chunks` has built-in automatic image delivery.** When it returns chunks containing images,",
|
|
31
|
+
"those images are automatically sent to the user's channel (Telegram/Feishu/etc). You do NOT need to send them again.",
|
|
32
|
+
"- The tool result will contain `resolved_assets` with `mode: 'image_sent'` for successfully delivered images.",
|
|
33
|
+
"- If the user asks to **see** or **view** an image from the knowledge base, call `knowhere_read_chunks` with the relevant section — images will be auto-delivered.",
|
|
34
|
+
"- `knowhere_view_image` is for **AI visual analysis only** (it loads image pixels into your context for you to describe/analyze). It does NOT send the image to the user.",
|
|
35
|
+
"- If the user asks you to re-send a specific image, use the `message` tool with the staged file path from `~/.openclaw/knowhere-assets/`.",
|
|
36
|
+
"- **Never tell the user you cannot send images.** You CAN — via `knowhere_read_chunks` auto-delivery or `message` tool."
|
|
17
37
|
].join("\n");
|
|
18
38
|
const KNOWHERE_DIR_PATTERN = ".knowhere";
|
|
19
39
|
const BLOCK_REASON = "Do not use exec to read .knowhere/ directly. Use knowhere retrieval tools instead: knowhere_get_map, knowhere_get_structure, knowhere_read_chunks, knowhere_kg_query.";
|
package/dist/index.js
CHANGED
package/dist/kg-service.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export declare class KnowledgeGraphService {
|
|
|
23
23
|
keywords: string[];
|
|
24
24
|
metadata: Record<string, unknown>;
|
|
25
25
|
}): Promise<void>;
|
|
26
|
+
removeDocumentFromKb(kbId: string, docId: string): Promise<void>;
|
|
26
27
|
scheduleBuild(kbId: string, task: () => Promise<void>): Promise<void>;
|
|
27
28
|
buildKnowledgeGraph(kbId: string): Promise<void>;
|
|
28
29
|
private updateKbMetadata;
|
package/dist/kg-service.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { resolveStoredKnowhereResultRoot } from "./parser.js";
|
|
2
2
|
import { buildConnections, init_connect_builder } from "./connect-builder.js";
|
|
3
3
|
import { buildKnowledgeGraph } from "./graph-builder.js";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import os from "node:os";
|
|
6
7
|
import { spawn } from "node:child_process";
|
|
7
|
-
import fs from "fs-extra";
|
|
8
|
+
import fs$1 from "fs-extra";
|
|
8
9
|
//#region src/kg-service.ts
|
|
9
10
|
init_connect_builder();
|
|
11
|
+
/**
|
|
12
|
+
* Directories that belong to the Store layer and must be excluded when the KG
|
|
13
|
+
* scans a kb directory for document entries. This matters when the kb
|
|
14
|
+
* directory coincides with the Store root (e.g. both resolve to
|
|
15
|
+
* `~/.knowhere/global/`).
|
|
16
|
+
*/
|
|
17
|
+
const STORE_INFRA_DIRS = new Set(["documents", "metadata"]);
|
|
10
18
|
const DEFAULT_CONNECT_CONFIG = {
|
|
11
19
|
minKeywordOverlap: 3,
|
|
12
20
|
keywordScoreWeight: 1,
|
|
@@ -101,19 +109,43 @@ var KnowledgeGraphService = class {
|
|
|
101
109
|
}
|
|
102
110
|
async ensureKbDirectory(kbId) {
|
|
103
111
|
const kbPath = this.getKbPath(kbId);
|
|
104
|
-
await fs.ensureDir(kbPath);
|
|
112
|
+
await fs$1.ensureDir(kbPath);
|
|
105
113
|
return kbPath;
|
|
106
114
|
}
|
|
107
115
|
async saveDocumentToKb(params) {
|
|
108
116
|
const kbPath = await this.ensureKbDirectory(params.kbId);
|
|
109
|
-
const
|
|
110
|
-
await fs.ensureDir(docDir);
|
|
117
|
+
const linkPath = path.join(kbPath, params.docId);
|
|
111
118
|
const sourceResultRoot = await resolveStoredKnowhereResultRoot(params.sourcePath);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
try {
|
|
120
|
+
const existingStat = await fs.lstat(linkPath).catch(() => null);
|
|
121
|
+
if (existingStat) {
|
|
122
|
+
if (existingStat.isSymbolicLink()) if (await fs.readlink(linkPath) === sourceResultRoot) this.logger.debug?.(`knowhere: saveDocumentToKb symlink already correct doc=${params.docId}`);
|
|
123
|
+
else {
|
|
124
|
+
await fs.unlink(linkPath);
|
|
125
|
+
await fs.symlink(sourceResultRoot, linkPath);
|
|
126
|
+
}
|
|
127
|
+
else if (existingStat.isDirectory()) {
|
|
128
|
+
await fs$1.remove(linkPath);
|
|
129
|
+
await fs.symlink(sourceResultRoot, linkPath);
|
|
130
|
+
this.logger.info(`knowhere: saveDocumentToKb replaced legacy copy with symlink doc=${params.docId}`);
|
|
131
|
+
}
|
|
132
|
+
} else await fs.symlink(sourceResultRoot, linkPath);
|
|
133
|
+
} catch (symlinkError) {
|
|
134
|
+
this.logger.warn(`knowhere: symlink failed for doc=${params.docId}, falling back to copy: ${formatUnknownError(symlinkError)}`);
|
|
135
|
+
await fs$1.ensureDir(linkPath);
|
|
136
|
+
await fs$1.copy(sourceResultRoot, linkPath, { overwrite: true });
|
|
137
|
+
}
|
|
115
138
|
this.logger.info(`Document saved to knowledge base: kb=${params.kbId} doc=${params.docId}`);
|
|
116
139
|
}
|
|
140
|
+
async removeDocumentFromKb(kbId, docId) {
|
|
141
|
+
const kbPath = this.getKbPath(kbId);
|
|
142
|
+
const docPath = path.join(kbPath, docId);
|
|
143
|
+
const stat = await fs.lstat(docPath).catch(() => null);
|
|
144
|
+
if (!stat) return;
|
|
145
|
+
if (stat.isSymbolicLink()) await fs.unlink(docPath);
|
|
146
|
+
else if (stat.isDirectory()) await fs$1.remove(docPath);
|
|
147
|
+
this.logger.info(`Document removed from knowledge base: kb=${kbId} doc=${docId}`);
|
|
148
|
+
}
|
|
117
149
|
async scheduleBuild(kbId, task) {
|
|
118
150
|
if ((this.config.concurrentBuildStrategy || "queue") === "skip") {
|
|
119
151
|
if (this.buildQueues.has(kbId)) {
|
|
@@ -132,11 +164,12 @@ var KnowledgeGraphService = class {
|
|
|
132
164
|
}
|
|
133
165
|
async buildKnowledgeGraph(kbId) {
|
|
134
166
|
const kbPath = this.getKbPath(kbId);
|
|
135
|
-
const docs = await fs.readdir(kbPath);
|
|
167
|
+
const docs = await fs$1.readdir(kbPath);
|
|
136
168
|
const docDirs = [];
|
|
137
169
|
for (const doc of docs) {
|
|
170
|
+
if (doc.startsWith(".") || STORE_INFRA_DIRS.has(doc)) continue;
|
|
138
171
|
const docPath = path.join(kbPath, doc);
|
|
139
|
-
if ((await fs.stat(docPath))
|
|
172
|
+
if ((await fs$1.stat(docPath).catch(() => null))?.isDirectory() && doc !== "knowledge_graph.json" && doc !== "chunk_stats.json" && doc !== "kb_metadata.json") docDirs.push(doc);
|
|
140
173
|
}
|
|
141
174
|
if (docDirs.length < 1) {
|
|
142
175
|
this.logger.info(`Not enough documents for graph building (need >=2, have ${docDirs.length}), skipping`);
|
|
@@ -147,8 +180,8 @@ var KnowledgeGraphService = class {
|
|
|
147
180
|
const allChunks = [];
|
|
148
181
|
for (const docDir of docDirs) {
|
|
149
182
|
const chunksPath = path.join(kbPath, docDir, "chunks.json");
|
|
150
|
-
if (await fs.pathExists(chunksPath)) {
|
|
151
|
-
const chunksData = await fs.readJSON(chunksPath);
|
|
183
|
+
if (await fs$1.pathExists(chunksPath)) {
|
|
184
|
+
const chunksData = await fs$1.readJSON(chunksPath);
|
|
152
185
|
if (chunksData.chunks && Array.isArray(chunksData.chunks)) allChunks.push(...chunksData.chunks.map((c) => ({
|
|
153
186
|
...c,
|
|
154
187
|
fileKey: docDir
|
|
@@ -164,10 +197,10 @@ var KnowledgeGraphService = class {
|
|
|
164
197
|
this.logger.info(`Built ${connections.length} connections`);
|
|
165
198
|
const chunkStatsPath = path.join(kbPath, "chunk_stats.json");
|
|
166
199
|
let chunkStats = {};
|
|
167
|
-
if (await fs.pathExists(chunkStatsPath)) chunkStats = await fs.readJSON(chunkStatsPath);
|
|
200
|
+
if (await fs$1.pathExists(chunkStatsPath)) chunkStats = await fs$1.readJSON(chunkStatsPath);
|
|
168
201
|
const knowledgeGraph = buildKnowledgeGraph(allChunks, connections, chunkStats, false, this.logger, kbId);
|
|
169
202
|
const graphFile = path.join(kbPath, "knowledge_graph.json");
|
|
170
|
-
await fs.writeJSON(graphFile, knowledgeGraph, { spaces: 2 });
|
|
203
|
+
await fs$1.writeJSON(graphFile, knowledgeGraph, { spaces: 2 });
|
|
171
204
|
this.logger.info(`Knowledge graph saved to ${graphFile}`);
|
|
172
205
|
await this.updateKbMetadata(kbPath, {
|
|
173
206
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -182,34 +215,34 @@ var KnowledgeGraphService = class {
|
|
|
182
215
|
async updateKbMetadata(kbPath, updates) {
|
|
183
216
|
const metadataPath = path.join(kbPath, "kb_metadata.json");
|
|
184
217
|
let metadata = {};
|
|
185
|
-
if (await fs.pathExists(metadataPath)) metadata = await fs.readJSON(metadataPath);
|
|
218
|
+
if (await fs$1.pathExists(metadataPath)) metadata = await fs$1.readJSON(metadataPath);
|
|
186
219
|
const updated = {
|
|
187
220
|
...metadata,
|
|
188
221
|
...updates
|
|
189
222
|
};
|
|
190
|
-
await fs.writeJSON(metadataPath, updated, { spaces: 2 });
|
|
223
|
+
await fs$1.writeJSON(metadataPath, updated, { spaces: 2 });
|
|
191
224
|
}
|
|
192
225
|
async queryGraph(kbId, fileKey) {
|
|
193
226
|
const graphPath = path.join(this.getKbPath(kbId), "knowledge_graph.json");
|
|
194
|
-
if (!await fs.pathExists(graphPath)) return [];
|
|
195
|
-
const graph = await fs.readJSON(graphPath);
|
|
227
|
+
if (!await fs$1.pathExists(graphPath)) return [];
|
|
228
|
+
const graph = await fs$1.readJSON(graphPath);
|
|
196
229
|
if (!fileKey) return graph.edges;
|
|
197
230
|
return graph.edges.filter((edge) => edge.source === fileKey || edge.target === fileKey);
|
|
198
231
|
}
|
|
199
232
|
async getKnowledgeGraph(kbId) {
|
|
200
233
|
const graphPath = path.join(this.getKbPath(kbId), "knowledge_graph.json");
|
|
201
|
-
if (!await fs.pathExists(graphPath)) return null;
|
|
202
|
-
return await fs.readJSON(graphPath);
|
|
234
|
+
if (!await fs$1.pathExists(graphPath)) return null;
|
|
235
|
+
return await fs$1.readJSON(graphPath);
|
|
203
236
|
}
|
|
204
237
|
async listKnowledgeBases() {
|
|
205
238
|
const knowhereRoot = path.join(os.homedir(), ".knowhere");
|
|
206
|
-
if (!await fs.pathExists(knowhereRoot)) return [];
|
|
207
|
-
return (await fs.readdir(knowhereRoot, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
239
|
+
if (!await fs$1.pathExists(knowhereRoot)) return [];
|
|
240
|
+
return (await fs$1.readdir(knowhereRoot, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
208
241
|
}
|
|
209
242
|
async getKbMetadata(kbId) {
|
|
210
243
|
const metadataPath = path.join(this.getKbPath(kbId), "kb_metadata.json");
|
|
211
|
-
if (!await fs.pathExists(metadataPath)) return null;
|
|
212
|
-
return await fs.readJSON(metadataPath);
|
|
244
|
+
if (!await fs$1.pathExists(metadataPath)) return null;
|
|
245
|
+
return await fs$1.readJSON(metadataPath);
|
|
213
246
|
}
|
|
214
247
|
isEnabled() {
|
|
215
248
|
return this.degradationMode !== "disabled";
|
package/dist/tools.js
CHANGED
|
@@ -125,6 +125,11 @@ async function persistIngestedDocument(params) {
|
|
|
125
125
|
jobResult: params.ingestResult.jobResult,
|
|
126
126
|
downloadedResult: params.ingestResult.downloadedResult
|
|
127
127
|
}, { overwrite: params.overwrite });
|
|
128
|
+
if (storedDocument.chunkCount === 0) {
|
|
129
|
+
params.api.logger.warn(`knowhere: rejecting empty document scope=${params.scope.label} docId=${storedDocument.id} title=${JSON.stringify(storedDocument.title)} — chunkCount is 0; removing from store`);
|
|
130
|
+
await params.store.removeDocument(params.scope, storedDocument.id);
|
|
131
|
+
throw new Error(`Parsed result for "${storedDocument.title || storedDocument.id}" contains no usable content (0 chunks). The file may be corrupt, empty, or require authentication to download. For cloud files (e.g. Feishu Drive), make sure to obtain an authenticated download URL first.`);
|
|
132
|
+
}
|
|
128
133
|
params.api.logger.info(`knowhere: knowhere_ingest_document stored document scope=${params.scope.label} jobId=${params.ingestResult.job.job_id} docId=${storedDocument.id}`);
|
|
129
134
|
startKnowledgeGraphBuild({
|
|
130
135
|
api: params.api,
|
|
@@ -1046,7 +1051,10 @@ async function t2EnrichChunks(chunks, docDir) {
|
|
|
1046
1051
|
if (c.type === "image" && inlinedImagePaths.has(c.path)) return false;
|
|
1047
1052
|
return true;
|
|
1048
1053
|
});
|
|
1049
|
-
return
|
|
1054
|
+
return {
|
|
1055
|
+
chunks,
|
|
1056
|
+
inlinedImagePaths
|
|
1057
|
+
};
|
|
1050
1058
|
}
|
|
1051
1059
|
async function replacePlaceholders(text, idToRaw, docDir, inlinedTablePaths, inlinedImagePaths, manifestPaths) {
|
|
1052
1060
|
const matches = [];
|
|
@@ -1222,6 +1230,15 @@ async function t2ResolveAssets(params) {
|
|
|
1222
1230
|
summary: chunk.summary || chunk.content?.slice(0, 200) || ""
|
|
1223
1231
|
});
|
|
1224
1232
|
}
|
|
1233
|
+
if (params.enrichedImagePaths && params.enrichedImagePaths.size > 0) for (const relativePath of params.enrichedImagePaths) {
|
|
1234
|
+
if (processedPaths.has(path.join(params.docDir, relativePath))) continue;
|
|
1235
|
+
await resolveOne({
|
|
1236
|
+
chunkId: relativePath,
|
|
1237
|
+
type: "image",
|
|
1238
|
+
relativePath,
|
|
1239
|
+
summary: path.basename(relativePath)
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1225
1242
|
return assets;
|
|
1226
1243
|
}
|
|
1227
1244
|
function createGetMapTool(_params) {
|
|
@@ -1436,7 +1453,8 @@ function createReadChunksTool(_params) {
|
|
|
1436
1453
|
await fs.writeFile(kgPath, JSON.stringify(g, null, 2), "utf-8");
|
|
1437
1454
|
}
|
|
1438
1455
|
} catch {}
|
|
1439
|
-
|
|
1456
|
+
const enrichResult = await t2EnrichChunks(chunks, docDir);
|
|
1457
|
+
chunks = enrichResult.chunks;
|
|
1440
1458
|
let resolvedAssets = [];
|
|
1441
1459
|
try {
|
|
1442
1460
|
resolvedAssets = await t2ResolveAssets({
|
|
@@ -1444,7 +1462,8 @@ function createReadChunksTool(_params) {
|
|
|
1444
1462
|
store: _params.store,
|
|
1445
1463
|
ctx: _params.ctx,
|
|
1446
1464
|
docDir,
|
|
1447
|
-
returnedChunks: chunks
|
|
1465
|
+
returnedChunks: chunks,
|
|
1466
|
+
enrichedImagePaths: enrichResult.inlinedImagePaths
|
|
1448
1467
|
});
|
|
1449
1468
|
} catch (err) {
|
|
1450
1469
|
_params.api.logger.debug?.(`knowhere: read_chunks asset resolution failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1602,6 +1621,51 @@ function createDiscoverFilesTool(_params) {
|
|
|
1602
1621
|
}
|
|
1603
1622
|
};
|
|
1604
1623
|
}
|
|
1624
|
+
function createDeleteDocumentTool(params) {
|
|
1625
|
+
return {
|
|
1626
|
+
name: "knowhere_delete_document",
|
|
1627
|
+
label: "Knowhere Delete Document",
|
|
1628
|
+
description: "Delete a parsed document from the underlying storage and remove it from the Knowledge Graph mapping. Use this to completely remove a document or file from the user's knowledge base. You must provide the exact docId obtained from knowhere_kg_query or the ingest result.",
|
|
1629
|
+
parameters: {
|
|
1630
|
+
type: "object",
|
|
1631
|
+
additionalProperties: false,
|
|
1632
|
+
properties: { docId: {
|
|
1633
|
+
type: "string",
|
|
1634
|
+
description: "The targeted document ID to delete."
|
|
1635
|
+
} },
|
|
1636
|
+
required: ["docId"]
|
|
1637
|
+
},
|
|
1638
|
+
execute: async (_toolCallId, rawParams) => {
|
|
1639
|
+
const docId = readString((isRecord(rawParams) ? rawParams : {}).docId);
|
|
1640
|
+
if (!docId) throw new Error("docId is required.");
|
|
1641
|
+
const scope = params.store.resolveScope(params.ctx);
|
|
1642
|
+
const kbId = params.kgService.resolveKbId(params.ctx);
|
|
1643
|
+
let wasRemovedFromStore = false;
|
|
1644
|
+
try {
|
|
1645
|
+
if (await params.store.removeDocument(scope, docId)) {
|
|
1646
|
+
wasRemovedFromStore = true;
|
|
1647
|
+
params.api.logger.info(`knowhere: document ${docId} removed from store`);
|
|
1648
|
+
}
|
|
1649
|
+
} catch (error) {
|
|
1650
|
+
params.api.logger.warn(`knowhere: store.removeDocument failed for ${docId}: ${formatErrorMessage(error)}`);
|
|
1651
|
+
}
|
|
1652
|
+
let wasRemovedFromKg = false;
|
|
1653
|
+
if (kbId) try {
|
|
1654
|
+
await params.kgService.removeDocumentFromKb(kbId, docId);
|
|
1655
|
+
wasRemovedFromKg = true;
|
|
1656
|
+
params.kgService.scheduleBuild(kbId, async () => {
|
|
1657
|
+
await params.kgService.buildKnowledgeGraph(kbId);
|
|
1658
|
+
}).catch((e) => {
|
|
1659
|
+
params.api.logger.warn(`knowhere: rebuild failed after doc removal: ${formatErrorMessage(e)}`);
|
|
1660
|
+
});
|
|
1661
|
+
} catch (error) {
|
|
1662
|
+
params.api.logger.warn(`knowhere: kgService.removeDocumentFromKb failed for ${docId}: ${formatErrorMessage(error)}`);
|
|
1663
|
+
}
|
|
1664
|
+
if (wasRemovedFromStore || wasRemovedFromKg) return textResult(`Success: The document "${docId}" has been deleted from the knowledge base.\nThe Knowledge Graph is being rebuilt in the background.`);
|
|
1665
|
+
else return textResult(`Failed: The document "${docId}" could not be found or removed.`);
|
|
1666
|
+
}
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1605
1669
|
function createKnowhereToolFactory(params) {
|
|
1606
1670
|
return (ctx) => [
|
|
1607
1671
|
createIngestTool({
|
|
@@ -1652,7 +1716,13 @@ function createKnowhereToolFactory(params) {
|
|
|
1652
1716
|
ctx
|
|
1653
1717
|
}),
|
|
1654
1718
|
createViewImageTool({ api: params.api }),
|
|
1655
|
-
createDiscoverFilesTool({ api: params.api })
|
|
1719
|
+
createDiscoverFilesTool({ api: params.api }),
|
|
1720
|
+
createDeleteDocumentTool({
|
|
1721
|
+
api: params.api,
|
|
1722
|
+
store: params.store,
|
|
1723
|
+
kgService: params.kgService,
|
|
1724
|
+
ctx
|
|
1725
|
+
})
|
|
1656
1726
|
];
|
|
1657
1727
|
}
|
|
1658
1728
|
//#endregion
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Knowhere",
|
|
4
4
|
"description": "Parse documents with Knowhere and expose the stored result as tool-queryable document state for OpenClaw agents.",
|
|
5
5
|
"skills": ["./skills"],
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.8",
|
|
7
7
|
"uiHints": {
|
|
8
8
|
"apiKey": {
|
|
9
9
|
"label": "Knowhere API Key",
|
package/package.json
CHANGED
|
@@ -55,8 +55,10 @@ The plugin handles everything automatically:
|
|
|
55
55
|
- Uploads/fetches the file for parsing
|
|
56
56
|
- Polls until parsing completes
|
|
57
57
|
- Downloads and extracts the result package
|
|
58
|
-
-
|
|
58
|
+
- Stores parsed data under `~/.knowhere/global/documents/{docId}/`
|
|
59
|
+
- Creates a symlink in `~/.knowhere/{kbId}/{docId}` → the stored document
|
|
59
60
|
- Builds/updates `knowledge_graph.json`
|
|
61
|
+
- **Rejects** files that parse to 0 chunks (empty, corrupt, or auth-gated)
|
|
60
62
|
|
|
61
63
|
After ingest completes, the new document is immediately searchable via the retrieval workflow below.
|
|
62
64
|
|
|
@@ -70,14 +72,21 @@ All knowledge data lives under `~/.knowhere/{kb_id}/`:
|
|
|
70
72
|
|
|
71
73
|
```text
|
|
72
74
|
~/.knowhere/
|
|
73
|
-
|
|
75
|
+
├── global/ # Store: document storage (scopeMode=global)
|
|
76
|
+
│ ├── index.json # Store document index
|
|
77
|
+
│ ├── documents/
|
|
78
|
+
│ │ └── {docId}/ # One subdir per parsed document
|
|
79
|
+
│ │ ├── chunks.json # All chunks (the actual content)
|
|
80
|
+
│ │ ├── hierarchy.json # Document structure tree
|
|
81
|
+
│ │ ├── images/ # Extracted images
|
|
82
|
+
│ │ └── tables/ # Extracted tables (HTML)
|
|
83
|
+
│ └── metadata/
|
|
84
|
+
│ └── {docId}.json # Document metadata
|
|
85
|
+
└── {kb_id}/ # KG: knowledge graph layer
|
|
74
86
|
├── knowledge_graph.json # File-level overview + cross-file edges
|
|
87
|
+
├── kb_metadata.json # KG metadata
|
|
75
88
|
├── chunk_stats.json # Usage stats per chunk
|
|
76
|
-
└── {
|
|
77
|
-
├── chunks.json # All chunks (the actual content)
|
|
78
|
-
├── hierarchy.json # Document structure tree
|
|
79
|
-
├── images/ # Extracted images
|
|
80
|
-
└── tables/ # Extracted tables (HTML)
|
|
89
|
+
└── {docId} → ../global/documents/{docId} # Symlink to Store
|
|
81
90
|
```
|
|
82
91
|
|
|
83
92
|
### Strategy: Prefer tools, fall back to files
|
|
@@ -165,3 +174,19 @@ Check `edges` from Step 1 for cross-document connections. If related files weren
|
|
|
165
174
|
- **Show connections**: mention cross-file relationships from edges
|
|
166
175
|
- **No internal IDs**: never expose `chunk_id` or UUID paths to the user
|
|
167
176
|
- **User's language**: reply in the same language the user is using
|
|
177
|
+
|
|
178
|
+
## Part 3: Deleting Knowledge
|
|
179
|
+
|
|
180
|
+
When the user asks to "delete", "remove", or "forget" a specific document:
|
|
181
|
+
|
|
182
|
+
1. Use `knowhere_kg_query` to search the Knowledge Graph to find the correct `docId` that uniquely identifies the document.
|
|
183
|
+
2. If the user provided a filename, use it to disambiguate and cross-check multiple hits.
|
|
184
|
+
3. Call `knowhere_delete_document` with the discovered `docId`.
|
|
185
|
+
|
|
186
|
+
The `knowhere_delete_document` tool natively handles all internal consistency logic:
|
|
187
|
+
|
|
188
|
+
- Deeply cleaning up the `chunks.json`, `images/`, and `tables/` locally.
|
|
189
|
+
- Removing the symlink mapping from the knowledge base profile.
|
|
190
|
+
- Dispatching a background rebuild for `knowledge_graph.json` so that the reference disappears from future queries.
|
|
191
|
+
|
|
192
|
+
**Rule:** DO NOT try to execute Unix file deletion (`rm`) commands on `~/.knowhere/` directly. Always use `knowhere_delete_document`.
|