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