@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.
@@ -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
@@ -50,7 +50,8 @@ const plugin = {
50
50
  "knowhere_get_map",
51
51
  "knowhere_get_structure",
52
52
  "knowhere_read_chunks",
53
- "knowhere_discover_files"
53
+ "knowhere_discover_files",
54
+ "knowhere_delete_document"
54
55
  ] });
55
56
  }
56
57
  };
@@ -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;
@@ -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 docDir = path.join(kbPath, params.docId);
110
- await fs.ensureDir(docDir);
117
+ const linkPath = path.join(kbPath, params.docId);
111
118
  const sourceResultRoot = await resolveStoredKnowhereResultRoot(params.sourcePath);
112
- await fs.copy(sourceResultRoot, docDir, { overwrite: true });
113
- const metadataPath = path.join(docDir, "metadata.json");
114
- await fs.writeJSON(metadataPath, params.metadata, { spaces: 2 });
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)).isDirectory() && doc !== "knowledge_graph.json" && doc !== "chunk_stats.json" && doc !== "kb_metadata.json") docDirs.push(doc);
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 chunks;
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
- chunks = await t2EnrichChunks(chunks, docDir);
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
@@ -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.7",
6
+ "version": "0.2.8",
7
7
  "uiHints": {
8
8
  "apiKey": {
9
9
  "label": "Knowhere API Key",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ontos-ai/knowhere-claw",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "OpenClaw plugin for Knowhere-powered document ingestion and automatic grounding.",
5
5
  "files": [
6
6
  "dist/",
@@ -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
- - Copies parsed data to `~/.knowhere/{kbId}/`
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
- └── {kb_id}/ # e.g. "telegram"
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
- └── {document_name}/ # One subdir per parsed document
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`.