@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/NOTICE +3 -0
- package/README.md +39 -5
- package/dist/agent/agent.d.ts +19 -4
- package/dist/agent/answer-guidelines.d.ts +17 -0
- package/dist/agent/pi-session.d.ts +21 -0
- package/dist/agent/search-documents.d.ts +1 -1
- package/dist/agent/web-search-tool.d.ts +2 -2
- package/dist/cli/commands/types.d.ts +2 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1218 -725
- package/dist/cli/whatsapp-extension.d.ts +21 -0
- package/dist/core.js +1903 -1482
- package/dist/datasource/sidecar/descriptors.d.ts +1 -0
- package/dist/datasource/skills/lazykatok/skill.d.ts +13 -0
- package/dist/everything/client.d.ts +5 -0
- package/dist/filesystem/path-exclusions.d.ts +2 -0
- package/dist/fsearch/client.d.ts +3 -2
- package/dist/fsearch/walk.d.ts +2 -0
- package/dist/index.js +1887 -1466
- package/dist/mcp/index.js +1887 -1466
- package/dist/minsync/client.d.ts +8 -0
- package/dist/minsync/embedder-config.d.ts +11 -1
- package/dist/minsync/installer.d.ts +1 -1
- package/dist/minsync/kiwi.d.ts +17 -0
- package/dist/minsync/lexical-language.d.ts +18 -0
- package/dist/minsync/types.d.ts +9 -0
- package/dist/web/search/types.d.ts +22 -18
- package/package.json +2 -1
- package/skills/autorag-doctor/SKILL.md +1 -0
- package/skills/autorag-lite-setup/SKILL.md +1 -1
- package/skills/autorag-setup/SKILL.md +1 -1
package/dist/core.js
CHANGED
|
@@ -2109,6 +2109,25 @@ function errorMessage(error) {
|
|
|
2109
2109
|
var MANIFEST_FILENAME = "manifest.json";
|
|
2110
2110
|
var init_bundle = () => {};
|
|
2111
2111
|
|
|
2112
|
+
// src/filesystem/path-exclusions.ts
|
|
2113
|
+
import { posix as posix2, win32 as win322 } from "node:path";
|
|
2114
|
+
function createPathExclusionMatcher(excludedPaths, platform = process.platform) {
|
|
2115
|
+
const paths = platform === "win32" ? win322 : posix2;
|
|
2116
|
+
const normalize = (path) => {
|
|
2117
|
+
const absolute = paths.resolve(path);
|
|
2118
|
+
return platform === "win32" ? absolute.toLowerCase() : absolute;
|
|
2119
|
+
};
|
|
2120
|
+
const excluded = excludedPaths.map((path) => {
|
|
2121
|
+
const absolute = normalize(path);
|
|
2122
|
+
return { absolute, prefix: absolute.endsWith(paths.sep) ? absolute : `${absolute}${paths.sep}` };
|
|
2123
|
+
});
|
|
2124
|
+
return (path) => {
|
|
2125
|
+
const absolute = normalize(path);
|
|
2126
|
+
return excluded.some((entry) => absolute === entry.absolute || absolute.startsWith(entry.prefix));
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
var init_path_exclusions = () => {};
|
|
2130
|
+
|
|
2112
2131
|
// src/everything/client.ts
|
|
2113
2132
|
import { spawn as spawn2 } from "node:child_process";
|
|
2114
2133
|
import { createHash as createHash4 } from "node:crypto";
|
|
@@ -2226,6 +2245,7 @@ class EverythingClient {
|
|
|
2226
2245
|
run;
|
|
2227
2246
|
launch;
|
|
2228
2247
|
stateDir;
|
|
2248
|
+
isExcludedPath;
|
|
2229
2249
|
binaries;
|
|
2230
2250
|
operation = Promise.resolve();
|
|
2231
2251
|
constructor(options) {
|
|
@@ -2235,6 +2255,7 @@ class EverythingClient {
|
|
|
2235
2255
|
this.run = options.run ?? runProcess;
|
|
2236
2256
|
this.launch = options.launch ?? launchDetached;
|
|
2237
2257
|
this.stateDir = join6(options.root, ".autorag", "everything");
|
|
2258
|
+
this.isExcludedPath = createPathExclusionMatcher(options.excludeFolders ?? [], "win32");
|
|
2238
2259
|
}
|
|
2239
2260
|
isSupported() {
|
|
2240
2261
|
return this.platform === "win32";
|
|
@@ -2254,6 +2275,9 @@ class EverythingClient {
|
|
|
2254
2275
|
}
|
|
2255
2276
|
const es = { ok: true, esPath: binaries.esPath };
|
|
2256
2277
|
const timeoutMs = this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
2278
|
+
if ((this.options.excludeFolders?.length ?? 0) > 0) {
|
|
2279
|
+
return this.searchWithExclusions(es.esPath, request, timeoutMs);
|
|
2280
|
+
}
|
|
2257
2281
|
const args = buildEverythingSearchArgs(this.instanceName, request, timeoutMs);
|
|
2258
2282
|
const result = await this.run(es.esPath, args, timeoutMs + 5000);
|
|
2259
2283
|
if (result.code !== 0)
|
|
@@ -2265,6 +2289,50 @@ class EverythingClient {
|
|
|
2265
2289
|
}
|
|
2266
2290
|
});
|
|
2267
2291
|
}
|
|
2292
|
+
async searchWithExclusions(esPath, request, timeoutMs) {
|
|
2293
|
+
const deadline = Date.now() + timeoutMs;
|
|
2294
|
+
const offset = request.offset ?? 0;
|
|
2295
|
+
const maxResults = request.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
2296
|
+
const results = [];
|
|
2297
|
+
let skipped = 0;
|
|
2298
|
+
let nativeOffset = 0;
|
|
2299
|
+
const timedOut = () => ({
|
|
2300
|
+
ok: false,
|
|
2301
|
+
reason: "search-failed",
|
|
2302
|
+
message: `Everything search exceeded ${timeoutMs}ms while filtering excluded paths; narrow the query or increase the Everything timeout.`
|
|
2303
|
+
});
|
|
2304
|
+
for (;; ) {
|
|
2305
|
+
const remainingMs = deadline - Date.now();
|
|
2306
|
+
if (remainingMs <= 0)
|
|
2307
|
+
return timedOut();
|
|
2308
|
+
const args = buildEverythingSearchArgs(this.instanceName, { ...request, offset: nativeOffset, maxResults: MAX_FILTER_CANDIDATES }, remainingMs);
|
|
2309
|
+
const response = await this.run(esPath, args, remainingMs + 5000);
|
|
2310
|
+
if (response.code !== 0)
|
|
2311
|
+
return this.failure("search-failed", esPath, args, response);
|
|
2312
|
+
if (Date.now() >= deadline)
|
|
2313
|
+
return timedOut();
|
|
2314
|
+
let entries;
|
|
2315
|
+
try {
|
|
2316
|
+
entries = parseEverythingJson(response.stdout);
|
|
2317
|
+
} catch (error) {
|
|
2318
|
+
return { ok: false, reason: "search-failed", message: error.message };
|
|
2319
|
+
}
|
|
2320
|
+
for (const entry of entries) {
|
|
2321
|
+
if (this.isExcludedPath(entry.path))
|
|
2322
|
+
continue;
|
|
2323
|
+
if (skipped < offset) {
|
|
2324
|
+
skipped += 1;
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2327
|
+
results.push(entry);
|
|
2328
|
+
if (results.length >= maxResults)
|
|
2329
|
+
return { ok: true, results };
|
|
2330
|
+
}
|
|
2331
|
+
if (entries.length < MAX_FILTER_CANDIDATES)
|
|
2332
|
+
return { ok: true, results };
|
|
2333
|
+
nativeOffset += entries.length;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2268
2336
|
index() {
|
|
2269
2337
|
return this.serialize(async () => {
|
|
2270
2338
|
const es = await this.ensureRunning(true);
|
|
@@ -2338,7 +2406,8 @@ class EverythingClient {
|
|
|
2338
2406
|
excludeFolders: [
|
|
2339
2407
|
...new Set([
|
|
2340
2408
|
join6(this.options.root, ".autorag"),
|
|
2341
|
-
...this.options.folders.map((folder) => join6(folder, ".autorag"))
|
|
2409
|
+
...this.options.folders.map((folder) => join6(folder, ".autorag")),
|
|
2410
|
+
...this.options.excludeFolders ?? []
|
|
2342
2411
|
])
|
|
2343
2412
|
]
|
|
2344
2413
|
}));
|
|
@@ -2432,8 +2501,9 @@ function launchDetached(command, args) {
|
|
|
2432
2501
|
child.on("error", () => {});
|
|
2433
2502
|
child.unref();
|
|
2434
2503
|
}
|
|
2435
|
-
var DEFAULT_TIMEOUT_MS2 = 30000, DEFAULT_STARTUP_TIMEOUT_MS = 20000, DEFAULT_INDEX_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS = 250, DEFAULT_MAX_RESULTS = 100, MAX_OUTPUT_BYTES, VERSION_PATTERN;
|
|
2504
|
+
var DEFAULT_TIMEOUT_MS2 = 30000, DEFAULT_STARTUP_TIMEOUT_MS = 20000, DEFAULT_INDEX_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS = 250, DEFAULT_MAX_RESULTS = 100, MAX_FILTER_CANDIDATES = 1000, MAX_OUTPUT_BYTES, VERSION_PATTERN;
|
|
2436
2505
|
var init_client = __esm(() => {
|
|
2506
|
+
init_path_exclusions();
|
|
2437
2507
|
init_bundle();
|
|
2438
2508
|
DEFAULT_INDEX_TIMEOUT_MS = 10 * 60000;
|
|
2439
2509
|
MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
@@ -2453,6 +2523,7 @@ function compare(a, b) {
|
|
|
2453
2523
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
2454
2524
|
}
|
|
2455
2525
|
async function walkFileSearch(folders, request, options = {}) {
|
|
2526
|
+
const isExcluded = createPathExclusionMatcher(options.excludeFolders ?? []);
|
|
2456
2527
|
const maxVisited = options.maxVisited ?? DEFAULT_MAX_VISITED;
|
|
2457
2528
|
const maxMatches = options.maxMatches ?? DEFAULT_MAX_MATCHES;
|
|
2458
2529
|
const deadline = Date.now() + (options.deadlineMs ?? DEFAULT_DEADLINE_MS);
|
|
@@ -2475,6 +2546,8 @@ async function walkFileSearch(folders, request, options = {}) {
|
|
|
2475
2546
|
outer:
|
|
2476
2547
|
while (pending.length > 0) {
|
|
2477
2548
|
const dir = pending.pop();
|
|
2549
|
+
if (isExcluded(dir))
|
|
2550
|
+
continue;
|
|
2478
2551
|
if (Date.now() >= deadline) {
|
|
2479
2552
|
truncated = true;
|
|
2480
2553
|
break;
|
|
@@ -2492,6 +2565,8 @@ async function walkFileSearch(folders, request, options = {}) {
|
|
|
2492
2565
|
}
|
|
2493
2566
|
visited += 1;
|
|
2494
2567
|
const path = join7(dir, dirent.name);
|
|
2568
|
+
if (isExcluded(path))
|
|
2569
|
+
continue;
|
|
2495
2570
|
if (dirent.isDirectory()) {
|
|
2496
2571
|
if (dirent.name === ".autorag")
|
|
2497
2572
|
continue;
|
|
@@ -2526,6 +2601,8 @@ async function walkFileSearch(folders, request, options = {}) {
|
|
|
2526
2601
|
}
|
|
2527
2602
|
}
|
|
2528
2603
|
for (const folder of folders) {
|
|
2604
|
+
if (isExcluded(folder))
|
|
2605
|
+
continue;
|
|
2529
2606
|
if (!underPath(folder) || !matches(folder))
|
|
2530
2607
|
continue;
|
|
2531
2608
|
if (request.kind === "files")
|
|
@@ -2552,6 +2629,7 @@ async function walkFileSearch(folders, request, options = {}) {
|
|
|
2552
2629
|
}
|
|
2553
2630
|
var DEFAULT_MAX_VISITED = 500000, DEFAULT_DEADLINE_MS = 20000, DEFAULT_MAX_MATCHES = 1e4, SORT_ORDERS;
|
|
2554
2631
|
var init_walk = __esm(() => {
|
|
2632
|
+
init_path_exclusions();
|
|
2555
2633
|
SORT_ORDERS = {
|
|
2556
2634
|
"name-ascending": (a, b) => compare(a.name, b.name),
|
|
2557
2635
|
"name-descending": (a, b) => compare(b.name, a.name),
|
|
@@ -2661,6 +2739,7 @@ class FSearchClient {
|
|
|
2661
2739
|
launch;
|
|
2662
2740
|
isAlive;
|
|
2663
2741
|
kill;
|
|
2742
|
+
isExcludedPath;
|
|
2664
2743
|
stateDir;
|
|
2665
2744
|
dbPath;
|
|
2666
2745
|
pidPath;
|
|
@@ -2673,6 +2752,7 @@ class FSearchClient {
|
|
|
2673
2752
|
this.launch = options.launch ?? launchDetached2;
|
|
2674
2753
|
this.isAlive = options.isProcessAlive ?? defaultIsProcessAlive;
|
|
2675
2754
|
this.kill = options.killProcess ?? ((pid) => process.kill(pid, "SIGTERM"));
|
|
2755
|
+
this.isExcludedPath = createPathExclusionMatcher(options.excludeFolders ?? []);
|
|
2676
2756
|
this.stateDir = join8(options.root, ".autorag", "fsearch");
|
|
2677
2757
|
this.dbPath = join8(this.stateDir, "fsearch.db");
|
|
2678
2758
|
this.pidPath = join8(this.stateDir, "watch.pid");
|
|
@@ -2690,6 +2770,7 @@ class FSearchClient {
|
|
|
2690
2770
|
if (unavailable !== undefined || !binary.ok) {
|
|
2691
2771
|
try {
|
|
2692
2772
|
const walked = await walkFileSearch(this.options.folders, request, {
|
|
2773
|
+
excludeFolders: this.options.excludeFolders,
|
|
2693
2774
|
maxVisited: this.options.walkMaxVisited,
|
|
2694
2775
|
deadlineMs: this.options.walkDeadlineMs
|
|
2695
2776
|
});
|
|
@@ -2704,7 +2785,9 @@ class FSearchClient {
|
|
|
2704
2785
|
return { ok: false, reason: "search-failed", message: error.message };
|
|
2705
2786
|
}
|
|
2706
2787
|
}
|
|
2707
|
-
const
|
|
2788
|
+
const hasExclusions = (this.options.excludeFolders?.length ?? 0) > 0;
|
|
2789
|
+
const nativeRequest = hasExclusions ? { ...request, offset: 0, maxResults: MAX_RESULTS_CAP } : request;
|
|
2790
|
+
const args = buildFSearchSearchArgs(this.dbPath, this.socketPath(), nativeRequest);
|
|
2708
2791
|
const result = await this.run(binary.path, args, this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS3);
|
|
2709
2792
|
if (result.code !== 0)
|
|
2710
2793
|
return this.failure("search-failed", binary.path, args, result);
|
|
@@ -2715,6 +2798,10 @@ class FSearchClient {
|
|
|
2715
2798
|
return { ok: false, reason: "search-failed", message: error.message };
|
|
2716
2799
|
}
|
|
2717
2800
|
let entries = parsed.entries;
|
|
2801
|
+
if (hasExclusions) {
|
|
2802
|
+
const isExcluded = this.isExcludedPath;
|
|
2803
|
+
entries = entries.filter((entry) => !isExcluded(entry.path));
|
|
2804
|
+
}
|
|
2718
2805
|
if (request.path !== undefined) {
|
|
2719
2806
|
const prefix = request.path.endsWith(sep3) ? request.path : `${request.path}${sep3}`;
|
|
2720
2807
|
entries = entries.filter((entry) => entry.path === request.path || entry.path.startsWith(prefix));
|
|
@@ -2724,7 +2811,16 @@ class FSearchClient {
|
|
|
2724
2811
|
const maxResults = Math.min(request.maxResults ?? DEFAULT_MAX_RESULTS2, MAX_RESULTS_CAP);
|
|
2725
2812
|
if (entries.length > maxResults)
|
|
2726
2813
|
entries = entries.slice(0, maxResults);
|
|
2727
|
-
return {
|
|
2814
|
+
return {
|
|
2815
|
+
ok: true,
|
|
2816
|
+
backend: "fsearch-cli",
|
|
2817
|
+
results: entries,
|
|
2818
|
+
total: parsed.total,
|
|
2819
|
+
...hasExclusions && parsed.total !== undefined && parsed.total > parsed.entries.length && entries.length < maxResults ? {
|
|
2820
|
+
truncated: true,
|
|
2821
|
+
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.`
|
|
2822
|
+
} : {}
|
|
2823
|
+
};
|
|
2728
2824
|
});
|
|
2729
2825
|
}
|
|
2730
2826
|
index() {
|
|
@@ -2918,6 +3014,7 @@ function defaultIsProcessAlive(pid) {
|
|
|
2918
3014
|
}
|
|
2919
3015
|
var DEFAULT_TIMEOUT_MS3 = 30000, DEFAULT_STARTUP_TIMEOUT_MS2 = 1e4, STOP_TIMEOUT_MS = 2000, DEFAULT_INDEX_TIMEOUT_MS2, DEFAULT_POLL_INTERVAL_MS2 = 250, DEFAULT_MAX_RESULTS2 = 100, MAX_RESULTS_CAP = 1000, MAX_OUTPUT_BYTES2, SORT_KEYS;
|
|
2920
3016
|
var init_client2 = __esm(() => {
|
|
3017
|
+
init_path_exclusions();
|
|
2921
3018
|
init_walk();
|
|
2922
3019
|
DEFAULT_INDEX_TIMEOUT_MS2 = 10 * 60000;
|
|
2923
3020
|
MAX_OUTPUT_BYTES2 = 8 * 1024 * 1024;
|
|
@@ -5045,7 +5142,20 @@ function configuredVectorDimension(workspacePath) {
|
|
|
5045
5142
|
const dimension = options.dimension;
|
|
5046
5143
|
return typeof dimension === "number" ? dimension : undefined;
|
|
5047
5144
|
}
|
|
5048
|
-
function
|
|
5145
|
+
function configuredLexicalLanguage(workspacePath) {
|
|
5146
|
+
let raw;
|
|
5147
|
+
try {
|
|
5148
|
+
raw = readFileSync8(minSyncConfigPath(workspacePath), "utf8");
|
|
5149
|
+
} catch {
|
|
5150
|
+
return;
|
|
5151
|
+
}
|
|
5152
|
+
const lexical = parse(raw).lexical;
|
|
5153
|
+
if (typeof lexical !== "object" || lexical === null || Array.isArray(lexical))
|
|
5154
|
+
return "simple";
|
|
5155
|
+
const language = lexical.language;
|
|
5156
|
+
return typeof language === "string" ? language : "simple";
|
|
5157
|
+
}
|
|
5158
|
+
function rewriteEmbedderConfig(workspacePath, embedder, managed = {}) {
|
|
5049
5159
|
const configPath = minSyncConfigPath(workspacePath);
|
|
5050
5160
|
let raw;
|
|
5051
5161
|
try {
|
|
@@ -5088,8 +5198,13 @@ function rewriteEmbedderConfig(workspacePath, embedder, chunkerConfig = {}) {
|
|
|
5088
5198
|
}
|
|
5089
5199
|
if (embedder.dimension !== undefined)
|
|
5090
5200
|
options.dimension = embedder.dimension;
|
|
5091
|
-
if (
|
|
5092
|
-
chunkerOptions.max_chunk_size =
|
|
5201
|
+
if (managed.maxChunkSize !== undefined)
|
|
5202
|
+
chunkerOptions.max_chunk_size = managed.maxChunkSize;
|
|
5203
|
+
if (managed.lexicalLanguage !== undefined) {
|
|
5204
|
+
const lexical = typeof parsed.lexical === "object" && parsed.lexical !== null && !Array.isArray(parsed.lexical) ? parsed.lexical : {};
|
|
5205
|
+
parsed.lexical = lexical;
|
|
5206
|
+
lexical.language = managed.lexicalLanguage;
|
|
5207
|
+
}
|
|
5093
5208
|
writeFileSync6(configPath, stringify(parsed));
|
|
5094
5209
|
return true;
|
|
5095
5210
|
}
|
|
@@ -5101,6 +5216,66 @@ var init_embedder_config = __esm(() => {
|
|
|
5101
5216
|
};
|
|
5102
5217
|
});
|
|
5103
5218
|
|
|
5219
|
+
// src/minsync/kiwi.ts
|
|
5220
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
5221
|
+
import { homedir as homedir2 } from "node:os";
|
|
5222
|
+
import { posix as posix3, win32 as win323 } from "node:path";
|
|
5223
|
+
function kiwiAvailable(environment = defaultEnvironment()) {
|
|
5224
|
+
const { env, exists } = environment;
|
|
5225
|
+
const library = env.KIWI_LIBRARY_PATH;
|
|
5226
|
+
const model = env.KIWI_MODEL_PATH;
|
|
5227
|
+
const libraryFound = library !== undefined && library !== "" ? exists(library) : libraryCandidates(environment).some(exists);
|
|
5228
|
+
const modelFound = model !== undefined && model !== "" ? exists(model) : modelCandidates(environment).some(exists);
|
|
5229
|
+
return libraryFound && modelFound;
|
|
5230
|
+
}
|
|
5231
|
+
function defaultEnvironment() {
|
|
5232
|
+
let home;
|
|
5233
|
+
try {
|
|
5234
|
+
home = homedir2();
|
|
5235
|
+
} catch {
|
|
5236
|
+
home = undefined;
|
|
5237
|
+
}
|
|
5238
|
+
return { env: process.env, platform: process.platform, home, exists: existsSync10 };
|
|
5239
|
+
}
|
|
5240
|
+
function userRoots({ env, platform, home }) {
|
|
5241
|
+
if (platform === "win32") {
|
|
5242
|
+
const roots = [];
|
|
5243
|
+
if (env.LOCALAPPDATA)
|
|
5244
|
+
roots.push(win323.join(env.LOCALAPPDATA, "kiwi"));
|
|
5245
|
+
const profile = env.USERPROFILE ?? home;
|
|
5246
|
+
if (profile)
|
|
5247
|
+
roots.push(win323.join(profile, "AppData", "Local", "kiwi"));
|
|
5248
|
+
roots.push("C:\\kiwi", "C:\\Program Files\\Kiwi");
|
|
5249
|
+
return roots;
|
|
5250
|
+
}
|
|
5251
|
+
return home ? [posix3.join(home, ".local", "kiwi")] : [];
|
|
5252
|
+
}
|
|
5253
|
+
function libraryCandidates(environment) {
|
|
5254
|
+
const { platform } = environment;
|
|
5255
|
+
if (platform === "win32")
|
|
5256
|
+
return userRoots(environment).map((root) => win323.join(root, "lib", "kiwi.dll"));
|
|
5257
|
+
const file = platform === "darwin" ? "libkiwi.dylib" : "libkiwi.so";
|
|
5258
|
+
const system = platform === "darwin" ? ["/usr/local/lib", "/opt/homebrew/lib"] : ["/usr/local/lib", "/usr/lib"];
|
|
5259
|
+
return [
|
|
5260
|
+
...userRoots(environment).map((root) => posix3.join(root, "lib", file)),
|
|
5261
|
+
...system.map((dir) => posix3.join(dir, file))
|
|
5262
|
+
];
|
|
5263
|
+
}
|
|
5264
|
+
function modelCandidates(environment) {
|
|
5265
|
+
const { platform } = environment;
|
|
5266
|
+
if (platform === "win32")
|
|
5267
|
+
return userRoots(environment).map((root) => win323.join(root, "models", "cong", "base"));
|
|
5268
|
+
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"];
|
|
5269
|
+
return [
|
|
5270
|
+
...userRoots(environment).map((root) => posix3.join(root, "models", "cong", "base")),
|
|
5271
|
+
...system.map((dir) => posix3.join(dir, "cong", "base"))
|
|
5272
|
+
];
|
|
5273
|
+
}
|
|
5274
|
+
var init_kiwi = () => {};
|
|
5275
|
+
|
|
5276
|
+
// src/minsync/lexical-language.ts
|
|
5277
|
+
var MINSYNC_LEXICAL_LANGUAGE = "multilingual";
|
|
5278
|
+
|
|
5104
5279
|
// src/minsync/local-embedder.ts
|
|
5105
5280
|
import { spawn as spawn7 } from "node:child_process";
|
|
5106
5281
|
import { request } from "node:http";
|
|
@@ -5289,7 +5464,7 @@ var init_process = __esm(() => {
|
|
|
5289
5464
|
});
|
|
5290
5465
|
|
|
5291
5466
|
// src/minsync/client.ts
|
|
5292
|
-
import { existsSync as
|
|
5467
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
|
|
5293
5468
|
import { join as join14 } from "node:path";
|
|
5294
5469
|
|
|
5295
5470
|
class MinSyncClient {
|
|
@@ -5384,7 +5559,7 @@ class MinSyncClient {
|
|
|
5384
5559
|
return previous === undefined || JSON.stringify(previous) !== JSON.stringify(identity);
|
|
5385
5560
|
}
|
|
5386
5561
|
async sync(force = false, signal) {
|
|
5387
|
-
if (!
|
|
5562
|
+
if (!existsSync11(this.binaryPath))
|
|
5388
5563
|
throw minSyncBinaryMissingError();
|
|
5389
5564
|
let effective = { config: {} };
|
|
5390
5565
|
try {
|
|
@@ -5422,14 +5597,16 @@ class MinSyncClient {
|
|
|
5422
5597
|
}
|
|
5423
5598
|
}
|
|
5424
5599
|
const spawnOptions = signal === undefined ? {} : { signal };
|
|
5425
|
-
const
|
|
5600
|
+
const lexical = this.resolveLexicalLanguage();
|
|
5601
|
+
const initialized = existsSync11(minSyncConfigPath(this.workspacePath));
|
|
5426
5602
|
if (!initialized) {
|
|
5427
5603
|
const initArgs = ["init", "--format", "json"];
|
|
5428
5604
|
if (this.embedder?.id) {
|
|
5429
5605
|
initArgs.push("--embedder", this.embedder.id);
|
|
5430
5606
|
}
|
|
5607
|
+
initArgs.push("--language", lexical.language);
|
|
5431
5608
|
const init = await this.spawn(initArgs, spawnOptions);
|
|
5432
|
-
if (!init.ok || !
|
|
5609
|
+
if (!init.ok || !existsSync11(minSyncConfigPath(this.workspacePath))) {
|
|
5433
5610
|
return {
|
|
5434
5611
|
ok: false,
|
|
5435
5612
|
synced: 0,
|
|
@@ -5441,20 +5618,29 @@ class MinSyncClient {
|
|
|
5441
5618
|
const chunkSizeChanged = this.maxChunkSize !== undefined && configuredMaxChunkSize(this.workspacePath) !== this.maxChunkSize;
|
|
5442
5619
|
const dimensionChanged = embedder.dimension !== undefined && configuredVectorDimension(this.workspacePath) !== embedder.dimension;
|
|
5443
5620
|
const configPath = minSyncConfigPath(this.workspacePath);
|
|
5444
|
-
const
|
|
5445
|
-
const
|
|
5446
|
-
|
|
5621
|
+
const originalConfig = readConfigSnapshot(configPath);
|
|
5622
|
+
const configRewritten = rewriteEmbedderConfig(this.workspacePath, embedder, {
|
|
5623
|
+
maxChunkSize: this.maxChunkSize,
|
|
5624
|
+
lexicalLanguage: lexical.language
|
|
5625
|
+
});
|
|
5447
5626
|
const restoreConfig = () => {
|
|
5448
5627
|
if (configRewritten && originalConfig !== undefined)
|
|
5449
5628
|
writeFileSync7(configPath, originalConfig);
|
|
5450
5629
|
};
|
|
5451
5630
|
try {
|
|
5452
|
-
|
|
5631
|
+
const result = await this.checkAndSync(force || chunkSizeChanged || dimensionChanged, signal, effective, restoreConfig);
|
|
5632
|
+
return result.ok && lexical.fallback !== undefined ? { ...result, lexicalFallback: lexical.fallback } : result;
|
|
5453
5633
|
} catch (error) {
|
|
5454
5634
|
restoreConfig();
|
|
5455
5635
|
throw error;
|
|
5456
5636
|
}
|
|
5457
5637
|
}
|
|
5638
|
+
resolveLexicalLanguage() {
|
|
5639
|
+
const requested = MINSYNC_LEXICAL_LANGUAGE;
|
|
5640
|
+
if (kiwiAvailable() || configuredLexicalLanguage(this.workspacePath) === requested)
|
|
5641
|
+
return { language: requested };
|
|
5642
|
+
return { language: "simple", fallback: { requested, used: "simple" } };
|
|
5643
|
+
}
|
|
5458
5644
|
lockHeldResult(stderr) {
|
|
5459
5645
|
return {
|
|
5460
5646
|
ok: false,
|
|
@@ -5507,7 +5693,7 @@ class MinSyncClient {
|
|
|
5507
5693
|
}
|
|
5508
5694
|
const identityChanged = effective.identity !== undefined && this.identityMismatch(effective.identity);
|
|
5509
5695
|
const fullReindex = forceFull || identityChanged;
|
|
5510
|
-
const syncArgs =
|
|
5696
|
+
const syncArgs = existsSync11(cursorPath) && !fullReindex ? ["sync", "--format", "json"] : ["sync", "--full", "--format", "json"];
|
|
5511
5697
|
const result = await this.spawn(syncArgs, spawnOptions);
|
|
5512
5698
|
if (!result.ok && result.code === MINSYNC_LOCK_HELD_EXIT_CODE) {
|
|
5513
5699
|
restoreConfig();
|
|
@@ -5528,7 +5714,7 @@ class MinSyncClient {
|
|
|
5528
5714
|
}
|
|
5529
5715
|
};
|
|
5530
5716
|
}
|
|
5531
|
-
if (!
|
|
5717
|
+
if (!existsSync11(cursorPath)) {
|
|
5532
5718
|
restoreConfig();
|
|
5533
5719
|
return { ok: false, synced: 0, workspacePath: this.workspacePath, reason: "not-ready: missing cursor" };
|
|
5534
5720
|
}
|
|
@@ -5549,7 +5735,7 @@ class MinSyncClient {
|
|
|
5549
5735
|
};
|
|
5550
5736
|
}
|
|
5551
5737
|
async query(text, topK, mode = "vector", signal) {
|
|
5552
|
-
if (!
|
|
5738
|
+
if (!existsSync11(this.binaryPath))
|
|
5553
5739
|
throw minSyncBinaryMissingError();
|
|
5554
5740
|
const result = await this.spawn(["query", "--format", "json", "--mode", mode, "-k", String(topK), text], {
|
|
5555
5741
|
...signal === undefined ? {} : { signal }
|
|
@@ -5643,6 +5829,7 @@ var MINSYNC_OLLAMA_MIGRATION_MESSAGE = "This workspace uses the legacy 768-dimen
|
|
|
5643
5829
|
var init_client4 = __esm(() => {
|
|
5644
5830
|
init_embedder_config();
|
|
5645
5831
|
init_errors();
|
|
5832
|
+
init_kiwi();
|
|
5646
5833
|
init_local_embedder();
|
|
5647
5834
|
init_process();
|
|
5648
5835
|
API_KEY_ENV_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -5665,14 +5852,14 @@ var init_client4 = __esm(() => {
|
|
|
5665
5852
|
|
|
5666
5853
|
// src/minsync/installer.ts
|
|
5667
5854
|
import { createHash as createHash9 } from "node:crypto";
|
|
5668
|
-
import { copyFileSync, createWriteStream as createWriteStream2, existsSync as
|
|
5855
|
+
import { copyFileSync, createWriteStream as createWriteStream2, existsSync as existsSync12, mkdirSync as mkdirSync8, renameSync as renameSync4, rmSync as rmSync5 } from "node:fs";
|
|
5669
5856
|
import { chmod as chmod2, mkdtemp } from "node:fs/promises";
|
|
5670
5857
|
import { get } from "node:https";
|
|
5671
5858
|
import { basename as basename7, delimiter as delimiter2, dirname as dirname7, join as join15 } from "node:path";
|
|
5672
5859
|
import { pipeline } from "node:stream/promises";
|
|
5673
5860
|
async function ensureMinSyncBinary(options) {
|
|
5674
5861
|
const binaryPath = join15(options.root, ".autorag", "bin", executableName(options.platform ?? process.platform));
|
|
5675
|
-
if (
|
|
5862
|
+
if (existsSync12(binaryPath))
|
|
5676
5863
|
return { binaryPath, version: "cached" };
|
|
5677
5864
|
mkdirSync8(dirname7(binaryPath), { recursive: true });
|
|
5678
5865
|
const cargoInstaller = options.cargoInstaller ?? ((destination) => installMinSyncFromCargoIfAvailable(options, destination));
|
|
@@ -5720,7 +5907,7 @@ async function installMinSyncFromCargo(options, destination, cargo) {
|
|
|
5720
5907
|
throw new MinSyncReleaseError(result.stderr || `Could not install MinSync ${MINSYNC_VERSION} from crates.io`);
|
|
5721
5908
|
}
|
|
5722
5909
|
const installedBinary = join15(cargoRoot, "bin", executableName(options.platform ?? process.platform));
|
|
5723
|
-
if (!
|
|
5910
|
+
if (!existsSync12(installedBinary)) {
|
|
5724
5911
|
throw new MinSyncReleaseError(`Cargo did not produce the MinSync ${MINSYNC_VERSION} binary`);
|
|
5725
5912
|
}
|
|
5726
5913
|
copyFileSync(installedBinary, destination);
|
|
@@ -5852,7 +6039,7 @@ function lookupCargo(env, platform) {
|
|
|
5852
6039
|
if (dir.length === 0)
|
|
5853
6040
|
continue;
|
|
5854
6041
|
const candidate = join15(dir, execName);
|
|
5855
|
-
if (
|
|
6042
|
+
if (existsSync12(candidate))
|
|
5856
6043
|
return candidate;
|
|
5857
6044
|
}
|
|
5858
6045
|
return;
|
|
@@ -5883,7 +6070,7 @@ function isGitHubAsset(value) {
|
|
|
5883
6070
|
function isRecord5(value) {
|
|
5884
6071
|
return typeof value === "object" && value !== null;
|
|
5885
6072
|
}
|
|
5886
|
-
var LATEST_RELEASE_URL = "https://api.github.com/repos/NomaDamas/MinSync/releases/latest", SHA256_HEX_PATTERN, MINSYNC_VERSION = "0.4.
|
|
6073
|
+
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;
|
|
5887
6074
|
var init_installer2 = __esm(() => {
|
|
5888
6075
|
init_process();
|
|
5889
6076
|
SHA256_HEX_PATTERN = /^[a-f0-9]{64}$/;
|
|
@@ -5911,7 +6098,7 @@ var init_paths2 = __esm(() => {
|
|
|
5911
6098
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
5912
6099
|
import {
|
|
5913
6100
|
copyFileSync as copyFileSync2,
|
|
5914
|
-
existsSync as
|
|
6101
|
+
existsSync as existsSync13,
|
|
5915
6102
|
lstatSync as lstatSync2,
|
|
5916
6103
|
mkdirSync as mkdirSync9,
|
|
5917
6104
|
readdirSync as readdirSync2,
|
|
@@ -5926,8 +6113,8 @@ function syncMinSyncWorkspace(root, options = {}) {
|
|
|
5926
6113
|
const filesRoot = join17(workspacePath, "files");
|
|
5927
6114
|
const index = loadMirrorIndex(root);
|
|
5928
6115
|
const candidates = Object.values(index.entries).sort((a, b) => a.virtualPath.localeCompare(b.virtualPath));
|
|
5929
|
-
const excluded = candidates.filter((entry) => normalizeVirtualPath(entry.virtualPath) !== entry.virtualPath || !
|
|
5930
|
-
const entries = candidates.filter((entry) => normalizeVirtualPath(entry.virtualPath) === entry.virtualPath &&
|
|
6116
|
+
const excluded = candidates.filter((entry) => normalizeVirtualPath(entry.virtualPath) !== entry.virtualPath || !existsSync13(entry.outputPath)).map((entry) => entry.virtualPath);
|
|
6117
|
+
const entries = candidates.filter((entry) => normalizeVirtualPath(entry.virtualPath) === entry.virtualPath && existsSync13(entry.outputPath)).map((entry) => {
|
|
5931
6118
|
const minSyncPath = minSyncDocumentPath(workspacePath, entry.virtualPath);
|
|
5932
6119
|
return {
|
|
5933
6120
|
virtualPath: entry.virtualPath,
|
|
@@ -6064,7 +6251,7 @@ var init_workspace = __esm(() => {
|
|
|
6064
6251
|
});
|
|
6065
6252
|
|
|
6066
6253
|
// src/minsync/method.ts
|
|
6067
|
-
import { accessSync, constants, existsSync as
|
|
6254
|
+
import { accessSync, constants, existsSync as existsSync14, realpathSync as realpathSync4 } from "node:fs";
|
|
6068
6255
|
import { basename as basename9, delimiter as delimiter3, join as join18, normalize as normalize2 } from "node:path";
|
|
6069
6256
|
function lookupInPath(env) {
|
|
6070
6257
|
const pathEnv = env.PATH;
|
|
@@ -6160,19 +6347,19 @@ class MinSyncVectorMethod {
|
|
|
6160
6347
|
}
|
|
6161
6348
|
isBinaryMissing() {
|
|
6162
6349
|
if (this.binaryPath !== undefined)
|
|
6163
|
-
return !
|
|
6350
|
+
return !existsSync14(this.binaryPath);
|
|
6164
6351
|
if (lookupInPath(process.env) !== undefined)
|
|
6165
6352
|
return false;
|
|
6166
6353
|
const cachedBinary = join18(this.root, ".autorag", "bin", executableName(process.platform));
|
|
6167
|
-
if (
|
|
6354
|
+
if (existsSync14(cachedBinary))
|
|
6168
6355
|
return false;
|
|
6169
6356
|
return true;
|
|
6170
6357
|
}
|
|
6171
6358
|
isExplicitBinaryMissing() {
|
|
6172
|
-
return this.binaryPath !== undefined && !
|
|
6359
|
+
return this.binaryPath !== undefined && !existsSync14(this.binaryPath);
|
|
6173
6360
|
}
|
|
6174
6361
|
isReady() {
|
|
6175
|
-
return
|
|
6362
|
+
return existsSync14(join18(this.workspacePath, ".minsync", "cursor.json"));
|
|
6176
6363
|
}
|
|
6177
6364
|
async retrieve(query, options) {
|
|
6178
6365
|
if ((workspaceSyncInFlight.get(this.workspacePath) ?? 0) > 0) {
|
|
@@ -6225,7 +6412,7 @@ class MinSyncVectorMethod {
|
|
|
6225
6412
|
return results;
|
|
6226
6413
|
}
|
|
6227
6414
|
async resolveBinary(options = {}) {
|
|
6228
|
-
if (this.binaryPath &&
|
|
6415
|
+
if (this.binaryPath && existsSync14(this.binaryPath))
|
|
6229
6416
|
return this.binaryPath;
|
|
6230
6417
|
if (this.binaryPath !== undefined)
|
|
6231
6418
|
throw minSyncBinaryMissingError();
|
|
@@ -6233,7 +6420,7 @@ class MinSyncVectorMethod {
|
|
|
6233
6420
|
if (pathBinary)
|
|
6234
6421
|
return pathBinary;
|
|
6235
6422
|
const cachedBinary = join18(this.root, ".autorag", "bin", executableName(process.platform));
|
|
6236
|
-
if (
|
|
6423
|
+
if (existsSync14(cachedBinary))
|
|
6237
6424
|
return cachedBinary;
|
|
6238
6425
|
if (this.autoInstall && options.install !== false) {
|
|
6239
6426
|
try {
|
|
@@ -6936,7 +7123,7 @@ var init_defaults = __esm(() => {
|
|
|
6936
7123
|
});
|
|
6937
7124
|
|
|
6938
7125
|
// src/parser/isolated.ts
|
|
6939
|
-
import { existsSync as
|
|
7126
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
6940
7127
|
import { dirname as dirname10, join as join21 } from "node:path";
|
|
6941
7128
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
6942
7129
|
import { Worker } from "node:worker_threads";
|
|
@@ -7075,7 +7262,7 @@ function defaultWorkerUrl() {
|
|
|
7075
7262
|
join21(here, "parser", "parse-worker.js"),
|
|
7076
7263
|
join21(here, "..", "parser", "parse-worker.js")
|
|
7077
7264
|
]) {
|
|
7078
|
-
if (
|
|
7265
|
+
if (existsSync15(candidate))
|
|
7079
7266
|
return pathToFileURL(candidate);
|
|
7080
7267
|
}
|
|
7081
7268
|
throw new Error(`The AutoRAG parse worker entry was not found near ${here}.`);
|
|
@@ -7087,7 +7274,7 @@ var init_isolated = __esm(() => {
|
|
|
7087
7274
|
});
|
|
7088
7275
|
|
|
7089
7276
|
// src/mirror/sync.ts
|
|
7090
|
-
import { existsSync as
|
|
7277
|
+
import { existsSync as existsSync16, rmSync as rmSync7 } from "node:fs";
|
|
7091
7278
|
import { opendir, readFile as readFile3, stat as stat5 } from "node:fs/promises";
|
|
7092
7279
|
import { extname as extname3, resolve as resolve11, sep as sep6 } from "node:path";
|
|
7093
7280
|
import { setTimeout as sleep3 } from "node:timers/promises";
|
|
@@ -7279,7 +7466,7 @@ async function syncWithRegistry(options, registry, isolated) {
|
|
|
7279
7466
|
}
|
|
7280
7467
|
const previousEntry = previous.entries[entry.virtualPath];
|
|
7281
7468
|
const outputPath = parsedOutputPath(options.root, entry.virtualPath);
|
|
7282
|
-
const unchanged = !options.force && previousEntry?.sourceMtimeNs === entry.mtimeNs && previousEntry.sourceSizeBytes === entry.sizeBytes && previousEntry.parserName === parser.name &&
|
|
7469
|
+
const unchanged = !options.force && previousEntry?.sourceMtimeNs === entry.mtimeNs && previousEntry.sourceSizeBytes === entry.sizeBytes && previousEntry.parserName === parser.name && existsSync16(outputPath);
|
|
7283
7470
|
if (!unchanged) {
|
|
7284
7471
|
let bytes;
|
|
7285
7472
|
try {
|
|
@@ -7660,7 +7847,7 @@ function removePrevious(root, index, virtualPath) {
|
|
|
7660
7847
|
return 1;
|
|
7661
7848
|
}
|
|
7662
7849
|
function removeFile(path) {
|
|
7663
|
-
if (
|
|
7850
|
+
if (existsSync16(path))
|
|
7664
7851
|
rmSync7(path, { force: true });
|
|
7665
7852
|
}
|
|
7666
7853
|
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;
|
|
@@ -9186,12 +9373,43 @@ function digitsEndAt(text, from) {
|
|
|
9186
9373
|
end += 1;
|
|
9187
9374
|
return end;
|
|
9188
9375
|
}
|
|
9189
|
-
function continuesAsProse(answer, from) {
|
|
9190
|
-
|
|
9376
|
+
function continuesAsProse(answer, openIndex, from) {
|
|
9377
|
+
let cursor = from;
|
|
9378
|
+
let leadingEmphasis = 0;
|
|
9379
|
+
while (openIndex - 1 - leadingEmphasis >= 0 && (answer[openIndex - 1 - leadingEmphasis] === "*" || answer[openIndex - 1 - leadingEmphasis] === "_")) {
|
|
9380
|
+
leadingEmphasis += 1;
|
|
9381
|
+
}
|
|
9382
|
+
if (leadingEmphasis > 0) {
|
|
9383
|
+
let trailingEmphasis = 0;
|
|
9384
|
+
while (cursor + trailingEmphasis < answer.length && trailingEmphasis < leadingEmphasis && answer[cursor + trailingEmphasis] === answer[openIndex - 1 - trailingEmphasis]) {
|
|
9385
|
+
trailingEmphasis += 1;
|
|
9386
|
+
}
|
|
9387
|
+
if (trailingEmphasis === leadingEmphasis)
|
|
9388
|
+
cursor += trailingEmphasis;
|
|
9389
|
+
}
|
|
9390
|
+
const tokenStart = openIndex - leadingEmphasis;
|
|
9391
|
+
const linePrefix = answer.slice(answer.lastIndexOf(`
|
|
9392
|
+
`, tokenStart - 1) + 1, tokenStart);
|
|
9393
|
+
const atHeadingStart = /^[ \t>*-]*$/u.test(linePrefix);
|
|
9394
|
+
if (atHeadingStart && answer[cursor] === ":")
|
|
9395
|
+
cursor += 1;
|
|
9396
|
+
if (answer[cursor] !== " " && answer[cursor] !== " ")
|
|
9191
9397
|
return false;
|
|
9192
|
-
|
|
9398
|
+
cursor += 1;
|
|
9399
|
+
while (cursor < answer.length && (answer[cursor] === "*" || answer[cursor] === "_" || answer[cursor] === '"' || answer[cursor] === "'" || answer[cursor] === "“" || answer[cursor] === "‘")) {
|
|
9400
|
+
cursor += 1;
|
|
9401
|
+
}
|
|
9402
|
+
const next = answer.codePointAt(cursor);
|
|
9193
9403
|
return next !== undefined && /[\p{L}\p{N}]/u.test(String.fromCodePoint(next));
|
|
9194
9404
|
}
|
|
9405
|
+
function isLegalPinpoint(answer, openIndex, afterCloseIndex) {
|
|
9406
|
+
if (PINPOINT_RANGE_AHEAD.test(answer.slice(afterCloseIndex, afterCloseIndex + 16)))
|
|
9407
|
+
return true;
|
|
9408
|
+
if (PINPOINT_RANGE_BEHIND.test(answer.slice(Math.max(0, openIndex - 16), openIndex)))
|
|
9409
|
+
return true;
|
|
9410
|
+
const prefixWindow = answer.slice(Math.max(0, openIndex - 120), openIndex);
|
|
9411
|
+
return PARA_PINPOINT_PREFIX.test(prefixWindow) || CLAUSE_START_AT_PREFIX.test(prefixWindow) || CASE_CITATION_AT_PREFIX.test(prefixWindow);
|
|
9412
|
+
}
|
|
9195
9413
|
function evidenceMarkerAt(answer, index) {
|
|
9196
9414
|
const ids = [];
|
|
9197
9415
|
let cursor = index + 1;
|
|
@@ -9257,7 +9475,7 @@ function answerMarkers(answer, isFile = () => false) {
|
|
|
9257
9475
|
}
|
|
9258
9476
|
if (char === "[") {
|
|
9259
9477
|
const digitsEnd = digitsEndAt(answer, index + 1);
|
|
9260
|
-
if (digitsEnd > index + 1 && answer[digitsEnd] === "]" && answer[digitsEnd + 1] !== "(" && !continuesAsProse(answer, digitsEnd + 1)) {
|
|
9478
|
+
if (digitsEnd > index + 1 && answer[digitsEnd] === "]" && answer[digitsEnd + 1] !== "(" && !continuesAsProse(answer, index, digitsEnd + 1) && !isLegalPinpoint(answer, index, digitsEnd + 1)) {
|
|
9261
9479
|
markers.push({
|
|
9262
9480
|
kind: "number",
|
|
9263
9481
|
start: leadingStart(index),
|
|
@@ -9329,7 +9547,14 @@ function assertResultsMappingOneToOne(label, results, mapping) {
|
|
|
9329
9547
|
].filter((note) => note !== undefined);
|
|
9330
9548
|
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.");
|
|
9331
9549
|
}
|
|
9332
|
-
var MAX_FILE_MARKER_CLOSERS = 8, FILE_MARKER_PREFIX = "[file:";
|
|
9550
|
+
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;
|
|
9551
|
+
var init_citations = __esm(() => {
|
|
9552
|
+
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;
|
|
9553
|
+
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;
|
|
9554
|
+
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;
|
|
9555
|
+
PINPOINT_RANGE_AHEAD = /^[ \t\u00a0]*[-–—][ \t\u00a0]*\[\d+\]/u;
|
|
9556
|
+
PINPOINT_RANGE_BEHIND = /\[\d+\][ \t\u00a0]*[-–—][ \t\u00a0]*$/u;
|
|
9557
|
+
});
|
|
9333
9558
|
|
|
9334
9559
|
// src/agent/evidence-ledger.ts
|
|
9335
9560
|
import { createHash as createHash10 } from "node:crypto";
|
|
@@ -9613,6 +9838,7 @@ function deriveResultsFromAnswer(text, ledger, options) {
|
|
|
9613
9838
|
}
|
|
9614
9839
|
var MAX_EXCERPT_CHARS = 400, MAX_CLAIM_CHARS = 280, CLAIM_LOOKBACK_CHARS = 600, SENTENCE_END, LEADING_LIST_MARK, EVIDENCE_MARKER_TEXT, LEADING_BLANK;
|
|
9615
9840
|
var init_answer_citations = __esm(() => {
|
|
9841
|
+
init_citations();
|
|
9616
9842
|
init_evidence_ledger();
|
|
9617
9843
|
SENTENCE_END = /[.!?。!?]/u;
|
|
9618
9844
|
LEADING_LIST_MARK = /^[\s\-*•>]+/u;
|
|
@@ -9621,7 +9847,7 @@ var init_answer_citations = __esm(() => {
|
|
|
9621
9847
|
});
|
|
9622
9848
|
|
|
9623
9849
|
// src/agent/answer-guidelines.ts
|
|
9624
|
-
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: ``, followed by its citation, e.g. ` [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.";
|
|
9850
|
+
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: ``, followed by its citation, e.g. ` [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.";
|
|
9625
9851
|
|
|
9626
9852
|
// src/agent/datasource-skill.ts
|
|
9627
9853
|
import { Type as Type3 } from "typebox";
|
|
@@ -11346,6 +11572,20 @@ function interactiveKeyOf(data) {
|
|
|
11346
11572
|
return "followUp";
|
|
11347
11573
|
return;
|
|
11348
11574
|
}
|
|
11575
|
+
function stripTelegramEnvelope(text) {
|
|
11576
|
+
return text.replace(/^\s*\[telegram\]\s?/u, "").replace(/\n*\[time\][^\n]*/gu, "").trim();
|
|
11577
|
+
}
|
|
11578
|
+
function userInputText(event) {
|
|
11579
|
+
if (event.source === "interactive")
|
|
11580
|
+
return event.text.trim();
|
|
11581
|
+
if (event.source === "extension" && event.text.trimStart().startsWith(TELEGRAM_INPUT_PREFIX)) {
|
|
11582
|
+
return stripTelegramEnvelope(event.text);
|
|
11583
|
+
}
|
|
11584
|
+
if (event.source === "extension" && event.text.trimStart().startsWith(WHATSAPP_INPUT_PREFIX)) {
|
|
11585
|
+
return event.text.trim();
|
|
11586
|
+
}
|
|
11587
|
+
return;
|
|
11588
|
+
}
|
|
11349
11589
|
function createAutoRAGInteractiveExtension(getSystemPrompt, contextTransform, options, updateNotice) {
|
|
11350
11590
|
return (pi) => {
|
|
11351
11591
|
pi.on("before_agent_start", () => ({ systemPrompt: getSystemPrompt() }));
|
|
@@ -11376,8 +11616,8 @@ function createAutoRAGInteractiveExtension(getSystemPrompt, contextTransform, op
|
|
|
11376
11616
|
});
|
|
11377
11617
|
}
|
|
11378
11618
|
pi.on("input", (event) => {
|
|
11379
|
-
const text = event
|
|
11380
|
-
if (
|
|
11619
|
+
const text = userInputText(event);
|
|
11620
|
+
if (text === undefined || text.length === 0)
|
|
11381
11621
|
return;
|
|
11382
11622
|
if (text.startsWith("/") && isSearchRunning?.() !== true)
|
|
11383
11623
|
return;
|
|
@@ -11474,7 +11714,9 @@ async function createAutoRAGPiInteractiveRuntime(options) {
|
|
|
11474
11714
|
agentDir: runtimeAgentDir,
|
|
11475
11715
|
modelRuntime,
|
|
11476
11716
|
settingsManager,
|
|
11717
|
+
...options.extensionFlags === undefined ? {} : { extensionFlagValues: new Map(Object.entries(options.extensionFlags)) },
|
|
11477
11718
|
resourceLoaderOptions: {
|
|
11719
|
+
...options.extensionPaths === undefined ? {} : { additionalExtensionPaths: [...options.extensionPaths] },
|
|
11478
11720
|
extensionFactories: [
|
|
11479
11721
|
createAutoRAGInteractiveExtension(options.getSystemPrompt, options.contextTransform, { onInput: options.onInput, isSearchRunning: options.isSearchRunning, onKey: options.onKey }, options.updateNotice),
|
|
11480
11722
|
...options.extensionFactories ?? []
|
|
@@ -11508,7 +11750,7 @@ async function createAutoRAGPiInteractiveRuntime(options) {
|
|
|
11508
11750
|
});
|
|
11509
11751
|
return { runtime, dispose: () => runtime.dispose() };
|
|
11510
11752
|
}
|
|
11511
|
-
var PI_BUILTIN_TOOL_NAMES, EMBEDDED_AUTH_MARKER = "<autorag-embedded-model>";
|
|
11753
|
+
var PI_BUILTIN_TOOL_NAMES, EMBEDDED_AUTH_MARKER = "<autorag-embedded-model>", TELEGRAM_INPUT_PREFIX = "[telegram]", WHATSAPP_INPUT_PREFIX = "Message from ";
|
|
11512
11754
|
var init_pi_session = __esm(() => {
|
|
11513
11755
|
init_provider();
|
|
11514
11756
|
init_context_token_guard();
|
|
@@ -11764,11 +12006,11 @@ var init_jikji_find_tool = __esm(() => {
|
|
|
11764
12006
|
|
|
11765
12007
|
// src/agent/local-model.ts
|
|
11766
12008
|
import { readFileSync as readFileSync12 } from "node:fs";
|
|
11767
|
-
import { homedir as
|
|
12009
|
+
import { homedir as homedir3 } from "node:os";
|
|
11768
12010
|
import { join as join24 } from "node:path";
|
|
11769
12011
|
import { parse as parse2 } from "smol-toml";
|
|
11770
12012
|
function loadLocalAutoRAGModel(options = {}) {
|
|
11771
|
-
const configPath = options.configPath ?? join24(
|
|
12013
|
+
const configPath = options.configPath ?? join24(homedir3(), ".codex", "config.toml");
|
|
11772
12014
|
const config = parse2(readFileSync12(configPath, "utf8"));
|
|
11773
12015
|
const provider = config.model_provider;
|
|
11774
12016
|
if (typeof provider !== "string" || provider.length === 0) {
|
|
@@ -11811,7 +12053,7 @@ var init_local_model = () => {};
|
|
|
11811
12053
|
|
|
11812
12054
|
// src/p2p/approval-store.ts
|
|
11813
12055
|
import {
|
|
11814
|
-
existsSync as
|
|
12056
|
+
existsSync as existsSync17,
|
|
11815
12057
|
mkdirSync as mkdirSync11,
|
|
11816
12058
|
readdirSync as readdirSync3,
|
|
11817
12059
|
readFileSync as readFileSync13,
|
|
@@ -11876,7 +12118,7 @@ function savePendingPeerRequest(workspacePath, request) {
|
|
|
11876
12118
|
}
|
|
11877
12119
|
function loadPendingPeerRequest(workspacePath, id) {
|
|
11878
12120
|
const path = requestPath(workspacePath, id);
|
|
11879
|
-
if (!
|
|
12121
|
+
if (!existsSync17(path))
|
|
11880
12122
|
return;
|
|
11881
12123
|
try {
|
|
11882
12124
|
return parsePending(JSON.parse(readFileSync13(path, "utf8")));
|
|
@@ -11886,7 +12128,7 @@ function loadPendingPeerRequest(workspacePath, id) {
|
|
|
11886
12128
|
}
|
|
11887
12129
|
function listPendingPeerRequests(workspacePath) {
|
|
11888
12130
|
const dir = requestsDir(workspacePath);
|
|
11889
|
-
if (!
|
|
12131
|
+
if (!existsSync17(dir))
|
|
11890
12132
|
return [];
|
|
11891
12133
|
const requests = [];
|
|
11892
12134
|
for (const name of readdirSync3(dir)) {
|
|
@@ -11908,7 +12150,7 @@ function writePeerRequestDecision(workspacePath, id, decision) {
|
|
|
11908
12150
|
}
|
|
11909
12151
|
function loadPeerRequestDecision(workspacePath, id) {
|
|
11910
12152
|
const path = decisionPath(workspacePath, id);
|
|
11911
|
-
if (!
|
|
12153
|
+
if (!existsSync17(path))
|
|
11912
12154
|
return;
|
|
11913
12155
|
try {
|
|
11914
12156
|
return parseDecision(JSON.parse(readFileSync13(path, "utf8")));
|
|
@@ -12242,7 +12484,7 @@ var init_egress_gate = __esm(() => {
|
|
|
12242
12484
|
});
|
|
12243
12485
|
|
|
12244
12486
|
// src/p2p/policy.ts
|
|
12245
|
-
import { existsSync as
|
|
12487
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "node:fs";
|
|
12246
12488
|
import { basename as basename10, dirname as dirname13, join as join26, resolve as resolve12 } from "node:path";
|
|
12247
12489
|
import { parse as parseToml } from "smol-toml";
|
|
12248
12490
|
function isRecord11(value) {
|
|
@@ -12322,7 +12564,7 @@ function normalizePolicyEntries(value, workspacePath, context) {
|
|
|
12322
12564
|
return entries;
|
|
12323
12565
|
}
|
|
12324
12566
|
function readJsonFile(path) {
|
|
12325
|
-
if (!
|
|
12567
|
+
if (!existsSync18(path))
|
|
12326
12568
|
return;
|
|
12327
12569
|
let raw;
|
|
12328
12570
|
try {
|
|
@@ -12337,7 +12579,7 @@ function readJsonFile(path) {
|
|
|
12337
12579
|
}
|
|
12338
12580
|
}
|
|
12339
12581
|
function readTomlFile(path) {
|
|
12340
|
-
if (!
|
|
12582
|
+
if (!existsSync18(path))
|
|
12341
12583
|
return;
|
|
12342
12584
|
let raw;
|
|
12343
12585
|
try {
|
|
@@ -12478,7 +12720,7 @@ function globToRegExp(pattern) {
|
|
|
12478
12720
|
return new RegExp(`${expression}$`, "u");
|
|
12479
12721
|
}
|
|
12480
12722
|
function loadSeenSources(path, workspacePath) {
|
|
12481
|
-
if (!
|
|
12723
|
+
if (!existsSync18(path))
|
|
12482
12724
|
return { promoted: new Set, pending: new Set };
|
|
12483
12725
|
let parsed;
|
|
12484
12726
|
try {
|
|
@@ -12785,7 +13027,7 @@ var init_wire = __esm(() => {
|
|
|
12785
13027
|
|
|
12786
13028
|
// src/p2p/simplex-query-store.ts
|
|
12787
13029
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
12788
|
-
import { existsSync as
|
|
13030
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync13, readdirSync as readdirSync4, readFileSync as readFileSync15, renameSync as renameSync7, unlinkSync as unlinkSync4, writeFileSync as writeFileSync11 } from "node:fs";
|
|
12789
13031
|
import { dirname as dirname14, join as join27 } from "node:path";
|
|
12790
13032
|
import { Value as Value2 } from "typebox/value";
|
|
12791
13033
|
function statePath(workspacePath, directory, id) {
|
|
@@ -12821,7 +13063,7 @@ function parseState(value) {
|
|
|
12821
13063
|
return value;
|
|
12822
13064
|
}
|
|
12823
13065
|
function readState(path) {
|
|
12824
|
-
if (!
|
|
13066
|
+
if (!existsSync19(path))
|
|
12825
13067
|
return;
|
|
12826
13068
|
try {
|
|
12827
13069
|
return parseState(JSON.parse(readFileSync15(path, "utf8")));
|
|
@@ -12848,14 +13090,14 @@ function moveState(workspacePath, from, to, state) {
|
|
|
12848
13090
|
}
|
|
12849
13091
|
function recoverClaims(workspacePath) {
|
|
12850
13092
|
const directory = join27(workspacePath, QUERY_DIR, PENDING_DIR);
|
|
12851
|
-
if (!
|
|
13093
|
+
if (!existsSync19(directory))
|
|
12852
13094
|
return;
|
|
12853
13095
|
for (const name of readdirSync4(directory)) {
|
|
12854
13096
|
if (!name.endsWith(".json.claim"))
|
|
12855
13097
|
continue;
|
|
12856
13098
|
const claim = join27(directory, name);
|
|
12857
13099
|
const restored = join27(directory, name.slice(0, -".claim".length));
|
|
12858
|
-
if (!
|
|
13100
|
+
if (!existsSync19(restored))
|
|
12859
13101
|
renameSync7(claim, restored);
|
|
12860
13102
|
}
|
|
12861
13103
|
}
|
|
@@ -12879,7 +13121,7 @@ function loadSimplexQueryState(workspacePath, id) {
|
|
|
12879
13121
|
}
|
|
12880
13122
|
function listPendingSimplexQueryStates(workspacePath) {
|
|
12881
13123
|
const directory = join27(workspacePath, QUERY_DIR, PENDING_DIR);
|
|
12882
|
-
if (!
|
|
13124
|
+
if (!existsSync19(directory))
|
|
12883
13125
|
return [];
|
|
12884
13126
|
return readdirSync4(directory).filter((name) => name.endsWith(".json")).map((name) => readState(join27(directory, name))).filter((state) => state?.status === "pending").filter((state) => loadSimplexQueryState(workspacePath, state.id)?.status === "pending").sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
12885
13127
|
}
|
|
@@ -13022,7 +13264,7 @@ var init_simplex_query_store = __esm(() => {
|
|
|
13022
13264
|
});
|
|
13023
13265
|
|
|
13024
13266
|
// src/p2p/simplex-server.ts
|
|
13025
|
-
import { existsSync as
|
|
13267
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "node:fs";
|
|
13026
13268
|
import { join as join28 } from "node:path";
|
|
13027
13269
|
import { Type as Type10 } from "typebox";
|
|
13028
13270
|
import { Value as Value3 } from "typebox/value";
|
|
@@ -13051,7 +13293,7 @@ function rankSimplexPeerTargets(query, registry) {
|
|
|
13051
13293
|
}
|
|
13052
13294
|
function loadSimplexPeerRegistry(workspacePath) {
|
|
13053
13295
|
const path = join28(workspacePath, PEERS_DIR, PEERS_FILENAME);
|
|
13054
|
-
if (!
|
|
13296
|
+
if (!existsSync20(path))
|
|
13055
13297
|
return {};
|
|
13056
13298
|
try {
|
|
13057
13299
|
const parsed = JSON.parse(readFileSync16(path, "utf8"));
|
|
@@ -13957,6 +14199,7 @@ function recordStructuredResultsSession(sessionId, query, details, memory, compo
|
|
|
13957
14199
|
}
|
|
13958
14200
|
var init_search_documents = __esm(() => {
|
|
13959
14201
|
init_memory();
|
|
14202
|
+
init_citations();
|
|
13960
14203
|
});
|
|
13961
14204
|
|
|
13962
14205
|
// src/agent/search-all-tool.ts
|
|
@@ -14175,14 +14418,14 @@ var init_search_single_datasource_tool = __esm(() => {
|
|
|
14175
14418
|
});
|
|
14176
14419
|
|
|
14177
14420
|
// src/agent/self-config.ts
|
|
14178
|
-
import { existsSync as
|
|
14421
|
+
import { existsSync as existsSync21, readFileSync as readFileSync18, rmSync as rmSync9, writeFileSync as writeFileSync14 } from "node:fs";
|
|
14179
14422
|
import { dirname as dirname16, join as join29 } from "node:path";
|
|
14180
14423
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
14181
14424
|
function findPackagedSetupSkill() {
|
|
14182
14425
|
let dir = dirname16(fileURLToPath3(import.meta.url));
|
|
14183
14426
|
for (let depth = 0;depth < 6; depth += 1) {
|
|
14184
14427
|
const candidate = join29(dir, SETUP_SKILL_RELATIVE_PATH);
|
|
14185
|
-
if (
|
|
14428
|
+
if (existsSync21(candidate))
|
|
14186
14429
|
return candidate;
|
|
14187
14430
|
const parent = dirname16(dir);
|
|
14188
14431
|
if (parent === dir)
|
|
@@ -14205,7 +14448,7 @@ function loadSetupSkill(skillPath) {
|
|
|
14205
14448
|
return text.replace(/^---\r?\n[\s\S]*?\r?\n---(?:\r?\n)+/u, "");
|
|
14206
14449
|
}
|
|
14207
14450
|
function snapshotConfigFile(configPath) {
|
|
14208
|
-
return
|
|
14451
|
+
return existsSync21(configPath) ? readFileSync18(configPath, "utf8") : undefined;
|
|
14209
14452
|
}
|
|
14210
14453
|
async function rollbackIfBroken(options, before) {
|
|
14211
14454
|
if (options.validate === undefined)
|
|
@@ -14273,6 +14516,10 @@ function buildSystemPrompt(config) {
|
|
|
14273
14516
|
const modelId = config.modelId ?? "the configured model";
|
|
14274
14517
|
const toolLines = [
|
|
14275
14518
|
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)"),
|
|
14519
|
+
...config.remoteSession ? [] : [
|
|
14520
|
+
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"),
|
|
14521
|
+
toolLine(config, "edit", "surgically update an existing deliverable or config file when the user asks (local sessions only)")
|
|
14522
|
+
],
|
|
14276
14523
|
toolLine(config, "jikji_find", "local discovery through Jikji answer packs"),
|
|
14277
14524
|
toolLine(config, "everything_search", "instant Windows file/folder name search (voidtools Everything) across the configured search folders"),
|
|
14278
14525
|
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"),
|
|
@@ -14289,6 +14536,12 @@ function buildSystemPrompt(config) {
|
|
|
14289
14536
|
...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`),
|
|
14290
14537
|
...config.toolNames.filter((name) => ![
|
|
14291
14538
|
"bash",
|
|
14539
|
+
"read",
|
|
14540
|
+
"write",
|
|
14541
|
+
"edit",
|
|
14542
|
+
"grep",
|
|
14543
|
+
"find",
|
|
14544
|
+
"ls",
|
|
14292
14545
|
"jikji_find",
|
|
14293
14546
|
"everything_search",
|
|
14294
14547
|
"fsearch_search",
|
|
@@ -14421,8 +14674,9 @@ Your answer is your final reply, written as plain text, never as a tool call. Ci
|
|
|
14421
14674
|
## Answer Guidelines
|
|
14422
14675
|
|
|
14423
14676
|
- **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.
|
|
14424
|
-
- **Bullet-point core answer**:
|
|
14425
|
-
- **Direct answer only**:
|
|
14677
|
+
- **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.
|
|
14678
|
+
- **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.
|
|
14679
|
+
- ${ANSWER_DELIVERABLE_RULE} ${ANSWER_DELIVERABLE_DELTA_RULE}
|
|
14426
14680
|
- ${ANSWER_CITATION_RULE}
|
|
14427
14681
|
- ${ANSWER_IMAGE_EMBED_RULE} ${ANSWER_IMAGE_DELTA_RULE}
|
|
14428
14682
|
- **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.
|
|
@@ -16717,11 +16971,6 @@ var init_types3 = __esm(() => {
|
|
|
16717
16971
|
label: "xAI",
|
|
16718
16972
|
description: "xAI Responses web_search via the agent's xAI model credentials (XAI_API_KEY)"
|
|
16719
16973
|
},
|
|
16720
|
-
{
|
|
16721
|
-
value: "perplexity",
|
|
16722
|
-
label: "Perplexity",
|
|
16723
|
-
description: "Anonymous Perplexity ask endpoint; no key or signup required"
|
|
16724
|
-
},
|
|
16725
16974
|
{
|
|
16726
16975
|
value: "parallel",
|
|
16727
16976
|
label: "Parallel",
|
|
@@ -16732,16 +16981,21 @@ var init_types3 = __esm(() => {
|
|
|
16732
16981
|
label: "SearXNG",
|
|
16733
16982
|
description: "Advanced: requires a self-hosted SEARXNG_ENDPOINT; never part of the default experience"
|
|
16734
16983
|
},
|
|
16735
|
-
{
|
|
16736
|
-
value: "startpage",
|
|
16737
|
-
label: "Startpage",
|
|
16738
|
-
description: "Credential-free scrape of Startpage (Google-backed) results; may be bot-challenged"
|
|
16739
|
-
},
|
|
16740
16984
|
{
|
|
16741
16985
|
value: "duckduckgo",
|
|
16742
16986
|
label: "DuckDuckGo",
|
|
16743
16987
|
description: "Credential-free best-effort fallback; may be bot-challenged on datacenter/shared-egress IPs"
|
|
16744
16988
|
},
|
|
16989
|
+
{
|
|
16990
|
+
value: "perplexity",
|
|
16991
|
+
label: "Perplexity",
|
|
16992
|
+
description: "Anonymous Perplexity ask endpoint; no key or signup required"
|
|
16993
|
+
},
|
|
16994
|
+
{
|
|
16995
|
+
value: "startpage",
|
|
16996
|
+
label: "Startpage",
|
|
16997
|
+
description: "Credential-free scrape of Startpage (Google-backed) results; may be bot-challenged"
|
|
16998
|
+
},
|
|
16745
16999
|
{
|
|
16746
17000
|
value: "ecosia",
|
|
16747
17001
|
label: "Ecosia",
|
|
@@ -18089,12 +18343,11 @@ function eventAnswer(event) {
|
|
|
18089
18343
|
}
|
|
18090
18344
|
return "";
|
|
18091
18345
|
}
|
|
18092
|
-
var PERPLEXITY_ASK_URL = "https://www.perplexity.ai/rest/sse/perplexity_ask", API_VERSION2 = "2.18",
|
|
18346
|
+
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;
|
|
18093
18347
|
var init_perplexity = __esm(() => {
|
|
18094
18348
|
init_query();
|
|
18095
18349
|
init_types3();
|
|
18096
18350
|
init_utils();
|
|
18097
|
-
DEFLECTION_PATTERN = /sign up|log in|create an account|verify you are human|are you a robot|가입|로그인|회원 ?가입/i;
|
|
18098
18351
|
MAX_ERROR_BYTES4 = 8 * 1024;
|
|
18099
18352
|
PERPLEXITY_QUERY_SYNTAX = {
|
|
18100
18353
|
phrases: true,
|
|
@@ -18208,8 +18461,8 @@ var init_perplexity = __esm(() => {
|
|
|
18208
18461
|
break;
|
|
18209
18462
|
}
|
|
18210
18463
|
const sources = [...sourcesByUrl.values()];
|
|
18211
|
-
if (sources.length === 0
|
|
18212
|
-
throw new SearchProviderError("perplexity",
|
|
18464
|
+
if (sources.length === 0) {
|
|
18465
|
+
throw new SearchProviderError("perplexity", `Perplexity anonymous ask returned no web sources (sign-up wall or conversational deflection): ${answer.slice(0, 120)}`, 403);
|
|
18213
18466
|
}
|
|
18214
18467
|
const numResults = params.numSearchResults ?? params.limit;
|
|
18215
18468
|
return {
|
|
@@ -18627,7 +18880,7 @@ var init_browser_headers = __esm(() => {
|
|
|
18627
18880
|
});
|
|
18628
18881
|
|
|
18629
18882
|
// src/web/search/browser-loader.ts
|
|
18630
|
-
import { existsSync as
|
|
18883
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
18631
18884
|
async function closeBounded(close) {
|
|
18632
18885
|
let timer;
|
|
18633
18886
|
try {
|
|
@@ -18706,7 +18959,7 @@ function ensureWebSearchBrowserLoader(options = {}) {
|
|
|
18706
18959
|
return true;
|
|
18707
18960
|
const exists = options.exists ?? ((path) => {
|
|
18708
18961
|
try {
|
|
18709
|
-
return
|
|
18962
|
+
return existsSync22(path);
|
|
18710
18963
|
} catch {
|
|
18711
18964
|
return false;
|
|
18712
18965
|
}
|
|
@@ -18811,6 +19064,8 @@ function normalizeText(value) {
|
|
|
18811
19064
|
function isChallengeResponse(page) {
|
|
18812
19065
|
if (/\/(?:errors|captcha)\//.test(page.url) || page.url.includes("/sp/captcha"))
|
|
18813
19066
|
return true;
|
|
19067
|
+
if (ANUBIS_CHALLENGE_PATTERN.test(page.html))
|
|
19068
|
+
return true;
|
|
18814
19069
|
return page.html.includes("component---src-pages-captcha") || page.html.includes("/sp/captcha");
|
|
18815
19070
|
}
|
|
18816
19071
|
function parseSearchFormInputs(html) {
|
|
@@ -18915,7 +19170,7 @@ async function callStartpageHtml(params) {
|
|
|
18915
19170
|
});
|
|
18916
19171
|
}
|
|
18917
19172
|
if (isChallengeResponse(page)) {
|
|
18918
|
-
throw new SearchProviderError("startpage", "Startpage blocked the request with a CAPTCHA
|
|
19173
|
+
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);
|
|
18919
19174
|
}
|
|
18920
19175
|
if (page.status < 200 || page.status >= 300) {
|
|
18921
19176
|
const classified = classifyProviderHttpError("startpage", page.status, page.html);
|
|
@@ -18941,7 +19196,7 @@ async function searchStartpage(params) {
|
|
|
18941
19196
|
}
|
|
18942
19197
|
return { provider: "startpage", sources };
|
|
18943
19198
|
}
|
|
18944
|
-
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;
|
|
19199
|
+
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;
|
|
18945
19200
|
var init_startpage = __esm(() => {
|
|
18946
19201
|
init_entities();
|
|
18947
19202
|
init_browser_page();
|
|
@@ -18954,6 +19209,7 @@ var init_startpage = __esm(() => {
|
|
|
18954
19209
|
month: "m",
|
|
18955
19210
|
year: "y"
|
|
18956
19211
|
};
|
|
19212
|
+
ANUBIS_CHALLENGE_PATTERN = /<script\b[^>]*\bid\s*=\s*["']anubis_(?:challenge|version)["']/i;
|
|
18957
19213
|
StartpageProvider = class StartpageProvider extends SearchProvider {
|
|
18958
19214
|
id = "startpage";
|
|
18959
19215
|
label = "Startpage";
|
|
@@ -20171,7 +20427,7 @@ var init_web_search_tool = __esm(() => {
|
|
|
20171
20427
|
|
|
20172
20428
|
// src/agent/agent.ts
|
|
20173
20429
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
20174
|
-
import { existsSync as
|
|
20430
|
+
import { existsSync as existsSync23, watch as fsWatch, mkdirSync as mkdirSync16, realpathSync as realpathSync5, statSync as statSync2, writeFileSync as writeFileSync15 } from "node:fs";
|
|
20175
20431
|
import { basename as basename11, dirname as dirname17, join as join31, resolve as resolve13 } from "node:path";
|
|
20176
20432
|
import { clampThinkingLevel } from "@earendil-works/pi-ai/compat";
|
|
20177
20433
|
import { getAgentDir as getAgentDir3 } from "@earendil-works/pi-coding-agent";
|
|
@@ -20302,6 +20558,9 @@ class AutoRAGAgent {
|
|
|
20302
20558
|
piSessionDir;
|
|
20303
20559
|
persistPiSessions;
|
|
20304
20560
|
updateNotice;
|
|
20561
|
+
piExtensionPaths;
|
|
20562
|
+
piExtensionFlags;
|
|
20563
|
+
telegram;
|
|
20305
20564
|
boundPiRuntime;
|
|
20306
20565
|
memoryEmbedder;
|
|
20307
20566
|
memoryVectorPath;
|
|
@@ -20327,6 +20586,9 @@ class AutoRAGAgent {
|
|
|
20327
20586
|
this.piSessionDir = options.piSessionDir;
|
|
20328
20587
|
this.persistPiSessions = options.persistPiSessions ?? true;
|
|
20329
20588
|
this.updateNotice = options.updateNotice;
|
|
20589
|
+
this.piExtensionPaths = options.piExtensionPaths;
|
|
20590
|
+
this.piExtensionFlags = options.piExtensionFlags;
|
|
20591
|
+
this.telegram = options.telegram === true;
|
|
20330
20592
|
this.datasourceSkills = options.datasourceSkills ?? [];
|
|
20331
20593
|
this.datasourceRetry = options.datasourceRetry ?? {};
|
|
20332
20594
|
this.datasourceVirtualScopePrefixes = this.datasourceSkills.map((skill) => normalizeVirtualPath2(`/${skill.describe().name}`));
|
|
@@ -20379,7 +20641,8 @@ class AutoRAGAgent {
|
|
|
20379
20641
|
const everythingClient = new EverythingClient({
|
|
20380
20642
|
...options.everything ?? {},
|
|
20381
20643
|
root: this.workspaceProjectRoot,
|
|
20382
|
-
folders: this.searchPaths
|
|
20644
|
+
folders: this.searchPaths,
|
|
20645
|
+
excludeFolders: this.excludePaths
|
|
20383
20646
|
});
|
|
20384
20647
|
if (everythingClient.isSupported())
|
|
20385
20648
|
this.everythingClient = everythingClient;
|
|
@@ -20485,8 +20748,9 @@ class AutoRAGAgent {
|
|
|
20485
20748
|
return true;
|
|
20486
20749
|
});
|
|
20487
20750
|
this.tools = tools;
|
|
20751
|
+
const builtinToolNames = this.remoteSession ? PI_BUILTIN_TOOL_NAMES.filter((name) => name !== "write" && name !== "edit") : PI_BUILTIN_TOOL_NAMES;
|
|
20488
20752
|
const toolNames = [
|
|
20489
|
-
...
|
|
20753
|
+
...builtinToolNames,
|
|
20490
20754
|
...tools.map((tool) => tool.name),
|
|
20491
20755
|
...this.jevExtension !== undefined ? [JEV_TOOL_NAME] : []
|
|
20492
20756
|
];
|
|
@@ -20502,7 +20766,7 @@ class AutoRAGAgent {
|
|
|
20502
20766
|
this.innerAgent = {
|
|
20503
20767
|
state: {
|
|
20504
20768
|
systemPrompt,
|
|
20505
|
-
tools: [...
|
|
20769
|
+
tools: [...builtinToolNames.map((name) => ({ name })), ...tools],
|
|
20506
20770
|
messages: []
|
|
20507
20771
|
},
|
|
20508
20772
|
transformContext: (messages) => this.withMemoryContext(messages)
|
|
@@ -20796,6 +21060,7 @@ ${context.text}
|
|
|
20796
21060
|
}
|
|
20797
21061
|
async createPiInteractiveRuntime() {
|
|
20798
21062
|
const resolved = this.configuredModel === undefined ? undefined : await this.resolveSessionModel();
|
|
21063
|
+
const telegramBridge = this.telegram ? (await import("@llblab/pi-telegram")).default : undefined;
|
|
20799
21064
|
const runtime = await createAutoRAGPiInteractiveRuntime({
|
|
20800
21065
|
cwd: this.workspaceProjectRoot,
|
|
20801
21066
|
agentDir: this.piAgentDir,
|
|
@@ -20816,10 +21081,14 @@ ${context.text}
|
|
|
20816
21081
|
onKey: (key) => this.handleInteractiveKey(key),
|
|
20817
21082
|
extensionFactories: [
|
|
20818
21083
|
createEvidencePersistence(this.evidenceLedger),
|
|
20819
|
-
...this.jevExtension !== undefined ? [this.jevExtension] : []
|
|
21084
|
+
...this.jevExtension !== undefined ? [this.jevExtension] : [],
|
|
21085
|
+
...telegramBridge !== undefined ? [telegramBridge] : []
|
|
20820
21086
|
],
|
|
20821
21087
|
...this.jevExtension !== undefined ? { extensionToolNames: [JEV_TOOL_NAME] } : {},
|
|
20822
|
-
|
|
21088
|
+
remoteSession: this.remoteSession,
|
|
21089
|
+
...this.updateNotice === undefined ? {} : { updateNotice: this.updateNotice },
|
|
21090
|
+
...this.piExtensionPaths === undefined ? {} : { extensionPaths: this.piExtensionPaths },
|
|
21091
|
+
...this.piExtensionFlags === undefined ? {} : { extensionFlags: this.piExtensionFlags }
|
|
20823
21092
|
});
|
|
20824
21093
|
this.boundPiRuntime = runtime.runtime;
|
|
20825
21094
|
const dispose = runtime.dispose;
|
|
@@ -21739,21 +22008,22 @@ ${result.content.replace(/\s+/gu, " ")}`).join(`
|
|
|
21739
22008
|
const scope = options.scope ? ` Restrict search to virtual path scope ${options.scope}.` : "";
|
|
21740
22009
|
return `Answer this original query immediately: ${query}${limit}${scope}
|
|
21741
22010
|
|
|
21742
|
-
|
|
22011
|
+
Baseline retrieval evidence (already gathered for you):
|
|
21743
22012
|
${baseline}
|
|
21744
22013
|
|
|
21745
|
-
|
|
22014
|
+
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.
|
|
21746
22015
|
|
|
21747
|
-
|
|
21748
|
-
|
|
21749
|
-
|
|
21750
|
-
|
|
21751
|
-
|
|
21752
|
-
|
|
21753
|
-
|
|
21754
|
-
|
|
22016
|
+
Formatting and content rules for the answer:
|
|
22017
|
+
- 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.
|
|
22018
|
+
- 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.
|
|
22019
|
+
- ${ANSWER_DELIVERABLE_RULE}
|
|
22020
|
+
- ${ANSWER_CITATION_RULE}
|
|
22021
|
+
- ${ANSWER_IMAGE_EMBED_RULE}
|
|
22022
|
+
- Do not report per-source negative findings (e.g. "no information found in Slack" or "checked Drive but found nothing").
|
|
22023
|
+
- When evidence conflicts, treat the freshest (most recent) information as the correct source of truth.
|
|
22024
|
+
- 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.
|
|
21755
22025
|
|
|
21756
|
-
|
|
22026
|
+
Write the answer as your reply, then stop.`;
|
|
21757
22027
|
}
|
|
21758
22028
|
isConversationTurn() {
|
|
21759
22029
|
if (this.remoteSession || this.boundPiRuntime === undefined)
|
|
@@ -21763,7 +22033,7 @@ ${baseline}
|
|
|
21763
22033
|
buildDirectAnswerPrompt(query) {
|
|
21764
22034
|
return `Answer this query directly from your own general knowledge: ${query}
|
|
21765
22035
|
|
|
21766
|
-
|
|
22036
|
+
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.`;
|
|
21767
22037
|
}
|
|
21768
22038
|
discoveryHint(sentence) {
|
|
21769
22039
|
const names = this.tools.map((tool) => tool.name).filter((name) => name === JIKJI_FIND_TOOL_NAME || name === EVERYTHING_SEARCH_TOOL_NAME);
|
|
@@ -21781,52 +22051,55 @@ ${baseline}
|
|
|
21781
22051
|
const scope = options.scope ? ` Restrict search to virtual path scope ${options.scope}.` : "";
|
|
21782
22052
|
const firstAnswer = formatFirstAnswerContext(fastAnswer, fastAnswerDelivered);
|
|
21783
22053
|
const answerRules = fastAnswerDelivered ? `Formatting and content rules for your final answer (DELTA ONLY):
|
|
21784
|
-
|
|
21785
|
-
|
|
21786
|
-
|
|
21787
|
-
|
|
21788
|
-
|
|
21789
|
-
|
|
21790
|
-
|
|
21791
|
-
|
|
21792
|
-
|
|
21793
|
-
` + `-
|
|
21794
|
-
|
|
21795
|
-
|
|
21796
|
-
|
|
21797
|
-
|
|
22054
|
+
- 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.
|
|
22055
|
+
- NEVER restate or re-list first-answer facts that remain correct, and never repeat its bullet list.
|
|
22056
|
+
- Mark each item clearly as a correction or as a new finding.
|
|
22057
|
+
- 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.
|
|
22058
|
+
- ${ANSWER_DELIVERABLE_RULE} ${ANSWER_DELIVERABLE_DELTA_RULE}
|
|
22059
|
+
- ${ANSWER_CITATION_RULE} A correction or new finding that relies on evidence the first answer cited must cite that evidence's id again.
|
|
22060
|
+
- ${ANSWER_IMAGE_EMBED_RULE} ${ANSWER_IMAGE_DELTA_RULE}
|
|
22061
|
+
- Do not report per-source negative findings (e.g. "no information found in Slack").
|
|
22062
|
+
- 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):
|
|
22063
|
+
` + `- 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.
|
|
22064
|
+
- 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.
|
|
22065
|
+
- ${ANSWER_DELIVERABLE_RULE}${fastAnswer !== undefined ? ` ${ANSWER_DELIVERABLE_DELTA_RULE}` : ""}
|
|
22066
|
+
- ${ANSWER_CITATION_RULE}
|
|
22067
|
+
- ${ANSWER_IMAGE_EMBED_RULE}
|
|
22068
|
+
- Do not report per-source negative findings (e.g. "no information found in Slack").
|
|
22069
|
+
- When evidence conflicts, treat the freshest (most recent) information as the correct source of truth.`;
|
|
21798
22070
|
return `Original query: ${query}${limit}${scope}
|
|
21799
22071
|
|
|
21800
|
-
|
|
22072
|
+
${firstAnswer}
|
|
21801
22073
|
|
|
21802
22074
|
` + 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()}
|
|
21803
22075
|
|
|
21804
|
-
|
|
22076
|
+
${answerRules}
|
|
21805
22077
|
|
|
21806
|
-
|
|
22078
|
+
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.`;
|
|
21807
22079
|
}
|
|
21808
22080
|
buildSearchPrompt(query, options, initialRetrievalContext) {
|
|
21809
22081
|
const limit = typeof options.topK === "number" ? ` Cite at most ${options.topK} sources.` : "";
|
|
21810
22082
|
const scope = options.scope ? ` Restrict search to virtual path scope ${options.scope}.` : "";
|
|
21811
22083
|
return `Find and curate information for this original query: ${query}${limit}${scope}
|
|
21812
22084
|
|
|
21813
|
-
|
|
22085
|
+
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.
|
|
21814
22086
|
|
|
21815
|
-
|
|
22087
|
+
Baseline retrieval context:
|
|
21816
22088
|
${initialRetrievalContext ?? "Pending; continue only with a brief progress statement."}
|
|
21817
22089
|
|
|
21818
|
-
|
|
22090
|
+
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.
|
|
21819
22091
|
|
|
21820
|
-
|
|
21821
|
-
|
|
21822
|
-
|
|
21823
|
-
|
|
21824
|
-
|
|
21825
|
-
|
|
21826
|
-
|
|
21827
|
-
|
|
22092
|
+
Formatting and content rules for the answer:
|
|
22093
|
+
- 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.
|
|
22094
|
+
- 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.
|
|
22095
|
+
- ${ANSWER_DELIVERABLE_RULE}
|
|
22096
|
+
- ${ANSWER_CITATION_RULE}
|
|
22097
|
+
- ${ANSWER_IMAGE_EMBED_RULE}
|
|
22098
|
+
- Do not report per-source negative findings (e.g. "no information found in Slack").
|
|
22099
|
+
- When evidence conflicts, treat the freshest (most recent) information as the correct source of truth.
|
|
22100
|
+
- If information is incomplete or uncertain, acknowledge it briefly without lengthy explanations. If there are partial clues or leads, mention them concisely.
|
|
21828
22101
|
|
|
21829
|
-
|
|
22102
|
+
${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.`;
|
|
21830
22103
|
}
|
|
21831
22104
|
async refresh(force = false, opts) {
|
|
21832
22105
|
const signal = opts?.signal;
|
|
@@ -22062,7 +22335,7 @@ ${initialRetrievalContext ?? "Pending; continue only with a brief progress state
|
|
|
22062
22335
|
});
|
|
22063
22336
|
}
|
|
22064
22337
|
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;
|
|
22065
|
-
const parsedMirrorReady = this.refreshState.lastOutcome === "success" || persistedProgress?.state === "success" ||
|
|
22338
|
+
const parsedMirrorReady = this.refreshState.lastOutcome === "success" || persistedProgress?.state === "success" || existsSync23(refreshReadinessPath(this.workspaceProjectRoot));
|
|
22066
22339
|
const effectiveProgress = this.refreshState.inFlight || persistedRunning ? persistedProgress : undefined;
|
|
22067
22340
|
return {
|
|
22068
22341
|
state,
|
|
@@ -22522,7 +22795,7 @@ function buildSteerMessage(text) {
|
|
|
22522
22795
|
|
|
22523
22796
|
${text}
|
|
22524
22797
|
|
|
22525
|
-
|
|
22798
|
+
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.`;
|
|
22526
22799
|
}
|
|
22527
22800
|
function frameSteers(messages, steerTexts) {
|
|
22528
22801
|
if (steerTexts.size === 0)
|
|
@@ -22542,12 +22815,12 @@ function formatSteerContext(steers) {
|
|
|
22542
22815
|
return `While the search ran, the user corrected it:
|
|
22543
22816
|
${steers.map((steer) => `- ${steer}`).join(`
|
|
22544
22817
|
`)}
|
|
22545
|
-
|
|
22818
|
+
The first answer predates these corrections; follow them over it.
|
|
22546
22819
|
|
|
22547
22820
|
`;
|
|
22548
22821
|
}
|
|
22549
22822
|
function buildSteerContinuationPrompt(steers) {
|
|
22550
|
-
return `${formatSteerContext(steers)}Apply the latest correction: search or verify further if it needs that,
|
|
22823
|
+
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.`;
|
|
22551
22824
|
}
|
|
22552
22825
|
function formatFirstAnswerContext(fastAnswer, delivered) {
|
|
22553
22826
|
if (fastAnswer === undefined) {
|
|
@@ -22670,11 +22943,20 @@ function fsearchRefreshDiagnostics(fsearch) {
|
|
|
22670
22943
|
function minSyncRefreshDiagnostics(minsync) {
|
|
22671
22944
|
if (!minsync)
|
|
22672
22945
|
return [];
|
|
22946
|
+
const diagnostics = [];
|
|
22673
22947
|
if (minsync.diagnostic)
|
|
22674
|
-
|
|
22675
|
-
if (!minsync.ok && minsync.reason)
|
|
22676
|
-
|
|
22677
|
-
|
|
22948
|
+
diagnostics.push(toMinSyncDiagnostic(minsync.diagnostic, minsync.ok));
|
|
22949
|
+
else if (!minsync.ok && minsync.reason)
|
|
22950
|
+
diagnostics.push(toMinSyncReasonDiagnostic(minsync.reason));
|
|
22951
|
+
if (minsync.lexicalFallback) {
|
|
22952
|
+
diagnostics.push({
|
|
22953
|
+
code: "lexical-tokenizer-unavailable",
|
|
22954
|
+
severity: "warning",
|
|
22955
|
+
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.`,
|
|
22956
|
+
source: "minsync"
|
|
22957
|
+
});
|
|
22958
|
+
}
|
|
22959
|
+
return diagnostics;
|
|
22678
22960
|
}
|
|
22679
22961
|
function toMinSyncDiagnostic(diag, ok) {
|
|
22680
22962
|
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";
|
|
@@ -23010,7 +23292,7 @@ var init_connector = () => {};
|
|
|
23010
23292
|
|
|
23011
23293
|
// src/datasource/sidecar/config.ts
|
|
23012
23294
|
import { readFileSync as readFileSync19 } from "node:fs";
|
|
23013
|
-
import { homedir as
|
|
23295
|
+
import { homedir as homedir4 } from "node:os";
|
|
23014
23296
|
import { dirname as dirname18, isAbsolute as isAbsolute8, join as join32, resolve as resolve14, sep as sep7 } from "node:path";
|
|
23015
23297
|
import { parse as parseToml2 } from "smol-toml";
|
|
23016
23298
|
function resolveSidecarOptions(input) {
|
|
@@ -23050,9 +23332,9 @@ function archivePathFromCrawlerConfig(input) {
|
|
|
23050
23332
|
}
|
|
23051
23333
|
function expandHome(path) {
|
|
23052
23334
|
if (path === "~")
|
|
23053
|
-
return
|
|
23335
|
+
return homedir4();
|
|
23054
23336
|
const prefixed = path.startsWith("~/") || sep7 === "\\" && path.startsWith("~\\");
|
|
23055
|
-
return prefixed ? join32(
|
|
23337
|
+
return prefixed ? join32(homedir4(), path.slice(2)) : path;
|
|
23056
23338
|
}
|
|
23057
23339
|
var init_config2 = __esm(() => {
|
|
23058
23340
|
init_connector();
|
|
@@ -23081,7 +23363,7 @@ function chatCrawler(id) {
|
|
|
23081
23363
|
hierarchy: (metadata) => ["chats", str(metadata.chatName) ?? str(metadata.chatId) ?? "unknown"]
|
|
23082
23364
|
};
|
|
23083
23365
|
}
|
|
23084
|
-
var str = (value) => typeof value === "string" && value.length > 0 ? value : undefined, slack, CRAWLER_DESCRIPTORS;
|
|
23366
|
+
var str = (value) => typeof value === "string" && value.length > 0 ? value : undefined, slack, kakao, CRAWLER_DESCRIPTORS;
|
|
23085
23367
|
var init_descriptors = __esm(() => {
|
|
23086
23368
|
slack = {
|
|
23087
23369
|
id: "slack",
|
|
@@ -23112,16 +23394,38 @@ var init_descriptors = __esm(() => {
|
|
|
23112
23394
|
str(metadata.channelName) ?? str(metadata.channelId) ?? "unknown"
|
|
23113
23395
|
]
|
|
23114
23396
|
};
|
|
23397
|
+
kakao = {
|
|
23398
|
+
id: "kakao",
|
|
23399
|
+
keyExpression: "m.chunk_id",
|
|
23400
|
+
chunkId: (key) => key,
|
|
23401
|
+
fromClause: "chunks m",
|
|
23402
|
+
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))",
|
|
23403
|
+
textExpression: "coalesce(m.text, '')",
|
|
23404
|
+
titleExpression: "m.chat_name",
|
|
23405
|
+
epochSecondsExpression: "unixepoch(m.started_at)",
|
|
23406
|
+
fts: { table: "chunks_fts", joinOn: "chunks_fts.rowid = m.rowid" },
|
|
23407
|
+
metadata: [
|
|
23408
|
+
{ sql: "m.chat_id", key: "chatId" },
|
|
23409
|
+
{ sql: "m.chat_name", key: "chatName" },
|
|
23410
|
+
{ sql: "m.sender_nickname", key: "senderNickname" },
|
|
23411
|
+
{ sql: "m.started_at", key: "startedAt" },
|
|
23412
|
+
{ sql: "m.ended_at", key: "endedAt" }
|
|
23413
|
+
],
|
|
23414
|
+
channelIdExpression: "m.chat_id",
|
|
23415
|
+
channelNameExpression: "m.chat_name",
|
|
23416
|
+
hierarchy: (metadata) => ["chats", str(metadata.chatName) ?? str(metadata.chatId) ?? "unknown"]
|
|
23417
|
+
};
|
|
23115
23418
|
CRAWLER_DESCRIPTORS = {
|
|
23116
23419
|
slack,
|
|
23117
23420
|
telegram: chatCrawler("telegram"),
|
|
23118
|
-
whatsapp: chatCrawler("whatsapp")
|
|
23421
|
+
whatsapp: chatCrawler("whatsapp"),
|
|
23422
|
+
kakao
|
|
23119
23423
|
};
|
|
23120
23424
|
});
|
|
23121
23425
|
|
|
23122
23426
|
// src/datasource/skills/clawgallery/client.ts
|
|
23123
23427
|
import { spawn as spawn10 } from "node:child_process";
|
|
23124
|
-
import { existsSync as
|
|
23428
|
+
import { existsSync as existsSync24 } from "node:fs";
|
|
23125
23429
|
|
|
23126
23430
|
class ClawGalleryClient {
|
|
23127
23431
|
options;
|
|
@@ -23129,7 +23433,7 @@ class ClawGalleryClient {
|
|
|
23129
23433
|
this.options = options;
|
|
23130
23434
|
}
|
|
23131
23435
|
async bootstrap(signal) {
|
|
23132
|
-
if (this.options.path !== undefined && !
|
|
23436
|
+
if (this.options.path !== undefined && !existsSync24(this.options.path)) {
|
|
23133
23437
|
return {
|
|
23134
23438
|
ok: false,
|
|
23135
23439
|
reason: "nonzero-exit",
|
|
@@ -24047,7 +24351,7 @@ var init_rclone_connector = __esm(() => {
|
|
|
24047
24351
|
});
|
|
24048
24352
|
|
|
24049
24353
|
// src/datasource/chunk-store.ts
|
|
24050
|
-
import { existsSync as
|
|
24354
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync18, readFileSync as readFileSync21, writeFileSync as writeFileSync17 } from "node:fs";
|
|
24051
24355
|
import { dirname as dirname20, join as join34 } from "node:path";
|
|
24052
24356
|
function datasourceChunkStorePath(workspaceRoot, skillName, instanceId) {
|
|
24053
24357
|
return join34(workspaceRoot, ".autorag", "datasources", sanitizeIdSegment(skillName), sanitizeIdSegment(instanceId), "chunks.json");
|
|
@@ -24087,7 +24391,7 @@ class DatasourceChunkStore {
|
|
|
24087
24391
|
if (this.storePath === undefined)
|
|
24088
24392
|
return false;
|
|
24089
24393
|
try {
|
|
24090
|
-
if (!
|
|
24394
|
+
if (!existsSync25(this.storePath))
|
|
24091
24395
|
return false;
|
|
24092
24396
|
const parsed = JSON.parse(readFileSync21(this.storePath, "utf8"));
|
|
24093
24397
|
if (parsed.version !== STORE_VERSION || !Array.isArray(parsed.chunks))
|
|
@@ -25614,7 +25918,7 @@ var init_github = __esm(() => {
|
|
|
25614
25918
|
|
|
25615
25919
|
// src/datasource/skills/github-gist/connector.ts
|
|
25616
25920
|
import { execFile as execFile2 } from "node:child_process";
|
|
25617
|
-
import { existsSync as
|
|
25921
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync20, readFileSync as readFileSync23, writeFileSync as writeFileSync19 } from "node:fs";
|
|
25618
25922
|
import { dirname as dirname22 } from "node:path";
|
|
25619
25923
|
async function defaultGhTokenRunner() {
|
|
25620
25924
|
return new Promise((resolve) => {
|
|
@@ -25734,7 +26038,7 @@ class GitHubGistConnector {
|
|
|
25734
26038
|
}
|
|
25735
26039
|
loadState() {
|
|
25736
26040
|
const statePath = this.options.statePath;
|
|
25737
|
-
if (statePath === undefined || !
|
|
26041
|
+
if (statePath === undefined || !existsSync26(statePath))
|
|
25738
26042
|
return { version: STATE_VERSION, gists: {} };
|
|
25739
26043
|
try {
|
|
25740
26044
|
const parsed = JSON.parse(readFileSync23(statePath, "utf8"));
|
|
@@ -25797,7 +26101,7 @@ var init_connector3 = () => {};
|
|
|
25797
26101
|
|
|
25798
26102
|
// src/datasource/skills/github-gist/semantic.ts
|
|
25799
26103
|
import { createHash as createHash13 } from "node:crypto";
|
|
25800
|
-
import { existsSync as
|
|
26104
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync21, readFileSync as readFileSync24, writeFileSync as writeFileSync20 } from "node:fs";
|
|
25801
26105
|
import { dirname as dirname23 } from "node:path";
|
|
25802
26106
|
function contentHash(chunk) {
|
|
25803
26107
|
return createHash13("sha256").update(`${chunk.title ?? ""}
|
|
@@ -25888,7 +26192,7 @@ ${chunk.content}`));
|
|
|
25888
26192
|
if (this.loaded)
|
|
25889
26193
|
return;
|
|
25890
26194
|
this.loaded = true;
|
|
25891
|
-
if (this.statePath === undefined || !
|
|
26195
|
+
if (this.statePath === undefined || !existsSync27(this.statePath))
|
|
25892
26196
|
return;
|
|
25893
26197
|
try {
|
|
25894
26198
|
const parsed = JSON.parse(readFileSync24(this.statePath, "utf8"));
|
|
@@ -26751,7 +27055,7 @@ var init_types5 = __esm(() => {
|
|
|
26751
27055
|
|
|
26752
27056
|
// src/datasource/skills/lazykatok/client.ts
|
|
26753
27057
|
import { spawn as spawn14 } from "node:child_process";
|
|
26754
|
-
import { existsSync as
|
|
27058
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
26755
27059
|
import { delimiter as delimiter4, join as join36 } from "node:path";
|
|
26756
27060
|
|
|
26757
27061
|
class LazykatokClient {
|
|
@@ -26941,7 +27245,7 @@ function lookupInPath2(executable, env) {
|
|
|
26941
27245
|
if (dir.length === 0)
|
|
26942
27246
|
continue;
|
|
26943
27247
|
const candidate = join36(dir, executable);
|
|
26944
|
-
if (
|
|
27248
|
+
if (existsSync28(candidate))
|
|
26945
27249
|
return candidate;
|
|
26946
27250
|
}
|
|
26947
27251
|
return;
|
|
@@ -27263,1436 +27567,1528 @@ var init_methods3 = __esm(() => {
|
|
|
27263
27567
|
DEFAULT_KAKAO_TAGS = ["kakaotalk", "personal", "pii"];
|
|
27264
27568
|
});
|
|
27265
27569
|
|
|
27266
|
-
// src/datasource/
|
|
27267
|
-
|
|
27268
|
-
|
|
27269
|
-
|
|
27270
|
-
|
|
27271
|
-
|
|
27272
|
-
|
|
27273
|
-
|
|
27274
|
-
|
|
27275
|
-
|
|
27276
|
-
|
|
27277
|
-
|
|
27278
|
-
|
|
27279
|
-
|
|
27280
|
-
|
|
27281
|
-
|
|
27282
|
-
|
|
27283
|
-
|
|
27284
|
-
|
|
27285
|
-
|
|
27286
|
-
|
|
27287
|
-
|
|
27288
|
-
capabilities: ["chat", "external-cli", "polling", "bm25", "semantic"],
|
|
27289
|
-
tags: this.tags,
|
|
27290
|
-
status: "active",
|
|
27291
|
-
requiresExternalCli: true,
|
|
27292
|
-
datasourceId: KAKAO_DATASOURCE_ID2,
|
|
27293
|
-
instanceId: this.instanceId,
|
|
27294
|
-
instances: this.instances
|
|
27295
|
-
};
|
|
27570
|
+
// src/datasource/sidecar/fusion.ts
|
|
27571
|
+
function encodeVector(vector) {
|
|
27572
|
+
const floats = Float32Array.from(vector);
|
|
27573
|
+
return new Uint8Array(floats.buffer, floats.byteOffset, floats.byteLength);
|
|
27574
|
+
}
|
|
27575
|
+
function decodeVector(blob) {
|
|
27576
|
+
if (blob.byteLength % Float32Array.BYTES_PER_ELEMENT !== 0)
|
|
27577
|
+
return;
|
|
27578
|
+
return new Float32Array(new Uint8Array(blob).buffer);
|
|
27579
|
+
}
|
|
27580
|
+
function cosine3(a, b) {
|
|
27581
|
+
if (a.length === 0 || a.length !== b.length)
|
|
27582
|
+
return 0;
|
|
27583
|
+
let dot = 0;
|
|
27584
|
+
let normA = 0;
|
|
27585
|
+
let normB = 0;
|
|
27586
|
+
for (let index = 0;index < a.length; index += 1) {
|
|
27587
|
+
const x = a[index] ?? 0;
|
|
27588
|
+
const y = b[index] ?? 0;
|
|
27589
|
+
dot += x * y;
|
|
27590
|
+
normA += x * x;
|
|
27591
|
+
normB += y * y;
|
|
27296
27592
|
}
|
|
27297
|
-
|
|
27298
|
-
return
|
|
27299
|
-
|
|
27300
|
-
|
|
27301
|
-
|
|
27302
|
-
|
|
27593
|
+
if (normA === 0 || normB === 0)
|
|
27594
|
+
return 0;
|
|
27595
|
+
return dot / Math.sqrt(normA * normB);
|
|
27596
|
+
}
|
|
27597
|
+
function topKByCosine(query, rows, k) {
|
|
27598
|
+
if (k <= 0)
|
|
27599
|
+
return [];
|
|
27600
|
+
const scored = [];
|
|
27601
|
+
for (const row of rows) {
|
|
27602
|
+
const score = cosine3(query, row.vector);
|
|
27603
|
+
if (score > 0)
|
|
27604
|
+
scored.push({ key: row.key, score });
|
|
27303
27605
|
}
|
|
27304
|
-
|
|
27305
|
-
|
|
27306
|
-
|
|
27307
|
-
|
|
27308
|
-
|
|
27309
|
-
|
|
27310
|
-
|
|
27311
|
-
|
|
27312
|
-
|
|
27313
|
-
|
|
27314
|
-
|
|
27315
|
-
|
|
27316
|
-
const chunkCount = "data" in indexResult && typeof indexResult.data === "object" && indexResult.data !== null && "chunkCount" in indexResult.data && typeof indexResult.data.chunkCount === "number" ? indexResult.data.chunkCount : 0;
|
|
27317
|
-
return {
|
|
27318
|
-
ok: true,
|
|
27319
|
-
instanceId: this.instanceId,
|
|
27320
|
-
skill: KAKAO_DATASOURCE_ID2,
|
|
27321
|
-
chunkCount,
|
|
27322
|
-
indexedAt: this.lastIndexedAt,
|
|
27323
|
-
diagnostics: []
|
|
27324
|
-
};
|
|
27325
|
-
} catch (error) {
|
|
27326
|
-
return this.fail("datasource-unavailable", {
|
|
27327
|
-
ok: false,
|
|
27328
|
-
reason: "spawn-error",
|
|
27329
|
-
stdout: "",
|
|
27330
|
-
stderr: describeRetrievalError(error),
|
|
27331
|
-
code: null
|
|
27332
|
-
});
|
|
27606
|
+
scored.sort((a, b) => b.score - a.score || a.key.localeCompare(b.key));
|
|
27607
|
+
return scored.slice(0, k);
|
|
27608
|
+
}
|
|
27609
|
+
function reciprocalRankFusion(lists) {
|
|
27610
|
+
const scores = new Map;
|
|
27611
|
+
for (const list of lists) {
|
|
27612
|
+
const seen = new Set;
|
|
27613
|
+
for (const [index, key] of list.entries()) {
|
|
27614
|
+
if (seen.has(key))
|
|
27615
|
+
continue;
|
|
27616
|
+
seen.add(key);
|
|
27617
|
+
scores.set(key, (scores.get(key) ?? 0) + 1 / (RRF_K2 + index + 1));
|
|
27333
27618
|
}
|
|
27334
27619
|
}
|
|
27335
|
-
|
|
27336
|
-
return [
|
|
27337
|
-
new LazykatokBm25Method({ client: this.client, instanceId: this.instanceId, tags: this.tags }),
|
|
27338
|
-
new LazykatokSemanticMethod({ client: this.client, instanceId: this.instanceId, tags: this.tags })
|
|
27339
|
-
];
|
|
27340
|
-
}
|
|
27341
|
-
describeSources() {
|
|
27342
|
-
return this.instances.map((instanceId) => ({
|
|
27343
|
-
source: datasourceSourcePath(KAKAO_DATASOURCE_ID2, instanceId),
|
|
27344
|
-
datasourceId: KAKAO_DATASOURCE_ID2,
|
|
27345
|
-
skill: KAKAO_DATASOURCE_ID2,
|
|
27346
|
-
instanceId,
|
|
27347
|
-
contentType: "chat",
|
|
27348
|
-
metadata: {
|
|
27349
|
-
datasourceId: KAKAO_DATASOURCE_ID2,
|
|
27350
|
-
instanceId,
|
|
27351
|
-
tags: this.tags
|
|
27352
|
-
}
|
|
27353
|
-
}));
|
|
27354
|
-
}
|
|
27355
|
-
skillManifest() {
|
|
27356
|
-
const instanceSources = this.instances.map((instanceId) => `- \`${datasourceSourcePath(KAKAO_DATASOURCE_ID2, instanceId)}\``).join(`
|
|
27357
|
-
`);
|
|
27358
|
-
const cadence = this.pollingIntervalMs > 0 ? `roughly every ${Math.round(this.pollingIntervalMs / 60000)} minute(s) when auto-refresh runs` : "on manual refresh only";
|
|
27359
|
-
return {
|
|
27360
|
-
name: `datasource-${KAKAO_DATASOURCE_ID2}`,
|
|
27361
|
-
description: "Search indexed KakaoTalk chat history (messages, senders, room context). Use for questions about KakaoTalk conversations, decisions made in chats, or who said what.",
|
|
27362
|
-
content: [
|
|
27363
|
-
`# KakaoTalk datasource (${KAKAO_SKILL_TYPE})`,
|
|
27364
|
-
"",
|
|
27365
|
-
"This skill searches KakaoTalk chats that are indexed through the external `lazykatok` CLI. AutoRAG never reads KakaoTalk databases directly.",
|
|
27366
|
-
"",
|
|
27367
|
-
"## When to use",
|
|
27368
|
-
"Use this skill when the question is about KakaoTalk conversations, chat participants, or content shared inside chats.",
|
|
27369
|
-
"",
|
|
27370
|
-
"## Indexing",
|
|
27371
|
-
`Indexing is server-managed and refreshed ${cadence}. You do not trigger indexing; just search.`,
|
|
27372
|
-
"",
|
|
27373
|
-
"## How to search",
|
|
27374
|
-
`Call the dedicated \`${datasourceSearchToolName(KAKAO_DATASOURCE_ID2)}\` tool with a natural-language \`query\` and \`topK\`. This datasource does not support per-source scope narrowing. Configured datasource instances:`,
|
|
27375
|
-
instanceSources.length > 0 ? instanceSources : "- (no configured instances)",
|
|
27376
|
-
"",
|
|
27377
|
-
"Chat/channel filtering is owned by lazykatok.",
|
|
27378
|
-
"",
|
|
27379
|
-
"## Native CLI",
|
|
27380
|
-
"The external `lazykatok` CLI owns the archive, index, and credentials. When the dedicated tool cannot express what you need, call lazykatok directly through `bash`:",
|
|
27381
|
-
'- `lazykatok search bm25 "<query>" --json --limit 20` — lexical search (`semantic` mode also available)',
|
|
27382
|
-
"- `lazykatok chunk get <chunkId> --json` — fetch one chunk by id (chunk ids appear in result metadata)",
|
|
27383
|
-
"- `lazykatok chunk context <chunkId> --json` — surrounding messages of a chunk",
|
|
27384
|
-
"Never pass datasource virtual paths (`/kakao/...`) to bash; they are not OS paths.",
|
|
27385
|
-
"",
|
|
27386
|
-
"## Output rules",
|
|
27387
|
-
"Datasource source identifiers such as `/kakao/<instance>/chunks/<id>` are internal and opaque. Never put them, real file paths, account IDs, or phone numbers in the visible answer."
|
|
27388
|
-
].join(`
|
|
27389
|
-
`)
|
|
27390
|
-
};
|
|
27391
|
-
}
|
|
27392
|
-
fail(code, result) {
|
|
27393
|
-
const base = result.stderr.length > 0 ? `${result.reason}: ${result.stderr.trim().slice(0, 4000)}` : result.reason;
|
|
27394
|
-
const message = code === "datasource-index-failed" && result.reason === "timeout" ? `${base} — ${connectorSyncTimeoutHint()}` : base;
|
|
27395
|
-
const diagnostic = {
|
|
27396
|
-
code,
|
|
27397
|
-
severity: code === "datasource-unavailable" ? "warning" : "error",
|
|
27398
|
-
message,
|
|
27399
|
-
instanceId: this.instanceId,
|
|
27400
|
-
source: KAKAO_DATASOURCE_ID2
|
|
27401
|
-
};
|
|
27402
|
-
return {
|
|
27403
|
-
ok: false,
|
|
27404
|
-
instanceId: this.instanceId,
|
|
27405
|
-
skill: KAKAO_DATASOURCE_ID2,
|
|
27406
|
-
indexedAt: Date.now(),
|
|
27407
|
-
diagnostics: [diagnostic],
|
|
27408
|
-
error: result.reason,
|
|
27409
|
-
code,
|
|
27410
|
-
message
|
|
27411
|
-
};
|
|
27412
|
-
}
|
|
27620
|
+
return [...scores.entries()].map(([key, score]) => ({ key, score })).sort((a, b) => b.score - a.score);
|
|
27413
27621
|
}
|
|
27414
|
-
var
|
|
27415
|
-
var init_skill7 = __esm(() => {
|
|
27416
|
-
init_skip();
|
|
27417
|
-
init_scope2();
|
|
27418
|
-
init_methods3();
|
|
27419
|
-
DEFAULT_POLLING_INTERVAL_MS3 = 15 * 60 * 1000;
|
|
27420
|
-
DEFAULT_KAKAO_TAGS2 = ["kakaotalk", "personal", "pii"];
|
|
27421
|
-
});
|
|
27422
|
-
|
|
27423
|
-
// src/datasource/skills/lazykatok/index.ts
|
|
27424
|
-
var init_lazykatok = __esm(() => {
|
|
27425
|
-
init_client8();
|
|
27426
|
-
init_methods3();
|
|
27427
|
-
init_skill7();
|
|
27428
|
-
init_types5();
|
|
27429
|
-
});
|
|
27622
|
+
var RRF_K2 = 60;
|
|
27430
27623
|
|
|
27431
|
-
// src/datasource/
|
|
27432
|
-
import {
|
|
27433
|
-
import {
|
|
27434
|
-
import {
|
|
27624
|
+
// src/datasource/sidecar/index.ts
|
|
27625
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
27626
|
+
import { mkdirSync as mkdirSync22, statSync as statSync3 } from "node:fs";
|
|
27627
|
+
import { dirname as dirname24 } from "node:path";
|
|
27628
|
+
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
27629
|
+
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
27435
27630
|
|
|
27436
|
-
class
|
|
27631
|
+
class CrawlerSidecar {
|
|
27632
|
+
descriptor;
|
|
27437
27633
|
options;
|
|
27438
|
-
|
|
27634
|
+
select;
|
|
27635
|
+
where;
|
|
27636
|
+
opened;
|
|
27637
|
+
attached;
|
|
27638
|
+
lastSemantic;
|
|
27639
|
+
constructor(options) {
|
|
27439
27640
|
this.options = options;
|
|
27641
|
+
const { descriptor } = options;
|
|
27642
|
+
this.descriptor = descriptor;
|
|
27643
|
+
const metadataColumns = descriptor.metadata.map((column, index) => `${column.sql} as m${index}`);
|
|
27644
|
+
this.select = [
|
|
27645
|
+
`${descriptor.keyExpression} as k`,
|
|
27646
|
+
`${descriptor.textExpression} as content`,
|
|
27647
|
+
`${descriptor.titleExpression ?? "null"} as title`,
|
|
27648
|
+
`${descriptor.epochSecondsExpression ?? "null"} as epoch`,
|
|
27649
|
+
...metadataColumns
|
|
27650
|
+
].join(", ");
|
|
27651
|
+
const scope = descriptor.scopeExpression !== undefined && options.workspaceId !== undefined;
|
|
27652
|
+
const allowlist = [
|
|
27653
|
+
...(options.channelIds?.length ?? 0) > 0 ? [`${descriptor.channelIdExpression} in (${options.channelIds?.map(() => "?").join(", ")})`] : [],
|
|
27654
|
+
...(options.channelNames?.length ?? 0) > 0 ? [`${descriptor.channelNameExpression} in (${options.channelNames?.map(() => "?").join(", ")})`] : []
|
|
27655
|
+
];
|
|
27656
|
+
this.where = [
|
|
27657
|
+
descriptor.liveWhere,
|
|
27658
|
+
...scope ? [`${descriptor.scopeExpression} = ?`] : [],
|
|
27659
|
+
...allowlist.length > 0 ? [`(${allowlist.join(" or ")})`] : []
|
|
27660
|
+
].join(" and ");
|
|
27440
27661
|
}
|
|
27441
|
-
|
|
27442
|
-
|
|
27443
|
-
|
|
27444
|
-
|
|
27445
|
-
const
|
|
27446
|
-
|
|
27447
|
-
|
|
27448
|
-
|
|
27449
|
-
|
|
27450
|
-
|
|
27451
|
-
|
|
27452
|
-
|
|
27453
|
-
|
|
27454
|
-
|
|
27455
|
-
|
|
27456
|
-
|
|
27457
|
-
|
|
27458
|
-
|
|
27459
|
-
|
|
27460
|
-
|
|
27461
|
-
|
|
27462
|
-
|
|
27463
|
-
files.push(path);
|
|
27464
|
-
}
|
|
27465
|
-
} catch {
|
|
27466
|
-
missingPaths += 1;
|
|
27467
|
-
}
|
|
27662
|
+
get db() {
|
|
27663
|
+
if (this.opened !== undefined)
|
|
27664
|
+
return this.opened;
|
|
27665
|
+
mkdirSync22(dirname24(this.options.sidecarDbPath), { recursive: true });
|
|
27666
|
+
const db = new DatabaseSync2(this.options.sidecarDbPath);
|
|
27667
|
+
db.exec(`pragma busy_timeout = ${BUSY_TIMEOUT_MS}`);
|
|
27668
|
+
db.exec("pragma journal_mode = wal");
|
|
27669
|
+
db.exec(`
|
|
27670
|
+
create table if not exists sidecar_meta (k text primary key, v text not null);
|
|
27671
|
+
create table if not exists sidecar_vec (message_key text primary key, hash text not null, vec blob not null);
|
|
27672
|
+
`);
|
|
27673
|
+
this.opened = db;
|
|
27674
|
+
return db;
|
|
27675
|
+
}
|
|
27676
|
+
attachCrawler() {
|
|
27677
|
+
let file;
|
|
27678
|
+
try {
|
|
27679
|
+
const { dev, ino } = statSync3(this.options.crawlerDbPath);
|
|
27680
|
+
file = { dev, ino };
|
|
27681
|
+
} catch {
|
|
27682
|
+
this.detachCrawler();
|
|
27683
|
+
return false;
|
|
27468
27684
|
}
|
|
27469
|
-
if (
|
|
27470
|
-
|
|
27685
|
+
if (this.attached !== undefined) {
|
|
27686
|
+
if (this.attached.dev === file.dev && this.attached.ino === file.ino)
|
|
27687
|
+
return true;
|
|
27688
|
+
this.detachCrawler();
|
|
27471
27689
|
}
|
|
27472
|
-
const
|
|
27473
|
-
|
|
27474
|
-
|
|
27475
|
-
|
|
27476
|
-
|
|
27477
|
-
let skippedLarge = 0;
|
|
27478
|
-
for (const file of files.sort()) {
|
|
27479
|
-
if (documents.length >= maxDocuments)
|
|
27480
|
-
break;
|
|
27481
|
-
let bytes;
|
|
27482
|
-
try {
|
|
27483
|
-
const info = await stat6(file);
|
|
27484
|
-
if (info.size > maxBytes) {
|
|
27485
|
-
skippedLarge += 1;
|
|
27486
|
-
continue;
|
|
27487
|
-
}
|
|
27488
|
-
bytes = await readFile5(file);
|
|
27489
|
-
} catch {
|
|
27490
|
-
parseFailures += 1;
|
|
27491
|
-
continue;
|
|
27492
|
-
}
|
|
27493
|
-
const stem = basename12(file, extname6(file));
|
|
27494
|
-
const mailboxDir = basename12(dirname24(file));
|
|
27495
|
-
const hierarchy = ["mailboxes", mailboxDir, stem];
|
|
27496
|
-
const extension = extname6(file).toLowerCase();
|
|
27497
|
-
const rawMessages = extension === ".mbox" ? splitMbox(bytes.toString("utf8")) : [bytes.toString("utf8")];
|
|
27498
|
-
for (const [index, raw] of rawMessages.entries()) {
|
|
27499
|
-
if (documents.length >= maxDocuments)
|
|
27500
|
-
break;
|
|
27501
|
-
try {
|
|
27502
|
-
const parsed = await simpleParser2(raw);
|
|
27503
|
-
const subject = parsed.subject ?? "(no subject)";
|
|
27504
|
-
const from = Array.isArray(parsed.from) ? parsed.from[0]?.text : parsed.from?.text;
|
|
27505
|
-
const to = Array.isArray(parsed.to) ? parsed.to[0]?.text : parsed.to?.text;
|
|
27506
|
-
const body = (parsed.text ?? "").trim().slice(0, MAX_BODY_CHARS);
|
|
27507
|
-
const headerBlock = [
|
|
27508
|
-
`Subject: ${subject}`,
|
|
27509
|
-
...from !== undefined ? [`From: ${from}`] : [],
|
|
27510
|
-
...to !== undefined ? [`To: ${to}`] : [],
|
|
27511
|
-
...parsed.date !== undefined ? [`Date: ${parsed.date.toISOString()}`] : []
|
|
27512
|
-
].join(`
|
|
27513
|
-
`);
|
|
27514
|
-
const content = `${headerBlock}
|
|
27515
|
-
|
|
27516
|
-
${body}`.trim();
|
|
27517
|
-
if (content.length === 0)
|
|
27518
|
-
continue;
|
|
27519
|
-
documents.push({
|
|
27520
|
-
docId: extension === ".mbox" ? `${stem}-${index + 1}` : stem,
|
|
27521
|
-
hierarchy,
|
|
27522
|
-
title: subject,
|
|
27523
|
-
content,
|
|
27524
|
-
...parsed.date !== undefined ? { publishedAt: parsed.date.getTime() } : {},
|
|
27525
|
-
metadata: {
|
|
27526
|
-
path: file,
|
|
27527
|
-
subject,
|
|
27528
|
-
...from !== undefined ? { from } : {},
|
|
27529
|
-
messageIndex: index
|
|
27530
|
-
}
|
|
27531
|
-
});
|
|
27532
|
-
} catch {
|
|
27533
|
-
parseFailures += 1;
|
|
27534
|
-
}
|
|
27535
|
-
}
|
|
27690
|
+
const crawlerUri = `${pathToFileURL2(this.options.crawlerDbPath).href}?mode=ro`.replaceAll("'", "''");
|
|
27691
|
+
try {
|
|
27692
|
+
this.db.exec(`attach database '${crawlerUri}' as crawler`);
|
|
27693
|
+
} catch {
|
|
27694
|
+
return false;
|
|
27536
27695
|
}
|
|
27537
|
-
|
|
27538
|
-
|
|
27539
|
-
|
|
27540
|
-
|
|
27541
|
-
|
|
27696
|
+
const { descriptor } = this.options;
|
|
27697
|
+
try {
|
|
27698
|
+
this.db.prepare(`select ${this.select} from ${descriptor.fromClause} where ${this.where} limit 0`).all(...this.whereParams());
|
|
27699
|
+
this.db.prepare(`select 1 from ${descriptor.fts.table} limit 0`).all();
|
|
27700
|
+
} catch {
|
|
27701
|
+
this.db.exec("detach database crawler");
|
|
27702
|
+
return false;
|
|
27703
|
+
}
|
|
27704
|
+
this.attached = file;
|
|
27705
|
+
this.lastSemantic = undefined;
|
|
27706
|
+
return true;
|
|
27542
27707
|
}
|
|
27543
|
-
|
|
27544
|
-
|
|
27545
|
-
|
|
27546
|
-
|
|
27547
|
-
|
|
27548
|
-
|
|
27549
|
-
|
|
27550
|
-
|
|
27551
|
-
messages.push(current.join(`
|
|
27552
|
-
`));
|
|
27553
|
-
current = [];
|
|
27554
|
-
continue;
|
|
27555
|
-
}
|
|
27556
|
-
current.push(line);
|
|
27557
|
-
}
|
|
27558
|
-
if (current.length > 0)
|
|
27559
|
-
messages.push(current.join(`
|
|
27560
|
-
`));
|
|
27561
|
-
return messages.filter((message) => message.trim().length > 0);
|
|
27562
|
-
}
|
|
27563
|
-
var DEFAULT_MAX_DOCUMENTS3 = 500, DEFAULT_MAX_BYTES_PER_FILE2, MAX_BODY_CHARS = 50000, MBOX_SEPARATOR;
|
|
27564
|
-
var init_connector4 = __esm(() => {
|
|
27565
|
-
DEFAULT_MAX_BYTES_PER_FILE2 = 50 * 1024 * 1024;
|
|
27566
|
-
MBOX_SEPARATOR = /^From .*\d{4}$/mu;
|
|
27567
|
-
});
|
|
27568
|
-
|
|
27569
|
-
// src/datasource/skills/mail-export/skill.ts
|
|
27570
|
-
var MAIL_EXPORT_SKILL_DEFINITION, MailExportSkill;
|
|
27571
|
-
var init_skill8 = __esm(() => {
|
|
27572
|
-
init_connector_skill();
|
|
27573
|
-
init_connector4();
|
|
27574
|
-
MAIL_EXPORT_SKILL_DEFINITION = {
|
|
27575
|
-
skillName: "mail-export",
|
|
27576
|
-
skillType: "mail-export",
|
|
27577
|
-
description: "Local mail export datasource",
|
|
27578
|
-
capabilities: ["email", "filesystem"],
|
|
27579
|
-
defaultTags: ["mail-export", "email", "pii"],
|
|
27580
|
-
contentType: "email",
|
|
27581
|
-
manifestDescription: "Search indexed local mail exports (mbox/eml archives). Use for questions about archived email conversations.",
|
|
27582
|
-
manifestNotes: [
|
|
27583
|
-
"Only the server-configured export paths are indexed; archives are read locally and never uploaded."
|
|
27584
|
-
]
|
|
27585
|
-
};
|
|
27586
|
-
MailExportSkill = class MailExportSkill extends ConnectorDatasourceSkill {
|
|
27587
|
-
constructor(options = {}) {
|
|
27588
|
-
const { connector, connectorOptions, ...rest } = options;
|
|
27589
|
-
super(MAIL_EXPORT_SKILL_DEFINITION, {
|
|
27590
|
-
...rest,
|
|
27591
|
-
connector: connector ?? new MailExportConnector(connectorOptions ?? {})
|
|
27592
|
-
});
|
|
27593
|
-
}
|
|
27594
|
-
};
|
|
27595
|
-
});
|
|
27596
|
-
|
|
27597
|
-
// src/datasource/skills/mail-export/index.ts
|
|
27598
|
-
var init_mail_export = __esm(() => {
|
|
27599
|
-
init_connector4();
|
|
27600
|
-
init_skill8();
|
|
27601
|
-
});
|
|
27602
|
-
|
|
27603
|
-
// src/datasource/skills/mailcrawl/config.ts
|
|
27604
|
-
import { chmodSync, mkdirSync as mkdirSync22 } from "node:fs";
|
|
27605
|
-
function validateMailcrawlInstanceId(instanceId) {
|
|
27606
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(instanceId)) {
|
|
27607
|
-
throw new Error("mailcrawl instanceId must be a safe single path segment");
|
|
27708
|
+
detachCrawler() {
|
|
27709
|
+
if (this.attached === undefined)
|
|
27710
|
+
return;
|
|
27711
|
+
this.attached = undefined;
|
|
27712
|
+
this.lastSemantic = undefined;
|
|
27713
|
+
try {
|
|
27714
|
+
this.opened?.exec("detach database crawler");
|
|
27715
|
+
} catch {}
|
|
27608
27716
|
}
|
|
27609
|
-
|
|
27610
|
-
|
|
27611
|
-
mkdirSync22(path, { recursive: true, mode: 448 });
|
|
27612
|
-
if (process.platform !== "win32")
|
|
27613
|
-
chmodSync(path, 448);
|
|
27614
|
-
}
|
|
27615
|
-
var init_config3 = () => {};
|
|
27616
|
-
|
|
27617
|
-
// src/datasource/skills/mailcrawl/client.ts
|
|
27618
|
-
import { spawn as spawn15 } from "node:child_process";
|
|
27619
|
-
|
|
27620
|
-
class MailcrawlClient {
|
|
27621
|
-
options;
|
|
27622
|
-
constructor(options = {}) {
|
|
27623
|
-
this.options = options;
|
|
27624
|
-
if (options.instanceId !== undefined)
|
|
27625
|
-
validateMailcrawlInstanceId(options.instanceId);
|
|
27717
|
+
get semanticEnabled() {
|
|
27718
|
+
return this.options.embedder !== undefined;
|
|
27626
27719
|
}
|
|
27627
|
-
|
|
27628
|
-
|
|
27629
|
-
|
|
27630
|
-
|
|
27631
|
-
|
|
27632
|
-
...this.options.mailbox === undefined ? [] : ["--mailbox", this.options.mailbox],
|
|
27633
|
-
...this.options.backend === undefined ? [] : ["--backend", this.options.backend],
|
|
27634
|
-
...this.options.source === undefined ? [] : ["--source", this.options.source],
|
|
27635
|
-
...this.options.fixture === undefined ? [] : ["--fixture", this.options.fixture],
|
|
27636
|
-
...this.options.himalayaConfig === undefined ? [] : ["--himalaya-config", this.options.himalayaConfig]
|
|
27637
|
-
];
|
|
27638
|
-
const result = await this.run(args, signal, this.indexTimeoutMs());
|
|
27639
|
-
if (!result.ok)
|
|
27640
|
-
return failure3(result);
|
|
27641
|
-
const parsed = parseObject2(result.stdout);
|
|
27642
|
-
if (parsed === undefined || !hasAnyNumber(parsed, ["added", "updated", "deleted", "unchanged", "chunksAdded"])) {
|
|
27643
|
-
return failure3({ ...result, ok: false, reason: "invalid-output" });
|
|
27644
|
-
}
|
|
27645
|
-
const messages = number(parsed, "added") ?? number(parsed, "updated") ?? number(parsed, "unchanged") ?? 0;
|
|
27646
|
-
return ok4({
|
|
27647
|
-
messages,
|
|
27648
|
-
added: number(parsed, "added"),
|
|
27649
|
-
updated: number(parsed, "updated"),
|
|
27650
|
-
deleted: number(parsed, "deleted"),
|
|
27651
|
-
unchanged: number(parsed, "unchanged"),
|
|
27652
|
-
chunksAdded: number(parsed, "chunksAdded"),
|
|
27653
|
-
archiveRevision: string(parsed, "archiveRevision")
|
|
27654
|
-
}, result);
|
|
27720
|
+
close() {
|
|
27721
|
+
this.opened?.close();
|
|
27722
|
+
this.opened = undefined;
|
|
27723
|
+
this.attached = undefined;
|
|
27724
|
+
this.lastSemantic = undefined;
|
|
27655
27725
|
}
|
|
27656
|
-
|
|
27657
|
-
|
|
27658
|
-
|
|
27659
|
-
|
|
27660
|
-
|
|
27661
|
-
|
|
27662
|
-
mode === "keyword" ? "bm25" : mode,
|
|
27663
|
-
...this.options.account === undefined ? [] : ["--account", this.options.account],
|
|
27664
|
-
...this.options.mailbox === undefined ? [] : ["--mailbox", this.options.mailbox],
|
|
27665
|
-
"--limit",
|
|
27666
|
-
String(options?.topK ?? 20),
|
|
27667
|
-
"--json",
|
|
27668
|
-
query.trim()
|
|
27726
|
+
whereParams() {
|
|
27727
|
+
const { descriptor, workspaceId, channelIds = [], channelNames = [] } = this.options;
|
|
27728
|
+
return [
|
|
27729
|
+
...descriptor.scopeExpression !== undefined && workspaceId !== undefined ? [workspaceId] : [],
|
|
27730
|
+
...channelIds,
|
|
27731
|
+
...channelNames
|
|
27669
27732
|
];
|
|
27670
|
-
const result = await this.run(args, options?.signal, this.options.timeoutMs ?? DEFAULT_SEARCH_TIMEOUT_MS2);
|
|
27671
|
-
if (!result.ok)
|
|
27672
|
-
return failure3(result);
|
|
27673
|
-
const parsed = parseJson3(result.stdout);
|
|
27674
|
-
if (parsed === undefined)
|
|
27675
|
-
return failure3({ ...result, ok: false, reason: "invalid-output" });
|
|
27676
|
-
const rows = Array.isArray(parsed) ? parsed : objectArray(parsed, ["hits", "results", "items"]);
|
|
27677
|
-
if (rows === undefined)
|
|
27678
|
-
return failure3({ ...result, ok: false, reason: "invalid-output" });
|
|
27679
|
-
const hits = rows.map((row, index) => normalizeHit2(row, mode, index));
|
|
27680
|
-
if (hits.some((hit) => hit === undefined)) {
|
|
27681
|
-
return failure3({ ...result, ok: false, reason: "invalid-output" });
|
|
27682
|
-
}
|
|
27683
|
-
return {
|
|
27684
|
-
ok: true,
|
|
27685
|
-
hits,
|
|
27686
|
-
stdout: result.stdout,
|
|
27687
|
-
stderr: result.stderr,
|
|
27688
|
-
code: result.code ?? 0
|
|
27689
|
-
};
|
|
27690
27733
|
}
|
|
27691
|
-
|
|
27692
|
-
|
|
27734
|
+
searchLexical(query, topK) {
|
|
27735
|
+
const match = toMatchQuery(query);
|
|
27736
|
+
if (match === undefined || topK <= 0 || !this.attachCrawler())
|
|
27737
|
+
return [];
|
|
27738
|
+
const { descriptor } = this.options;
|
|
27739
|
+
const rows = this.db.prepare(`select ${this.select}, bm25(${descriptor.fts.table}) as rank
|
|
27740
|
+
from ${descriptor.fromClause} join ${descriptor.fts.table} on ${descriptor.fts.joinOn}
|
|
27741
|
+
where ${descriptor.fts.table} match ? and ${this.where}
|
|
27742
|
+
order by rank limit ?`).all(match, ...this.whereParams(), topK);
|
|
27743
|
+
return rows.map((row) => this.toHit(this.toRow(row), -Number(row.rank)));
|
|
27693
27744
|
}
|
|
27694
|
-
async
|
|
27695
|
-
const
|
|
27696
|
-
if (
|
|
27697
|
-
return
|
|
27745
|
+
async searchSemantic(query, topK) {
|
|
27746
|
+
const trimmed = query.trim();
|
|
27747
|
+
if (this.options.embedder === undefined || trimmed.length === 0 || topK <= 0 || !this.attachCrawler())
|
|
27748
|
+
return [];
|
|
27749
|
+
const ranked = await this.rankSemantic(trimmed, topK * POOL_FACTOR);
|
|
27750
|
+
if (ranked.length === 0)
|
|
27751
|
+
return [];
|
|
27752
|
+
const live = this.hydrate(ranked.map((entry) => entry.key));
|
|
27753
|
+
const hits = [];
|
|
27754
|
+
for (const entry of ranked) {
|
|
27755
|
+
const row = live.get(entry.key);
|
|
27756
|
+
if (row !== undefined)
|
|
27757
|
+
hits.push(this.toHit(row, entry.score));
|
|
27758
|
+
if (hits.length >= topK)
|
|
27759
|
+
break;
|
|
27698
27760
|
}
|
|
27699
|
-
|
|
27700
|
-
|
|
27701
|
-
|
|
27702
|
-
const
|
|
27703
|
-
|
|
27704
|
-
|
|
27705
|
-
|
|
27761
|
+
return hits;
|
|
27762
|
+
}
|
|
27763
|
+
async rankSemantic(query, pool) {
|
|
27764
|
+
const now = Date.now();
|
|
27765
|
+
let cached = this.lastSemantic;
|
|
27766
|
+
if (cached === undefined || cached.query !== query || now - cached.at > SEMANTIC_REUSE_MS) {
|
|
27767
|
+
const embedder = this.options.embedder;
|
|
27768
|
+
if (embedder === undefined)
|
|
27769
|
+
return [];
|
|
27770
|
+
cached = {
|
|
27771
|
+
query,
|
|
27772
|
+
at: now,
|
|
27773
|
+
vector: embedder.embed([query]).then((vectors) => vectors[0], () => {
|
|
27774
|
+
return;
|
|
27775
|
+
}),
|
|
27776
|
+
ranked: undefined
|
|
27777
|
+
};
|
|
27778
|
+
this.lastSemantic = cached;
|
|
27706
27779
|
}
|
|
27707
|
-
|
|
27708
|
-
|
|
27709
|
-
|
|
27710
|
-
|
|
27711
|
-
|
|
27780
|
+
const vector = await cached.vector;
|
|
27781
|
+
if (vector === undefined) {
|
|
27782
|
+
if (this.lastSemantic === cached)
|
|
27783
|
+
this.lastSemantic = undefined;
|
|
27784
|
+
return [];
|
|
27712
27785
|
}
|
|
27713
|
-
if (
|
|
27714
|
-
|
|
27715
|
-
|
|
27716
|
-
}
|
|
27717
|
-
async index(signal) {
|
|
27718
|
-
const result = await this.run(["index", "--json"], signal, this.indexTimeoutMs());
|
|
27719
|
-
if (!result.ok)
|
|
27720
|
-
return failure3(result);
|
|
27721
|
-
const parsed = parseObject2(result.stdout);
|
|
27722
|
-
if (parsed === undefined || number(parsed, "embedded") === undefined && number(parsed, "reused") === undefined && string(parsed, "generation") === undefined) {
|
|
27723
|
-
return failure3({ ...result, ok: false, reason: "invalid-output" });
|
|
27786
|
+
if (cached.ranked === undefined || cached.ranked.pool < pool) {
|
|
27787
|
+
const wide = pool * POOL_FACTOR;
|
|
27788
|
+
cached.ranked = { pool: wide, keys: topKByCosine(vector, this.storedVectors(), wide) };
|
|
27724
27789
|
}
|
|
27725
|
-
return
|
|
27726
|
-
embedded: number(parsed, "embedded"),
|
|
27727
|
-
reused: number(parsed, "reused"),
|
|
27728
|
-
generation: string(parsed, "generation"),
|
|
27729
|
-
archiveRevision: string(parsed, "archiveRevision"),
|
|
27730
|
-
rebuilt: typeof parsed.rebuilt === "boolean" ? parsed.rebuilt : undefined,
|
|
27731
|
-
embedder: string(parsed, "embedder")
|
|
27732
|
-
}, result);
|
|
27790
|
+
return cached.ranked.keys.slice(0, pool);
|
|
27733
27791
|
}
|
|
27734
|
-
|
|
27735
|
-
|
|
27736
|
-
|
|
27737
|
-
const
|
|
27738
|
-
const
|
|
27739
|
-
|
|
27740
|
-
|
|
27741
|
-
|
|
27742
|
-
|
|
27743
|
-
|
|
27744
|
-
|
|
27745
|
-
const
|
|
27746
|
-
|
|
27747
|
-
|
|
27748
|
-
|
|
27749
|
-
|
|
27750
|
-
|
|
27751
|
-
|
|
27792
|
+
async searchHybrid(query, topK) {
|
|
27793
|
+
if (topK <= 0)
|
|
27794
|
+
return [];
|
|
27795
|
+
const pool = topK * POOL_FACTOR;
|
|
27796
|
+
const lexical = this.searchLexical(query, pool);
|
|
27797
|
+
const semantic = await this.searchSemantic(query, pool);
|
|
27798
|
+
if (semantic.length === 0)
|
|
27799
|
+
return lexical.slice(0, topK);
|
|
27800
|
+
const byKey = new Map;
|
|
27801
|
+
for (const hit of [...semantic, ...lexical])
|
|
27802
|
+
byKey.set(hit.key, hit);
|
|
27803
|
+
const fused = [];
|
|
27804
|
+
for (const entry of reciprocalRankFusion([lexical.map((hit) => hit.key), semantic.map((hit) => hit.key)])) {
|
|
27805
|
+
const hit = byKey.get(entry.key);
|
|
27806
|
+
if (hit !== undefined)
|
|
27807
|
+
fused.push({ ...hit, score: entry.score });
|
|
27808
|
+
if (fused.length >= topK)
|
|
27809
|
+
break;
|
|
27810
|
+
}
|
|
27811
|
+
return fused;
|
|
27812
|
+
}
|
|
27813
|
+
async sync() {
|
|
27814
|
+
const { embedder } = this.options;
|
|
27815
|
+
if (embedder === undefined)
|
|
27816
|
+
return { ok: false, message: "no embedder configured" };
|
|
27817
|
+
if (!this.attachCrawler())
|
|
27818
|
+
return { ok: false, message: "crawler archive not available yet" };
|
|
27819
|
+
let identity;
|
|
27820
|
+
try {
|
|
27821
|
+
identity = await embedder.identity();
|
|
27822
|
+
} catch (error) {
|
|
27823
|
+
return { ok: false, message: errorMessage2(error) };
|
|
27824
|
+
}
|
|
27825
|
+
this.adoptIdentity(identity);
|
|
27826
|
+
const pruned = this.prune();
|
|
27827
|
+
const pending = this.pendingKeys();
|
|
27828
|
+
const batchSize = Math.max(1, this.options.batchSize ?? DEFAULT_BATCH_SIZE);
|
|
27829
|
+
const budget = Math.max(1, this.options.maxEmbedPerSync ?? DEFAULT_MAX_EMBED_PER_SYNC);
|
|
27830
|
+
const target = Math.min(pending.length, budget);
|
|
27831
|
+
const embed = async (rows) => {
|
|
27832
|
+
const vectors = await embedder.embed(rows.map((row) => row.input));
|
|
27833
|
+
if (vectors.length !== rows.length) {
|
|
27834
|
+
throw new Error(`embedder returned ${vectors.length} vectors for ${rows.length} messages`);
|
|
27835
|
+
}
|
|
27836
|
+
const wrong = vectors.find((vector) => vector.length !== identity.dimension);
|
|
27837
|
+
if (wrong !== undefined) {
|
|
27838
|
+
throw new Error(`embedder returned a ${wrong.length}-dimension vector, expected ${identity.dimension}`);
|
|
27839
|
+
}
|
|
27840
|
+
return vectors;
|
|
27752
27841
|
};
|
|
27753
|
-
|
|
27754
|
-
|
|
27755
|
-
|
|
27756
|
-
|
|
27757
|
-
|
|
27758
|
-
|
|
27759
|
-
|
|
27760
|
-
|
|
27761
|
-
|
|
27762
|
-
|
|
27842
|
+
let embedded = 0;
|
|
27843
|
+
let skipped = 0;
|
|
27844
|
+
for (let offset = 0;offset < target; offset += batchSize) {
|
|
27845
|
+
const rows = [...this.hydrate(pending.slice(offset, Math.min(offset + batchSize, target))).values()];
|
|
27846
|
+
if (rows.length === 0)
|
|
27847
|
+
continue;
|
|
27848
|
+
try {
|
|
27849
|
+
this.store(rows, await embed(rows));
|
|
27850
|
+
embedded += rows.length;
|
|
27851
|
+
} catch (batchError) {
|
|
27852
|
+
const rejected = [];
|
|
27853
|
+
let lastError = batchError;
|
|
27854
|
+
for (const row of rows) {
|
|
27855
|
+
try {
|
|
27856
|
+
this.store([row], await embed([row]));
|
|
27857
|
+
embedded += 1;
|
|
27858
|
+
} catch (error) {
|
|
27859
|
+
rejected.push(row);
|
|
27860
|
+
lastError = error;
|
|
27861
|
+
}
|
|
27862
|
+
}
|
|
27863
|
+
if (rejected.length === 0)
|
|
27864
|
+
continue;
|
|
27865
|
+
if (embedded === 0)
|
|
27866
|
+
return { ok: false, message: errorMessage2(lastError) };
|
|
27867
|
+
this.store(rejected, undefined);
|
|
27868
|
+
skipped += rejected.length;
|
|
27763
27869
|
}
|
|
27870
|
+
}
|
|
27871
|
+
return { ok: true, embedded, skipped, pruned, remaining: pending.length - embedded - skipped };
|
|
27872
|
+
}
|
|
27873
|
+
adoptIdentity(identity) {
|
|
27874
|
+
const serialized = JSON.stringify(identity);
|
|
27875
|
+
const current = this.db.prepare("select v from sidecar_meta where k = ?").get(IDENTITY_KEY);
|
|
27876
|
+
if (current?.v === serialized)
|
|
27877
|
+
return;
|
|
27878
|
+
this.transaction(() => {
|
|
27879
|
+
this.db.exec("delete from sidecar_vec");
|
|
27880
|
+
this.db.prepare("insert or replace into sidecar_meta (k, v) values (?, ?)").run(IDENTITY_KEY, serialized);
|
|
27764
27881
|
});
|
|
27765
|
-
|
|
27766
|
-
|
|
27767
|
-
|
|
27768
|
-
|
|
27769
|
-
|
|
27882
|
+
this.lastSemantic = undefined;
|
|
27883
|
+
}
|
|
27884
|
+
prune() {
|
|
27885
|
+
const { descriptor } = this.options;
|
|
27886
|
+
const result = this.db.prepare(`delete from sidecar_vec where message_key not in (
|
|
27887
|
+
select ${descriptor.keyExpression} from ${descriptor.fromClause} where ${this.where}
|
|
27888
|
+
)`).run(...this.whereParams());
|
|
27889
|
+
if (Number(result.changes) > 0)
|
|
27890
|
+
this.lastSemantic = undefined;
|
|
27891
|
+
return Number(result.changes);
|
|
27892
|
+
}
|
|
27893
|
+
pendingKeys() {
|
|
27894
|
+
const { descriptor } = this.options;
|
|
27895
|
+
const rows = this.db.prepare(`select ${this.select}, v.hash as stored_hash
|
|
27896
|
+
from ${descriptor.fromClause}
|
|
27897
|
+
left join sidecar_vec v on v.message_key = ${descriptor.keyExpression}
|
|
27898
|
+
where ${this.where}`).iterate(...this.whereParams());
|
|
27899
|
+
const pending = [];
|
|
27900
|
+
for (const row of rows) {
|
|
27901
|
+
if (row.stored_hash !== this.toRow(row).hash)
|
|
27902
|
+
pending.push(String(row.k));
|
|
27903
|
+
}
|
|
27904
|
+
return pending;
|
|
27905
|
+
}
|
|
27906
|
+
hydrate(keys) {
|
|
27907
|
+
const { descriptor } = this.options;
|
|
27908
|
+
const out = new Map;
|
|
27909
|
+
if (keys.length === 0)
|
|
27910
|
+
return out;
|
|
27911
|
+
const rows = this.db.prepare(`select ${this.select} from ${descriptor.fromClause}
|
|
27912
|
+
where ${descriptor.keyExpression} in (${keys.map(() => "?").join(", ")}) and ${this.where}`).all(...keys, ...this.whereParams());
|
|
27913
|
+
const byKey = new Map(rows.map((row) => [String(row.k), this.toRow(row)]));
|
|
27914
|
+
for (const key of keys) {
|
|
27915
|
+
const row = byKey.get(key);
|
|
27916
|
+
if (row !== undefined)
|
|
27917
|
+
out.set(key, row);
|
|
27918
|
+
}
|
|
27919
|
+
return out;
|
|
27920
|
+
}
|
|
27921
|
+
store(rows, vectors) {
|
|
27922
|
+
const upsert = this.db.prepare("insert or replace into sidecar_vec (message_key, hash, vec) values (?, ?, ?)");
|
|
27923
|
+
this.transaction(() => {
|
|
27924
|
+
for (const [index, row] of rows.entries()) {
|
|
27925
|
+
upsert.run(row.key, row.hash, encodeVector(vectors?.[index] ?? []));
|
|
27770
27926
|
}
|
|
27771
27927
|
});
|
|
27772
|
-
|
|
27773
|
-
|
|
27774
|
-
|
|
27775
|
-
|
|
27776
|
-
|
|
27777
|
-
|
|
27778
|
-
|
|
27779
|
-
|
|
27780
|
-
|
|
27781
|
-
|
|
27782
|
-
|
|
27783
|
-
|
|
27784
|
-
});
|
|
27785
|
-
});
|
|
27786
|
-
child.on("close", (code) => {
|
|
27787
|
-
if (settled)
|
|
27788
|
-
return;
|
|
27789
|
-
settled = true;
|
|
27790
|
-
clearTimeout(timer);
|
|
27791
|
-
signal?.removeEventListener("abort", abort);
|
|
27792
|
-
resolveResult({
|
|
27793
|
-
ok: code === 0 && reason === undefined,
|
|
27794
|
-
...reason === undefined ? code === 0 ? {} : { reason: "nonzero-exit" } : { reason },
|
|
27795
|
-
stdout: stdout.text,
|
|
27796
|
-
stderr: stderr.text,
|
|
27797
|
-
code
|
|
27798
|
-
});
|
|
27799
|
-
});
|
|
27800
|
-
});
|
|
27801
|
-
}
|
|
27802
|
-
function terminate4(child) {
|
|
27803
|
-
if (child.killed)
|
|
27804
|
-
return;
|
|
27805
|
-
if (process.platform !== "win32" && child.pid !== undefined) {
|
|
27928
|
+
this.lastSemantic = undefined;
|
|
27929
|
+
}
|
|
27930
|
+
*storedVectors() {
|
|
27931
|
+
const rows = this.db.prepare("select message_key, vec from sidecar_vec where length(vec) > 0").iterate();
|
|
27932
|
+
for (const row of rows) {
|
|
27933
|
+
const vector = row.vec instanceof Uint8Array ? decodeVector(row.vec) : undefined;
|
|
27934
|
+
if (vector !== undefined)
|
|
27935
|
+
yield { key: String(row.message_key), vector };
|
|
27936
|
+
}
|
|
27937
|
+
}
|
|
27938
|
+
transaction(work) {
|
|
27939
|
+
this.db.exec("begin immediate");
|
|
27806
27940
|
try {
|
|
27807
|
-
|
|
27808
|
-
|
|
27809
|
-
} catch {
|
|
27941
|
+
work();
|
|
27942
|
+
this.db.exec("commit");
|
|
27943
|
+
} catch (error) {
|
|
27944
|
+
this.db.exec("rollback");
|
|
27945
|
+
throw error;
|
|
27946
|
+
}
|
|
27810
27947
|
}
|
|
27811
|
-
|
|
27812
|
-
}
|
|
27813
|
-
|
|
27814
|
-
|
|
27815
|
-
|
|
27816
|
-
|
|
27817
|
-
|
|
27948
|
+
toRow(row) {
|
|
27949
|
+
const metadata = {};
|
|
27950
|
+
for (const [index, column] of this.options.descriptor.metadata.entries()) {
|
|
27951
|
+
const value = row[`m${index}`];
|
|
27952
|
+
if (typeof value === "string" && value.length > 0 || typeof value === "number")
|
|
27953
|
+
metadata[column.key] = String(value);
|
|
27954
|
+
}
|
|
27955
|
+
const title = typeof row.title === "string" && row.title.length > 0 ? row.title : undefined;
|
|
27956
|
+
const epoch = typeof row.epoch === "number" && Number.isFinite(row.epoch) ? row.epoch : undefined;
|
|
27957
|
+
const content = String(row.content ?? "");
|
|
27958
|
+
const input = `${title ?? ""}
|
|
27959
|
+
${content}`.slice(0, MAX_EMBED_INPUT_CHARS);
|
|
27960
|
+
return {
|
|
27961
|
+
key: String(row.k),
|
|
27962
|
+
content,
|
|
27963
|
+
title,
|
|
27964
|
+
epoch,
|
|
27965
|
+
metadata,
|
|
27966
|
+
input,
|
|
27967
|
+
hash: createHash14("sha1").update(input).digest("hex")
|
|
27968
|
+
};
|
|
27969
|
+
}
|
|
27970
|
+
toHit(row, score) {
|
|
27971
|
+
return {
|
|
27972
|
+
key: row.key,
|
|
27973
|
+
content: row.content,
|
|
27974
|
+
score,
|
|
27975
|
+
...row.title !== undefined ? { title: row.title } : {},
|
|
27976
|
+
...row.epoch !== undefined ? { publishedAt: Math.round(row.epoch * 1000) } : {},
|
|
27977
|
+
metadata: row.metadata
|
|
27978
|
+
};
|
|
27818
27979
|
}
|
|
27819
27980
|
}
|
|
27820
|
-
function
|
|
27821
|
-
const
|
|
27822
|
-
return
|
|
27981
|
+
function toMatchQuery(text) {
|
|
27982
|
+
const terms = [...new Set(text.match(/[\p{L}\p{N}_]+/gu) ?? [])];
|
|
27983
|
+
return terms.length === 0 ? undefined : terms.map((term) => `"${term}"`).join(" OR ");
|
|
27823
27984
|
}
|
|
27824
|
-
function
|
|
27825
|
-
|
|
27826
|
-
return;
|
|
27827
|
-
for (const key of keys)
|
|
27828
|
-
if (Array.isArray(value[key]))
|
|
27829
|
-
return value[key];
|
|
27830
|
-
return;
|
|
27831
|
-
}
|
|
27832
|
-
function hasAnyNumber(value, keys) {
|
|
27833
|
-
return keys.some((key) => number(value, key) !== undefined);
|
|
27834
|
-
}
|
|
27835
|
-
function normalizeHit2(value, mode, index) {
|
|
27836
|
-
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
27837
|
-
return;
|
|
27838
|
-
const row = value;
|
|
27839
|
-
const text = (key) => typeof row[key] === "string" ? row[key] : "";
|
|
27840
|
-
const chunkId = text("chunkId") || text("chunk_id");
|
|
27841
|
-
const messageId = text("messageId") || text("message_id");
|
|
27842
|
-
const accountId = text("accountId") || text("account_id");
|
|
27843
|
-
const mailbox = text("mailbox");
|
|
27844
|
-
const snippet = text("snippet") || text("content") || text("text");
|
|
27845
|
-
if (!isSafeOpaqueId(chunkId) || !isSafeOpaqueId(messageId) || !snippet)
|
|
27846
|
-
return;
|
|
27847
|
-
const to = Array.isArray(row.to) ? row.to.filter((item) => typeof item === "string") : [];
|
|
27848
|
-
return {
|
|
27849
|
-
chunkId,
|
|
27850
|
-
messageId,
|
|
27851
|
-
threadId: text("threadId") || text("thread_id"),
|
|
27852
|
-
accountId,
|
|
27853
|
-
mailbox,
|
|
27854
|
-
subject: text("subject"),
|
|
27855
|
-
from: text("from"),
|
|
27856
|
-
to,
|
|
27857
|
-
date: text("date"),
|
|
27858
|
-
snippet,
|
|
27859
|
-
score: typeof row.score === "number" ? row.score : 1 / (index + 1),
|
|
27860
|
-
mode
|
|
27861
|
-
};
|
|
27862
|
-
}
|
|
27863
|
-
function number(value, key) {
|
|
27864
|
-
return typeof value[key] === "number" && Number.isFinite(value[key]) ? value[key] : undefined;
|
|
27865
|
-
}
|
|
27866
|
-
function string(value, key) {
|
|
27867
|
-
return typeof value[key] === "string" ? value[key] : undefined;
|
|
27868
|
-
}
|
|
27869
|
-
function ok4(data, result) {
|
|
27870
|
-
return { ok: true, data, stdout: result.stdout, stderr: result.stderr, code: result.code ?? 0 };
|
|
27871
|
-
}
|
|
27872
|
-
function failure3(result) {
|
|
27873
|
-
return {
|
|
27874
|
-
ok: false,
|
|
27875
|
-
reason: result.reason ?? "nonzero-exit",
|
|
27876
|
-
stdout: result.stdout,
|
|
27877
|
-
stderr: boundDiagnosticText6(result.stderr),
|
|
27878
|
-
code: result.code
|
|
27879
|
-
};
|
|
27880
|
-
}
|
|
27881
|
-
function boundDiagnosticText6(value) {
|
|
27882
|
-
if (value.length === 0)
|
|
27883
|
-
return "";
|
|
27884
|
-
return value.trim().slice(0, 4000);
|
|
27885
|
-
}
|
|
27886
|
-
function appendOutput(current, chunk, maxBytes) {
|
|
27887
|
-
if (current.exceeded)
|
|
27888
|
-
return current;
|
|
27889
|
-
const bytes = Buffer.from(chunk, "utf8");
|
|
27890
|
-
const remaining = Math.max(0, maxBytes - current.bytes);
|
|
27891
|
-
const prefix = truncateUtf8(chunk, remaining);
|
|
27892
|
-
const total = current.bytes + bytes.byteLength;
|
|
27893
|
-
return {
|
|
27894
|
-
text: current.text + prefix,
|
|
27895
|
-
bytes: Math.min(total, maxBytes),
|
|
27896
|
-
exceeded: total > maxBytes
|
|
27897
|
-
};
|
|
27985
|
+
function errorMessage2(error) {
|
|
27986
|
+
return error instanceof Error ? error.message : String(error);
|
|
27898
27987
|
}
|
|
27899
|
-
|
|
27900
|
-
|
|
27988
|
+
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";
|
|
27989
|
+
var init_sidecar = () => {};
|
|
27990
|
+
|
|
27991
|
+
// src/datasource/sidecar/methods.ts
|
|
27992
|
+
function sidecarMethodName(datasourceId, mode) {
|
|
27993
|
+
return mode === "bm25" ? `${datasourceId}-sidecar-bm25` : `${datasourceId}-${mode}`;
|
|
27901
27994
|
}
|
|
27902
|
-
|
|
27903
|
-
|
|
27904
|
-
|
|
27905
|
-
|
|
27906
|
-
|
|
27907
|
-
if (REMOTE_EMBEDDING_ENV_KEYS.has(lower))
|
|
27908
|
-
return key;
|
|
27909
|
-
if (lower === "mailcrawl_embedder" || lower === "mailcrawl_embed_url") {
|
|
27910
|
-
if (isRemoteHttpEndpoint(value))
|
|
27911
|
-
return key;
|
|
27912
|
-
}
|
|
27995
|
+
|
|
27996
|
+
class SidecarRetrievalMethod {
|
|
27997
|
+
options;
|
|
27998
|
+
constructor(options) {
|
|
27999
|
+
this.options = options;
|
|
27913
28000
|
}
|
|
27914
|
-
|
|
27915
|
-
}
|
|
27916
|
-
|
|
27917
|
-
|
|
27918
|
-
|
|
27919
|
-
|
|
27920
|
-
|
|
27921
|
-
|
|
27922
|
-
|
|
27923
|
-
|
|
28001
|
+
describe() {
|
|
28002
|
+
const { datasourceId, skillType, mode, tags } = this.options;
|
|
28003
|
+
const { type, text, capabilities } = DESCRIPTIONS[mode];
|
|
28004
|
+
return {
|
|
28005
|
+
name: sidecarMethodName(datasourceId, mode),
|
|
28006
|
+
type,
|
|
28007
|
+
description: `${text} over ${skillType} via a read-only sidecar index`,
|
|
28008
|
+
status: "active",
|
|
28009
|
+
capabilities: [...capabilities, "scoped", "path-opaque-sources"],
|
|
28010
|
+
datasourceId,
|
|
28011
|
+
tags
|
|
28012
|
+
};
|
|
27924
28013
|
}
|
|
27925
|
-
|
|
27926
|
-
|
|
27927
|
-
|
|
27928
|
-
|
|
27929
|
-
|
|
27930
|
-
|
|
27931
|
-
|
|
27932
|
-
|
|
27933
|
-
|
|
27934
|
-
|
|
28014
|
+
async retrieve(query, options) {
|
|
28015
|
+
const trimmed = query.trim();
|
|
28016
|
+
if (trimmed.length === 0)
|
|
28017
|
+
return [];
|
|
28018
|
+
const topK = options.topK ?? DEFAULT_TOP_K5;
|
|
28019
|
+
const { sidecar, mode } = this.options;
|
|
28020
|
+
const hits = mode === "bm25" ? sidecar.searchLexical(trimmed, topK) : mode === "semantic" ? await sidecar.searchSemantic(trimmed, topK) : await sidecar.searchHybrid(trimmed, topK);
|
|
28021
|
+
const results = [];
|
|
28022
|
+
for (const hit of hits) {
|
|
28023
|
+
const result = this.toResult(hit, options);
|
|
28024
|
+
if (result !== undefined)
|
|
28025
|
+
results.push(result);
|
|
28026
|
+
}
|
|
28027
|
+
return results;
|
|
28028
|
+
}
|
|
28029
|
+
toResult(hit, options) {
|
|
28030
|
+
const { descriptor, datasourceId, instanceId, mode } = this.options;
|
|
28031
|
+
const chunkId = descriptor.chunkId(hit.key, hit.metadata);
|
|
28032
|
+
const source = datasourceSourcePath(datasourceId, instanceId, chunkId);
|
|
28033
|
+
if (!matchesDatasourceScope(source, options.scope))
|
|
28034
|
+
return;
|
|
28035
|
+
const method = sidecarMethodName(datasourceId, mode);
|
|
28036
|
+
return {
|
|
28037
|
+
id: `${datasourceId}:${instanceId}:${chunkId}`,
|
|
28038
|
+
content: hit.content,
|
|
28039
|
+
source,
|
|
28040
|
+
score: hit.score,
|
|
28041
|
+
metadata: {
|
|
28042
|
+
...hit.metadata,
|
|
28043
|
+
method,
|
|
28044
|
+
datasourceId,
|
|
28045
|
+
instanceId,
|
|
28046
|
+
backend: "sidecar",
|
|
28047
|
+
...hit.title !== undefined ? { title: hit.title } : {},
|
|
28048
|
+
hierarchy: descriptor.hierarchy(hit.metadata).join("/"),
|
|
28049
|
+
...hit.publishedAt !== undefined ? { publishedAt: hit.publishedAt } : {}
|
|
28050
|
+
}
|
|
28051
|
+
};
|
|
27935
28052
|
}
|
|
27936
|
-
return "";
|
|
27937
28053
|
}
|
|
27938
|
-
var
|
|
27939
|
-
var
|
|
27940
|
-
|
|
27941
|
-
|
|
27942
|
-
|
|
27943
|
-
|
|
27944
|
-
|
|
27945
|
-
|
|
27946
|
-
|
|
27947
|
-
|
|
27948
|
-
|
|
27949
|
-
|
|
27950
|
-
"MAILCRAWL_EMBED_DIM",
|
|
27951
|
-
"MAILCRAWL_EMBED_TIMEOUT",
|
|
27952
|
-
"MAILCRAWL_EMBED_BATCH_SIZE",
|
|
27953
|
-
"MAILCRAWL_QUERY_PREFIX",
|
|
27954
|
-
"MAILCRAWL_PASSAGE_PREFIX",
|
|
27955
|
-
"MAILCRAWL_NATIVE_MODEL",
|
|
27956
|
-
"MAILCRAWL_NATIVE_DEVICE",
|
|
27957
|
-
"MAILCRAWL_NATIVE_DTYPE",
|
|
27958
|
-
"MAILCRAWL_KIWI_MODEL",
|
|
27959
|
-
"MAILCRAWL_JA_HELPER",
|
|
27960
|
-
"MAILCRAWL_ZH_HELPER"
|
|
27961
|
-
]);
|
|
27962
|
-
REMOTE_EMBEDDING_ENV_KEYS = new Set([
|
|
27963
|
-
"mailcrawl_embedder_base_url",
|
|
27964
|
-
"mailcrawl_embedding_base_url",
|
|
27965
|
-
"mailcrawl_allow_remote_embeddings"
|
|
27966
|
-
]);
|
|
28054
|
+
var DEFAULT_TOP_K5 = 20, DESCRIPTIONS;
|
|
28055
|
+
var init_methods4 = __esm(() => {
|
|
28056
|
+
init_scope2();
|
|
28057
|
+
DESCRIPTIONS = {
|
|
28058
|
+
bm25: { type: "bm25", text: "BM25-ranked FTS5 retrieval", capabilities: ["lexical", "fts5", "bm25"] },
|
|
28059
|
+
semantic: { type: "vector", text: "Semantic (embedding) retrieval", capabilities: ["semantic"] },
|
|
28060
|
+
hybrid: {
|
|
28061
|
+
type: "hybrid",
|
|
28062
|
+
text: "Hybrid BM25 + semantic retrieval (reciprocal rank fusion)",
|
|
28063
|
+
capabilities: ["lexical", "semantic", "hybrid"]
|
|
28064
|
+
}
|
|
28065
|
+
};
|
|
27967
28066
|
});
|
|
27968
28067
|
|
|
27969
|
-
// src/datasource/skills/
|
|
27970
|
-
class
|
|
28068
|
+
// src/datasource/skills/lazykatok/skill.ts
|
|
28069
|
+
class LazykatokSkill {
|
|
27971
28070
|
client;
|
|
27972
|
-
mode;
|
|
27973
28071
|
instanceId;
|
|
28072
|
+
instances;
|
|
28073
|
+
pollingIntervalMs;
|
|
27974
28074
|
tags;
|
|
27975
|
-
|
|
27976
|
-
|
|
27977
|
-
constructor(
|
|
27978
|
-
this.client = client;
|
|
27979
|
-
this.
|
|
27980
|
-
this.
|
|
27981
|
-
this.
|
|
27982
|
-
this.
|
|
27983
|
-
this.
|
|
28075
|
+
sidecar;
|
|
28076
|
+
lastIndexedAt;
|
|
28077
|
+
constructor(options) {
|
|
28078
|
+
this.client = options.client;
|
|
28079
|
+
this.instanceId = options.instanceId ?? DEFAULT_INSTANCE_ID4;
|
|
28080
|
+
this.instances = options.instances !== undefined && options.instances.length > 0 ? options.instances : [this.instanceId];
|
|
28081
|
+
this.pollingIntervalMs = options.pollingIntervalMs ?? DEFAULT_POLLING_INTERVAL_MS3;
|
|
28082
|
+
this.tags = options.tags ?? DEFAULT_KAKAO_TAGS2;
|
|
28083
|
+
this.lastIndexedAt = options.lastIndexedAt;
|
|
28084
|
+
this.sidecar = options.sidecar === undefined ? undefined : new CrawlerSidecar({
|
|
28085
|
+
...options.sidecar,
|
|
28086
|
+
channelIds: options.channelIds ?? options.sidecar.channelIds ?? [],
|
|
28087
|
+
channelNames: options.channelNames ?? options.sidecar.channelNames ?? []
|
|
28088
|
+
});
|
|
27984
28089
|
}
|
|
27985
28090
|
describe() {
|
|
27986
28091
|
return {
|
|
27987
|
-
name:
|
|
27988
|
-
|
|
27989
|
-
|
|
28092
|
+
name: KAKAO_DATASOURCE_ID2,
|
|
28093
|
+
id: KAKAO_DATASOURCE_ID2,
|
|
28094
|
+
type: KAKAO_SKILL_TYPE,
|
|
28095
|
+
description: "KakaoTalk datasource via the external lazykatok CLI",
|
|
28096
|
+
capabilities: ["chat", "external-cli", "polling", "bm25", "semantic"],
|
|
28097
|
+
tags: this.tags,
|
|
27990
28098
|
status: "active",
|
|
27991
|
-
|
|
27992
|
-
datasourceId:
|
|
27993
|
-
|
|
27994
|
-
|
|
27995
|
-
}
|
|
27996
|
-
async retrieve(query, options) {
|
|
27997
|
-
if (!query.trim())
|
|
27998
|
-
return [];
|
|
27999
|
-
const result = await this.client.search(this.mode, query, {
|
|
28000
|
-
topK: options.topK,
|
|
28001
|
-
signal: options.signal
|
|
28002
|
-
});
|
|
28003
|
-
if (!result.ok)
|
|
28004
|
-
throw datasourceCliError("mailcrawl", `search --mode ${this.mode}`, result);
|
|
28005
|
-
return result.hits.filter((hit) => {
|
|
28006
|
-
if (this.account !== undefined && hit.accountId !== this.account)
|
|
28007
|
-
return false;
|
|
28008
|
-
if (this.mailbox !== undefined && hit.mailbox !== this.mailbox)
|
|
28009
|
-
return false;
|
|
28010
|
-
const source = datasourceSourcePath("mailcrawl", this.instanceId, hit.chunkId);
|
|
28011
|
-
return matchesDatasourceScope(source, options.scope);
|
|
28012
|
-
}).map((hit) => {
|
|
28013
|
-
const source = datasourceSourcePath("mailcrawl", this.instanceId, hit.chunkId);
|
|
28014
|
-
return {
|
|
28015
|
-
id: `mailcrawl:${this.instanceId}:${hit.chunkId}`,
|
|
28016
|
-
content: hit.snippet,
|
|
28017
|
-
source,
|
|
28018
|
-
score: hit.score,
|
|
28019
|
-
metadata: {
|
|
28020
|
-
method: `mailcrawl-${this.mode}`,
|
|
28021
|
-
datasourceId: "mailcrawl",
|
|
28022
|
-
instanceId: this.instanceId,
|
|
28023
|
-
mode: this.mode,
|
|
28024
|
-
chunkId: hit.chunkId,
|
|
28025
|
-
messageId: hit.messageId,
|
|
28026
|
-
threadId: hit.threadId,
|
|
28027
|
-
accountId: hit.accountId,
|
|
28028
|
-
mailbox: hit.mailbox,
|
|
28029
|
-
subject: hit.subject,
|
|
28030
|
-
from: hit.from,
|
|
28031
|
-
to: hit.to,
|
|
28032
|
-
date: hit.date
|
|
28033
|
-
}
|
|
28034
|
-
};
|
|
28035
|
-
}).slice(0, options.topK ?? 20);
|
|
28036
|
-
}
|
|
28037
|
-
}
|
|
28038
|
-
var modes, MAILCRAWL_MODES;
|
|
28039
|
-
var init_methods4 = __esm(() => {
|
|
28040
|
-
init_errors4();
|
|
28041
|
-
init_scope2();
|
|
28042
|
-
modes = ["bm25", "semantic", "hybrid"];
|
|
28043
|
-
MAILCRAWL_MODES = modes;
|
|
28044
|
-
});
|
|
28045
|
-
|
|
28046
|
-
// src/datasource/skills/mailcrawl/skill.ts
|
|
28047
|
-
class MailcrawlSkill {
|
|
28048
|
-
client;
|
|
28049
|
-
instanceId;
|
|
28050
|
-
instances;
|
|
28051
|
-
interval;
|
|
28052
|
-
tags;
|
|
28053
|
-
account;
|
|
28054
|
-
mailbox;
|
|
28055
|
-
lastIndexedAt;
|
|
28056
|
-
constructor(options = {}) {
|
|
28057
|
-
const connectorOptions = options.connectorOptions ?? options;
|
|
28058
|
-
this.instanceId = options.instanceId ?? "default";
|
|
28059
|
-
this.instances = options.instances?.length ? options.instances : [this.instanceId];
|
|
28060
|
-
validateMailcrawlInstanceId(this.instanceId);
|
|
28061
|
-
for (const instance of this.instances)
|
|
28062
|
-
validateMailcrawlInstanceId(instance);
|
|
28063
|
-
this.interval = options.pollingIntervalMs ?? 15 * 60 * 1000;
|
|
28064
|
-
this.tags = options.tags ?? TAGS;
|
|
28065
|
-
this.account = connectorOptions.account;
|
|
28066
|
-
this.mailbox = connectorOptions.mailbox;
|
|
28067
|
-
this.client = options.client ?? new MailcrawlClient({ ...connectorOptions, instanceId: this.instanceId });
|
|
28068
|
-
}
|
|
28069
|
-
describe() {
|
|
28070
|
-
return {
|
|
28071
|
-
name: "mailcrawl",
|
|
28072
|
-
id: "mailcrawl",
|
|
28073
|
-
type: "mailcrawl-archive",
|
|
28074
|
-
description: "Local email archive datasource via the external mailcrawl CLI",
|
|
28075
|
-
capabilities: ["email", "external-cli", "polling", "incremental", "bm25", "semantic", "hybrid"],
|
|
28076
|
-
tags: this.tags,
|
|
28077
|
-
status: "active",
|
|
28078
|
-
requiresExternalCli: true,
|
|
28079
|
-
datasourceId: "mailcrawl",
|
|
28080
|
-
instanceId: this.instanceId,
|
|
28081
|
-
instances: this.instances
|
|
28099
|
+
requiresExternalCli: true,
|
|
28100
|
+
datasourceId: KAKAO_DATASOURCE_ID2,
|
|
28101
|
+
instanceId: this.instanceId,
|
|
28102
|
+
instances: this.instances
|
|
28082
28103
|
};
|
|
28083
28104
|
}
|
|
28084
28105
|
polling() {
|
|
28085
28106
|
return {
|
|
28086
|
-
mode:
|
|
28087
|
-
|
|
28107
|
+
mode: "poll",
|
|
28108
|
+
intervalMs: this.pollingIntervalMs,
|
|
28088
28109
|
lastIndexedAt: this.lastIndexedAt
|
|
28089
28110
|
};
|
|
28090
28111
|
}
|
|
28091
28112
|
async index(options) {
|
|
28092
28113
|
try {
|
|
28093
|
-
const
|
|
28094
|
-
if (!
|
|
28095
|
-
return this.fail(
|
|
28096
|
-
|
|
28097
|
-
|
|
28098
|
-
|
|
28099
|
-
if (
|
|
28100
|
-
|
|
28101
|
-
|
|
28102
|
-
|
|
28103
|
-
message: `mailcrawl semantic index unavailable (${indexed.reason}); BM25 remains available`,
|
|
28114
|
+
const doctor = await this.client.doctor(options?.signal);
|
|
28115
|
+
if (!doctor.ok)
|
|
28116
|
+
return this.fail("datasource-unavailable", doctor);
|
|
28117
|
+
const sync = await this.client.sync(options?.signal);
|
|
28118
|
+
if (!sync.ok)
|
|
28119
|
+
return this.fail("datasource-index-failed", sync);
|
|
28120
|
+
if (this.sidecar !== undefined) {
|
|
28121
|
+
this.lastIndexedAt = Date.now();
|
|
28122
|
+
return {
|
|
28123
|
+
ok: true,
|
|
28104
28124
|
instanceId: this.instanceId,
|
|
28105
|
-
|
|
28106
|
-
|
|
28125
|
+
skill: KAKAO_DATASOURCE_ID2,
|
|
28126
|
+
chunkCount: syncChunkCount(sync),
|
|
28127
|
+
indexedAt: this.lastIndexedAt,
|
|
28128
|
+
diagnostics: await this.syncSidecar()
|
|
28129
|
+
};
|
|
28107
28130
|
}
|
|
28131
|
+
const indexResult = await this.client.index(options?.signal);
|
|
28132
|
+
if (!indexResult.ok)
|
|
28133
|
+
return this.fail("datasource-index-failed", indexResult);
|
|
28108
28134
|
this.lastIndexedAt = Date.now();
|
|
28135
|
+
const chunkCount = "data" in indexResult && typeof indexResult.data === "object" && indexResult.data !== null && "chunkCount" in indexResult.data && typeof indexResult.data.chunkCount === "number" ? indexResult.data.chunkCount : 0;
|
|
28109
28136
|
return {
|
|
28110
28137
|
ok: true,
|
|
28111
28138
|
instanceId: this.instanceId,
|
|
28112
|
-
skill:
|
|
28113
|
-
chunkCount
|
|
28139
|
+
skill: KAKAO_DATASOURCE_ID2,
|
|
28140
|
+
chunkCount,
|
|
28114
28141
|
indexedAt: this.lastIndexedAt,
|
|
28115
|
-
diagnostics
|
|
28142
|
+
diagnostics: []
|
|
28116
28143
|
};
|
|
28117
|
-
} catch {
|
|
28118
|
-
return this.fail("datasource-unavailable",
|
|
28144
|
+
} catch (error) {
|
|
28145
|
+
return this.fail("datasource-unavailable", {
|
|
28146
|
+
ok: false,
|
|
28147
|
+
reason: "spawn-error",
|
|
28148
|
+
stdout: "",
|
|
28149
|
+
stderr: describeRetrievalError(error),
|
|
28150
|
+
code: null
|
|
28151
|
+
});
|
|
28119
28152
|
}
|
|
28120
28153
|
}
|
|
28154
|
+
close() {
|
|
28155
|
+
this.sidecar?.close();
|
|
28156
|
+
}
|
|
28121
28157
|
retrievalMethods() {
|
|
28158
|
+
const cliBm25 = new LazykatokBm25Method({ client: this.client, instanceId: this.instanceId, tags: this.tags });
|
|
28159
|
+
const { sidecar } = this;
|
|
28160
|
+
if (sidecar === undefined) {
|
|
28161
|
+
return [
|
|
28162
|
+
cliBm25,
|
|
28163
|
+
new LazykatokSemanticMethod({ client: this.client, instanceId: this.instanceId, tags: this.tags })
|
|
28164
|
+
];
|
|
28165
|
+
}
|
|
28166
|
+
const shared = {
|
|
28167
|
+
sidecar,
|
|
28168
|
+
descriptor: sidecar.descriptor,
|
|
28169
|
+
datasourceId: KAKAO_DATASOURCE_ID2,
|
|
28170
|
+
skillType: KAKAO_SKILL_TYPE,
|
|
28171
|
+
instanceId: this.instanceId,
|
|
28172
|
+
tags: this.tags
|
|
28173
|
+
};
|
|
28174
|
+
const bm25 = new SidecarRetrievalMethod({ ...shared, mode: "bm25" });
|
|
28175
|
+
if (!sidecar.semanticEnabled)
|
|
28176
|
+
return [cliBm25, bm25];
|
|
28122
28177
|
return [
|
|
28123
|
-
|
|
28124
|
-
|
|
28125
|
-
new
|
|
28178
|
+
cliBm25,
|
|
28179
|
+
bm25,
|
|
28180
|
+
new SidecarRetrievalMethod({ ...shared, mode: "semantic" }),
|
|
28181
|
+
new SidecarRetrievalMethod({ ...shared, mode: "hybrid" })
|
|
28126
28182
|
];
|
|
28127
28183
|
}
|
|
28184
|
+
async syncSidecar() {
|
|
28185
|
+
if (this.sidecar === undefined || !this.sidecar.semanticEnabled)
|
|
28186
|
+
return [];
|
|
28187
|
+
const base = { instanceId: this.instanceId, source: KAKAO_DATASOURCE_ID2 };
|
|
28188
|
+
let result;
|
|
28189
|
+
try {
|
|
28190
|
+
result = await this.sidecar.sync();
|
|
28191
|
+
} catch (error) {
|
|
28192
|
+
result = { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
28193
|
+
}
|
|
28194
|
+
if (!result.ok) {
|
|
28195
|
+
return [
|
|
28196
|
+
{
|
|
28197
|
+
code: "datasource-index-failed",
|
|
28198
|
+
severity: "warning",
|
|
28199
|
+
message: `semantic-unavailable: ${result.message}`.slice(0, 300),
|
|
28200
|
+
...base
|
|
28201
|
+
}
|
|
28202
|
+
];
|
|
28203
|
+
}
|
|
28204
|
+
const diagnostics = [];
|
|
28205
|
+
if (result.skipped > 0) {
|
|
28206
|
+
diagnostics.push({
|
|
28207
|
+
code: "datasource-index-partial",
|
|
28208
|
+
severity: "warning",
|
|
28209
|
+
message: `semantic index skipped ${result.skipped} message(s) the embedder rejected; they stay searchable by BM25 and are retried when edited`,
|
|
28210
|
+
...base
|
|
28211
|
+
});
|
|
28212
|
+
}
|
|
28213
|
+
if (result.remaining > 0) {
|
|
28214
|
+
diagnostics.push({
|
|
28215
|
+
code: "datasource-index-partial",
|
|
28216
|
+
severity: "info",
|
|
28217
|
+
message: `semantic index catching up: ${result.embedded} embedded this refresh, ${result.remaining} remaining; hybrid search uses BM25 for the rest until a later refresh`,
|
|
28218
|
+
...base
|
|
28219
|
+
});
|
|
28220
|
+
}
|
|
28221
|
+
return diagnostics;
|
|
28222
|
+
}
|
|
28128
28223
|
describeSources() {
|
|
28129
28224
|
return this.instances.map((instanceId) => ({
|
|
28130
|
-
source: datasourceSourcePath(
|
|
28131
|
-
datasourceId:
|
|
28132
|
-
skill:
|
|
28225
|
+
source: datasourceSourcePath(KAKAO_DATASOURCE_ID2, instanceId),
|
|
28226
|
+
datasourceId: KAKAO_DATASOURCE_ID2,
|
|
28227
|
+
skill: KAKAO_DATASOURCE_ID2,
|
|
28133
28228
|
instanceId,
|
|
28134
|
-
contentType: "
|
|
28135
|
-
metadata: {
|
|
28229
|
+
contentType: "chat",
|
|
28230
|
+
metadata: {
|
|
28231
|
+
datasourceId: KAKAO_DATASOURCE_ID2,
|
|
28232
|
+
instanceId,
|
|
28233
|
+
tags: this.tags
|
|
28234
|
+
}
|
|
28136
28235
|
}));
|
|
28137
28236
|
}
|
|
28138
28237
|
skillManifest() {
|
|
28139
|
-
const
|
|
28238
|
+
const instanceSources = this.instances.map((instanceId) => `- \`${datasourceSourcePath(KAKAO_DATASOURCE_ID2, instanceId)}\``).join(`
|
|
28140
28239
|
`);
|
|
28240
|
+
const cadence = this.pollingIntervalMs > 0 ? `roughly every ${Math.round(this.pollingIntervalMs / 60000)} minute(s) when auto-refresh runs` : "on manual refresh only";
|
|
28141
28241
|
return {
|
|
28142
|
-
name:
|
|
28143
|
-
description: "Search
|
|
28144
|
-
content:
|
|
28145
|
-
|
|
28146
|
-
|
|
28147
|
-
|
|
28148
|
-
|
|
28149
|
-
|
|
28150
|
-
|
|
28151
|
-
|
|
28152
|
-
|
|
28153
|
-
${
|
|
28154
|
-
|
|
28155
|
-
|
|
28156
|
-
|
|
28157
|
-
|
|
28158
|
-
|
|
28159
|
-
|
|
28160
|
-
|
|
28161
|
-
|
|
28162
|
-
|
|
28163
|
-
|
|
28164
|
-
|
|
28165
|
-
|
|
28166
|
-
|
|
28167
|
-
|
|
28168
|
-
|
|
28169
|
-
|
|
28170
|
-
|
|
28171
|
-
|
|
28172
|
-
|
|
28173
|
-
|
|
28174
|
-
|
|
28175
|
-
|
|
28176
|
-
|
|
28177
|
-
|
|
28178
|
-
|
|
28179
|
-
code,
|
|
28180
|
-
message
|
|
28181
|
-
|
|
28182
|
-
|
|
28183
|
-
}
|
|
28184
|
-
|
|
28185
|
-
|
|
28186
|
-
|
|
28187
|
-
|
|
28188
|
-
|
|
28242
|
+
name: `datasource-${KAKAO_DATASOURCE_ID2}`,
|
|
28243
|
+
description: "Search indexed KakaoTalk chat history (messages, senders, room context). Use for questions about KakaoTalk conversations, decisions made in chats, or who said what.",
|
|
28244
|
+
content: [
|
|
28245
|
+
`# KakaoTalk datasource (${KAKAO_SKILL_TYPE})`,
|
|
28246
|
+
"",
|
|
28247
|
+
"This skill searches KakaoTalk chats that are indexed through the external `lazykatok` CLI. AutoRAG never reads KakaoTalk databases directly.",
|
|
28248
|
+
"",
|
|
28249
|
+
"## When to use",
|
|
28250
|
+
"Use this skill when the question is about KakaoTalk conversations, chat participants, or content shared inside chats.",
|
|
28251
|
+
"",
|
|
28252
|
+
"## Indexing",
|
|
28253
|
+
`Indexing is server-managed and refreshed ${cadence}. You do not trigger indexing; just search.`,
|
|
28254
|
+
"",
|
|
28255
|
+
"## How to search",
|
|
28256
|
+
`Call the dedicated \`${datasourceSearchToolName(KAKAO_DATASOURCE_ID2)}\` tool with a natural-language \`query\` and \`topK\`. This datasource does not support per-source scope narrowing. Configured datasource instances:`,
|
|
28257
|
+
instanceSources.length > 0 ? instanceSources : "- (no configured instances)",
|
|
28258
|
+
"",
|
|
28259
|
+
"Chat/channel filtering is owned by lazykatok.",
|
|
28260
|
+
"",
|
|
28261
|
+
"## Native CLI",
|
|
28262
|
+
"The external `lazykatok` CLI owns the archive, index, and credentials. When the dedicated tool cannot express what you need, call lazykatok directly through `bash`:",
|
|
28263
|
+
'- `lazykatok search bm25 "<query>" --json --limit 20` — lexical search (`semantic` mode also available)',
|
|
28264
|
+
"- `lazykatok chunk get <chunkId> --json` — fetch one chunk by id (chunk ids appear in result metadata)",
|
|
28265
|
+
"- `lazykatok chunk context <chunkId> --json` — surrounding messages of a chunk",
|
|
28266
|
+
"Never pass datasource virtual paths (`/kakao/...`) to bash; they are not OS paths.",
|
|
28267
|
+
"",
|
|
28268
|
+
"## Output rules",
|
|
28269
|
+
"Datasource source identifiers such as `/kakao/<instance>/chunks/<id>` are internal and opaque. Never put them, real file paths, account IDs, or phone numbers in the visible answer."
|
|
28270
|
+
].join(`
|
|
28271
|
+
`)
|
|
28272
|
+
};
|
|
28273
|
+
}
|
|
28274
|
+
fail(code, result) {
|
|
28275
|
+
const base = result.stderr.length > 0 ? `${result.reason}: ${result.stderr.trim().slice(0, 4000)}` : result.reason;
|
|
28276
|
+
const message = code === "datasource-index-failed" && result.reason === "timeout" ? `${base} — ${connectorSyncTimeoutHint()}` : base;
|
|
28277
|
+
const diagnostic = {
|
|
28278
|
+
code,
|
|
28279
|
+
severity: code === "datasource-unavailable" ? "warning" : "error",
|
|
28280
|
+
message,
|
|
28281
|
+
instanceId: this.instanceId,
|
|
28282
|
+
source: KAKAO_DATASOURCE_ID2
|
|
28283
|
+
};
|
|
28284
|
+
return {
|
|
28285
|
+
ok: false,
|
|
28286
|
+
instanceId: this.instanceId,
|
|
28287
|
+
skill: KAKAO_DATASOURCE_ID2,
|
|
28288
|
+
indexedAt: Date.now(),
|
|
28289
|
+
diagnostics: [diagnostic],
|
|
28290
|
+
error: result.reason,
|
|
28291
|
+
code,
|
|
28292
|
+
message
|
|
28293
|
+
};
|
|
28294
|
+
}
|
|
28295
|
+
}
|
|
28296
|
+
function syncChunkCount(sync) {
|
|
28297
|
+
if (!("data" in sync) || typeof sync.data !== "object" || sync.data === null)
|
|
28298
|
+
return 0;
|
|
28299
|
+
const meta = "metadata" in sync.data && typeof sync.data.metadata === "object" && sync.data.metadata !== null ? sync.data.metadata : undefined;
|
|
28300
|
+
if (typeof meta?.chunks === "number")
|
|
28301
|
+
return meta.chunks;
|
|
28302
|
+
if ("messageCount" in sync.data && typeof sync.data.messageCount === "number")
|
|
28303
|
+
return sync.data.messageCount;
|
|
28304
|
+
return 0;
|
|
28305
|
+
}
|
|
28306
|
+
var KAKAO_DATASOURCE_ID2 = "kakao", KAKAO_SKILL_TYPE = "kakaotalk", DEFAULT_INSTANCE_ID4 = "default", DEFAULT_POLLING_INTERVAL_MS3, DEFAULT_KAKAO_TAGS2;
|
|
28307
|
+
var init_skill7 = __esm(() => {
|
|
28308
|
+
init_skip();
|
|
28309
|
+
init_scope2();
|
|
28310
|
+
init_sidecar();
|
|
28189
28311
|
init_methods4();
|
|
28190
|
-
|
|
28312
|
+
init_methods3();
|
|
28313
|
+
DEFAULT_POLLING_INTERVAL_MS3 = 15 * 60 * 1000;
|
|
28314
|
+
DEFAULT_KAKAO_TAGS2 = ["kakaotalk", "personal", "pii"];
|
|
28191
28315
|
});
|
|
28192
28316
|
|
|
28193
|
-
// src/datasource/skills/
|
|
28194
|
-
var
|
|
28195
|
-
|
|
28196
|
-
|
|
28197
|
-
|
|
28317
|
+
// src/datasource/skills/lazykatok/index.ts
|
|
28318
|
+
var init_lazykatok = __esm(() => {
|
|
28319
|
+
init_client8();
|
|
28320
|
+
init_methods3();
|
|
28321
|
+
init_skill7();
|
|
28322
|
+
init_types5();
|
|
28198
28323
|
});
|
|
28199
28324
|
|
|
28200
|
-
// src/datasource/
|
|
28201
|
-
|
|
28202
|
-
|
|
28203
|
-
|
|
28204
|
-
|
|
28205
|
-
|
|
28206
|
-
|
|
28207
|
-
|
|
28208
|
-
|
|
28209
|
-
}
|
|
28210
|
-
function cosine3(a, b) {
|
|
28211
|
-
if (a.length === 0 || a.length !== b.length)
|
|
28212
|
-
return 0;
|
|
28213
|
-
let dot = 0;
|
|
28214
|
-
let normA = 0;
|
|
28215
|
-
let normB = 0;
|
|
28216
|
-
for (let index = 0;index < a.length; index += 1) {
|
|
28217
|
-
const x = a[index] ?? 0;
|
|
28218
|
-
const y = b[index] ?? 0;
|
|
28219
|
-
dot += x * y;
|
|
28220
|
-
normA += x * x;
|
|
28221
|
-
normB += y * y;
|
|
28325
|
+
// src/datasource/skills/mail-export/connector.ts
|
|
28326
|
+
import { readdir as readdir4, readFile as readFile5, stat as stat6 } from "node:fs/promises";
|
|
28327
|
+
import { basename as basename12, dirname as dirname25, extname as extname6, join as join37 } from "node:path";
|
|
28328
|
+
import { simpleParser as simpleParser2 } from "mailparser";
|
|
28329
|
+
|
|
28330
|
+
class MailExportConnector {
|
|
28331
|
+
options;
|
|
28332
|
+
constructor(options = {}) {
|
|
28333
|
+
this.options = options;
|
|
28222
28334
|
}
|
|
28223
|
-
|
|
28224
|
-
|
|
28225
|
-
|
|
28226
|
-
}
|
|
28227
|
-
|
|
28228
|
-
|
|
28229
|
-
|
|
28230
|
-
|
|
28231
|
-
|
|
28232
|
-
|
|
28233
|
-
|
|
28234
|
-
|
|
28335
|
+
async fetch() {
|
|
28336
|
+
const paths = this.options.paths ?? [];
|
|
28337
|
+
if (paths.length === 0)
|
|
28338
|
+
return { ok: false, reason: "not-configured", message: "no mail export paths configured" };
|
|
28339
|
+
const maxDocuments = this.options.maxDocuments ?? DEFAULT_MAX_DOCUMENTS3;
|
|
28340
|
+
const maxBytes = this.options.maxBytesPerFile ?? DEFAULT_MAX_BYTES_PER_FILE2;
|
|
28341
|
+
const files = [];
|
|
28342
|
+
let missingPaths = 0;
|
|
28343
|
+
for (const path of paths) {
|
|
28344
|
+
try {
|
|
28345
|
+
const info = await stat6(path);
|
|
28346
|
+
if (info.isDirectory()) {
|
|
28347
|
+
const entries = await readdir4(path, { recursive: true, withFileTypes: true });
|
|
28348
|
+
for (const entry of entries) {
|
|
28349
|
+
if (!entry.isFile())
|
|
28350
|
+
continue;
|
|
28351
|
+
const extension = extname6(entry.name).toLowerCase();
|
|
28352
|
+
if (extension === ".eml" || extension === ".mbox") {
|
|
28353
|
+
files.push(join37(entry.parentPath, entry.name));
|
|
28354
|
+
}
|
|
28355
|
+
}
|
|
28356
|
+
} else if (info.isFile()) {
|
|
28357
|
+
files.push(path);
|
|
28358
|
+
}
|
|
28359
|
+
} catch {
|
|
28360
|
+
missingPaths += 1;
|
|
28361
|
+
}
|
|
28362
|
+
}
|
|
28363
|
+
if (missingPaths === paths.length) {
|
|
28364
|
+
return { ok: false, reason: "unavailable", message: "no configured mail export paths were readable" };
|
|
28365
|
+
}
|
|
28366
|
+
const documents = [];
|
|
28367
|
+
const warnings = [];
|
|
28368
|
+
if (missingPaths > 0)
|
|
28369
|
+
warnings.push(`${missingPaths} configured path(s) were unreadable`);
|
|
28370
|
+
let parseFailures = 0;
|
|
28371
|
+
let skippedLarge = 0;
|
|
28372
|
+
for (const file of files.sort()) {
|
|
28373
|
+
if (documents.length >= maxDocuments)
|
|
28374
|
+
break;
|
|
28375
|
+
let bytes;
|
|
28376
|
+
try {
|
|
28377
|
+
const info = await stat6(file);
|
|
28378
|
+
if (info.size > maxBytes) {
|
|
28379
|
+
skippedLarge += 1;
|
|
28380
|
+
continue;
|
|
28381
|
+
}
|
|
28382
|
+
bytes = await readFile5(file);
|
|
28383
|
+
} catch {
|
|
28384
|
+
parseFailures += 1;
|
|
28385
|
+
continue;
|
|
28386
|
+
}
|
|
28387
|
+
const stem = basename12(file, extname6(file));
|
|
28388
|
+
const mailboxDir = basename12(dirname25(file));
|
|
28389
|
+
const hierarchy = ["mailboxes", mailboxDir, stem];
|
|
28390
|
+
const extension = extname6(file).toLowerCase();
|
|
28391
|
+
const rawMessages = extension === ".mbox" ? splitMbox(bytes.toString("utf8")) : [bytes.toString("utf8")];
|
|
28392
|
+
for (const [index, raw] of rawMessages.entries()) {
|
|
28393
|
+
if (documents.length >= maxDocuments)
|
|
28394
|
+
break;
|
|
28395
|
+
try {
|
|
28396
|
+
const parsed = await simpleParser2(raw);
|
|
28397
|
+
const subject = parsed.subject ?? "(no subject)";
|
|
28398
|
+
const from = Array.isArray(parsed.from) ? parsed.from[0]?.text : parsed.from?.text;
|
|
28399
|
+
const to = Array.isArray(parsed.to) ? parsed.to[0]?.text : parsed.to?.text;
|
|
28400
|
+
const body = (parsed.text ?? "").trim().slice(0, MAX_BODY_CHARS);
|
|
28401
|
+
const headerBlock = [
|
|
28402
|
+
`Subject: ${subject}`,
|
|
28403
|
+
...from !== undefined ? [`From: ${from}`] : [],
|
|
28404
|
+
...to !== undefined ? [`To: ${to}`] : [],
|
|
28405
|
+
...parsed.date !== undefined ? [`Date: ${parsed.date.toISOString()}`] : []
|
|
28406
|
+
].join(`
|
|
28407
|
+
`);
|
|
28408
|
+
const content = `${headerBlock}
|
|
28409
|
+
|
|
28410
|
+
${body}`.trim();
|
|
28411
|
+
if (content.length === 0)
|
|
28412
|
+
continue;
|
|
28413
|
+
documents.push({
|
|
28414
|
+
docId: extension === ".mbox" ? `${stem}-${index + 1}` : stem,
|
|
28415
|
+
hierarchy,
|
|
28416
|
+
title: subject,
|
|
28417
|
+
content,
|
|
28418
|
+
...parsed.date !== undefined ? { publishedAt: parsed.date.getTime() } : {},
|
|
28419
|
+
metadata: {
|
|
28420
|
+
path: file,
|
|
28421
|
+
subject,
|
|
28422
|
+
...from !== undefined ? { from } : {},
|
|
28423
|
+
messageIndex: index
|
|
28424
|
+
}
|
|
28425
|
+
});
|
|
28426
|
+
} catch {
|
|
28427
|
+
parseFailures += 1;
|
|
28428
|
+
}
|
|
28429
|
+
}
|
|
28430
|
+
}
|
|
28431
|
+
if (skippedLarge > 0)
|
|
28432
|
+
warnings.push(`${skippedLarge} file(s) exceeded the size limit and were skipped`);
|
|
28433
|
+
if (parseFailures > 0)
|
|
28434
|
+
warnings.push(`${parseFailures} message(s) failed to parse`);
|
|
28435
|
+
return { ok: true, documents, ...warnings.length > 0 ? { warnings } : {} };
|
|
28235
28436
|
}
|
|
28236
|
-
scored.sort((a, b) => b.score - a.score || a.key.localeCompare(b.key));
|
|
28237
|
-
return scored.slice(0, k);
|
|
28238
28437
|
}
|
|
28239
|
-
function
|
|
28240
|
-
const
|
|
28241
|
-
|
|
28242
|
-
|
|
28243
|
-
|
|
28244
|
-
|
|
28245
|
-
|
|
28246
|
-
|
|
28247
|
-
|
|
28438
|
+
function splitMbox(text) {
|
|
28439
|
+
const lines = text.split(/\r?\n/u);
|
|
28440
|
+
const messages = [];
|
|
28441
|
+
let current = [];
|
|
28442
|
+
for (const line of lines) {
|
|
28443
|
+
if (MBOX_SEPARATOR.test(line) && line.startsWith("From ")) {
|
|
28444
|
+
if (current.length > 0)
|
|
28445
|
+
messages.push(current.join(`
|
|
28446
|
+
`));
|
|
28447
|
+
current = [];
|
|
28448
|
+
continue;
|
|
28248
28449
|
}
|
|
28450
|
+
current.push(line);
|
|
28249
28451
|
}
|
|
28250
|
-
|
|
28452
|
+
if (current.length > 0)
|
|
28453
|
+
messages.push(current.join(`
|
|
28454
|
+
`));
|
|
28455
|
+
return messages.filter((message) => message.trim().length > 0);
|
|
28251
28456
|
}
|
|
28252
|
-
var
|
|
28457
|
+
var DEFAULT_MAX_DOCUMENTS3 = 500, DEFAULT_MAX_BYTES_PER_FILE2, MAX_BODY_CHARS = 50000, MBOX_SEPARATOR;
|
|
28458
|
+
var init_connector4 = __esm(() => {
|
|
28459
|
+
DEFAULT_MAX_BYTES_PER_FILE2 = 50 * 1024 * 1024;
|
|
28460
|
+
MBOX_SEPARATOR = /^From .*\d{4}$/mu;
|
|
28461
|
+
});
|
|
28253
28462
|
|
|
28254
|
-
// src/datasource/
|
|
28255
|
-
|
|
28256
|
-
|
|
28257
|
-
|
|
28258
|
-
|
|
28259
|
-
|
|
28463
|
+
// src/datasource/skills/mail-export/skill.ts
|
|
28464
|
+
var MAIL_EXPORT_SKILL_DEFINITION, MailExportSkill;
|
|
28465
|
+
var init_skill8 = __esm(() => {
|
|
28466
|
+
init_connector_skill();
|
|
28467
|
+
init_connector4();
|
|
28468
|
+
MAIL_EXPORT_SKILL_DEFINITION = {
|
|
28469
|
+
skillName: "mail-export",
|
|
28470
|
+
skillType: "mail-export",
|
|
28471
|
+
description: "Local mail export datasource",
|
|
28472
|
+
capabilities: ["email", "filesystem"],
|
|
28473
|
+
defaultTags: ["mail-export", "email", "pii"],
|
|
28474
|
+
contentType: "email",
|
|
28475
|
+
manifestDescription: "Search indexed local mail exports (mbox/eml archives). Use for questions about archived email conversations.",
|
|
28476
|
+
manifestNotes: [
|
|
28477
|
+
"Only the server-configured export paths are indexed; archives are read locally and never uploaded."
|
|
28478
|
+
]
|
|
28479
|
+
};
|
|
28480
|
+
MailExportSkill = class MailExportSkill extends ConnectorDatasourceSkill {
|
|
28481
|
+
constructor(options = {}) {
|
|
28482
|
+
const { connector, connectorOptions, ...rest } = options;
|
|
28483
|
+
super(MAIL_EXPORT_SKILL_DEFINITION, {
|
|
28484
|
+
...rest,
|
|
28485
|
+
connector: connector ?? new MailExportConnector(connectorOptions ?? {})
|
|
28486
|
+
});
|
|
28487
|
+
}
|
|
28488
|
+
};
|
|
28489
|
+
});
|
|
28260
28490
|
|
|
28261
|
-
|
|
28262
|
-
|
|
28491
|
+
// src/datasource/skills/mail-export/index.ts
|
|
28492
|
+
var init_mail_export = __esm(() => {
|
|
28493
|
+
init_connector4();
|
|
28494
|
+
init_skill8();
|
|
28495
|
+
});
|
|
28496
|
+
|
|
28497
|
+
// src/datasource/skills/mailcrawl/config.ts
|
|
28498
|
+
import { chmodSync, mkdirSync as mkdirSync23 } from "node:fs";
|
|
28499
|
+
function validateMailcrawlInstanceId(instanceId) {
|
|
28500
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(instanceId)) {
|
|
28501
|
+
throw new Error("mailcrawl instanceId must be a safe single path segment");
|
|
28502
|
+
}
|
|
28503
|
+
}
|
|
28504
|
+
function ensurePrivateMailcrawlDataDir(path) {
|
|
28505
|
+
mkdirSync23(path, { recursive: true, mode: 448 });
|
|
28506
|
+
if (process.platform !== "win32")
|
|
28507
|
+
chmodSync(path, 448);
|
|
28508
|
+
}
|
|
28509
|
+
var init_config3 = () => {};
|
|
28510
|
+
|
|
28511
|
+
// src/datasource/skills/mailcrawl/client.ts
|
|
28512
|
+
import { spawn as spawn15 } from "node:child_process";
|
|
28513
|
+
|
|
28514
|
+
class MailcrawlClient {
|
|
28263
28515
|
options;
|
|
28264
|
-
|
|
28265
|
-
where;
|
|
28266
|
-
opened;
|
|
28267
|
-
attached;
|
|
28268
|
-
lastSemantic;
|
|
28269
|
-
constructor(options) {
|
|
28516
|
+
constructor(options = {}) {
|
|
28270
28517
|
this.options = options;
|
|
28271
|
-
|
|
28272
|
-
|
|
28273
|
-
const metadataColumns = descriptor.metadata.map((column, index) => `${column.sql} as m${index}`);
|
|
28274
|
-
this.select = [
|
|
28275
|
-
`${descriptor.keyExpression} as k`,
|
|
28276
|
-
`${descriptor.textExpression} as content`,
|
|
28277
|
-
`${descriptor.titleExpression ?? "null"} as title`,
|
|
28278
|
-
`${descriptor.epochSecondsExpression ?? "null"} as epoch`,
|
|
28279
|
-
...metadataColumns
|
|
28280
|
-
].join(", ");
|
|
28281
|
-
const scope = descriptor.scopeExpression !== undefined && options.workspaceId !== undefined;
|
|
28282
|
-
const allowlist = [
|
|
28283
|
-
...(options.channelIds?.length ?? 0) > 0 ? [`${descriptor.channelIdExpression} in (${options.channelIds?.map(() => "?").join(", ")})`] : [],
|
|
28284
|
-
...(options.channelNames?.length ?? 0) > 0 ? [`${descriptor.channelNameExpression} in (${options.channelNames?.map(() => "?").join(", ")})`] : []
|
|
28285
|
-
];
|
|
28286
|
-
this.where = [
|
|
28287
|
-
descriptor.liveWhere,
|
|
28288
|
-
...scope ? [`${descriptor.scopeExpression} = ?`] : [],
|
|
28289
|
-
...allowlist.length > 0 ? [`(${allowlist.join(" or ")})`] : []
|
|
28290
|
-
].join(" and ");
|
|
28518
|
+
if (options.instanceId !== undefined)
|
|
28519
|
+
validateMailcrawlInstanceId(options.instanceId);
|
|
28291
28520
|
}
|
|
28292
|
-
|
|
28293
|
-
|
|
28294
|
-
|
|
28295
|
-
|
|
28296
|
-
|
|
28297
|
-
|
|
28298
|
-
|
|
28299
|
-
|
|
28300
|
-
|
|
28301
|
-
|
|
28302
|
-
|
|
28303
|
-
this.
|
|
28304
|
-
|
|
28521
|
+
async sync(signal) {
|
|
28522
|
+
const args = [
|
|
28523
|
+
"sync",
|
|
28524
|
+
"--json",
|
|
28525
|
+
...this.options.account === undefined ? [] : ["--account", this.options.account],
|
|
28526
|
+
...this.options.mailbox === undefined ? [] : ["--mailbox", this.options.mailbox],
|
|
28527
|
+
...this.options.backend === undefined ? [] : ["--backend", this.options.backend],
|
|
28528
|
+
...this.options.source === undefined ? [] : ["--source", this.options.source],
|
|
28529
|
+
...this.options.fixture === undefined ? [] : ["--fixture", this.options.fixture],
|
|
28530
|
+
...this.options.himalayaConfig === undefined ? [] : ["--himalaya-config", this.options.himalayaConfig]
|
|
28531
|
+
];
|
|
28532
|
+
const result = await this.run(args, signal, this.indexTimeoutMs());
|
|
28533
|
+
if (!result.ok)
|
|
28534
|
+
return failure3(result);
|
|
28535
|
+
const parsed = parseObject2(result.stdout);
|
|
28536
|
+
if (parsed === undefined || !hasAnyNumber(parsed, ["added", "updated", "deleted", "unchanged", "chunksAdded"])) {
|
|
28537
|
+
return failure3({ ...result, ok: false, reason: "invalid-output" });
|
|
28538
|
+
}
|
|
28539
|
+
const messages = number(parsed, "added") ?? number(parsed, "updated") ?? number(parsed, "unchanged") ?? 0;
|
|
28540
|
+
return ok4({
|
|
28541
|
+
messages,
|
|
28542
|
+
added: number(parsed, "added"),
|
|
28543
|
+
updated: number(parsed, "updated"),
|
|
28544
|
+
deleted: number(parsed, "deleted"),
|
|
28545
|
+
unchanged: number(parsed, "unchanged"),
|
|
28546
|
+
chunksAdded: number(parsed, "chunksAdded"),
|
|
28547
|
+
archiveRevision: string(parsed, "archiveRevision")
|
|
28548
|
+
}, result);
|
|
28305
28549
|
}
|
|
28306
|
-
|
|
28307
|
-
|
|
28308
|
-
|
|
28309
|
-
|
|
28310
|
-
|
|
28311
|
-
|
|
28312
|
-
|
|
28313
|
-
|
|
28550
|
+
async search(mode, query, options) {
|
|
28551
|
+
if (query.trim().length === 0)
|
|
28552
|
+
return { ok: true, hits: [], stdout: "", stderr: "", code: 0 };
|
|
28553
|
+
const args = [
|
|
28554
|
+
"search",
|
|
28555
|
+
"--mode",
|
|
28556
|
+
mode === "keyword" ? "bm25" : mode,
|
|
28557
|
+
...this.options.account === undefined ? [] : ["--account", this.options.account],
|
|
28558
|
+
...this.options.mailbox === undefined ? [] : ["--mailbox", this.options.mailbox],
|
|
28559
|
+
"--limit",
|
|
28560
|
+
String(options?.topK ?? 20),
|
|
28561
|
+
"--json",
|
|
28562
|
+
query.trim()
|
|
28563
|
+
];
|
|
28564
|
+
const result = await this.run(args, options?.signal, this.options.timeoutMs ?? DEFAULT_SEARCH_TIMEOUT_MS2);
|
|
28565
|
+
if (!result.ok)
|
|
28566
|
+
return failure3(result);
|
|
28567
|
+
const parsed = parseJson3(result.stdout);
|
|
28568
|
+
if (parsed === undefined)
|
|
28569
|
+
return failure3({ ...result, ok: false, reason: "invalid-output" });
|
|
28570
|
+
const rows = Array.isArray(parsed) ? parsed : objectArray(parsed, ["hits", "results", "items"]);
|
|
28571
|
+
if (rows === undefined)
|
|
28572
|
+
return failure3({ ...result, ok: false, reason: "invalid-output" });
|
|
28573
|
+
const hits = rows.map((row, index) => normalizeHit2(row, mode, index));
|
|
28574
|
+
if (hits.some((hit) => hit === undefined)) {
|
|
28575
|
+
return failure3({ ...result, ok: false, reason: "invalid-output" });
|
|
28314
28576
|
}
|
|
28315
|
-
|
|
28316
|
-
|
|
28317
|
-
|
|
28318
|
-
|
|
28577
|
+
return {
|
|
28578
|
+
ok: true,
|
|
28579
|
+
hits,
|
|
28580
|
+
stdout: result.stdout,
|
|
28581
|
+
stderr: result.stderr,
|
|
28582
|
+
code: result.code ?? 0
|
|
28583
|
+
};
|
|
28584
|
+
}
|
|
28585
|
+
indexTimeoutMs() {
|
|
28586
|
+
return this.options.indexTimeoutMs ?? this.options.timeoutMs ?? DEFAULT_INDEX_TIMEOUT_MS3;
|
|
28587
|
+
}
|
|
28588
|
+
async run(args, signal, timeoutMs) {
|
|
28589
|
+
const violation = findRemoteEmbeddingViolation({ ...process.env, ...this.options.env ?? {} });
|
|
28590
|
+
if (violation !== undefined) {
|
|
28591
|
+
return { ok: false, reason: "remote-embedding-rejected", stdout: "", stderr: "", code: null };
|
|
28319
28592
|
}
|
|
28320
|
-
const
|
|
28321
|
-
|
|
28322
|
-
|
|
28323
|
-
|
|
28324
|
-
|
|
28593
|
+
const dataDir = this.options.dataDir;
|
|
28594
|
+
if (dataDir !== undefined)
|
|
28595
|
+
ensurePrivateMailcrawlDataDir(dataDir);
|
|
28596
|
+
const env = {};
|
|
28597
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
28598
|
+
if (value !== undefined && (SAFE_ENV_KEYS2.has(key) || SAFE_MAILCRAWL_ENV_KEYS.has(key)))
|
|
28599
|
+
env[key] = value;
|
|
28325
28600
|
}
|
|
28326
|
-
const
|
|
28327
|
-
|
|
28328
|
-
|
|
28329
|
-
|
|
28330
|
-
|
|
28331
|
-
this.db.exec("detach database crawler");
|
|
28332
|
-
return false;
|
|
28601
|
+
for (const [key, value] of Object.entries(this.options.env ?? {})) {
|
|
28602
|
+
if (value === undefined)
|
|
28603
|
+
delete env[key];
|
|
28604
|
+
else if (SAFE_ENV_KEYS2.has(key) || SAFE_MAILCRAWL_ENV_KEYS.has(key))
|
|
28605
|
+
env[key] = value;
|
|
28333
28606
|
}
|
|
28334
|
-
|
|
28335
|
-
|
|
28336
|
-
return
|
|
28337
|
-
}
|
|
28338
|
-
detachCrawler() {
|
|
28339
|
-
if (this.attached === undefined)
|
|
28340
|
-
return;
|
|
28341
|
-
this.attached = undefined;
|
|
28342
|
-
this.lastSemantic = undefined;
|
|
28343
|
-
try {
|
|
28344
|
-
this.opened?.exec("detach database crawler");
|
|
28345
|
-
} catch {}
|
|
28346
|
-
}
|
|
28347
|
-
get semanticEnabled() {
|
|
28348
|
-
return this.options.embedder !== undefined;
|
|
28349
|
-
}
|
|
28350
|
-
close() {
|
|
28351
|
-
this.opened?.close();
|
|
28352
|
-
this.opened = undefined;
|
|
28353
|
-
this.attached = undefined;
|
|
28354
|
-
this.lastSemantic = undefined;
|
|
28607
|
+
if (dataDir !== undefined)
|
|
28608
|
+
env.MAILCRAWL_DATA_DIR = dataDir;
|
|
28609
|
+
return spawnMailcrawl(this.options.binaryPath ?? DEFAULT_BINARY4, args, env, this.options, signal, timeoutMs);
|
|
28355
28610
|
}
|
|
28356
|
-
|
|
28357
|
-
const
|
|
28358
|
-
|
|
28359
|
-
|
|
28360
|
-
|
|
28361
|
-
|
|
28362
|
-
|
|
28611
|
+
async index(signal) {
|
|
28612
|
+
const result = await this.run(["index", "--json"], signal, this.indexTimeoutMs());
|
|
28613
|
+
if (!result.ok)
|
|
28614
|
+
return failure3(result);
|
|
28615
|
+
const parsed = parseObject2(result.stdout);
|
|
28616
|
+
if (parsed === undefined || number(parsed, "embedded") === undefined && number(parsed, "reused") === undefined && string(parsed, "generation") === undefined) {
|
|
28617
|
+
return failure3({ ...result, ok: false, reason: "invalid-output" });
|
|
28618
|
+
}
|
|
28619
|
+
return ok4({
|
|
28620
|
+
embedded: number(parsed, "embedded"),
|
|
28621
|
+
reused: number(parsed, "reused"),
|
|
28622
|
+
generation: string(parsed, "generation"),
|
|
28623
|
+
archiveRevision: string(parsed, "archiveRevision"),
|
|
28624
|
+
rebuilt: typeof parsed.rebuilt === "boolean" ? parsed.rebuilt : undefined,
|
|
28625
|
+
embedder: string(parsed, "embedder")
|
|
28626
|
+
}, result);
|
|
28363
28627
|
}
|
|
28364
|
-
|
|
28365
|
-
|
|
28366
|
-
|
|
28367
|
-
|
|
28368
|
-
const
|
|
28369
|
-
|
|
28370
|
-
|
|
28371
|
-
|
|
28372
|
-
|
|
28373
|
-
|
|
28374
|
-
|
|
28375
|
-
|
|
28376
|
-
|
|
28377
|
-
|
|
28378
|
-
|
|
28379
|
-
const
|
|
28380
|
-
|
|
28381
|
-
|
|
28382
|
-
const live = this.hydrate(ranked.map((entry) => entry.key));
|
|
28383
|
-
const hits = [];
|
|
28384
|
-
for (const entry of ranked) {
|
|
28385
|
-
const row = live.get(entry.key);
|
|
28386
|
-
if (row !== undefined)
|
|
28387
|
-
hits.push(this.toHit(row, entry.score));
|
|
28388
|
-
if (hits.length >= topK)
|
|
28389
|
-
break;
|
|
28390
|
-
}
|
|
28391
|
-
return hits;
|
|
28392
|
-
}
|
|
28393
|
-
async rankSemantic(query, pool) {
|
|
28394
|
-
const now = Date.now();
|
|
28395
|
-
let cached = this.lastSemantic;
|
|
28396
|
-
if (cached === undefined || cached.query !== query || now - cached.at > SEMANTIC_REUSE_MS) {
|
|
28397
|
-
const embedder = this.options.embedder;
|
|
28398
|
-
if (embedder === undefined)
|
|
28399
|
-
return [];
|
|
28400
|
-
cached = {
|
|
28401
|
-
query,
|
|
28402
|
-
at: now,
|
|
28403
|
-
vector: embedder.embed([query]).then((vectors) => vectors[0], () => {
|
|
28404
|
-
return;
|
|
28405
|
-
}),
|
|
28406
|
-
ranked: undefined
|
|
28407
|
-
};
|
|
28408
|
-
this.lastSemantic = cached;
|
|
28409
|
-
}
|
|
28410
|
-
const vector = await cached.vector;
|
|
28411
|
-
if (vector === undefined) {
|
|
28412
|
-
if (this.lastSemantic === cached)
|
|
28413
|
-
this.lastSemantic = undefined;
|
|
28414
|
-
return [];
|
|
28415
|
-
}
|
|
28416
|
-
if (cached.ranked === undefined || cached.ranked.pool < pool) {
|
|
28417
|
-
const wide = pool * POOL_FACTOR;
|
|
28418
|
-
cached.ranked = { pool: wide, keys: topKByCosine(vector, this.storedVectors(), wide) };
|
|
28419
|
-
}
|
|
28420
|
-
return cached.ranked.keys.slice(0, pool);
|
|
28421
|
-
}
|
|
28422
|
-
async searchHybrid(query, topK) {
|
|
28423
|
-
if (topK <= 0)
|
|
28424
|
-
return [];
|
|
28425
|
-
const pool = topK * POOL_FACTOR;
|
|
28426
|
-
const lexical = this.searchLexical(query, pool);
|
|
28427
|
-
const semantic = await this.searchSemantic(query, pool);
|
|
28428
|
-
if (semantic.length === 0)
|
|
28429
|
-
return lexical.slice(0, topK);
|
|
28430
|
-
const byKey = new Map;
|
|
28431
|
-
for (const hit of [...semantic, ...lexical])
|
|
28432
|
-
byKey.set(hit.key, hit);
|
|
28433
|
-
const fused = [];
|
|
28434
|
-
for (const entry of reciprocalRankFusion([lexical.map((hit) => hit.key), semantic.map((hit) => hit.key)])) {
|
|
28435
|
-
const hit = byKey.get(entry.key);
|
|
28436
|
-
if (hit !== undefined)
|
|
28437
|
-
fused.push({ ...hit, score: entry.score });
|
|
28438
|
-
if (fused.length >= topK)
|
|
28439
|
-
break;
|
|
28440
|
-
}
|
|
28441
|
-
return fused;
|
|
28442
|
-
}
|
|
28443
|
-
async sync() {
|
|
28444
|
-
const { embedder } = this.options;
|
|
28445
|
-
if (embedder === undefined)
|
|
28446
|
-
return { ok: false, message: "no embedder configured" };
|
|
28447
|
-
if (!this.attachCrawler())
|
|
28448
|
-
return { ok: false, message: "crawler archive not available yet" };
|
|
28449
|
-
let identity;
|
|
28450
|
-
try {
|
|
28451
|
-
identity = await embedder.identity();
|
|
28452
|
-
} catch (error) {
|
|
28453
|
-
return { ok: false, message: errorMessage2(error) };
|
|
28454
|
-
}
|
|
28455
|
-
this.adoptIdentity(identity);
|
|
28456
|
-
const pruned = this.prune();
|
|
28457
|
-
const pending = this.pendingKeys();
|
|
28458
|
-
const batchSize = Math.max(1, this.options.batchSize ?? DEFAULT_BATCH_SIZE);
|
|
28459
|
-
const budget = Math.max(1, this.options.maxEmbedPerSync ?? DEFAULT_MAX_EMBED_PER_SYNC);
|
|
28460
|
-
const target = Math.min(pending.length, budget);
|
|
28461
|
-
const embed = async (rows) => {
|
|
28462
|
-
const vectors = await embedder.embed(rows.map((row) => row.input));
|
|
28463
|
-
if (vectors.length !== rows.length) {
|
|
28464
|
-
throw new Error(`embedder returned ${vectors.length} vectors for ${rows.length} messages`);
|
|
28465
|
-
}
|
|
28466
|
-
const wrong = vectors.find((vector) => vector.length !== identity.dimension);
|
|
28467
|
-
if (wrong !== undefined) {
|
|
28468
|
-
throw new Error(`embedder returned a ${wrong.length}-dimension vector, expected ${identity.dimension}`);
|
|
28469
|
-
}
|
|
28470
|
-
return vectors;
|
|
28628
|
+
}
|
|
28629
|
+
function spawnMailcrawl(binary, args, env, options, signal, timeoutMs) {
|
|
28630
|
+
return new Promise((resolveResult) => {
|
|
28631
|
+
const portable = portableSpawnCommand(binary, args);
|
|
28632
|
+
const child = spawn15(portable.command, portable.args, {
|
|
28633
|
+
env,
|
|
28634
|
+
detached: process.platform !== "win32",
|
|
28635
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
28636
|
+
});
|
|
28637
|
+
let stdout = { text: "", bytes: 0, exceeded: false }, stderr = { text: "", bytes: 0, exceeded: false }, settled = false, reason;
|
|
28638
|
+
const max = options.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES4;
|
|
28639
|
+
const timer = setTimeout(() => {
|
|
28640
|
+
reason = "timeout";
|
|
28641
|
+
terminate4(child);
|
|
28642
|
+
}, timeoutMs);
|
|
28643
|
+
const abort = () => {
|
|
28644
|
+
reason = "aborted";
|
|
28645
|
+
terminate4(child);
|
|
28471
28646
|
};
|
|
28472
|
-
|
|
28473
|
-
|
|
28474
|
-
|
|
28475
|
-
|
|
28476
|
-
|
|
28477
|
-
|
|
28478
|
-
|
|
28479
|
-
|
|
28480
|
-
|
|
28481
|
-
|
|
28482
|
-
const rejected = [];
|
|
28483
|
-
let lastError = batchError;
|
|
28484
|
-
for (const row of rows) {
|
|
28485
|
-
try {
|
|
28486
|
-
this.store([row], await embed([row]));
|
|
28487
|
-
embedded += 1;
|
|
28488
|
-
} catch (error) {
|
|
28489
|
-
rejected.push(row);
|
|
28490
|
-
lastError = error;
|
|
28491
|
-
}
|
|
28492
|
-
}
|
|
28493
|
-
if (rejected.length === 0)
|
|
28494
|
-
continue;
|
|
28495
|
-
if (embedded === 0)
|
|
28496
|
-
return { ok: false, message: errorMessage2(lastError) };
|
|
28497
|
-
this.store(rejected, undefined);
|
|
28498
|
-
skipped += rejected.length;
|
|
28647
|
+
if (signal?.aborted)
|
|
28648
|
+
abort();
|
|
28649
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
28650
|
+
child.stdout.setEncoding("utf8");
|
|
28651
|
+
child.stderr.setEncoding("utf8");
|
|
28652
|
+
child.stdout.on("data", (chunk) => {
|
|
28653
|
+
stdout = appendOutput(stdout, chunk, max);
|
|
28654
|
+
if (stdout.exceeded && reason === undefined) {
|
|
28655
|
+
reason = "stdout-too-large";
|
|
28656
|
+
terminate4(child);
|
|
28499
28657
|
}
|
|
28500
|
-
}
|
|
28501
|
-
return { ok: true, embedded, skipped, pruned, remaining: pending.length - embedded - skipped };
|
|
28502
|
-
}
|
|
28503
|
-
adoptIdentity(identity) {
|
|
28504
|
-
const serialized = JSON.stringify(identity);
|
|
28505
|
-
const current = this.db.prepare("select v from sidecar_meta where k = ?").get(IDENTITY_KEY);
|
|
28506
|
-
if (current?.v === serialized)
|
|
28507
|
-
return;
|
|
28508
|
-
this.transaction(() => {
|
|
28509
|
-
this.db.exec("delete from sidecar_vec");
|
|
28510
|
-
this.db.prepare("insert or replace into sidecar_meta (k, v) values (?, ?)").run(IDENTITY_KEY, serialized);
|
|
28511
28658
|
});
|
|
28512
|
-
|
|
28513
|
-
|
|
28514
|
-
|
|
28515
|
-
|
|
28516
|
-
|
|
28517
|
-
select ${descriptor.keyExpression} from ${descriptor.fromClause} where ${this.where}
|
|
28518
|
-
)`).run(...this.whereParams());
|
|
28519
|
-
if (Number(result.changes) > 0)
|
|
28520
|
-
this.lastSemantic = undefined;
|
|
28521
|
-
return Number(result.changes);
|
|
28522
|
-
}
|
|
28523
|
-
pendingKeys() {
|
|
28524
|
-
const { descriptor } = this.options;
|
|
28525
|
-
const rows = this.db.prepare(`select ${this.select}, v.hash as stored_hash
|
|
28526
|
-
from ${descriptor.fromClause}
|
|
28527
|
-
left join sidecar_vec v on v.message_key = ${descriptor.keyExpression}
|
|
28528
|
-
where ${this.where}`).iterate(...this.whereParams());
|
|
28529
|
-
const pending = [];
|
|
28530
|
-
for (const row of rows) {
|
|
28531
|
-
if (row.stored_hash !== this.toRow(row).hash)
|
|
28532
|
-
pending.push(String(row.k));
|
|
28533
|
-
}
|
|
28534
|
-
return pending;
|
|
28535
|
-
}
|
|
28536
|
-
hydrate(keys) {
|
|
28537
|
-
const { descriptor } = this.options;
|
|
28538
|
-
const out = new Map;
|
|
28539
|
-
if (keys.length === 0)
|
|
28540
|
-
return out;
|
|
28541
|
-
const rows = this.db.prepare(`select ${this.select} from ${descriptor.fromClause}
|
|
28542
|
-
where ${descriptor.keyExpression} in (${keys.map(() => "?").join(", ")}) and ${this.where}`).all(...keys, ...this.whereParams());
|
|
28543
|
-
const byKey = new Map(rows.map((row) => [String(row.k), this.toRow(row)]));
|
|
28544
|
-
for (const key of keys) {
|
|
28545
|
-
const row = byKey.get(key);
|
|
28546
|
-
if (row !== undefined)
|
|
28547
|
-
out.set(key, row);
|
|
28548
|
-
}
|
|
28549
|
-
return out;
|
|
28550
|
-
}
|
|
28551
|
-
store(rows, vectors) {
|
|
28552
|
-
const upsert = this.db.prepare("insert or replace into sidecar_vec (message_key, hash, vec) values (?, ?, ?)");
|
|
28553
|
-
this.transaction(() => {
|
|
28554
|
-
for (const [index, row] of rows.entries()) {
|
|
28555
|
-
upsert.run(row.key, row.hash, encodeVector(vectors?.[index] ?? []));
|
|
28659
|
+
child.stderr.on("data", (chunk) => {
|
|
28660
|
+
stderr = appendOutput(stderr, chunk, max);
|
|
28661
|
+
if (stderr.exceeded && reason === undefined) {
|
|
28662
|
+
reason = "stderr-too-large";
|
|
28663
|
+
terminate4(child);
|
|
28556
28664
|
}
|
|
28557
28665
|
});
|
|
28558
|
-
|
|
28666
|
+
child.on("error", (error) => {
|
|
28667
|
+
if (settled)
|
|
28668
|
+
return;
|
|
28669
|
+
settled = true;
|
|
28670
|
+
clearTimeout(timer);
|
|
28671
|
+
signal?.removeEventListener("abort", abort);
|
|
28672
|
+
resolveResult({
|
|
28673
|
+
ok: false,
|
|
28674
|
+
reason: error.code === "ENOENT" ? "binary-missing" : "spawn-error",
|
|
28675
|
+
stdout: stdout.text,
|
|
28676
|
+
stderr: "",
|
|
28677
|
+
code: null
|
|
28678
|
+
});
|
|
28679
|
+
});
|
|
28680
|
+
child.on("close", (code) => {
|
|
28681
|
+
if (settled)
|
|
28682
|
+
return;
|
|
28683
|
+
settled = true;
|
|
28684
|
+
clearTimeout(timer);
|
|
28685
|
+
signal?.removeEventListener("abort", abort);
|
|
28686
|
+
resolveResult({
|
|
28687
|
+
ok: code === 0 && reason === undefined,
|
|
28688
|
+
...reason === undefined ? code === 0 ? {} : { reason: "nonzero-exit" } : { reason },
|
|
28689
|
+
stdout: stdout.text,
|
|
28690
|
+
stderr: stderr.text,
|
|
28691
|
+
code
|
|
28692
|
+
});
|
|
28693
|
+
});
|
|
28694
|
+
});
|
|
28695
|
+
}
|
|
28696
|
+
function terminate4(child) {
|
|
28697
|
+
if (child.killed)
|
|
28698
|
+
return;
|
|
28699
|
+
if (process.platform !== "win32" && child.pid !== undefined) {
|
|
28700
|
+
try {
|
|
28701
|
+
process.kill(-child.pid, "SIGTERM");
|
|
28702
|
+
return;
|
|
28703
|
+
} catch {}
|
|
28559
28704
|
}
|
|
28560
|
-
|
|
28561
|
-
|
|
28562
|
-
|
|
28563
|
-
|
|
28564
|
-
|
|
28565
|
-
|
|
28705
|
+
child.kill("SIGTERM");
|
|
28706
|
+
}
|
|
28707
|
+
function parseJson3(text) {
|
|
28708
|
+
try {
|
|
28709
|
+
return JSON.parse(text.trim());
|
|
28710
|
+
} catch {
|
|
28711
|
+
return;
|
|
28712
|
+
}
|
|
28713
|
+
}
|
|
28714
|
+
function parseObject2(text) {
|
|
28715
|
+
const value = parseJson3(text);
|
|
28716
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
28717
|
+
}
|
|
28718
|
+
function objectArray(value, keys) {
|
|
28719
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
28720
|
+
return;
|
|
28721
|
+
for (const key of keys)
|
|
28722
|
+
if (Array.isArray(value[key]))
|
|
28723
|
+
return value[key];
|
|
28724
|
+
return;
|
|
28725
|
+
}
|
|
28726
|
+
function hasAnyNumber(value, keys) {
|
|
28727
|
+
return keys.some((key) => number(value, key) !== undefined);
|
|
28728
|
+
}
|
|
28729
|
+
function normalizeHit2(value, mode, index) {
|
|
28730
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
28731
|
+
return;
|
|
28732
|
+
const row = value;
|
|
28733
|
+
const text = (key) => typeof row[key] === "string" ? row[key] : "";
|
|
28734
|
+
const chunkId = text("chunkId") || text("chunk_id");
|
|
28735
|
+
const messageId = text("messageId") || text("message_id");
|
|
28736
|
+
const accountId = text("accountId") || text("account_id");
|
|
28737
|
+
const mailbox = text("mailbox");
|
|
28738
|
+
const snippet = text("snippet") || text("content") || text("text");
|
|
28739
|
+
if (!isSafeOpaqueId(chunkId) || !isSafeOpaqueId(messageId) || !snippet)
|
|
28740
|
+
return;
|
|
28741
|
+
const to = Array.isArray(row.to) ? row.to.filter((item) => typeof item === "string") : [];
|
|
28742
|
+
return {
|
|
28743
|
+
chunkId,
|
|
28744
|
+
messageId,
|
|
28745
|
+
threadId: text("threadId") || text("thread_id"),
|
|
28746
|
+
accountId,
|
|
28747
|
+
mailbox,
|
|
28748
|
+
subject: text("subject"),
|
|
28749
|
+
from: text("from"),
|
|
28750
|
+
to,
|
|
28751
|
+
date: text("date"),
|
|
28752
|
+
snippet,
|
|
28753
|
+
score: typeof row.score === "number" ? row.score : 1 / (index + 1),
|
|
28754
|
+
mode
|
|
28755
|
+
};
|
|
28756
|
+
}
|
|
28757
|
+
function number(value, key) {
|
|
28758
|
+
return typeof value[key] === "number" && Number.isFinite(value[key]) ? value[key] : undefined;
|
|
28759
|
+
}
|
|
28760
|
+
function string(value, key) {
|
|
28761
|
+
return typeof value[key] === "string" ? value[key] : undefined;
|
|
28762
|
+
}
|
|
28763
|
+
function ok4(data, result) {
|
|
28764
|
+
return { ok: true, data, stdout: result.stdout, stderr: result.stderr, code: result.code ?? 0 };
|
|
28765
|
+
}
|
|
28766
|
+
function failure3(result) {
|
|
28767
|
+
return {
|
|
28768
|
+
ok: false,
|
|
28769
|
+
reason: result.reason ?? "nonzero-exit",
|
|
28770
|
+
stdout: result.stdout,
|
|
28771
|
+
stderr: boundDiagnosticText6(result.stderr),
|
|
28772
|
+
code: result.code
|
|
28773
|
+
};
|
|
28774
|
+
}
|
|
28775
|
+
function boundDiagnosticText6(value) {
|
|
28776
|
+
if (value.length === 0)
|
|
28777
|
+
return "";
|
|
28778
|
+
return value.trim().slice(0, 4000);
|
|
28779
|
+
}
|
|
28780
|
+
function appendOutput(current, chunk, maxBytes) {
|
|
28781
|
+
if (current.exceeded)
|
|
28782
|
+
return current;
|
|
28783
|
+
const bytes = Buffer.from(chunk, "utf8");
|
|
28784
|
+
const remaining = Math.max(0, maxBytes - current.bytes);
|
|
28785
|
+
const prefix = truncateUtf8(chunk, remaining);
|
|
28786
|
+
const total = current.bytes + bytes.byteLength;
|
|
28787
|
+
return {
|
|
28788
|
+
text: current.text + prefix,
|
|
28789
|
+
bytes: Math.min(total, maxBytes),
|
|
28790
|
+
exceeded: total > maxBytes
|
|
28791
|
+
};
|
|
28792
|
+
}
|
|
28793
|
+
function isSafeOpaqueId(value) {
|
|
28794
|
+
return value.length > 0 && value !== "." && value !== ".." && !/[\\/#\u0000-\u001f\u007f]/u.test(value);
|
|
28795
|
+
}
|
|
28796
|
+
function findRemoteEmbeddingViolation(env) {
|
|
28797
|
+
for (const [key, value] of Object.entries(env)) {
|
|
28798
|
+
if (value === undefined)
|
|
28799
|
+
continue;
|
|
28800
|
+
const lower = key.toLowerCase();
|
|
28801
|
+
if (REMOTE_EMBEDDING_ENV_KEYS.has(lower))
|
|
28802
|
+
return key;
|
|
28803
|
+
if (lower === "mailcrawl_embedder" || lower === "mailcrawl_embed_url") {
|
|
28804
|
+
if (isRemoteHttpEndpoint(value))
|
|
28805
|
+
return key;
|
|
28566
28806
|
}
|
|
28567
28807
|
}
|
|
28568
|
-
|
|
28569
|
-
|
|
28808
|
+
return;
|
|
28809
|
+
}
|
|
28810
|
+
function isRemoteHttpEndpoint(value) {
|
|
28811
|
+
const trimmed = value.trim();
|
|
28812
|
+
if (!/^https?:\/\//iu.test(trimmed))
|
|
28813
|
+
return false;
|
|
28814
|
+
try {
|
|
28815
|
+
return !LOOPBACK_HOSTS2.has(new URL(trimmed).hostname);
|
|
28816
|
+
} catch {
|
|
28817
|
+
return true;
|
|
28818
|
+
}
|
|
28819
|
+
}
|
|
28820
|
+
function truncateUtf8(value, maxBytes) {
|
|
28821
|
+
if (Buffer.byteLength(value) <= maxBytes)
|
|
28822
|
+
return value;
|
|
28823
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
28824
|
+
const bytes = Buffer.from(value, "utf8");
|
|
28825
|
+
for (let end = Math.min(maxBytes, bytes.length);end >= 0; end -= 1) {
|
|
28570
28826
|
try {
|
|
28571
|
-
|
|
28572
|
-
|
|
28573
|
-
} catch (error) {
|
|
28574
|
-
this.db.exec("rollback");
|
|
28575
|
-
throw error;
|
|
28576
|
-
}
|
|
28827
|
+
return decoder.decode(bytes.subarray(0, end));
|
|
28828
|
+
} catch {}
|
|
28577
28829
|
}
|
|
28578
|
-
|
|
28579
|
-
|
|
28580
|
-
|
|
28581
|
-
|
|
28582
|
-
|
|
28583
|
-
|
|
28584
|
-
|
|
28585
|
-
|
|
28586
|
-
|
|
28587
|
-
|
|
28588
|
-
|
|
28589
|
-
|
|
28590
|
-
|
|
28591
|
-
|
|
28592
|
-
|
|
28593
|
-
|
|
28594
|
-
|
|
28595
|
-
|
|
28596
|
-
|
|
28597
|
-
|
|
28598
|
-
|
|
28830
|
+
return "";
|
|
28831
|
+
}
|
|
28832
|
+
var DEFAULT_BINARY4 = "mailcrawl", DEFAULT_SEARCH_TIMEOUT_MS2 = 60000, DEFAULT_INDEX_TIMEOUT_MS3 = 1800000, DEFAULT_MAX_BUFFER_BYTES4 = 1048576, LOOPBACK_HOSTS2, SAFE_ENV_KEYS2, SAFE_MAILCRAWL_ENV_KEYS, REMOTE_EMBEDDING_ENV_KEYS;
|
|
28833
|
+
var init_client9 = __esm(() => {
|
|
28834
|
+
init_portable_spawn();
|
|
28835
|
+
init_config3();
|
|
28836
|
+
LOOPBACK_HOSTS2 = new Set(["127.0.0.1", "localhost", "[::1]", "::1"]);
|
|
28837
|
+
SAFE_ENV_KEYS2 = new Set(["HOME", "LANG", "LC_ALL", "PATH", "TMPDIR", "TMP", "TEMP", "NO_COLOR"]);
|
|
28838
|
+
SAFE_MAILCRAWL_ENV_KEYS = new Set([
|
|
28839
|
+
"MAILCRAWL_DATA_DIR",
|
|
28840
|
+
"MAILCRAWL_EMBEDDER",
|
|
28841
|
+
"MAILCRAWL_EMBEDDER_PROVIDER",
|
|
28842
|
+
"MAILCRAWL_EMBED_URL",
|
|
28843
|
+
"MAILCRAWL_EMBED_MODEL",
|
|
28844
|
+
"MAILCRAWL_EMBED_DIM",
|
|
28845
|
+
"MAILCRAWL_EMBED_TIMEOUT",
|
|
28846
|
+
"MAILCRAWL_EMBED_BATCH_SIZE",
|
|
28847
|
+
"MAILCRAWL_QUERY_PREFIX",
|
|
28848
|
+
"MAILCRAWL_PASSAGE_PREFIX",
|
|
28849
|
+
"MAILCRAWL_NATIVE_MODEL",
|
|
28850
|
+
"MAILCRAWL_NATIVE_DEVICE",
|
|
28851
|
+
"MAILCRAWL_NATIVE_DTYPE",
|
|
28852
|
+
"MAILCRAWL_KIWI_MODEL",
|
|
28853
|
+
"MAILCRAWL_JA_HELPER",
|
|
28854
|
+
"MAILCRAWL_ZH_HELPER"
|
|
28855
|
+
]);
|
|
28856
|
+
REMOTE_EMBEDDING_ENV_KEYS = new Set([
|
|
28857
|
+
"mailcrawl_embedder_base_url",
|
|
28858
|
+
"mailcrawl_embedding_base_url",
|
|
28859
|
+
"mailcrawl_allow_remote_embeddings"
|
|
28860
|
+
]);
|
|
28861
|
+
});
|
|
28862
|
+
|
|
28863
|
+
// src/datasource/skills/mailcrawl/methods.ts
|
|
28864
|
+
class MailcrawlMethod {
|
|
28865
|
+
client;
|
|
28866
|
+
mode;
|
|
28867
|
+
instanceId;
|
|
28868
|
+
tags;
|
|
28869
|
+
account;
|
|
28870
|
+
mailbox;
|
|
28871
|
+
constructor(client, mode, instanceId, tags, account, mailbox) {
|
|
28872
|
+
this.client = client;
|
|
28873
|
+
this.mode = mode;
|
|
28874
|
+
this.instanceId = instanceId;
|
|
28875
|
+
this.tags = tags;
|
|
28876
|
+
this.account = account;
|
|
28877
|
+
this.mailbox = mailbox;
|
|
28599
28878
|
}
|
|
28600
|
-
|
|
28879
|
+
describe() {
|
|
28601
28880
|
return {
|
|
28602
|
-
|
|
28603
|
-
|
|
28604
|
-
|
|
28605
|
-
|
|
28606
|
-
|
|
28607
|
-
|
|
28881
|
+
name: `mailcrawl-${this.mode}`,
|
|
28882
|
+
type: this.mode === "bm25" ? "bm25" : this.mode === "semantic" ? "vector" : "hybrid",
|
|
28883
|
+
description: `${this.mode} retrieval over archived email via the external mailcrawl CLI`,
|
|
28884
|
+
status: "active",
|
|
28885
|
+
capabilities: ["email", this.mode, "scoped", "external-cli", "path-opaque-sources"],
|
|
28886
|
+
datasourceId: "mailcrawl",
|
|
28887
|
+
tags: this.tags
|
|
28608
28888
|
};
|
|
28609
28889
|
}
|
|
28890
|
+
async retrieve(query, options) {
|
|
28891
|
+
if (!query.trim())
|
|
28892
|
+
return [];
|
|
28893
|
+
const result = await this.client.search(this.mode, query, {
|
|
28894
|
+
topK: options.topK,
|
|
28895
|
+
signal: options.signal
|
|
28896
|
+
});
|
|
28897
|
+
if (!result.ok)
|
|
28898
|
+
throw datasourceCliError("mailcrawl", `search --mode ${this.mode}`, result);
|
|
28899
|
+
return result.hits.filter((hit) => {
|
|
28900
|
+
if (this.account !== undefined && hit.accountId !== this.account)
|
|
28901
|
+
return false;
|
|
28902
|
+
if (this.mailbox !== undefined && hit.mailbox !== this.mailbox)
|
|
28903
|
+
return false;
|
|
28904
|
+
const source = datasourceSourcePath("mailcrawl", this.instanceId, hit.chunkId);
|
|
28905
|
+
return matchesDatasourceScope(source, options.scope);
|
|
28906
|
+
}).map((hit) => {
|
|
28907
|
+
const source = datasourceSourcePath("mailcrawl", this.instanceId, hit.chunkId);
|
|
28908
|
+
return {
|
|
28909
|
+
id: `mailcrawl:${this.instanceId}:${hit.chunkId}`,
|
|
28910
|
+
content: hit.snippet,
|
|
28911
|
+
source,
|
|
28912
|
+
score: hit.score,
|
|
28913
|
+
metadata: {
|
|
28914
|
+
method: `mailcrawl-${this.mode}`,
|
|
28915
|
+
datasourceId: "mailcrawl",
|
|
28916
|
+
instanceId: this.instanceId,
|
|
28917
|
+
mode: this.mode,
|
|
28918
|
+
chunkId: hit.chunkId,
|
|
28919
|
+
messageId: hit.messageId,
|
|
28920
|
+
threadId: hit.threadId,
|
|
28921
|
+
accountId: hit.accountId,
|
|
28922
|
+
mailbox: hit.mailbox,
|
|
28923
|
+
subject: hit.subject,
|
|
28924
|
+
from: hit.from,
|
|
28925
|
+
to: hit.to,
|
|
28926
|
+
date: hit.date
|
|
28927
|
+
}
|
|
28928
|
+
};
|
|
28929
|
+
}).slice(0, options.topK ?? 20);
|
|
28930
|
+
}
|
|
28610
28931
|
}
|
|
28611
|
-
|
|
28612
|
-
|
|
28613
|
-
|
|
28614
|
-
|
|
28615
|
-
|
|
28616
|
-
|
|
28617
|
-
}
|
|
28618
|
-
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";
|
|
28619
|
-
var init_sidecar = () => {};
|
|
28620
|
-
|
|
28621
|
-
// src/datasource/sidecar/methods.ts
|
|
28622
|
-
function sidecarMethodName(datasourceId, mode) {
|
|
28623
|
-
return mode === "bm25" ? `${datasourceId}-sidecar-bm25` : `${datasourceId}-${mode}`;
|
|
28624
|
-
}
|
|
28932
|
+
var modes, MAILCRAWL_MODES;
|
|
28933
|
+
var init_methods5 = __esm(() => {
|
|
28934
|
+
init_errors4();
|
|
28935
|
+
init_scope2();
|
|
28936
|
+
modes = ["bm25", "semantic", "hybrid"];
|
|
28937
|
+
MAILCRAWL_MODES = modes;
|
|
28938
|
+
});
|
|
28625
28939
|
|
|
28626
|
-
|
|
28627
|
-
|
|
28628
|
-
|
|
28629
|
-
|
|
28940
|
+
// src/datasource/skills/mailcrawl/skill.ts
|
|
28941
|
+
class MailcrawlSkill {
|
|
28942
|
+
client;
|
|
28943
|
+
instanceId;
|
|
28944
|
+
instances;
|
|
28945
|
+
interval;
|
|
28946
|
+
tags;
|
|
28947
|
+
account;
|
|
28948
|
+
mailbox;
|
|
28949
|
+
lastIndexedAt;
|
|
28950
|
+
constructor(options = {}) {
|
|
28951
|
+
const connectorOptions = options.connectorOptions ?? options;
|
|
28952
|
+
this.instanceId = options.instanceId ?? "default";
|
|
28953
|
+
this.instances = options.instances?.length ? options.instances : [this.instanceId];
|
|
28954
|
+
validateMailcrawlInstanceId(this.instanceId);
|
|
28955
|
+
for (const instance of this.instances)
|
|
28956
|
+
validateMailcrawlInstanceId(instance);
|
|
28957
|
+
this.interval = options.pollingIntervalMs ?? 15 * 60 * 1000;
|
|
28958
|
+
this.tags = options.tags ?? TAGS;
|
|
28959
|
+
this.account = connectorOptions.account;
|
|
28960
|
+
this.mailbox = connectorOptions.mailbox;
|
|
28961
|
+
this.client = options.client ?? new MailcrawlClient({ ...connectorOptions, instanceId: this.instanceId });
|
|
28630
28962
|
}
|
|
28631
28963
|
describe() {
|
|
28632
|
-
const { datasourceId, skillType, mode, tags } = this.options;
|
|
28633
|
-
const { type, text, capabilities } = DESCRIPTIONS[mode];
|
|
28634
28964
|
return {
|
|
28635
|
-
name:
|
|
28636
|
-
|
|
28637
|
-
|
|
28965
|
+
name: "mailcrawl",
|
|
28966
|
+
id: "mailcrawl",
|
|
28967
|
+
type: "mailcrawl-archive",
|
|
28968
|
+
description: "Local email archive datasource via the external mailcrawl CLI",
|
|
28969
|
+
capabilities: ["email", "external-cli", "polling", "incremental", "bm25", "semantic", "hybrid"],
|
|
28970
|
+
tags: this.tags,
|
|
28638
28971
|
status: "active",
|
|
28639
|
-
|
|
28640
|
-
datasourceId,
|
|
28641
|
-
|
|
28972
|
+
requiresExternalCli: true,
|
|
28973
|
+
datasourceId: "mailcrawl",
|
|
28974
|
+
instanceId: this.instanceId,
|
|
28975
|
+
instances: this.instances
|
|
28642
28976
|
};
|
|
28643
28977
|
}
|
|
28644
|
-
|
|
28645
|
-
|
|
28646
|
-
|
|
28647
|
-
|
|
28648
|
-
|
|
28649
|
-
|
|
28650
|
-
|
|
28651
|
-
|
|
28652
|
-
|
|
28653
|
-
const result = this.
|
|
28654
|
-
if (result
|
|
28655
|
-
|
|
28978
|
+
polling() {
|
|
28979
|
+
return {
|
|
28980
|
+
mode: this.interval > 0 ? "poll" : "none",
|
|
28981
|
+
...this.interval > 0 ? { intervalMs: this.interval } : {},
|
|
28982
|
+
lastIndexedAt: this.lastIndexedAt
|
|
28983
|
+
};
|
|
28984
|
+
}
|
|
28985
|
+
async index(options) {
|
|
28986
|
+
try {
|
|
28987
|
+
const result = await this.client.sync(options?.signal);
|
|
28988
|
+
if (!result.ok) {
|
|
28989
|
+
return this.fail(result.reason === "binary-missing" || result.reason === "spawn-error" || result.reason === "timeout" ? "datasource-unavailable" : result.reason === "remote-embedding-rejected" ? "datasource-embedding-egress-rejected" : "datasource-index-failed", result.reason);
|
|
28990
|
+
}
|
|
28991
|
+
const indexed = await this.client.index(options?.signal);
|
|
28992
|
+
const diagnostics = [];
|
|
28993
|
+
if (!indexed.ok) {
|
|
28994
|
+
diagnostics.push({
|
|
28995
|
+
code: indexed.reason === "remote-embedding-rejected" ? "datasource-embedding-egress-rejected" : "datasource-index-failed",
|
|
28996
|
+
severity: "warning",
|
|
28997
|
+
message: `mailcrawl semantic index unavailable (${indexed.reason}); BM25 remains available`,
|
|
28998
|
+
instanceId: this.instanceId,
|
|
28999
|
+
source: "mailcrawl"
|
|
29000
|
+
});
|
|
29001
|
+
}
|
|
29002
|
+
this.lastIndexedAt = Date.now();
|
|
29003
|
+
return {
|
|
29004
|
+
ok: true,
|
|
29005
|
+
instanceId: this.instanceId,
|
|
29006
|
+
skill: "mailcrawl",
|
|
29007
|
+
chunkCount: result.data.chunksAdded ?? result.data.messages,
|
|
29008
|
+
indexedAt: this.lastIndexedAt,
|
|
29009
|
+
diagnostics
|
|
29010
|
+
};
|
|
29011
|
+
} catch {
|
|
29012
|
+
return this.fail("datasource-unavailable", "mailcrawl sync failed unexpectedly");
|
|
28656
29013
|
}
|
|
28657
|
-
return results;
|
|
28658
29014
|
}
|
|
28659
|
-
|
|
28660
|
-
|
|
28661
|
-
|
|
28662
|
-
|
|
28663
|
-
|
|
28664
|
-
|
|
28665
|
-
|
|
29015
|
+
retrievalMethods() {
|
|
29016
|
+
return [
|
|
29017
|
+
new MailcrawlMethod(this.client, "bm25", this.instanceId, this.tags, this.account, this.mailbox),
|
|
29018
|
+
new MailcrawlMethod(this.client, "semantic", this.instanceId, this.tags, this.account, this.mailbox),
|
|
29019
|
+
new MailcrawlMethod(this.client, "hybrid", this.instanceId, this.tags, this.account, this.mailbox)
|
|
29020
|
+
];
|
|
29021
|
+
}
|
|
29022
|
+
describeSources() {
|
|
29023
|
+
return this.instances.map((instanceId) => ({
|
|
29024
|
+
source: datasourceSourcePath("mailcrawl", instanceId),
|
|
29025
|
+
datasourceId: "mailcrawl",
|
|
29026
|
+
skill: "mailcrawl",
|
|
29027
|
+
instanceId,
|
|
29028
|
+
contentType: "email",
|
|
29029
|
+
metadata: { datasourceId: "mailcrawl", instanceId, tags: this.tags }
|
|
29030
|
+
}));
|
|
29031
|
+
}
|
|
29032
|
+
skillManifest() {
|
|
29033
|
+
const scopes = this.instances.map((id) => `- \`${datasourceSourcePath("mailcrawl", id)}\``).join(`
|
|
29034
|
+
`);
|
|
28666
29035
|
return {
|
|
28667
|
-
|
|
28668
|
-
|
|
28669
|
-
|
|
28670
|
-
|
|
28671
|
-
|
|
28672
|
-
|
|
28673
|
-
|
|
28674
|
-
|
|
28675
|
-
|
|
28676
|
-
|
|
28677
|
-
|
|
28678
|
-
|
|
28679
|
-
|
|
28680
|
-
|
|
29036
|
+
name: "datasource-mailcrawl",
|
|
29037
|
+
description: "Search local email synchronized and indexed by mailcrawl.",
|
|
29038
|
+
content: `# mailcrawl email datasource
|
|
29039
|
+
|
|
29040
|
+
Use this skill for authorized local email retrieval. AutoRAG invokes the external \`mailcrawl\` CLI and never opens its SQLite archive directly.
|
|
29041
|
+
|
|
29042
|
+
## Indexing
|
|
29043
|
+
Indexing is server-managed and incremental; do not trigger sync yourself.
|
|
29044
|
+
|
|
29045
|
+
## Search
|
|
29046
|
+
Call the dedicated \`${datasourceSearchToolName("mailcrawl")}\` tool with a natural-language query. Available scopes:
|
|
29047
|
+
${scopes}
|
|
29048
|
+
|
|
29049
|
+
The selectable retrieval modes are BM25, semantic, and hybrid. Scope arguments can only narrow trusted access. Source identifiers remain opaque and hierarchical.
|
|
29050
|
+
|
|
29051
|
+
## Native CLI
|
|
29052
|
+
The external \`mailcrawl\` CLI owns the archive and index. When the dedicated tool cannot express what you need, call mailcrawl directly through \`bash\`:
|
|
29053
|
+
- \`mailcrawl search --mode hybrid --limit 20 --json "<query>"\` (\`bm25\` and \`semantic\` modes also available; \`--account\` / \`--mailbox\` narrow the archive)
|
|
29054
|
+
Never pass datasource virtual paths (\`/mailcrawl/...\`) to bash; they are not OS paths.`
|
|
29055
|
+
};
|
|
29056
|
+
}
|
|
29057
|
+
fail(code, message) {
|
|
29058
|
+
return {
|
|
29059
|
+
ok: false,
|
|
29060
|
+
instanceId: this.instanceId,
|
|
29061
|
+
skill: "mailcrawl",
|
|
29062
|
+
indexedAt: Date.now(),
|
|
29063
|
+
diagnostics: [
|
|
29064
|
+
{
|
|
29065
|
+
code,
|
|
29066
|
+
severity: code === "datasource-unavailable" ? "warning" : "error",
|
|
29067
|
+
message: `mailcrawl ${message}`,
|
|
29068
|
+
instanceId: this.instanceId,
|
|
29069
|
+
source: "mailcrawl"
|
|
29070
|
+
}
|
|
29071
|
+
],
|
|
29072
|
+
error: message,
|
|
29073
|
+
code,
|
|
29074
|
+
message
|
|
28681
29075
|
};
|
|
28682
29076
|
}
|
|
28683
29077
|
}
|
|
28684
|
-
var
|
|
28685
|
-
var
|
|
29078
|
+
var TAGS;
|
|
29079
|
+
var init_skill9 = __esm(() => {
|
|
28686
29080
|
init_scope2();
|
|
28687
|
-
|
|
28688
|
-
|
|
28689
|
-
|
|
28690
|
-
|
|
28691
|
-
|
|
28692
|
-
|
|
28693
|
-
|
|
28694
|
-
|
|
28695
|
-
|
|
29081
|
+
init_client9();
|
|
29082
|
+
init_config3();
|
|
29083
|
+
init_methods5();
|
|
29084
|
+
TAGS = ["mailcrawl", "email", "pii"];
|
|
29085
|
+
});
|
|
29086
|
+
|
|
29087
|
+
// src/datasource/skills/mailcrawl/index.ts
|
|
29088
|
+
var init_mailcrawl = __esm(() => {
|
|
29089
|
+
init_client9();
|
|
29090
|
+
init_methods5();
|
|
29091
|
+
init_skill9();
|
|
28696
29092
|
});
|
|
28697
29093
|
|
|
28698
29094
|
// src/datasource/crawler-skill.ts
|
|
@@ -28975,7 +29371,7 @@ var init_crawler_skill = __esm(() => {
|
|
|
28975
29371
|
init_errors4();
|
|
28976
29372
|
init_scope2();
|
|
28977
29373
|
init_sidecar();
|
|
28978
|
-
|
|
29374
|
+
init_methods4();
|
|
28979
29375
|
DEFAULT_POLLING_INTERVAL_MS4 = 15 * 60 * 1000;
|
|
28980
29376
|
});
|
|
28981
29377
|
|
|
@@ -30602,7 +30998,7 @@ var init_wacrawl = __esm(() => {
|
|
|
30602
30998
|
});
|
|
30603
30999
|
|
|
30604
31000
|
// src/datasource/skills/factory.ts
|
|
30605
|
-
import { homedir as
|
|
31001
|
+
import { homedir as homedir5 } from "node:os";
|
|
30606
31002
|
import { join as join39 } from "node:path";
|
|
30607
31003
|
function chatCrawlerArgs(descriptor, defaultCrawlerDb, config, workspaceRoot, registrationName) {
|
|
30608
31004
|
const sidecar = resolveSidecarOptions({
|
|
@@ -30706,9 +31102,9 @@ var init_factory = __esm(() => {
|
|
|
30706
31102
|
init_telecrawl();
|
|
30707
31103
|
init_wacrawl();
|
|
30708
31104
|
BUILDERS = {
|
|
30709
|
-
telegram: (config, workspaceRoot, registrationName) => new TelecrawlSkill(chatCrawlerArgs(CRAWLER_DESCRIPTORS.telegram, join39(
|
|
30710
|
-
whatsapp: (config, workspaceRoot, registrationName) => new WacrawlSkill(chatCrawlerArgs(CRAWLER_DESCRIPTORS.whatsapp, join39(
|
|
30711
|
-
slack: (config, workspaceRoot, registrationName) => new SlackSkill(chatCrawlerArgs(CRAWLER_DESCRIPTORS.slack, join39(
|
|
31105
|
+
telegram: (config, workspaceRoot, registrationName) => new TelecrawlSkill(chatCrawlerArgs(CRAWLER_DESCRIPTORS.telegram, join39(homedir5(), ".telecrawl", "telecrawl.db"), config, workspaceRoot, registrationName)),
|
|
31106
|
+
whatsapp: (config, workspaceRoot, registrationName) => new WacrawlSkill(chatCrawlerArgs(CRAWLER_DESCRIPTORS.whatsapp, join39(homedir5(), ".wacrawl", "wacrawl.db"), config, workspaceRoot, registrationName)),
|
|
31107
|
+
slack: (config, workspaceRoot, registrationName) => new SlackSkill(chatCrawlerArgs(CRAWLER_DESCRIPTORS.slack, join39(homedir5(), ".slacrawl", "slacrawl.db"), config, workspaceRoot, registrationName)),
|
|
30712
31108
|
discord: (config, workspaceRoot, registrationName, embeddingRuntime) => {
|
|
30713
31109
|
const connector = config.connector ?? {};
|
|
30714
31110
|
const clientOptions = {
|
|
@@ -30764,24 +31160,42 @@ var init_factory = __esm(() => {
|
|
|
30764
31160
|
...config.channels?.ids !== undefined ? { chatIds: config.channels.ids } : {},
|
|
30765
31161
|
...larkConnectorOptions(config.connector)
|
|
30766
31162
|
}),
|
|
30767
|
-
kakao: (config,
|
|
30768
|
-
|
|
30769
|
-
|
|
30770
|
-
|
|
30771
|
-
|
|
30772
|
-
|
|
30773
|
-
|
|
30774
|
-
|
|
31163
|
+
kakao: (config, workspaceRoot, registrationName) => {
|
|
31164
|
+
const connector = config.connector ?? {};
|
|
31165
|
+
const dataDir = typeof connector.workspacePath === "string" && connector.workspacePath.length > 0 ? connector.workspacePath : join39(homedir5(), "Library", "Application Support", "katok");
|
|
31166
|
+
const sidecar = resolveSidecarOptions({
|
|
31167
|
+
descriptor: CRAWLER_DESCRIPTORS.kakao,
|
|
31168
|
+
connector: config.connector,
|
|
31169
|
+
workspaceRoot,
|
|
31170
|
+
registrationName,
|
|
31171
|
+
instanceId: config.instanceId ?? "default",
|
|
31172
|
+
defaultCrawlerDb: join39(dataDir, "archive.sqlite3")
|
|
31173
|
+
});
|
|
31174
|
+
return new LazykatokSkill({
|
|
31175
|
+
client: new LazykatokClient(connector),
|
|
31176
|
+
...config.instanceId !== undefined ? { instanceId: config.instanceId } : {},
|
|
31177
|
+
...config.pollingIntervalMs !== undefined ? { pollingIntervalMs: config.pollingIntervalMs } : {},
|
|
31178
|
+
...config.tags !== undefined ? { tags: config.tags } : {},
|
|
31179
|
+
channelIds: config.channels?.ids,
|
|
31180
|
+
channelNames: config.channels?.names,
|
|
31181
|
+
...sidecar === undefined ? {} : { sidecar }
|
|
31182
|
+
});
|
|
31183
|
+
},
|
|
30775
31184
|
github: (config, workspaceRoot, registrationName) => new GitHubSkill({
|
|
30776
31185
|
...common(config, workspaceRoot),
|
|
30777
31186
|
skillName: registrationName,
|
|
30778
31187
|
connectorOptions: config.connector
|
|
30779
31188
|
}),
|
|
30780
|
-
"github-gist": (config, workspaceRoot, registrationName) =>
|
|
30781
|
-
...
|
|
30782
|
-
|
|
30783
|
-
|
|
30784
|
-
|
|
31189
|
+
"github-gist": (config, workspaceRoot, registrationName) => {
|
|
31190
|
+
const { semantic, ...connectorOptions } = config.connector ?? {};
|
|
31191
|
+
const semanticOff = semantic === false || typeof semantic === "object" && semantic !== null && semantic.enabled === false;
|
|
31192
|
+
return new GitHubGistSkill({
|
|
31193
|
+
...common(config, workspaceRoot),
|
|
31194
|
+
skillName: registrationName,
|
|
31195
|
+
connectorOptions,
|
|
31196
|
+
...semanticOff ? { semantic: { enabled: false } } : {}
|
|
31197
|
+
});
|
|
31198
|
+
},
|
|
30785
31199
|
"cloud-drive": (config, workspaceRoot, registrationName) => new CloudDriveSkill({
|
|
30786
31200
|
...common(config, workspaceRoot),
|
|
30787
31201
|
skillName: registrationName,
|
|
@@ -30827,7 +31241,7 @@ var init_factory = __esm(() => {
|
|
|
30827
31241
|
|
|
30828
31242
|
// src/cli/config.ts
|
|
30829
31243
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
30830
|
-
import { existsSync as
|
|
31244
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync25, readFileSync as readFileSync25, renameSync as renameSync10, unlinkSync as unlinkSync5, writeFileSync as writeFileSync22 } from "node:fs";
|
|
30831
31245
|
import { basename as basename14, dirname as dirname26, isAbsolute as isAbsolute10, join as join40, resolve as resolve16 } from "node:path";
|
|
30832
31246
|
import { findEnvKeys, getEnvApiKey } from "@earendil-works/pi-ai/compat";
|
|
30833
31247
|
import { getAgentDir as getAgentDir4, ModelRuntime as ModelRuntime2, SettingsManager as SettingsManager2 } from "@earendil-works/pi-coding-agent";
|
|
@@ -30852,7 +31266,7 @@ function resolveConfigPath(input) {
|
|
|
30852
31266
|
function readConfigFile(configPath, explicit) {
|
|
30853
31267
|
let exists;
|
|
30854
31268
|
try {
|
|
30855
|
-
exists =
|
|
31269
|
+
exists = existsSync29(configPath);
|
|
30856
31270
|
} catch {
|
|
30857
31271
|
exists = false;
|
|
30858
31272
|
}
|
|
@@ -30912,12 +31326,12 @@ function replaceFileAtomically(path, contents, assertCommitAllowed) {
|
|
|
30912
31326
|
}
|
|
30913
31327
|
}
|
|
30914
31328
|
function migrateLegacyConfig(configPath, legacyPath) {
|
|
30915
|
-
if (
|
|
31329
|
+
if (existsSync29(configPath) || !existsSync29(legacyPath))
|
|
30916
31330
|
return;
|
|
30917
31331
|
const legacy = readConfigFile(legacyPath, true);
|
|
30918
31332
|
const legacyBytes = readFileSync25(legacyPath);
|
|
30919
31333
|
const migrated = normalizeLegacyConfigPaths(legacy ?? {}, dirname26(legacyPath));
|
|
30920
|
-
const migratedPathsUnchanged = legacy?.workspacePath === migrated.workspacePath && legacy?.memoryPath === migrated.memoryPath && JSON.stringify(legacy?.searchPaths) === JSON.stringify(migrated.searchPaths);
|
|
31334
|
+
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);
|
|
30921
31335
|
const migratedBytes = migratedPathsUnchanged ? legacyBytes : `${JSON.stringify(migrated, null, 2)}
|
|
30922
31336
|
`;
|
|
30923
31337
|
mkdirSync25(dirname26(configPath), { recursive: true });
|
|
@@ -30955,15 +31369,25 @@ function resolveConfigFileReadOnly(configPath, explicit, legacyPath) {
|
|
|
30955
31369
|
function resolveSearchPaths(searchPaths, origin) {
|
|
30956
31370
|
return searchPaths.map((searchPath) => resolvePersistedPath(searchPath, origin));
|
|
30957
31371
|
}
|
|
31372
|
+
function resolveExcludePaths(value, origin) {
|
|
31373
|
+
if (value === undefined)
|
|
31374
|
+
return;
|
|
31375
|
+
if (!Array.isArray(value) || value.some((path) => typeof path !== "string" || path.length === 0)) {
|
|
31376
|
+
throw new ConfigError("Config field 'excludePaths' must be a non-empty string array");
|
|
31377
|
+
}
|
|
31378
|
+
return resolveSearchPaths(value, origin);
|
|
31379
|
+
}
|
|
30958
31380
|
function resolvePersistedPath(path, origin) {
|
|
30959
31381
|
return isAbsolute10(path) ? path : resolve16(origin, path);
|
|
30960
31382
|
}
|
|
30961
31383
|
function normalizeLegacyConfigPaths(partial, origin) {
|
|
30962
31384
|
const workspacePath = resolvePersistedPath(partial.workspacePath ?? ".", origin);
|
|
31385
|
+
const excludePaths = resolveExcludePaths(partial.excludePaths, workspacePath);
|
|
30963
31386
|
return {
|
|
30964
31387
|
...partial,
|
|
30965
31388
|
workspacePath,
|
|
30966
31389
|
...partial.searchPaths === undefined ? {} : { searchPaths: resolveSearchPaths(partial.searchPaths, workspacePath) },
|
|
31390
|
+
...excludePaths === undefined ? {} : { excludePaths },
|
|
30967
31391
|
...partial.memoryPath === undefined ? {} : { memoryPath: resolvePersistedPath(partial.memoryPath, workspacePath) }
|
|
30968
31392
|
};
|
|
30969
31393
|
}
|
|
@@ -31418,10 +31842,7 @@ function resolveConfig(input) {
|
|
|
31418
31842
|
const configOrigin = dirname26(resolve16(configPath));
|
|
31419
31843
|
const fileWorkspacePath = typeof file.workspacePath === "string" ? resolvePersistedPath(file.workspacePath, configOrigin) : undefined;
|
|
31420
31844
|
const fileSearchPaths = file.searchPaths ? resolveSearchPaths(file.searchPaths, fileWorkspacePath ?? configOrigin) : undefined;
|
|
31421
|
-
|
|
31422
|
-
throw new ConfigError("Config field 'excludePaths' must be a non-empty string array");
|
|
31423
|
-
}
|
|
31424
|
-
const fileExcludePaths = file.excludePaths ? resolveSearchPaths(file.excludePaths, fileWorkspacePath ?? configOrigin) : undefined;
|
|
31845
|
+
const fileExcludePaths = resolveExcludePaths(file.excludePaths, fileWorkspacePath ?? configOrigin);
|
|
31425
31846
|
const searchPaths = flagSearchPaths ?? envSearchPaths ?? fileSearchPaths ?? defaultSearchPaths;
|
|
31426
31847
|
const flagWorkspacePath = flagString(flags, "workspace");
|
|
31427
31848
|
const envWorkspacePath = envString(env, "AUTORAG_WORKSPACE");
|
|
@@ -32157,7 +32578,7 @@ function writeDefaultConfig(path, partial, opts = {}) {
|
|
|
32157
32578
|
full.dupey = partial.dupey ?? { enabled: true };
|
|
32158
32579
|
full.excludeExactDuplicates = partial.excludeExactDuplicates ?? true;
|
|
32159
32580
|
if (partial.excludePaths !== undefined)
|
|
32160
|
-
full.excludePaths =
|
|
32581
|
+
full.excludePaths = resolveExcludePaths(partial.excludePaths, workspacePath);
|
|
32161
32582
|
if (partial.limits !== undefined)
|
|
32162
32583
|
full.limits = normalizeLimitsConfig(partial.limits);
|
|
32163
32584
|
if (partial.refresh !== undefined)
|
|
@@ -32176,7 +32597,7 @@ function writeDefaultConfig(path, partial, opts = {}) {
|
|
|
32176
32597
|
`;
|
|
32177
32598
|
const lock = acquireConfigWriteLock(path);
|
|
32178
32599
|
try {
|
|
32179
|
-
const exists =
|
|
32600
|
+
const exists = existsSync29(path);
|
|
32180
32601
|
if (!opts.force && exists) {
|
|
32181
32602
|
throw new ConfigError(`Config file already exists: ${path}`);
|
|
32182
32603
|
}
|