@autorag/librarian 2.10.3 → 2.10.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -2657,7 +2657,7 @@ function chatCrawler(id) {
2657
2657
  hierarchy: (metadata) => ["chats", str(metadata.chatName) ?? str(metadata.chatId) ?? "unknown"]
2658
2658
  };
2659
2659
  }
2660
- var str = (value) => typeof value === "string" && value.length > 0 ? value : undefined, slack, CRAWLER_DESCRIPTORS;
2660
+ var str = (value) => typeof value === "string" && value.length > 0 ? value : undefined, slack, kakao, CRAWLER_DESCRIPTORS;
2661
2661
  var init_descriptors = __esm(() => {
2662
2662
  slack = {
2663
2663
  id: "slack",
@@ -2688,10 +2688,32 @@ var init_descriptors = __esm(() => {
2688
2688
  str(metadata.channelName) ?? str(metadata.channelId) ?? "unknown"
2689
2689
  ]
2690
2690
  };
2691
+ kakao = {
2692
+ id: "kakao",
2693
+ keyExpression: "m.chunk_id",
2694
+ chunkId: (key) => key,
2695
+ fromClause: "chunks m",
2696
+ liveWhere: "length(trim(coalesce(m.text, ''))) > 0 and not (json_valid(m.text) and (json_extract(m.text, '$.feedType') is not null or json_extract(m.text, '$.csIP') is not null))",
2697
+ textExpression: "coalesce(m.text, '')",
2698
+ titleExpression: "m.chat_name",
2699
+ epochSecondsExpression: "unixepoch(m.started_at)",
2700
+ fts: { table: "chunks_fts", joinOn: "chunks_fts.rowid = m.rowid" },
2701
+ metadata: [
2702
+ { sql: "m.chat_id", key: "chatId" },
2703
+ { sql: "m.chat_name", key: "chatName" },
2704
+ { sql: "m.sender_nickname", key: "senderNickname" },
2705
+ { sql: "m.started_at", key: "startedAt" },
2706
+ { sql: "m.ended_at", key: "endedAt" }
2707
+ ],
2708
+ channelIdExpression: "m.chat_id",
2709
+ channelNameExpression: "m.chat_name",
2710
+ hierarchy: (metadata) => ["chats", str(metadata.chatName) ?? str(metadata.chatId) ?? "unknown"]
2711
+ };
2691
2712
  CRAWLER_DESCRIPTORS = {
2692
2713
  slack,
2693
2714
  telegram: chatCrawler("telegram"),
2694
- whatsapp: chatCrawler("whatsapp")
2715
+ whatsapp: chatCrawler("whatsapp"),
2716
+ kakao
2695
2717
  };
2696
2718
  });
2697
2719
 
@@ -8054,6 +8076,504 @@ var init_skip = __esm(() => {
8054
8076
  MINSYNC_METHOD_NAMES = new Set(["minsync", "hybrid", "bm25"]);
8055
8077
  });
8056
8078
 
8079
+ // src/datasource/sidecar/fusion.ts
8080
+ function encodeVector(vector) {
8081
+ const floats = Float32Array.from(vector);
8082
+ return new Uint8Array(floats.buffer, floats.byteOffset, floats.byteLength);
8083
+ }
8084
+ function decodeVector(blob) {
8085
+ if (blob.byteLength % Float32Array.BYTES_PER_ELEMENT !== 0)
8086
+ return;
8087
+ return new Float32Array(new Uint8Array(blob).buffer);
8088
+ }
8089
+ function cosine2(a, b) {
8090
+ if (a.length === 0 || a.length !== b.length)
8091
+ return 0;
8092
+ let dot = 0;
8093
+ let normA = 0;
8094
+ let normB = 0;
8095
+ for (let index = 0;index < a.length; index += 1) {
8096
+ const x = a[index] ?? 0;
8097
+ const y = b[index] ?? 0;
8098
+ dot += x * y;
8099
+ normA += x * x;
8100
+ normB += y * y;
8101
+ }
8102
+ if (normA === 0 || normB === 0)
8103
+ return 0;
8104
+ return dot / Math.sqrt(normA * normB);
8105
+ }
8106
+ function topKByCosine(query, rows, k) {
8107
+ if (k <= 0)
8108
+ return [];
8109
+ const scored = [];
8110
+ for (const row of rows) {
8111
+ const score = cosine2(query, row.vector);
8112
+ if (score > 0)
8113
+ scored.push({ key: row.key, score });
8114
+ }
8115
+ scored.sort((a, b) => b.score - a.score || a.key.localeCompare(b.key));
8116
+ return scored.slice(0, k);
8117
+ }
8118
+ function reciprocalRankFusion(lists) {
8119
+ const scores = new Map;
8120
+ for (const list of lists) {
8121
+ const seen = new Set;
8122
+ for (const [index, key] of list.entries()) {
8123
+ if (seen.has(key))
8124
+ continue;
8125
+ seen.add(key);
8126
+ scores.set(key, (scores.get(key) ?? 0) + 1 / (RRF_K + index + 1));
8127
+ }
8128
+ }
8129
+ return [...scores.entries()].map(([key, score]) => ({ key, score })).sort((a, b) => b.score - a.score);
8130
+ }
8131
+ var RRF_K = 60;
8132
+
8133
+ // src/datasource/sidecar/index.ts
8134
+ import { createHash as createHash6 } from "node:crypto";
8135
+ import { mkdirSync as mkdirSync7, statSync } from "node:fs";
8136
+ import { dirname as dirname11 } from "node:path";
8137
+ import { DatabaseSync } from "node:sqlite";
8138
+ import { pathToFileURL } from "node:url";
8139
+
8140
+ class CrawlerSidecar {
8141
+ descriptor;
8142
+ options;
8143
+ select;
8144
+ where;
8145
+ opened;
8146
+ attached;
8147
+ lastSemantic;
8148
+ constructor(options) {
8149
+ this.options = options;
8150
+ const { descriptor } = options;
8151
+ this.descriptor = descriptor;
8152
+ const metadataColumns = descriptor.metadata.map((column, index) => `${column.sql} as m${index}`);
8153
+ this.select = [
8154
+ `${descriptor.keyExpression} as k`,
8155
+ `${descriptor.textExpression} as content`,
8156
+ `${descriptor.titleExpression ?? "null"} as title`,
8157
+ `${descriptor.epochSecondsExpression ?? "null"} as epoch`,
8158
+ ...metadataColumns
8159
+ ].join(", ");
8160
+ const scope = descriptor.scopeExpression !== undefined && options.workspaceId !== undefined;
8161
+ const allowlist = [
8162
+ ...(options.channelIds?.length ?? 0) > 0 ? [`${descriptor.channelIdExpression} in (${options.channelIds?.map(() => "?").join(", ")})`] : [],
8163
+ ...(options.channelNames?.length ?? 0) > 0 ? [`${descriptor.channelNameExpression} in (${options.channelNames?.map(() => "?").join(", ")})`] : []
8164
+ ];
8165
+ this.where = [
8166
+ descriptor.liveWhere,
8167
+ ...scope ? [`${descriptor.scopeExpression} = ?`] : [],
8168
+ ...allowlist.length > 0 ? [`(${allowlist.join(" or ")})`] : []
8169
+ ].join(" and ");
8170
+ }
8171
+ get db() {
8172
+ if (this.opened !== undefined)
8173
+ return this.opened;
8174
+ mkdirSync7(dirname11(this.options.sidecarDbPath), { recursive: true });
8175
+ const db = new DatabaseSync(this.options.sidecarDbPath);
8176
+ db.exec(`pragma busy_timeout = ${BUSY_TIMEOUT_MS}`);
8177
+ db.exec("pragma journal_mode = wal");
8178
+ db.exec(`
8179
+ create table if not exists sidecar_meta (k text primary key, v text not null);
8180
+ create table if not exists sidecar_vec (message_key text primary key, hash text not null, vec blob not null);
8181
+ `);
8182
+ this.opened = db;
8183
+ return db;
8184
+ }
8185
+ attachCrawler() {
8186
+ let file;
8187
+ try {
8188
+ const { dev, ino } = statSync(this.options.crawlerDbPath);
8189
+ file = { dev, ino };
8190
+ } catch {
8191
+ this.detachCrawler();
8192
+ return false;
8193
+ }
8194
+ if (this.attached !== undefined) {
8195
+ if (this.attached.dev === file.dev && this.attached.ino === file.ino)
8196
+ return true;
8197
+ this.detachCrawler();
8198
+ }
8199
+ const crawlerUri = `${pathToFileURL(this.options.crawlerDbPath).href}?mode=ro`.replaceAll("'", "''");
8200
+ try {
8201
+ this.db.exec(`attach database '${crawlerUri}' as crawler`);
8202
+ } catch {
8203
+ return false;
8204
+ }
8205
+ const { descriptor } = this.options;
8206
+ try {
8207
+ this.db.prepare(`select ${this.select} from ${descriptor.fromClause} where ${this.where} limit 0`).all(...this.whereParams());
8208
+ this.db.prepare(`select 1 from ${descriptor.fts.table} limit 0`).all();
8209
+ } catch {
8210
+ this.db.exec("detach database crawler");
8211
+ return false;
8212
+ }
8213
+ this.attached = file;
8214
+ this.lastSemantic = undefined;
8215
+ return true;
8216
+ }
8217
+ detachCrawler() {
8218
+ if (this.attached === undefined)
8219
+ return;
8220
+ this.attached = undefined;
8221
+ this.lastSemantic = undefined;
8222
+ try {
8223
+ this.opened?.exec("detach database crawler");
8224
+ } catch {}
8225
+ }
8226
+ get semanticEnabled() {
8227
+ return this.options.embedder !== undefined;
8228
+ }
8229
+ close() {
8230
+ this.opened?.close();
8231
+ this.opened = undefined;
8232
+ this.attached = undefined;
8233
+ this.lastSemantic = undefined;
8234
+ }
8235
+ whereParams() {
8236
+ const { descriptor, workspaceId, channelIds = [], channelNames = [] } = this.options;
8237
+ return [
8238
+ ...descriptor.scopeExpression !== undefined && workspaceId !== undefined ? [workspaceId] : [],
8239
+ ...channelIds,
8240
+ ...channelNames
8241
+ ];
8242
+ }
8243
+ searchLexical(query, topK) {
8244
+ const match = toMatchQuery(query);
8245
+ if (match === undefined || topK <= 0 || !this.attachCrawler())
8246
+ return [];
8247
+ const { descriptor } = this.options;
8248
+ const rows = this.db.prepare(`select ${this.select}, bm25(${descriptor.fts.table}) as rank
8249
+ from ${descriptor.fromClause} join ${descriptor.fts.table} on ${descriptor.fts.joinOn}
8250
+ where ${descriptor.fts.table} match ? and ${this.where}
8251
+ order by rank limit ?`).all(match, ...this.whereParams(), topK);
8252
+ return rows.map((row) => this.toHit(this.toRow(row), -Number(row.rank)));
8253
+ }
8254
+ async searchSemantic(query, topK) {
8255
+ const trimmed = query.trim();
8256
+ if (this.options.embedder === undefined || trimmed.length === 0 || topK <= 0 || !this.attachCrawler())
8257
+ return [];
8258
+ const ranked = await this.rankSemantic(trimmed, topK * POOL_FACTOR);
8259
+ if (ranked.length === 0)
8260
+ return [];
8261
+ const live = this.hydrate(ranked.map((entry) => entry.key));
8262
+ const hits = [];
8263
+ for (const entry of ranked) {
8264
+ const row = live.get(entry.key);
8265
+ if (row !== undefined)
8266
+ hits.push(this.toHit(row, entry.score));
8267
+ if (hits.length >= topK)
8268
+ break;
8269
+ }
8270
+ return hits;
8271
+ }
8272
+ async rankSemantic(query, pool) {
8273
+ const now = Date.now();
8274
+ let cached = this.lastSemantic;
8275
+ if (cached === undefined || cached.query !== query || now - cached.at > SEMANTIC_REUSE_MS) {
8276
+ const embedder = this.options.embedder;
8277
+ if (embedder === undefined)
8278
+ return [];
8279
+ cached = {
8280
+ query,
8281
+ at: now,
8282
+ vector: embedder.embed([query]).then((vectors) => vectors[0], () => {
8283
+ return;
8284
+ }),
8285
+ ranked: undefined
8286
+ };
8287
+ this.lastSemantic = cached;
8288
+ }
8289
+ const vector = await cached.vector;
8290
+ if (vector === undefined) {
8291
+ if (this.lastSemantic === cached)
8292
+ this.lastSemantic = undefined;
8293
+ return [];
8294
+ }
8295
+ if (cached.ranked === undefined || cached.ranked.pool < pool) {
8296
+ const wide = pool * POOL_FACTOR;
8297
+ cached.ranked = { pool: wide, keys: topKByCosine(vector, this.storedVectors(), wide) };
8298
+ }
8299
+ return cached.ranked.keys.slice(0, pool);
8300
+ }
8301
+ async searchHybrid(query, topK) {
8302
+ if (topK <= 0)
8303
+ return [];
8304
+ const pool = topK * POOL_FACTOR;
8305
+ const lexical = this.searchLexical(query, pool);
8306
+ const semantic = await this.searchSemantic(query, pool);
8307
+ if (semantic.length === 0)
8308
+ return lexical.slice(0, topK);
8309
+ const byKey = new Map;
8310
+ for (const hit of [...semantic, ...lexical])
8311
+ byKey.set(hit.key, hit);
8312
+ const fused = [];
8313
+ for (const entry of reciprocalRankFusion([lexical.map((hit) => hit.key), semantic.map((hit) => hit.key)])) {
8314
+ const hit = byKey.get(entry.key);
8315
+ if (hit !== undefined)
8316
+ fused.push({ ...hit, score: entry.score });
8317
+ if (fused.length >= topK)
8318
+ break;
8319
+ }
8320
+ return fused;
8321
+ }
8322
+ async sync() {
8323
+ const { embedder } = this.options;
8324
+ if (embedder === undefined)
8325
+ return { ok: false, message: "no embedder configured" };
8326
+ if (!this.attachCrawler())
8327
+ return { ok: false, message: "crawler archive not available yet" };
8328
+ let identity;
8329
+ try {
8330
+ identity = await embedder.identity();
8331
+ } catch (error) {
8332
+ return { ok: false, message: errorMessage(error) };
8333
+ }
8334
+ this.adoptIdentity(identity);
8335
+ const pruned = this.prune();
8336
+ const pending = this.pendingKeys();
8337
+ const batchSize = Math.max(1, this.options.batchSize ?? DEFAULT_BATCH_SIZE);
8338
+ const budget = Math.max(1, this.options.maxEmbedPerSync ?? DEFAULT_MAX_EMBED_PER_SYNC);
8339
+ const target = Math.min(pending.length, budget);
8340
+ const embed = async (rows) => {
8341
+ const vectors = await embedder.embed(rows.map((row) => row.input));
8342
+ if (vectors.length !== rows.length) {
8343
+ throw new Error(`embedder returned ${vectors.length} vectors for ${rows.length} messages`);
8344
+ }
8345
+ const wrong = vectors.find((vector) => vector.length !== identity.dimension);
8346
+ if (wrong !== undefined) {
8347
+ throw new Error(`embedder returned a ${wrong.length}-dimension vector, expected ${identity.dimension}`);
8348
+ }
8349
+ return vectors;
8350
+ };
8351
+ let embedded = 0;
8352
+ let skipped = 0;
8353
+ for (let offset = 0;offset < target; offset += batchSize) {
8354
+ const rows = [...this.hydrate(pending.slice(offset, Math.min(offset + batchSize, target))).values()];
8355
+ if (rows.length === 0)
8356
+ continue;
8357
+ try {
8358
+ this.store(rows, await embed(rows));
8359
+ embedded += rows.length;
8360
+ } catch (batchError) {
8361
+ const rejected = [];
8362
+ let lastError = batchError;
8363
+ for (const row of rows) {
8364
+ try {
8365
+ this.store([row], await embed([row]));
8366
+ embedded += 1;
8367
+ } catch (error) {
8368
+ rejected.push(row);
8369
+ lastError = error;
8370
+ }
8371
+ }
8372
+ if (rejected.length === 0)
8373
+ continue;
8374
+ if (embedded === 0)
8375
+ return { ok: false, message: errorMessage(lastError) };
8376
+ this.store(rejected, undefined);
8377
+ skipped += rejected.length;
8378
+ }
8379
+ }
8380
+ return { ok: true, embedded, skipped, pruned, remaining: pending.length - embedded - skipped };
8381
+ }
8382
+ adoptIdentity(identity) {
8383
+ const serialized = JSON.stringify(identity);
8384
+ const current = this.db.prepare("select v from sidecar_meta where k = ?").get(IDENTITY_KEY);
8385
+ if (current?.v === serialized)
8386
+ return;
8387
+ this.transaction(() => {
8388
+ this.db.exec("delete from sidecar_vec");
8389
+ this.db.prepare("insert or replace into sidecar_meta (k, v) values (?, ?)").run(IDENTITY_KEY, serialized);
8390
+ });
8391
+ this.lastSemantic = undefined;
8392
+ }
8393
+ prune() {
8394
+ const { descriptor } = this.options;
8395
+ const result = this.db.prepare(`delete from sidecar_vec where message_key not in (
8396
+ select ${descriptor.keyExpression} from ${descriptor.fromClause} where ${this.where}
8397
+ )`).run(...this.whereParams());
8398
+ if (Number(result.changes) > 0)
8399
+ this.lastSemantic = undefined;
8400
+ return Number(result.changes);
8401
+ }
8402
+ pendingKeys() {
8403
+ const { descriptor } = this.options;
8404
+ const rows = this.db.prepare(`select ${this.select}, v.hash as stored_hash
8405
+ from ${descriptor.fromClause}
8406
+ left join sidecar_vec v on v.message_key = ${descriptor.keyExpression}
8407
+ where ${this.where}`).iterate(...this.whereParams());
8408
+ const pending = [];
8409
+ for (const row of rows) {
8410
+ if (row.stored_hash !== this.toRow(row).hash)
8411
+ pending.push(String(row.k));
8412
+ }
8413
+ return pending;
8414
+ }
8415
+ hydrate(keys) {
8416
+ const { descriptor } = this.options;
8417
+ const out = new Map;
8418
+ if (keys.length === 0)
8419
+ return out;
8420
+ const rows = this.db.prepare(`select ${this.select} from ${descriptor.fromClause}
8421
+ where ${descriptor.keyExpression} in (${keys.map(() => "?").join(", ")}) and ${this.where}`).all(...keys, ...this.whereParams());
8422
+ const byKey = new Map(rows.map((row) => [String(row.k), this.toRow(row)]));
8423
+ for (const key of keys) {
8424
+ const row = byKey.get(key);
8425
+ if (row !== undefined)
8426
+ out.set(key, row);
8427
+ }
8428
+ return out;
8429
+ }
8430
+ store(rows, vectors) {
8431
+ const upsert = this.db.prepare("insert or replace into sidecar_vec (message_key, hash, vec) values (?, ?, ?)");
8432
+ this.transaction(() => {
8433
+ for (const [index, row] of rows.entries()) {
8434
+ upsert.run(row.key, row.hash, encodeVector(vectors?.[index] ?? []));
8435
+ }
8436
+ });
8437
+ this.lastSemantic = undefined;
8438
+ }
8439
+ *storedVectors() {
8440
+ const rows = this.db.prepare("select message_key, vec from sidecar_vec where length(vec) > 0").iterate();
8441
+ for (const row of rows) {
8442
+ const vector = row.vec instanceof Uint8Array ? decodeVector(row.vec) : undefined;
8443
+ if (vector !== undefined)
8444
+ yield { key: String(row.message_key), vector };
8445
+ }
8446
+ }
8447
+ transaction(work) {
8448
+ this.db.exec("begin immediate");
8449
+ try {
8450
+ work();
8451
+ this.db.exec("commit");
8452
+ } catch (error) {
8453
+ this.db.exec("rollback");
8454
+ throw error;
8455
+ }
8456
+ }
8457
+ toRow(row) {
8458
+ const metadata = {};
8459
+ for (const [index, column] of this.options.descriptor.metadata.entries()) {
8460
+ const value = row[`m${index}`];
8461
+ if (typeof value === "string" && value.length > 0 || typeof value === "number")
8462
+ metadata[column.key] = String(value);
8463
+ }
8464
+ const title = typeof row.title === "string" && row.title.length > 0 ? row.title : undefined;
8465
+ const epoch = typeof row.epoch === "number" && Number.isFinite(row.epoch) ? row.epoch : undefined;
8466
+ const content = String(row.content ?? "");
8467
+ const input = `${title ?? ""}
8468
+ ${content}`.slice(0, MAX_EMBED_INPUT_CHARS);
8469
+ return {
8470
+ key: String(row.k),
8471
+ content,
8472
+ title,
8473
+ epoch,
8474
+ metadata,
8475
+ input,
8476
+ hash: createHash6("sha1").update(input).digest("hex")
8477
+ };
8478
+ }
8479
+ toHit(row, score) {
8480
+ return {
8481
+ key: row.key,
8482
+ content: row.content,
8483
+ score,
8484
+ ...row.title !== undefined ? { title: row.title } : {},
8485
+ ...row.epoch !== undefined ? { publishedAt: Math.round(row.epoch * 1000) } : {},
8486
+ metadata: row.metadata
8487
+ };
8488
+ }
8489
+ }
8490
+ function toMatchQuery(text) {
8491
+ const terms = [...new Set(text.match(/[\p{L}\p{N}_]+/gu) ?? [])];
8492
+ return terms.length === 0 ? undefined : terms.map((term) => `"${term}"`).join(" OR ");
8493
+ }
8494
+ function errorMessage(error) {
8495
+ return error instanceof Error ? error.message : String(error);
8496
+ }
8497
+ var DEFAULT_BATCH_SIZE = 256, DEFAULT_MAX_EMBED_PER_SYNC = 2048, MAX_EMBED_INPUT_CHARS = 8192, BUSY_TIMEOUT_MS = 5000, POOL_FACTOR = 3, SEMANTIC_REUSE_MS = 1e4, IDENTITY_KEY = "identity";
8498
+ var init_sidecar = () => {};
8499
+
8500
+ // src/datasource/sidecar/methods.ts
8501
+ function sidecarMethodName(datasourceId, mode) {
8502
+ return mode === "bm25" ? `${datasourceId}-sidecar-bm25` : `${datasourceId}-${mode}`;
8503
+ }
8504
+
8505
+ class SidecarRetrievalMethod {
8506
+ options;
8507
+ constructor(options) {
8508
+ this.options = options;
8509
+ }
8510
+ describe() {
8511
+ const { datasourceId, skillType, mode, tags } = this.options;
8512
+ const { type, text, capabilities } = DESCRIPTIONS[mode];
8513
+ return {
8514
+ name: sidecarMethodName(datasourceId, mode),
8515
+ type,
8516
+ description: `${text} over ${skillType} via a read-only sidecar index`,
8517
+ status: "active",
8518
+ capabilities: [...capabilities, "scoped", "path-opaque-sources"],
8519
+ datasourceId,
8520
+ tags
8521
+ };
8522
+ }
8523
+ async retrieve(query, options) {
8524
+ const trimmed = query.trim();
8525
+ if (trimmed.length === 0)
8526
+ return [];
8527
+ const topK = options.topK ?? DEFAULT_TOP_K5;
8528
+ const { sidecar, mode } = this.options;
8529
+ const hits = mode === "bm25" ? sidecar.searchLexical(trimmed, topK) : mode === "semantic" ? await sidecar.searchSemantic(trimmed, topK) : await sidecar.searchHybrid(trimmed, topK);
8530
+ const results = [];
8531
+ for (const hit of hits) {
8532
+ const result = this.toResult(hit, options);
8533
+ if (result !== undefined)
8534
+ results.push(result);
8535
+ }
8536
+ return results;
8537
+ }
8538
+ toResult(hit, options) {
8539
+ const { descriptor, datasourceId, instanceId, mode } = this.options;
8540
+ const chunkId = descriptor.chunkId(hit.key, hit.metadata);
8541
+ const source = datasourceSourcePath(datasourceId, instanceId, chunkId);
8542
+ if (!matchesDatasourceScope(source, options.scope))
8543
+ return;
8544
+ const method = sidecarMethodName(datasourceId, mode);
8545
+ return {
8546
+ id: `${datasourceId}:${instanceId}:${chunkId}`,
8547
+ content: hit.content,
8548
+ source,
8549
+ score: hit.score,
8550
+ metadata: {
8551
+ ...hit.metadata,
8552
+ method,
8553
+ datasourceId,
8554
+ instanceId,
8555
+ backend: "sidecar",
8556
+ ...hit.title !== undefined ? { title: hit.title } : {},
8557
+ hierarchy: descriptor.hierarchy(hit.metadata).join("/"),
8558
+ ...hit.publishedAt !== undefined ? { publishedAt: hit.publishedAt } : {}
8559
+ }
8560
+ };
8561
+ }
8562
+ }
8563
+ var DEFAULT_TOP_K5 = 20, DESCRIPTIONS;
8564
+ var init_methods4 = __esm(() => {
8565
+ init_scope2();
8566
+ DESCRIPTIONS = {
8567
+ bm25: { type: "bm25", text: "BM25-ranked FTS5 retrieval", capabilities: ["lexical", "fts5", "bm25"] },
8568
+ semantic: { type: "vector", text: "Semantic (embedding) retrieval", capabilities: ["semantic"] },
8569
+ hybrid: {
8570
+ type: "hybrid",
8571
+ text: "Hybrid BM25 + semantic retrieval (reciprocal rank fusion)",
8572
+ capabilities: ["lexical", "semantic", "hybrid"]
8573
+ }
8574
+ };
8575
+ });
8576
+
8057
8577
  // src/datasource/skills/lazykatok/skill.ts
8058
8578
  class LazykatokSkill {
8059
8579
  client;
@@ -8061,6 +8581,7 @@ class LazykatokSkill {
8061
8581
  instances;
8062
8582
  pollingIntervalMs;
8063
8583
  tags;
8584
+ sidecar;
8064
8585
  lastIndexedAt;
8065
8586
  constructor(options) {
8066
8587
  this.client = options.client;
@@ -8069,6 +8590,11 @@ class LazykatokSkill {
8069
8590
  this.pollingIntervalMs = options.pollingIntervalMs ?? DEFAULT_POLLING_INTERVAL_MS3;
8070
8591
  this.tags = options.tags ?? DEFAULT_KAKAO_TAGS2;
8071
8592
  this.lastIndexedAt = options.lastIndexedAt;
8593
+ this.sidecar = options.sidecar === undefined ? undefined : new CrawlerSidecar({
8594
+ ...options.sidecar,
8595
+ channelIds: options.channelIds ?? options.sidecar.channelIds ?? [],
8596
+ channelNames: options.channelNames ?? options.sidecar.channelNames ?? []
8597
+ });
8072
8598
  }
8073
8599
  describe() {
8074
8600
  return {
@@ -8100,6 +8626,17 @@ class LazykatokSkill {
8100
8626
  const sync = await this.client.sync(options?.signal);
8101
8627
  if (!sync.ok)
8102
8628
  return this.fail("datasource-index-failed", sync);
8629
+ if (this.sidecar !== undefined) {
8630
+ this.lastIndexedAt = Date.now();
8631
+ return {
8632
+ ok: true,
8633
+ instanceId: this.instanceId,
8634
+ skill: KAKAO_DATASOURCE_ID2,
8635
+ chunkCount: syncChunkCount(sync),
8636
+ indexedAt: this.lastIndexedAt,
8637
+ diagnostics: await this.syncSidecar()
8638
+ };
8639
+ }
8103
8640
  const indexResult = await this.client.index(options?.signal);
8104
8641
  if (!indexResult.ok)
8105
8642
  return this.fail("datasource-index-failed", indexResult);
@@ -8123,12 +8660,75 @@ class LazykatokSkill {
8123
8660
  });
8124
8661
  }
8125
8662
  }
8663
+ close() {
8664
+ this.sidecar?.close();
8665
+ }
8126
8666
  retrievalMethods() {
8667
+ const cliBm25 = new LazykatokBm25Method({ client: this.client, instanceId: this.instanceId, tags: this.tags });
8668
+ const { sidecar } = this;
8669
+ if (sidecar === undefined) {
8670
+ return [
8671
+ cliBm25,
8672
+ new LazykatokSemanticMethod({ client: this.client, instanceId: this.instanceId, tags: this.tags })
8673
+ ];
8674
+ }
8675
+ const shared = {
8676
+ sidecar,
8677
+ descriptor: sidecar.descriptor,
8678
+ datasourceId: KAKAO_DATASOURCE_ID2,
8679
+ skillType: KAKAO_SKILL_TYPE,
8680
+ instanceId: this.instanceId,
8681
+ tags: this.tags
8682
+ };
8683
+ const bm25 = new SidecarRetrievalMethod({ ...shared, mode: "bm25" });
8684
+ if (!sidecar.semanticEnabled)
8685
+ return [cliBm25, bm25];
8127
8686
  return [
8128
- new LazykatokBm25Method({ client: this.client, instanceId: this.instanceId, tags: this.tags }),
8129
- new LazykatokSemanticMethod({ client: this.client, instanceId: this.instanceId, tags: this.tags })
8687
+ cliBm25,
8688
+ bm25,
8689
+ new SidecarRetrievalMethod({ ...shared, mode: "semantic" }),
8690
+ new SidecarRetrievalMethod({ ...shared, mode: "hybrid" })
8130
8691
  ];
8131
8692
  }
8693
+ async syncSidecar() {
8694
+ if (this.sidecar === undefined || !this.sidecar.semanticEnabled)
8695
+ return [];
8696
+ const base = { instanceId: this.instanceId, source: KAKAO_DATASOURCE_ID2 };
8697
+ let result;
8698
+ try {
8699
+ result = await this.sidecar.sync();
8700
+ } catch (error) {
8701
+ result = { ok: false, message: error instanceof Error ? error.message : String(error) };
8702
+ }
8703
+ if (!result.ok) {
8704
+ return [
8705
+ {
8706
+ code: "datasource-index-failed",
8707
+ severity: "warning",
8708
+ message: `semantic-unavailable: ${result.message}`.slice(0, 300),
8709
+ ...base
8710
+ }
8711
+ ];
8712
+ }
8713
+ const diagnostics = [];
8714
+ if (result.skipped > 0) {
8715
+ diagnostics.push({
8716
+ code: "datasource-index-partial",
8717
+ severity: "warning",
8718
+ message: `semantic index skipped ${result.skipped} message(s) the embedder rejected; they stay searchable by BM25 and are retried when edited`,
8719
+ ...base
8720
+ });
8721
+ }
8722
+ if (result.remaining > 0) {
8723
+ diagnostics.push({
8724
+ code: "datasource-index-partial",
8725
+ severity: "info",
8726
+ message: `semantic index catching up: ${result.embedded} embedded this refresh, ${result.remaining} remaining; hybrid search uses BM25 for the rest until a later refresh`,
8727
+ ...base
8728
+ });
8729
+ }
8730
+ return diagnostics;
8731
+ }
8132
8732
  describeSources() {
8133
8733
  return this.instances.map((instanceId) => ({
8134
8734
  source: datasourceSourcePath(KAKAO_DATASOURCE_ID2, instanceId),
@@ -8202,10 +8802,22 @@ class LazykatokSkill {
8202
8802
  };
8203
8803
  }
8204
8804
  }
8805
+ function syncChunkCount(sync) {
8806
+ if (!("data" in sync) || typeof sync.data !== "object" || sync.data === null)
8807
+ return 0;
8808
+ const meta = "metadata" in sync.data && typeof sync.data.metadata === "object" && sync.data.metadata !== null ? sync.data.metadata : undefined;
8809
+ if (typeof meta?.chunks === "number")
8810
+ return meta.chunks;
8811
+ if ("messageCount" in sync.data && typeof sync.data.messageCount === "number")
8812
+ return sync.data.messageCount;
8813
+ return 0;
8814
+ }
8205
8815
  var KAKAO_DATASOURCE_ID2 = "kakao", KAKAO_SKILL_TYPE = "kakaotalk", DEFAULT_INSTANCE_ID4 = "default", DEFAULT_POLLING_INTERVAL_MS3, DEFAULT_KAKAO_TAGS2;
8206
8816
  var init_skill7 = __esm(() => {
8207
8817
  init_skip();
8208
8818
  init_scope2();
8819
+ init_sidecar();
8820
+ init_methods4();
8209
8821
  init_methods3();
8210
8822
  DEFAULT_POLLING_INTERVAL_MS3 = 15 * 60 * 1000;
8211
8823
  DEFAULT_KAKAO_TAGS2 = ["kakaotalk", "personal", "pii"];
@@ -8221,7 +8833,7 @@ var init_lazykatok = __esm(() => {
8221
8833
 
8222
8834
  // src/datasource/skills/mail-export/connector.ts
8223
8835
  import { readdir as readdir2, readFile as readFile4, stat as stat3 } from "node:fs/promises";
8224
- import { basename as basename5, dirname as dirname11, extname as extname3, join as join14 } from "node:path";
8836
+ import { basename as basename5, dirname as dirname12, extname as extname3, join as join14 } from "node:path";
8225
8837
  import { simpleParser as simpleParser2 } from "mailparser";
8226
8838
 
8227
8839
  class MailExportConnector {
@@ -8282,7 +8894,7 @@ class MailExportConnector {
8282
8894
  continue;
8283
8895
  }
8284
8896
  const stem = basename5(file, extname3(file));
8285
- const mailboxDir = basename5(dirname11(file));
8897
+ const mailboxDir = basename5(dirname12(file));
8286
8898
  const hierarchy = ["mailboxes", mailboxDir, stem];
8287
8899
  const extension = extname3(file).toLowerCase();
8288
8900
  const rawMessages = extension === ".mbox" ? splitMbox(bytes.toString("utf8")) : [bytes.toString("utf8")];
@@ -8392,14 +9004,14 @@ var init_mail_export = __esm(() => {
8392
9004
  });
8393
9005
 
8394
9006
  // src/datasource/skills/mailcrawl/config.ts
8395
- import { chmodSync, mkdirSync as mkdirSync7 } from "node:fs";
9007
+ import { chmodSync, mkdirSync as mkdirSync8 } from "node:fs";
8396
9008
  function validateMailcrawlInstanceId(instanceId) {
8397
9009
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(instanceId)) {
8398
9010
  throw new Error("mailcrawl instanceId must be a safe single path segment");
8399
9011
  }
8400
9012
  }
8401
9013
  function ensurePrivateMailcrawlDataDir(path) {
8402
- mkdirSync7(path, { recursive: true, mode: 448 });
9014
+ mkdirSync8(path, { recursive: true, mode: 448 });
8403
9015
  if (process.platform !== "win32")
8404
9016
  chmodSync(path, 448);
8405
9017
  }
@@ -8827,7 +9439,7 @@ class MailcrawlMethod {
8827
9439
  }
8828
9440
  }
8829
9441
  var modes, MAILCRAWL_MODES;
8830
- var init_methods4 = __esm(() => {
9442
+ var init_methods5 = __esm(() => {
8831
9443
  init_errors2();
8832
9444
  init_scope2();
8833
9445
  modes = ["bm25", "semantic", "hybrid"];
@@ -8977,515 +9589,17 @@ var init_skill9 = __esm(() => {
8977
9589
  init_scope2();
8978
9590
  init_client5();
8979
9591
  init_config3();
8980
- init_methods4();
9592
+ init_methods5();
8981
9593
  TAGS = ["mailcrawl", "email", "pii"];
8982
9594
  });
8983
9595
 
8984
9596
  // src/datasource/skills/mailcrawl/index.ts
8985
9597
  var init_mailcrawl = __esm(() => {
8986
9598
  init_client5();
8987
- init_methods4();
9599
+ init_methods5();
8988
9600
  init_skill9();
8989
9601
  });
8990
9602
 
8991
- // src/datasource/sidecar/fusion.ts
8992
- function encodeVector(vector) {
8993
- const floats = Float32Array.from(vector);
8994
- return new Uint8Array(floats.buffer, floats.byteOffset, floats.byteLength);
8995
- }
8996
- function decodeVector(blob) {
8997
- if (blob.byteLength % Float32Array.BYTES_PER_ELEMENT !== 0)
8998
- return;
8999
- return new Float32Array(new Uint8Array(blob).buffer);
9000
- }
9001
- function cosine2(a, b) {
9002
- if (a.length === 0 || a.length !== b.length)
9003
- return 0;
9004
- let dot = 0;
9005
- let normA = 0;
9006
- let normB = 0;
9007
- for (let index = 0;index < a.length; index += 1) {
9008
- const x = a[index] ?? 0;
9009
- const y = b[index] ?? 0;
9010
- dot += x * y;
9011
- normA += x * x;
9012
- normB += y * y;
9013
- }
9014
- if (normA === 0 || normB === 0)
9015
- return 0;
9016
- return dot / Math.sqrt(normA * normB);
9017
- }
9018
- function topKByCosine(query, rows, k) {
9019
- if (k <= 0)
9020
- return [];
9021
- const scored = [];
9022
- for (const row of rows) {
9023
- const score = cosine2(query, row.vector);
9024
- if (score > 0)
9025
- scored.push({ key: row.key, score });
9026
- }
9027
- scored.sort((a, b) => b.score - a.score || a.key.localeCompare(b.key));
9028
- return scored.slice(0, k);
9029
- }
9030
- function reciprocalRankFusion(lists) {
9031
- const scores = new Map;
9032
- for (const list of lists) {
9033
- const seen = new Set;
9034
- for (const [index, key] of list.entries()) {
9035
- if (seen.has(key))
9036
- continue;
9037
- seen.add(key);
9038
- scores.set(key, (scores.get(key) ?? 0) + 1 / (RRF_K + index + 1));
9039
- }
9040
- }
9041
- return [...scores.entries()].map(([key, score]) => ({ key, score })).sort((a, b) => b.score - a.score);
9042
- }
9043
- var RRF_K = 60;
9044
-
9045
- // src/datasource/sidecar/index.ts
9046
- import { createHash as createHash6 } from "node:crypto";
9047
- import { mkdirSync as mkdirSync8, statSync } from "node:fs";
9048
- import { dirname as dirname12 } from "node:path";
9049
- import { DatabaseSync } from "node:sqlite";
9050
- import { pathToFileURL } from "node:url";
9051
-
9052
- class CrawlerSidecar {
9053
- descriptor;
9054
- options;
9055
- select;
9056
- where;
9057
- opened;
9058
- attached;
9059
- lastSemantic;
9060
- constructor(options) {
9061
- this.options = options;
9062
- const { descriptor } = options;
9063
- this.descriptor = descriptor;
9064
- const metadataColumns = descriptor.metadata.map((column, index) => `${column.sql} as m${index}`);
9065
- this.select = [
9066
- `${descriptor.keyExpression} as k`,
9067
- `${descriptor.textExpression} as content`,
9068
- `${descriptor.titleExpression ?? "null"} as title`,
9069
- `${descriptor.epochSecondsExpression ?? "null"} as epoch`,
9070
- ...metadataColumns
9071
- ].join(", ");
9072
- const scope = descriptor.scopeExpression !== undefined && options.workspaceId !== undefined;
9073
- const allowlist = [
9074
- ...(options.channelIds?.length ?? 0) > 0 ? [`${descriptor.channelIdExpression} in (${options.channelIds?.map(() => "?").join(", ")})`] : [],
9075
- ...(options.channelNames?.length ?? 0) > 0 ? [`${descriptor.channelNameExpression} in (${options.channelNames?.map(() => "?").join(", ")})`] : []
9076
- ];
9077
- this.where = [
9078
- descriptor.liveWhere,
9079
- ...scope ? [`${descriptor.scopeExpression} = ?`] : [],
9080
- ...allowlist.length > 0 ? [`(${allowlist.join(" or ")})`] : []
9081
- ].join(" and ");
9082
- }
9083
- get db() {
9084
- if (this.opened !== undefined)
9085
- return this.opened;
9086
- mkdirSync8(dirname12(this.options.sidecarDbPath), { recursive: true });
9087
- const db = new DatabaseSync(this.options.sidecarDbPath);
9088
- db.exec(`pragma busy_timeout = ${BUSY_TIMEOUT_MS}`);
9089
- db.exec("pragma journal_mode = wal");
9090
- db.exec(`
9091
- create table if not exists sidecar_meta (k text primary key, v text not null);
9092
- create table if not exists sidecar_vec (message_key text primary key, hash text not null, vec blob not null);
9093
- `);
9094
- this.opened = db;
9095
- return db;
9096
- }
9097
- attachCrawler() {
9098
- let file;
9099
- try {
9100
- const { dev, ino } = statSync(this.options.crawlerDbPath);
9101
- file = { dev, ino };
9102
- } catch {
9103
- this.detachCrawler();
9104
- return false;
9105
- }
9106
- if (this.attached !== undefined) {
9107
- if (this.attached.dev === file.dev && this.attached.ino === file.ino)
9108
- return true;
9109
- this.detachCrawler();
9110
- }
9111
- const crawlerUri = `${pathToFileURL(this.options.crawlerDbPath).href}?mode=ro`.replaceAll("'", "''");
9112
- try {
9113
- this.db.exec(`attach database '${crawlerUri}' as crawler`);
9114
- } catch {
9115
- return false;
9116
- }
9117
- const { descriptor } = this.options;
9118
- try {
9119
- this.db.prepare(`select ${this.select} from ${descriptor.fromClause} where ${this.where} limit 0`).all(...this.whereParams());
9120
- this.db.prepare(`select 1 from ${descriptor.fts.table} limit 0`).all();
9121
- } catch {
9122
- this.db.exec("detach database crawler");
9123
- return false;
9124
- }
9125
- this.attached = file;
9126
- this.lastSemantic = undefined;
9127
- return true;
9128
- }
9129
- detachCrawler() {
9130
- if (this.attached === undefined)
9131
- return;
9132
- this.attached = undefined;
9133
- this.lastSemantic = undefined;
9134
- try {
9135
- this.opened?.exec("detach database crawler");
9136
- } catch {}
9137
- }
9138
- get semanticEnabled() {
9139
- return this.options.embedder !== undefined;
9140
- }
9141
- close() {
9142
- this.opened?.close();
9143
- this.opened = undefined;
9144
- this.attached = undefined;
9145
- this.lastSemantic = undefined;
9146
- }
9147
- whereParams() {
9148
- const { descriptor, workspaceId, channelIds = [], channelNames = [] } = this.options;
9149
- return [
9150
- ...descriptor.scopeExpression !== undefined && workspaceId !== undefined ? [workspaceId] : [],
9151
- ...channelIds,
9152
- ...channelNames
9153
- ];
9154
- }
9155
- searchLexical(query, topK) {
9156
- const match = toMatchQuery(query);
9157
- if (match === undefined || topK <= 0 || !this.attachCrawler())
9158
- return [];
9159
- const { descriptor } = this.options;
9160
- const rows = this.db.prepare(`select ${this.select}, bm25(${descriptor.fts.table}) as rank
9161
- from ${descriptor.fromClause} join ${descriptor.fts.table} on ${descriptor.fts.joinOn}
9162
- where ${descriptor.fts.table} match ? and ${this.where}
9163
- order by rank limit ?`).all(match, ...this.whereParams(), topK);
9164
- return rows.map((row) => this.toHit(this.toRow(row), -Number(row.rank)));
9165
- }
9166
- async searchSemantic(query, topK) {
9167
- const trimmed = query.trim();
9168
- if (this.options.embedder === undefined || trimmed.length === 0 || topK <= 0 || !this.attachCrawler())
9169
- return [];
9170
- const ranked = await this.rankSemantic(trimmed, topK * POOL_FACTOR);
9171
- if (ranked.length === 0)
9172
- return [];
9173
- const live = this.hydrate(ranked.map((entry) => entry.key));
9174
- const hits = [];
9175
- for (const entry of ranked) {
9176
- const row = live.get(entry.key);
9177
- if (row !== undefined)
9178
- hits.push(this.toHit(row, entry.score));
9179
- if (hits.length >= topK)
9180
- break;
9181
- }
9182
- return hits;
9183
- }
9184
- async rankSemantic(query, pool) {
9185
- const now = Date.now();
9186
- let cached = this.lastSemantic;
9187
- if (cached === undefined || cached.query !== query || now - cached.at > SEMANTIC_REUSE_MS) {
9188
- const embedder = this.options.embedder;
9189
- if (embedder === undefined)
9190
- return [];
9191
- cached = {
9192
- query,
9193
- at: now,
9194
- vector: embedder.embed([query]).then((vectors) => vectors[0], () => {
9195
- return;
9196
- }),
9197
- ranked: undefined
9198
- };
9199
- this.lastSemantic = cached;
9200
- }
9201
- const vector = await cached.vector;
9202
- if (vector === undefined) {
9203
- if (this.lastSemantic === cached)
9204
- this.lastSemantic = undefined;
9205
- return [];
9206
- }
9207
- if (cached.ranked === undefined || cached.ranked.pool < pool) {
9208
- const wide = pool * POOL_FACTOR;
9209
- cached.ranked = { pool: wide, keys: topKByCosine(vector, this.storedVectors(), wide) };
9210
- }
9211
- return cached.ranked.keys.slice(0, pool);
9212
- }
9213
- async searchHybrid(query, topK) {
9214
- if (topK <= 0)
9215
- return [];
9216
- const pool = topK * POOL_FACTOR;
9217
- const lexical = this.searchLexical(query, pool);
9218
- const semantic = await this.searchSemantic(query, pool);
9219
- if (semantic.length === 0)
9220
- return lexical.slice(0, topK);
9221
- const byKey = new Map;
9222
- for (const hit of [...semantic, ...lexical])
9223
- byKey.set(hit.key, hit);
9224
- const fused = [];
9225
- for (const entry of reciprocalRankFusion([lexical.map((hit) => hit.key), semantic.map((hit) => hit.key)])) {
9226
- const hit = byKey.get(entry.key);
9227
- if (hit !== undefined)
9228
- fused.push({ ...hit, score: entry.score });
9229
- if (fused.length >= topK)
9230
- break;
9231
- }
9232
- return fused;
9233
- }
9234
- async sync() {
9235
- const { embedder } = this.options;
9236
- if (embedder === undefined)
9237
- return { ok: false, message: "no embedder configured" };
9238
- if (!this.attachCrawler())
9239
- return { ok: false, message: "crawler archive not available yet" };
9240
- let identity;
9241
- try {
9242
- identity = await embedder.identity();
9243
- } catch (error) {
9244
- return { ok: false, message: errorMessage(error) };
9245
- }
9246
- this.adoptIdentity(identity);
9247
- const pruned = this.prune();
9248
- const pending = this.pendingKeys();
9249
- const batchSize = Math.max(1, this.options.batchSize ?? DEFAULT_BATCH_SIZE);
9250
- const budget = Math.max(1, this.options.maxEmbedPerSync ?? DEFAULT_MAX_EMBED_PER_SYNC);
9251
- const target = Math.min(pending.length, budget);
9252
- const embed = async (rows) => {
9253
- const vectors = await embedder.embed(rows.map((row) => row.input));
9254
- if (vectors.length !== rows.length) {
9255
- throw new Error(`embedder returned ${vectors.length} vectors for ${rows.length} messages`);
9256
- }
9257
- const wrong = vectors.find((vector) => vector.length !== identity.dimension);
9258
- if (wrong !== undefined) {
9259
- throw new Error(`embedder returned a ${wrong.length}-dimension vector, expected ${identity.dimension}`);
9260
- }
9261
- return vectors;
9262
- };
9263
- let embedded = 0;
9264
- let skipped = 0;
9265
- for (let offset = 0;offset < target; offset += batchSize) {
9266
- const rows = [...this.hydrate(pending.slice(offset, Math.min(offset + batchSize, target))).values()];
9267
- if (rows.length === 0)
9268
- continue;
9269
- try {
9270
- this.store(rows, await embed(rows));
9271
- embedded += rows.length;
9272
- } catch (batchError) {
9273
- const rejected = [];
9274
- let lastError = batchError;
9275
- for (const row of rows) {
9276
- try {
9277
- this.store([row], await embed([row]));
9278
- embedded += 1;
9279
- } catch (error) {
9280
- rejected.push(row);
9281
- lastError = error;
9282
- }
9283
- }
9284
- if (rejected.length === 0)
9285
- continue;
9286
- if (embedded === 0)
9287
- return { ok: false, message: errorMessage(lastError) };
9288
- this.store(rejected, undefined);
9289
- skipped += rejected.length;
9290
- }
9291
- }
9292
- return { ok: true, embedded, skipped, pruned, remaining: pending.length - embedded - skipped };
9293
- }
9294
- adoptIdentity(identity) {
9295
- const serialized = JSON.stringify(identity);
9296
- const current = this.db.prepare("select v from sidecar_meta where k = ?").get(IDENTITY_KEY);
9297
- if (current?.v === serialized)
9298
- return;
9299
- this.transaction(() => {
9300
- this.db.exec("delete from sidecar_vec");
9301
- this.db.prepare("insert or replace into sidecar_meta (k, v) values (?, ?)").run(IDENTITY_KEY, serialized);
9302
- });
9303
- this.lastSemantic = undefined;
9304
- }
9305
- prune() {
9306
- const { descriptor } = this.options;
9307
- const result = this.db.prepare(`delete from sidecar_vec where message_key not in (
9308
- select ${descriptor.keyExpression} from ${descriptor.fromClause} where ${this.where}
9309
- )`).run(...this.whereParams());
9310
- if (Number(result.changes) > 0)
9311
- this.lastSemantic = undefined;
9312
- return Number(result.changes);
9313
- }
9314
- pendingKeys() {
9315
- const { descriptor } = this.options;
9316
- const rows = this.db.prepare(`select ${this.select}, v.hash as stored_hash
9317
- from ${descriptor.fromClause}
9318
- left join sidecar_vec v on v.message_key = ${descriptor.keyExpression}
9319
- where ${this.where}`).iterate(...this.whereParams());
9320
- const pending = [];
9321
- for (const row of rows) {
9322
- if (row.stored_hash !== this.toRow(row).hash)
9323
- pending.push(String(row.k));
9324
- }
9325
- return pending;
9326
- }
9327
- hydrate(keys) {
9328
- const { descriptor } = this.options;
9329
- const out = new Map;
9330
- if (keys.length === 0)
9331
- return out;
9332
- const rows = this.db.prepare(`select ${this.select} from ${descriptor.fromClause}
9333
- where ${descriptor.keyExpression} in (${keys.map(() => "?").join(", ")}) and ${this.where}`).all(...keys, ...this.whereParams());
9334
- const byKey = new Map(rows.map((row) => [String(row.k), this.toRow(row)]));
9335
- for (const key of keys) {
9336
- const row = byKey.get(key);
9337
- if (row !== undefined)
9338
- out.set(key, row);
9339
- }
9340
- return out;
9341
- }
9342
- store(rows, vectors) {
9343
- const upsert = this.db.prepare("insert or replace into sidecar_vec (message_key, hash, vec) values (?, ?, ?)");
9344
- this.transaction(() => {
9345
- for (const [index, row] of rows.entries()) {
9346
- upsert.run(row.key, row.hash, encodeVector(vectors?.[index] ?? []));
9347
- }
9348
- });
9349
- this.lastSemantic = undefined;
9350
- }
9351
- *storedVectors() {
9352
- const rows = this.db.prepare("select message_key, vec from sidecar_vec where length(vec) > 0").iterate();
9353
- for (const row of rows) {
9354
- const vector = row.vec instanceof Uint8Array ? decodeVector(row.vec) : undefined;
9355
- if (vector !== undefined)
9356
- yield { key: String(row.message_key), vector };
9357
- }
9358
- }
9359
- transaction(work) {
9360
- this.db.exec("begin immediate");
9361
- try {
9362
- work();
9363
- this.db.exec("commit");
9364
- } catch (error) {
9365
- this.db.exec("rollback");
9366
- throw error;
9367
- }
9368
- }
9369
- toRow(row) {
9370
- const metadata = {};
9371
- for (const [index, column] of this.options.descriptor.metadata.entries()) {
9372
- const value = row[`m${index}`];
9373
- if (typeof value === "string" && value.length > 0 || typeof value === "number")
9374
- metadata[column.key] = String(value);
9375
- }
9376
- const title = typeof row.title === "string" && row.title.length > 0 ? row.title : undefined;
9377
- const epoch = typeof row.epoch === "number" && Number.isFinite(row.epoch) ? row.epoch : undefined;
9378
- const content = String(row.content ?? "");
9379
- const input = `${title ?? ""}
9380
- ${content}`.slice(0, MAX_EMBED_INPUT_CHARS);
9381
- return {
9382
- key: String(row.k),
9383
- content,
9384
- title,
9385
- epoch,
9386
- metadata,
9387
- input,
9388
- hash: createHash6("sha1").update(input).digest("hex")
9389
- };
9390
- }
9391
- toHit(row, score) {
9392
- return {
9393
- key: row.key,
9394
- content: row.content,
9395
- score,
9396
- ...row.title !== undefined ? { title: row.title } : {},
9397
- ...row.epoch !== undefined ? { publishedAt: Math.round(row.epoch * 1000) } : {},
9398
- metadata: row.metadata
9399
- };
9400
- }
9401
- }
9402
- function toMatchQuery(text) {
9403
- const terms = [...new Set(text.match(/[\p{L}\p{N}_]+/gu) ?? [])];
9404
- return terms.length === 0 ? undefined : terms.map((term) => `"${term}"`).join(" OR ");
9405
- }
9406
- function errorMessage(error) {
9407
- return error instanceof Error ? error.message : String(error);
9408
- }
9409
- var DEFAULT_BATCH_SIZE = 256, DEFAULT_MAX_EMBED_PER_SYNC = 2048, MAX_EMBED_INPUT_CHARS = 8192, BUSY_TIMEOUT_MS = 5000, POOL_FACTOR = 3, SEMANTIC_REUSE_MS = 1e4, IDENTITY_KEY = "identity";
9410
- var init_sidecar = () => {};
9411
-
9412
- // src/datasource/sidecar/methods.ts
9413
- function sidecarMethodName(datasourceId, mode) {
9414
- return mode === "bm25" ? `${datasourceId}-sidecar-bm25` : `${datasourceId}-${mode}`;
9415
- }
9416
-
9417
- class SidecarRetrievalMethod {
9418
- options;
9419
- constructor(options) {
9420
- this.options = options;
9421
- }
9422
- describe() {
9423
- const { datasourceId, skillType, mode, tags } = this.options;
9424
- const { type, text, capabilities } = DESCRIPTIONS[mode];
9425
- return {
9426
- name: sidecarMethodName(datasourceId, mode),
9427
- type,
9428
- description: `${text} over ${skillType} via a read-only sidecar index`,
9429
- status: "active",
9430
- capabilities: [...capabilities, "scoped", "path-opaque-sources"],
9431
- datasourceId,
9432
- tags
9433
- };
9434
- }
9435
- async retrieve(query, options) {
9436
- const trimmed = query.trim();
9437
- if (trimmed.length === 0)
9438
- return [];
9439
- const topK = options.topK ?? DEFAULT_TOP_K5;
9440
- const { sidecar, mode } = this.options;
9441
- const hits = mode === "bm25" ? sidecar.searchLexical(trimmed, topK) : mode === "semantic" ? await sidecar.searchSemantic(trimmed, topK) : await sidecar.searchHybrid(trimmed, topK);
9442
- const results = [];
9443
- for (const hit of hits) {
9444
- const result = this.toResult(hit, options);
9445
- if (result !== undefined)
9446
- results.push(result);
9447
- }
9448
- return results;
9449
- }
9450
- toResult(hit, options) {
9451
- const { descriptor, datasourceId, instanceId, mode } = this.options;
9452
- const chunkId = descriptor.chunkId(hit.key, hit.metadata);
9453
- const source = datasourceSourcePath(datasourceId, instanceId, chunkId);
9454
- if (!matchesDatasourceScope(source, options.scope))
9455
- return;
9456
- const method = sidecarMethodName(datasourceId, mode);
9457
- return {
9458
- id: `${datasourceId}:${instanceId}:${chunkId}`,
9459
- content: hit.content,
9460
- source,
9461
- score: hit.score,
9462
- metadata: {
9463
- ...hit.metadata,
9464
- method,
9465
- datasourceId,
9466
- instanceId,
9467
- backend: "sidecar",
9468
- ...hit.title !== undefined ? { title: hit.title } : {},
9469
- hierarchy: descriptor.hierarchy(hit.metadata).join("/"),
9470
- ...hit.publishedAt !== undefined ? { publishedAt: hit.publishedAt } : {}
9471
- }
9472
- };
9473
- }
9474
- }
9475
- var DEFAULT_TOP_K5 = 20, DESCRIPTIONS;
9476
- var init_methods5 = __esm(() => {
9477
- init_scope2();
9478
- DESCRIPTIONS = {
9479
- bm25: { type: "bm25", text: "BM25-ranked FTS5 retrieval", capabilities: ["lexical", "fts5", "bm25"] },
9480
- semantic: { type: "vector", text: "Semantic (embedding) retrieval", capabilities: ["semantic"] },
9481
- hybrid: {
9482
- type: "hybrid",
9483
- text: "Hybrid BM25 + semantic retrieval (reciprocal rank fusion)",
9484
- capabilities: ["lexical", "semantic", "hybrid"]
9485
- }
9486
- };
9487
- });
9488
-
9489
9603
  // src/datasource/crawler-skill.ts
9490
9604
  class CrawlerDatasourceSkill {
9491
9605
  definition;
@@ -9766,7 +9880,7 @@ var init_crawler_skill = __esm(() => {
9766
9880
  init_errors2();
9767
9881
  init_scope2();
9768
9882
  init_sidecar();
9769
- init_methods5();
9883
+ init_methods4();
9770
9884
  DEFAULT_POLLING_INTERVAL_MS4 = 15 * 60 * 1000;
9771
9885
  });
9772
9886
 
@@ -11555,24 +11669,42 @@ var init_factory = __esm(() => {
11555
11669
  ...config.channels?.ids !== undefined ? { chatIds: config.channels.ids } : {},
11556
11670
  ...larkConnectorOptions(config.connector)
11557
11671
  }),
11558
- kakao: (config, _workspaceRoot, _registrationName) => new LazykatokSkill({
11559
- client: new LazykatokClient({
11560
- ...config.connector
11561
- }),
11562
- ...config.instanceId !== undefined ? { instanceId: config.instanceId } : {},
11563
- ...config.pollingIntervalMs !== undefined ? { pollingIntervalMs: config.pollingIntervalMs } : {},
11564
- ...config.tags !== undefined ? { tags: config.tags } : {}
11565
- }),
11672
+ kakao: (config, workspaceRoot, registrationName) => {
11673
+ const connector = config.connector ?? {};
11674
+ const dataDir = typeof connector.workspacePath === "string" && connector.workspacePath.length > 0 ? connector.workspacePath : join16(homedir4(), "Library", "Application Support", "katok");
11675
+ const sidecar = resolveSidecarOptions({
11676
+ descriptor: CRAWLER_DESCRIPTORS.kakao,
11677
+ connector: config.connector,
11678
+ workspaceRoot,
11679
+ registrationName,
11680
+ instanceId: config.instanceId ?? "default",
11681
+ defaultCrawlerDb: join16(dataDir, "archive.sqlite3")
11682
+ });
11683
+ return new LazykatokSkill({
11684
+ client: new LazykatokClient(connector),
11685
+ ...config.instanceId !== undefined ? { instanceId: config.instanceId } : {},
11686
+ ...config.pollingIntervalMs !== undefined ? { pollingIntervalMs: config.pollingIntervalMs } : {},
11687
+ ...config.tags !== undefined ? { tags: config.tags } : {},
11688
+ channelIds: config.channels?.ids,
11689
+ channelNames: config.channels?.names,
11690
+ ...sidecar === undefined ? {} : { sidecar }
11691
+ });
11692
+ },
11566
11693
  github: (config, workspaceRoot, registrationName) => new GitHubSkill({
11567
11694
  ...common(config, workspaceRoot),
11568
11695
  skillName: registrationName,
11569
11696
  connectorOptions: config.connector
11570
11697
  }),
11571
- "github-gist": (config, workspaceRoot, registrationName) => new GitHubGistSkill({
11572
- ...common(config, workspaceRoot),
11573
- skillName: registrationName,
11574
- connectorOptions: config.connector
11575
- }),
11698
+ "github-gist": (config, workspaceRoot, registrationName) => {
11699
+ const { semantic, ...connectorOptions } = config.connector ?? {};
11700
+ const semanticOff = semantic === false || typeof semantic === "object" && semantic !== null && semantic.enabled === false;
11701
+ return new GitHubGistSkill({
11702
+ ...common(config, workspaceRoot),
11703
+ skillName: registrationName,
11704
+ connectorOptions,
11705
+ ...semanticOff ? { semantic: { enabled: false } } : {}
11706
+ });
11707
+ },
11576
11708
  "cloud-drive": (config, workspaceRoot, registrationName) => new CloudDriveSkill({
11577
11709
  ...common(config, workspaceRoot),
11578
11710
  skillName: registrationName,
@@ -12073,11 +12205,6 @@ var init_types4 = __esm(() => {
12073
12205
  label: "xAI",
12074
12206
  description: "xAI Responses web_search via the agent's xAI model credentials (XAI_API_KEY)"
12075
12207
  },
12076
- {
12077
- value: "perplexity",
12078
- label: "Perplexity",
12079
- description: "Anonymous Perplexity ask endpoint; no key or signup required"
12080
- },
12081
12208
  {
12082
12209
  value: "parallel",
12083
12210
  label: "Parallel",
@@ -12088,16 +12215,21 @@ var init_types4 = __esm(() => {
12088
12215
  label: "SearXNG",
12089
12216
  description: "Advanced: requires a self-hosted SEARXNG_ENDPOINT; never part of the default experience"
12090
12217
  },
12091
- {
12092
- value: "startpage",
12093
- label: "Startpage",
12094
- description: "Credential-free scrape of Startpage (Google-backed) results; may be bot-challenged"
12095
- },
12096
12218
  {
12097
12219
  value: "duckduckgo",
12098
12220
  label: "DuckDuckGo",
12099
12221
  description: "Credential-free best-effort fallback; may be bot-challenged on datacenter/shared-egress IPs"
12100
12222
  },
12223
+ {
12224
+ value: "perplexity",
12225
+ label: "Perplexity",
12226
+ description: "Anonymous Perplexity ask endpoint; no key or signup required"
12227
+ },
12228
+ {
12229
+ value: "startpage",
12230
+ label: "Startpage",
12231
+ description: "Credential-free scrape of Startpage (Google-backed) results; may be bot-challenged"
12232
+ },
12101
12233
  {
12102
12234
  value: "ecosia",
12103
12235
  label: "Ecosia",
@@ -12227,7 +12359,7 @@ function migrateLegacyConfig(configPath, legacyPath) {
12227
12359
  const legacy = readConfigFile(legacyPath, true);
12228
12360
  const legacyBytes = readFileSync14(legacyPath);
12229
12361
  const migrated = normalizeLegacyConfigPaths(legacy ?? {}, dirname13(legacyPath));
12230
- const migratedPathsUnchanged = legacy?.workspacePath === migrated.workspacePath && legacy?.memoryPath === migrated.memoryPath && JSON.stringify(legacy?.searchPaths) === JSON.stringify(migrated.searchPaths);
12362
+ const migratedPathsUnchanged = legacy?.workspacePath === migrated.workspacePath && legacy?.memoryPath === migrated.memoryPath && JSON.stringify(legacy?.searchPaths) === JSON.stringify(migrated.searchPaths) && JSON.stringify(legacy?.excludePaths) === JSON.stringify(migrated.excludePaths);
12231
12363
  const migratedBytes = migratedPathsUnchanged ? legacyBytes : `${JSON.stringify(migrated, null, 2)}
12232
12364
  `;
12233
12365
  mkdirSync11(dirname13(configPath), { recursive: true });
@@ -12265,15 +12397,25 @@ function resolveConfigFileReadOnly(configPath, explicit, legacyPath) {
12265
12397
  function resolveSearchPaths(searchPaths, origin) {
12266
12398
  return searchPaths.map((searchPath) => resolvePersistedPath(searchPath, origin));
12267
12399
  }
12400
+ function resolveExcludePaths(value, origin) {
12401
+ if (value === undefined)
12402
+ return;
12403
+ if (!Array.isArray(value) || value.some((path) => typeof path !== "string" || path.length === 0)) {
12404
+ throw new ConfigError("Config field 'excludePaths' must be a non-empty string array");
12405
+ }
12406
+ return resolveSearchPaths(value, origin);
12407
+ }
12268
12408
  function resolvePersistedPath(path, origin) {
12269
12409
  return isAbsolute5(path) ? path : resolve7(origin, path);
12270
12410
  }
12271
12411
  function normalizeLegacyConfigPaths(partial, origin) {
12272
12412
  const workspacePath = resolvePersistedPath(partial.workspacePath ?? ".", origin);
12413
+ const excludePaths = resolveExcludePaths(partial.excludePaths, workspacePath);
12273
12414
  return {
12274
12415
  ...partial,
12275
12416
  workspacePath,
12276
12417
  ...partial.searchPaths === undefined ? {} : { searchPaths: resolveSearchPaths(partial.searchPaths, workspacePath) },
12418
+ ...excludePaths === undefined ? {} : { excludePaths },
12277
12419
  ...partial.memoryPath === undefined ? {} : { memoryPath: resolvePersistedPath(partial.memoryPath, workspacePath) }
12278
12420
  };
12279
12421
  }
@@ -12728,10 +12870,7 @@ function resolveConfig(input) {
12728
12870
  const configOrigin = dirname13(resolve7(configPath));
12729
12871
  const fileWorkspacePath = typeof file.workspacePath === "string" ? resolvePersistedPath(file.workspacePath, configOrigin) : undefined;
12730
12872
  const fileSearchPaths = file.searchPaths ? resolveSearchPaths(file.searchPaths, fileWorkspacePath ?? configOrigin) : undefined;
12731
- if (file.excludePaths !== undefined && (!Array.isArray(file.excludePaths) || file.excludePaths.some((path) => typeof path !== "string" || path.length === 0))) {
12732
- throw new ConfigError("Config field 'excludePaths' must be a non-empty string array");
12733
- }
12734
- const fileExcludePaths = file.excludePaths ? resolveSearchPaths(file.excludePaths, fileWorkspacePath ?? configOrigin) : undefined;
12873
+ const fileExcludePaths = resolveExcludePaths(file.excludePaths, fileWorkspacePath ?? configOrigin);
12735
12874
  const searchPaths = flagSearchPaths ?? envSearchPaths ?? fileSearchPaths ?? defaultSearchPaths;
12736
12875
  const flagWorkspacePath = flagString(flags, "workspace");
12737
12876
  const envWorkspacePath = envString(env, "AUTORAG_WORKSPACE");
@@ -13467,7 +13606,7 @@ function writeDefaultConfig(path, partial, opts = {}) {
13467
13606
  full.dupey = partial.dupey ?? { enabled: true };
13468
13607
  full.excludeExactDuplicates = partial.excludeExactDuplicates ?? true;
13469
13608
  if (partial.excludePaths !== undefined)
13470
- full.excludePaths = resolveSearchPaths(partial.excludePaths, workspacePath);
13609
+ full.excludePaths = resolveExcludePaths(partial.excludePaths, workspacePath);
13471
13610
  if (partial.limits !== undefined)
13472
13611
  full.limits = normalizeLimitsConfig(partial.limits);
13473
13612
  if (partial.refresh !== undefined)
@@ -13611,7 +13750,7 @@ var init_config4 = __esm(() => {
13611
13750
 
13612
13751
  // src/cli/commands/init.ts
13613
13752
  import { existsSync as existsSync11, readFileSync as readFileSync15 } from "node:fs";
13614
- import { dirname as dirname14 } from "node:path";
13753
+ import { dirname as dirname14, resolve as resolve8 } from "node:path";
13615
13754
  async function runInit(ctx) {
13616
13755
  const flags = ctx.flags;
13617
13756
  const partial = {};
@@ -13619,6 +13758,14 @@ async function runInit(ctx) {
13619
13758
  if (searchPathsFlag) {
13620
13759
  partial.searchPaths = searchPathsFlag.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
13621
13760
  }
13761
+ const excludedPaths = ctx.repeatedFlags?.exclude ?? (typeof flags.exclude === "string" ? [flags.exclude] : undefined);
13762
+ if (flags.exclude !== undefined || excludedPaths !== undefined) {
13763
+ if (excludedPaths === undefined || excludedPaths.length === 0 || excludedPaths.some((path) => path.trim().length === 0)) {
13764
+ ctx.stderr(renderError(new ConfigError("--exclude requires a non-empty path"), { json: ctx.json, debug: ctx.debug }));
13765
+ return 2;
13766
+ }
13767
+ partial.excludePaths = excludedPaths.map((path) => resolve8(ctx.cwd, path));
13768
+ }
13622
13769
  if (typeof flags.workspace === "string")
13623
13770
  partial.workspacePath = flags.workspace;
13624
13771
  if (typeof flags["memory-path"] === "string")
@@ -13683,6 +13830,7 @@ async function runInit(ctx) {
13683
13830
  const legacy = JSON.parse(readFileSync15(legacyPath, "utf8"));
13684
13831
  const normalizedLegacy = normalizeLegacyConfigPaths(legacy, dirname14(legacyPath));
13685
13832
  partial.searchPaths ??= normalizedLegacy.searchPaths;
13833
+ partial.excludePaths ??= normalizedLegacy.excludePaths;
13686
13834
  partial.workspacePath ??= normalizedLegacy.workspacePath;
13687
13835
  partial.memoryPath ??= normalizedLegacy.memoryPath;
13688
13836
  partial.model ??= legacy.model;
@@ -13809,7 +13957,20 @@ function configuredVectorDimension(workspacePath) {
13809
13957
  const dimension = options.dimension;
13810
13958
  return typeof dimension === "number" ? dimension : undefined;
13811
13959
  }
13812
- function rewriteEmbedderConfig(workspacePath, embedder, chunkerConfig = {}) {
13960
+ function configuredLexicalLanguage(workspacePath) {
13961
+ let raw;
13962
+ try {
13963
+ raw = readFileSync16(minSyncConfigPath(workspacePath), "utf8");
13964
+ } catch {
13965
+ return;
13966
+ }
13967
+ const lexical = parse2(raw).lexical;
13968
+ if (typeof lexical !== "object" || lexical === null || Array.isArray(lexical))
13969
+ return "simple";
13970
+ const language = lexical.language;
13971
+ return typeof language === "string" ? language : "simple";
13972
+ }
13973
+ function rewriteEmbedderConfig(workspacePath, embedder, managed = {}) {
13813
13974
  const configPath = minSyncConfigPath(workspacePath);
13814
13975
  let raw;
13815
13976
  try {
@@ -13852,8 +14013,13 @@ function rewriteEmbedderConfig(workspacePath, embedder, chunkerConfig = {}) {
13852
14013
  }
13853
14014
  if (embedder.dimension !== undefined)
13854
14015
  options.dimension = embedder.dimension;
13855
- if (chunkerConfig.maxChunkSize !== undefined)
13856
- chunkerOptions.max_chunk_size = chunkerConfig.maxChunkSize;
14016
+ if (managed.maxChunkSize !== undefined)
14017
+ chunkerOptions.max_chunk_size = managed.maxChunkSize;
14018
+ if (managed.lexicalLanguage !== undefined) {
14019
+ const lexical = typeof parsed.lexical === "object" && parsed.lexical !== null && !Array.isArray(parsed.lexical) ? parsed.lexical : {};
14020
+ parsed.lexical = lexical;
14021
+ lexical.language = managed.lexicalLanguage;
14022
+ }
13857
14023
  writeFileSync9(configPath, stringify(parsed));
13858
14024
  return true;
13859
14025
  }
@@ -14265,9 +14431,9 @@ var init_retry = __esm(() => {
14265
14431
 
14266
14432
  // src/dupey/cli.ts
14267
14433
  import { spawn as spawn10 } from "node:child_process";
14268
- import { resolve as resolve8 } from "node:path";
14434
+ import { resolve as resolve9 } from "node:path";
14269
14435
  async function scanWithDupey(dir, options = {}) {
14270
- const absoluteDir = resolve8(dir);
14436
+ const absoluteDir = resolve9(dir);
14271
14437
  const stdout = options.run ? await options.run(["scan", absoluteDir, "--json"]) : await spawnDupey(options.executable ?? "dupey", ["scan", absoluteDir, "--json"], options);
14272
14438
  let parsed;
14273
14439
  try {
@@ -14337,13 +14503,13 @@ var init_cli = __esm(() => {
14337
14503
 
14338
14504
  // src/dupey/filter.ts
14339
14505
  import { stat as stat5 } from "node:fs/promises";
14340
- import { resolve as resolve9 } from "node:path";
14506
+ import { resolve as resolve10 } from "node:path";
14341
14507
  async function selectExactDuplicateExclusions(root, scan, isUnavailable) {
14342
14508
  const byHash = new Map;
14343
14509
  for (const file of scan.files) {
14344
14510
  if (typeof file.content_hash !== "string" || file.content_hash.length === 0)
14345
14511
  continue;
14346
- const path = resolve9(root, file.path);
14512
+ const path = resolve10(root, file.path);
14347
14513
  const group = byHash.get(file.content_hash) ?? [];
14348
14514
  group.push(path);
14349
14515
  byHash.set(file.content_hash, group);
@@ -14380,7 +14546,7 @@ function dupeyExactRanking(scan) {
14380
14546
  return;
14381
14547
  const path = entry.path;
14382
14548
  if (typeof path === "string")
14383
- ranking.set(resolve9(path), index);
14549
+ ranking.set(resolve10(path), index);
14384
14550
  });
14385
14551
  }
14386
14552
  return ranking;
@@ -14536,14 +14702,33 @@ function errorMessage2(error) {
14536
14702
  var MANIFEST_FILENAME = "manifest.json";
14537
14703
  var init_bundle = () => {};
14538
14704
 
14705
+ // src/filesystem/path-exclusions.ts
14706
+ import { posix as posix2, win32 as win322 } from "node:path";
14707
+ function createPathExclusionMatcher(excludedPaths, platform = process.platform) {
14708
+ const paths = platform === "win32" ? win322 : posix2;
14709
+ const normalize = (path) => {
14710
+ const absolute = paths.resolve(path);
14711
+ return platform === "win32" ? absolute.toLowerCase() : absolute;
14712
+ };
14713
+ const excluded = excludedPaths.map((path) => {
14714
+ const absolute = normalize(path);
14715
+ return { absolute, prefix: absolute.endsWith(paths.sep) ? absolute : `${absolute}${paths.sep}` };
14716
+ });
14717
+ return (path) => {
14718
+ const absolute = normalize(path);
14719
+ return excluded.some((entry) => absolute === entry.absolute || absolute.startsWith(entry.prefix));
14720
+ };
14721
+ }
14722
+ var init_path_exclusions = () => {};
14723
+
14539
14724
  // src/everything/client.ts
14540
14725
  import { spawn as spawn11 } from "node:child_process";
14541
14726
  import { createHash as createHash9 } from "node:crypto";
14542
14727
  import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "node:fs";
14543
- import { join as join22, resolve as resolve10 } from "node:path";
14728
+ import { join as join22, resolve as resolve11 } from "node:path";
14544
14729
  import { setTimeout as sleep } from "node:timers/promises";
14545
14730
  function everythingInstanceName(root) {
14546
- return `autorag-${createHash9("sha256").update(resolve10(root).toLowerCase()).digest("hex").slice(0, 12)}`;
14731
+ return `autorag-${createHash9("sha256").update(resolve11(root).toLowerCase()).digest("hex").slice(0, 12)}`;
14547
14732
  }
14548
14733
  function buildEverythingIni(input) {
14549
14734
  const list = (values) => values.map((value) => `"${value.replace(/\\/g, "\\\\")}"`).join(",");
@@ -14653,6 +14838,7 @@ class EverythingClient {
14653
14838
  run;
14654
14839
  launch;
14655
14840
  stateDir;
14841
+ isExcludedPath;
14656
14842
  binaries;
14657
14843
  operation = Promise.resolve();
14658
14844
  constructor(options) {
@@ -14662,6 +14848,7 @@ class EverythingClient {
14662
14848
  this.run = options.run ?? runProcess;
14663
14849
  this.launch = options.launch ?? launchDetached;
14664
14850
  this.stateDir = join22(options.root, ".autorag", "everything");
14851
+ this.isExcludedPath = createPathExclusionMatcher(options.excludeFolders ?? [], "win32");
14665
14852
  }
14666
14853
  isSupported() {
14667
14854
  return this.platform === "win32";
@@ -14681,6 +14868,9 @@ class EverythingClient {
14681
14868
  }
14682
14869
  const es = { ok: true, esPath: binaries.esPath };
14683
14870
  const timeoutMs = this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS6;
14871
+ if ((this.options.excludeFolders?.length ?? 0) > 0) {
14872
+ return this.searchWithExclusions(es.esPath, request, timeoutMs);
14873
+ }
14684
14874
  const args = buildEverythingSearchArgs(this.instanceName, request, timeoutMs);
14685
14875
  const result = await this.run(es.esPath, args, timeoutMs + 5000);
14686
14876
  if (result.code !== 0)
@@ -14692,6 +14882,50 @@ class EverythingClient {
14692
14882
  }
14693
14883
  });
14694
14884
  }
14885
+ async searchWithExclusions(esPath, request, timeoutMs) {
14886
+ const deadline = Date.now() + timeoutMs;
14887
+ const offset = request.offset ?? 0;
14888
+ const maxResults = request.maxResults ?? DEFAULT_MAX_RESULTS;
14889
+ const results = [];
14890
+ let skipped = 0;
14891
+ let nativeOffset = 0;
14892
+ const timedOut = () => ({
14893
+ ok: false,
14894
+ reason: "search-failed",
14895
+ message: `Everything search exceeded ${timeoutMs}ms while filtering excluded paths; narrow the query or increase the Everything timeout.`
14896
+ });
14897
+ for (;; ) {
14898
+ const remainingMs = deadline - Date.now();
14899
+ if (remainingMs <= 0)
14900
+ return timedOut();
14901
+ const args = buildEverythingSearchArgs(this.instanceName, { ...request, offset: nativeOffset, maxResults: MAX_FILTER_CANDIDATES }, remainingMs);
14902
+ const response = await this.run(esPath, args, remainingMs + 5000);
14903
+ if (response.code !== 0)
14904
+ return this.failure("search-failed", esPath, args, response);
14905
+ if (Date.now() >= deadline)
14906
+ return timedOut();
14907
+ let entries;
14908
+ try {
14909
+ entries = parseEverythingJson(response.stdout);
14910
+ } catch (error) {
14911
+ return { ok: false, reason: "search-failed", message: error.message };
14912
+ }
14913
+ for (const entry of entries) {
14914
+ if (this.isExcludedPath(entry.path))
14915
+ continue;
14916
+ if (skipped < offset) {
14917
+ skipped += 1;
14918
+ continue;
14919
+ }
14920
+ results.push(entry);
14921
+ if (results.length >= maxResults)
14922
+ return { ok: true, results };
14923
+ }
14924
+ if (entries.length < MAX_FILTER_CANDIDATES)
14925
+ return { ok: true, results };
14926
+ nativeOffset += entries.length;
14927
+ }
14928
+ }
14695
14929
  index() {
14696
14930
  return this.serialize(async () => {
14697
14931
  const es = await this.ensureRunning(true);
@@ -14765,7 +14999,8 @@ class EverythingClient {
14765
14999
  excludeFolders: [
14766
15000
  ...new Set([
14767
15001
  join22(this.options.root, ".autorag"),
14768
- ...this.options.folders.map((folder) => join22(folder, ".autorag"))
15002
+ ...this.options.folders.map((folder) => join22(folder, ".autorag")),
15003
+ ...this.options.excludeFolders ?? []
14769
15004
  ])
14770
15005
  ]
14771
15006
  }));
@@ -14859,8 +15094,9 @@ function launchDetached(command, args) {
14859
15094
  child.on("error", () => {});
14860
15095
  child.unref();
14861
15096
  }
14862
- var DEFAULT_TIMEOUT_MS6 = 30000, DEFAULT_STARTUP_TIMEOUT_MS = 20000, DEFAULT_INDEX_TIMEOUT_MS2, DEFAULT_POLL_INTERVAL_MS = 250, DEFAULT_MAX_RESULTS = 100, MAX_OUTPUT_BYTES, VERSION_PATTERN;
15097
+ var DEFAULT_TIMEOUT_MS6 = 30000, DEFAULT_STARTUP_TIMEOUT_MS = 20000, DEFAULT_INDEX_TIMEOUT_MS2, DEFAULT_POLL_INTERVAL_MS = 250, DEFAULT_MAX_RESULTS = 100, MAX_FILTER_CANDIDATES = 1000, MAX_OUTPUT_BYTES, VERSION_PATTERN;
14863
15098
  var init_client7 = __esm(() => {
15099
+ init_path_exclusions();
14864
15100
  init_bundle();
14865
15101
  DEFAULT_INDEX_TIMEOUT_MS2 = 10 * 60000;
14866
15102
  MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
@@ -14880,6 +15116,7 @@ function compare(a, b) {
14880
15116
  return a < b ? -1 : a > b ? 1 : 0;
14881
15117
  }
14882
15118
  async function walkFileSearch(folders, request, options = {}) {
15119
+ const isExcluded = createPathExclusionMatcher(options.excludeFolders ?? []);
14883
15120
  const maxVisited = options.maxVisited ?? DEFAULT_MAX_VISITED;
14884
15121
  const maxMatches = options.maxMatches ?? DEFAULT_MAX_MATCHES;
14885
15122
  const deadline = Date.now() + (options.deadlineMs ?? DEFAULT_DEADLINE_MS);
@@ -14902,6 +15139,8 @@ async function walkFileSearch(folders, request, options = {}) {
14902
15139
  outer:
14903
15140
  while (pending.length > 0) {
14904
15141
  const dir = pending.pop();
15142
+ if (isExcluded(dir))
15143
+ continue;
14905
15144
  if (Date.now() >= deadline) {
14906
15145
  truncated = true;
14907
15146
  break;
@@ -14919,6 +15158,8 @@ async function walkFileSearch(folders, request, options = {}) {
14919
15158
  }
14920
15159
  visited += 1;
14921
15160
  const path = join23(dir, dirent.name);
15161
+ if (isExcluded(path))
15162
+ continue;
14922
15163
  if (dirent.isDirectory()) {
14923
15164
  if (dirent.name === ".autorag")
14924
15165
  continue;
@@ -14953,6 +15194,8 @@ async function walkFileSearch(folders, request, options = {}) {
14953
15194
  }
14954
15195
  }
14955
15196
  for (const folder of folders) {
15197
+ if (isExcluded(folder))
15198
+ continue;
14956
15199
  if (!underPath(folder) || !matches(folder))
14957
15200
  continue;
14958
15201
  if (request.kind === "files")
@@ -14979,6 +15222,7 @@ async function walkFileSearch(folders, request, options = {}) {
14979
15222
  }
14980
15223
  var DEFAULT_MAX_VISITED = 500000, DEFAULT_DEADLINE_MS = 20000, DEFAULT_MAX_MATCHES = 1e4, SORT_ORDERS;
14981
15224
  var init_walk = __esm(() => {
15225
+ init_path_exclusions();
14982
15226
  SORT_ORDERS = {
14983
15227
  "name-ascending": (a, b) => compare(a.name, b.name),
14984
15228
  "name-descending": (a, b) => compare(b.name, a.name),
@@ -14995,14 +15239,14 @@ var init_walk = __esm(() => {
14995
15239
  import { spawn as spawn12 } from "node:child_process";
14996
15240
  import { createHash as createHash10 } from "node:crypto";
14997
15241
  import { existsSync as existsSync15, mkdirSync as mkdirSync14, readFileSync as readFileSync18, realpathSync as realpathSync2, rmSync as rmSync4, writeFileSync as writeFileSync12 } from "node:fs";
14998
- import { basename as basename9, join as join24, resolve as resolve11, sep as sep4 } from "node:path";
15242
+ import { basename as basename9, join as join24, resolve as resolve12, sep as sep4 } from "node:path";
14999
15243
  import { setTimeout as sleep2 } from "node:timers/promises";
15000
15244
  function fsearchSocketPath(dbPath) {
15001
15245
  let canonical;
15002
15246
  try {
15003
15247
  canonical = realpathSync2(dbPath);
15004
15248
  } catch {
15005
- canonical = resolve11(dbPath);
15249
+ canonical = resolve12(dbPath);
15006
15250
  }
15007
15251
  const hash = createHash10("sha256").update(canonical).digest("hex").slice(0, 12);
15008
15252
  return `/tmp/autorag-fsearch-${process.getuid?.() ?? 0}-${hash}.sock`;
@@ -15088,6 +15332,7 @@ class FSearchClient {
15088
15332
  launch;
15089
15333
  isAlive;
15090
15334
  kill;
15335
+ isExcludedPath;
15091
15336
  stateDir;
15092
15337
  dbPath;
15093
15338
  pidPath;
@@ -15100,6 +15345,7 @@ class FSearchClient {
15100
15345
  this.launch = options.launch ?? launchDetached2;
15101
15346
  this.isAlive = options.isProcessAlive ?? defaultIsProcessAlive;
15102
15347
  this.kill = options.killProcess ?? ((pid) => process.kill(pid, "SIGTERM"));
15348
+ this.isExcludedPath = createPathExclusionMatcher(options.excludeFolders ?? []);
15103
15349
  this.stateDir = join24(options.root, ".autorag", "fsearch");
15104
15350
  this.dbPath = join24(this.stateDir, "fsearch.db");
15105
15351
  this.pidPath = join24(this.stateDir, "watch.pid");
@@ -15117,6 +15363,7 @@ class FSearchClient {
15117
15363
  if (unavailable !== undefined || !binary.ok) {
15118
15364
  try {
15119
15365
  const walked = await walkFileSearch(this.options.folders, request, {
15366
+ excludeFolders: this.options.excludeFolders,
15120
15367
  maxVisited: this.options.walkMaxVisited,
15121
15368
  deadlineMs: this.options.walkDeadlineMs
15122
15369
  });
@@ -15131,7 +15378,9 @@ class FSearchClient {
15131
15378
  return { ok: false, reason: "search-failed", message: error.message };
15132
15379
  }
15133
15380
  }
15134
- const args = buildFSearchSearchArgs(this.dbPath, this.socketPath(), request);
15381
+ const hasExclusions = (this.options.excludeFolders?.length ?? 0) > 0;
15382
+ const nativeRequest = hasExclusions ? { ...request, offset: 0, maxResults: MAX_RESULTS_CAP } : request;
15383
+ const args = buildFSearchSearchArgs(this.dbPath, this.socketPath(), nativeRequest);
15135
15384
  const result = await this.run(binary.path, args, this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS7);
15136
15385
  if (result.code !== 0)
15137
15386
  return this.failure("search-failed", binary.path, args, result);
@@ -15142,6 +15391,10 @@ class FSearchClient {
15142
15391
  return { ok: false, reason: "search-failed", message: error.message };
15143
15392
  }
15144
15393
  let entries = parsed.entries;
15394
+ if (hasExclusions) {
15395
+ const isExcluded = this.isExcludedPath;
15396
+ entries = entries.filter((entry) => !isExcluded(entry.path));
15397
+ }
15145
15398
  if (request.path !== undefined) {
15146
15399
  const prefix = request.path.endsWith(sep4) ? request.path : `${request.path}${sep4}`;
15147
15400
  entries = entries.filter((entry) => entry.path === request.path || entry.path.startsWith(prefix));
@@ -15151,7 +15404,16 @@ class FSearchClient {
15151
15404
  const maxResults = Math.min(request.maxResults ?? DEFAULT_MAX_RESULTS2, MAX_RESULTS_CAP);
15152
15405
  if (entries.length > maxResults)
15153
15406
  entries = entries.slice(0, maxResults);
15154
- return { ok: true, backend: "fsearch-cli", results: entries, total: parsed.total };
15407
+ return {
15408
+ ok: true,
15409
+ backend: "fsearch-cli",
15410
+ results: entries,
15411
+ total: parsed.total,
15412
+ ...hasExclusions && parsed.total !== undefined && parsed.total > parsed.entries.length && entries.length < maxResults ? {
15413
+ truncated: true,
15414
+ note: `FSearch checked ${parsed.entries.length} of ${parsed.total} native matches before exclusions (candidate limit ${MAX_RESULTS_CAP}); results may be incomplete. Narrow the query.`
15415
+ } : {}
15416
+ };
15155
15417
  });
15156
15418
  }
15157
15419
  index() {
@@ -15345,6 +15607,7 @@ function defaultIsProcessAlive(pid) {
15345
15607
  }
15346
15608
  var DEFAULT_TIMEOUT_MS7 = 30000, DEFAULT_STARTUP_TIMEOUT_MS2 = 1e4, STOP_TIMEOUT_MS = 2000, DEFAULT_INDEX_TIMEOUT_MS3, DEFAULT_POLL_INTERVAL_MS2 = 250, DEFAULT_MAX_RESULTS2 = 100, MAX_RESULTS_CAP = 1000, MAX_OUTPUT_BYTES2, SORT_KEYS;
15347
15609
  var init_client8 = __esm(() => {
15610
+ init_path_exclusions();
15348
15611
  init_walk();
15349
15612
  DEFAULT_INDEX_TIMEOUT_MS3 = 10 * 60000;
15350
15613
  MAX_OUTPUT_BYTES2 = 8 * 1024 * 1024;
@@ -15416,7 +15679,7 @@ var init_diagnostics = __esm(() => {
15416
15679
 
15417
15680
  // src/jikji/answer-pack.ts
15418
15681
  import { realpathSync as realpathSync3 } from "node:fs";
15419
- import { isAbsolute as isAbsolute7, join as join25, relative as relative3, resolve as resolve12 } from "node:path";
15682
+ import { isAbsolute as isAbsolute7, join as join25, relative as relative3, resolve as resolve13 } from "node:path";
15420
15683
  function isRecord10(value) {
15421
15684
  return typeof value === "object" && value !== null && !Array.isArray(value);
15422
15685
  }
@@ -15546,7 +15809,7 @@ function normalizeJikjiAnswerPath(rawPath, sourceRoots) {
15546
15809
  } else {
15547
15810
  if (normalized.split("/").includes(".."))
15548
15811
  continue;
15549
- candidate = resolve12(realRoot, normalized);
15812
+ candidate = resolve13(realRoot, normalized);
15550
15813
  }
15551
15814
  let realCandidate;
15552
15815
  try {
@@ -15983,7 +16246,7 @@ var init_client9 = __esm(() => {
15983
16246
  });
15984
16247
 
15985
16248
  // src/jikji/path-map.ts
15986
- import { isAbsolute as isAbsolute8, relative as relative4, resolve as resolve13, sep as sep5 } from "node:path";
16249
+ import { isAbsolute as isAbsolute8, relative as relative4, resolve as resolve14, sep as sep5 } from "node:path";
15987
16250
  function planJikjiSourceRoots(searchPaths) {
15988
16251
  return planSourceRoots(searchPaths).map((sourceRoot) => ({
15989
16252
  ...sourceRoot,
@@ -15991,7 +16254,7 @@ function planJikjiSourceRoots(searchPaths) {
15991
16254
  }));
15992
16255
  }
15993
16256
  function outermostJikjiRoots(searchPaths) {
15994
- const unique = [...new Set(searchPaths.map((searchPath) => resolve13(searchPath)))];
16257
+ const unique = [...new Set(searchPaths.map((searchPath) => resolve14(searchPath)))];
15995
16258
  return unique.filter((candidate) => !unique.some((other) => {
15996
16259
  if (other === candidate)
15997
16260
  return false;
@@ -16012,13 +16275,13 @@ function resolveReturnedPath(rootPath, returnedPath) {
16012
16275
  return;
16013
16276
  if (normalized.split("/").includes(".."))
16014
16277
  return;
16015
- return resolve13(rootPath, normalized);
16278
+ return resolve14(rootPath, normalized);
16016
16279
  }
16017
16280
  function isUnsafeReturnedPath(normalizedPath) {
16018
16281
  return normalizedPath.startsWith("/") || /^[A-Za-z]:\//.test(normalizedPath) || normalizedPath.startsWith("//") || /^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(normalizedPath);
16019
16282
  }
16020
16283
  function isWithinRoot(rootPath, sourcePath) {
16021
- const rel = relative4(resolve13(rootPath), resolve13(sourcePath));
16284
+ const rel = relative4(resolve14(rootPath), resolve14(sourcePath));
16022
16285
  return rel === "" || !rel.startsWith("..") && !rel.includes(`..${sep5}`) && !isAbsolute8(rel);
16023
16286
  }
16024
16287
  var init_path_map = __esm(() => {
@@ -16976,6 +17239,66 @@ var init_errors4 = __esm(() => {
16976
17239
  };
16977
17240
  });
16978
17241
 
17242
+ // src/minsync/kiwi.ts
17243
+ import { existsSync as existsSync19 } from "node:fs";
17244
+ import { homedir as homedir5 } from "node:os";
17245
+ import { posix as posix3, win32 as win323 } from "node:path";
17246
+ function kiwiAvailable(environment = defaultEnvironment()) {
17247
+ const { env, exists } = environment;
17248
+ const library = env.KIWI_LIBRARY_PATH;
17249
+ const model = env.KIWI_MODEL_PATH;
17250
+ const libraryFound = library !== undefined && library !== "" ? exists(library) : libraryCandidates(environment).some(exists);
17251
+ const modelFound = model !== undefined && model !== "" ? exists(model) : modelCandidates(environment).some(exists);
17252
+ return libraryFound && modelFound;
17253
+ }
17254
+ function defaultEnvironment() {
17255
+ let home;
17256
+ try {
17257
+ home = homedir5();
17258
+ } catch {
17259
+ home = undefined;
17260
+ }
17261
+ return { env: process.env, platform: process.platform, home, exists: existsSync19 };
17262
+ }
17263
+ function userRoots({ env, platform, home }) {
17264
+ if (platform === "win32") {
17265
+ const roots = [];
17266
+ if (env.LOCALAPPDATA)
17267
+ roots.push(win323.join(env.LOCALAPPDATA, "kiwi"));
17268
+ const profile = env.USERPROFILE ?? home;
17269
+ if (profile)
17270
+ roots.push(win323.join(profile, "AppData", "Local", "kiwi"));
17271
+ roots.push("C:\\kiwi", "C:\\Program Files\\Kiwi");
17272
+ return roots;
17273
+ }
17274
+ return home ? [posix3.join(home, ".local", "kiwi")] : [];
17275
+ }
17276
+ function libraryCandidates(environment) {
17277
+ const { platform } = environment;
17278
+ if (platform === "win32")
17279
+ return userRoots(environment).map((root) => win323.join(root, "lib", "kiwi.dll"));
17280
+ const file = platform === "darwin" ? "libkiwi.dylib" : "libkiwi.so";
17281
+ const system = platform === "darwin" ? ["/usr/local/lib", "/opt/homebrew/lib"] : ["/usr/local/lib", "/usr/lib"];
17282
+ return [
17283
+ ...userRoots(environment).map((root) => posix3.join(root, "lib", file)),
17284
+ ...system.map((dir) => posix3.join(dir, file))
17285
+ ];
17286
+ }
17287
+ function modelCandidates(environment) {
17288
+ const { platform } = environment;
17289
+ if (platform === "win32")
17290
+ return userRoots(environment).map((root) => win323.join(root, "models", "cong", "base"));
17291
+ const system = platform === "darwin" ? ["/usr/local/models", "/opt/homebrew/models", "/usr/local/share/kiwi/models"] : ["/usr/local/models", "/usr/local/share/kiwi/models", "/usr/share/kiwi/models"];
17292
+ return [
17293
+ ...userRoots(environment).map((root) => posix3.join(root, "models", "cong", "base")),
17294
+ ...system.map((dir) => posix3.join(dir, "cong", "base"))
17295
+ ];
17296
+ }
17297
+ var init_kiwi = () => {};
17298
+
17299
+ // src/minsync/lexical-language.ts
17300
+ var MINSYNC_LEXICAL_LANGUAGE = "multilingual";
17301
+
16979
17302
  // src/minsync/local-embedder.ts
16980
17303
  import { spawn as spawn15 } from "node:child_process";
16981
17304
  import { request } from "node:http";
@@ -17164,7 +17487,7 @@ var init_process = __esm(() => {
17164
17487
  });
17165
17488
 
17166
17489
  // src/minsync/client.ts
17167
- import { existsSync as existsSync19, readFileSync as readFileSync20, writeFileSync as writeFileSync14 } from "node:fs";
17490
+ import { existsSync as existsSync20, readFileSync as readFileSync20, writeFileSync as writeFileSync14 } from "node:fs";
17168
17491
  import { join as join28 } from "node:path";
17169
17492
 
17170
17493
  class MinSyncClient {
@@ -17259,7 +17582,7 @@ class MinSyncClient {
17259
17582
  return previous === undefined || JSON.stringify(previous) !== JSON.stringify(identity);
17260
17583
  }
17261
17584
  async sync(force = false, signal) {
17262
- if (!existsSync19(this.binaryPath))
17585
+ if (!existsSync20(this.binaryPath))
17263
17586
  throw minSyncBinaryMissingError();
17264
17587
  let effective = { config: {} };
17265
17588
  try {
@@ -17297,14 +17620,16 @@ class MinSyncClient {
17297
17620
  }
17298
17621
  }
17299
17622
  const spawnOptions = signal === undefined ? {} : { signal };
17300
- const initialized = existsSync19(minSyncConfigPath(this.workspacePath));
17623
+ const lexical = this.resolveLexicalLanguage();
17624
+ const initialized = existsSync20(minSyncConfigPath(this.workspacePath));
17301
17625
  if (!initialized) {
17302
17626
  const initArgs = ["init", "--format", "json"];
17303
17627
  if (this.embedder?.id) {
17304
17628
  initArgs.push("--embedder", this.embedder.id);
17305
17629
  }
17630
+ initArgs.push("--language", lexical.language);
17306
17631
  const init = await this.spawn(initArgs, spawnOptions);
17307
- if (!init.ok || !existsSync19(minSyncConfigPath(this.workspacePath))) {
17632
+ if (!init.ok || !existsSync20(minSyncConfigPath(this.workspacePath))) {
17308
17633
  return {
17309
17634
  ok: false,
17310
17635
  synced: 0,
@@ -17316,20 +17641,29 @@ class MinSyncClient {
17316
17641
  const chunkSizeChanged = this.maxChunkSize !== undefined && configuredMaxChunkSize(this.workspacePath) !== this.maxChunkSize;
17317
17642
  const dimensionChanged = embedder.dimension !== undefined && configuredVectorDimension(this.workspacePath) !== embedder.dimension;
17318
17643
  const configPath = minSyncConfigPath(this.workspacePath);
17319
- const shouldRewriteConfig = this.embedder !== undefined || this.maxChunkSize !== undefined || effective.identity !== undefined;
17320
- const originalConfig = shouldRewriteConfig ? readConfigSnapshot(configPath) : undefined;
17321
- const configRewritten = shouldRewriteConfig && rewriteEmbedderConfig(this.workspacePath, embedder, { maxChunkSize: this.maxChunkSize });
17644
+ const originalConfig = readConfigSnapshot(configPath);
17645
+ const configRewritten = rewriteEmbedderConfig(this.workspacePath, embedder, {
17646
+ maxChunkSize: this.maxChunkSize,
17647
+ lexicalLanguage: lexical.language
17648
+ });
17322
17649
  const restoreConfig = () => {
17323
17650
  if (configRewritten && originalConfig !== undefined)
17324
17651
  writeFileSync14(configPath, originalConfig);
17325
17652
  };
17326
17653
  try {
17327
- return await this.checkAndSync(force || chunkSizeChanged || dimensionChanged, signal, effective, restoreConfig);
17654
+ const result = await this.checkAndSync(force || chunkSizeChanged || dimensionChanged, signal, effective, restoreConfig);
17655
+ return result.ok && lexical.fallback !== undefined ? { ...result, lexicalFallback: lexical.fallback } : result;
17328
17656
  } catch (error) {
17329
17657
  restoreConfig();
17330
17658
  throw error;
17331
17659
  }
17332
17660
  }
17661
+ resolveLexicalLanguage() {
17662
+ const requested = MINSYNC_LEXICAL_LANGUAGE;
17663
+ if (kiwiAvailable() || configuredLexicalLanguage(this.workspacePath) === requested)
17664
+ return { language: requested };
17665
+ return { language: "simple", fallback: { requested, used: "simple" } };
17666
+ }
17333
17667
  lockHeldResult(stderr) {
17334
17668
  return {
17335
17669
  ok: false,
@@ -17382,7 +17716,7 @@ class MinSyncClient {
17382
17716
  }
17383
17717
  const identityChanged = effective.identity !== undefined && this.identityMismatch(effective.identity);
17384
17718
  const fullReindex = forceFull || identityChanged;
17385
- const syncArgs = existsSync19(cursorPath) && !fullReindex ? ["sync", "--format", "json"] : ["sync", "--full", "--format", "json"];
17719
+ const syncArgs = existsSync20(cursorPath) && !fullReindex ? ["sync", "--format", "json"] : ["sync", "--full", "--format", "json"];
17386
17720
  const result = await this.spawn(syncArgs, spawnOptions);
17387
17721
  if (!result.ok && result.code === MINSYNC_LOCK_HELD_EXIT_CODE) {
17388
17722
  restoreConfig();
@@ -17403,7 +17737,7 @@ class MinSyncClient {
17403
17737
  }
17404
17738
  };
17405
17739
  }
17406
- if (!existsSync19(cursorPath)) {
17740
+ if (!existsSync20(cursorPath)) {
17407
17741
  restoreConfig();
17408
17742
  return { ok: false, synced: 0, workspacePath: this.workspacePath, reason: "not-ready: missing cursor" };
17409
17743
  }
@@ -17424,7 +17758,7 @@ class MinSyncClient {
17424
17758
  };
17425
17759
  }
17426
17760
  async query(text, topK, mode = "vector", signal) {
17427
- if (!existsSync19(this.binaryPath))
17761
+ if (!existsSync20(this.binaryPath))
17428
17762
  throw minSyncBinaryMissingError();
17429
17763
  const result = await this.spawn(["query", "--format", "json", "--mode", mode, "-k", String(topK), text], {
17430
17764
  ...signal === undefined ? {} : { signal }
@@ -17518,6 +17852,7 @@ var MINSYNC_OLLAMA_MIGRATION_MESSAGE = "This workspace uses the legacy 768-dimen
17518
17852
  var init_client10 = __esm(() => {
17519
17853
  init_embedder_config();
17520
17854
  init_errors4();
17855
+ init_kiwi();
17521
17856
  init_local_embedder();
17522
17857
  init_process();
17523
17858
  API_KEY_ENV_PATTERN2 = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -17540,14 +17875,14 @@ var init_client10 = __esm(() => {
17540
17875
 
17541
17876
  // src/minsync/installer.ts
17542
17877
  import { createHash as createHash14 } from "node:crypto";
17543
- import { copyFileSync, createWriteStream as createWriteStream2, existsSync as existsSync20, mkdirSync as mkdirSync17, renameSync as renameSync6, rmSync as rmSync6 } from "node:fs";
17878
+ import { copyFileSync, createWriteStream as createWriteStream2, existsSync as existsSync21, mkdirSync as mkdirSync17, renameSync as renameSync6, rmSync as rmSync6 } from "node:fs";
17544
17879
  import { chmod as chmod2, mkdtemp } from "node:fs/promises";
17545
17880
  import { get } from "node:https";
17546
17881
  import { basename as basename10, delimiter as delimiter4, dirname as dirname18, join as join29 } from "node:path";
17547
17882
  import { pipeline } from "node:stream/promises";
17548
17883
  async function ensureMinSyncBinary(options) {
17549
17884
  const binaryPath = join29(options.root, ".autorag", "bin", executableName(options.platform ?? process.platform));
17550
- if (existsSync20(binaryPath))
17885
+ if (existsSync21(binaryPath))
17551
17886
  return { binaryPath, version: "cached" };
17552
17887
  mkdirSync17(dirname18(binaryPath), { recursive: true });
17553
17888
  const cargoInstaller = options.cargoInstaller ?? ((destination) => installMinSyncFromCargoIfAvailable(options, destination));
@@ -17595,7 +17930,7 @@ async function installMinSyncFromCargo(options, destination, cargo) {
17595
17930
  throw new MinSyncReleaseError(result.stderr || `Could not install MinSync ${MINSYNC_VERSION} from crates.io`);
17596
17931
  }
17597
17932
  const installedBinary = join29(cargoRoot, "bin", executableName(options.platform ?? process.platform));
17598
- if (!existsSync20(installedBinary)) {
17933
+ if (!existsSync21(installedBinary)) {
17599
17934
  throw new MinSyncReleaseError(`Cargo did not produce the MinSync ${MINSYNC_VERSION} binary`);
17600
17935
  }
17601
17936
  copyFileSync(installedBinary, destination);
@@ -17727,7 +18062,7 @@ function lookupCargo(env, platform) {
17727
18062
  if (dir.length === 0)
17728
18063
  continue;
17729
18064
  const candidate = join29(dir, execName);
17730
- if (existsSync20(candidate))
18065
+ if (existsSync21(candidate))
17731
18066
  return candidate;
17732
18067
  }
17733
18068
  return;
@@ -17758,7 +18093,7 @@ function isGitHubAsset(value) {
17758
18093
  function isRecord12(value) {
17759
18094
  return typeof value === "object" && value !== null;
17760
18095
  }
17761
- var LATEST_RELEASE_URL = "https://api.github.com/repos/NomaDamas/MinSync/releases/latest", SHA256_HEX_PATTERN, MINSYNC_VERSION = "0.4.8", MINSYNC_CARGO_INSTALL_TIMEOUT_MS, CARGO_PROBE_TIMEOUT_MS = 5000, MinSyncReleaseError;
18096
+ var LATEST_RELEASE_URL = "https://api.github.com/repos/NomaDamas/MinSync/releases/latest", SHA256_HEX_PATTERN, MINSYNC_VERSION = "0.4.10", MINSYNC_CARGO_INSTALL_TIMEOUT_MS, CARGO_PROBE_TIMEOUT_MS = 5000, MinSyncReleaseError;
17762
18097
  var init_installer2 = __esm(() => {
17763
18098
  init_process();
17764
18099
  SHA256_HEX_PATTERN = /^[a-f0-9]{64}$/;
@@ -17786,7 +18121,7 @@ var init_paths5 = __esm(() => {
17786
18121
  import { randomUUID as randomUUID5 } from "node:crypto";
17787
18122
  import {
17788
18123
  copyFileSync as copyFileSync2,
17789
- existsSync as existsSync21,
18124
+ existsSync as existsSync22,
17790
18125
  lstatSync as lstatSync2,
17791
18126
  mkdirSync as mkdirSync18,
17792
18127
  readdirSync as readdirSync2,
@@ -17801,8 +18136,8 @@ function syncMinSyncWorkspace(root, options = {}) {
17801
18136
  const filesRoot = join31(workspacePath, "files");
17802
18137
  const index = loadMirrorIndex(root);
17803
18138
  const candidates = Object.values(index.entries).sort((a, b) => a.virtualPath.localeCompare(b.virtualPath));
17804
- const excluded = candidates.filter((entry) => normalizeVirtualPath(entry.virtualPath) !== entry.virtualPath || !existsSync21(entry.outputPath)).map((entry) => entry.virtualPath);
17805
- const entries = candidates.filter((entry) => normalizeVirtualPath(entry.virtualPath) === entry.virtualPath && existsSync21(entry.outputPath)).map((entry) => {
18139
+ const excluded = candidates.filter((entry) => normalizeVirtualPath(entry.virtualPath) !== entry.virtualPath || !existsSync22(entry.outputPath)).map((entry) => entry.virtualPath);
18140
+ const entries = candidates.filter((entry) => normalizeVirtualPath(entry.virtualPath) === entry.virtualPath && existsSync22(entry.outputPath)).map((entry) => {
17806
18141
  const minSyncPath = minSyncDocumentPath(workspacePath, entry.virtualPath);
17807
18142
  return {
17808
18143
  virtualPath: entry.virtualPath,
@@ -17939,7 +18274,7 @@ var init_workspace = __esm(() => {
17939
18274
  });
17940
18275
 
17941
18276
  // src/minsync/method.ts
17942
- import { accessSync as accessSync2, constants as constants2, existsSync as existsSync22, realpathSync as realpathSync4 } from "node:fs";
18277
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync23, realpathSync as realpathSync4 } from "node:fs";
17943
18278
  import { basename as basename12, delimiter as delimiter5, join as join32, normalize as normalize3 } from "node:path";
17944
18279
  function lookupInPath2(env) {
17945
18280
  const pathEnv = env.PATH;
@@ -18035,19 +18370,19 @@ class MinSyncVectorMethod {
18035
18370
  }
18036
18371
  isBinaryMissing() {
18037
18372
  if (this.binaryPath !== undefined)
18038
- return !existsSync22(this.binaryPath);
18373
+ return !existsSync23(this.binaryPath);
18039
18374
  if (lookupInPath2(process.env) !== undefined)
18040
18375
  return false;
18041
18376
  const cachedBinary = join32(this.root, ".autorag", "bin", executableName(process.platform));
18042
- if (existsSync22(cachedBinary))
18377
+ if (existsSync23(cachedBinary))
18043
18378
  return false;
18044
18379
  return true;
18045
18380
  }
18046
18381
  isExplicitBinaryMissing() {
18047
- return this.binaryPath !== undefined && !existsSync22(this.binaryPath);
18382
+ return this.binaryPath !== undefined && !existsSync23(this.binaryPath);
18048
18383
  }
18049
18384
  isReady() {
18050
- return existsSync22(join32(this.workspacePath, ".minsync", "cursor.json"));
18385
+ return existsSync23(join32(this.workspacePath, ".minsync", "cursor.json"));
18051
18386
  }
18052
18387
  async retrieve(query, options) {
18053
18388
  if ((workspaceSyncInFlight.get(this.workspacePath) ?? 0) > 0) {
@@ -18100,7 +18435,7 @@ class MinSyncVectorMethod {
18100
18435
  return results;
18101
18436
  }
18102
18437
  async resolveBinary(options = {}) {
18103
- if (this.binaryPath && existsSync22(this.binaryPath))
18438
+ if (this.binaryPath && existsSync23(this.binaryPath))
18104
18439
  return this.binaryPath;
18105
18440
  if (this.binaryPath !== undefined)
18106
18441
  throw minSyncBinaryMissingError();
@@ -18108,7 +18443,7 @@ class MinSyncVectorMethod {
18108
18443
  if (pathBinary)
18109
18444
  return pathBinary;
18110
18445
  const cachedBinary = join32(this.root, ".autorag", "bin", executableName(process.platform));
18111
- if (existsSync22(cachedBinary))
18446
+ if (existsSync23(cachedBinary))
18112
18447
  return cachedBinary;
18113
18448
  if (this.autoInstall && options.install !== false) {
18114
18449
  try {
@@ -18161,7 +18496,7 @@ var init_minsync = __esm(() => {
18161
18496
  });
18162
18497
 
18163
18498
  // src/parser/isolated.ts
18164
- import { existsSync as existsSync23 } from "node:fs";
18499
+ import { existsSync as existsSync24 } from "node:fs";
18165
18500
  import { dirname as dirname20, join as join33 } from "node:path";
18166
18501
  import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "node:url";
18167
18502
  import { Worker } from "node:worker_threads";
@@ -18300,7 +18635,7 @@ function defaultWorkerUrl() {
18300
18635
  join33(here, "parser", "parse-worker.js"),
18301
18636
  join33(here, "..", "parser", "parse-worker.js")
18302
18637
  ]) {
18303
- if (existsSync23(candidate))
18638
+ if (existsSync24(candidate))
18304
18639
  return pathToFileURL2(candidate);
18305
18640
  }
18306
18641
  throw new Error(`The AutoRAG parse worker entry was not found near ${here}.`);
@@ -18312,9 +18647,9 @@ var init_isolated = __esm(() => {
18312
18647
  });
18313
18648
 
18314
18649
  // src/mirror/sync.ts
18315
- import { existsSync as existsSync24, rmSync as rmSync8 } from "node:fs";
18650
+ import { existsSync as existsSync25, rmSync as rmSync8 } from "node:fs";
18316
18651
  import { opendir, readFile as readFile5, stat as stat7 } from "node:fs/promises";
18317
- import { extname as extname5, resolve as resolve14, sep as sep7 } from "node:path";
18652
+ import { extname as extname5, resolve as resolve15, sep as sep7 } from "node:path";
18318
18653
  import { setTimeout as sleep3 } from "node:timers/promises";
18319
18654
  function errorCode(error) {
18320
18655
  if (!(error instanceof Error))
@@ -18504,7 +18839,7 @@ async function syncWithRegistry(options, registry, isolated) {
18504
18839
  }
18505
18840
  const previousEntry = previous.entries[entry.virtualPath];
18506
18841
  const outputPath = parsedOutputPath(options.root, entry.virtualPath);
18507
- const unchanged = !options.force && previousEntry?.sourceMtimeNs === entry.mtimeNs && previousEntry.sourceSizeBytes === entry.sizeBytes && previousEntry.parserName === parser.name && existsSync24(outputPath);
18842
+ const unchanged = !options.force && previousEntry?.sourceMtimeNs === entry.mtimeNs && previousEntry.sourceSizeBytes === entry.sizeBytes && previousEntry.parserName === parser.name && existsSync25(outputPath);
18508
18843
  if (!unchanged) {
18509
18844
  let bytes;
18510
18845
  try {
@@ -18832,7 +19167,7 @@ async function collectFiles(state, directory, depth) {
18832
19167
  continue;
18833
19168
  if (PRODUCT_ARTIFACT_FILE_NAMES.has(entry.name))
18834
19169
  continue;
18835
- const sourcePath = resolve14(directory, entry.name);
19170
+ const sourcePath = resolve15(directory, entry.name);
18836
19171
  if (entry.isDirectory()) {
18837
19172
  if (depth + 1 > state.limits.maxDepth) {
18838
19173
  listing.unreliablePrefixes.push(sourceIdentifier(sourceRoot, sourcePath));
@@ -18885,7 +19220,7 @@ function removePrevious(root, index, virtualPath) {
18885
19220
  return 1;
18886
19221
  }
18887
19222
  function removeFile(path) {
18888
- if (existsSync24(path))
19223
+ if (existsSync25(path))
18889
19224
  rmSync8(path, { force: true });
18890
19225
  }
18891
19226
  var DEFAULT_MAX_SOURCE_BYTES, MIRROR_CHECKPOINT_EVERY = 25, DEFAULT_MAX_DELETE_RATIO = 0.3, MASS_DELETE_FLOOR = 100, DEFAULT_MAX_SCANNED_ENTRIES = 2000000, DEFAULT_MAX_WALK_DEPTH = 64, PRODUCT_ARTIFACT_FILE_NAMES, SKIP_DIR_NAMES, PARSER_FAILURE_MESSAGE = "The registered parser failed on this file; it was skipped during indexing.", PARSER_CRASH_MESSAGE = "The registered parser crashed unexpectedly on this file; it was skipped during indexing.", DEFAULT_TRANSIENT_RETRIES = 3, DEFAULT_TRANSIENT_BACKOFF_MS = 50, TRANSIENT_ERROR_CODES, WORKSPACE_ERROR_CODES, MirrorWorkspaceError, STANDALONE_OCR_IMAGE_EXTENSIONS, RETRYABLE_SKIP_MESSAGES, SEARCH_ROOT_UNAVAILABLE_MESSAGES;
@@ -20015,12 +20350,43 @@ function digitsEndAt(text, from) {
20015
20350
  end += 1;
20016
20351
  return end;
20017
20352
  }
20018
- function continuesAsProse(answer, from) {
20019
- if (answer[from] !== " " && answer[from] !== " ")
20353
+ function continuesAsProse(answer, openIndex, from) {
20354
+ let cursor = from;
20355
+ let leadingEmphasis = 0;
20356
+ while (openIndex - 1 - leadingEmphasis >= 0 && (answer[openIndex - 1 - leadingEmphasis] === "*" || answer[openIndex - 1 - leadingEmphasis] === "_")) {
20357
+ leadingEmphasis += 1;
20358
+ }
20359
+ if (leadingEmphasis > 0) {
20360
+ let trailingEmphasis = 0;
20361
+ while (cursor + trailingEmphasis < answer.length && trailingEmphasis < leadingEmphasis && answer[cursor + trailingEmphasis] === answer[openIndex - 1 - trailingEmphasis]) {
20362
+ trailingEmphasis += 1;
20363
+ }
20364
+ if (trailingEmphasis === leadingEmphasis)
20365
+ cursor += trailingEmphasis;
20366
+ }
20367
+ const tokenStart = openIndex - leadingEmphasis;
20368
+ const linePrefix = answer.slice(answer.lastIndexOf(`
20369
+ `, tokenStart - 1) + 1, tokenStart);
20370
+ const atHeadingStart = /^[ \t>*-]*$/u.test(linePrefix);
20371
+ if (atHeadingStart && answer[cursor] === ":")
20372
+ cursor += 1;
20373
+ if (answer[cursor] !== " " && answer[cursor] !== " ")
20020
20374
  return false;
20021
- const next = answer.codePointAt(from + 1);
20375
+ cursor += 1;
20376
+ while (cursor < answer.length && (answer[cursor] === "*" || answer[cursor] === "_" || answer[cursor] === '"' || answer[cursor] === "'" || answer[cursor] === "“" || answer[cursor] === "‘")) {
20377
+ cursor += 1;
20378
+ }
20379
+ const next = answer.codePointAt(cursor);
20022
20380
  return next !== undefined && /[\p{L}\p{N}]/u.test(String.fromCodePoint(next));
20023
20381
  }
20382
+ function isLegalPinpoint(answer, openIndex, afterCloseIndex) {
20383
+ if (PINPOINT_RANGE_AHEAD.test(answer.slice(afterCloseIndex, afterCloseIndex + 16)))
20384
+ return true;
20385
+ if (PINPOINT_RANGE_BEHIND.test(answer.slice(Math.max(0, openIndex - 16), openIndex)))
20386
+ return true;
20387
+ const prefixWindow = answer.slice(Math.max(0, openIndex - 120), openIndex);
20388
+ return PARA_PINPOINT_PREFIX.test(prefixWindow) || CLAUSE_START_AT_PREFIX.test(prefixWindow) || CASE_CITATION_AT_PREFIX.test(prefixWindow);
20389
+ }
20024
20390
  function evidenceMarkerAt(answer, index) {
20025
20391
  const ids = [];
20026
20392
  let cursor = index + 1;
@@ -20086,7 +20452,7 @@ function answerMarkers(answer, isFile = () => false) {
20086
20452
  }
20087
20453
  if (char === "[") {
20088
20454
  const digitsEnd = digitsEndAt(answer, index + 1);
20089
- if (digitsEnd > index + 1 && answer[digitsEnd] === "]" && answer[digitsEnd + 1] !== "(" && !continuesAsProse(answer, digitsEnd + 1)) {
20455
+ if (digitsEnd > index + 1 && answer[digitsEnd] === "]" && answer[digitsEnd + 1] !== "(" && !continuesAsProse(answer, index, digitsEnd + 1) && !isLegalPinpoint(answer, index, digitsEnd + 1)) {
20090
20456
  markers.push({
20091
20457
  kind: "number",
20092
20458
  start: leadingStart(index),
@@ -20158,7 +20524,14 @@ function assertResultsMappingOneToOne(label, results, mapping) {
20158
20524
  ].filter((note) => note !== undefined);
20159
20525
  throw new Error(`${label}: result numbers and mapping numbers must be one-to-one, but results contain ${formatCitationList(resultNumbers)} ` + `and mapping contains ${formatCitationList(mappingNumbers)}` + (duplicateNotes.length > 0 ? ` (${duplicateNotes.join("; ")})` : "") + ". Give every result a unique number with exactly one mapping entry carrying the same number and no mapping entry without a result.");
20160
20526
  }
20161
- var MAX_FILE_MARKER_CLOSERS = 8, FILE_MARKER_PREFIX = "[file:";
20527
+ var MAX_FILE_MARKER_CLOSERS = 8, FILE_MARKER_PREFIX = "[file:", PARA_PINPOINT_PREFIX, CLAUSE_START_AT_PREFIX, CASE_CITATION_AT_PREFIX, PINPOINT_RANGE_AHEAD, PINPOINT_RANGE_BEHIND;
20528
+ var init_citations = __esm(() => {
20529
+ PARA_PINPOINT_PREFIX = /(?:^|[^\p{L}\p{N}])(?:paras?\.?|paragraphs?)(?:[ \t\u00a0]+\[\d+\][ \t\u00a0]*(?:[-–—]|,(?:[ \t\u00a0]*(?:and|or)\b)?|and\b|or\b))*[ \t\u00a0]*$/iu;
20530
+ CLAUSE_START_AT_PREFIX = /(?:^|[\n.!?؛;:()]|[>*-])[ \t\u00a0]*At(?:[ \t\u00a0]+\[\d+\][ \t\u00a0]*(?:[-–—]|,(?:[ \t\u00a0]*(?:and|or)\b)?|and\b|or\b))*[ \t\u00a0]+$/u;
20531
+ CASE_CITATION_AT_PREFIX = /(?:\[\d{4}\][^\n.;]*?|\b(?:v|vs\.?)\s+[^\n.;]{1,60}?|\bs(?:ection)?[ \t\u00a0]+\d+[A-Za-z]?(?:\(\d+\))?[^\n.;]{0,30}?)[ \t\u00a0]+at(?:[ \t\u00a0]+\[\d+\][ \t\u00a0]*(?:[-–—]|,(?:[ \t\u00a0]*(?:and|or)\b)?|and\b|or\b))*[ \t\u00a0]+$/iu;
20532
+ PINPOINT_RANGE_AHEAD = /^[ \t\u00a0]*[-–—][ \t\u00a0]*\[\d+\]/u;
20533
+ PINPOINT_RANGE_BEHIND = /\[\d+\][ \t\u00a0]*[-–—][ \t\u00a0]*$/u;
20534
+ });
20162
20535
 
20163
20536
  // src/agent/evidence-ledger.ts
20164
20537
  import { createHash as createHash15 } from "node:crypto";
@@ -20442,6 +20815,7 @@ function deriveResultsFromAnswer(text, ledger, options) {
20442
20815
  }
20443
20816
  var MAX_EXCERPT_CHARS = 400, MAX_CLAIM_CHARS = 280, CLAIM_LOOKBACK_CHARS = 600, SENTENCE_END, LEADING_LIST_MARK, EVIDENCE_MARKER_TEXT, LEADING_BLANK;
20444
20817
  var init_answer_citations = __esm(() => {
20818
+ init_citations();
20445
20819
  init_evidence_ledger();
20446
20820
  SENTENCE_END = /[.!?。!?]/u;
20447
20821
  LEADING_LIST_MARK = /^[\s\-*•>]+/u;
@@ -20450,7 +20824,7 @@ var init_answer_citations = __esm(() => {
20450
20824
  });
20451
20825
 
20452
20826
  // src/agent/answer-guidelines.ts
20453
- var ANSWER_IMAGE_EMBED_RULE = "**Images**: when a retrieved result is itself an image file (png, jpg, jpeg, gif, webp, svg, bmp, avif, heic, tiff) and showing it answers the question, embed it in the answer as a markdown image on its own line: `![short description](<absolute source path>)`, followed by its citation, e.g. `![Q3 revenue chart](</data/reports/q3 chart.png>) [e2]`. Use only the real source path of retrieved evidence. Never invent a path, use relative paths, or embed remote URLs. Apart from these image embeds, the no-file-paths rule stands.", ANSWER_IMAGE_DELTA_RULE = "An image already embedded in the first answer must not be embedded again.", ANSWER_CITATION_RULE = "**Citations**: cite supporting evidence inline with the evidence id shown next to each retrieved result, in square brackets (e.g. [e3], or [e3][e7] for two). Copy ids exactly; never invent one. For a local file you opened yourself with `bash`, cite `[file:<absolute path>]`. Cite the evidence that backs a sentence at the end of that sentence. Do not cite numbers, paths, or anything else.";
20827
+ var ANSWER_IMAGE_EMBED_RULE = "**Images**: when a retrieved result is itself an image file (png, jpg, jpeg, gif, webp, svg, bmp, avif, heic, tiff) and showing it answers the question, embed it in the answer as a markdown image on its own line: `![short description](<absolute source path>)`, followed by its citation, e.g. `![Q3 revenue chart](</data/reports/q3 chart.png>) [e2]`. Use only the real source path of retrieved evidence. Never invent a path, use relative paths, or embed remote URLs. Apart from these image embeds, the no-file-paths rule stands.", ANSWER_IMAGE_DELTA_RULE = "An image already embedded in the first answer must not be embedded again.", ANSWER_CITATION_RULE = "**Citations**: cite supporting evidence inline with the evidence id shown next to each retrieved result, in square brackets (e.g. [e3], or [e3][e7] for two). Copy ids exactly; never invent one. For a local file you opened yourself with `bash`, cite `[file:<absolute path>]`. Cite the evidence that backs a sentence at the end of that sentence. Do not cite numbers, paths, or anything else.", ANSWER_DELIVERABLE_RULE = '**Deliverables and artifacts**: if the user asks you to draft, write, generate, or prepare a deliverable or artifact (a reply email, letter, legal opinion, summary document, standard-format case citation block, checklist, report, or workflow document — including requests phrased as "how should I reply..." to a specific message), produce the finished, ready-to-use artifact as a file (using `write`, `edit`, or `bash` in local sessions, without overwriting existing source files) in a file format, extension, and structure aligned with the context and domain (e.g. `.eml` or `.md`/`.txt` with headers for an email reply; `.md`, `.txt`, `.docx`, `.hwp`, or `.pdf` matching workspace conventions for a legal opinion, insertable case-citation block, or summary document; `.csv`, `.xlsx`, or `.json` for structured tabular data), containing the complete unabridged artifact (including required headers, precedent passages, pinpoint paragraphs, and standard-format citations) inside the file instead of merely describing how to produce it. Do NOT dump the full artifact text into your reply: unless the user explicitly requests otherwise, keep your reply concise within the at-most-5-bullet direct-answer format, clearly stating the created file\'s exact path/location and briefly summarizing what was generated with inline citations to the underlying source evidence (`[eN]` or `[file:<source path>]`); never cite the newly created deliverable file itself as `[file:...]`.', ANSWER_DELIVERABLE_DELTA_RULE = "If a deliverable or artifact was requested and the first answer could not write the file yet (the fast phase runs without tool calls) or only gave advice about it, create the complete context-aligned deliverable file now during verification and report the created file's location and a brief summary concisely.";
20454
20828
 
20455
20829
  // src/agent/datasource-skill.ts
20456
20830
  import { Type as Type3 } from "typebox";
@@ -21801,6 +22175,20 @@ function interactiveKeyOf(data) {
21801
22175
  return "followUp";
21802
22176
  return;
21803
22177
  }
22178
+ function stripTelegramEnvelope(text) {
22179
+ return text.replace(/^\s*\[telegram\]\s?/u, "").replace(/\n*\[time\][^\n]*/gu, "").trim();
22180
+ }
22181
+ function userInputText(event) {
22182
+ if (event.source === "interactive")
22183
+ return event.text.trim();
22184
+ if (event.source === "extension" && event.text.trimStart().startsWith(TELEGRAM_INPUT_PREFIX)) {
22185
+ return stripTelegramEnvelope(event.text);
22186
+ }
22187
+ if (event.source === "extension" && event.text.trimStart().startsWith(WHATSAPP_INPUT_PREFIX)) {
22188
+ return event.text.trim();
22189
+ }
22190
+ return;
22191
+ }
21804
22192
  function createAutoRAGInteractiveExtension(getSystemPrompt, contextTransform, options, updateNotice) {
21805
22193
  return (pi) => {
21806
22194
  pi.on("before_agent_start", () => ({ systemPrompt: getSystemPrompt() }));
@@ -21831,8 +22219,8 @@ function createAutoRAGInteractiveExtension(getSystemPrompt, contextTransform, op
21831
22219
  });
21832
22220
  }
21833
22221
  pi.on("input", (event) => {
21834
- const text = event.text.trim();
21835
- if (event.source !== "interactive" || text.length === 0)
22222
+ const text = userInputText(event);
22223
+ if (text === undefined || text.length === 0)
21836
22224
  return;
21837
22225
  if (text.startsWith("/") && isSearchRunning?.() !== true)
21838
22226
  return;
@@ -21929,7 +22317,9 @@ async function createAutoRAGPiInteractiveRuntime(options) {
21929
22317
  agentDir: runtimeAgentDir,
21930
22318
  modelRuntime,
21931
22319
  settingsManager,
22320
+ ...options.extensionFlags === undefined ? {} : { extensionFlagValues: new Map(Object.entries(options.extensionFlags)) },
21932
22321
  resourceLoaderOptions: {
22322
+ ...options.extensionPaths === undefined ? {} : { additionalExtensionPaths: [...options.extensionPaths] },
21933
22323
  extensionFactories: [
21934
22324
  createAutoRAGInteractiveExtension(options.getSystemPrompt, options.contextTransform, { onInput: options.onInput, isSearchRunning: options.isSearchRunning, onKey: options.onKey }, options.updateNotice),
21935
22325
  ...options.extensionFactories ?? []
@@ -21963,7 +22353,7 @@ async function createAutoRAGPiInteractiveRuntime(options) {
21963
22353
  });
21964
22354
  return { runtime, dispose: () => runtime.dispose() };
21965
22355
  }
21966
- var PI_BUILTIN_TOOL_NAMES, EMBEDDED_AUTH_MARKER = "<autorag-embedded-model>";
22356
+ var PI_BUILTIN_TOOL_NAMES, EMBEDDED_AUTH_MARKER = "<autorag-embedded-model>", TELEGRAM_INPUT_PREFIX = "[telegram]", WHATSAPP_INPUT_PREFIX = "Message from ";
21967
22357
  var init_pi_session = __esm(() => {
21968
22358
  init_provider();
21969
22359
  init_context_token_guard();
@@ -22219,7 +22609,7 @@ var init_jikji_find_tool = __esm(() => {
22219
22609
 
22220
22610
  // src/p2p/approval-store.ts
22221
22611
  import {
22222
- existsSync as existsSync25,
22612
+ existsSync as existsSync26,
22223
22613
  mkdirSync as mkdirSync20,
22224
22614
  readdirSync as readdirSync3,
22225
22615
  readFileSync as readFileSync22,
@@ -22284,7 +22674,7 @@ function savePendingPeerRequest(workspacePath, request) {
22284
22674
  }
22285
22675
  function loadPendingPeerRequest(workspacePath, id) {
22286
22676
  const path = requestPath(workspacePath, id);
22287
- if (!existsSync25(path))
22677
+ if (!existsSync26(path))
22288
22678
  return;
22289
22679
  try {
22290
22680
  return parsePending(JSON.parse(readFileSync22(path, "utf8")));
@@ -22294,7 +22684,7 @@ function loadPendingPeerRequest(workspacePath, id) {
22294
22684
  }
22295
22685
  function listPendingPeerRequests(workspacePath) {
22296
22686
  const dir = requestsDir(workspacePath);
22297
- if (!existsSync25(dir))
22687
+ if (!existsSync26(dir))
22298
22688
  return [];
22299
22689
  const requests = [];
22300
22690
  for (const name of readdirSync3(dir)) {
@@ -22316,7 +22706,7 @@ function writePeerRequestDecision(workspacePath, id, decision) {
22316
22706
  }
22317
22707
  function loadPeerRequestDecision(workspacePath, id) {
22318
22708
  const path = decisionPath(workspacePath, id);
22319
- if (!existsSync25(path))
22709
+ if (!existsSync26(path))
22320
22710
  return;
22321
22711
  try {
22322
22712
  return parseDecision(JSON.parse(readFileSync22(path, "utf8")));
@@ -22650,8 +23040,8 @@ var init_egress_gate = __esm(() => {
22650
23040
  });
22651
23041
 
22652
23042
  // src/p2p/policy.ts
22653
- import { existsSync as existsSync26, mkdirSync as mkdirSync21, readFileSync as readFileSync23, writeFileSync as writeFileSync17 } from "node:fs";
22654
- import { basename as basename13, dirname as dirname23, join as join36, resolve as resolve15 } from "node:path";
23043
+ import { existsSync as existsSync27, mkdirSync as mkdirSync21, readFileSync as readFileSync23, writeFileSync as writeFileSync17 } from "node:fs";
23044
+ import { basename as basename13, dirname as dirname23, join as join36, resolve as resolve16 } from "node:path";
22655
23045
  import { parse as parseToml2 } from "smol-toml";
22656
23046
  function isRecord17(value) {
22657
23047
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -22730,7 +23120,7 @@ function normalizePolicyEntries(value, workspacePath, context) {
22730
23120
  return entries;
22731
23121
  }
22732
23122
  function readJsonFile(path) {
22733
- if (!existsSync26(path))
23123
+ if (!existsSync27(path))
22734
23124
  return;
22735
23125
  let raw;
22736
23126
  try {
@@ -22745,7 +23135,7 @@ function readJsonFile(path) {
22745
23135
  }
22746
23136
  }
22747
23137
  function readTomlFile(path) {
22748
- if (!existsSync26(path))
23138
+ if (!existsSync27(path))
22749
23139
  return;
22750
23140
  let raw;
22751
23141
  try {
@@ -22886,7 +23276,7 @@ function globToRegExp(pattern) {
22886
23276
  return new RegExp(`${expression}$`, "u");
22887
23277
  }
22888
23278
  function loadSeenSources(path, workspacePath) {
22889
- if (!existsSync26(path))
23279
+ if (!existsSync27(path))
22890
23280
  return { promoted: new Set, pending: new Set };
22891
23281
  let parsed;
22892
23282
  try {
@@ -22934,7 +23324,7 @@ class PolicyStore {
22934
23324
  if (typeof normalizedOptions.workspacePath !== "string" || normalizedOptions.workspacePath.length === 0) {
22935
23325
  throw new PolicyError("workspacePath must be a non-empty path");
22936
23326
  }
22937
- this.workspacePath = resolve15(normalizedOptions.workspacePath);
23327
+ this.workspacePath = resolve16(normalizedOptions.workspacePath);
22938
23328
  this.sourceRoots = normalizedOptions.sourceRoots;
22939
23329
  const globalConfigPath = normalizedOptions.globalConfigPath ?? resolveGlobalConfigPath(normalizedOptions.homePath);
22940
23330
  const workspacePolicyPath = normalizedOptions.workspacePolicyPath ?? join36(this.workspacePath, ".autorag", "p2p", "policy.toml");
@@ -23193,7 +23583,7 @@ var init_wire = __esm(() => {
23193
23583
 
23194
23584
  // src/p2p/simplex-query-store.ts
23195
23585
  import { randomUUID as randomUUID6 } from "node:crypto";
23196
- import { existsSync as existsSync27, mkdirSync as mkdirSync22, readdirSync as readdirSync4, readFileSync as readFileSync24, renameSync as renameSync9, unlinkSync as unlinkSync5, writeFileSync as writeFileSync18 } from "node:fs";
23586
+ import { existsSync as existsSync28, mkdirSync as mkdirSync22, readdirSync as readdirSync4, readFileSync as readFileSync24, renameSync as renameSync9, unlinkSync as unlinkSync5, writeFileSync as writeFileSync18 } from "node:fs";
23197
23587
  import { dirname as dirname24, join as join37 } from "node:path";
23198
23588
  import { Value as Value2 } from "typebox/value";
23199
23589
  function statePath(workspacePath, directory, id) {
@@ -23229,7 +23619,7 @@ function parseState(value) {
23229
23619
  return value;
23230
23620
  }
23231
23621
  function readState(path) {
23232
- if (!existsSync27(path))
23622
+ if (!existsSync28(path))
23233
23623
  return;
23234
23624
  try {
23235
23625
  return parseState(JSON.parse(readFileSync24(path, "utf8")));
@@ -23256,14 +23646,14 @@ function moveState(workspacePath, from, to, state) {
23256
23646
  }
23257
23647
  function recoverClaims(workspacePath) {
23258
23648
  const directory = join37(workspacePath, QUERY_DIR, PENDING_DIR);
23259
- if (!existsSync27(directory))
23649
+ if (!existsSync28(directory))
23260
23650
  return;
23261
23651
  for (const name of readdirSync4(directory)) {
23262
23652
  if (!name.endsWith(".json.claim"))
23263
23653
  continue;
23264
23654
  const claim = join37(directory, name);
23265
23655
  const restored = join37(directory, name.slice(0, -".claim".length));
23266
- if (!existsSync27(restored))
23656
+ if (!existsSync28(restored))
23267
23657
  renameSync9(claim, restored);
23268
23658
  }
23269
23659
  }
@@ -23287,7 +23677,7 @@ function loadSimplexQueryState(workspacePath, id) {
23287
23677
  }
23288
23678
  function listPendingSimplexQueryStates(workspacePath) {
23289
23679
  const directory = join37(workspacePath, QUERY_DIR, PENDING_DIR);
23290
- if (!existsSync27(directory))
23680
+ if (!existsSync28(directory))
23291
23681
  return [];
23292
23682
  return readdirSync4(directory).filter((name) => name.endsWith(".json")).map((name) => readState(join37(directory, name))).filter((state) => state?.status === "pending").filter((state) => loadSimplexQueryState(workspacePath, state.id)?.status === "pending").sort((left, right) => left.createdAt.localeCompare(right.createdAt));
23293
23683
  }
@@ -23430,7 +23820,7 @@ var init_simplex_query_store = __esm(() => {
23430
23820
  });
23431
23821
 
23432
23822
  // src/p2p/simplex-server.ts
23433
- import { existsSync as existsSync28, mkdirSync as mkdirSync23, readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "node:fs";
23823
+ import { existsSync as existsSync29, mkdirSync as mkdirSync23, readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "node:fs";
23434
23824
  import { join as join38 } from "node:path";
23435
23825
  import { Type as Type10 } from "typebox";
23436
23826
  import { Value as Value3 } from "typebox/value";
@@ -23459,7 +23849,7 @@ function rankSimplexPeerTargets(query, registry) {
23459
23849
  }
23460
23850
  function loadSimplexPeerRegistry(workspacePath) {
23461
23851
  const path = join38(workspacePath, PEERS_DIR, PEERS_FILENAME);
23462
- if (!existsSync28(path))
23852
+ if (!existsSync29(path))
23463
23853
  return {};
23464
23854
  try {
23465
23855
  const parsed = JSON.parse(readFileSync25(path, "utf8"));
@@ -24365,6 +24755,7 @@ function recordStructuredResultsSession(sessionId, query, details, memory, compo
24365
24755
  }
24366
24756
  var init_search_documents = __esm(() => {
24367
24757
  init_memory();
24758
+ init_citations();
24368
24759
  });
24369
24760
 
24370
24761
  // src/agent/search-all-tool.ts
@@ -24570,14 +24961,14 @@ var init_search_single_datasource_tool = __esm(() => {
24570
24961
  });
24571
24962
 
24572
24963
  // src/agent/self-config.ts
24573
- import { existsSync as existsSync29, readFileSync as readFileSync27, rmSync as rmSync10, writeFileSync as writeFileSync21 } from "node:fs";
24964
+ import { existsSync as existsSync30, readFileSync as readFileSync27, rmSync as rmSync10, writeFileSync as writeFileSync21 } from "node:fs";
24574
24965
  import { dirname as dirname26, join as join39 } from "node:path";
24575
24966
  import { fileURLToPath as fileURLToPath3 } from "node:url";
24576
24967
  function findPackagedSetupSkill() {
24577
24968
  let dir = dirname26(fileURLToPath3(import.meta.url));
24578
24969
  for (let depth = 0;depth < 6; depth += 1) {
24579
24970
  const candidate = join39(dir, SETUP_SKILL_RELATIVE_PATH);
24580
- if (existsSync29(candidate))
24971
+ if (existsSync30(candidate))
24581
24972
  return candidate;
24582
24973
  const parent = dirname26(dir);
24583
24974
  if (parent === dir)
@@ -24600,7 +24991,7 @@ function loadSetupSkill(skillPath) {
24600
24991
  return text.replace(/^---\r?\n[\s\S]*?\r?\n---(?:\r?\n)+/u, "");
24601
24992
  }
24602
24993
  function snapshotConfigFile(configPath) {
24603
- return existsSync29(configPath) ? readFileSync27(configPath, "utf8") : undefined;
24994
+ return existsSync30(configPath) ? readFileSync27(configPath, "utf8") : undefined;
24604
24995
  }
24605
24996
  async function rollbackIfBroken(options, before) {
24606
24997
  if (options.validate === undefined)
@@ -24668,6 +25059,10 @@ function buildSystemPrompt(config) {
24668
25059
  const modelId = config.modelId ?? "the configured model";
24669
25060
  const toolLines = [
24670
25061
  toolLine(config, "bash", "read and inspect configured document collections with ls, find, grep, cat, and similar tools, and — in local sessions — organize local files when the user asks (rename, move, copy, create folders, move to trash)"),
25062
+ ...config.remoteSession ? [] : [
25063
+ toolLine(config, "write", "create a new file in local sessions — use this to produce requested deliverables and artifacts in a file format aligned with the request and context without overwriting existing source files"),
25064
+ toolLine(config, "edit", "surgically update an existing deliverable or config file when the user asks (local sessions only)")
25065
+ ],
24671
25066
  toolLine(config, "jikji_find", "local discovery through Jikji answer packs"),
24672
25067
  toolLine(config, "everything_search", "instant Windows file/folder name search (voidtools Everything) across the configured search folders"),
24673
25068
  toolLine(config, "fsearch_search", "instant macOS/Linux file/folder name search (fsearch-cli/FSearch) across the configured search folders, with a slow filesystem-walk fallback"),
@@ -24684,6 +25079,12 @@ function buildSystemPrompt(config) {
24684
25079
  ...config.toolNames.filter((name) => name.startsWith("search_datasource_")).map((name) => `- **${name}**: search only the ${name.slice("search_datasource_".length).replace(/_/g, "-")} datasource connection, spawning no other datasource CLIs`),
24685
25080
  ...config.toolNames.filter((name) => ![
24686
25081
  "bash",
25082
+ "read",
25083
+ "write",
25084
+ "edit",
25085
+ "grep",
25086
+ "find",
25087
+ "ls",
24687
25088
  "jikji_find",
24688
25089
  "everything_search",
24689
25090
  "fsearch_search",
@@ -24816,8 +25217,9 @@ Your answer is your final reply, written as plain text, never as a tool call. Ci
24816
25217
  ## Answer Guidelines
24817
25218
 
24818
25219
  - **Complete vs. delta answer**: When no first answer reached the caller, give the complete core answer. When a first answer was already delivered, return only the delta against it — corrections and newly verified findings — and never repeat its unchanged content; if nothing changed, confirm the first answer in one short line.
24819
- - **Bullet-point core answer**: Provide the core answer to the user's question in at most 5 bullet points. If additional explanation or context is necessary, append it after the bullet points.
24820
- - **Direct answer only**: The caller only needs the answer to their question. Never include specific file paths, datasource descriptions, or retrieval mechanics/principles in the answer (source paths are attached to each citation from the evidence ids you cite). Do not quote raw chunk text.
25220
+ - **Bullet-point core answer**: Unless the user explicitly requests a different format or length, provide the core answer to the user's question in at most 5 bullet points. If additional explanation or context is necessary, append it after the bullet points.
25221
+ - **Direct answer only**: Unless the user explicitly requests otherwise, the caller only needs the direct answer to their question. Never include specific file paths, datasource descriptions, or retrieval mechanics/principles in the answer (source paths are attached to each citation from the evidence ids you cite). Do not quote raw chunk text.
25222
+ - ${ANSWER_DELIVERABLE_RULE} ${ANSWER_DELIVERABLE_DELTA_RULE}
24821
25223
  - ${ANSWER_CITATION_RULE}
24822
25224
  - ${ANSWER_IMAGE_EMBED_RULE} ${ANSWER_IMAGE_DELTA_RULE}
24823
25225
  - **No per-source negative reports**: Never report individual negative findings per source (e.g. "no information found in Slack" or "checked Drive but found nothing"). Simply omit unproductive sources from the answer and focus on what was found or provide a concise overall conclusion.
@@ -28390,12 +28792,11 @@ function eventAnswer(event) {
28390
28792
  }
28391
28793
  return "";
28392
28794
  }
28393
- var PERPLEXITY_ASK_URL = "https://www.perplexity.ai/rest/sse/perplexity_ask", API_VERSION2 = "2.18", DEFLECTION_PATTERN, MAX_ERROR_BYTES4, ANONYMOUS_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", PERPLEXITY_QUERY_SYNTAX, PerplexityProvider;
28795
+ var PERPLEXITY_ASK_URL = "https://www.perplexity.ai/rest/sse/perplexity_ask", API_VERSION2 = "2.18", MAX_ERROR_BYTES4, ANONYMOUS_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", PERPLEXITY_QUERY_SYNTAX, PerplexityProvider;
28394
28796
  var init_perplexity = __esm(() => {
28395
28797
  init_query();
28396
28798
  init_types4();
28397
28799
  init_utils();
28398
- DEFLECTION_PATTERN = /sign up|log in|create an account|verify you are human|are you a robot|가입|로그인|회원 ?가입/i;
28399
28800
  MAX_ERROR_BYTES4 = 8 * 1024;
28400
28801
  PERPLEXITY_QUERY_SYNTAX = {
28401
28802
  phrases: true,
@@ -28509,8 +28910,8 @@ var init_perplexity = __esm(() => {
28509
28910
  break;
28510
28911
  }
28511
28912
  const sources = [...sourcesByUrl.values()];
28512
- if (sources.length === 0 && DEFLECTION_PATTERN.test(answer)) {
28513
- throw new SearchProviderError("perplexity", "Perplexity anonymous ask was deflected with a sign-up wall", 403);
28913
+ if (sources.length === 0) {
28914
+ throw new SearchProviderError("perplexity", `Perplexity anonymous ask returned no web sources (sign-up wall or conversational deflection): ${answer.slice(0, 120)}`, 403);
28514
28915
  }
28515
28916
  const numResults = params.numSearchResults ?? params.limit;
28516
28917
  return {
@@ -28928,7 +29329,7 @@ var init_browser_headers = __esm(() => {
28928
29329
  });
28929
29330
 
28930
29331
  // src/web/search/browser-loader.ts
28931
- import { existsSync as existsSync30 } from "node:fs";
29332
+ import { existsSync as existsSync31 } from "node:fs";
28932
29333
  async function closeBounded(close) {
28933
29334
  let timer;
28934
29335
  try {
@@ -29007,7 +29408,7 @@ function ensureWebSearchBrowserLoader(options = {}) {
29007
29408
  return true;
29008
29409
  const exists = options.exists ?? ((path) => {
29009
29410
  try {
29010
- return existsSync30(path);
29411
+ return existsSync31(path);
29011
29412
  } catch {
29012
29413
  return false;
29013
29414
  }
@@ -29112,6 +29513,8 @@ function normalizeText(value) {
29112
29513
  function isChallengeResponse(page) {
29113
29514
  if (/\/(?:errors|captcha)\//.test(page.url) || page.url.includes("/sp/captcha"))
29114
29515
  return true;
29516
+ if (ANUBIS_CHALLENGE_PATTERN.test(page.html))
29517
+ return true;
29115
29518
  return page.html.includes("component---src-pages-captcha") || page.html.includes("/sp/captcha");
29116
29519
  }
29117
29520
  function parseSearchFormInputs(html) {
@@ -29216,7 +29619,7 @@ async function callStartpageHtml(params) {
29216
29619
  });
29217
29620
  }
29218
29621
  if (isChallengeResponse(page)) {
29219
- throw new SearchProviderError("startpage", "Startpage blocked the request with a CAPTCHA challenge. Startpage rate-limits automated searches from datacenter/shared-egress IPs; try another provider such as DuckDuckGo or Mojeek, or retry later.", 429);
29622
+ throw new SearchProviderError("startpage", "Startpage blocked the request with a bot challenge (CAPTCHA or Anubis proof-of-work). Startpage rate-limits automated searches from datacenter/shared-egress IPs; try another provider such as DuckDuckGo or Mojeek, or retry later.", 429);
29220
29623
  }
29221
29624
  if (page.status < 200 || page.status >= 300) {
29222
29625
  const classified = classifyProviderHttpError("startpage", page.status, page.html);
@@ -29242,7 +29645,7 @@ async function searchStartpage(params) {
29242
29645
  }
29243
29646
  return { provider: "startpage", sources };
29244
29647
  }
29245
- var STARTPAGE_HOME_URL = "https://www.startpage.com/", STARTPAGE_SEARCH_URL = "https://www.startpage.com/sp/search", DEFAULT_NUM_RESULTS3 = 10, MAX_NUM_RESULTS3 = 20, RECENCY_TO_STARTPAGE_WITH_DATE, StartpageProvider;
29648
+ var STARTPAGE_HOME_URL = "https://www.startpage.com/", STARTPAGE_SEARCH_URL = "https://www.startpage.com/sp/search", DEFAULT_NUM_RESULTS3 = 10, MAX_NUM_RESULTS3 = 20, RECENCY_TO_STARTPAGE_WITH_DATE, ANUBIS_CHALLENGE_PATTERN, StartpageProvider;
29246
29649
  var init_startpage = __esm(() => {
29247
29650
  init_entities();
29248
29651
  init_browser_page();
@@ -29255,6 +29658,7 @@ var init_startpage = __esm(() => {
29255
29658
  month: "m",
29256
29659
  year: "y"
29257
29660
  };
29661
+ ANUBIS_CHALLENGE_PATTERN = /<script\b[^>]*\bid\s*=\s*["']anubis_(?:challenge|version)["']/i;
29258
29662
  StartpageProvider = class StartpageProvider extends SearchProvider {
29259
29663
  id = "startpage";
29260
29664
  label = "Startpage";
@@ -30472,8 +30876,8 @@ var init_web_search_tool = __esm(() => {
30472
30876
 
30473
30877
  // src/agent/agent.ts
30474
30878
  import { randomUUID as randomUUID8 } from "node:crypto";
30475
- import { existsSync as existsSync31, watch as fsWatch, mkdirSync as mkdirSync25, realpathSync as realpathSync5, statSync as statSync3, writeFileSync as writeFileSync22 } from "node:fs";
30476
- import { basename as basename14, dirname as dirname27, join as join41, resolve as resolve16 } from "node:path";
30879
+ import { existsSync as existsSync32, watch as fsWatch, mkdirSync as mkdirSync25, realpathSync as realpathSync5, statSync as statSync3, writeFileSync as writeFileSync22 } from "node:fs";
30880
+ import { basename as basename14, dirname as dirname27, join as join41, resolve as resolve17 } from "node:path";
30477
30881
  import { clampThinkingLevel } from "@earendil-works/pi-ai/compat";
30478
30882
  import { getAgentDir as getAgentDir4 } from "@earendil-works/pi-coding-agent";
30479
30883
  function positiveLimit(value, fallback, path) {
@@ -30603,6 +31007,9 @@ class AutoRAGAgent {
30603
31007
  piSessionDir;
30604
31008
  persistPiSessions;
30605
31009
  updateNotice;
31010
+ piExtensionPaths;
31011
+ piExtensionFlags;
31012
+ telegram;
30606
31013
  boundPiRuntime;
30607
31014
  memoryEmbedder;
30608
31015
  memoryVectorPath;
@@ -30628,12 +31035,15 @@ class AutoRAGAgent {
30628
31035
  this.piSessionDir = options.piSessionDir;
30629
31036
  this.persistPiSessions = options.persistPiSessions ?? true;
30630
31037
  this.updateNotice = options.updateNotice;
31038
+ this.piExtensionPaths = options.piExtensionPaths;
31039
+ this.piExtensionFlags = options.piExtensionFlags;
31040
+ this.telegram = options.telegram === true;
30631
31041
  this.datasourceSkills = options.datasourceSkills ?? [];
30632
31042
  this.datasourceRetry = options.datasourceRetry ?? {};
30633
31043
  this.datasourceVirtualScopePrefixes = this.datasourceSkills.map((skill) => normalizeVirtualPath2(`/${skill.describe().name}`));
30634
31044
  this.startupDiagnostics = options.startupDiagnostics ?? [];
30635
31045
  this.datasourceAgentSkills = this.buildDatasourceAgentSkills();
30636
- this.configuredSearchPaths = options.searchPaths.map((searchPath) => resolve16(searchPath));
31046
+ this.configuredSearchPaths = options.searchPaths.map((searchPath) => resolve17(searchPath));
30637
31047
  this.languages = options.languages ?? DEFAULT_LANGUAGES;
30638
31048
  this.searchPaths = options.searchPaths.map(pinSearchRoot);
30639
31049
  this.jikjiRoots = outermostJikjiRoots(this.searchPaths);
@@ -30680,7 +31090,8 @@ class AutoRAGAgent {
30680
31090
  const everythingClient = new EverythingClient({
30681
31091
  ...options.everything ?? {},
30682
31092
  root: this.workspaceProjectRoot,
30683
- folders: this.searchPaths
31093
+ folders: this.searchPaths,
31094
+ excludeFolders: this.excludePaths
30684
31095
  });
30685
31096
  if (everythingClient.isSupported())
30686
31097
  this.everythingClient = everythingClient;
@@ -30786,8 +31197,9 @@ class AutoRAGAgent {
30786
31197
  return true;
30787
31198
  });
30788
31199
  this.tools = tools;
31200
+ const builtinToolNames = this.remoteSession ? PI_BUILTIN_TOOL_NAMES.filter((name) => name !== "write" && name !== "edit") : PI_BUILTIN_TOOL_NAMES;
30789
31201
  const toolNames = [
30790
- ...PI_BUILTIN_TOOL_NAMES,
31202
+ ...builtinToolNames,
30791
31203
  ...tools.map((tool) => tool.name),
30792
31204
  ...this.jevExtension !== undefined ? [JEV_TOOL_NAME] : []
30793
31205
  ];
@@ -30803,7 +31215,7 @@ class AutoRAGAgent {
30803
31215
  this.innerAgent = {
30804
31216
  state: {
30805
31217
  systemPrompt,
30806
- tools: [...PI_BUILTIN_TOOL_NAMES.map((name) => ({ name })), ...tools],
31218
+ tools: [...builtinToolNames.map((name) => ({ name })), ...tools],
30807
31219
  messages: []
30808
31220
  },
30809
31221
  transformContext: (messages) => this.withMemoryContext(messages)
@@ -31097,6 +31509,7 @@ ${context.text}
31097
31509
  }
31098
31510
  async createPiInteractiveRuntime() {
31099
31511
  const resolved = this.configuredModel === undefined ? undefined : await this.resolveSessionModel();
31512
+ const telegramBridge = this.telegram ? (await import("@llblab/pi-telegram")).default : undefined;
31100
31513
  const runtime = await createAutoRAGPiInteractiveRuntime({
31101
31514
  cwd: this.workspaceProjectRoot,
31102
31515
  agentDir: this.piAgentDir,
@@ -31117,10 +31530,14 @@ ${context.text}
31117
31530
  onKey: (key) => this.handleInteractiveKey(key),
31118
31531
  extensionFactories: [
31119
31532
  createEvidencePersistence(this.evidenceLedger),
31120
- ...this.jevExtension !== undefined ? [this.jevExtension] : []
31533
+ ...this.jevExtension !== undefined ? [this.jevExtension] : [],
31534
+ ...telegramBridge !== undefined ? [telegramBridge] : []
31121
31535
  ],
31122
31536
  ...this.jevExtension !== undefined ? { extensionToolNames: [JEV_TOOL_NAME] } : {},
31123
- ...this.updateNotice === undefined ? {} : { updateNotice: this.updateNotice }
31537
+ remoteSession: this.remoteSession,
31538
+ ...this.updateNotice === undefined ? {} : { updateNotice: this.updateNotice },
31539
+ ...this.piExtensionPaths === undefined ? {} : { extensionPaths: this.piExtensionPaths },
31540
+ ...this.piExtensionFlags === undefined ? {} : { extensionFlags: this.piExtensionFlags }
31124
31541
  });
31125
31542
  this.boundPiRuntime = runtime.runtime;
31126
31543
  const dispose = runtime.dispose;
@@ -31574,7 +31991,7 @@ Warning: the edited config did not validate, so the previous config was restored
31574
31991
  result.summary,
31575
31992
  ...result.evidence.map((evidence) => evidence.excerpt)
31576
31993
  ])
31577
- ], [this.workspaceProjectRoot, ...this.searchPaths].map((root) => resolve16(root)));
31994
+ ], [this.workspaceProjectRoot, ...this.searchPaths].map((root) => resolve17(root)));
31578
31995
  if (!scan.ok)
31579
31996
  throw new RemoteSessionRejectedError(scan.code);
31580
31997
  }
@@ -32040,21 +32457,22 @@ ${result.content.replace(/\s+/gu, " ")}`).join(`
32040
32457
  const scope = options.scope ? ` Restrict search to virtual path scope ${options.scope}.` : "";
32041
32458
  return `Answer this original query immediately: ${query}${limit}${scope}
32042
32459
 
32043
- ` + `Baseline retrieval evidence (already gathered for you):
32460
+ Baseline retrieval evidence (already gathered for you):
32044
32461
  ${baseline}
32045
32462
 
32046
- ` + `Produce the best complete, self-contained answer you can RIGHT NOW from this evidence. Do NOT call any search, retrieval, or file-reading tools and do NOT wait for more evidence. ` + `If the query is answerable from general knowledge alone, answer directly.
32463
+ Produce the best complete, self-contained answer you can RIGHT NOW from this evidence. Do NOT call any search, retrieval, or file-reading tools and do NOT wait for more evidence. If the query is answerable from general knowledge alone, answer directly.
32047
32464
 
32048
- ` + `Formatting and content rules for the answer:
32049
- ` + `- Provide the core answer to the user's question in at most 5 bullet points. If additional explanation is necessary, append it after the bullet points.
32050
- ` + `- Answer the question directly. Do not include specific file paths, datasource descriptions, or retrieval mechanics in the answer text.
32051
- ` + `- ${ANSWER_CITATION_RULE}
32052
- ` + `- ${ANSWER_IMAGE_EMBED_RULE}
32053
- ` + `- Do not report per-source negative findings (e.g. "no information found in Slack" or "checked Drive but found nothing").
32054
- ` + `- When evidence conflicts, treat the freshest (most recent) information as the correct source of truth.
32055
- ` + `- If information is incomplete or uncertain, acknowledge it briefly without lengthy explanations, stating that it is difficult to answer fully with the given information and searching continues. If there are partial clues or leads (even if not the exact answer), mention those clues concisely.
32465
+ Formatting and content rules for the answer:
32466
+ - Unless the user explicitly requests a different format or length, provide the core answer to the user's question in at most 5 bullet points. If additional explanation is necessary, append it after the bullet points.
32467
+ - Unless the user explicitly requests otherwise, answer the question directly. Do not include specific file paths, datasource descriptions, or retrieval mechanics in the answer text.
32468
+ - ${ANSWER_DELIVERABLE_RULE}
32469
+ - ${ANSWER_CITATION_RULE}
32470
+ - ${ANSWER_IMAGE_EMBED_RULE}
32471
+ - Do not report per-source negative findings (e.g. "no information found in Slack" or "checked Drive but found nothing").
32472
+ - When evidence conflicts, treat the freshest (most recent) information as the correct source of truth.
32473
+ - If information is incomplete or uncertain, acknowledge it briefly without lengthy explanations, stating that it is difficult to answer fully with the given information and searching continues. If there are partial clues or leads (even if not the exact answer), mention those clues concisely.
32056
32474
 
32057
- ` + `Write the answer as your reply, then stop.`;
32475
+ Write the answer as your reply, then stop.`;
32058
32476
  }
32059
32477
  isConversationTurn() {
32060
32478
  if (this.remoteSession || this.boundPiRuntime === undefined)
@@ -32064,7 +32482,7 @@ ${baseline}
32064
32482
  buildDirectAnswerPrompt(query) {
32065
32483
  return `Answer this query directly from your own general knowledge: ${query}
32066
32484
 
32067
- ` + `It needs no search: it is general knowledge, simple reasoning, or conversation. Do NOT call any search, retrieval, web, or file-reading tools. ` + `Reply naturally and concisely; for small talk, just respond conversationally. Do not cite sources or mention retrieval.`;
32485
+ It needs no search: it is general knowledge, simple reasoning, or conversation. Do NOT call any search, retrieval, web, or file-reading tools. Reply naturally and concisely; for small talk, just respond conversationally. Do not cite sources or mention retrieval.`;
32068
32486
  }
32069
32487
  discoveryHint(sentence) {
32070
32488
  const names = this.tools.map((tool) => tool.name).filter((name) => name === JIKJI_FIND_TOOL_NAME || name === EVERYTHING_SEARCH_TOOL_NAME);
@@ -32082,52 +32500,55 @@ ${baseline}
32082
32500
  const scope = options.scope ? ` Restrict search to virtual path scope ${options.scope}.` : "";
32083
32501
  const firstAnswer = formatFirstAnswerContext(fastAnswer, fastAnswerDelivered);
32084
32502
  const answerRules = fastAnswerDelivered ? `Formatting and content rules for your final answer (DELTA ONLY):
32085
- ` + `- The user already has the first answer above. Your final answer MUST contain only the delta against it: (a) corrections to anything in the first answer that is wrong, unsupported, or outdated, and (b) newly verified findings that were not present in the first answer.
32086
- ` + `- NEVER restate or re-list first-answer facts that remain correct, and never repeat its bullet list.
32087
- ` + `- Mark each item clearly as a correction or as a new finding.
32088
- ` + `- If verification changed nothing and found nothing new, say so in one short line (the first answer is confirmed as-is) instead of restating it.
32089
- ` + `- ${ANSWER_CITATION_RULE} A correction or new finding that relies on evidence the first answer cited must cite that evidence's id again.
32090
- ` + `- ${ANSWER_IMAGE_EMBED_RULE} ${ANSWER_IMAGE_DELTA_RULE}
32091
- ` + `- Do not report per-source negative findings (e.g. "no information found in Slack").
32092
- ` + `- When evidence conflicts, treat the freshest (most recent) information as the correct source of truth.` : `Formatting and content rules for the final answer (COMPLETE — no first answer reached the caller):
32093
- ` + `- Provide the core answer to the user's question in at most 5 bullet points. If additional explanation is necessary, append it after the bullet points.
32094
- ` + `- Answer the question directly. Do not include specific file paths, datasource descriptions, or retrieval mechanics in the answer text.
32095
- ` + `- ${ANSWER_CITATION_RULE}
32096
- ` + `- ${ANSWER_IMAGE_EMBED_RULE}
32097
- ` + `- Do not report per-source negative findings (e.g. "no information found in Slack").
32098
- ` + `- When evidence conflicts, treat the freshest (most recent) information as the correct source of truth.`;
32503
+ - The user already has the first answer above. Your final answer MUST contain only the delta against it: (a) corrections to anything in the first answer that is wrong, unsupported, or outdated, and (b) newly verified findings that were not present in the first answer.
32504
+ - NEVER restate or re-list first-answer facts that remain correct, and never repeat its bullet list.
32505
+ - Mark each item clearly as a correction or as a new finding.
32506
+ - If verification changed nothing and found nothing new, say so in one short line (the first answer is confirmed as-is) instead of restating it.
32507
+ - ${ANSWER_DELIVERABLE_RULE} ${ANSWER_DELIVERABLE_DELTA_RULE}
32508
+ - ${ANSWER_CITATION_RULE} A correction or new finding that relies on evidence the first answer cited must cite that evidence's id again.
32509
+ - ${ANSWER_IMAGE_EMBED_RULE} ${ANSWER_IMAGE_DELTA_RULE}
32510
+ - Do not report per-source negative findings (e.g. "no information found in Slack").
32511
+ - When evidence conflicts, treat the freshest (most recent) information as the correct source of truth.` : `Formatting and content rules for the final answer (COMPLETE — no first answer reached the caller):
32512
+ ` + `- Unless the user explicitly requests a different format or length, provide the core answer to the user's question in at most 5 bullet points. If additional explanation is necessary, append it after the bullet points.
32513
+ - Unless the user explicitly requests otherwise, answer the question directly. Do not include specific file paths, datasource descriptions, or retrieval mechanics in the answer text.
32514
+ - ${ANSWER_DELIVERABLE_RULE}${fastAnswer !== undefined ? ` ${ANSWER_DELIVERABLE_DELTA_RULE}` : ""}
32515
+ - ${ANSWER_CITATION_RULE}
32516
+ - ${ANSWER_IMAGE_EMBED_RULE}
32517
+ - Do not report per-source negative findings (e.g. "no information found in Slack").
32518
+ - When evidence conflicts, treat the freshest (most recent) information as the correct source of truth.`;
32099
32519
  return `Original query: ${query}${limit}${scope}
32100
32520
 
32101
- ` + `${firstAnswer}
32521
+ ${firstAnswer}
32102
32522
 
32103
32523
  ` + formatSteerContext(steers) + `Now verify it rigorously. ${this.discoveryHint((tools) => `Actively use ${tools} when discovering or exploring local files and folders. `)}Check important claims against source files with bash when needed, correct anything wrong or unsupported, fill gaps with retrieval tools, and resolve conflicts and freshness. ${this.webFallbackHint()}
32104
32524
 
32105
- ` + `${answerRules}
32525
+ ${answerRules}
32106
32526
 
32107
- ` + `Do not use broad grep/find or recursive filesystem scans: only inspect a path or narrow neighborhood surfaced by retrieval, and only when evidence clearly points there. ` + `Avoid spinning repeated near-identical queries against the same datasource; once additional attempts stop surfacing new evidence, conclude from the evidence available. ` + `If more search is needed, first write a brief 1–2 line progress update stating the best current hypothesis and what you are checking next, then call retrieval tools. ` + `When finished, reply with the final answer and no tool call: that last reply is what the user receives, so keep progress notes out of it.`;
32527
+ Do not use broad grep/find or recursive filesystem scans: only inspect a path or narrow neighborhood surfaced by retrieval, and only when evidence clearly points there. Avoid spinning repeated near-identical queries against the same datasource; once additional attempts stop surfacing new evidence, conclude from the evidence available. ` + `If more search is needed, first write a brief 1–2 line progress update stating the best current hypothesis and what you are checking next, then call retrieval tools. ` + `When finished, reply with the final answer and no tool call: that last reply is what the user receives, so keep progress notes out of it.`;
32108
32528
  }
32109
32529
  buildSearchPrompt(query, options, initialRetrievalContext) {
32110
32530
  const limit = typeof options.topK === "number" ? ` Cite at most ${options.topK} sources.` : "";
32111
32531
  const scope = options.scope ? ` Restrict search to virtual path scope ${options.scope}.` : "";
32112
32532
  return `Find and curate information for this original query: ${query}${limit}${scope}
32113
32533
 
32114
- ` + `Start by deciding whether this is answerable from general knowledge or memory. If it is a generic, stable question, answer it immediately without retrieval. ` + `Otherwise, baseline MinSync and Jikji retrieval is already running in parallel; do not give the final answer until its next message arrives.
32534
+ Start by deciding whether this is answerable from general knowledge or memory. If it is a generic, stable question, answer it immediately without retrieval. Otherwise, baseline MinSync and Jikji retrieval is already running in parallel; do not give the final answer until its next message arrives.
32115
32535
 
32116
- ` + `Baseline retrieval context:
32536
+ Baseline retrieval context:
32117
32537
  ${initialRetrievalContext ?? "Pending; continue only with a brief progress statement."}
32118
32538
 
32119
- ` + `Treat candidates as unverified evidence, verify important claims against source files when needed, and use additional tools when needed. ` + `Judge relevance, conflicts, freshness, and sufficiency in this agent loop.
32539
+ Treat candidates as unverified evidence, verify important claims against source files when needed, and use additional tools when needed. Judge relevance, conflicts, freshness, and sufficiency in this agent loop.
32120
32540
 
32121
- ` + `Formatting and content rules for the answer:
32122
- ` + `- Provide the core answer to the user's question in at most 5 bullet points. If additional explanation is necessary, append it after the bullet points.
32123
- ` + `- Answer the question directly. Do not include specific file paths, datasource descriptions, or retrieval mechanics in the answer text.
32124
- ` + `- ${ANSWER_CITATION_RULE}
32125
- ` + `- ${ANSWER_IMAGE_EMBED_RULE}
32126
- ` + `- Do not report per-source negative findings (e.g. "no information found in Slack").
32127
- ` + `- When evidence conflicts, treat the freshest (most recent) information as the correct source of truth.
32128
- ` + `- If information is incomplete or uncertain, acknowledge it briefly without lengthy explanations. If there are partial clues or leads, mention them concisely.
32541
+ Formatting and content rules for the answer:
32542
+ - Unless the user explicitly requests a different format or length, provide the core answer to the user's question in at most 5 bullet points. If additional explanation is necessary, append it after the bullet points.
32543
+ - Unless the user explicitly requests otherwise, answer the question directly. Do not include specific file paths, datasource descriptions, or retrieval mechanics in the answer text.
32544
+ - ${ANSWER_DELIVERABLE_RULE}
32545
+ - ${ANSWER_CITATION_RULE}
32546
+ - ${ANSWER_IMAGE_EMBED_RULE}
32547
+ - Do not report per-source negative findings (e.g. "no information found in Slack").
32548
+ - When evidence conflicts, treat the freshest (most recent) information as the correct source of truth.
32549
+ - If information is incomplete or uncertain, acknowledge it briefly without lengthy explanations. If there are partial clues or leads, mention them concisely.
32129
32550
 
32130
- ` + `${this.discoveryHint((tools) => `When exploring local files and folders, actively use ${tools} rather than exploratory bash commands. `)}If more search is needed, first write a brief 1–2 line progress update stating the best current hypothesis and what you are checking next, then call retrieval tools. ` + `Never repeat a generic status message. Do not use broad grep/find or recursive filesystem scans: only inspect a path or narrow neighborhood surfaced by retrieval, and only when evidence clearly points there. ` + `Avoid spinning repeated near-identical queries against the same datasource; once additional attempts stop surfacing new evidence, conclude from the evidence available. ` + `When finished, reply with the final answer and no tool call: that last reply is what the user receives, so keep progress notes out of it.`;
32551
+ ${this.discoveryHint((tools) => `When exploring local files and folders, actively use ${tools} rather than exploratory bash commands. `)}If more search is needed, first write a brief 1–2 line progress update stating the best current hypothesis and what you are checking next, then call retrieval tools. ` + `Never repeat a generic status message. Do not use broad grep/find or recursive filesystem scans: only inspect a path or narrow neighborhood surfaced by retrieval, and only when evidence clearly points there. Avoid spinning repeated near-identical queries against the same datasource; once additional attempts stop surfacing new evidence, conclude from the evidence available. When finished, reply with the final answer and no tool call: that last reply is what the user receives, so keep progress notes out of it.`;
32131
32552
  }
32132
32553
  async refresh(force = false, opts) {
32133
32554
  const signal = opts?.signal;
@@ -32363,7 +32784,7 @@ ${initialRetrievalContext ?? "Pending; continue only with a brief progress state
32363
32784
  });
32364
32785
  }
32365
32786
  const state = this.refreshState.inFlight || persistedRunning && ownerAlive ? "indexing" : interrupted ? "failed" : persistedProgress?.state === "success" || persistedProgress?.state === "failed" ? persistedProgress.state : this.refreshState.lastOutcome === "never" ? "idle" : this.refreshState.lastOutcome;
32366
- const parsedMirrorReady = this.refreshState.lastOutcome === "success" || persistedProgress?.state === "success" || existsSync31(refreshReadinessPath(this.workspaceProjectRoot));
32787
+ const parsedMirrorReady = this.refreshState.lastOutcome === "success" || persistedProgress?.state === "success" || existsSync32(refreshReadinessPath(this.workspaceProjectRoot));
32367
32788
  const effectiveProgress = this.refreshState.inFlight || persistedRunning ? persistedProgress : undefined;
32368
32789
  return {
32369
32790
  state,
@@ -32405,7 +32826,7 @@ ${initialRetrievalContext ?? "Pending; continue only with a brief progress state
32405
32826
  }
32406
32827
  startWatchRefresh(options = {}) {
32407
32828
  this.refreshState = { ...this.refreshState, watchLimited: false, watchFailed: false };
32408
- const dirs = this.searchPaths.map((searchPath) => resolve16(searchPath));
32829
+ const dirs = this.searchPaths.map((searchPath) => resolve17(searchPath));
32409
32830
  const watcherFactory = options.watcherFactory ?? this.defaultWatcherFactory();
32410
32831
  return createWatchRefresh({
32411
32832
  dirs,
@@ -32582,7 +33003,7 @@ ${initialRetrievalContext ?? "Pending; continue only with a brief progress state
32582
33003
  const evidencePack = [];
32583
33004
  const allPaths = [];
32584
33005
  for (const entry of entries) {
32585
- const originRoot = sourceRoots.find((sr) => sr.rootPath === resolve16(entry.root));
33006
+ const originRoot = sourceRoots.find((sr) => sr.rootPath === resolve17(entry.root));
32586
33007
  if (originRoot === undefined)
32587
33008
  continue;
32588
33009
  const originRoots = [originRoot];
@@ -32823,7 +33244,7 @@ function buildSteerMessage(text) {
32823
33244
 
32824
33245
  ${text}
32825
33246
 
32826
- ` + `Treat it as direction for the search in progress, not as a new question: adjust which sources, queries, or claims you pursue, then continue. ` + `When finished, reply with the final answer to the original question, updated for this correction, and no tool call.`;
33247
+ Treat it as direction for the search in progress, not as a new question: adjust which sources, queries, or claims you pursue, then continue. When finished, reply with the final answer to the original question, updated for this correction, and no tool call.`;
32827
33248
  }
32828
33249
  function frameSteers(messages, steerTexts) {
32829
33250
  if (steerTexts.size === 0)
@@ -32843,12 +33264,12 @@ function formatSteerContext(steers) {
32843
33264
  return `While the search ran, the user corrected it:
32844
33265
  ${steers.map((steer) => `- ${steer}`).join(`
32845
33266
  `)}
32846
- ` + `The first answer predates these corrections; follow them over it.
33267
+ The first answer predates these corrections; follow them over it.
32847
33268
 
32848
33269
  `;
32849
33270
  }
32850
33271
  function buildSteerContinuationPrompt(steers) {
32851
- return `${formatSteerContext(steers)}Apply the latest correction: search or verify further if it needs that, ` + `then reply with the complete final answer to the original question and no tool call.`;
33272
+ return `${formatSteerContext(steers)}Apply the latest correction: search or verify further if it needs that, then reply with the complete final answer to the original question and no tool call.`;
32852
33273
  }
32853
33274
  function formatFirstAnswerContext(fastAnswer, delivered) {
32854
33275
  if (fastAnswer === undefined) {
@@ -32971,11 +33392,20 @@ function fsearchRefreshDiagnostics(fsearch) {
32971
33392
  function minSyncRefreshDiagnostics(minsync) {
32972
33393
  if (!minsync)
32973
33394
  return [];
33395
+ const diagnostics = [];
32974
33396
  if (minsync.diagnostic)
32975
- return [toMinSyncDiagnostic(minsync.diagnostic, minsync.ok)];
32976
- if (!minsync.ok && minsync.reason)
32977
- return [toMinSyncReasonDiagnostic(minsync.reason)];
32978
- return [];
33397
+ diagnostics.push(toMinSyncDiagnostic(minsync.diagnostic, minsync.ok));
33398
+ else if (!minsync.ok && minsync.reason)
33399
+ diagnostics.push(toMinSyncReasonDiagnostic(minsync.reason));
33400
+ if (minsync.lexicalFallback) {
33401
+ diagnostics.push({
33402
+ code: "lexical-tokenizer-unavailable",
33403
+ severity: "warning",
33404
+ message: `The "${minsync.lexicalFallback.requested}" BM25 tokenizer needs the Kiwi native library and model, which were not found, so MinSync tokenizes on whitespace and a Korean word with a particle (인증서를) does not match its stem (인증서). Install Kiwi (set KIWI_LIBRARY_PATH and KIWI_MODEL_PATH, or install it under ~/.local/kiwi), then refresh again.`,
33405
+ source: "minsync"
33406
+ });
33407
+ }
33408
+ return diagnostics;
32979
33409
  }
32980
33410
  function toMinSyncDiagnostic(diag, ok) {
32981
33411
  const code = diag.code === "embedder-unavailable" ? "embedder-unavailable" : diag.code === "embedding-identity-mismatch" ? "embedding-identity-mismatch" : diag.code === "minsync-lock-held" ? "minsync-lock-held" : "minsync-sync-failed";
@@ -32995,7 +33425,7 @@ function toMinSyncReasonDiagnostic(reason) {
32995
33425
  };
32996
33426
  }
32997
33427
  function pinExcludedPath(path) {
32998
- const resolvedPath = resolve16(path);
33428
+ const resolvedPath = resolve17(path);
32999
33429
  try {
33000
33430
  return join41(realpathSync5(dirname27(resolvedPath)), basename14(resolvedPath));
33001
33431
  } catch {
@@ -33003,7 +33433,7 @@ function pinExcludedPath(path) {
33003
33433
  }
33004
33434
  }
33005
33435
  function pinSearchRoot(searchPath) {
33006
- const resolvedPath = resolve16(searchPath);
33436
+ const resolvedPath = resolve17(searchPath);
33007
33437
  let canonicalPath;
33008
33438
  try {
33009
33439
  canonicalPath = realpathSync5(resolvedPath);
@@ -33455,8 +33885,8 @@ var init_memory2 = __esm(() => {
33455
33885
  });
33456
33886
 
33457
33887
  // src/cli/commands/index.ts
33458
- import { existsSync as existsSync32, lstatSync as lstatSync3, rmSync as rmSync11 } from "node:fs";
33459
- import { join as join42, resolve as resolve17, sep as sep8 } from "node:path";
33888
+ import { existsSync as existsSync33, lstatSync as lstatSync3, rmSync as rmSync11 } from "node:fs";
33889
+ import { join as join42, resolve as resolve18, sep as sep8 } from "node:path";
33460
33890
  async function runIndex2(ctx) {
33461
33891
  const sub = ctx.positionals[0];
33462
33892
  if (sub !== "reset" && sub !== "rebuild") {
@@ -33472,10 +33902,10 @@ async function runIndex2(ctx) {
33472
33902
  }
33473
33903
  const methods = parseMethodFlag(ctx.flags.method);
33474
33904
  const { targetNames, targetSubdirs, refreshMethods } = resolveResetScope(methods);
33475
- const autoragDir = resolve17(config.workspacePath, ".autorag");
33476
- const targets = targetSubdirs.map((subdir) => resolve17(config.workspacePath, subdir));
33905
+ const autoragDir = resolve18(config.workspacePath, ".autorag");
33906
+ const targets = targetSubdirs.map((subdir) => resolve18(config.workspacePath, subdir));
33477
33907
  for (const dir of [autoragDir, ...targets]) {
33478
- if (existsSync32(dir) && lstatSync3(dir).isSymbolicLink()) {
33908
+ if (existsSync33(dir) && lstatSync3(dir).isSymbolicLink()) {
33479
33909
  ctx.stderr(renderError(new Error("Refusing to reset: an index path is a symlink, not a real directory."), {
33480
33910
  json: ctx.json
33481
33911
  }));
@@ -33835,12 +34265,12 @@ var init_health = __esm(() => {
33835
34265
  });
33836
34266
 
33837
34267
  // src/cli/commands/duplicates.ts
33838
- import { resolve as resolve18 } from "node:path";
34268
+ import { resolve as resolve19 } from "node:path";
33839
34269
  async function runDuplicates(ctx, scanner = (path) => scanWithDupey(path)) {
33840
34270
  try {
33841
34271
  const config = resolveConfig({ flags: ctx.flags, cwd: ctx.cwd });
33842
34272
  const requested = ctx.positionals[0];
33843
- const roots = requested ? [resolve18(ctx.cwd, requested)] : config.searchPaths.map((path) => resolve18(path));
34273
+ const roots = requested ? [resolve19(ctx.cwd, requested)] : config.searchPaths.map((path) => resolve19(path));
33844
34274
  const scans = await Promise.all(roots.map((root) => scanner(root)));
33845
34275
  const exactGroups = scans.flatMap(exactGroupsFromScan);
33846
34276
  const extractionErrors = scans.flatMap((scan) => scan.errors);
@@ -33965,6 +34395,40 @@ var init_update_check = __esm(() => {
33965
34395
  SEMVER_PATTERN = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
33966
34396
  });
33967
34397
 
34398
+ // src/cli/whatsapp-extension.ts
34399
+ import { DefaultPackageManager, getAgentDir as getAgentDir5, SettingsManager as SettingsManager3 } from "@earendil-works/pi-coding-agent";
34400
+ async function installWithPi(cwd, sources) {
34401
+ const agentDir = getAgentDir5();
34402
+ const packageManager = new DefaultPackageManager({
34403
+ cwd,
34404
+ agentDir,
34405
+ settingsManager: SettingsManager3.create(cwd, agentDir)
34406
+ });
34407
+ const resolved = await packageManager.resolveExtensionSources(sources, { local: false, temporary: false });
34408
+ return resolved.extensions.filter((entry) => entry.enabled).map((entry) => entry.path);
34409
+ }
34410
+ async function resolveWhatsAppExtension(input) {
34411
+ const group = input.flags["whatsapp-group"];
34412
+ if (input.flags.whatsapp !== true) {
34413
+ if (group !== undefined)
34414
+ throw new Error("--whatsapp-group requires --whatsapp.");
34415
+ return;
34416
+ }
34417
+ const resolve = input.resolveSources ?? ((sources) => installWithPi(input.cwd ?? process.cwd(), sources));
34418
+ const paths = await resolve([WHATSAPP_PI_SOURCE]);
34419
+ if (paths.length === 0)
34420
+ throw new Error(`${WHATSAPP_PI_SOURCE} exposes no pi extension; cannot enable WhatsApp.`);
34421
+ return {
34422
+ paths,
34423
+ flags: {
34424
+ "whatsapp-pi-online": true,
34425
+ ...typeof group === "string" && group !== "" ? { "whatsapp-group": group } : {}
34426
+ }
34427
+ };
34428
+ }
34429
+ var WHATSAPP_PI_SOURCE = "npm:whatsapp-pi@1.0.70";
34430
+ var init_whatsapp_extension = () => {};
34431
+
33968
34432
  // src/cli/commands/tui.ts
33969
34433
  import { InteractiveMode } from "@earendil-works/pi-coding-agent";
33970
34434
  function parseThinkingFlags(flags) {
@@ -33985,6 +34449,8 @@ async function createTuiAgent(ctx) {
33985
34449
  const config = resolveConfig({ flags: ctx.flags, cwd: ctx.cwd });
33986
34450
  const options = { ...buildAgentOptions(config) };
33987
34451
  options.updateNotice = async () => renderAutoRAGUpdateNotice(await checkAutoRAGUpdate({ currentVersion: readPackageVersion() }));
34452
+ if (ctx.flags.telegram === true)
34453
+ options.telegram = true;
33988
34454
  const thinking = parseThinkingFlags(ctx.flags);
33989
34455
  if (thinking !== undefined)
33990
34456
  options.thinking = thinking;
@@ -33996,6 +34462,11 @@ async function createTuiAgent(ctx) {
33996
34462
  if (resolved.providerApiKeys !== undefined)
33997
34463
  options.providerApiKeys = resolved.providerApiKeys;
33998
34464
  }
34465
+ const whatsapp = await resolveWhatsAppExtension({ flags: ctx.flags, cwd: ctx.cwd });
34466
+ if (whatsapp !== undefined) {
34467
+ options.piExtensionPaths = whatsapp.paths;
34468
+ options.piExtensionFlags = whatsapp.flags;
34469
+ }
33999
34470
  const decompositionModel = await resolveQueryDecompositionModel(config);
34000
34471
  if (decompositionModel !== undefined)
34001
34472
  options.queryDecomposition = decompositionModel;
@@ -34036,11 +34507,12 @@ var init_tui = __esm(() => {
34036
34507
  init_config4();
34037
34508
  init_update_check();
34038
34509
  init_version();
34510
+ init_whatsapp_extension();
34039
34511
  THINKING_LEVELS2 = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
34040
34512
  });
34041
34513
 
34042
34514
  // src/cli/commands/serve.ts
34043
- import { existsSync as existsSync33 } from "node:fs";
34515
+ import { existsSync as existsSync34 } from "node:fs";
34044
34516
  import { completeSimple as completeSimple3 } from "@earendil-works/pi-ai/compat";
34045
34517
  function classifierText(message) {
34046
34518
  return message.content.filter((part) => part.type === "text").map((part) => part.text).join("");
@@ -34058,7 +34530,7 @@ function createInjectionClassifierModel(resolved) {
34058
34530
  async function runServe(ctx, deps = {}) {
34059
34531
  const flags = ctx.flags;
34060
34532
  const resolved = resolveConfigPath({ flags, cwd: ctx.cwd });
34061
- if (!existsSync33(resolved.configPath)) {
34533
+ if (!existsSync34(resolved.configPath)) {
34062
34534
  ctx.stderr(renderError(new ConfigError(`Config file not found: ${resolved.configPath}. Run autorag init first.`), {
34063
34535
  json: ctx.json
34064
34536
  }));
@@ -34439,7 +34911,7 @@ var init_p2p = __esm(() => {
34439
34911
  });
34440
34912
 
34441
34913
  // src/cli/commands/p2p-policy.ts
34442
- import { existsSync as existsSync34, mkdirSync as mkdirSync26, readFileSync as readFileSync28, writeFileSync as writeFileSync23 } from "node:fs";
34914
+ import { existsSync as existsSync35, mkdirSync as mkdirSync26, readFileSync as readFileSync28, writeFileSync as writeFileSync23 } from "node:fs";
34443
34915
  import { dirname as dirname28, join as join43 } from "node:path";
34444
34916
  import { parse as parseToml3, stringify as stringifyToml } from "smol-toml";
34445
34917
  function workspacePolicyPath(cwd) {
@@ -34470,7 +34942,7 @@ function normalizeEntry(value, key) {
34470
34942
  throw new Error(`Invalid policy entry for ${key}: ${JSON.stringify(value)}`);
34471
34943
  }
34472
34944
  function readPolicyToml(policyPath) {
34473
- if (!existsSync34(policyPath))
34945
+ if (!existsSync35(policyPath))
34474
34946
  return {};
34475
34947
  const parsed = parseToml3(readFileSync28(policyPath, "utf8"));
34476
34948
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
@@ -34505,7 +34977,7 @@ function writePolicyToml(policyPath, entries) {
34505
34977
  function getGlobalPolicy() {
34506
34978
  const home = resolveAutoRAGHome();
34507
34979
  const configPath = join43(home, "config.json");
34508
- if (!existsSync34(configPath))
34980
+ if (!existsSync35(configPath))
34509
34981
  return {};
34510
34982
  try {
34511
34983
  const raw = readFileSync28(configPath, "utf8");
@@ -34589,9 +35061,9 @@ var init_p2p_policy = __esm(() => {
34589
35061
 
34590
35062
  // src/cli/commands/models.ts
34591
35063
  import { join as join44 } from "node:path";
34592
- import { getAgentDir as getAgentDir5, ModelRuntime as ModelRuntime3 } from "@earendil-works/pi-coding-agent";
35064
+ import { getAgentDir as getAgentDir6, ModelRuntime as ModelRuntime3 } from "@earendil-works/pi-coding-agent";
34593
35065
  async function listModelCatalog(options) {
34594
- const agentDir = options.agentDir ?? getAgentDir5();
35066
+ const agentDir = options.agentDir ?? getAgentDir6();
34595
35067
  const runtime = await ModelRuntime3.create({
34596
35068
  authPath: join44(agentDir, "auth.json"),
34597
35069
  modelsPath: join44(agentDir, "models.json"),
@@ -35136,7 +35608,7 @@ var init_results = __esm(() => {
35136
35608
  confidence: Type18.Optional(Type18.Number({ description: "Evidence confidence, 0..1", minimum: 0, maximum: 1 }))
35137
35609
  });
35138
35610
  answerSchema = Type18.String({
35139
- description: `Answer for the caller. At most 5 bullet points (plus optional explanation); reference results by bracketed number (e.g. [1], [2]) without file paths or raw chunk text, except the image-embed exception below. ${ANSWER_CITATION_RULE} ${ANSWER_IMAGE_EMBED_RULE}`
35611
+ description: `Answer for the caller. Unless the user explicitly requests otherwise, provide at most 5 bullet points (plus optional explanation) and answer directly; reference results by bracketed number (e.g. [1], [2]) without file paths or raw chunk text, except the image-embed exception below. ${ANSWER_DELIVERABLE_RULE} ${ANSWER_CITATION_RULE} ${ANSWER_IMAGE_EMBED_RULE}`
35140
35612
  });
35141
35613
  warningsSchema = Type18.Optional(Type18.Array(Type18.String(), { description: "Optional warnings about this result set" }));
35142
35614
  curatedResultFields = {
@@ -35313,7 +35785,8 @@ Usage: autorag lite <subcommand> [args] [flags]
35313
35785
  Subcommands:
35314
35786
  autorag lite init
35315
35787
  Write the configured model-free lifecycle config
35316
- (--search-paths PATHS --workspace DIR --memory-path FILE --force)
35788
+ (--search-paths PATHS --exclude PATH --workspace DIR --memory-path FILE --force)
35789
+ --exclude may be repeated; relative paths use the current directory
35317
35790
  autorag lite refresh Run an incremental index refresh (no model required)
35318
35791
  (--full --force --method minsync,parsed,datasources,jikji,everything,fsearch,all)
35319
35792
  autorag lite watch Watch configured roots (or --once for one refresh tick)
@@ -35389,6 +35862,7 @@ var COMMAND_USAGE = {
35389
35862
  "",
35390
35863
  "Flags:",
35391
35864
  " --search-paths <csv> Folders to index/search",
35865
+ " --exclude <path> Exclude a file or folder (repeatable; relative to the current directory)",
35392
35866
  " --languages <csv> OCR/parser languages",
35393
35867
  " --workspace <dir> Workspace that owns .autorag state",
35394
35868
  " --memory-path <file> Retrieval memory file",
@@ -35549,7 +36023,14 @@ var COMMAND_USAGE = {
35549
36023
  "",
35550
36024
  "Flags:",
35551
36025
  " --fast-thinking <lvl> Thinking level for the fast first answer",
35552
- " --final-thinking <lvl> Thinking level for the verified answer"
36026
+ " --final-thinking <lvl> Thinking level for the verified answer",
36027
+ " --telegram Load the Telegram bridge (@llblab/pi-telegram) so a paired",
36028
+ " Telegram chat can ask, steer, and read answers. In-session:",
36029
+ " /telegram-setup (bot token), /telegram-connect, /telegram-status",
36030
+ " --whatsapp Control this session from WhatsApp (installs whatsapp-pi;",
36031
+ " pair once with /whatsapp, allow your contact there). Messages",
36032
+ " from allowed chats run as questions; answers go back to the chat",
36033
+ " --whatsapp-group <jid> With --whatsapp, answer only this WhatsApp group"
35553
36034
  ]),
35554
36035
  serve: usage([
35555
36036
  "autorag serve - P2P peer query server over SimpleX",
@@ -35652,11 +36133,14 @@ var BOOLEAN_FLAGS = new Set([
35652
36133
  "retry-failed",
35653
36134
  "strict",
35654
36135
  "refresh",
35655
- "available"
36136
+ "available",
36137
+ "whatsapp",
36138
+ "telegram"
35656
36139
  ]);
35657
36140
  var VALUE_FLAGS = new Set([
35658
36141
  "config",
35659
36142
  "search-paths",
36143
+ "exclude",
35660
36144
  "languages",
35661
36145
  "workspace",
35662
36146
  "memory-path",
@@ -35694,7 +36178,8 @@ var VALUE_FLAGS = new Set([
35694
36178
  "format",
35695
36179
  "provider",
35696
36180
  "fast-thinking",
35697
- "final-thinking"
36181
+ "final-thinking",
36182
+ "whatsapp-group"
35698
36183
  ]);
35699
36184
  var COMMANDS = [
35700
36185
  "init",
@@ -35742,6 +36227,7 @@ Commands:
35742
36227
  lite report <query> Persist a structured report (--input FILE)
35743
36228
  duplicates [DIR] Scan exact/near duplicate document families; never deletes files
35744
36229
  tui Open an interactive Pi-powered librarian terminal UI
36230
+ (--whatsapp [--whatsapp-group JID]: also take questions from WhatsApp)
35745
36231
  serve Start the P2P peer query server over SimpleX
35746
36232
  (--port N --force)
35747
36233
  p2p SimpleX peer trust management
@@ -35786,6 +36272,7 @@ Run: autorag <command> --help for command-specific usage.
35786
36272
  function parseArgs(argv) {
35787
36273
  const positionals = [];
35788
36274
  const flags = {};
36275
+ const repeatedFlags = {};
35789
36276
  for (let i = 0;i < argv.length; i++) {
35790
36277
  const token = argv[i];
35791
36278
  if (token === "-h") {
@@ -35806,22 +36293,27 @@ function parseArgs(argv) {
35806
36293
  if (!BOOLEAN_FLAGS.has(key) && !VALUE_FLAGS.has(key)) {
35807
36294
  return { error: `Unknown flag: --${key}` };
35808
36295
  }
35809
- if (eq !== -1) {
35810
- flags[key] = body.slice(eq + 1);
35811
- continue;
35812
- }
35813
- if (BOOLEAN_FLAGS.has(key)) {
36296
+ if (eq === -1 && BOOLEAN_FLAGS.has(key)) {
35814
36297
  flags[key] = true;
35815
36298
  continue;
35816
36299
  }
35817
- const next = argv[i + 1];
35818
- if (next === undefined || next.startsWith("--")) {
36300
+ const value = eq === -1 ? argv[++i] : body.slice(eq + 1);
36301
+ if (value === undefined || eq === -1 && value.startsWith("--")) {
35819
36302
  return { error: `Flag --${key} requires a value` };
35820
36303
  }
35821
- flags[key] = next;
35822
- i++;
36304
+ if (key === "exclude") {
36305
+ if (value.trim().length === 0)
36306
+ return { error: "Flag --exclude requires a non-empty path" };
36307
+ repeatedFlags[key] ??= [];
36308
+ repeatedFlags[key].push(value);
36309
+ }
36310
+ flags[key] = value;
35823
36311
  }
35824
- return { positionals, flags };
36312
+ return {
36313
+ positionals,
36314
+ flags,
36315
+ ...Object.keys(repeatedFlags).length > 0 ? { repeatedFlags } : {}
36316
+ };
35825
36317
  }
35826
36318
  function promptYesNo(question) {
35827
36319
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -35943,6 +36435,7 @@ ${USAGE2}`);
35943
36435
  const ctx = {
35944
36436
  positionals: positionals.slice(1),
35945
36437
  flags,
36438
+ ...parsed.repeatedFlags === undefined ? {} : { repeatedFlags: parsed.repeatedFlags },
35946
36439
  json,
35947
36440
  debug,
35948
36441
  cwd: process.cwd(),