import {
  QmdClient
} from "./chunk-WSZIHQBK.js";
import {
  isSafeRouteNamespace
} from "./chunk-2LGMW3DJ.js";
import {
  launchProcess
} from "./chunk-OR64ZGRZ.js";
import {
  StorageManager
} from "./chunk-DCE6SQLA.js";
import {
  log
} from "./chunk-2ODBA7MQ.js";

// src/search/noop-backend.ts
var NoopSearchBackend = class {
  async probe() {
    return false;
  }
  isAvailable() {
    return false;
  }
  debugStatus() {
    return "backend=noop";
  }
  async search(_query, _collection, _maxResults, _options, _execution) {
    return [];
  }
  async searchGlobal(_query, _maxResults, _execution) {
    return [];
  }
  async bm25Search(_query, _collection, _maxResults, _execution) {
    return [];
  }
  async vectorSearch(_query, _collection, _maxResults, _execution) {
    return [];
  }
  async hybridSearch(_query, _collection, _maxResults, _execution) {
    return [];
  }
  async update(_execution) {
  }
  async updateCollection(_collection, _execution) {
  }
  async embed() {
  }
  async embedCollection(_collection) {
  }
  async ensureCollection(_memoryDir) {
    return "skipped";
  }
};

// src/search/document-scanner.ts
import path from "path";
import { readdir, readFile } from "fs/promises";
function parseFrontmatter(raw) {
  const normalized = raw.replace(/\r\n/g, "\n");
  const match = normalized.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
  if (!match) return null;
  const fmBlock = match[1];
  const body = (match[2] ?? "").trim();
  const data = {};
  for (const line of fmBlock.split("\n")) {
    const colonIdx = line.indexOf(":");
    if (colonIdx === -1) continue;
    const key = line.slice(0, colonIdx).trim();
    const value = line.slice(colonIdx + 1).trim();
    data[key] = value;
  }
  return { data, body };
}
async function scanDir(dir) {
  const docs = [];
  try {
    const entries = await readdir(dir, { withFileTypes: true });
    for (const entry of entries) {
      const fullPath = path.join(dir, entry.name);
      if (entry.isDirectory()) {
        const sub = await scanDir(fullPath);
        docs.push(...sub);
      } else if (entry.name.endsWith(".md")) {
        try {
          const raw = await readFile(fullPath, "utf-8");
          const parsed = parseFrontmatter(raw);
          const body = parsed ? parsed.body : raw.trim();
          const docid = parsed?.data.id || path.basename(entry.name, ".md");
          docs.push({
            docid,
            path: fullPath,
            content: body,
            snippet: body.slice(0, 200)
          });
        } catch {
        }
      }
    }
  } catch {
  }
  return docs;
}
async function scanMemoryDir(memoryDir) {
  const factsDir = path.join(memoryDir, "facts");
  const correctionsDir = path.join(memoryDir, "corrections");
  const proceduresDir = path.join(memoryDir, "procedures");
  const reasoningTracesDir = path.join(memoryDir, "reasoning-traces");
  const [facts, corrections, procedures, reasoningTraces] = await Promise.all([
    scanDir(factsDir),
    scanDir(correctionsDir),
    scanDir(proceduresDir),
    scanDir(reasoningTracesDir)
  ]);
  return [...facts, ...corrections, ...procedures, ...reasoningTraces];
}

// src/search/lancedb-backend.ts
var LanceDbBackend = class {
  dbPath;
  collection;
  embedHelper;
  memoryDir;
  embeddingDimension;
  available = false;
  db = null;
  lanceModule = null;
  constructor(opts) {
    this.dbPath = opts.dbPath;
    this.collection = opts.collection;
    this.embedHelper = opts.embedHelper;
    this.memoryDir = opts.memoryDir;
    this.embeddingDimension = opts.embeddingDimension;
  }
  async probe() {
    try {
      await this.ensureDb();
      this.available = true;
      return true;
    } catch (err) {
      log.debug(`LanceDbBackend probe failed: ${err}`);
      this.available = false;
      return false;
    }
  }
  isAvailable() {
    return this.available;
  }
  debugStatus() {
    return `backend=lancedb available=${this.available} dbPath=${this.dbPath}`;
  }
  async search(query, _collection, maxResults, _options, _execution) {
    return this.hybridSearch(query, _collection, maxResults);
  }
  async searchGlobal(query, maxResults, _execution) {
    const limit = maxResults ?? 10;
    if (!this.available) return [];
    try {
      const db = await this.ensureDb();
      const tableNames = await db.tableNames();
      const allResults = [];
      for (const name of tableNames) {
        try {
          const table = await db.openTable(name);
          const results = await this.searchTable(table, query, "hybrid", limit);
          allResults.push(...results);
        } catch {
        }
      }
      allResults.sort((a, b) => b.score - a.score);
      return allResults.slice(0, limit);
    } catch (err) {
      log.debug(`LanceDbBackend searchGlobal failed: ${err}`);
      return [];
    }
  }
  async bm25Search(query, collection, maxResults, _execution) {
    const table = await this.ensureTableForCollection(collection ?? this.collection);
    if (!table) return [];
    return this.searchTable(table, query, "fts", maxResults ?? 10);
  }
  async vectorSearch(query, collection, maxResults, _execution) {
    const table = await this.ensureTableForCollection(collection ?? this.collection);
    if (!table) return [];
    return this.searchTable(table, query, "vector", maxResults ?? 10);
  }
  async hybridSearch(query, collection, maxResults, _execution) {
    const table = await this.ensureTableForCollection(collection ?? this.collection);
    if (!table) return [];
    return this.searchTable(table, query, "hybrid", maxResults ?? 10);
  }
  async update(execution) {
    await this.updateCollection(this.collection, execution);
  }
  async updateCollection(collection, _execution) {
    const table = await this.ensureTableForCollection(collection);
    if (!table) return;
    const docs = await scanMemoryDir(this.memoryDir);
    if (docs.length === 0) {
      try {
        const db = await this.ensureDb();
        await db.dropTable(collection).catch(() => {
        });
        if (collection === this.collection) this.table = null;
      } catch {
      }
      return;
    }
    const rows = docs.map((d) => ({
      docid: d.docid,
      path: d.path,
      content: d.content,
      snippet: d.snippet,
      vector: new Array(this.embeddingDimension).fill(0)
    }));
    try {
      const db = await this.ensureDb();
      await db.dropTable(collection).catch(() => {
      });
      if (collection === this.collection) this.table = null;
      const newTable = await db.createTable(collection, rows);
      try {
        await newTable.createIndex("content", { config: this.lanceIndex.fts() });
      } catch {
      }
      if (collection === this.collection) this.table = newTable;
    } catch (err) {
      log.debug(`LanceDbBackend update failed: ${err}`);
    }
  }
  async embed() {
    await this.embedCollection(this.collection);
  }
  async embedCollection(collection) {
    if (!this.embedHelper.isAvailable()) return;
    const table = await this.ensureTableForCollection(collection);
    if (!table) return;
    try {
      const allRows = await table.query().select(["docid", "content", "vector"]).toArray();
      const needsEmbed = allRows.filter((row) => {
        const vec = row.vector;
        if (!vec || typeof vec !== "object") return true;
        const arr = Array.from(vec);
        return arr.length === 0 || arr.every((v) => v === 0);
      });
      if (needsEmbed.length === 0) return;
      const texts = needsEmbed.map((row) => row.content);
      const vectors = await this.embedHelper.embedBatch(texts);
      for (let i = 0; i < needsEmbed.length; i++) {
        const vec = vectors[i];
        if (!vec) continue;
        const docid = needsEmbed[i].docid;
        await table.update({ where: `docid = '${docid.replace(/'/g, "''")}'`, values: { vector: vec } });
      }
    } catch (err) {
      log.debug(`LanceDbBackend embed failed: ${err}`);
    }
  }
  async ensureCollection(_memoryDir) {
    try {
      await this.ensureTable();
      return "present";
    } catch {
      return "missing";
    }
  }
  table = null;
  get lanceIndex() {
    return this.lanceModule.Index ?? this.lanceModule.default?.Index;
  }
  async ensureDb() {
    if (this.db) return this.db;
    if (!this.lanceModule) {
      this.lanceModule = await import("@lancedb/lancedb");
    }
    const connect = this.lanceModule.connect ?? this.lanceModule.default?.connect;
    this.db = await connect(this.dbPath);
    return this.db;
  }
  async ensureTableForCollection(collection) {
    if (collection === this.collection) return this.ensureTable();
    const db = await this.ensureDb();
    const tables = await db.tableNames();
    if (tables.includes(collection)) {
      return await db.openTable(collection);
    }
    const emptyRow = {
      docid: "__placeholder__",
      path: "",
      content: "",
      snippet: "",
      vector: new Array(this.embeddingDimension).fill(0)
    };
    const newTable = await db.createTable(collection, [emptyRow]);
    try {
      await newTable.createIndex("content", { config: this.lanceIndex.fts() });
    } catch {
    }
    try {
      await newTable.delete("docid = '__placeholder__'");
    } catch {
    }
    return newTable;
  }
  async ensureTable() {
    if (this.table) return this.table;
    const db = await this.ensureDb();
    const tables = await db.tableNames();
    if (tables.includes(this.collection)) {
      this.table = await db.openTable(this.collection);
      return this.table;
    }
    const emptyRow = {
      docid: "__placeholder__",
      path: "",
      content: "",
      snippet: "",
      vector: new Array(this.embeddingDimension).fill(0)
    };
    this.table = await db.createTable(this.collection, [emptyRow]);
    try {
      await this.table.createIndex("content", { config: this.lanceIndex.fts() });
    } catch {
    }
    try {
      await this.table.delete("docid = '__placeholder__'");
    } catch {
    }
    return this.table;
  }
  async searchTable(table, query, mode, limit) {
    try {
      if (mode === "fts") {
        const results = await table.search(query, "fts").limit(limit).toArray();
        return this.mapRows(results);
      }
      if (mode === "vector") {
        const vec2 = await this.embedHelper.embed(query);
        if (!vec2) {
          const results2 = await table.search(query, "fts").limit(limit).toArray();
          return this.mapRows(results2);
        }
        const results = await table.search(vec2).limit(limit).toArray();
        return this.mapRows(results);
      }
      const vec = await this.embedHelper.embed(query);
      if (!vec) {
        const results = await table.search(query, "fts").limit(limit).toArray();
        return this.mapRows(results);
      }
      try {
        const results = await table.search(query, "hybrid").vector(vec).limit(limit).toArray();
        return this.mapRows(results);
      } catch {
        const results = await table.search(vec).limit(limit).toArray();
        return this.mapRows(results);
      }
    } catch (err) {
      log.debug(`LanceDbBackend search (${mode}) failed: ${err}`);
      return [];
    }
  }
  mapRows(rows) {
    return (rows ?? []).filter((row) => row.docid && row.docid !== "__placeholder__").map((row) => ({
      docid: row.docid ?? "",
      path: row.path ?? "",
      snippet: row.snippet ?? row.content?.slice(0, 200) ?? "",
      score: row._relevance_score ?? (row._distance != null ? 1 / (1 + (row._distance ?? 0)) : 0.5)
    }));
  }
};

// src/search/meilisearch-backend.ts
var MeilisearchBackend = class {
  host;
  apiKey;
  collection;
  timeoutMs;
  autoIndex;
  memoryDir;
  available = false;
  client = null;
  meiliModule = null;
  constructor(opts) {
    this.host = opts.host;
    this.apiKey = opts.apiKey;
    this.collection = opts.collection;
    this.timeoutMs = opts.timeoutMs ?? 3e4;
    this.autoIndex = opts.autoIndex ?? false;
    this.memoryDir = opts.memoryDir;
  }
  async probe() {
    try {
      const client = await this.ensureClient();
      await client.health();
      this.available = true;
      return true;
    } catch (err) {
      log.debug(`MeilisearchBackend probe failed: ${err}`);
      this.available = false;
      return false;
    }
  }
  isAvailable() {
    return this.available;
  }
  debugStatus() {
    return `backend=meilisearch available=${this.available} host=${this.host}`;
  }
  async search(query, collection, maxResults, _options, _execution) {
    try {
      return await this.doSearch(query, maxResults ?? 10, { hybrid: { semanticRatio: 0.5, embedder: "default" } }, collection, true);
    } catch {
      return this.bm25Search(query, collection, maxResults);
    }
  }
  async searchGlobal(query, maxResults, _execution) {
    const limit = maxResults ?? 10;
    if (!this.available) return [];
    try {
      const client = await this.ensureClient();
      const indexes = await client.getIndexes();
      const queries = (indexes.results ?? []).map((idx) => ({
        indexUid: idx.uid,
        q: query,
        limit,
        showRankingScore: true
      }));
      if (queries.length === 0) return [];
      const multiResult = await client.multiSearch({ queries });
      const allResults = [];
      for (const result of multiResult.results ?? []) {
        allResults.push(...this.mapHits(result.hits ?? []));
      }
      allResults.sort((a, b) => b.score - a.score);
      return allResults.slice(0, limit);
    } catch (err) {
      log.debug(`MeilisearchBackend searchGlobal failed: ${err}`);
      return [];
    }
  }
  async bm25Search(query, collection, maxResults, _execution) {
    return this.doSearch(query, maxResults ?? 10, void 0, collection);
  }
  async vectorSearch(query, collection, maxResults, _execution) {
    return this.doSearch(query, maxResults ?? 10, { hybrid: { semanticRatio: 1, embedder: "default" } }, collection);
  }
  async hybridSearch(query, collection, maxResults, _execution) {
    return this.doSearch(query, maxResults ?? 10, { hybrid: { semanticRatio: 0.5, embedder: "default" } }, collection);
  }
  async update(execution) {
    await this.updateCollection(this.collection, execution);
  }
  async updateCollection(collection, _execution) {
    if (!this.autoIndex || !this.memoryDir) return;
    if (!this.available) return;
    try {
      const client = await this.ensureClient();
      const docs = await scanMemoryDir(this.memoryDir);
      const index = client.index(collection);
      const meilDocs = docs.map((d) => ({
        id: d.docid,
        path: d.path,
        content: d.content,
        snippet: d.snippet
      }));
      const addTask = await index.addDocuments(meilDocs, { primaryKey: "id" });
      await client.waitForTask(addTask.taskUid, { timeOutMs: this.timeoutMs });
      const currentIds = new Set(docs.map((d) => d.docid));
      try {
        const PAGE_SIZE = 1e3;
        let offset = 0;
        let staleIds = [];
        let hasMore = true;
        while (hasMore) {
          const page = await index.getDocuments({ limit: PAGE_SIZE, offset, fields: ["id"] });
          const results = page.results ?? [];
          for (const doc of results) {
            const id = doc.id;
            if (!currentIds.has(id)) staleIds.push(id);
          }
          offset += results.length;
          hasMore = results.length === PAGE_SIZE;
        }
        if (staleIds.length > 0) {
          const delTask = await index.deleteDocuments(staleIds);
          await client.waitForTask(delTask.taskUid, { timeOutMs: this.timeoutMs });
        }
      } catch {
      }
    } catch (err) {
      log.debug(`MeilisearchBackend update failed: ${err}`);
    }
  }
  async embed() {
  }
  async embedCollection(collection) {
  }
  async ensureCollection(_memoryDir) {
    if (!this.available) return "skipped";
    try {
      const client = await this.ensureClient();
      try {
        await client.getIndex(this.collection);
        return "present";
      } catch {
        await client.createIndex(this.collection, { primaryKey: "id" });
        return "present";
      }
    } catch {
      return "skipped";
    }
  }
  async ensureClient() {
    if (this.client) return this.client;
    if (!this.meiliModule) {
      this.meiliModule = await import("meilisearch");
    }
    const MeiliSearch = this.meiliModule.MeiliSearch ?? this.meiliModule.default?.MeiliSearch;
    this.client = new MeiliSearch({
      host: this.host,
      apiKey: this.apiKey,
      timeout: this.timeoutMs
    });
    return this.client;
  }
  async doSearch(query, limit, extra, collection, rethrow = false) {
    if (!this.available) return [];
    try {
      const client = await this.ensureClient();
      const index = client.index(collection ?? this.collection);
      const result = await index.search(query, { limit, showRankingScore: true, ...extra });
      return this.mapHits(result.hits ?? []);
    } catch (err) {
      log.debug(`MeilisearchBackend search failed: ${err}`);
      if (rethrow) throw err;
      return [];
    }
  }
  mapHits(hits) {
    return hits.map((hit) => ({
      docid: hit.id ?? "",
      path: hit.path ?? "",
      snippet: hit._formatted?.content ?? hit.snippet ?? hit.content?.slice(0, 200) ?? "",
      score: hit._rankingScore ?? 0.5
    }));
  }
};

// src/search/orama-backend.ts
import path2 from "path";
import { mkdir, readdir as readdir2, readFile as readFile2, writeFile } from "fs/promises";
var OramaBackend = class {
  dbPath;
  collection;
  embedHelper;
  memoryDir;
  embeddingDimension;
  available = false;
  db = null;
  oramaModule = null;
  persistModule = null;
  constructor(opts) {
    this.dbPath = opts.dbPath;
    this.collection = opts.collection;
    this.embedHelper = opts.embedHelper;
    this.memoryDir = opts.memoryDir;
    this.embeddingDimension = opts.embeddingDimension;
  }
  async probe() {
    try {
      await this.ensureModules();
      await this.ensureDb();
      this.available = true;
      return true;
    } catch (err) {
      log.debug(`OramaBackend probe failed: ${err}`);
      this.available = false;
      return false;
    }
  }
  isAvailable() {
    return this.available;
  }
  debugStatus() {
    return `backend=orama available=${this.available} dbPath=${this.dbPath}`;
  }
  async search(query, _collection, maxResults, _options, _execution) {
    return this.hybridSearch(query, _collection, maxResults);
  }
  async searchGlobal(query, maxResults, _execution) {
    const limit = maxResults ?? 10;
    if (!this.available) return [];
    try {
      const files = await this.listDbFiles();
      const allResults = [];
      for (const file of files) {
        const db = await this.loadDbFromFile(file);
        if (!db) continue;
        const results = await this.searchDb(db, query, "hybrid", limit);
        allResults.push(...results);
      }
      allResults.sort((a, b) => b.score - a.score);
      return allResults.slice(0, limit);
    } catch (err) {
      log.debug(`OramaBackend searchGlobal failed: ${err}`);
      return [];
    }
  }
  async bm25Search(query, collection, maxResults, _execution) {
    const db = await this.ensureDbForCollection(collection ?? this.collection);
    if (!db) return [];
    return this.searchDb(db, query, "fulltext", maxResults ?? 10);
  }
  async vectorSearch(query, collection, maxResults, _execution) {
    const db = await this.ensureDbForCollection(collection ?? this.collection);
    if (!db) return [];
    return this.searchDb(db, query, "vector", maxResults ?? 10);
  }
  async hybridSearch(query, collection, maxResults, _execution) {
    const db = await this.ensureDbForCollection(collection ?? this.collection);
    if (!db) return [];
    return this.searchDb(db, query, "hybrid", maxResults ?? 10);
  }
  async update(execution) {
    await this.updateCollection(this.collection, execution);
  }
  async updateCollection(collection, _execution) {
    const db = await this.ensureDbForCollection(collection);
    if (!db) return;
    const { search: oramaSearch, insert, remove, count } = this.oramaModule;
    const docs = await scanMemoryDir(this.memoryDir);
    const docMap = new Map(docs.map((d) => [d.docid, d]));
    const { update: oramaUpdate } = this.oramaModule;
    const existingDocs = /* @__PURE__ */ new Map();
    const existingCount = await count(db);
    if (existingCount > 0) {
      const allHits = await oramaSearch(db, { term: "", limit: existingCount + 100 });
      for (const hit of allHits.hits) {
        if (!docMap.has(hit.document.id)) {
          await remove(db, hit.id);
        } else {
          existingDocs.set(hit.document.id, {
            internalId: hit.id,
            vector: hit.document.vector
          });
        }
      }
    }
    for (const doc of docs) {
      const existing = existingDocs.get(doc.docid);
      if (existing) {
        const payload = {
          id: doc.docid,
          path: doc.path,
          content: doc.content,
          snippet: doc.snippet
        };
        if (existing.vector && existing.vector.length > 0) {
          payload.vector = existing.vector;
        }
        try {
          await oramaUpdate(db, existing.internalId, payload);
        } catch {
        }
      } else {
        try {
          await insert(db, {
            id: doc.docid,
            path: doc.path,
            content: doc.content,
            snippet: doc.snippet
          });
        } catch {
        }
      }
    }
    await this.persistDbForCollection(db, collection);
  }
  async embed() {
    await this.embedCollection(this.collection);
  }
  async embedCollection(collection) {
    if (!this.embedHelper.isAvailable()) return;
    const db = await this.ensureDbForCollection(collection);
    if (!db) return;
    const { search: oramaSearch, update: oramaUpdate, count } = this.oramaModule;
    const existingCount = await count(db);
    if (existingCount === 0) return;
    const allHits = await oramaSearch(db, { term: "", limit: existingCount + 100 });
    const needsEmbed = allHits.hits.filter((h) => !h.document.vector || h.document.vector.length === 0);
    if (needsEmbed.length === 0) return;
    const texts = needsEmbed.map((h) => h.document.content);
    const vectors = await this.embedHelper.embedBatch(texts);
    for (let i = 0; i < needsEmbed.length; i++) {
      const vec = vectors[i];
      if (!vec) continue;
      const doc = needsEmbed[i].document;
      await oramaUpdate(db, needsEmbed[i].id, {
        id: doc.id,
        path: doc.path,
        content: doc.content,
        snippet: doc.snippet,
        vector: vec
      });
    }
    await this.persistDbForCollection(db, collection);
  }
  async ensureCollection(_memoryDir) {
    try {
      await this.ensureModules();
      await this.ensureDb();
      return "present";
    } catch {
      return "missing";
    }
  }
  async ensureModules() {
    if (this.oramaModule && this.persistModule) return;
    this.oramaModule = await import("@orama/orama");
    this.persistModule = await import("@orama/plugin-data-persistence");
  }
  async ensureDb() {
    if (this.db) return this.db;
    await this.ensureModules();
    await mkdir(this.dbPath, { recursive: true });
    const filePath = this.dbFilePath(this.collection);
    try {
      const raw = await readFile2(filePath, "utf-8");
      this.db = await this.persistModule.restore("json", raw);
      return this.db;
    } catch {
    }
    const { create } = this.oramaModule;
    const schema = {
      id: "string",
      path: "string",
      content: "string",
      snippet: "string"
    };
    if (this.embedHelper.isAvailable()) {
      schema.vector = `vector[${this.embeddingDimension}]`;
    }
    this.db = await create({ schema });
    return this.db;
  }
  async ensureDbForCollection(collection) {
    if (collection === this.collection) return this.ensureDb();
    await this.ensureModules();
    await mkdir(this.dbPath, { recursive: true });
    const filePath = this.dbFilePath(collection);
    try {
      const raw = await readFile2(filePath, "utf-8");
      return await this.persistModule.restore("json", raw);
    } catch {
    }
    const { create } = this.oramaModule;
    const schema = {
      id: "string",
      path: "string",
      content: "string",
      snippet: "string"
    };
    if (this.embedHelper.isAvailable()) {
      schema.vector = `vector[${this.embeddingDimension}]`;
    }
    return await create({ schema });
  }
  async persistDbForCollection(db, collection) {
    const data = await this.persistModule.persist(db, "json");
    const filePath = this.dbFilePath(collection);
    await mkdir(path2.dirname(filePath), { recursive: true });
    await writeFile(filePath, data, "utf-8");
  }
  dbFilePath(collection) {
    return path2.join(this.dbPath, `${collection}.msp`);
  }
  async listDbFiles() {
    try {
      const entries = await readdir2(this.dbPath);
      return entries.filter((e) => e.endsWith(".msp")).map((e) => path2.join(this.dbPath, e));
    } catch {
      return [];
    }
  }
  async loadDbFromFile(filePath) {
    try {
      await this.ensureModules();
      const raw = await readFile2(filePath, "utf-8");
      return await this.persistModule.restore("json", raw);
    } catch {
      return null;
    }
  }
  async searchDb(db, query, mode, limit) {
    const { search: oramaSearch } = this.oramaModule;
    try {
      let searchParams;
      if (mode === "fulltext") {
        searchParams = { term: query, limit };
      } else if (mode === "vector") {
        const vec = await this.embedHelper.embed(query);
        if (!vec) {
          searchParams = { term: query, limit };
        } else {
          searchParams = { mode: "vector", vector: { value: vec, property: "vector" }, limit };
        }
      } else {
        const vec = await this.embedHelper.embed(query);
        if (!vec) {
          searchParams = { term: query, limit };
        } else {
          searchParams = { mode: "hybrid", term: query, vector: { value: vec, property: "vector" }, limit };
        }
      }
      const result = await oramaSearch(db, searchParams);
      return (result.hits ?? []).map((hit) => ({
        docid: hit.document?.id ?? "",
        path: hit.document?.path ?? "",
        snippet: hit.document?.snippet ?? hit.document?.content?.slice(0, 200) ?? "",
        score: hit.score ?? 0
      }));
    } catch (err) {
      log.debug(`OramaBackend search (${mode}) failed: ${err}`);
      return [];
    }
  }
};

// src/conversation-index/indexer.ts
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
import path3 from "path";
function sanitizeSessionKey(sessionKey) {
  const raw = typeof sessionKey === "string" && sessionKey.trim().length > 0 ? sessionKey : "unknown-session";
  return raw.toLowerCase().replace(/[^a-z0-9._-]+/g, "_").slice(0, 200);
}
async function writeConversationChunks(rootDir, chunks) {
  const written = [];
  for (const c of chunks) {
    const safe = sanitizeSessionKey(c.sessionKey);
    const date = c.startTs.slice(0, 10);
    const dir = path3.join(rootDir, safe, date);
    await mkdir2(dir, { recursive: true });
    const fp = path3.join(dir, `${c.id}.md`);
    const content = `---
kind: conversation_chunk
sessionKey: ${c.sessionKey}
startTs: ${c.startTs}
endTs: ${c.endTs}
---

` + c.text + "\n";
    await writeFile2(fp, content, "utf-8");
    written.push(fp);
  }
  return written;
}
async function upsertConversationChunksFailOpen(adapter, chunks) {
  if (!adapter) {
    return { upserted: 0, skipped: true, reason: "adapter-unavailable" };
  }
  try {
    const upserted = await adapter.upsertChunks(chunks);
    return { upserted, skipped: false };
  } catch (err) {
    log.debug(`conversation index FAISS upsert failed (fail-open): ${err}`);
    return { upserted: 0, skipped: true, reason: "adapter-error" };
  }
}
async function rebuildConversationChunksFailOpen(adapter, chunks) {
  if (!adapter) {
    return { rebuilt: 0, skipped: true, reason: "adapter-unavailable" };
  }
  try {
    const rebuilt = await adapter.rebuildChunks(chunks);
    return { rebuilt, skipped: false };
  } catch (err) {
    log.debug(`conversation index FAISS rebuild failed (fail-open): ${err}`);
    return { rebuilt: 0, skipped: true, reason: "adapter-error" };
  }
}

// src/search/factory.ts
import path5 from "path";

// src/search/remote-backend.ts
var RemoteSearchBackend = class {
  baseUrl;
  apiKey;
  timeoutMs;
  available = false;
  constructor(opts) {
    let url = opts.baseUrl;
    while (url.endsWith("/")) url = url.slice(0, -1);
    this.baseUrl = url;
    this.apiKey = opts.apiKey;
    this.timeoutMs = opts.timeoutMs ?? 3e4;
  }
  async probe() {
    try {
      const res = await fetch(`${this.baseUrl}/health`, {
        method: "GET",
        headers: this.headers(),
        signal: AbortSignal.timeout(this.timeoutMs)
      });
      this.available = res.ok;
      return this.available;
    } catch (err) {
      log.debug(`RemoteSearchBackend probe failed: ${err}`);
      this.available = false;
      return false;
    }
  }
  isAvailable() {
    return this.available;
  }
  debugStatus() {
    return `backend=remote available=${this.available} baseUrl=${this.baseUrl}`;
  }
  async search(query, collection, maxResults, _options, execution) {
    return this.post("/search/deep", { query, collection, maxResults }, execution);
  }
  async searchGlobal(query, maxResults, execution) {
    return this.post("/search/deep", { query, maxResults }, execution);
  }
  async bm25Search(query, collection, maxResults, execution) {
    return this.post("/search/bm25", { query, collection, maxResults }, execution);
  }
  async vectorSearch(query, collection, maxResults, execution) {
    return this.post("/search/vector", { query, collection, maxResults }, execution);
  }
  async hybridSearch(query, collection, maxResults, execution) {
    return this.post("/search/hybrid", { query, collection, maxResults }, execution);
  }
  async update(_execution) {
  }
  async updateCollection(_collection, _execution) {
  }
  async embed() {
  }
  async embedCollection(_collection) {
  }
  async ensureCollection(_memoryDir) {
    return "skipped";
  }
  headers() {
    const h = { "Content-Type": "application/json" };
    if (this.apiKey) {
      h["Authorization"] = `Bearer ${this.apiKey}`;
    }
    return h;
  }
  async post(endpoint, body, execution) {
    if (!this.available) return [];
    try {
      const res = await fetch(`${this.baseUrl}${endpoint}`, {
        method: "POST",
        headers: this.headers(),
        body: JSON.stringify(body),
        signal: execution?.signal ? AbortSignal.any([execution.signal, AbortSignal.timeout(this.timeoutMs)]) : AbortSignal.timeout(this.timeoutMs)
      });
      if (!res.ok) {
        log.debug(`RemoteSearchBackend ${endpoint} returned ${res.status}`);
        return [];
      }
      const data = await res.json();
      if (!Array.isArray(data)) return [];
      return data;
    } catch (err) {
      log.debug(`RemoteSearchBackend ${endpoint} failed: ${err}`);
      return [];
    }
  }
};

// src/search/embed-helper.ts
var DEFAULT_OPENAI_MODEL = "text-embedding-3-small";
var EmbedHelper = class {
  // undefined = not yet resolved
  constructor(config) {
    this.config = config;
  }
  config;
  provider;
  /**
   * Whether an embedding provider is available.
   * Resolves the provider on first call.
   */
  isAvailable() {
    if (this.provider === void 0) {
      this.provider = this.resolveProvider();
    }
    return this.provider !== null;
  }
  /**
   * Embed a single text string. Returns null if no provider is available.
   */
  async embed(text) {
    const provider = this.getProvider();
    if (!provider) return null;
    return this.callEmbed(text, provider);
  }
  /**
   * Embed a batch of texts. Returns an array parallel to input; entries are null on failure.
   */
  async embedBatch(texts, batchSize = 32) {
    const provider = this.getProvider();
    if (!provider) return texts.map(() => null);
    const results = new Array(texts.length).fill(null);
    for (let i = 0; i < texts.length; i += batchSize) {
      const batch = texts.slice(i, i + batchSize);
      const batchResults = await Promise.all(batch.map((t) => this.callEmbed(t, provider)));
      for (let j = 0; j < batchResults.length; j++) {
        results[i + j] = batchResults[j];
      }
    }
    return results;
  }
  getProvider() {
    if (this.provider === void 0) {
      this.provider = this.resolveProvider();
    }
    return this.provider;
  }
  resolveProvider() {
    if (!this.config.embeddingFallbackEnabled) return null;
    const preferred = this.config.embeddingFallbackProvider;
    const providers = preferred === "auto" ? ["openai", "local"] : [preferred];
    for (const p of providers) {
      if (p === "openai" && this.config.openaiApiKey) {
        const baseUrl = this.config.openaiBaseUrl ?? "https://api.openai.com/v1";
        return {
          type: "openai",
          model: DEFAULT_OPENAI_MODEL,
          endpoint: `${baseUrl.replace(/\/$/, "")}/embeddings`,
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${this.config.openaiApiKey}`
          }
        };
      }
      if (p === "local" && this.config.localLlmEnabled && this.config.localLlmUrl) {
        const base = this.config.localLlmUrl.replace(/\/$/, "");
        const endpoint = /\/v1$/i.test(base) ? `${base}/embeddings` : `${base}/v1/embeddings`;
        const headers = {
          "Content-Type": "application/json",
          ...this.config.localLlmHeaders ?? {}
        };
        if (this.config.localLlmApiKey && this.config.localLlmAuthHeader !== false) {
          headers.Authorization = `Bearer ${this.config.localLlmApiKey}`;
        }
        return {
          type: "local",
          model: this.config.localLlmModel || DEFAULT_OPENAI_MODEL,
          endpoint,
          headers
        };
      }
    }
    return null;
  }
  async callEmbed(input, provider) {
    try {
      const res = await fetch(provider.endpoint, {
        method: "POST",
        headers: provider.headers,
        body: JSON.stringify({
          model: provider.model,
          input: input.slice(0, 8e3),
          encoding_format: "float"
        }),
        signal: AbortSignal.timeout(3e4)
      });
      if (!res.ok) {
        log.debug(`EmbedHelper request failed: ${provider.type} ${res.status}`);
        return null;
      }
      const payload = await res.json();
      const vector = payload?.data?.[0]?.embedding;
      if (!Array.isArray(vector)) return null;
      return vector.map((n) => {
        const v = Number(n);
        return Number.isFinite(v) ? v : 0;
      });
    } catch (err) {
      log.debug(`EmbedHelper error: ${err}`);
      return null;
    }
  }
};

// src/conversation-index/faiss-adapter.ts
import { fileURLToPath } from "url";
import path4 from "path";
var FaissAdapterError = class extends Error {
  constructor(message, code) {
    super(message);
    this.code = code;
    this.name = "FaissAdapterError";
  }
  code;
};
function parseSidecarManifest(result) {
  const manifest = result.manifest;
  if (!manifest || typeof manifest.version !== "number" || typeof manifest.modelId !== "string" || typeof manifest.normalizedModelId !== "string" || typeof manifest.dimension !== "number" || typeof manifest.chunkCount !== "number" || typeof manifest.updatedAt !== "string" || typeof manifest.lastSuccessfulRebuildAt !== "string") {
    return void 0;
  }
  return {
    version: manifest.version,
    modelId: manifest.modelId,
    normalizedModelId: manifest.normalizedModelId,
    dimension: manifest.dimension,
    chunkCount: manifest.chunkCount,
    updatedAt: manifest.updatedAt,
    lastSuccessfulRebuildAt: manifest.lastSuccessfulRebuildAt
  };
}
function resolveDefaultFaissScriptPath(fromModuleUrl = import.meta.url) {
  const currentFile = fileURLToPath(fromModuleUrl);
  const moduleDir = path4.dirname(currentFile);
  if (moduleDir.endsWith(`${path4.sep}conversation-index`)) {
    return path4.resolve(moduleDir, "..", "..", "scripts", "faiss_index.py");
  }
  return path4.resolve(moduleDir, "..", "scripts", "faiss_index.py");
}
var FaissConversationIndexAdapter = class {
  constructor(config) {
    this.config = config;
    this.pythonBin = config.pythonBin && config.pythonBin.trim().length > 0 ? config.pythonBin.trim() : "python3";
    this.scriptPath = config.scriptPath && config.scriptPath.trim().length > 0 ? config.scriptPath.trim() : resolveDefaultFaissScriptPath();
    this.indexPath = path4.isAbsolute(config.indexDir) ? config.indexDir : path4.join(config.memoryDir, config.indexDir);
    this.spawnFn = config.spawnFn ?? launchProcess;
  }
  config;
  pythonBin;
  scriptPath;
  indexPath;
  spawnFn;
  async upsertChunks(chunks) {
    if (this.config.maxBatchSize <= 0) return 0;
    let totalUpserted = 0;
    for (let offset = 0; offset < chunks.length; offset += this.config.maxBatchSize) {
      const batch = chunks.slice(offset, offset + this.config.maxBatchSize);
      if (batch.length === 0) continue;
      const payload = {
        modelId: this.config.modelId,
        indexPath: this.indexPath,
        chunks: batch.map((chunk) => ({
          id: chunk.id,
          sessionKey: chunk.sessionKey,
          text: chunk.text,
          startTs: chunk.startTs,
          endTs: chunk.endTs
        }))
      };
      const result = await this.runCommand("upsert", payload, this.config.upsertTimeoutMs);
      const upserted = result.upserted;
      if (typeof upserted !== "number" || !Number.isFinite(upserted)) {
        throw new FaissAdapterError("FAISS sidecar produced malformed upsert response", "malformed_output");
      }
      totalUpserted += Math.max(0, Math.floor(upserted));
    }
    return totalUpserted;
  }
  async searchChunks(query, topK) {
    const requestedTopK = Number.isFinite(topK) ? Math.floor(topK) : 0;
    const boundedTopK = this.config.maxSearchK > 0 ? Math.max(0, Math.min(requestedTopK, this.config.maxSearchK)) : 0;
    if (boundedTopK <= 0 || query.trim().length === 0) return [];
    const payload = {
      modelId: this.config.modelId,
      indexPath: this.indexPath,
      query,
      topK: boundedTopK
    };
    const result = await this.runCommand("search", payload, this.config.searchTimeoutMs);
    if (!Array.isArray(result.results)) {
      throw new FaissAdapterError("FAISS sidecar produced malformed search response", "malformed_output");
    }
    const rows = result.results;
    return rows.filter(
      (row) => row && typeof row.path === "string" && typeof row.snippet === "string" && typeof row.score === "number"
    ).map((row) => ({ path: row.path, snippet: row.snippet, score: row.score }));
  }
  async health() {
    const payload = {
      modelId: this.config.modelId,
      indexPath: this.indexPath
    };
    const result = await this.runCommand("health", payload, this.config.healthTimeoutMs);
    if (result.status !== "ok" && result.status !== "degraded" && result.status !== "error") {
      throw new FaissAdapterError("FAISS sidecar produced malformed health response", "malformed_output");
    }
    return {
      ok: result.ok === true,
      status: result.status,
      indexPath: this.indexPath,
      message: typeof result.error === "string" && result.error.length > 0 ? result.error : void 0,
      manifest: parseSidecarManifest(result)
    };
  }
  async inspect() {
    const payload = {
      modelId: this.config.modelId,
      indexPath: this.indexPath
    };
    const result = await this.runCommand("inspect", payload, this.config.healthTimeoutMs);
    if (result.status !== "ok" && result.status !== "degraded" && result.status !== "error") {
      throw new FaissAdapterError("FAISS sidecar produced malformed inspect response", "malformed_output");
    }
    return {
      ok: result.ok === true,
      status: result.status,
      indexPath: this.indexPath,
      message: typeof result.error === "string" && result.error.length > 0 ? result.error : void 0,
      manifest: parseSidecarManifest(result),
      metadata: {
        chunkCount: result.metadata && typeof result.metadata.chunkCount === "number" ? result.metadata.chunkCount : 0,
        hasIndex: result.metadata?.hasIndex === true,
        hasMetadata: result.metadata?.hasMetadata === true,
        hasManifest: result.metadata?.hasManifest === true
      }
    };
  }
  async rebuildChunks(chunks) {
    if (this.config.maxBatchSize <= 0) return 0;
    const firstBatch = chunks.slice(0, this.config.maxBatchSize);
    const rebuildPayload = {
      modelId: this.config.modelId,
      indexPath: this.indexPath,
      chunks: firstBatch.map((chunk) => ({
        id: chunk.id,
        sessionKey: chunk.sessionKey,
        text: chunk.text,
        startTs: chunk.startTs,
        endTs: chunk.endTs
      }))
    };
    const result = await this.runCommand("rebuild", rebuildPayload, this.config.upsertTimeoutMs);
    const rebuilt = result.rebuilt;
    if (typeof rebuilt !== "number" || !Number.isFinite(rebuilt)) {
      throw new FaissAdapterError("FAISS sidecar produced malformed rebuild response", "malformed_output");
    }
    const rebuildCount = Math.max(0, Math.floor(rebuilt));
    const remaining = chunks.slice(firstBatch.length);
    if (remaining.length === 0) return rebuildCount;
    const upserted = await this.upsertChunks(remaining);
    return rebuildCount + upserted;
  }
  async runCommand(command, payload, timeoutMs) {
    const args = [this.scriptPath, command];
    const child = this.spawnFn(this.pythonBin, args, {
      stdio: ["pipe", "pipe", "pipe"]
    });
    if (!child.stdin || !child.stdout || !child.stderr) {
      throw new FaissAdapterError(
        `FAISS sidecar missing stdio pipes (${command})`,
        "non_zero_exit"
      );
    }
    const stdinPipe = child.stdin;
    const stdoutPipe = child.stdout;
    const stderrPipe = child.stderr;
    const stdoutChunks = [];
    const stderrChunks = [];
    let timedOut = false;
    const timer = timeoutMs > 0 ? setTimeout(() => {
      timedOut = true;
      child.kill("SIGKILL");
    }, timeoutMs) : void 0;
    stdoutPipe.on("data", (chunk) => {
      stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
    });
    stderrPipe.on("data", (chunk) => {
      stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
    });
    let code;
    try {
      stdinPipe.write(JSON.stringify(payload));
      stdinPipe.end();
      code = await new Promise((resolve, reject) => {
        const rejectAsProcessError = (err) => {
          const msg = err instanceof Error ? err.message : String(err);
          reject(new FaissAdapterError(`FAISS sidecar stream/process error (${command}): ${msg}`, "non_zero_exit"));
        };
        child.once("error", rejectAsProcessError);
        stdinPipe.once("error", rejectAsProcessError);
        child.once("close", (exitCode) => resolve(exitCode));
      });
    } catch (err) {
      if (err instanceof FaissAdapterError) throw err;
      const msg = err instanceof Error ? err.message : String(err);
      throw new FaissAdapterError(`FAISS sidecar stream/process error (${command}): ${msg}`, "non_zero_exit");
    } finally {
      if (timer) clearTimeout(timer);
    }
    const stdout = Buffer.concat(stdoutChunks).toString("utf-8").trim();
    const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
    if (timedOut) {
      throw new FaissAdapterError(
        `FAISS sidecar command timed out (${command}, ${timeoutMs}ms)`,
        "timeout"
      );
    }
    if (code !== 0) {
      throw new FaissAdapterError(
        `FAISS sidecar exited non-zero (${command}, code=${code ?? "null"})${stderr ? `: ${stderr}` : ""}`,
        "non_zero_exit"
      );
    }
    if (stdout.length === 0) {
      throw new FaissAdapterError(
        `FAISS sidecar produced empty output (${command})`,
        "malformed_output"
      );
    }
    let parsed;
    try {
      parsed = JSON.parse(stdout);
    } catch {
      throw new FaissAdapterError(
        `FAISS sidecar produced malformed JSON (${command})`,
        "malformed_output"
      );
    }
    if (parsed.ok === false) {
      const message = typeof parsed.error === "string" && parsed.error.length > 0 ? parsed.error : `FAISS sidecar command failed (${command})`;
      throw new FaissAdapterError(message, "non_zero_exit");
    }
    if (parsed.ok !== true) {
      throw new FaissAdapterError(
        `FAISS sidecar produced malformed success envelope (${command})`,
        "malformed_output"
      );
    }
    return parsed;
  }
};
async function failOpenFaissHealth(adapter) {
  if (!adapter) {
    return { ok: false, status: "error", indexPath: "", message: "adapter-unavailable" };
  }
  try {
    return await adapter.health();
  } catch (err) {
    log.debug(`faiss adapter health failed (fail-open): ${err}`);
    return { ok: false, status: "error", indexPath: "", message: "adapter-error" };
  }
}

// src/conversation-index/search.ts
async function searchConversationIndex(qmd, query, maxResults) {
  try {
    const results = await qmd.search(query, void 0, maxResults);
    return results.map((r) => ({ path: r.path, snippet: r.snippet, score: r.score }));
  } catch (err) {
    log.debug(`conversation index search failed: ${err}`);
    return [];
  }
}
async function searchConversationIndexFaissFailOpen(adapter, query, maxResults) {
  if (!adapter) return [];
  try {
    return await adapter.searchChunks(query, maxResults);
  } catch (err) {
    log.debug(`conversation index FAISS search failed (fail-open): ${err}`);
    return [];
  }
}

// src/conversation-index/backend.ts
function createConversationIndexBackend(options) {
  if (!options.enabled) return void 0;
  const getQmd = options.getQmd ?? (() => options.qmd);
  const getFaiss = options.getFaiss ?? (() => options.faiss);
  if (options.backend === "faiss") {
    return createFaissBackend(getFaiss);
  }
  return createQmdBackend(getQmd, options.collectionDir);
}
function createQmdBackend(getQmd, collectionDir) {
  return {
    kind: "qmd",
    async initialize() {
      const qmd = getQmd();
      if (!qmd) {
        return {
          enabled: true,
          logLevel: "warn",
          message: "Conversation index QMD: not available search backend disabled or unsupported"
        };
      }
      const available = await qmd.probe();
      if (!available) {
        return {
          enabled: true,
          logLevel: "warn",
          message: `Conversation index QMD: not available ${qmd.debugStatus()}`
        };
      }
      const collectionState = await qmd.ensureCollection(collectionDir);
      if (collectionState === "missing") {
        return {
          enabled: false,
          logLevel: "warn",
          message: "Conversation index collection missing; disabling conversation semantic recall for this runtime"
        };
      }
      if (collectionState === "unknown") {
        return {
          enabled: true,
          logLevel: "warn",
          message: "Conversation index collection check unavailable; keeping conversation semantic recall enabled for fail-open behavior"
        };
      }
      if (collectionState === "skipped") {
        return {
          enabled: true,
          logLevel: "debug",
          message: "Conversation index collection check skipped in daemon-only mode"
        };
      }
      return {
        enabled: true,
        logLevel: "info",
        message: `Conversation index QMD: available ${qmd.debugStatus()}`
      };
    },
    async search(query, maxResults) {
      const qmd = getQmd();
      if (!qmd || !qmd.isAvailable()) return [];
      return searchConversationIndex(qmd, query, maxResults);
    },
    async update(_chunks, options) {
      const qmd = getQmd();
      if (!qmd || !qmd.isAvailable()) return { embedded: false };
      await qmd.update();
      if (options.embed) {
        await qmd.embed();
        return { embedded: true };
      }
      return { embedded: false };
    },
    async rebuild(_chunks, options) {
      const qmd = getQmd();
      if (!qmd || !qmd.isAvailable()) return { embedded: false, rebuilt: false };
      await qmd.update();
      if (options.embed) {
        await qmd.embed();
        return { embedded: true, rebuilt: true };
      }
      return { embedded: false, rebuilt: true };
    },
    async health() {
      const qmd = getQmd();
      let qmdAvailable = !!qmd?.isAvailable();
      if (!qmdAvailable && qmd) {
        try {
          qmdAvailable = await qmd.probe();
        } catch {
          qmdAvailable = false;
        }
      }
      return {
        backend: "qmd",
        status: qmdAvailable ? "ok" : "degraded",
        qmdAvailable
      };
    },
    async inspect() {
      const qmd = getQmd();
      let qmdAvailable = !!qmd?.isAvailable();
      if (!qmdAvailable && qmd) {
        try {
          qmdAvailable = await qmd.probe();
        } catch {
          qmdAvailable = false;
        }
      }
      return {
        backend: "qmd",
        status: qmdAvailable ? "ok" : "degraded",
        available: qmdAvailable,
        indexPath: collectionDir,
        supportsIncrementalUpdate: true,
        message: qmd ? void 0 : "Conversation index QMD runtime unavailable",
        metadata: {
          chunkCount: null,
          qmdAvailable,
          debugStatus: qmd?.debugStatus()
        }
      };
    }
  };
}
function createFaissBackend(getFaiss) {
  return {
    kind: "faiss",
    async initialize() {
      const health = await failOpenFaissHealth(getFaiss());
      return health.status === "ok" ? {
        enabled: true,
        logLevel: "info",
        message: `Conversation index FAISS: available (status=${health.status})`
      } : {
        enabled: true,
        logLevel: "warn",
        message: `Conversation index FAISS: degraded (${health.message ?? health.status})`
      };
    },
    async search(query, maxResults) {
      return searchConversationIndexFaissFailOpen(getFaiss(), query, maxResults);
    },
    async update(chunks, _options) {
      await upsertConversationChunksFailOpen(getFaiss(), chunks);
      return { embedded: false };
    },
    async rebuild(chunks, _options) {
      const result = await rebuildConversationChunksFailOpen(getFaiss(), chunks);
      return { embedded: false, rebuilt: result.skipped !== true };
    },
    async health() {
      const faiss = await failOpenFaissHealth(getFaiss());
      return {
        backend: "faiss",
        status: faiss.status === "ok" ? "ok" : "degraded",
        message: faiss.message,
        faiss
      };
    },
    async inspect() {
      const adapter = getFaiss();
      if (!adapter) {
        return {
          backend: "faiss",
          status: "degraded",
          available: false,
          indexPath: "",
          supportsIncrementalUpdate: true,
          message: "Conversation index FAISS runtime unavailable",
          metadata: {
            chunkCount: 0,
            hasIndex: false,
            hasMetadata: false,
            hasManifest: false
          }
        };
      }
      try {
        const inspection = await adapter.inspect();
        return {
          backend: "faiss",
          status: inspection.status === "ok" ? "ok" : "degraded",
          available: inspection.status === "ok",
          indexPath: inspection.indexPath,
          supportsIncrementalUpdate: true,
          message: inspection.message,
          metadata: {
            chunkCount: inspection.metadata.chunkCount,
            hasIndex: inspection.metadata.hasIndex,
            hasMetadata: inspection.metadata.hasMetadata,
            hasManifest: inspection.metadata.hasManifest,
            manifest: inspection.manifest
          }
        };
      } catch (err) {
        const fallback = await failOpenFaissHealth(adapter);
        return {
          backend: "faiss",
          status: "degraded",
          available: false,
          indexPath: fallback.indexPath,
          supportsIncrementalUpdate: true,
          message: fallback.message ?? String(err),
          metadata: {
            chunkCount: fallback.manifest?.chunkCount ?? 0,
            hasIndex: false,
            hasMetadata: false,
            hasManifest: !!fallback.manifest,
            manifest: fallback.manifest
          }
        };
      }
    }
  };
}

// src/search/factory.ts
function resolveNonQmdBackend(config) {
  const backend = config.searchBackend ?? "qmd";
  const collection = config.qmdCollection;
  if (backend === "noop") {
    return new NoopSearchBackend();
  }
  if (backend === "remote") {
    const baseUrl = config.remoteSearchBaseUrl || "http://localhost:8181";
    if (!config.remoteSearchBaseUrl) {
      log.warn("searchBackend is 'remote' but remoteSearchBaseUrl is not configured; using default http://localhost:8181");
    }
    return new RemoteSearchBackend({
      baseUrl,
      apiKey: config.remoteSearchApiKey,
      timeoutMs: config.remoteSearchTimeoutMs
    });
  }
  if (backend === "lancedb") {
    const embedHelper = new EmbedHelper(config);
    return new LanceDbBackend({
      dbPath: config.lanceDbPath,
      collection,
      embedHelper,
      memoryDir: config.memoryDir,
      embeddingDimension: config.lanceEmbeddingDimension
    });
  }
  if (backend === "meilisearch") {
    return new MeilisearchBackend({
      host: config.meilisearchHost,
      apiKey: config.meilisearchApiKey,
      collection,
      timeoutMs: config.meilisearchTimeoutMs,
      autoIndex: config.meilisearchAutoIndex,
      memoryDir: config.memoryDir
    });
  }
  if (backend === "orama") {
    const embedHelper = new EmbedHelper(config);
    return new OramaBackend({
      dbPath: config.oramaDbPath,
      collection,
      embedHelper,
      memoryDir: config.memoryDir,
      embeddingDimension: config.oramaEmbeddingDimension
    });
  }
  return void 0;
}
function qmdOptions(config) {
  return {
    slowLog: {
      enabled: config.slowLogEnabled,
      thresholdMs: config.slowLogThresholdMs
    },
    updateTimeoutMs: config.qmdUpdateTimeoutMs,
    updateMinIntervalMs: config.qmdUpdateMinIntervalMs,
    qmdPath: config.qmdPath,
    daemonUrl: config.qmdDaemonEnabled ? config.qmdDaemonUrl : void 0,
    daemonRecheckIntervalMs: config.qmdDaemonRecheckIntervalMs
  };
}
function createSearchBackend(config) {
  const nonQmd = resolveNonQmdBackend(config);
  if (nonQmd) return nonQmd;
  if (!config.qmdEnabled) {
    return new NoopSearchBackend();
  }
  return new QmdClient(config.qmdCollection, config.qmdMaxResults, qmdOptions(config));
}
function createConversationSearchBackend(config) {
  if (!config.conversationIndexEnabled || config.conversationIndexBackend !== "qmd") {
    return void 0;
  }
  const backend = config.searchBackend ?? "qmd";
  if (backend === "noop") return void 0;
  if (!config.qmdEnabled) return void 0;
  return new QmdClient(
    config.conversationIndexQmdCollection,
    Math.max(6, config.conversationRecallTopK),
    qmdOptions(config)
  );
}
function createConversationIndexRuntime(config, overrides) {
  const qmd = createConversationSearchBackend(config);
  const faiss = config.conversationIndexEnabled && config.conversationIndexBackend === "faiss" ? new FaissConversationIndexAdapter({
    memoryDir: config.memoryDir,
    scriptPath: config.conversationIndexFaissScriptPath,
    pythonBin: config.conversationIndexFaissPythonBin,
    modelId: config.conversationIndexFaissModelId,
    indexDir: config.conversationIndexFaissIndexDir,
    upsertTimeoutMs: config.conversationIndexFaissUpsertTimeoutMs,
    searchTimeoutMs: config.conversationIndexFaissSearchTimeoutMs,
    healthTimeoutMs: config.conversationIndexFaissHealthTimeoutMs,
    maxBatchSize: config.conversationIndexFaissMaxBatchSize,
    maxSearchK: config.conversationIndexFaissMaxSearchK
  }) : void 0;
  const backend = createConversationIndexBackend({
    enabled: config.conversationIndexEnabled,
    backend: config.conversationIndexBackend,
    getQmd: () => overrides?.getQmd?.() ?? qmd,
    getFaiss: () => overrides?.getFaiss?.() ?? faiss,
    collectionDir: path5.join(config.memoryDir, "conversation-index")
  });
  return { qmd, faiss, backend };
}

// src/namespaces/storage.ts
import path6 from "path";
import { access } from "fs/promises";
async function exists(p) {
  try {
    await access(p);
    return true;
  } catch {
    return false;
  }
}
var NamespaceStorageRouter = class {
  constructor(config) {
    this.config = config;
  }
  config;
  cache = /* @__PURE__ */ new Map();
  defaultNsRootResolved = null;
  async defaultNamespaceRoot() {
    if (this.defaultNsRootResolved) return this.defaultNsRootResolved;
    if (!this.config.namespacesEnabled) {
      this.defaultNsRootResolved = this.config.memoryDir;
      return this.defaultNsRootResolved;
    }
    const nsDir = path6.join(this.config.memoryDir, "namespaces", this.config.defaultNamespace);
    this.defaultNsRootResolved = await exists(nsDir) ? nsDir : this.config.memoryDir;
    return this.defaultNsRootResolved;
  }
  namespaceRootSync(namespace) {
    if (!this.config.namespacesEnabled) return this.config.memoryDir;
    if (namespace === this.config.defaultNamespace) {
      return this.defaultNsRootResolved ?? this.config.memoryDir;
    }
    return path6.join(this.config.memoryDir, "namespaces", namespace);
  }
  async storageFor(namespace) {
    const ns = namespace || this.config.defaultNamespace;
    if (ns !== this.config.defaultNamespace && !isSafeRouteNamespace(ns)) {
      throw new Error(`unsafe namespace: ${ns}`);
    }
    if (this.cache.has(ns)) return this.cache.get(ns);
    if (ns === this.config.defaultNamespace) {
      await this.defaultNamespaceRoot();
    }
    const root = this.namespaceRootSync(ns);
    const sm = new StorageManager(root, this.config.entitySchemas);
    sm.citationTemplate = this.config.inlineSourceAttributionFormat;
    this.cache.set(ns, sm);
    return sm;
  }
};

// src/namespaces/search.ts
function namespaceCollectionName(baseCollection, namespace, options) {
  const trimmed = namespace.trim();
  const defaultNamespace = options?.defaultNamespace?.trim() || "default";
  if (options?.useLegacyDefaultCollection === true && trimmed === defaultNamespace) {
    return baseCollection;
  }
  const normalized = trimmed.toLowerCase().replace(/[^a-z0-9._-]+/g, "-");
  let start = 0;
  let end = normalized.length;
  while (start < end && normalized[start] === "-") start += 1;
  while (end > start && normalized[end - 1] === "-") end -= 1;
  const token = normalized.slice(start, end) || defaultNamespace;
  return `${baseCollection}--ns--${token}`;
}
var NamespaceSearchRouter = class {
  constructor(config, storageRouter, createBackend = createSearchBackend) {
    this.config = config;
    this.storageRouter = storageRouter;
    this.createBackend = createBackend;
  }
  config;
  storageRouter;
  createBackend;
  cache = /* @__PURE__ */ new Map();
  async collectionForNamespace(namespace) {
    return (await this.backendRecordFor(namespace)).collection;
  }
  async searchAcrossNamespaces(options) {
    const query = options.query.trim();
    if (!query) return [];
    const maxResults = Math.max(0, Math.floor(options.maxResults ?? this.config.qmdMaxResults));
    if (maxResults === 0) return [];
    const method = options.mode ?? "search";
    const namespaces = Array.from(new Set(options.namespaces.map((value) => value.trim()).filter(Boolean)));
    if (namespaces.length === 0) return [];
    const resultsByNamespace = await Promise.all(
      namespaces.map(async (namespace) => {
        const record = await this.backendRecordFor(namespace);
        if (!record.available || record.collectionState === "missing") return [];
        switch (method) {
          case "hybrid":
            return await record.backend.hybridSearch(query, void 0, maxResults, options.execution);
          case "bm25":
            return await record.backend.bm25Search(query, void 0, maxResults, options.execution);
          case "vector":
            return await record.backend.vectorSearch(query, void 0, maxResults, options.execution);
          default:
            return await record.backend.search(
              query,
              void 0,
              maxResults,
              options.searchOptions,
              options.execution
            );
        }
      })
    );
    return mergeNamespaceSearchResults(resultsByNamespace, maxResults);
  }
  /**
   * Update all namespace backends.
   * Returns the number of backends for which an update was attempted
   * (i.e., available and collection present).  Callers can treat 0 as a
   * signal that no backend was eligible — useful for success-verification in
   * startup-sync when namespacesEnabled is true.
   */
  async updateNamespaces(namespaces, execution) {
    const unique = Array.from(new Set(namespaces.map((value) => value.trim()).filter(Boolean)));
    const results = await Promise.all(
      unique.map(async (namespace) => {
        const record = await this.backendRecordFor(namespace);
        if (!record.available || record.collectionState === "missing") return 0;
        await record.backend.update(execution);
        return 1;
      })
    );
    return results.reduce((sum, v) => sum + v, 0);
  }
  async embedNamespaces(namespaces) {
    const unique = Array.from(new Set(namespaces.map((value) => value.trim()).filter(Boolean)));
    await Promise.all(
      unique.map(async (namespace) => {
        const record = await this.backendRecordFor(namespace);
        if (!record.available || record.collectionState === "missing") return;
        await record.backend.embed();
      })
    );
  }
  async ensureNamespaceCollection(namespace) {
    const record = await this.backendRecordFor(namespace);
    return record.collectionState;
  }
  /** Clear cached backend records so the next access re-probes availability. */
  clearCache() {
    this.cache.clear();
  }
  async backendRecordFor(namespace) {
    const key = namespace.trim() || this.config.defaultNamespace;
    const existing = this.cache.get(key);
    if (existing) return await existing;
    const pending = (async () => {
      const storage = await this.storageRouter.storageFor(key);
      const useLegacyDefaultCollection = key === this.config.defaultNamespace && storage.dir === this.config.memoryDir;
      const scopedConfig = {
        ...this.config,
        memoryDir: storage.dir,
        qmdCollection: namespaceCollectionName(this.config.qmdCollection, key, {
          defaultNamespace: this.config.defaultNamespace,
          useLegacyDefaultCollection
        })
      };
      const backend = this.createBackend(scopedConfig);
      const available = await backend.probe().catch(() => false);
      const collectionState = available ? await backend.ensureCollection(storage.dir).catch(() => "unknown") : "unknown";
      return {
        backend,
        collection: scopedConfig.qmdCollection,
        memoryDir: storage.dir,
        available,
        collectionState
      };
    })();
    this.cache.set(key, pending);
    return await pending;
  }
};
function mergeNamespaceSearchResults(lists, maxResults) {
  const merged = /* @__PURE__ */ new Map();
  for (const list of lists) {
    for (const result of list) {
      const key = result.path || result.docid;
      const existing = merged.get(key);
      if (!existing) {
        merged.set(key, result);
        continue;
      }
      if (result.score > existing.score) {
        merged.set(key, {
          ...result,
          snippet: existing.snippet || result.snippet || ""
        });
      }
    }
  }
  return [...merged.values()].sort((a, b) => b.score - a.score).slice(0, maxResults);
}

export {
  NoopSearchBackend,
  LanceDbBackend,
  MeilisearchBackend,
  OramaBackend,
  writeConversationChunks,
  createSearchBackend,
  createConversationIndexRuntime,
  NamespaceStorageRouter,
  namespaceCollectionName,
  NamespaceSearchRouter
};
//# sourceMappingURL=chunk-R2L7SUX2.js.map