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