@ontos-ai/knowhere-claw 0.1.0-beta.0 → 0.1.2

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/config.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { isRecord } from "./types.js";
2
+ import fs from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  //#region src/config.ts
4
5
  const DEFAULT_BASE_URL = "https://api.knowhereto.ai";
@@ -24,16 +25,6 @@ const knowherePluginConfigSchema = {
24
25
  ],
25
26
  default: "session"
26
27
  },
27
- autoGrounding: {
28
- type: "boolean",
29
- default: true
30
- },
31
- maxContextChars: {
32
- type: "integer",
33
- minimum: 500,
34
- maximum: 12e3,
35
- default: 4e3
36
- },
37
28
  pollIntervalMs: {
38
29
  type: "integer",
39
30
  minimum: 1e3,
@@ -63,8 +54,6 @@ const knowherePluginConfigSchema = {
63
54
  const KNOWHERE_PLUGIN_DEFAULTS = Object.freeze({
64
55
  baseUrl: DEFAULT_BASE_URL,
65
56
  scopeMode: "session",
66
- autoGrounding: true,
67
- maxContextChars: 4e3,
68
57
  pollIntervalMs: 1e4,
69
58
  pollTimeoutMs: 18e5,
70
59
  requestTimeoutMs: 6e4,
@@ -74,9 +63,6 @@ function readString(raw, key) {
74
63
  const value = raw[key];
75
64
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
76
65
  }
77
- function readBoolean(raw, key, fallback) {
78
- return typeof raw[key] === "boolean" ? raw[key] : fallback;
79
- }
80
66
  function readNumber(raw, key, fallback, options = {}) {
81
67
  const value = raw[key];
82
68
  const parsed = typeof value === "number" && Number.isFinite(value) ? value : fallback;
@@ -97,12 +83,6 @@ function resolveKnowhereConfig(api) {
97
83
  baseUrl: readString(raw, "baseUrl") || process.env.KNOWHERE_BASE_URL || "https://api.knowhereto.ai",
98
84
  storageDir: storageDirRaw ? api.resolvePath(storageDirRaw) : path.join(stateDir, "plugins", api.id),
99
85
  scopeMode: readScopeMode(raw),
100
- autoGrounding: readBoolean(raw, "autoGrounding", KNOWHERE_PLUGIN_DEFAULTS.autoGrounding),
101
- maxContextChars: readNumber(raw, "maxContextChars", KNOWHERE_PLUGIN_DEFAULTS.maxContextChars, {
102
- min: 500,
103
- max: 12e3,
104
- integer: true
105
- }),
106
86
  pollIntervalMs: readNumber(raw, "pollIntervalMs", KNOWHERE_PLUGIN_DEFAULTS.pollIntervalMs, {
107
87
  min: 1e3,
108
88
  max: 6e4,
@@ -125,8 +105,42 @@ function resolveKnowhereConfig(api) {
125
105
  })
126
106
  };
127
107
  }
108
+ const API_KEY_URL = "https://knowhereto.ai/api-keys";
109
+ const PURCHASE_CREDITS_URL = "https://knowhereto.ai/usage?buy=true";
110
+ const API_KEY_STATE_FILE = "api-key.json";
128
111
  function assertKnowhereApiKey(config) {
129
- if (!config.apiKey) throw new Error("Knowhere API key missing. Set plugins.entries.knowhere.config.apiKey or KNOWHERE_API_KEY.");
112
+ if (!config.apiKey) throw new Error([
113
+ "Knowhere API key is not configured.",
114
+ `Please create one at: ${API_KEY_URL}`,
115
+ "Then paste the key into this conversation so I can set it up with the knowhere_set_api_key tool."
116
+ ].join("\n"));
117
+ }
118
+ function isPaymentRequiredError(error) {
119
+ if (!(error instanceof Error)) return false;
120
+ if (!("code" in error)) return false;
121
+ if (typeof error.code !== "string") return false;
122
+ return error.code.includes("PAYMENT_REQUIRED");
123
+ }
124
+ function formatPaymentRequiredMessage() {
125
+ return ["Your Knowhere account has insufficient credits.", `Please purchase more at: ${PURCHASE_CREDITS_URL}`].join("\n");
126
+ }
127
+ function resolveApiKeyStatePath(storageDir) {
128
+ return path.join(storageDir, API_KEY_STATE_FILE);
129
+ }
130
+ async function readPersistedApiKey(storageDir) {
131
+ try {
132
+ const raw = await fs.readFile(resolveApiKeyStatePath(storageDir), "utf-8");
133
+ const parsed = JSON.parse(raw);
134
+ if (isRecord(parsed) && typeof parsed.apiKey === "string" && parsed.apiKey.trim()) return parsed.apiKey.trim();
135
+ return null;
136
+ } catch {
137
+ return null;
138
+ }
139
+ }
140
+ async function persistApiKey(storageDir, apiKey) {
141
+ await fs.mkdir(storageDir, { recursive: true });
142
+ const filePath = resolveApiKeyStatePath(storageDir);
143
+ await fs.writeFile(filePath, JSON.stringify({ apiKey: apiKey.trim() }), "utf-8");
130
144
  }
131
145
  //#endregion
132
- export { assertKnowhereApiKey, knowherePluginConfigSchema, resolveKnowhereConfig };
146
+ export { assertKnowhereApiKey, formatPaymentRequiredMessage, isPaymentRequiredError, knowherePluginConfigSchema, persistApiKey, readPersistedApiKey, resolveKnowhereConfig };
package/dist/index.js CHANGED
@@ -1,10 +1,9 @@
1
- import { knowherePluginConfigSchema, resolveKnowhereConfig } from "./config.js";
2
- import { registerKnowhereAutoGrounding } from "./hooks.js";
1
+ import { knowherePluginConfigSchema, readPersistedApiKey, resolveKnowhereConfig } from "./config.js";
3
2
  import { KnowhereStore } from "./store.js";
4
3
  import { createKnowhereToolFactory } from "./tools.js";
5
4
  //#region src/index.ts
6
5
  const plugin = {
7
- id: "knowhere",
6
+ id: "knowhere-claw",
8
7
  name: "Knowhere",
9
8
  description: "Knowhere document ingestion and direct stored-result access for OpenClaw.",
10
9
  configSchema: knowherePluginConfigSchema,
@@ -15,16 +14,18 @@ const plugin = {
15
14
  scopeMode: config.scopeMode,
16
15
  logger: api.logger
17
16
  });
18
- const autoGroundingController = config.autoGrounding ? registerKnowhereAutoGrounding({
19
- api,
20
- config,
21
- store
22
- }) : void 0;
17
+ if (!config.apiKey) readPersistedApiKey(config.storageDir).then((persistedKey) => {
18
+ if (persistedKey) {
19
+ config.apiKey = persistedKey;
20
+ api.logger.info("knowhere: loaded API key from persisted state");
21
+ }
22
+ }).catch((error) => {
23
+ api.logger.warn(`knowhere: failed to read persisted API key: ${error instanceof Error ? error.message : String(error)}`);
24
+ });
23
25
  api.registerTool(createKnowhereToolFactory({
24
26
  api,
25
27
  config,
26
- store,
27
- autoGroundingController
28
+ store
28
29
  }), { names: [
29
30
  "knowhere_ingest_document",
30
31
  "knowhere_list_jobs",
@@ -35,7 +36,8 @@ const plugin = {
35
36
  "knowhere_preview_document",
36
37
  "knowhere_list_documents",
37
38
  "knowhere_remove_document",
38
- "knowhere_clear_scope"
39
+ "knowhere_clear_scope",
40
+ "knowhere_set_api_key"
39
41
  ] });
40
42
  }
41
43
  };
package/dist/parser.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { isNodeError, isRecord } from "./types.js";
2
2
  import { strFromU8, unzipSync } from "./node_modules/.pnpm/fflate@0.8.2/node_modules/fflate/esm/browser.js";
3
- import path from "node:path";
4
3
  import fs from "node:fs/promises";
4
+ import path from "node:path";
5
5
  import { createHash } from "node:crypto";
6
6
  const CHUNKS_FILE_NAME = "chunks.json";
7
7
  const FULL_MARKDOWN_FILE_NAME = "full.md";
package/dist/session.js CHANGED
@@ -7,6 +7,7 @@ const RESERVED_AGENT_SESSION_ROOTS = new Set([
7
7
  "global"
8
8
  ]);
9
9
  const CONVERSATION_KIND_SEGMENTS = new Set([
10
+ "account",
10
11
  "channel",
11
12
  "chat",
12
13
  "conversation",
package/dist/store.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { KnowhereScope, PluginLogger, SaveStoredDocumentPayload, ScopeMode, StoredDocumentPayload, StoredDocumentRecord } from "./types";
1
+ import type { ChannelRouteRecord, KnowhereScope, PluginLogger, SaveStoredDocumentPayload, ScopeMode, StoredDocumentPayload, StoredDocumentRecord } from "./types";
2
2
  export declare class KnowhereStore {
3
3
  private readonly rootDir;
4
4
  private readonly scopeMode;
@@ -9,6 +9,8 @@ export declare class KnowhereStore {
9
9
  private readonly scopeKeyAliases;
10
10
  private readonly sessionScopeKeysBySessionId;
11
11
  private readonly sessionScopeKeysBySessionKey;
12
+ private readonly channelRoutes;
13
+ private routesLoaded;
12
14
  constructor(params: {
13
15
  rootDir: string;
14
16
  scopeMode: ScopeMode;
@@ -24,6 +26,17 @@ export declare class KnowhereStore {
24
26
  sessionId?: string;
25
27
  messageContext: unknown;
26
28
  }): string | undefined;
29
+ registerChannelRoute(params: {
30
+ channelId: string;
31
+ accountId?: string;
32
+ conversationId?: string;
33
+ }): Promise<void>;
34
+ resolveChannelRoute(params: {
35
+ channelId?: string;
36
+ conversationId?: string;
37
+ sessionKey?: string;
38
+ }): Promise<ChannelRouteRecord | undefined>;
39
+ private lookupChannelRoute;
27
40
  inheritScopeAlias(context: {
28
41
  childSessionKey?: string;
29
42
  childSessionId?: string;
@@ -37,6 +50,7 @@ export declare class KnowhereStore {
37
50
  }): KnowhereScope;
38
51
  listDocuments(scope: KnowhereScope): Promise<StoredDocumentRecord[]>;
39
52
  loadDocumentPayload(scope: KnowhereScope, docId: string): Promise<StoredDocumentPayload | null>;
53
+ getResultFileAbsolutePath(scope: KnowhereScope, docId: string, relativePath: string): string;
40
54
  readResultFile(scope: KnowhereScope, docId: string, relativePath: string): Promise<{
41
55
  document: StoredDocumentRecord;
42
56
  relativePath: string;
@@ -56,6 +70,9 @@ export declare class KnowhereStore {
56
70
  private deleteDocumentPayloadCache;
57
71
  private deleteScopeDocumentPayloadCaches;
58
72
  private loadOrBuildBrowseIndex;
73
+ private buildRouteKey;
74
+ private ensureRoutesLoaded;
75
+ private persistRoutes;
59
76
  private resolveCanonicalScopeKey;
60
77
  private resolveKnownScopeKey;
61
78
  private rebuildIndex;
package/dist/store.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { isNodeError } from "./types.js";
2
- import { hashString, normalizeWhitespace, sanitizeStringArray, slugify } from "./text.js";
3
- import { deriveMessageContextScopeKey } from "./session.js";
4
2
  import { buildStoredBrowseIndex, extractKnowhereResultArchive, isStoredBrowseIndex, readStoredKnowhereResultContent, readStoredKnowhereResultSummary, resolveResultEntryPath } from "./parser.js";
5
- import path from "node:path";
3
+ import { hashString, normalizeWhitespace, sanitizeStringArray, slugify } from "./text.js";
4
+ import { deriveMessageContextScopeKey, findConversationSegmentValue, parseConversationSessionKey } from "./session.js";
6
5
  import fs from "node:fs/promises";
6
+ import path from "node:path";
7
7
  import { randomUUID } from "node:crypto";
8
8
  //#region src/store.ts
9
9
  const INDEX_VERSION = 1;
@@ -11,6 +11,7 @@ const BROWSE_INDEX_FILE_NAME = "browse-index.json";
11
11
  const DOCUMENT_PAYLOAD_CACHE_LIMIT = 16;
12
12
  const METADATA_FILE_NAME = "metadata.json";
13
13
  const RESULT_DIRECTORY_NAME = "result";
14
+ const ROUTES_FILE_NAME = "routes.json";
14
15
  async function pathExists(targetPath) {
15
16
  try {
16
17
  await fs.access(targetPath);
@@ -146,6 +147,8 @@ var KnowhereStore = class {
146
147
  scopeKeyAliases;
147
148
  sessionScopeKeysBySessionId;
148
149
  sessionScopeKeysBySessionKey;
150
+ channelRoutes;
151
+ routesLoaded;
149
152
  constructor(params) {
150
153
  this.rootDir = params.rootDir;
151
154
  this.scopeMode = params.scopeMode;
@@ -156,6 +159,8 @@ var KnowhereStore = class {
156
159
  this.scopeKeyAliases = /* @__PURE__ */ new Map();
157
160
  this.sessionScopeKeysBySessionId = /* @__PURE__ */ new Map();
158
161
  this.sessionScopeKeysBySessionKey = /* @__PURE__ */ new Map();
162
+ this.channelRoutes = /* @__PURE__ */ new Map();
163
+ this.routesLoaded = false;
159
164
  }
160
165
  registerScopeAlias(context) {
161
166
  const canonicalScopeKey = this.resolveCanonicalScopeKey(context.scopeKey);
@@ -181,6 +186,68 @@ var KnowhereStore = class {
181
186
  sessionId
182
187
  });
183
188
  }
189
+ async registerChannelRoute(params) {
190
+ if (!params.accountId) {
191
+ this.logger.debug?.(`knowhere: registerChannelRoute skipped — no accountId channelId=${params.channelId} conversationId=${params.conversationId ?? "(none)"}`);
192
+ return;
193
+ }
194
+ const key = this.buildRouteKey(params.channelId, params.conversationId);
195
+ if (!key) return;
196
+ const record = {
197
+ channelId: params.channelId,
198
+ accountId: params.accountId,
199
+ conversationId: params.conversationId,
200
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
201
+ };
202
+ this.channelRoutes.set(key, record);
203
+ this.logger.info?.(`knowhere: registerChannelRoute key=${key} accountId=${params.accountId} (immediate)`);
204
+ await this.ensureRoutesLoaded();
205
+ this.channelRoutes.set(key, record);
206
+ this.logger.info?.(`knowhere: registerChannelRoute key=${key} accountId=${params.accountId} (persisted)`);
207
+ await this.persistRoutes();
208
+ }
209
+ async resolveChannelRoute(params) {
210
+ const immediate = this.lookupChannelRoute(params);
211
+ if (immediate) {
212
+ this.logger.debug?.(`knowhere: resolveChannelRoute hit (immediate) accountId=${immediate.accountId} channelId=${params.channelId ?? "(none)"} sessionKey=${params.sessionKey ?? "(none)"}`);
213
+ return immediate;
214
+ }
215
+ await this.ensureRoutesLoaded();
216
+ const resolved = this.lookupChannelRoute(params);
217
+ this.logger.debug?.(resolved ? `knowhere: resolveChannelRoute hit (disk) accountId=${resolved.accountId} channelId=${params.channelId ?? "(none)"} sessionKey=${params.sessionKey ?? "(none)"}` : `knowhere: resolveChannelRoute miss channelId=${params.channelId ?? "(none)"} sessionKey=${params.sessionKey ?? "(none)"} mapSize=${this.channelRoutes.size}`);
218
+ return resolved;
219
+ }
220
+ lookupChannelRoute(params) {
221
+ if (params.channelId) {
222
+ const directKey = this.buildRouteKey(params.channelId, params.conversationId);
223
+ if (directKey) {
224
+ const route = this.channelRoutes.get(directKey);
225
+ if (route) return route;
226
+ }
227
+ }
228
+ if (params.sessionKey) {
229
+ const parsed = parseConversationSessionKey(params.sessionKey);
230
+ if (parsed) {
231
+ const surface = parsed.surface;
232
+ const remainderAfterSurface = parsed.segments.slice(1).join(":");
233
+ if (surface && remainderAfterSurface) {
234
+ const fullKey = this.buildRouteKey(surface, remainderAfterSurface);
235
+ if (fullKey) {
236
+ const route = this.channelRoutes.get(fullKey);
237
+ if (route) return route;
238
+ }
239
+ }
240
+ const conversation = findConversationSegmentValue(parsed, "channel", "direct", "group", "chat", "room", "conversation", "user", "member");
241
+ if (surface && conversation) {
242
+ const derivedKey = this.buildRouteKey(surface, conversation);
243
+ if (derivedKey) {
244
+ const route = this.channelRoutes.get(derivedKey);
245
+ if (route) return route;
246
+ }
247
+ }
248
+ }
249
+ }
250
+ }
184
251
  inheritScopeAlias(context) {
185
252
  const requesterScopeKey = this.resolveKnownScopeKey({
186
253
  sessionId: context.requesterSessionId,
@@ -247,6 +314,9 @@ var KnowhereStore = class {
247
314
  return payload;
248
315
  });
249
316
  }
317
+ getResultFileAbsolutePath(scope, docId, relativePath) {
318
+ return resolveResultEntryPath(buildStoredDocumentPaths(scope, docId).resultDir, relativePath);
319
+ }
250
320
  async readResultFile(scope, docId, relativePath) {
251
321
  return this.runWithScopeAccessLock(scope, async () => {
252
322
  const paths = buildStoredDocumentPaths(scope, docId);
@@ -437,6 +507,24 @@ var KnowhereStore = class {
437
507
  await writeJsonAtomic(paths.browseIndexPath, browseIndex);
438
508
  return browseIndex;
439
509
  }
510
+ buildRouteKey(channelId, conversationId) {
511
+ const normalizedChannel = normalizeWhitespace(channelId)?.toLowerCase();
512
+ if (!normalizedChannel) return;
513
+ const normalizedConversation = normalizeWhitespace(conversationId);
514
+ return normalizedConversation ? `${normalizedChannel}:${normalizedConversation}` : normalizedChannel;
515
+ }
516
+ async ensureRoutesLoaded() {
517
+ if (this.routesLoaded) return;
518
+ const stored = await readJson(path.join(this.rootDir, ROUTES_FILE_NAME), {});
519
+ for (const [key, record] of Object.entries(stored)) this.channelRoutes.set(key, record);
520
+ this.routesLoaded = true;
521
+ }
522
+ async persistRoutes() {
523
+ const routesPath = path.join(this.rootDir, ROUTES_FILE_NAME);
524
+ const data = {};
525
+ for (const [key, record] of this.channelRoutes) data[key] = record;
526
+ await writeJsonAtomic(routesPath, data);
527
+ }
440
528
  resolveCanonicalScopeKey(scopeKey) {
441
529
  let currentKey = normalizeWhitespace(scopeKey) || scopeKey;
442
530
  const seen = /* @__PURE__ */ new Set();
package/dist/tools.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk/core";
1
+ import { type AnyAgentTool, type OpenClawPluginApi } from "openclaw/plugin-sdk/core";
2
2
  import { KnowhereStore } from "./store";
3
- import type { KnowhereAutoGroundingController, ResolvedKnowhereConfig, ToolRuntimeContext } from "./types";
3
+ import type { ResolvedKnowhereConfig, ToolRuntimeContext } from "./types";
4
4
  export declare function createKnowhereToolFactory(params: {
5
5
  api: OpenClawPluginApi;
6
6
  config: ResolvedKnowhereConfig;
7
7
  store: KnowhereStore;
8
- autoGroundingController?: KnowhereAutoGroundingController;
9
8
  }): (ctx: ToolRuntimeContext) => AnyAgentTool[];