@hasna/knowledge 0.2.22 → 0.2.24

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.
@@ -120,7 +120,7 @@ var MCP_HTTP_SERVICE_NAME = "knowledge", DEFAULT_MCP_HTTP_PORT = 8819;
120
120
  var init_mcp_http = () => {};
121
121
 
122
122
  // src/mcp.js
123
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
123
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
124
124
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
125
125
 
126
126
  // node_modules/zod/v4/classic/external.js
@@ -13660,7 +13660,7 @@ import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync
13660
13660
  // package.json
13661
13661
  var package_default = {
13662
13662
  name: "@hasna/knowledge",
13663
- version: "0.2.22",
13663
+ version: "0.2.24",
13664
13664
  description: "Agent-friendly local knowledge CLI with JSON output, pagination, and safe destructive actions",
13665
13665
  type: "module",
13666
13666
  bin: {
@@ -13723,9 +13723,8 @@ var package_default = {
13723
13723
  }
13724
13724
  };
13725
13725
 
13726
- // src/store.ts
13727
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, renameSync, unlinkSync } from "fs";
13728
- import { randomUUID } from "crypto";
13726
+ // src/knowledge-db.ts
13727
+ import { Database } from "bun:sqlite";
13729
13728
 
13730
13729
  // src/workspace.ts
13731
13730
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -13855,414 +13854,37 @@ function writeKnowledgeConfig(path, config2) {
13855
13854
  `);
13856
13855
  }
13857
13856
 
13858
- // src/store.ts
13859
- function defaultStorePath() {
13860
- return workspaceForHome(globalKnowledgeHome()).jsonStorePath;
13861
- }
13862
- function ensureStore(path) {
13863
- if (!existsSync2(path)) {
13864
- ensureParentDir(path);
13865
- if (path === defaultStorePath() && existsSync2(legacyGlobalStorePath())) {
13866
- writeFileSync2(path, readFileSync2(legacyGlobalStorePath(), "utf8"));
13867
- } else {
13868
- writeFileSync2(path, JSON.stringify({ items: [] }, null, 2));
13869
- }
13870
- }
13871
- }
13872
- function lockPath(path) {
13873
- return `${path}.lock`;
13874
- }
13875
- function acquireLock(lockPath2, ownerId) {
13876
- const maxWait = 5000;
13877
- const interval = 50;
13878
- const start = Date.now();
13879
- while (Date.now() - start < maxWait) {
13880
- try {
13881
- if (!existsSync2(lockPath2)) {
13882
- writeFileSync2(lockPath2, JSON.stringify({ owner: ownerId, ts: Date.now() }));
13883
- return;
13884
- }
13885
- const lock = JSON.parse(readFileSync2(lockPath2, "utf8"));
13886
- if (Date.now() - lock.ts > 1e4) {
13887
- unlinkSync(lockPath2);
13888
- }
13889
- } catch {}
13890
- const start2 = Date.now();
13891
- while (Date.now() - start2 < interval) {}
13892
- }
13893
- throw new Error(`Could not acquire lock on ${lockPath2} after ${maxWait}ms`);
13894
- }
13895
- function releaseLock(lockPath2, ownerId) {
13896
- try {
13897
- if (existsSync2(lockPath2)) {
13898
- const lock = JSON.parse(readFileSync2(lockPath2, "utf8"));
13899
- if (lock.owner === ownerId) {
13900
- unlinkSync(lockPath2);
13901
- }
13902
- }
13903
- } catch {}
13904
- }
13905
- function loadStore(path) {
13906
- ensureStore(path);
13907
- const raw = readFileSync2(path, "utf8");
13908
- const parsed = JSON.parse(raw);
13909
- if (!parsed || !Array.isArray(parsed.items)) {
13910
- return { items: [] };
13911
- }
13912
- return parsed;
13913
- }
13914
- function saveStore(path, store) {
13915
- const tmp = `${path}.tmp.${randomUUID()}`;
13916
- writeFileSync2(tmp, JSON.stringify(store, null, 2));
13917
- renameSync(tmp, path);
13918
- }
13919
- function withLock(path, fn) {
13920
- const owner = randomUUID();
13921
- const lpath = lockPath(path);
13922
- acquireLock(lpath, owner);
13923
- try {
13924
- return fn();
13925
- } finally {
13926
- releaseLock(lpath, owner);
13927
- }
13928
- }
13929
- function makeId() {
13930
- return `k_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
13931
- }
13857
+ // src/knowledge-db.ts
13858
+ var MIGRATION_1 = `
13859
+ PRAGMA journal_mode = WAL;
13860
+ PRAGMA foreign_keys = ON;
13932
13861
 
13933
- // src/source-ref.ts
13934
- function assertNonEmpty(value, message) {
13935
- if (!value)
13936
- throw new Error(message);
13937
- return value;
13938
- }
13939
- function parseOpenFilesRef(uri) {
13940
- const withoutScheme = uri.slice("open-files://".length);
13941
- const parts = withoutScheme.split("/").filter(Boolean);
13942
- const entity = parts[0];
13943
- if (entity !== "file" && entity !== "source") {
13944
- throw new Error("Invalid open-files ref. Expected open-files://file/<id>, open-files://file/<id>/revision/<revision_id>, or open-files://source/<id>/path/<path>.");
13945
- }
13946
- const id = assertNonEmpty(parts[1], "Invalid open-files ref. Missing id.");
13947
- if (entity === "file") {
13948
- if (parts.length === 2)
13949
- return { kind: "open-files", uri, entity, id };
13950
- if (parts[2] === "revision" && parts[3] && parts.length === 4) {
13951
- return { kind: "open-files", uri, entity, id, revision_id: decodeURIComponent(parts[3]) };
13952
- }
13953
- throw new Error("Invalid open-files file ref. Expected open-files://file/<id>/revision/<revision_id>.");
13954
- }
13955
- const pathIndex = parts.indexOf("path");
13956
- const path = pathIndex >= 0 ? decodeURIComponent(parts.slice(pathIndex + 1).join("/")) : undefined;
13957
- return { kind: "open-files", uri, entity, id, path };
13958
- }
13959
- function parseS3Ref(uri) {
13960
- const parsed = new URL(uri);
13961
- const bucket = assertNonEmpty(parsed.hostname, "Invalid s3 ref. Missing bucket.");
13962
- const key = decodeURIComponent(parsed.pathname.replace(/^\/+/, ""));
13963
- if (!key)
13964
- throw new Error("Invalid s3 ref. Missing object key.");
13965
- return { kind: "s3", uri, bucket, key };
13966
- }
13967
- function parseFileRef(uri) {
13968
- const parsed = new URL(uri);
13969
- return { kind: "file", uri, path: decodeURIComponent(parsed.pathname) };
13970
- }
13971
- function parseWebRef(uri) {
13972
- const parsed = new URL(uri);
13973
- return { kind: "web", uri, url: parsed.toString() };
13974
- }
13975
- function parseSourceRef(uri) {
13976
- if (uri.startsWith("open-files://"))
13977
- return parseOpenFilesRef(uri);
13978
- if (uri.startsWith("s3://"))
13979
- return parseS3Ref(uri);
13980
- if (uri.startsWith("file://"))
13981
- return parseFileRef(uri);
13982
- if (uri.startsWith("https://") || uri.startsWith("http://"))
13983
- return parseWebRef(uri);
13984
- throw new Error(`Unsupported source ref scheme: ${uri}`);
13985
- }
13986
- function catalogSourceUriForRef(uri, parsed = parseSourceRef(uri)) {
13987
- if (parsed.kind === "open-files" && parsed.entity === "file" && parsed.revision_id) {
13988
- return uri.replace(/\/revision\/[^/]+$/, "");
13989
- }
13990
- return uri;
13991
- }
13992
- function revisionIdForSourceRef(uri) {
13993
- const parsed = parseSourceRef(uri);
13994
- return parsed.kind === "open-files" && parsed.entity === "file" ? parsed.revision_id ?? null : null;
13995
- }
13862
+ CREATE TABLE IF NOT EXISTS schema_versions (
13863
+ version INTEGER PRIMARY KEY,
13864
+ applied_at TEXT NOT NULL
13865
+ );
13996
13866
 
13997
- // src/artifact-store.ts
13998
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
13999
- import { dirname as dirname2, join as join2, relative, sep } from "path";
14000
- function normalizeArtifactKey(key) {
14001
- const raw = key.replace(/\\/g, "/").trim();
14002
- if (!raw || raw.startsWith("/")) {
14003
- throw new Error(`Invalid artifact key: ${key}`);
14004
- }
14005
- const segments = raw.split("/").filter(Boolean);
14006
- if (segments.length === 0 || segments.some((segment) => segment === "." || segment === "..")) {
14007
- throw new Error(`Invalid artifact key: ${key}`);
14008
- }
14009
- return segments.join("/");
14010
- }
14011
- function assertInside(root, target) {
14012
- const rel = relative(root, target);
14013
- if (rel.startsWith("..") || rel === ".." || rel.startsWith(`..${sep}`)) {
14014
- throw new Error(`Artifact path escapes root: ${target}`);
14015
- }
14016
- }
13867
+ CREATE TABLE IF NOT EXISTS sources (
13868
+ id TEXT PRIMARY KEY,
13869
+ uri TEXT NOT NULL UNIQUE,
13870
+ kind TEXT NOT NULL,
13871
+ title TEXT,
13872
+ metadata_json TEXT NOT NULL DEFAULT '{}',
13873
+ acl_json TEXT NOT NULL DEFAULT '{}',
13874
+ created_at TEXT NOT NULL,
13875
+ updated_at TEXT NOT NULL
13876
+ );
14017
13877
 
14018
- class LocalArtifactStore {
14019
- root;
14020
- type = "local";
14021
- canRead = true;
14022
- canWrite = true;
14023
- constructor(root) {
14024
- this.root = root;
14025
- mkdirSync2(root, { recursive: true });
14026
- }
14027
- async put(entry) {
14028
- const key = normalizeArtifactKey(entry.key);
14029
- const path = join2(this.root, key);
14030
- assertInside(this.root, path);
14031
- mkdirSync2(dirname2(path), { recursive: true });
14032
- writeFileSync3(path, entry.body);
14033
- return { key, uri: `file://${path}` };
14034
- }
14035
- async getText(key) {
14036
- const normalizedKey = normalizeArtifactKey(key);
14037
- const path = join2(this.root, normalizedKey);
14038
- assertInside(this.root, path);
14039
- return readFileSync3(path, "utf8");
14040
- }
14041
- async exists(key) {
14042
- const normalizedKey = normalizeArtifactKey(key);
14043
- const path = join2(this.root, normalizedKey);
14044
- assertInside(this.root, path);
14045
- return existsSync3(path);
14046
- }
14047
- }
14048
-
14049
- class S3ArtifactStore {
14050
- options;
14051
- type = "s3";
14052
- canRead = true;
14053
- canWrite = true;
14054
- client;
14055
- constructor(options) {
14056
- this.options = options;
14057
- this.client = options.client;
14058
- }
14059
- async getClient() {
14060
- if (this.client)
14061
- return this.client;
14062
- const [{ S3Client }, { fromIni }] = await Promise.all([
14063
- import("@aws-sdk/client-s3"),
14064
- import("@aws-sdk/credential-providers")
14065
- ]);
14066
- this.client = new S3Client({
14067
- region: this.options.region,
14068
- credentials: this.options.profile ? fromIni({ profile: this.options.profile }) : undefined,
14069
- maxAttempts: this.options.max_attempts
14070
- });
14071
- return this.client;
14072
- }
14073
- objectKey(key) {
14074
- const normalizedKey = normalizeArtifactKey(key);
14075
- const prefix = this.options.prefix ? normalizeArtifactKey(this.options.prefix) : "";
14076
- return prefix ? `${prefix}/${normalizedKey}` : normalizedKey;
14077
- }
14078
- async put(entry) {
14079
- const [{ PutObjectCommand }, client] = await Promise.all([
14080
- import("@aws-sdk/client-s3"),
14081
- this.getClient()
14082
- ]);
14083
- const key = this.objectKey(entry.key);
14084
- await client.send(new PutObjectCommand({
14085
- Bucket: this.options.bucket,
14086
- Key: key,
14087
- Body: entry.body,
14088
- ContentType: entry.content_type,
14089
- Metadata: entry.metadata,
14090
- ServerSideEncryption: this.options.server_side_encryption,
14091
- SSEKMSKeyId: this.options.kms_key_id
14092
- }));
14093
- return { key, uri: `s3://${this.options.bucket}/${key}` };
14094
- }
14095
- async getText(key) {
14096
- const [{ GetObjectCommand }, client] = await Promise.all([
14097
- import("@aws-sdk/client-s3"),
14098
- this.getClient()
14099
- ]);
14100
- const objectKey = this.objectKey(key);
14101
- const response = await client.send(new GetObjectCommand({
14102
- Bucket: this.options.bucket,
14103
- Key: objectKey
14104
- }));
14105
- if (!response.Body)
14106
- return "";
14107
- return await response.Body.transformToString();
14108
- }
14109
- async exists(key) {
14110
- const [{ HeadObjectCommand }, client] = await Promise.all([
14111
- import("@aws-sdk/client-s3"),
14112
- this.getClient()
14113
- ]);
14114
- const objectKey = this.objectKey(key);
14115
- try {
14116
- await client.send(new HeadObjectCommand({
14117
- Bucket: this.options.bucket,
14118
- Key: objectKey
14119
- }));
14120
- return true;
14121
- } catch (error48) {
14122
- const name = error48 instanceof Error ? error48.name : "";
14123
- if (name === "NotFound" || name === "NoSuchKey" || name === "NotFoundError")
14124
- return false;
14125
- throw error48;
14126
- }
14127
- }
14128
- }
14129
- function createArtifactStore(config2, workspace) {
14130
- if (config2.storage.type === "s3") {
14131
- if (!config2.storage.s3?.bucket)
14132
- throw new Error("S3 artifact storage requires storage.s3.bucket");
14133
- return new S3ArtifactStore({
14134
- bucket: config2.storage.s3.bucket,
14135
- prefix: config2.storage.s3.prefix,
14136
- region: config2.storage.s3.region,
14137
- profile: config2.storage.s3.profile,
14138
- max_attempts: config2.storage.s3.max_attempts,
14139
- server_side_encryption: config2.storage.s3.server_side_encryption,
14140
- kms_key_id: config2.storage.s3.kms_key_id
14141
- });
14142
- }
14143
- return new LocalArtifactStore(workspace.artifactsDir);
14144
- }
14145
-
14146
- // src/auth.ts
14147
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
14148
- import { homedir as homedir2 } from "os";
14149
- import { dirname as dirname3, join as join3 } from "path";
14150
- var DEFAULT_KNOWLEDGE_API_URL = "https://knowledge.hasna.xyz";
14151
- function normalizeKnowledgeApiOrigin(apiUrl) {
14152
- const url2 = new URL(apiUrl);
14153
- if (url2.protocol !== "http:" && url2.protocol !== "https:") {
14154
- throw new Error("Knowledge API URL must use http or https.");
14155
- }
14156
- const pathname = url2.pathname.replace(/\/+$/, "");
14157
- if (pathname === "/api" || pathname === "/api/v1") {
14158
- url2.pathname = "/";
14159
- } else if (pathname.endsWith("/api/v1")) {
14160
- url2.pathname = pathname.slice(0, -"/api/v1".length) || "/";
14161
- } else if (pathname.endsWith("/api")) {
14162
- url2.pathname = pathname.slice(0, -"/api".length) || "/";
14163
- }
14164
- return url2.toString().replace(/\/+$/, "");
14165
- }
14166
- function knowledgeAuthPath(env = process.env) {
14167
- if (env.HASNA_KNOWLEDGE_AUTH_PATH)
14168
- return env.HASNA_KNOWLEDGE_AUTH_PATH;
14169
- const root = env.HASNA_KNOWLEDGE_AUTH_DIR ?? join3(homedir2(), ".hasna", "knowledge");
14170
- return join3(root, "auth.json");
14171
- }
14172
- function resolveKnowledgeApiUrl(config2, env = process.env) {
14173
- return normalizeKnowledgeApiOrigin(env.KNOWLEDGE_API_URL ?? config2?.hosted?.api_url ?? DEFAULT_KNOWLEDGE_API_URL);
14174
- }
14175
- function getKnowledgeAuth(env = process.env) {
14176
- try {
14177
- const path = knowledgeAuthPath(env);
14178
- if (!existsSync4(path))
14179
- return null;
14180
- const parsed = JSON.parse(readFileSync4(path, "utf8"));
14181
- return typeof parsed.api_key === "string" && parsed.api_key.length > 0 ? parsed : null;
14182
- } catch {
14183
- return null;
14184
- }
14185
- }
14186
- function saveKnowledgeAuth(auth, env = process.env) {
14187
- const path = knowledgeAuthPath(env);
14188
- const stored = {
14189
- ...auth,
14190
- api_url: auth.api_url ? normalizeKnowledgeApiOrigin(auth.api_url) : undefined,
14191
- created_at: auth.created_at ?? new Date().toISOString()
14192
- };
14193
- mkdirSync3(dirname3(path), { recursive: true, mode: 448 });
14194
- writeFileSync4(path, `${JSON.stringify(stored, null, 2)}
14195
- `, { mode: 384 });
14196
- return stored;
14197
- }
14198
- function clearKnowledgeAuth(env = process.env) {
14199
- try {
14200
- unlinkSync2(knowledgeAuthPath(env));
14201
- return true;
14202
- } catch {
14203
- return false;
14204
- }
14205
- }
14206
- function getKnowledgeApiKey(env = process.env) {
14207
- if (env.KNOWLEDGE_API_KEY)
14208
- return { apiKey: env.KNOWLEDGE_API_KEY, source: "env" };
14209
- if (env.HASNA_KNOWLEDGE_API_KEY)
14210
- return { apiKey: env.HASNA_KNOWLEDGE_API_KEY, source: "env" };
14211
- const auth = getKnowledgeAuth(env);
14212
- return auth?.api_key ? { apiKey: auth.api_key, source: "file" } : { apiKey: null, source: "none" };
14213
- }
14214
- function knowledgeAuthStatus(config2, env = process.env) {
14215
- const auth = getKnowledgeAuth(env);
14216
- const key = getKnowledgeApiKey(env);
14217
- const apiUrl = env.KNOWLEDGE_API_URL ? resolveKnowledgeApiUrl(config2, env) : auth?.api_url ? normalizeKnowledgeApiOrigin(auth.api_url) : resolveKnowledgeApiUrl(config2, env);
14218
- return {
14219
- authenticated: Boolean(key.apiKey),
14220
- source: key.source,
14221
- api_url: apiUrl,
14222
- auth_path: knowledgeAuthPath(env),
14223
- email: key.source === "file" ? auth?.email ?? null : null,
14224
- org_id: key.source === "file" ? auth?.org_id ?? null : null,
14225
- org_slug: key.source === "file" ? auth?.org_slug ?? null : null,
14226
- user_id: key.source === "file" ? auth?.user_id ?? null : null,
14227
- api_key_present: Boolean(key.apiKey)
14228
- };
14229
- }
14230
-
14231
- // src/agent.ts
14232
- import { randomUUID as randomUUID3 } from "crypto";
14233
-
14234
- // src/knowledge-db.ts
14235
- import { Database } from "bun:sqlite";
14236
- var MIGRATION_1 = `
14237
- PRAGMA journal_mode = WAL;
14238
- PRAGMA foreign_keys = ON;
14239
-
14240
- CREATE TABLE IF NOT EXISTS schema_versions (
14241
- version INTEGER PRIMARY KEY,
14242
- applied_at TEXT NOT NULL
14243
- );
14244
-
14245
- CREATE TABLE IF NOT EXISTS sources (
14246
- id TEXT PRIMARY KEY,
14247
- uri TEXT NOT NULL UNIQUE,
14248
- kind TEXT NOT NULL,
14249
- title TEXT,
14250
- metadata_json TEXT NOT NULL DEFAULT '{}',
14251
- acl_json TEXT NOT NULL DEFAULT '{}',
14252
- created_at TEXT NOT NULL,
14253
- updated_at TEXT NOT NULL
14254
- );
14255
-
14256
- CREATE TABLE IF NOT EXISTS source_revisions (
14257
- id TEXT PRIMARY KEY,
14258
- source_id TEXT NOT NULL REFERENCES sources(id) ON DELETE CASCADE,
14259
- revision TEXT NOT NULL,
14260
- hash TEXT,
14261
- extracted_text_uri TEXT,
14262
- metadata_json TEXT NOT NULL DEFAULT '{}',
14263
- created_at TEXT NOT NULL,
14264
- UNIQUE(source_id, revision)
14265
- );
13878
+ CREATE TABLE IF NOT EXISTS source_revisions (
13879
+ id TEXT PRIMARY KEY,
13880
+ source_id TEXT NOT NULL REFERENCES sources(id) ON DELETE CASCADE,
13881
+ revision TEXT NOT NULL,
13882
+ hash TEXT,
13883
+ extracted_text_uri TEXT,
13884
+ metadata_json TEXT NOT NULL DEFAULT '{}',
13885
+ created_at TEXT NOT NULL,
13886
+ UNIQUE(source_id, revision)
13887
+ );
14266
13888
 
14267
13889
  CREATE TABLE IF NOT EXISTS chunks (
14268
13890
  id TEXT PRIMARY KEY,
@@ -14536,28 +14158,406 @@ function count(db, table) {
14536
14158
  function getKnowledgeDbStats(path) {
14537
14159
  const db = openKnowledgeDb(path);
14538
14160
  try {
14539
- return {
14540
- schema_version: getSchemaVersion(db),
14541
- sources: count(db, "sources"),
14542
- source_revisions: count(db, "source_revisions"),
14543
- chunks: count(db, "chunks"),
14544
- wiki_pages: count(db, "wiki_pages"),
14545
- citations: count(db, "citations"),
14546
- indexes: count(db, "knowledge_indexes"),
14547
- runs: count(db, "runs"),
14548
- run_events: count(db, "run_events"),
14549
- redaction_findings: count(db, "redaction_findings"),
14550
- audit_events: count(db, "audit_events"),
14551
- approval_gates: count(db, "approval_gates"),
14552
- storage_objects: count(db, "storage_objects"),
14553
- embeddings: count(db, "chunk_embeddings"),
14554
- vector_entries: count(db, "vector_index_entries"),
14555
- reindex_queue: count(db, "reindex_queue")
14556
- };
14557
- } finally {
14558
- db.close();
14161
+ return {
14162
+ schema_version: getSchemaVersion(db),
14163
+ sources: count(db, "sources"),
14164
+ source_revisions: count(db, "source_revisions"),
14165
+ chunks: count(db, "chunks"),
14166
+ wiki_pages: count(db, "wiki_pages"),
14167
+ citations: count(db, "citations"),
14168
+ indexes: count(db, "knowledge_indexes"),
14169
+ runs: count(db, "runs"),
14170
+ run_events: count(db, "run_events"),
14171
+ redaction_findings: count(db, "redaction_findings"),
14172
+ audit_events: count(db, "audit_events"),
14173
+ approval_gates: count(db, "approval_gates"),
14174
+ storage_objects: count(db, "storage_objects"),
14175
+ embeddings: count(db, "chunk_embeddings"),
14176
+ vector_entries: count(db, "vector_index_entries"),
14177
+ reindex_queue: count(db, "reindex_queue")
14178
+ };
14179
+ } finally {
14180
+ db.close();
14181
+ }
14182
+ }
14183
+
14184
+ // src/store.ts
14185
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, renameSync, unlinkSync } from "fs";
14186
+ import { randomUUID } from "crypto";
14187
+ function defaultStorePath() {
14188
+ return workspaceForHome(globalKnowledgeHome()).jsonStorePath;
14189
+ }
14190
+ function ensureStore(path) {
14191
+ if (!existsSync2(path)) {
14192
+ ensureParentDir(path);
14193
+ if (path === defaultStorePath() && existsSync2(legacyGlobalStorePath())) {
14194
+ writeFileSync2(path, readFileSync2(legacyGlobalStorePath(), "utf8"));
14195
+ } else {
14196
+ writeFileSync2(path, JSON.stringify({ items: [] }, null, 2));
14197
+ }
14198
+ }
14199
+ }
14200
+ function lockPath(path) {
14201
+ return `${path}.lock`;
14202
+ }
14203
+ function acquireLock(lockPath2, ownerId) {
14204
+ const maxWait = 5000;
14205
+ const interval = 50;
14206
+ const start = Date.now();
14207
+ while (Date.now() - start < maxWait) {
14208
+ try {
14209
+ if (!existsSync2(lockPath2)) {
14210
+ writeFileSync2(lockPath2, JSON.stringify({ owner: ownerId, ts: Date.now() }));
14211
+ return;
14212
+ }
14213
+ const lock = JSON.parse(readFileSync2(lockPath2, "utf8"));
14214
+ if (Date.now() - lock.ts > 1e4) {
14215
+ unlinkSync(lockPath2);
14216
+ }
14217
+ } catch {}
14218
+ const start2 = Date.now();
14219
+ while (Date.now() - start2 < interval) {}
14220
+ }
14221
+ throw new Error(`Could not acquire lock on ${lockPath2} after ${maxWait}ms`);
14222
+ }
14223
+ function releaseLock(lockPath2, ownerId) {
14224
+ try {
14225
+ if (existsSync2(lockPath2)) {
14226
+ const lock = JSON.parse(readFileSync2(lockPath2, "utf8"));
14227
+ if (lock.owner === ownerId) {
14228
+ unlinkSync(lockPath2);
14229
+ }
14230
+ }
14231
+ } catch {}
14232
+ }
14233
+ function loadStore(path) {
14234
+ ensureStore(path);
14235
+ const raw = readFileSync2(path, "utf8");
14236
+ const parsed = JSON.parse(raw);
14237
+ if (!parsed || !Array.isArray(parsed.items)) {
14238
+ return { items: [] };
14239
+ }
14240
+ return parsed;
14241
+ }
14242
+ function saveStore(path, store) {
14243
+ const tmp = `${path}.tmp.${randomUUID()}`;
14244
+ writeFileSync2(tmp, JSON.stringify(store, null, 2));
14245
+ renameSync(tmp, path);
14246
+ }
14247
+ function withLock(path, fn) {
14248
+ const owner = randomUUID();
14249
+ const lpath = lockPath(path);
14250
+ acquireLock(lpath, owner);
14251
+ try {
14252
+ return fn();
14253
+ } finally {
14254
+ releaseLock(lpath, owner);
14255
+ }
14256
+ }
14257
+ function makeId() {
14258
+ return `k_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
14259
+ }
14260
+
14261
+ // src/source-ref.ts
14262
+ function assertNonEmpty(value, message) {
14263
+ if (!value)
14264
+ throw new Error(message);
14265
+ return value;
14266
+ }
14267
+ function parseOpenFilesRef(uri) {
14268
+ const withoutScheme = uri.slice("open-files://".length);
14269
+ const parts = withoutScheme.split("/").filter(Boolean);
14270
+ const entity = parts[0];
14271
+ if (entity !== "file" && entity !== "source") {
14272
+ throw new Error("Invalid open-files ref. Expected open-files://file/<id>, open-files://file/<id>/revision/<revision_id>, or open-files://source/<id>/path/<path>.");
14273
+ }
14274
+ const id = assertNonEmpty(parts[1], "Invalid open-files ref. Missing id.");
14275
+ if (entity === "file") {
14276
+ if (parts.length === 2)
14277
+ return { kind: "open-files", uri, entity, id };
14278
+ if (parts[2] === "revision" && parts[3] && parts.length === 4) {
14279
+ return { kind: "open-files", uri, entity, id, revision_id: decodeURIComponent(parts[3]) };
14280
+ }
14281
+ throw new Error("Invalid open-files file ref. Expected open-files://file/<id>/revision/<revision_id>.");
14282
+ }
14283
+ const pathIndex = parts.indexOf("path");
14284
+ const path = pathIndex >= 0 ? decodeURIComponent(parts.slice(pathIndex + 1).join("/")) : undefined;
14285
+ return { kind: "open-files", uri, entity, id, path };
14286
+ }
14287
+ function parseS3Ref(uri) {
14288
+ const parsed = new URL(uri);
14289
+ const bucket = assertNonEmpty(parsed.hostname, "Invalid s3 ref. Missing bucket.");
14290
+ const key = decodeURIComponent(parsed.pathname.replace(/^\/+/, ""));
14291
+ if (!key)
14292
+ throw new Error("Invalid s3 ref. Missing object key.");
14293
+ return { kind: "s3", uri, bucket, key };
14294
+ }
14295
+ function parseFileRef(uri) {
14296
+ const parsed = new URL(uri);
14297
+ return { kind: "file", uri, path: decodeURIComponent(parsed.pathname) };
14298
+ }
14299
+ function parseWebRef(uri) {
14300
+ const parsed = new URL(uri);
14301
+ return { kind: "web", uri, url: parsed.toString() };
14302
+ }
14303
+ function parseSourceRef(uri) {
14304
+ if (uri.startsWith("open-files://"))
14305
+ return parseOpenFilesRef(uri);
14306
+ if (uri.startsWith("s3://"))
14307
+ return parseS3Ref(uri);
14308
+ if (uri.startsWith("file://"))
14309
+ return parseFileRef(uri);
14310
+ if (uri.startsWith("https://") || uri.startsWith("http://"))
14311
+ return parseWebRef(uri);
14312
+ throw new Error(`Unsupported source ref scheme: ${uri}`);
14313
+ }
14314
+ function catalogSourceUriForRef(uri, parsed = parseSourceRef(uri)) {
14315
+ if (parsed.kind === "open-files" && parsed.entity === "file" && parsed.revision_id) {
14316
+ return uri.replace(/\/revision\/[^/]+$/, "");
14317
+ }
14318
+ return uri;
14319
+ }
14320
+ function revisionIdForSourceRef(uri) {
14321
+ const parsed = parseSourceRef(uri);
14322
+ return parsed.kind === "open-files" && parsed.entity === "file" ? parsed.revision_id ?? null : null;
14323
+ }
14324
+
14325
+ // src/artifact-store.ts
14326
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
14327
+ import { dirname as dirname2, join as join2, relative, sep } from "path";
14328
+ function normalizeArtifactKey(key) {
14329
+ const raw = key.replace(/\\/g, "/").trim();
14330
+ if (!raw || raw.startsWith("/")) {
14331
+ throw new Error(`Invalid artifact key: ${key}`);
14332
+ }
14333
+ const segments = raw.split("/").filter(Boolean);
14334
+ if (segments.length === 0 || segments.some((segment) => segment === "." || segment === "..")) {
14335
+ throw new Error(`Invalid artifact key: ${key}`);
14336
+ }
14337
+ return segments.join("/");
14338
+ }
14339
+ function assertInside(root, target) {
14340
+ const rel = relative(root, target);
14341
+ if (rel.startsWith("..") || rel === ".." || rel.startsWith(`..${sep}`)) {
14342
+ throw new Error(`Artifact path escapes root: ${target}`);
14343
+ }
14344
+ }
14345
+
14346
+ class LocalArtifactStore {
14347
+ root;
14348
+ type = "local";
14349
+ canRead = true;
14350
+ canWrite = true;
14351
+ constructor(root) {
14352
+ this.root = root;
14353
+ mkdirSync2(root, { recursive: true });
14354
+ }
14355
+ async put(entry) {
14356
+ const key = normalizeArtifactKey(entry.key);
14357
+ const path = join2(this.root, key);
14358
+ assertInside(this.root, path);
14359
+ mkdirSync2(dirname2(path), { recursive: true });
14360
+ writeFileSync3(path, entry.body);
14361
+ return { key, uri: `file://${path}` };
14362
+ }
14363
+ async getText(key) {
14364
+ const normalizedKey = normalizeArtifactKey(key);
14365
+ const path = join2(this.root, normalizedKey);
14366
+ assertInside(this.root, path);
14367
+ return readFileSync3(path, "utf8");
14368
+ }
14369
+ async exists(key) {
14370
+ const normalizedKey = normalizeArtifactKey(key);
14371
+ const path = join2(this.root, normalizedKey);
14372
+ assertInside(this.root, path);
14373
+ return existsSync3(path);
14374
+ }
14375
+ }
14376
+
14377
+ class S3ArtifactStore {
14378
+ options;
14379
+ type = "s3";
14380
+ canRead = true;
14381
+ canWrite = true;
14382
+ client;
14383
+ constructor(options) {
14384
+ this.options = options;
14385
+ this.client = options.client;
14386
+ }
14387
+ async getClient() {
14388
+ if (this.client)
14389
+ return this.client;
14390
+ const [{ S3Client }, { fromIni }] = await Promise.all([
14391
+ import("@aws-sdk/client-s3"),
14392
+ import("@aws-sdk/credential-providers")
14393
+ ]);
14394
+ this.client = new S3Client({
14395
+ region: this.options.region,
14396
+ credentials: this.options.profile ? fromIni({ profile: this.options.profile }) : undefined,
14397
+ maxAttempts: this.options.max_attempts
14398
+ });
14399
+ return this.client;
14400
+ }
14401
+ objectKey(key) {
14402
+ const normalizedKey = normalizeArtifactKey(key);
14403
+ const prefix = this.options.prefix ? normalizeArtifactKey(this.options.prefix) : "";
14404
+ return prefix ? `${prefix}/${normalizedKey}` : normalizedKey;
14405
+ }
14406
+ async put(entry) {
14407
+ const [{ PutObjectCommand }, client] = await Promise.all([
14408
+ import("@aws-sdk/client-s3"),
14409
+ this.getClient()
14410
+ ]);
14411
+ const key = this.objectKey(entry.key);
14412
+ await client.send(new PutObjectCommand({
14413
+ Bucket: this.options.bucket,
14414
+ Key: key,
14415
+ Body: entry.body,
14416
+ ContentType: entry.content_type,
14417
+ Metadata: entry.metadata,
14418
+ ServerSideEncryption: this.options.server_side_encryption,
14419
+ SSEKMSKeyId: this.options.kms_key_id
14420
+ }));
14421
+ return { key, uri: `s3://${this.options.bucket}/${key}` };
14422
+ }
14423
+ async getText(key) {
14424
+ const [{ GetObjectCommand }, client] = await Promise.all([
14425
+ import("@aws-sdk/client-s3"),
14426
+ this.getClient()
14427
+ ]);
14428
+ const objectKey = this.objectKey(key);
14429
+ const response = await client.send(new GetObjectCommand({
14430
+ Bucket: this.options.bucket,
14431
+ Key: objectKey
14432
+ }));
14433
+ if (!response.Body)
14434
+ return "";
14435
+ return await response.Body.transformToString();
14436
+ }
14437
+ async exists(key) {
14438
+ const [{ HeadObjectCommand }, client] = await Promise.all([
14439
+ import("@aws-sdk/client-s3"),
14440
+ this.getClient()
14441
+ ]);
14442
+ const objectKey = this.objectKey(key);
14443
+ try {
14444
+ await client.send(new HeadObjectCommand({
14445
+ Bucket: this.options.bucket,
14446
+ Key: objectKey
14447
+ }));
14448
+ return true;
14449
+ } catch (error48) {
14450
+ const name = error48 instanceof Error ? error48.name : "";
14451
+ if (name === "NotFound" || name === "NoSuchKey" || name === "NotFoundError")
14452
+ return false;
14453
+ throw error48;
14454
+ }
14455
+ }
14456
+ }
14457
+ function createArtifactStore(config2, workspace) {
14458
+ if (config2.storage.type === "s3") {
14459
+ if (!config2.storage.s3?.bucket)
14460
+ throw new Error("S3 artifact storage requires storage.s3.bucket");
14461
+ return new S3ArtifactStore({
14462
+ bucket: config2.storage.s3.bucket,
14463
+ prefix: config2.storage.s3.prefix,
14464
+ region: config2.storage.s3.region,
14465
+ profile: config2.storage.s3.profile,
14466
+ max_attempts: config2.storage.s3.max_attempts,
14467
+ server_side_encryption: config2.storage.s3.server_side_encryption,
14468
+ kms_key_id: config2.storage.s3.kms_key_id
14469
+ });
14470
+ }
14471
+ return new LocalArtifactStore(workspace.artifactsDir);
14472
+ }
14473
+
14474
+ // src/auth.ts
14475
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
14476
+ import { homedir as homedir2 } from "os";
14477
+ import { dirname as dirname3, join as join3 } from "path";
14478
+ var DEFAULT_KNOWLEDGE_API_URL = "https://knowledge.hasna.xyz";
14479
+ function normalizeKnowledgeApiOrigin(apiUrl) {
14480
+ const url2 = new URL(apiUrl);
14481
+ if (url2.protocol !== "http:" && url2.protocol !== "https:") {
14482
+ throw new Error("Knowledge API URL must use http or https.");
14483
+ }
14484
+ const pathname = url2.pathname.replace(/\/+$/, "");
14485
+ if (pathname === "/api" || pathname === "/api/v1") {
14486
+ url2.pathname = "/";
14487
+ } else if (pathname.endsWith("/api/v1")) {
14488
+ url2.pathname = pathname.slice(0, -"/api/v1".length) || "/";
14489
+ } else if (pathname.endsWith("/api")) {
14490
+ url2.pathname = pathname.slice(0, -"/api".length) || "/";
14491
+ }
14492
+ return url2.toString().replace(/\/+$/, "");
14493
+ }
14494
+ function knowledgeAuthPath(env = process.env) {
14495
+ if (env.HASNA_KNOWLEDGE_AUTH_PATH)
14496
+ return env.HASNA_KNOWLEDGE_AUTH_PATH;
14497
+ const root = env.HASNA_KNOWLEDGE_AUTH_DIR ?? join3(homedir2(), ".hasna", "knowledge");
14498
+ return join3(root, "auth.json");
14499
+ }
14500
+ function resolveKnowledgeApiUrl(config2, env = process.env) {
14501
+ return normalizeKnowledgeApiOrigin(env.KNOWLEDGE_API_URL ?? config2?.hosted?.api_url ?? DEFAULT_KNOWLEDGE_API_URL);
14502
+ }
14503
+ function getKnowledgeAuth(env = process.env) {
14504
+ try {
14505
+ const path = knowledgeAuthPath(env);
14506
+ if (!existsSync4(path))
14507
+ return null;
14508
+ const parsed = JSON.parse(readFileSync4(path, "utf8"));
14509
+ return typeof parsed.api_key === "string" && parsed.api_key.length > 0 ? parsed : null;
14510
+ } catch {
14511
+ return null;
14512
+ }
14513
+ }
14514
+ function saveKnowledgeAuth(auth, env = process.env) {
14515
+ const path = knowledgeAuthPath(env);
14516
+ const stored = {
14517
+ ...auth,
14518
+ api_url: auth.api_url ? normalizeKnowledgeApiOrigin(auth.api_url) : undefined,
14519
+ created_at: auth.created_at ?? new Date().toISOString()
14520
+ };
14521
+ mkdirSync3(dirname3(path), { recursive: true, mode: 448 });
14522
+ writeFileSync4(path, `${JSON.stringify(stored, null, 2)}
14523
+ `, { mode: 384 });
14524
+ return stored;
14525
+ }
14526
+ function clearKnowledgeAuth(env = process.env) {
14527
+ try {
14528
+ unlinkSync2(knowledgeAuthPath(env));
14529
+ return true;
14530
+ } catch {
14531
+ return false;
14559
14532
  }
14560
14533
  }
14534
+ function getKnowledgeApiKey(env = process.env) {
14535
+ if (env.KNOWLEDGE_API_KEY)
14536
+ return { apiKey: env.KNOWLEDGE_API_KEY, source: "env" };
14537
+ if (env.HASNA_KNOWLEDGE_API_KEY)
14538
+ return { apiKey: env.HASNA_KNOWLEDGE_API_KEY, source: "env" };
14539
+ const auth = getKnowledgeAuth(env);
14540
+ return auth?.api_key ? { apiKey: auth.api_key, source: "file" } : { apiKey: null, source: "none" };
14541
+ }
14542
+ function knowledgeAuthStatus(config2, env = process.env) {
14543
+ const auth = getKnowledgeAuth(env);
14544
+ const key = getKnowledgeApiKey(env);
14545
+ const apiUrl = env.KNOWLEDGE_API_URL ? resolveKnowledgeApiUrl(config2, env) : auth?.api_url ? normalizeKnowledgeApiOrigin(auth.api_url) : resolveKnowledgeApiUrl(config2, env);
14546
+ return {
14547
+ authenticated: Boolean(key.apiKey),
14548
+ source: key.source,
14549
+ api_url: apiUrl,
14550
+ auth_path: knowledgeAuthPath(env),
14551
+ email: key.source === "file" ? auth?.email ?? null : null,
14552
+ org_id: key.source === "file" ? auth?.org_id ?? null : null,
14553
+ org_slug: key.source === "file" ? auth?.org_slug ?? null : null,
14554
+ user_id: key.source === "file" ? auth?.user_id ?? null : null,
14555
+ api_key_present: Boolean(key.apiKey)
14556
+ };
14557
+ }
14558
+
14559
+ // src/agent.ts
14560
+ import { randomUUID as randomUUID3 } from "crypto";
14561
14561
 
14562
14562
  // src/providers.ts
14563
14563
  import { randomUUID as randomUUID2 } from "crypto";
@@ -19344,14 +19344,520 @@ function sortItems(items, sort = "created", desc = false) {
19344
19344
  function activeItems(items, includeArchived) {
19345
19345
  return includeArchived ? items : items.filter((item) => !item.archived);
19346
19346
  }
19347
+ function limitNumber(value, fallback = 20, max = 100) {
19348
+ if (!Number.isFinite(value) || value <= 0)
19349
+ return fallback;
19350
+ return Math.min(Math.floor(value), max);
19351
+ }
19352
+ function parseJsonObject5(value) {
19353
+ if (!value)
19354
+ return {};
19355
+ try {
19356
+ const parsed = JSON.parse(value);
19357
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
19358
+ } catch {
19359
+ return {};
19360
+ }
19361
+ }
19362
+ function jsonResource(uri, data) {
19363
+ return {
19364
+ contents: [{
19365
+ uri: uri.toString(),
19366
+ mimeType: "application/json",
19367
+ text: JSON.stringify(data, null, 2)
19368
+ }]
19369
+ };
19370
+ }
19347
19371
  function registerTool(server, name, title, description, inputSchema, handler) {
19348
19372
  server.registerTool(name, { title, description, inputSchema }, handler);
19349
19373
  }
19374
+ function registerJsonResource(server, name, uri, title, description, read) {
19375
+ server.registerResource(name, uri, {
19376
+ title,
19377
+ description,
19378
+ mimeType: "application/json"
19379
+ }, async (resourceUri) => jsonResource(resourceUri, await read(resourceUri)));
19380
+ }
19381
+ function registerJsonTemplate(server, name, template, title, description, list, read) {
19382
+ server.registerResource(name, new ResourceTemplate(template, { list }), {
19383
+ title,
19384
+ description,
19385
+ mimeType: "application/json"
19386
+ }, async (resourceUri, variables) => jsonResource(resourceUri, await read(resourceUri, variables)));
19387
+ }
19388
+ function projectService() {
19389
+ return createKnowledgeService({ scope: "project" });
19390
+ }
19391
+ function openProjectDb(service = projectService()) {
19392
+ const workspace = service.ensureWorkspace();
19393
+ migrateKnowledgeDb(workspace.knowledgeDbPath);
19394
+ return openKnowledgeDb(workspace.knowledgeDbPath);
19395
+ }
19396
+ function itemResources(storePath = createKnowledgeService({ scope: "project" }).jsonStorePath()) {
19397
+ return readStoreLocked(storePath, (db) => activeItems(db.items, false).slice(0, 100).map((item) => ({
19398
+ uri: `knowledge://project/items/${encodeURIComponent(item.id)}`,
19399
+ name: item.title,
19400
+ description: `Knowledge item ${item.id}`,
19401
+ mimeType: "application/json"
19402
+ })));
19403
+ }
19404
+ function listRows(db, sql, params = []) {
19405
+ return db.query(sql).all(...params);
19406
+ }
19407
+ function rowWithJson(row, fields = ["metadata_json", "acl_json"]) {
19408
+ if (!row)
19409
+ return null;
19410
+ const next = { ...row };
19411
+ for (const field of fields) {
19412
+ if (field in next) {
19413
+ const name = field.endsWith("_json") ? field.slice(0, -5) : field;
19414
+ next[name] = parseJsonObject5(next[field]);
19415
+ delete next[field];
19416
+ }
19417
+ }
19418
+ return next;
19419
+ }
19420
+ function dbStatsSnapshot(service = projectService()) {
19421
+ const stats = service.dbStats();
19422
+ const db = openProjectDb(service);
19423
+ try {
19424
+ return {
19425
+ ok: true,
19426
+ scope: "project",
19427
+ path: service.workspace.knowledgeDbPath,
19428
+ stats,
19429
+ schema_versions: listRows(db, "SELECT version, applied_at FROM schema_versions ORDER BY version ASC")
19430
+ };
19431
+ } finally {
19432
+ db.close();
19433
+ }
19434
+ }
19435
+ function storageSnapshot(service = projectService()) {
19436
+ const validation = service.validateStorage();
19437
+ return {
19438
+ ok: validation.ok,
19439
+ scope: "project",
19440
+ paths: service.paths(),
19441
+ storage: service.storageContract(),
19442
+ validation
19443
+ };
19444
+ }
19445
+ function configSnapshot(service = projectService()) {
19446
+ return {
19447
+ ok: true,
19448
+ scope: "project",
19449
+ package: {
19450
+ name: package_default.name,
19451
+ version: package_default.version
19452
+ },
19453
+ paths: service.paths(),
19454
+ storage: service.storageContract(),
19455
+ provider_status: service.providerStatus(),
19456
+ model_registry: service.modelRegistry()
19457
+ };
19458
+ }
19459
+ function sourceRows(limit = 50, service = projectService()) {
19460
+ const db = openProjectDb(service);
19461
+ try {
19462
+ return listRows(db, `
19463
+ SELECT
19464
+ s.id,
19465
+ s.uri,
19466
+ s.kind,
19467
+ s.title,
19468
+ s.metadata_json,
19469
+ s.acl_json,
19470
+ s.created_at,
19471
+ s.updated_at,
19472
+ COUNT(DISTINCT sr.id) AS revisions,
19473
+ COUNT(DISTINCT c.id) AS chunks
19474
+ FROM sources s
19475
+ LEFT JOIN source_revisions sr ON sr.source_id = s.id
19476
+ LEFT JOIN chunks c ON c.source_revision_id = sr.id
19477
+ GROUP BY s.id
19478
+ ORDER BY s.updated_at DESC
19479
+ LIMIT ?
19480
+ `, [limitNumber(limit, 50, 200)]).map((row) => rowWithJson(row));
19481
+ } finally {
19482
+ db.close();
19483
+ }
19484
+ }
19485
+ function sourceSnapshot(id, { limit = 10, service = projectService() } = {}) {
19486
+ const db = openProjectDb(service);
19487
+ try {
19488
+ const source = rowWithJson(db.query(`
19489
+ SELECT id, uri, kind, title, metadata_json, acl_json, created_at, updated_at
19490
+ FROM sources
19491
+ WHERE id = ? OR uri = ?
19492
+ `).get(id, id));
19493
+ if (!source)
19494
+ return null;
19495
+ const revisions = listRows(db, `
19496
+ SELECT id, revision, hash, extracted_text_uri, metadata_json, created_at
19497
+ FROM source_revisions
19498
+ WHERE source_id = ?
19499
+ ORDER BY created_at DESC
19500
+ LIMIT ?
19501
+ `, [source.id, limitNumber(limit, 10, 100)]).map((row) => rowWithJson(row, ["metadata_json"]));
19502
+ const chunks = listRows(db, `
19503
+ SELECT c.id, c.kind, c.ordinal, c.text, c.token_count, c.start_offset, c.end_offset, c.metadata_json, c.created_at,
19504
+ sr.revision, sr.hash
19505
+ FROM chunks c
19506
+ JOIN source_revisions sr ON sr.id = c.source_revision_id
19507
+ WHERE sr.source_id = ?
19508
+ ORDER BY sr.created_at DESC, c.ordinal ASC
19509
+ LIMIT ?
19510
+ `, [source.id, limitNumber(limit, 10, 50)]).map((row) => rowWithJson(row, ["metadata_json"]));
19511
+ return { source, revisions, chunks };
19512
+ } finally {
19513
+ db.close();
19514
+ }
19515
+ }
19516
+ function openFilesSnapshot(service = projectService()) {
19517
+ const db = openProjectDb(service);
19518
+ try {
19519
+ const rows = listRows(db, `
19520
+ SELECT
19521
+ s.id,
19522
+ s.uri,
19523
+ s.title,
19524
+ sr.revision,
19525
+ sr.hash,
19526
+ c.metadata_json,
19527
+ COUNT(c.id) AS chunks
19528
+ FROM sources s
19529
+ JOIN source_revisions sr ON sr.source_id = s.id
19530
+ LEFT JOIN chunks c ON c.source_revision_id = sr.id
19531
+ WHERE s.uri LIKE 'open-files://%'
19532
+ GROUP BY s.id, sr.id
19533
+ ORDER BY s.updated_at DESC
19534
+ LIMIT 100
19535
+ `);
19536
+ return {
19537
+ ok: true,
19538
+ scope: "project",
19539
+ source_ownership: "open-files",
19540
+ raw_source_bytes_exposed: false,
19541
+ refs: rows.map((row) => {
19542
+ const metadata = parseJsonObject5(row.metadata_json);
19543
+ return {
19544
+ id: row.id,
19545
+ uri: row.uri,
19546
+ source_ref: typeof metadata.source_ref === "string" ? metadata.source_ref : row.uri,
19547
+ title: row.title,
19548
+ revision: row.revision,
19549
+ hash: row.hash,
19550
+ chunks: row.chunks
19551
+ };
19552
+ })
19553
+ };
19554
+ } finally {
19555
+ db.close();
19556
+ }
19557
+ }
19558
+ function wikiRows(limit = 50, service = projectService()) {
19559
+ const db = openProjectDb(service);
19560
+ try {
19561
+ return listRows(db, `
19562
+ SELECT id, path, title, artifact_uri, content_hash, status, metadata_json, created_at, updated_at
19563
+ FROM wiki_pages
19564
+ ORDER BY updated_at DESC
19565
+ LIMIT ?
19566
+ `, [limitNumber(limit, 50, 200)]).map((row) => rowWithJson(row, ["metadata_json"]));
19567
+ } finally {
19568
+ db.close();
19569
+ }
19570
+ }
19571
+ async function wikiSnapshot(id, { includeContent = true, service = projectService() } = {}) {
19572
+ const db = openProjectDb(service);
19573
+ try {
19574
+ const page = rowWithJson(db.query(`
19575
+ SELECT id, path, title, artifact_uri, content_hash, status, metadata_json, created_at, updated_at
19576
+ FROM wiki_pages
19577
+ WHERE id = ? OR path = ?
19578
+ `).get(id, id), ["metadata_json"]);
19579
+ if (!page)
19580
+ return null;
19581
+ const citations = listRows(db, `
19582
+ SELECT id, chunk_id, source_uri, quote, start_offset, end_offset, metadata_json, created_at
19583
+ FROM citations
19584
+ WHERE wiki_page_id = ?
19585
+ ORDER BY created_at ASC
19586
+ LIMIT 100
19587
+ `, [page.id]).map((row) => rowWithJson(row, ["metadata_json"]));
19588
+ let content = null;
19589
+ if (includeContent) {
19590
+ const artifactKey = page.metadata?.artifact_key ?? page.path;
19591
+ if (typeof artifactKey === "string") {
19592
+ try {
19593
+ content = await service.artifactStore().getText(artifactKey);
19594
+ } catch {
19595
+ content = null;
19596
+ }
19597
+ }
19598
+ }
19599
+ return { page, citations, content };
19600
+ } finally {
19601
+ db.close();
19602
+ }
19603
+ }
19604
+ function indexRows(limit = 50, service = projectService()) {
19605
+ const db = openProjectDb(service);
19606
+ try {
19607
+ return listRows(db, `
19608
+ SELECT id, kind, name, artifact_uri, shard_key, metadata_json, created_at, updated_at
19609
+ FROM knowledge_indexes
19610
+ ORDER BY updated_at DESC
19611
+ LIMIT ?
19612
+ `, [limitNumber(limit, 50, 200)]).map((row) => rowWithJson(row, ["metadata_json"]));
19613
+ } finally {
19614
+ db.close();
19615
+ }
19616
+ }
19617
+ function indexSnapshot(id, service = projectService()) {
19618
+ const db = openProjectDb(service);
19619
+ try {
19620
+ const index = rowWithJson(db.query(`
19621
+ SELECT id, kind, name, artifact_uri, shard_key, metadata_json, created_at, updated_at
19622
+ FROM knowledge_indexes
19623
+ WHERE id = ? OR name = ? OR shard_key = ?
19624
+ `).get(id, id, id), ["metadata_json"]);
19625
+ if (!index)
19626
+ return null;
19627
+ const vector_counts = listRows(db, `
19628
+ SELECT provider, model, dimensions, status, COUNT(*) AS entries
19629
+ FROM vector_index_entries
19630
+ GROUP BY provider, model, dimensions, status
19631
+ ORDER BY entries DESC
19632
+ LIMIT 50
19633
+ `);
19634
+ return { index, vector_counts };
19635
+ } finally {
19636
+ db.close();
19637
+ }
19638
+ }
19639
+ function runRows(limit = 50, service = projectService()) {
19640
+ const db = openProjectDb(service);
19641
+ try {
19642
+ return listRows(db, `
19643
+ SELECT id, type, prompt, status, provider, model, cost_tokens, cost_usd, metadata_json, created_at, updated_at
19644
+ FROM runs
19645
+ ORDER BY updated_at DESC
19646
+ LIMIT ?
19647
+ `, [limitNumber(limit, 50, 200)]).map((row) => rowWithJson(row, ["metadata_json"]));
19648
+ } finally {
19649
+ db.close();
19650
+ }
19651
+ }
19652
+ function runSnapshot(id, { limit = 50, service = projectService() } = {}) {
19653
+ const db = openProjectDb(service);
19654
+ try {
19655
+ const run = rowWithJson(db.query(`
19656
+ SELECT id, type, prompt, status, provider, model, cost_tokens, cost_usd, metadata_json, created_at, updated_at
19657
+ FROM runs
19658
+ WHERE id = ?
19659
+ `).get(id), ["metadata_json"]);
19660
+ if (!run)
19661
+ return null;
19662
+ const events = listRows(db, `
19663
+ SELECT id, level, event, metadata_json, created_at
19664
+ FROM run_events
19665
+ WHERE run_id = ?
19666
+ ORDER BY created_at ASC
19667
+ LIMIT ?
19668
+ `, [id, limitNumber(limit, 50, 200)]).map((row) => rowWithJson(row, ["metadata_json"]));
19669
+ const usage = listRows(db, `
19670
+ SELECT id, provider, model, input_tokens, output_tokens, cost_usd, metadata_json, created_at
19671
+ FROM provider_usage
19672
+ WHERE run_id = ?
19673
+ ORDER BY created_at ASC
19674
+ LIMIT 100
19675
+ `, [id]).map((row) => rowWithJson(row, ["metadata_json"]));
19676
+ return { run, events, usage };
19677
+ } finally {
19678
+ db.close();
19679
+ }
19680
+ }
19681
+ function decisionsSnapshot(limit = 50, service = projectService()) {
19682
+ const db = openProjectDb(service);
19683
+ try {
19684
+ return {
19685
+ ok: true,
19686
+ scope: "project",
19687
+ approval_gates: listRows(db, `
19688
+ SELECT id, action, target_uri, status, reason, approved_by, metadata_json, created_at, updated_at
19689
+ FROM approval_gates
19690
+ ORDER BY updated_at DESC
19691
+ LIMIT ?
19692
+ `, [limitNumber(limit, 50, 200)]).map((row) => rowWithJson(row, ["metadata_json"])),
19693
+ audit_events: listRows(db, `
19694
+ SELECT id, event_type, action, target_uri, decision, metadata_json, created_at
19695
+ FROM audit_events
19696
+ ORDER BY created_at DESC
19697
+ LIMIT ?
19698
+ `, [limitNumber(limit, 50, 200)]).map((row) => rowWithJson(row, ["metadata_json"]))
19699
+ };
19700
+ } finally {
19701
+ db.close();
19702
+ }
19703
+ }
19704
+ function decisionSnapshot(id, service = projectService()) {
19705
+ const db = openProjectDb(service);
19706
+ try {
19707
+ const approval = rowWithJson(db.query(`
19708
+ SELECT id, action, target_uri, status, reason, approved_by, metadata_json, created_at, updated_at
19709
+ FROM approval_gates
19710
+ WHERE id = ? OR target_uri = ?
19711
+ `).get(id, id), ["metadata_json"]);
19712
+ if (approval)
19713
+ return { kind: "approval_gate", decision: approval };
19714
+ const audit = rowWithJson(db.query(`
19715
+ SELECT id, event_type, action, target_uri, decision, metadata_json, created_at
19716
+ FROM audit_events
19717
+ WHERE id = ? OR target_uri = ?
19718
+ `).get(id, id), ["metadata_json"]);
19719
+ return audit ? { kind: "audit_event", decision: audit } : null;
19720
+ } finally {
19721
+ db.close();
19722
+ }
19723
+ }
19724
+ async function getKnowledgeRecord(kind, id, options = {}) {
19725
+ const normalized = kind ?? "auto";
19726
+ const service = createKnowledgeService({ scope: options.scope });
19727
+ const attempts = normalized === "auto" ? ["item", "source", "wiki_page", "run", "index", "decision"] : [normalized];
19728
+ for (const entry of attempts) {
19729
+ if (entry === "item") {
19730
+ const storePath = resolveStorePath(options.store_path, options.scope);
19731
+ const item = readStoreLocked(storePath, (db) => findItem(db, id));
19732
+ if (item)
19733
+ return { kind: "item", item, store_path: storePath };
19734
+ }
19735
+ if (entry === "source") {
19736
+ const source = sourceSnapshot(id, { limit: options.limit, service });
19737
+ if (source)
19738
+ return { kind: "source", ...source };
19739
+ }
19740
+ if (entry === "wiki_page") {
19741
+ const page = await wikiSnapshot(id, { includeContent: options.include_content !== false, service });
19742
+ if (page)
19743
+ return { kind: "wiki_page", ...page };
19744
+ }
19745
+ if (entry === "run") {
19746
+ const run = runSnapshot(id, { limit: options.limit, service });
19747
+ if (run)
19748
+ return { kind: "run", ...run };
19749
+ }
19750
+ if (entry === "index") {
19751
+ const index = indexSnapshot(id, service);
19752
+ if (index)
19753
+ return { kind: "index", ...index };
19754
+ }
19755
+ if (entry === "decision") {
19756
+ const decision = decisionSnapshot(id, service);
19757
+ if (decision)
19758
+ return { kind: "decision", ...decision };
19759
+ }
19760
+ }
19761
+ return null;
19762
+ }
19763
+ function registerKnowledgeResources(server) {
19764
+ registerJsonResource(server, "knowledge-project-config", "knowledge://project/config", "Project knowledge config", "Resolved project workspace config, provider registry, and storage contract", async () => configSnapshot());
19765
+ registerJsonResource(server, "knowledge-project-storage", "knowledge://project/storage", "Project knowledge storage", "Artifact storage contract and validation for project knowledge", async () => storageSnapshot());
19766
+ registerJsonResource(server, "knowledge-project-schema", "knowledge://project/schema", "Project knowledge schema", "SQLite schema version and table counts for project knowledge", async () => dbStatsSnapshot());
19767
+ registerJsonResource(server, "knowledge-project-sources", "knowledge://project/sources", "Project knowledge sources", "Indexed source refs and revision/chunk counts without raw source bytes", async () => ({ ok: true, scope: "project", sources: sourceRows() }));
19768
+ registerJsonResource(server, "knowledge-project-open-files", "knowledge://project/open-files", "Project open-files refs", "Open-files source refs known to the project knowledge catalog", async () => openFilesSnapshot());
19769
+ registerJsonResource(server, "knowledge-project-wiki-pages", "knowledge://project/wiki/pages", "Project wiki pages", "Generated wiki pages and citation artifact metadata", async () => ({ ok: true, scope: "project", pages: wikiRows() }));
19770
+ registerJsonResource(server, "knowledge-project-indexes", "knowledge://project/indexes", "Project knowledge indexes", "Sharded knowledge indexes and vector-index status", async () => ({
19771
+ ok: true,
19772
+ scope: "project",
19773
+ indexes: indexRows(),
19774
+ embeddings: projectService().embeddingStatus()
19775
+ }));
19776
+ registerJsonResource(server, "knowledge-project-runs", "knowledge://project/runs", "Project knowledge runs", "Recent prompt, ingestion, web search, and reindex run ledger entries", async () => ({ ok: true, scope: "project", runs: runRows() }));
19777
+ registerJsonResource(server, "knowledge-project-decisions", "knowledge://project/decisions", "Project knowledge decisions", "Approval gates and audit decisions for generated knowledge operations", async () => decisionsSnapshot());
19778
+ registerJsonTemplate(server, "knowledge-project-items", "knowledge://project/items/{id}", "Project knowledge item", "Read a compatibility JSON-store item by id", async () => ({ resources: itemResources() }), async (_uri, variables) => {
19779
+ const id = decodeURIComponent(String(variables.id));
19780
+ const record2 = await getKnowledgeRecord("item", id, { scope: "project" });
19781
+ return record2 ? { ok: true, ...record2 } : { ok: false, error: `Item not found: ${id}` };
19782
+ });
19783
+ registerJsonTemplate(server, "knowledge-project-source", "knowledge://project/sources/{id}", "Project source", "Read indexed source metadata, revisions, and derived chunks", async () => ({
19784
+ resources: sourceRows().map((source) => ({
19785
+ uri: `knowledge://project/sources/${encodeURIComponent(source.id)}`,
19786
+ name: source.title ?? source.uri,
19787
+ description: `${source.kind} source with ${source.chunks} chunk(s)`,
19788
+ mimeType: "application/json"
19789
+ }))
19790
+ }), async (_uri, variables) => {
19791
+ const id = decodeURIComponent(String(variables.id));
19792
+ const record2 = sourceSnapshot(id);
19793
+ return record2 ? { ok: true, kind: "source", ...record2 } : { ok: false, error: `Source not found: ${id}` };
19794
+ });
19795
+ registerJsonTemplate(server, "knowledge-project-wiki-page", "knowledge://project/wiki/pages/{id}", "Project wiki page", "Read generated wiki page metadata, citations, and artifact text", async () => ({
19796
+ resources: wikiRows().map((page) => ({
19797
+ uri: `knowledge://project/wiki/pages/${encodeURIComponent(page.id)}`,
19798
+ name: page.title,
19799
+ description: page.path,
19800
+ mimeType: "application/json"
19801
+ }))
19802
+ }), async (_uri, variables) => {
19803
+ const id = decodeURIComponent(String(variables.id));
19804
+ const record2 = await wikiSnapshot(id);
19805
+ return record2 ? { ok: true, kind: "wiki_page", ...record2 } : { ok: false, error: `Wiki page not found: ${id}` };
19806
+ });
19807
+ registerJsonTemplate(server, "knowledge-project-index", "knowledge://project/indexes/{id}", "Project knowledge index", "Read a knowledge index row and vector-count snapshot", async () => ({
19808
+ resources: indexRows().map((index) => ({
19809
+ uri: `knowledge://project/indexes/${encodeURIComponent(index.id)}`,
19810
+ name: index.name,
19811
+ description: `${index.kind} index${index.shard_key ? ` shard ${index.shard_key}` : ""}`,
19812
+ mimeType: "application/json"
19813
+ }))
19814
+ }), async (_uri, variables) => {
19815
+ const id = decodeURIComponent(String(variables.id));
19816
+ const record2 = indexSnapshot(id);
19817
+ return record2 ? { ok: true, kind: "index", ...record2 } : { ok: false, error: `Index not found: ${id}` };
19818
+ });
19819
+ registerJsonTemplate(server, "knowledge-project-run", "knowledge://project/runs/{id}", "Project run", "Read a knowledge run ledger entry with events and usage", async () => ({
19820
+ resources: runRows().map((run) => ({
19821
+ uri: `knowledge://project/runs/${encodeURIComponent(run.id)}`,
19822
+ name: `${run.type}: ${run.status}`,
19823
+ description: run.prompt ?? run.id,
19824
+ mimeType: "application/json"
19825
+ }))
19826
+ }), async (_uri, variables) => {
19827
+ const id = decodeURIComponent(String(variables.id));
19828
+ const record2 = runSnapshot(id);
19829
+ return record2 ? { ok: true, kind: "run", ...record2 } : { ok: false, error: `Run not found: ${id}` };
19830
+ });
19831
+ registerJsonTemplate(server, "knowledge-project-decision", "knowledge://project/decisions/{id}", "Project decision", "Read an approval gate or audit decision", async () => {
19832
+ const decisions = decisionsSnapshot();
19833
+ return {
19834
+ resources: [
19835
+ ...decisions.approval_gates.map((entry) => ({
19836
+ uri: `knowledge://project/decisions/${encodeURIComponent(entry.id)}`,
19837
+ name: `${entry.action}: ${entry.status}`,
19838
+ description: entry.target_uri ?? entry.id,
19839
+ mimeType: "application/json"
19840
+ })),
19841
+ ...decisions.audit_events.map((entry) => ({
19842
+ uri: `knowledge://project/decisions/${encodeURIComponent(entry.id)}`,
19843
+ name: `${entry.action}: ${entry.decision}`,
19844
+ description: entry.target_uri ?? entry.id,
19845
+ mimeType: "application/json"
19846
+ }))
19847
+ ]
19848
+ };
19849
+ }, async (_uri, variables) => {
19850
+ const id = decodeURIComponent(String(variables.id));
19851
+ const record2 = decisionSnapshot(id);
19852
+ return record2 ? { ok: true, ...record2 } : { ok: false, error: `Decision not found: ${id}` };
19853
+ });
19854
+ }
19350
19855
  function buildServer() {
19351
19856
  const server = new McpServer({
19352
19857
  name: "open-knowledge",
19353
19858
  version: package_default.version
19354
19859
  });
19860
+ registerKnowledgeResources(server);
19355
19861
  registerTool(server, "ok_paths", "Knowledge workspace paths", "Show resolved workspace and store paths", {
19356
19862
  scope: scopeField
19357
19863
  }, async ({ scope }) => {
@@ -19532,6 +20038,150 @@ function buildServer() {
19532
20038
  return errorText(error48 instanceof Error ? error48.message : String(error48));
19533
20039
  }
19534
20040
  });
20041
+ registerTool(server, "knowledge_get", "Get knowledge record", "Read a knowledge item, indexed source, wiki page, run, index, or decision by id without raw source-byte access", {
20042
+ scope: scopeField,
20043
+ kind: exports_external.enum(["auto", "item", "source", "wiki_page", "run", "index", "decision"]).optional().describe("Record kind; auto tries all supported kinds"),
20044
+ id: exports_external.string().describe("Record id, short id, source URI, wiki path, index shard/name, or decision target URI"),
20045
+ include_content: exports_external.boolean().optional().describe("Include generated wiki artifact text when reading wiki pages"),
20046
+ limit: exports_external.number().optional().describe("Maximum related chunks/events to return"),
20047
+ store_path: storePathField
20048
+ }, async ({ scope, kind, id, include_content, limit, store_path }) => {
20049
+ try {
20050
+ const record2 = await getKnowledgeRecord(kind ?? "auto", id, {
20051
+ scope,
20052
+ include_content,
20053
+ limit,
20054
+ store_path
20055
+ });
20056
+ return record2 ? jsonText({ ok: true, ...record2 }) : errorText(`Knowledge record not found: ${id}`);
20057
+ } catch (error48) {
20058
+ return errorText(error48 instanceof Error ? error48.message : String(error48));
20059
+ }
20060
+ });
20061
+ registerTool(server, "knowledge_ingest", "Ingest knowledge source", "Ingest an open-files/S3/file/web source ref or open-files manifest into the derived knowledge catalog", {
20062
+ scope: scopeField,
20063
+ source_ref: exports_external.string().optional().describe("Source reference URI to ingest, e.g. open-files://file/<id>/revision/<rev>"),
20064
+ manifest: exports_external.string().optional().describe("Manifest file path or s3:// URI to ingest"),
20065
+ purpose: exports_external.string().optional().describe("Read-only purpose label, default knowledge_answer")
20066
+ }, async ({ scope, source_ref, manifest, purpose }) => {
20067
+ if (!source_ref && !manifest)
20068
+ return errorText("Missing input. Provide source_ref or manifest.");
20069
+ if (source_ref && manifest)
20070
+ return errorText("Use either source_ref or manifest, not both.");
20071
+ const service = createKnowledgeService({ scope });
20072
+ try {
20073
+ const result = source_ref ? await service.ingestSource(source_ref, purpose) : await service.ingestManifest(manifest);
20074
+ return jsonText({ ok: true, mode: source_ref ? "source" : "manifest", ...result });
20075
+ } catch (error48) {
20076
+ return errorText(error48 instanceof Error ? error48.message : String(error48));
20077
+ }
20078
+ });
20079
+ registerTool(server, "knowledge_build", "Build knowledge answer", "Run the knowledge prompt flow and optionally file the cited answer into generated wiki artifacts after approval", {
20080
+ scope: scopeField,
20081
+ prompt: exports_external.string().describe("Prompt to answer and build durable knowledge from"),
20082
+ limit: exports_external.number().optional().describe("Maximum context results"),
20083
+ semantic: exports_external.boolean().optional().describe("Include vector semantic results"),
20084
+ generate: exports_external.boolean().optional().describe("Call AI SDK text generation; omitted returns a local citation draft"),
20085
+ approve_write: exports_external.boolean().optional().describe("Approve durable wiki filing for this call"),
20086
+ file_answer: exports_external.boolean().optional().describe("Attempt wiki answer filing; writes only with approve_write=true"),
20087
+ model: exports_external.string().optional().describe("Model alias/ref, default configured provider default"),
20088
+ dimensions: exports_external.number().optional().describe("Embedding dimensions for deterministic fake mode"),
20089
+ fake: exports_external.boolean().optional().describe("Use deterministic fake embeddings/generation for local tests")
20090
+ }, async ({ scope, prompt, limit, semantic, generate, approve_write, file_answer, model, dimensions, fake }) => {
20091
+ const service = createKnowledgeService({ scope });
20092
+ try {
20093
+ const result = await service.runPrompt({ prompt, limit, semantic, generate, approveWrite: approve_write, modelRef: model, dimensions, fake });
20094
+ let wiki_file = null;
20095
+ if (file_answer === true || approve_write === true) {
20096
+ wiki_file = await service.fileAnswer({
20097
+ prompt,
20098
+ answer: result.answer,
20099
+ approveWrite: approve_write,
20100
+ limit,
20101
+ semantic,
20102
+ modelRef: model,
20103
+ dimensions,
20104
+ fake
20105
+ });
20106
+ }
20107
+ return jsonText({ ok: true, ...result, wiki_file });
20108
+ } catch (error48) {
20109
+ return errorText(error48 instanceof Error ? error48.message : String(error48));
20110
+ }
20111
+ });
20112
+ registerTool(server, "knowledge_web_search", "Knowledge web search", "Run safety-gated provider-native web search and optionally file snippets as web source refs", {
20113
+ scope: scopeField,
20114
+ query: exports_external.string().describe("Web search query"),
20115
+ limit: exports_external.number().optional().describe("Maximum sources"),
20116
+ provider: exports_external.enum(["openai", "anthropic", "deepseek"]).optional().describe("Provider override"),
20117
+ model: exports_external.string().optional().describe("Model alias/ref"),
20118
+ domains: exports_external.array(exports_external.string()).optional().describe("Allowed domains"),
20119
+ fake: exports_external.boolean().optional().describe("Use deterministic fake web results"),
20120
+ file_results: exports_external.boolean().optional().describe("File web snippets as web source refs")
20121
+ }, async ({ scope, query, limit, provider, model, domains, fake, file_results }) => {
20122
+ const service = createKnowledgeService({ scope });
20123
+ try {
20124
+ return jsonText({ ok: true, ...await service.webSearch({ query, limit, provider, modelRef: model, domains, fake, fileResults: file_results }) });
20125
+ } catch (error48) {
20126
+ return errorText(error48 instanceof Error ? error48.message : String(error48));
20127
+ }
20128
+ });
20129
+ registerTool(server, "knowledge_lint", "Lint knowledge wiki", "Check generated wiki pages for missing citations, stale citations, duplicates, or source issues", {
20130
+ scope: scopeField
20131
+ }, async ({ scope }) => {
20132
+ const service = createKnowledgeService({ scope });
20133
+ try {
20134
+ return jsonText({ ok: true, ...service.lintWiki() });
20135
+ } catch (error48) {
20136
+ return errorText(error48 instanceof Error ? error48.message : String(error48));
20137
+ }
20138
+ });
20139
+ registerTool(server, "knowledge_run_status", "Knowledge run status", "List recent runs or inspect one run ledger with events and provider usage", {
20140
+ scope: scopeField,
20141
+ run_id: exports_external.string().optional().describe("Run id to inspect; omitted lists recent runs"),
20142
+ limit: exports_external.number().optional().describe("Maximum runs or events to return")
20143
+ }, async ({ scope, run_id, limit }) => {
20144
+ const service = createKnowledgeService({ scope });
20145
+ try {
20146
+ if (run_id) {
20147
+ const run = runSnapshot(run_id, { limit, service });
20148
+ return run ? jsonText({ ok: true, kind: "run", ...run }) : errorText(`Run not found: ${run_id}`);
20149
+ }
20150
+ return jsonText({ ok: true, runs: runRows(limit, service) });
20151
+ } catch (error48) {
20152
+ return errorText(error48 instanceof Error ? error48.message : String(error48));
20153
+ }
20154
+ });
20155
+ registerTool(server, "knowledge_storage", "Knowledge storage contract", "Inspect local/S3 artifact storage, source ownership, and hosted/SaaS boundary metadata", {
20156
+ scope: scopeField
20157
+ }, async ({ scope }) => {
20158
+ const service = createKnowledgeService({ scope });
20159
+ try {
20160
+ const validation = service.validateStorage();
20161
+ return jsonText({
20162
+ ok: validation.ok,
20163
+ ...service.storageContract(),
20164
+ validation,
20165
+ remote_contract: service.remoteContract()
20166
+ });
20167
+ } catch (error48) {
20168
+ return errorText(error48 instanceof Error ? error48.message : String(error48));
20169
+ }
20170
+ });
20171
+ registerTool(server, "knowledge_resolve_source", "Resolve knowledge source", "Resolve indexed source chunks through the read-only open-files/source boundary with citation evidence", {
20172
+ source_ref: exports_external.string().describe("Source reference URI, preferably open-files://..."),
20173
+ purpose: exports_external.string().optional().describe("Read-only purpose label, default knowledge_answer"),
20174
+ limit: exports_external.number().optional().describe("Maximum chunks to return, default 10"),
20175
+ scope: scopeField
20176
+ }, async ({ source_ref, purpose, limit, scope }) => {
20177
+ const service = createKnowledgeService({ scope });
20178
+ try {
20179
+ const result = await service.resolveSource(source_ref, { purpose, limit });
20180
+ return jsonText({ ok: true, ...result });
20181
+ } catch (error48) {
20182
+ return errorText(error48 instanceof Error ? error48.message : String(error48));
20183
+ }
20184
+ });
19535
20185
  registerTool(server, "ok_web_search", "Provider web search", "Run safety-gated provider-native web search and return citations/sources", {
19536
20186
  scope: scopeField,
19537
20187
  query: exports_external.string().describe("Web search query"),