@karmaniverous/jeeves-watcher 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +100 -18
- package/dist/cli/jeeves-watcher/index.js +100 -18
- package/dist/index.iife.js +100 -18
- package/dist/index.iife.min.js +1 -1
- package/dist/mjs/index.js +100 -18
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -40,6 +40,31 @@ function _interopNamespaceDefault(e) {
|
|
|
40
40
|
|
|
41
41
|
var cheerio__namespace = /*#__PURE__*/_interopNamespaceDefault(cheerio);
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* @module util/normalizeError
|
|
45
|
+
*
|
|
46
|
+
* Normalizes unknown thrown values into proper Error objects for pino serialization.
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* Convert an unknown thrown value into a proper Error with message, stack, and cause.
|
|
50
|
+
* Pino's built-in `err` serializer requires an Error instance to extract message/stack.
|
|
51
|
+
*
|
|
52
|
+
* @param error - The caught value (may not be an Error).
|
|
53
|
+
* @returns A proper Error instance.
|
|
54
|
+
*/
|
|
55
|
+
function normalizeError(error) {
|
|
56
|
+
if (error instanceof Error)
|
|
57
|
+
return error;
|
|
58
|
+
if (typeof error === 'string')
|
|
59
|
+
return new Error(error);
|
|
60
|
+
const message = typeof error === 'object' && error !== null && 'message' in error
|
|
61
|
+
? String(error.message)
|
|
62
|
+
: String(error);
|
|
63
|
+
const normalized = new Error(message);
|
|
64
|
+
normalized.cause = error;
|
|
65
|
+
return normalized;
|
|
66
|
+
}
|
|
67
|
+
|
|
43
68
|
/**
|
|
44
69
|
* Best-effort base directory inference for a glob pattern.
|
|
45
70
|
*
|
|
@@ -162,13 +187,13 @@ function createConfigReindexHandler(deps) {
|
|
|
162
187
|
}
|
|
163
188
|
}
|
|
164
189
|
catch (error) {
|
|
165
|
-
deps.logger.error({ error, scope }, 'Config reindex failed');
|
|
190
|
+
deps.logger.error({ err: normalizeError(error), scope }, 'Config reindex failed');
|
|
166
191
|
}
|
|
167
192
|
})();
|
|
168
193
|
return await reply.status(200).send({ status: 'started', scope });
|
|
169
194
|
}
|
|
170
195
|
catch (error) {
|
|
171
|
-
deps.logger.error({ error }, 'Config reindex request failed');
|
|
196
|
+
deps.logger.error({ err: normalizeError(error) }, 'Config reindex request failed');
|
|
172
197
|
return await reply.status(500).send({ error: 'Internal server error' });
|
|
173
198
|
}
|
|
174
199
|
};
|
|
@@ -191,7 +216,7 @@ function createMetadataHandler(deps) {
|
|
|
191
216
|
return { ok: true };
|
|
192
217
|
}
|
|
193
218
|
catch (error) {
|
|
194
|
-
deps.logger.error({ error }, 'Metadata update failed');
|
|
219
|
+
deps.logger.error({ err: normalizeError(error) }, 'Metadata update failed');
|
|
195
220
|
return reply.status(500).send({ error: 'Internal server error' });
|
|
196
221
|
}
|
|
197
222
|
};
|
|
@@ -314,7 +339,7 @@ function createRebuildMetadataHandler(deps) {
|
|
|
314
339
|
return await reply.status(200).send({ ok: true });
|
|
315
340
|
}
|
|
316
341
|
catch (error) {
|
|
317
|
-
deps.logger.error({ error }, 'Rebuild metadata failed');
|
|
342
|
+
deps.logger.error({ err: normalizeError(error) }, 'Rebuild metadata failed');
|
|
318
343
|
return await reply.status(500).send({ error: 'Internal server error' });
|
|
319
344
|
}
|
|
320
345
|
};
|
|
@@ -336,7 +361,7 @@ function createReindexHandler(deps) {
|
|
|
336
361
|
return await reply.status(200).send({ ok: true, filesIndexed: count });
|
|
337
362
|
}
|
|
338
363
|
catch (error) {
|
|
339
|
-
deps.logger.error({ error }, 'Reindex failed');
|
|
364
|
+
deps.logger.error({ err: normalizeError(error) }, 'Reindex failed');
|
|
340
365
|
return await reply.status(500).send({ error: 'Internal server error' });
|
|
341
366
|
}
|
|
342
367
|
};
|
|
@@ -360,7 +385,7 @@ function createSearchHandler(deps) {
|
|
|
360
385
|
return results;
|
|
361
386
|
}
|
|
362
387
|
catch (error) {
|
|
363
|
-
deps.logger.error({ error }, 'Search failed');
|
|
388
|
+
deps.logger.error({ err: normalizeError(error) }, 'Search failed');
|
|
364
389
|
return reply.status(500).send({ error: 'Internal server error' });
|
|
365
390
|
}
|
|
366
391
|
};
|
|
@@ -642,6 +667,49 @@ const jeevesWatcherConfigSchema = zod.z.object({
|
|
|
642
667
|
.describe('Timeout in milliseconds for graceful shutdown.'),
|
|
643
668
|
});
|
|
644
669
|
|
|
670
|
+
/**
|
|
671
|
+
* @module config/substituteEnvVars
|
|
672
|
+
*
|
|
673
|
+
* Deep-walks config objects and replaces `${VAR_NAME}` patterns with environment variable values.
|
|
674
|
+
*/
|
|
675
|
+
const ENV_PATTERN = /\$\{([^}]+)\}/g;
|
|
676
|
+
/**
|
|
677
|
+
* Replace `${VAR_NAME}` patterns in a string with `process.env.VAR_NAME`.
|
|
678
|
+
*
|
|
679
|
+
* @param value - The string to process.
|
|
680
|
+
* @returns The string with resolved env vars; unresolvable expressions left untouched.
|
|
681
|
+
*/
|
|
682
|
+
function substituteString(value) {
|
|
683
|
+
return value.replace(ENV_PATTERN, (match, varName) => {
|
|
684
|
+
const envValue = process.env[varName];
|
|
685
|
+
if (envValue === undefined)
|
|
686
|
+
return match;
|
|
687
|
+
return envValue;
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Deep-walk a value and substitute `${VAR_NAME}` patterns in all string values.
|
|
692
|
+
*
|
|
693
|
+
* @param value - The value to walk (object, array, or primitive).
|
|
694
|
+
* @returns A new value with all env var references resolved.
|
|
695
|
+
*/
|
|
696
|
+
function substituteEnvVars(value) {
|
|
697
|
+
if (typeof value === 'string') {
|
|
698
|
+
return substituteString(value);
|
|
699
|
+
}
|
|
700
|
+
if (Array.isArray(value)) {
|
|
701
|
+
return value.map((item) => substituteEnvVars(item));
|
|
702
|
+
}
|
|
703
|
+
if (value !== null && typeof value === 'object') {
|
|
704
|
+
const result = {};
|
|
705
|
+
for (const [key, val] of Object.entries(value)) {
|
|
706
|
+
result[key] = substituteEnvVars(val);
|
|
707
|
+
}
|
|
708
|
+
return result;
|
|
709
|
+
}
|
|
710
|
+
return value;
|
|
711
|
+
}
|
|
712
|
+
|
|
645
713
|
const MODULE_NAME = 'jeeves-watcher';
|
|
646
714
|
/**
|
|
647
715
|
* Merge sensible defaults into a loaded configuration.
|
|
@@ -677,7 +745,8 @@ async function loadConfig(configPath) {
|
|
|
677
745
|
}
|
|
678
746
|
try {
|
|
679
747
|
const validated = jeevesWatcherConfigSchema.parse(result.config);
|
|
680
|
-
|
|
748
|
+
const withDefaults = applyDefaults(validated);
|
|
749
|
+
return substituteEnvVars(withDefaults);
|
|
681
750
|
}
|
|
682
751
|
catch (error) {
|
|
683
752
|
if (error instanceof zod.ZodError) {
|
|
@@ -844,7 +913,7 @@ function createGeminiProvider(config, logger) {
|
|
|
844
913
|
delayMs,
|
|
845
914
|
provider: 'gemini',
|
|
846
915
|
model: config.model,
|
|
847
|
-
error,
|
|
916
|
+
err: normalizeError(error),
|
|
848
917
|
}, 'Embedding call failed; will retry');
|
|
849
918
|
},
|
|
850
919
|
});
|
|
@@ -953,6 +1022,9 @@ function pointId(filePath, chunkIndex) {
|
|
|
953
1022
|
*/
|
|
954
1023
|
function extractMarkdownFrontmatter(markdown) {
|
|
955
1024
|
const trimmed = markdown.replace(/^\uFEFF/, '');
|
|
1025
|
+
// Only attempt frontmatter parsing if the file starts with ---
|
|
1026
|
+
if (!/^\s*---/.test(trimmed))
|
|
1027
|
+
return { body: markdown };
|
|
956
1028
|
const match = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m.exec(trimmed);
|
|
957
1029
|
if (!match)
|
|
958
1030
|
return { body: markdown };
|
|
@@ -1410,7 +1482,7 @@ class DocumentProcessor {
|
|
|
1410
1482
|
this.logger.info({ filePath, chunks: chunks.length }, 'File processed successfully');
|
|
1411
1483
|
}
|
|
1412
1484
|
catch (error) {
|
|
1413
|
-
this.logger.error({ filePath, error }, 'Failed to process file');
|
|
1485
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to process file');
|
|
1414
1486
|
}
|
|
1415
1487
|
}
|
|
1416
1488
|
/**
|
|
@@ -1430,7 +1502,7 @@ class DocumentProcessor {
|
|
|
1430
1502
|
this.logger.info({ filePath }, 'File deleted from index');
|
|
1431
1503
|
}
|
|
1432
1504
|
catch (error) {
|
|
1433
|
-
this.logger.error({ filePath, error }, 'Failed to delete file');
|
|
1505
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to delete file');
|
|
1434
1506
|
}
|
|
1435
1507
|
}
|
|
1436
1508
|
/**
|
|
@@ -1458,7 +1530,7 @@ class DocumentProcessor {
|
|
|
1458
1530
|
return merged;
|
|
1459
1531
|
}
|
|
1460
1532
|
catch (error) {
|
|
1461
|
-
this.logger.error({ filePath, error }, 'Failed to update metadata');
|
|
1533
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to update metadata');
|
|
1462
1534
|
return null;
|
|
1463
1535
|
}
|
|
1464
1536
|
}
|
|
@@ -1488,7 +1560,7 @@ class DocumentProcessor {
|
|
|
1488
1560
|
return metadata;
|
|
1489
1561
|
}
|
|
1490
1562
|
catch (error) {
|
|
1491
|
-
this.logger.error({ filePath, error }, 'Failed to re-apply rules');
|
|
1563
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to re-apply rules');
|
|
1492
1564
|
return null;
|
|
1493
1565
|
}
|
|
1494
1566
|
}
|
|
@@ -1716,7 +1788,12 @@ class VectorStoreClient {
|
|
|
1716
1788
|
maxDelayMs: 10_000,
|
|
1717
1789
|
jitter: 0.2,
|
|
1718
1790
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1719
|
-
this.log.warn({
|
|
1791
|
+
this.log.warn({
|
|
1792
|
+
attempt,
|
|
1793
|
+
delayMs,
|
|
1794
|
+
operation: 'qdrant.upsert',
|
|
1795
|
+
err: normalizeError(error),
|
|
1796
|
+
}, 'Qdrant upsert failed; will retry');
|
|
1720
1797
|
},
|
|
1721
1798
|
});
|
|
1722
1799
|
}
|
|
@@ -1742,7 +1819,12 @@ class VectorStoreClient {
|
|
|
1742
1819
|
maxDelayMs: 10_000,
|
|
1743
1820
|
jitter: 0.2,
|
|
1744
1821
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1745
|
-
this.log.warn({
|
|
1822
|
+
this.log.warn({
|
|
1823
|
+
attempt,
|
|
1824
|
+
delayMs,
|
|
1825
|
+
operation: 'qdrant.delete',
|
|
1826
|
+
err: normalizeError(error),
|
|
1827
|
+
}, 'Qdrant delete failed; will retry');
|
|
1746
1828
|
},
|
|
1747
1829
|
});
|
|
1748
1830
|
}
|
|
@@ -1896,7 +1978,7 @@ class FileSystemWatcher {
|
|
|
1896
1978
|
this.queue.enqueue({ type: 'delete', path, priority: 'normal' }, () => this.processor.deleteFile(path));
|
|
1897
1979
|
});
|
|
1898
1980
|
this.watcher.on('error', (error) => {
|
|
1899
|
-
this.logger.error({ error }, 'Watcher error');
|
|
1981
|
+
this.logger.error({ err: normalizeError(error) }, 'Watcher error');
|
|
1900
1982
|
});
|
|
1901
1983
|
this.queue.process();
|
|
1902
1984
|
this.logger.info({ paths: this.config.paths }, 'Filesystem watcher started');
|
|
@@ -1941,7 +2023,7 @@ class ConfigWatcher {
|
|
|
1941
2023
|
}, this.options.debounceMs);
|
|
1942
2024
|
});
|
|
1943
2025
|
this.watcher.on('error', (error) => {
|
|
1944
|
-
this.options.logger.error({ error }, 'Config watcher error');
|
|
2026
|
+
this.options.logger.error({ err: normalizeError(error) }, 'Config watcher error');
|
|
1945
2027
|
});
|
|
1946
2028
|
this.options.logger.info({
|
|
1947
2029
|
configPath: this.options.configPath,
|
|
@@ -2025,7 +2107,7 @@ class JeevesWatcher {
|
|
|
2025
2107
|
embeddingProvider = this.factories.createEmbeddingProvider(this.config.embedding, logger);
|
|
2026
2108
|
}
|
|
2027
2109
|
catch (error) {
|
|
2028
|
-
logger.fatal({ error }, 'Failed to create embedding provider');
|
|
2110
|
+
logger.fatal({ err: normalizeError(error) }, 'Failed to create embedding provider');
|
|
2029
2111
|
throw error;
|
|
2030
2112
|
}
|
|
2031
2113
|
const vectorStore = this.factories.createVectorStoreClient(this.config.vectorStore, embeddingProvider.dimensions, logger);
|
|
@@ -2132,7 +2214,7 @@ class JeevesWatcher {
|
|
|
2132
2214
|
logger.info({ configPath: this.configPath, rules: compiledRules.length }, 'Config reloaded');
|
|
2133
2215
|
}
|
|
2134
2216
|
catch (error) {
|
|
2135
|
-
logger.error({ error }, 'Failed to reload config');
|
|
2217
|
+
logger.error({ err: normalizeError(error) }, 'Failed to reload config');
|
|
2136
2218
|
}
|
|
2137
2219
|
}
|
|
2138
2220
|
}
|
|
@@ -22,6 +22,31 @@ import { MarkdownTextSplitter, RecursiveCharacterTextSplitter } from '@langchain
|
|
|
22
22
|
import { QdrantClient } from '@qdrant/js-client-rest';
|
|
23
23
|
import chokidar from 'chokidar';
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* @module util/normalizeError
|
|
27
|
+
*
|
|
28
|
+
* Normalizes unknown thrown values into proper Error objects for pino serialization.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Convert an unknown thrown value into a proper Error with message, stack, and cause.
|
|
32
|
+
* Pino's built-in `err` serializer requires an Error instance to extract message/stack.
|
|
33
|
+
*
|
|
34
|
+
* @param error - The caught value (may not be an Error).
|
|
35
|
+
* @returns A proper Error instance.
|
|
36
|
+
*/
|
|
37
|
+
function normalizeError(error) {
|
|
38
|
+
if (error instanceof Error)
|
|
39
|
+
return error;
|
|
40
|
+
if (typeof error === 'string')
|
|
41
|
+
return new Error(error);
|
|
42
|
+
const message = typeof error === 'object' && error !== null && 'message' in error
|
|
43
|
+
? String(error.message)
|
|
44
|
+
: String(error);
|
|
45
|
+
const normalized = new Error(message);
|
|
46
|
+
normalized.cause = error;
|
|
47
|
+
return normalized;
|
|
48
|
+
}
|
|
49
|
+
|
|
25
50
|
/**
|
|
26
51
|
* Best-effort base directory inference for a glob pattern.
|
|
27
52
|
*
|
|
@@ -144,13 +169,13 @@ function createConfigReindexHandler(deps) {
|
|
|
144
169
|
}
|
|
145
170
|
}
|
|
146
171
|
catch (error) {
|
|
147
|
-
deps.logger.error({ error, scope }, 'Config reindex failed');
|
|
172
|
+
deps.logger.error({ err: normalizeError(error), scope }, 'Config reindex failed');
|
|
148
173
|
}
|
|
149
174
|
})();
|
|
150
175
|
return await reply.status(200).send({ status: 'started', scope });
|
|
151
176
|
}
|
|
152
177
|
catch (error) {
|
|
153
|
-
deps.logger.error({ error }, 'Config reindex request failed');
|
|
178
|
+
deps.logger.error({ err: normalizeError(error) }, 'Config reindex request failed');
|
|
154
179
|
return await reply.status(500).send({ error: 'Internal server error' });
|
|
155
180
|
}
|
|
156
181
|
};
|
|
@@ -173,7 +198,7 @@ function createMetadataHandler(deps) {
|
|
|
173
198
|
return { ok: true };
|
|
174
199
|
}
|
|
175
200
|
catch (error) {
|
|
176
|
-
deps.logger.error({ error }, 'Metadata update failed');
|
|
201
|
+
deps.logger.error({ err: normalizeError(error) }, 'Metadata update failed');
|
|
177
202
|
return reply.status(500).send({ error: 'Internal server error' });
|
|
178
203
|
}
|
|
179
204
|
};
|
|
@@ -296,7 +321,7 @@ function createRebuildMetadataHandler(deps) {
|
|
|
296
321
|
return await reply.status(200).send({ ok: true });
|
|
297
322
|
}
|
|
298
323
|
catch (error) {
|
|
299
|
-
deps.logger.error({ error }, 'Rebuild metadata failed');
|
|
324
|
+
deps.logger.error({ err: normalizeError(error) }, 'Rebuild metadata failed');
|
|
300
325
|
return await reply.status(500).send({ error: 'Internal server error' });
|
|
301
326
|
}
|
|
302
327
|
};
|
|
@@ -318,7 +343,7 @@ function createReindexHandler(deps) {
|
|
|
318
343
|
return await reply.status(200).send({ ok: true, filesIndexed: count });
|
|
319
344
|
}
|
|
320
345
|
catch (error) {
|
|
321
|
-
deps.logger.error({ error }, 'Reindex failed');
|
|
346
|
+
deps.logger.error({ err: normalizeError(error) }, 'Reindex failed');
|
|
322
347
|
return await reply.status(500).send({ error: 'Internal server error' });
|
|
323
348
|
}
|
|
324
349
|
};
|
|
@@ -342,7 +367,7 @@ function createSearchHandler(deps) {
|
|
|
342
367
|
return results;
|
|
343
368
|
}
|
|
344
369
|
catch (error) {
|
|
345
|
-
deps.logger.error({ error }, 'Search failed');
|
|
370
|
+
deps.logger.error({ err: normalizeError(error) }, 'Search failed');
|
|
346
371
|
return reply.status(500).send({ error: 'Internal server error' });
|
|
347
372
|
}
|
|
348
373
|
};
|
|
@@ -645,6 +670,49 @@ const jeevesWatcherConfigSchema = z.object({
|
|
|
645
670
|
.describe('Timeout in milliseconds for graceful shutdown.'),
|
|
646
671
|
});
|
|
647
672
|
|
|
673
|
+
/**
|
|
674
|
+
* @module config/substituteEnvVars
|
|
675
|
+
*
|
|
676
|
+
* Deep-walks config objects and replaces `${VAR_NAME}` patterns with environment variable values.
|
|
677
|
+
*/
|
|
678
|
+
const ENV_PATTERN = /\$\{([^}]+)\}/g;
|
|
679
|
+
/**
|
|
680
|
+
* Replace `${VAR_NAME}` patterns in a string with `process.env.VAR_NAME`.
|
|
681
|
+
*
|
|
682
|
+
* @param value - The string to process.
|
|
683
|
+
* @returns The string with resolved env vars; unresolvable expressions left untouched.
|
|
684
|
+
*/
|
|
685
|
+
function substituteString(value) {
|
|
686
|
+
return value.replace(ENV_PATTERN, (match, varName) => {
|
|
687
|
+
const envValue = process.env[varName];
|
|
688
|
+
if (envValue === undefined)
|
|
689
|
+
return match;
|
|
690
|
+
return envValue;
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Deep-walk a value and substitute `${VAR_NAME}` patterns in all string values.
|
|
695
|
+
*
|
|
696
|
+
* @param value - The value to walk (object, array, or primitive).
|
|
697
|
+
* @returns A new value with all env var references resolved.
|
|
698
|
+
*/
|
|
699
|
+
function substituteEnvVars(value) {
|
|
700
|
+
if (typeof value === 'string') {
|
|
701
|
+
return substituteString(value);
|
|
702
|
+
}
|
|
703
|
+
if (Array.isArray(value)) {
|
|
704
|
+
return value.map((item) => substituteEnvVars(item));
|
|
705
|
+
}
|
|
706
|
+
if (value !== null && typeof value === 'object') {
|
|
707
|
+
const result = {};
|
|
708
|
+
for (const [key, val] of Object.entries(value)) {
|
|
709
|
+
result[key] = substituteEnvVars(val);
|
|
710
|
+
}
|
|
711
|
+
return result;
|
|
712
|
+
}
|
|
713
|
+
return value;
|
|
714
|
+
}
|
|
715
|
+
|
|
648
716
|
const MODULE_NAME = 'jeeves-watcher';
|
|
649
717
|
/**
|
|
650
718
|
* Merge sensible defaults into a loaded configuration.
|
|
@@ -680,7 +748,8 @@ async function loadConfig(configPath) {
|
|
|
680
748
|
}
|
|
681
749
|
try {
|
|
682
750
|
const validated = jeevesWatcherConfigSchema.parse(result.config);
|
|
683
|
-
|
|
751
|
+
const withDefaults = applyDefaults(validated);
|
|
752
|
+
return substituteEnvVars(withDefaults);
|
|
684
753
|
}
|
|
685
754
|
catch (error) {
|
|
686
755
|
if (error instanceof ZodError) {
|
|
@@ -847,7 +916,7 @@ function createGeminiProvider(config, logger) {
|
|
|
847
916
|
delayMs,
|
|
848
917
|
provider: 'gemini',
|
|
849
918
|
model: config.model,
|
|
850
|
-
error,
|
|
919
|
+
err: normalizeError(error),
|
|
851
920
|
}, 'Embedding call failed; will retry');
|
|
852
921
|
},
|
|
853
922
|
});
|
|
@@ -956,6 +1025,9 @@ function pointId(filePath, chunkIndex) {
|
|
|
956
1025
|
*/
|
|
957
1026
|
function extractMarkdownFrontmatter(markdown) {
|
|
958
1027
|
const trimmed = markdown.replace(/^\uFEFF/, '');
|
|
1028
|
+
// Only attempt frontmatter parsing if the file starts with ---
|
|
1029
|
+
if (!/^\s*---/.test(trimmed))
|
|
1030
|
+
return { body: markdown };
|
|
959
1031
|
const match = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m.exec(trimmed);
|
|
960
1032
|
if (!match)
|
|
961
1033
|
return { body: markdown };
|
|
@@ -1413,7 +1485,7 @@ class DocumentProcessor {
|
|
|
1413
1485
|
this.logger.info({ filePath, chunks: chunks.length }, 'File processed successfully');
|
|
1414
1486
|
}
|
|
1415
1487
|
catch (error) {
|
|
1416
|
-
this.logger.error({ filePath, error }, 'Failed to process file');
|
|
1488
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to process file');
|
|
1417
1489
|
}
|
|
1418
1490
|
}
|
|
1419
1491
|
/**
|
|
@@ -1433,7 +1505,7 @@ class DocumentProcessor {
|
|
|
1433
1505
|
this.logger.info({ filePath }, 'File deleted from index');
|
|
1434
1506
|
}
|
|
1435
1507
|
catch (error) {
|
|
1436
|
-
this.logger.error({ filePath, error }, 'Failed to delete file');
|
|
1508
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to delete file');
|
|
1437
1509
|
}
|
|
1438
1510
|
}
|
|
1439
1511
|
/**
|
|
@@ -1461,7 +1533,7 @@ class DocumentProcessor {
|
|
|
1461
1533
|
return merged;
|
|
1462
1534
|
}
|
|
1463
1535
|
catch (error) {
|
|
1464
|
-
this.logger.error({ filePath, error }, 'Failed to update metadata');
|
|
1536
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to update metadata');
|
|
1465
1537
|
return null;
|
|
1466
1538
|
}
|
|
1467
1539
|
}
|
|
@@ -1491,7 +1563,7 @@ class DocumentProcessor {
|
|
|
1491
1563
|
return metadata;
|
|
1492
1564
|
}
|
|
1493
1565
|
catch (error) {
|
|
1494
|
-
this.logger.error({ filePath, error }, 'Failed to re-apply rules');
|
|
1566
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to re-apply rules');
|
|
1495
1567
|
return null;
|
|
1496
1568
|
}
|
|
1497
1569
|
}
|
|
@@ -1719,7 +1791,12 @@ class VectorStoreClient {
|
|
|
1719
1791
|
maxDelayMs: 10_000,
|
|
1720
1792
|
jitter: 0.2,
|
|
1721
1793
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1722
|
-
this.log.warn({
|
|
1794
|
+
this.log.warn({
|
|
1795
|
+
attempt,
|
|
1796
|
+
delayMs,
|
|
1797
|
+
operation: 'qdrant.upsert',
|
|
1798
|
+
err: normalizeError(error),
|
|
1799
|
+
}, 'Qdrant upsert failed; will retry');
|
|
1723
1800
|
},
|
|
1724
1801
|
});
|
|
1725
1802
|
}
|
|
@@ -1745,7 +1822,12 @@ class VectorStoreClient {
|
|
|
1745
1822
|
maxDelayMs: 10_000,
|
|
1746
1823
|
jitter: 0.2,
|
|
1747
1824
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1748
|
-
this.log.warn({
|
|
1825
|
+
this.log.warn({
|
|
1826
|
+
attempt,
|
|
1827
|
+
delayMs,
|
|
1828
|
+
operation: 'qdrant.delete',
|
|
1829
|
+
err: normalizeError(error),
|
|
1830
|
+
}, 'Qdrant delete failed; will retry');
|
|
1749
1831
|
},
|
|
1750
1832
|
});
|
|
1751
1833
|
}
|
|
@@ -1899,7 +1981,7 @@ class FileSystemWatcher {
|
|
|
1899
1981
|
this.queue.enqueue({ type: 'delete', path, priority: 'normal' }, () => this.processor.deleteFile(path));
|
|
1900
1982
|
});
|
|
1901
1983
|
this.watcher.on('error', (error) => {
|
|
1902
|
-
this.logger.error({ error }, 'Watcher error');
|
|
1984
|
+
this.logger.error({ err: normalizeError(error) }, 'Watcher error');
|
|
1903
1985
|
});
|
|
1904
1986
|
this.queue.process();
|
|
1905
1987
|
this.logger.info({ paths: this.config.paths }, 'Filesystem watcher started');
|
|
@@ -1944,7 +2026,7 @@ class ConfigWatcher {
|
|
|
1944
2026
|
}, this.options.debounceMs);
|
|
1945
2027
|
});
|
|
1946
2028
|
this.watcher.on('error', (error) => {
|
|
1947
|
-
this.options.logger.error({ error }, 'Config watcher error');
|
|
2029
|
+
this.options.logger.error({ err: normalizeError(error) }, 'Config watcher error');
|
|
1948
2030
|
});
|
|
1949
2031
|
this.options.logger.info({
|
|
1950
2032
|
configPath: this.options.configPath,
|
|
@@ -2028,7 +2110,7 @@ class JeevesWatcher {
|
|
|
2028
2110
|
embeddingProvider = this.factories.createEmbeddingProvider(this.config.embedding, logger);
|
|
2029
2111
|
}
|
|
2030
2112
|
catch (error) {
|
|
2031
|
-
logger.fatal({ error }, 'Failed to create embedding provider');
|
|
2113
|
+
logger.fatal({ err: normalizeError(error) }, 'Failed to create embedding provider');
|
|
2032
2114
|
throw error;
|
|
2033
2115
|
}
|
|
2034
2116
|
const vectorStore = this.factories.createVectorStoreClient(this.config.vectorStore, embeddingProvider.dimensions, logger);
|
|
@@ -2135,7 +2217,7 @@ class JeevesWatcher {
|
|
|
2135
2217
|
logger.info({ configPath: this.configPath, rules: compiledRules.length }, 'Config reloaded');
|
|
2136
2218
|
}
|
|
2137
2219
|
catch (error) {
|
|
2138
|
-
logger.error({ error }, 'Failed to reload config');
|
|
2220
|
+
logger.error({ err: normalizeError(error) }, 'Failed to reload config');
|
|
2139
2221
|
}
|
|
2140
2222
|
}
|
|
2141
2223
|
}
|
package/dist/index.iife.js
CHANGED
|
@@ -20,6 +20,31 @@
|
|
|
20
20
|
|
|
21
21
|
var cheerio__namespace = /*#__PURE__*/_interopNamespaceDefault(cheerio);
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @module util/normalizeError
|
|
25
|
+
*
|
|
26
|
+
* Normalizes unknown thrown values into proper Error objects for pino serialization.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Convert an unknown thrown value into a proper Error with message, stack, and cause.
|
|
30
|
+
* Pino's built-in `err` serializer requires an Error instance to extract message/stack.
|
|
31
|
+
*
|
|
32
|
+
* @param error - The caught value (may not be an Error).
|
|
33
|
+
* @returns A proper Error instance.
|
|
34
|
+
*/
|
|
35
|
+
function normalizeError(error) {
|
|
36
|
+
if (error instanceof Error)
|
|
37
|
+
return error;
|
|
38
|
+
if (typeof error === 'string')
|
|
39
|
+
return new Error(error);
|
|
40
|
+
const message = typeof error === 'object' && error !== null && 'message' in error
|
|
41
|
+
? String(error.message)
|
|
42
|
+
: String(error);
|
|
43
|
+
const normalized = new Error(message);
|
|
44
|
+
normalized.cause = error;
|
|
45
|
+
return normalized;
|
|
46
|
+
}
|
|
47
|
+
|
|
23
48
|
/**
|
|
24
49
|
* Best-effort base directory inference for a glob pattern.
|
|
25
50
|
*
|
|
@@ -142,13 +167,13 @@
|
|
|
142
167
|
}
|
|
143
168
|
}
|
|
144
169
|
catch (error) {
|
|
145
|
-
deps.logger.error({ error, scope }, 'Config reindex failed');
|
|
170
|
+
deps.logger.error({ err: normalizeError(error), scope }, 'Config reindex failed');
|
|
146
171
|
}
|
|
147
172
|
})();
|
|
148
173
|
return await reply.status(200).send({ status: 'started', scope });
|
|
149
174
|
}
|
|
150
175
|
catch (error) {
|
|
151
|
-
deps.logger.error({ error }, 'Config reindex request failed');
|
|
176
|
+
deps.logger.error({ err: normalizeError(error) }, 'Config reindex request failed');
|
|
152
177
|
return await reply.status(500).send({ error: 'Internal server error' });
|
|
153
178
|
}
|
|
154
179
|
};
|
|
@@ -171,7 +196,7 @@
|
|
|
171
196
|
return { ok: true };
|
|
172
197
|
}
|
|
173
198
|
catch (error) {
|
|
174
|
-
deps.logger.error({ error }, 'Metadata update failed');
|
|
199
|
+
deps.logger.error({ err: normalizeError(error) }, 'Metadata update failed');
|
|
175
200
|
return reply.status(500).send({ error: 'Internal server error' });
|
|
176
201
|
}
|
|
177
202
|
};
|
|
@@ -294,7 +319,7 @@
|
|
|
294
319
|
return await reply.status(200).send({ ok: true });
|
|
295
320
|
}
|
|
296
321
|
catch (error) {
|
|
297
|
-
deps.logger.error({ error }, 'Rebuild metadata failed');
|
|
322
|
+
deps.logger.error({ err: normalizeError(error) }, 'Rebuild metadata failed');
|
|
298
323
|
return await reply.status(500).send({ error: 'Internal server error' });
|
|
299
324
|
}
|
|
300
325
|
};
|
|
@@ -316,7 +341,7 @@
|
|
|
316
341
|
return await reply.status(200).send({ ok: true, filesIndexed: count });
|
|
317
342
|
}
|
|
318
343
|
catch (error) {
|
|
319
|
-
deps.logger.error({ error }, 'Reindex failed');
|
|
344
|
+
deps.logger.error({ err: normalizeError(error) }, 'Reindex failed');
|
|
320
345
|
return await reply.status(500).send({ error: 'Internal server error' });
|
|
321
346
|
}
|
|
322
347
|
};
|
|
@@ -340,7 +365,7 @@
|
|
|
340
365
|
return results;
|
|
341
366
|
}
|
|
342
367
|
catch (error) {
|
|
343
|
-
deps.logger.error({ error }, 'Search failed');
|
|
368
|
+
deps.logger.error({ err: normalizeError(error) }, 'Search failed');
|
|
344
369
|
return reply.status(500).send({ error: 'Internal server error' });
|
|
345
370
|
}
|
|
346
371
|
};
|
|
@@ -622,6 +647,49 @@
|
|
|
622
647
|
.describe('Timeout in milliseconds for graceful shutdown.'),
|
|
623
648
|
});
|
|
624
649
|
|
|
650
|
+
/**
|
|
651
|
+
* @module config/substituteEnvVars
|
|
652
|
+
*
|
|
653
|
+
* Deep-walks config objects and replaces `${VAR_NAME}` patterns with environment variable values.
|
|
654
|
+
*/
|
|
655
|
+
const ENV_PATTERN = /\$\{([^}]+)\}/g;
|
|
656
|
+
/**
|
|
657
|
+
* Replace `${VAR_NAME}` patterns in a string with `process.env.VAR_NAME`.
|
|
658
|
+
*
|
|
659
|
+
* @param value - The string to process.
|
|
660
|
+
* @returns The string with resolved env vars; unresolvable expressions left untouched.
|
|
661
|
+
*/
|
|
662
|
+
function substituteString(value) {
|
|
663
|
+
return value.replace(ENV_PATTERN, (match, varName) => {
|
|
664
|
+
const envValue = process.env[varName];
|
|
665
|
+
if (envValue === undefined)
|
|
666
|
+
return match;
|
|
667
|
+
return envValue;
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Deep-walk a value and substitute `${VAR_NAME}` patterns in all string values.
|
|
672
|
+
*
|
|
673
|
+
* @param value - The value to walk (object, array, or primitive).
|
|
674
|
+
* @returns A new value with all env var references resolved.
|
|
675
|
+
*/
|
|
676
|
+
function substituteEnvVars(value) {
|
|
677
|
+
if (typeof value === 'string') {
|
|
678
|
+
return substituteString(value);
|
|
679
|
+
}
|
|
680
|
+
if (Array.isArray(value)) {
|
|
681
|
+
return value.map((item) => substituteEnvVars(item));
|
|
682
|
+
}
|
|
683
|
+
if (value !== null && typeof value === 'object') {
|
|
684
|
+
const result = {};
|
|
685
|
+
for (const [key, val] of Object.entries(value)) {
|
|
686
|
+
result[key] = substituteEnvVars(val);
|
|
687
|
+
}
|
|
688
|
+
return result;
|
|
689
|
+
}
|
|
690
|
+
return value;
|
|
691
|
+
}
|
|
692
|
+
|
|
625
693
|
const MODULE_NAME = 'jeeves-watcher';
|
|
626
694
|
/**
|
|
627
695
|
* Merge sensible defaults into a loaded configuration.
|
|
@@ -657,7 +725,8 @@
|
|
|
657
725
|
}
|
|
658
726
|
try {
|
|
659
727
|
const validated = jeevesWatcherConfigSchema.parse(result.config);
|
|
660
|
-
|
|
728
|
+
const withDefaults = applyDefaults(validated);
|
|
729
|
+
return substituteEnvVars(withDefaults);
|
|
661
730
|
}
|
|
662
731
|
catch (error) {
|
|
663
732
|
if (error instanceof zod.ZodError) {
|
|
@@ -824,7 +893,7 @@
|
|
|
824
893
|
delayMs,
|
|
825
894
|
provider: 'gemini',
|
|
826
895
|
model: config.model,
|
|
827
|
-
error,
|
|
896
|
+
err: normalizeError(error),
|
|
828
897
|
}, 'Embedding call failed; will retry');
|
|
829
898
|
},
|
|
830
899
|
});
|
|
@@ -933,6 +1002,9 @@
|
|
|
933
1002
|
*/
|
|
934
1003
|
function extractMarkdownFrontmatter(markdown) {
|
|
935
1004
|
const trimmed = markdown.replace(/^\uFEFF/, '');
|
|
1005
|
+
// Only attempt frontmatter parsing if the file starts with ---
|
|
1006
|
+
if (!/^\s*---/.test(trimmed))
|
|
1007
|
+
return { body: markdown };
|
|
936
1008
|
const match = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m.exec(trimmed);
|
|
937
1009
|
if (!match)
|
|
938
1010
|
return { body: markdown };
|
|
@@ -1390,7 +1462,7 @@
|
|
|
1390
1462
|
this.logger.info({ filePath, chunks: chunks.length }, 'File processed successfully');
|
|
1391
1463
|
}
|
|
1392
1464
|
catch (error) {
|
|
1393
|
-
this.logger.error({ filePath, error }, 'Failed to process file');
|
|
1465
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to process file');
|
|
1394
1466
|
}
|
|
1395
1467
|
}
|
|
1396
1468
|
/**
|
|
@@ -1410,7 +1482,7 @@
|
|
|
1410
1482
|
this.logger.info({ filePath }, 'File deleted from index');
|
|
1411
1483
|
}
|
|
1412
1484
|
catch (error) {
|
|
1413
|
-
this.logger.error({ filePath, error }, 'Failed to delete file');
|
|
1485
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to delete file');
|
|
1414
1486
|
}
|
|
1415
1487
|
}
|
|
1416
1488
|
/**
|
|
@@ -1438,7 +1510,7 @@
|
|
|
1438
1510
|
return merged;
|
|
1439
1511
|
}
|
|
1440
1512
|
catch (error) {
|
|
1441
|
-
this.logger.error({ filePath, error }, 'Failed to update metadata');
|
|
1513
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to update metadata');
|
|
1442
1514
|
return null;
|
|
1443
1515
|
}
|
|
1444
1516
|
}
|
|
@@ -1468,7 +1540,7 @@
|
|
|
1468
1540
|
return metadata;
|
|
1469
1541
|
}
|
|
1470
1542
|
catch (error) {
|
|
1471
|
-
this.logger.error({ filePath, error }, 'Failed to re-apply rules');
|
|
1543
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to re-apply rules');
|
|
1472
1544
|
return null;
|
|
1473
1545
|
}
|
|
1474
1546
|
}
|
|
@@ -1696,7 +1768,12 @@
|
|
|
1696
1768
|
maxDelayMs: 10_000,
|
|
1697
1769
|
jitter: 0.2,
|
|
1698
1770
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1699
|
-
this.log.warn({
|
|
1771
|
+
this.log.warn({
|
|
1772
|
+
attempt,
|
|
1773
|
+
delayMs,
|
|
1774
|
+
operation: 'qdrant.upsert',
|
|
1775
|
+
err: normalizeError(error),
|
|
1776
|
+
}, 'Qdrant upsert failed; will retry');
|
|
1700
1777
|
},
|
|
1701
1778
|
});
|
|
1702
1779
|
}
|
|
@@ -1722,7 +1799,12 @@
|
|
|
1722
1799
|
maxDelayMs: 10_000,
|
|
1723
1800
|
jitter: 0.2,
|
|
1724
1801
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1725
|
-
this.log.warn({
|
|
1802
|
+
this.log.warn({
|
|
1803
|
+
attempt,
|
|
1804
|
+
delayMs,
|
|
1805
|
+
operation: 'qdrant.delete',
|
|
1806
|
+
err: normalizeError(error),
|
|
1807
|
+
}, 'Qdrant delete failed; will retry');
|
|
1726
1808
|
},
|
|
1727
1809
|
});
|
|
1728
1810
|
}
|
|
@@ -1876,7 +1958,7 @@
|
|
|
1876
1958
|
this.queue.enqueue({ type: 'delete', path, priority: 'normal' }, () => this.processor.deleteFile(path));
|
|
1877
1959
|
});
|
|
1878
1960
|
this.watcher.on('error', (error) => {
|
|
1879
|
-
this.logger.error({ error }, 'Watcher error');
|
|
1961
|
+
this.logger.error({ err: normalizeError(error) }, 'Watcher error');
|
|
1880
1962
|
});
|
|
1881
1963
|
this.queue.process();
|
|
1882
1964
|
this.logger.info({ paths: this.config.paths }, 'Filesystem watcher started');
|
|
@@ -1921,7 +2003,7 @@
|
|
|
1921
2003
|
}, this.options.debounceMs);
|
|
1922
2004
|
});
|
|
1923
2005
|
this.watcher.on('error', (error) => {
|
|
1924
|
-
this.options.logger.error({ error }, 'Config watcher error');
|
|
2006
|
+
this.options.logger.error({ err: normalizeError(error) }, 'Config watcher error');
|
|
1925
2007
|
});
|
|
1926
2008
|
this.options.logger.info({
|
|
1927
2009
|
configPath: this.options.configPath,
|
|
@@ -2005,7 +2087,7 @@
|
|
|
2005
2087
|
embeddingProvider = this.factories.createEmbeddingProvider(this.config.embedding, logger);
|
|
2006
2088
|
}
|
|
2007
2089
|
catch (error) {
|
|
2008
|
-
logger.fatal({ error }, 'Failed to create embedding provider');
|
|
2090
|
+
logger.fatal({ err: normalizeError(error) }, 'Failed to create embedding provider');
|
|
2009
2091
|
throw error;
|
|
2010
2092
|
}
|
|
2011
2093
|
const vectorStore = this.factories.createVectorStoreClient(this.config.vectorStore, embeddingProvider.dimensions, logger);
|
|
@@ -2112,7 +2194,7 @@
|
|
|
2112
2194
|
logger.info({ configPath: this.configPath, rules: compiledRules.length }, 'Config reloaded');
|
|
2113
2195
|
}
|
|
2114
2196
|
catch (error) {
|
|
2115
|
-
logger.error({ error }, 'Failed to reload config');
|
|
2197
|
+
logger.error({ err: normalizeError(error) }, 'Failed to reload config');
|
|
2116
2198
|
}
|
|
2117
2199
|
}
|
|
2118
2200
|
}
|
package/dist/index.iife.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e,t,i,r,o,n,s,a,c,l,d,h,u,g,f,p,m,y,w,b,v){"use strict";function M(e){var t=Object.create(null);return e&&Object.keys(e).forEach((function(i){if("default"!==i){var r=Object.getOwnPropertyDescriptor(e,i);Object.defineProperty(t,i,r.get?r:{enumerable:!0,get:function(){return e[i]}})}})),t.default=e,Object.freeze(t)}var P=M(g);function S(e){const t=e.replace(/\\/g,"/"),i=t.search(/[*?\[]/);if(-1===i)return r.resolve(e);const o=t.slice(0,i),n=o.endsWith("/")?o.slice(0,-1):r.dirname(o);return r.resolve(n)}async function*k(e){let t;try{t=(await i.readdir(e,{withFileTypes:!0})).map((e=>({name:e.name,isDirectory:e.isDirectory()})))}catch{return}for(const o of t){const t=r.resolve(e,o.name);if(o.isDirectory)yield*k(t);else try{(await i.stat(t)).isFile()&&(yield t)}catch{}}}async function x(e,t,i,r){const n=await async function(e,t=[]){const i=e.map((e=>e.replace(/\\/g,"/"))),r=t.map((e=>e.replace(/\\/g,"/"))),n=o(i,{dot:!0}),s=r.length?o(r,{dot:!0}):()=>!1,a=Array.from(new Set(e.map(S))),c=new Set;for(const e of a)for await(const t of k(e)){const e=t.replace(/\\/g,"/");s(e)||n(e)&&c.add(t)}return Array.from(c)}(e,t);for(const e of n)await i[r](e);return n.length}function z(e,t=!1){let i=e.replace(/\\/g,"/").toLowerCase();return t&&(i=i.replace(/^([a-z]):/,((e,t)=>t))),i}function j(e,t){const i=z(e,!0),o=s.createHash("sha256").update(i,"utf8").digest("hex");return r.join(t,`${o}.meta.json`)}async function C(e,t){try{const r=await i.readFile(j(e,t),"utf8");return JSON.parse(r)}catch{return null}}async function F(e,t,o){const n=j(e,t);await i.mkdir(r.dirname(n),{recursive:!0}),await i.writeFile(n,JSON.stringify(o,null,2),"utf8")}async function R(e,t){try{await i.rm(j(e,t))}catch{}}const T=["file_path","chunk_index","total_chunks","content_hash","chunk_text"];function I(e){const{processor:i,vectorStore:r,embeddingProvider:o,logger:s,config:a}=e,c=t({logger:!1});var l;return c.get("/status",(()=>({status:"ok",uptime:process.uptime()}))),c.post("/metadata",(l={processor:i,logger:s},async(e,t)=>{try{const{path:t,metadata:i}=e.body;return await l.processor.processMetadataUpdate(t,i),{ok:!0}}catch(e){return l.logger.error({error:e},"Metadata update failed"),t.status(500).send({error:"Internal server error"})}})),c.post("/search",function(e){return async(t,i)=>{try{const{query:i,limit:r=10}=t.body,o=await e.embeddingProvider.embed([i]);return await e.vectorStore.search(o[0],r)}catch(t){return e.logger.error({error:t},"Search failed"),i.status(500).send({error:"Internal server error"})}}}({embeddingProvider:o,vectorStore:r,logger:s})),c.post("/reindex",function(e){return async(t,i)=>{try{const t=await x(e.config.watch.paths,e.config.watch.ignored,e.processor,"processFile");return await i.status(200).send({ok:!0,filesIndexed:t})}catch(t){return e.logger.error({error:t},"Reindex failed"),await i.status(500).send({error:"Internal server error"})}}}({config:a,processor:i,logger:s})),c.post("/rebuild-metadata",function(e){return async(t,i)=>{try{const t=e.config.metadataDir??".jeeves-metadata",r=[...T];for await(const i of e.vectorStore.scroll()){const e=i.payload,o=e.file_path;if("string"!=typeof o||0===o.length)continue;const s=n.omit(e,r);await F(o,t,s)}return await i.status(200).send({ok:!0})}catch(t){return e.logger.error({error:t},"Rebuild metadata failed"),await i.status(500).send({error:"Internal server error"})}}}({config:a,vectorStore:r,logger:s})),c.post("/config-reindex",function(e){return async(t,i)=>{try{const r=t.body.scope??"rules";return(async()=>{try{if("rules"===r){const t=await x(e.config.watch.paths,e.config.watch.ignored,e.processor,"processRulesUpdate");e.logger.info({scope:r,filesProcessed:t},"Config reindex (rules) completed")}else{const t=await x(e.config.watch.paths,e.config.watch.ignored,e.processor,"processFile");e.logger.info({scope:r,filesProcessed:t},"Config reindex (full) completed")}}catch(t){e.logger.error({error:t,scope:r},"Config reindex failed")}})(),await i.status(200).send({status:"started",scope:r})}catch(t){return e.logger.error({error:t},"Config reindex request failed"),await i.status(500).send({error:"Internal server error"})}}}({config:a,processor:i,logger:s})),c}const D={metadataDir:".jeeves-watcher",shutdownTimeoutMs:1e4},E={enabled:!0,debounceMs:1e3},W={host:"127.0.0.1",port:3456},N={level:"info"},q={debounceMs:300,stabilityThresholdMs:500,usePolling:!1,pollIntervalMs:1e3},A={chunkSize:1e3,chunkOverlap:200,dimensions:3072,rateLimitPerMinute:300,concurrency:5},_=c.z.object({paths:c.z.array(c.z.string()).min(1).describe('Glob patterns for files to watch (e.g., "**/*.md"). At least one required.'),ignored:c.z.array(c.z.string()).optional().describe('Glob patterns to exclude from watching (e.g., "**/node_modules/**").'),pollIntervalMs:c.z.number().optional().describe("Polling interval in milliseconds when usePolling is enabled."),usePolling:c.z.boolean().optional().describe("Use polling instead of native file system events (for network drives)."),debounceMs:c.z.number().optional().describe("Debounce delay in milliseconds for file change events."),stabilityThresholdMs:c.z.number().optional().describe("Time in milliseconds a file must remain unchanged before processing.")}),L=c.z.object({enabled:c.z.boolean().optional().describe("Enable automatic reloading when config file changes."),debounceMs:c.z.number().optional().describe("Debounce delay in milliseconds for config file change detection.")}),O=c.z.object({provider:c.z.string().default("gemini").describe('Embedding provider name (e.g., "gemini", "openai").'),model:c.z.string().default("gemini-embedding-001").describe('Embedding model identifier (e.g., "gemini-embedding-001", "text-embedding-3-small").'),chunkSize:c.z.number().optional().describe("Maximum chunk size in characters for text splitting."),chunkOverlap:c.z.number().optional().describe("Character overlap between consecutive chunks."),dimensions:c.z.number().optional().describe("Embedding vector dimensions (must match model output)."),apiKey:c.z.string().optional().describe("API key for embedding provider (supports ${ENV_VAR} substitution)."),rateLimitPerMinute:c.z.number().optional().describe("Maximum embedding API requests per minute (rate limiting)."),concurrency:c.z.number().optional().describe("Maximum concurrent embedding requests.")}),Q=c.z.object({url:c.z.string().describe('Qdrant server URL (e.g., "http://localhost:6333").'),collectionName:c.z.string().describe("Qdrant collection name for vector storage."),apiKey:c.z.string().optional().describe("Qdrant API key for authentication (supports ${ENV_VAR} substitution).")}),$=c.z.object({host:c.z.string().optional().describe('Host address for API server (e.g., "127.0.0.1", "0.0.0.0").'),port:c.z.number().optional().describe("Port for API server (e.g., 3456).")}),K=c.z.object({level:c.z.string().optional().describe("Logging level (trace, debug, info, warn, error, fatal)."),file:c.z.string().optional().describe("Path to log file (logs to stdout if omitted).")}),J=c.z.object({match:c.z.record(c.z.string(),c.z.unknown()).describe("JSON Schema object to match against file attributes."),set:c.z.record(c.z.string(),c.z.unknown()).describe("Metadata fields to set when match succeeds."),map:c.z.union([l.jsonMapMapSchema,c.z.string()]).optional().describe("JsonMap transformation (inline definition or named map reference).")}),G=c.z.object({watch:_.describe("File system watch configuration."),configWatch:L.optional().describe("Configuration file watch settings."),embedding:O.describe("Embedding model configuration."),vectorStore:Q.describe("Qdrant vector store configuration."),metadataDir:c.z.string().optional().describe("Directory for persisted metadata sidecar files."),api:$.optional().describe("API server configuration."),extractors:c.z.record(c.z.string(),c.z.unknown()).optional().describe("Extractor configurations keyed by name."),inferenceRules:c.z.array(J).optional().describe("Rules for inferring metadata from file attributes."),maps:c.z.record(c.z.string(),l.jsonMapMapSchema).optional().describe("Reusable named JsonMap transformations."),logging:K.optional().describe("Logging configuration."),shutdownTimeoutMs:c.z.number().optional().describe("Timeout in milliseconds for graceful shutdown.")}),U="jeeves-watcher";async function V(e){const t=a.cosmiconfig(U),i=e?await t.load(e):await t.search();if(!i||i.isEmpty)throw new Error("No jeeves-watcher configuration found. Create a .jeeves-watcherrc or jeeves-watcher.config.{js,ts,json,yaml} file.");try{const e=G.parse(i.config);return r=e,{...D,...r,watch:{...q,...r.watch},configWatch:{...E,...r.configWatch},embedding:{...A,...r.embedding},api:{...W,...r.api},logging:{...N,...r.logging}}}catch(e){if(e instanceof c.ZodError){const t=e.issues.map((e=>`${e.path.join(".")}: ${e.message}`)).join("; ");throw new Error(`Invalid jeeves-watcher configuration: ${t}`)}throw e}var r}function B(e){return e||{warn(e,t){t?console.warn(e,t):console.warn(e)}}}function H(e,t){return e<=0?Promise.resolve():new Promise(((i,r)=>{const o=setTimeout((()=>{s(),i()}),e),n=()=>{s(),r(new Error("Retry sleep aborted"))},s=()=>{clearTimeout(o),t&&t.removeEventListener("abort",n)};if(t){if(t.aborted)return void n();t.addEventListener("abort",n,{once:!0})}}))}function Y(e,t,i,r=0){const o=Math.max(0,e-1),n=Math.min(i,t*2**o),s=r>0?1+Math.random()*r:1;return Math.round(n*s)}async function Z(e,t){const i=Math.max(1,t.attempts);let r;for(let o=1;o<=i;o++)try{return await e(o)}catch(e){r=e;if(o>=i)break;const n=Y(o,t.baseDelayMs,t.maxDelayMs,t.jitter);t.onRetry?.({attempt:o,attempts:i,delayMs:n,error:e}),await H(n,t.signal)}throw r}const X=new Map([["mock",function(e){return function(e){return{dimensions:e,embed:t=>Promise.resolve(t.map((t=>{const i=s.createHash("sha256").update(t,"utf8").digest(),r=[];for(let t=0;t<e;t++){const e=i[t%i.length];r.push(e/127.5-1)}return r})))}}(e.dimensions??768)}],["gemini",function(e,t){if(!e.apiKey)throw new Error("Gemini embedding provider requires config.embedding.apiKey");const i=e.dimensions??3072,r=B(t),o=new d.GoogleGenerativeAIEmbeddings({apiKey:e.apiKey,model:e.model});return{dimensions:i,async embed(t){const n=await Z((async i=>(i>1&&r.warn({attempt:i,provider:"gemini",model:e.model},"Retrying embedding request"),o.embedDocuments(t))),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:t,delayMs:i,error:o})=>{r.warn({attempt:t,delayMs:i,provider:"gemini",model:e.model,error:o},"Embedding call failed; will retry")}});for(const e of n)if(e.length!==i)throw new Error(`Gemini embedding returned invalid dimensions: expected ${String(i)}, got ${String(e.length)}`);return n}}}]]);function ee(e,t){const i=X.get(e.provider);if(!i)throw new Error(`Unsupported embedding provider: ${e.provider}`);return i(e,t)}function te(e){const t=e?.level??"info";if(e?.file){const i=h.transport({target:"pino/file",options:{destination:e.file,mkdir:!0}});return h({level:t},i)}return h({level:t})}function ie(e){return s.createHash("sha256").update(e,"utf8").digest("hex")}const re="6a6f686e-6761-4c74-ad6a-656576657321";function oe(e,t){const i=void 0!==t?`${z(e)}#${String(t)}`:z(e);return u.v5(i,re)}const ne=["content","body","text","snippet","subject","description","summary","transcript"];function se(e){if(!e||"object"!=typeof e)return JSON.stringify(e);const t=e;for(const e of ne){const i=t[e];if("string"==typeof i&&i.trim())return i}return JSON.stringify(e)}async function ae(e){const t=await i.readFile(e,"utf8"),{frontmatter:r,body:o}=function(e){const t=e.replace(/^\uFEFF/,""),i=/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m.exec(t);if(!i)return{body:e};const[,r,o]=i,n=f.load(r);return{frontmatter:n&&"object"==typeof n&&!Array.isArray(n)?n:void 0,body:o}}(t);return{text:o,frontmatter:r}}async function ce(e){return{text:await i.readFile(e,"utf8")}}async function le(e){const t=await i.readFile(e,"utf8"),r=P.load(t);r("script, style").remove();return{text:r("body").text().trim()||r.text().trim()}}const de=new Map([[".md",ae],[".markdown",ae],[".txt",ce],[".text",ce],[".json",async function(e){const t=await i.readFile(e,"utf8"),r=JSON.parse(t),o=r&&"object"==typeof r&&!Array.isArray(r)?r:void 0;return{text:se(r),json:o}}],[".pdf",async function(e){const t=await i.readFile(e),r=new Uint8Array(t),{extractText:o}=await import("unpdf"),{text:n}=await o(r);return{text:Array.isArray(n)?n.join("\n\n"):n}}],[".docx",async function(e){const t=await i.readFile(e);return{text:(await p.extractRawText({buffer:t})).value}}],[".html",le],[".htm",le]]);async function he(e,t){const i=de.get(t.toLowerCase());return i?i(e):ce(e)}function ue(e,t){return"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,((e,i)=>{const r=n.get(t,i);return null==r?"":"string"==typeof r?r:JSON.stringify(r)}))}function ge(e,t){const i={};for(const[r,o]of Object.entries(e))i[r]=ue(o,t);return i}async function fe(e,t,i,r){const o={split:(e,t)=>e.split(t),slice:(e,t,i)=>e.slice(t,i),join:(e,t)=>e.join(t),toLowerCase:e=>e.toLowerCase(),replace:(e,t,i)=>e.replace(t,i),get:(e,t)=>n.get(e,t)};let s={};const a=r??console;for(const{rule:r,validate:n}of e)if(n(t)){const e=ge(r.set,t);if(s={...s,...e},r.map){let e;if("string"==typeof r.map){if(e=i?.[r.map],!e){a.warn(`Map reference "${r.map}" not found in named maps. Skipping map transformation.`);continue}}else e=r.map;try{const i=new l.JsonMap(e,o),r=await i.transform(t);r&&"object"==typeof r&&!Array.isArray(r)?s={...s,...r}:a.warn("JsonMap transformation did not return an object; skipping merge.")}catch(e){a.warn(`JsonMap transformation failed: ${e instanceof Error?e.message:String(e)}`)}}}return s}function pe(e,t,i,o){const n=e.replace(/\\/g,"/"),s={file:{path:n,directory:r.dirname(n).replace(/\\/g,"/"),filename:r.basename(n),extension:r.extname(n),sizeBytes:t.size,modified:t.mtime.toISOString()}};return i&&(s.frontmatter=i),o&&(s.json=o),s}function me(e){const t=function(){const e=new m({allErrors:!0});return y(e),e.addKeyword({keyword:"glob",type:"string",schemaType:"string",validate:(e,t)=>o.isMatch(t,e)}),e}();return e.map(((e,i)=>({rule:e,validate:t.compile({$id:`rule-${String(i)}`,...e.match})})))}async function ye(e,t,o,n,s){const a=r.extname(e),c=await i.stat(e),l=await he(e,a),d=pe(e,c,l.frontmatter,l.json),h=await fe(t,d,n,s),u=await C(e,o);return{inferred:h,enrichment:u,metadata:{...h,...u??{}},attributes:d,extracted:l}}function we(e,t){const i=[];for(let r=0;r<t;r++)i.push(oe(e,r));return i}function be(e,t=1){if(!e)return t;const i=e.total_chunks;return"number"==typeof i?i:t}class ve{config;embeddingProvider;vectorStore;compiledRules;logger;constructor(e,t,i,r,o){this.config=e,this.embeddingProvider=t,this.vectorStore=i,this.compiledRules=r,this.logger=o}async processFile(e){try{const t=r.extname(e),{metadata:i,extracted:o}=await ye(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger);if(!o.text.trim())return void this.logger.debug({filePath:e},"Skipping empty file");const n=ie(o.text),s=oe(e,0),a=await this.vectorStore.getPayload(s);if(a&&a.content_hash===n)return void this.logger.debug({filePath:e},"Content unchanged, skipping");const c=be(a),l=this.config.chunkSize??1e3,d=function(e,t,i){const r=e.toLowerCase();return".md"===r||".markdown"===r?new w.MarkdownTextSplitter({chunkSize:t,chunkOverlap:i}):new w.RecursiveCharacterTextSplitter({chunkSize:t,chunkOverlap:i})}(t,l,this.config.chunkOverlap??200),h=await d.splitText(o.text),u=await this.embeddingProvider.embed(h),g=h.map(((t,r)=>({id:oe(e,r),vector:u[r],payload:{...i,file_path:e.replace(/\\/g,"/"),chunk_index:r,total_chunks:h.length,content_hash:n,chunk_text:t}})));if(await this.vectorStore.upsert(g),c>h.length){const t=we(e,c).slice(h.length);await this.vectorStore.delete(t)}this.logger.info({filePath:e,chunks:h.length},"File processed successfully")}catch(t){this.logger.error({filePath:e,error:t},"Failed to process file")}}async deleteFile(e){try{const t=oe(e,0),i=await this.vectorStore.getPayload(t),r=we(e,be(i));await this.vectorStore.delete(r),await R(e,this.config.metadataDir),this.logger.info({filePath:e},"File deleted from index")}catch(t){this.logger.error({filePath:e,error:t},"Failed to delete file")}}async processMetadataUpdate(e,t){try{const i={...await C(e,this.config.metadataDir)??{},...t};await F(e,this.config.metadataDir,i);const r=oe(e,0),o=await this.vectorStore.getPayload(r);if(!o)return null;const n=be(o),s=we(e,n);return await this.vectorStore.setPayload(s,i),this.logger.info({filePath:e,chunks:n},"Metadata updated"),i}catch(t){return this.logger.error({filePath:e,error:t},"Failed to update metadata"),null}}async processRulesUpdate(e){try{const t=oe(e,0),i=await this.vectorStore.getPayload(t);if(!i)return this.logger.debug({filePath:e},"File not indexed, skipping"),null;const{metadata:r}=await ye(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger),o=be(i),n=we(e,o);return await this.vectorStore.setPayload(n,r),this.logger.info({filePath:e,chunks:o},"Rules re-applied"),r}catch(t){return this.logger.error({filePath:e,error:t},"Failed to re-apply rules"),null}}updateRules(e){this.compiledRules=e,this.logger.info({rules:e.length},"Inference rules updated")}}class Me{debounceMs;concurrency;rateLimitPerMinute;started=!1;active=0;debounceTimers=new Map;latestByKey=new Map;normalQueue=[];lowQueue=[];tokens;lastRefillMs=Date.now();drainWaiters=[];constructor(e){this.debounceMs=e.debounceMs,this.concurrency=e.concurrency,this.rateLimitPerMinute=e.rateLimitPerMinute,this.tokens=this.rateLimitPerMinute??Number.POSITIVE_INFINITY}enqueue(e,t){const i=`${e.priority}:${e.path}`;this.latestByKey.set(i,{event:e,fn:t});const r=this.debounceTimers.get(i);r&&clearTimeout(r);const o=setTimeout((()=>{this.debounceTimers.delete(i);const e=this.latestByKey.get(i);e&&(this.latestByKey.delete(i),this.push(e),this.pump())}),this.debounceMs);this.debounceTimers.set(i,o)}process(){this.started=!0,this.pump()}async drain(){this.isIdle()||await new Promise((e=>{this.drainWaiters.push(e)}))}push(e){"low"===e.event.priority?this.lowQueue.push(e):this.normalQueue.push(e)}refillTokens(e){if(void 0===this.rateLimitPerMinute)return;const t=Math.max(0,e-this.lastRefillMs)*(this.rateLimitPerMinute/6e4);this.tokens=Math.min(this.rateLimitPerMinute,this.tokens+t),this.lastRefillMs=e}takeToken(){const e=Date.now();return this.refillTokens(e),!(this.tokens<1)&&(this.tokens-=1,!0)}nextItem(){return this.normalQueue.shift()??this.lowQueue.shift()}pump(){if(this.started){for(;this.active<this.concurrency;){const e=this.nextItem();if(!e)break;if(!this.takeToken()){"low"===e.event.priority?this.lowQueue.unshift(e):this.normalQueue.unshift(e),setTimeout((()=>{this.pump()}),250);break}this.active+=1,Promise.resolve().then((()=>e.fn(e.event))).finally((()=>{this.active-=1,this.pump(),this.maybeResolveDrain()}))}this.maybeResolveDrain()}}isIdle(){return 0===this.active&&0===this.normalQueue.length&&0===this.lowQueue.length&&0===this.debounceTimers.size&&0===this.latestByKey.size}maybeResolveDrain(){if(!this.isIdle())return;const e=this.drainWaiters;this.drainWaiters=[];for(const t of e)t()}}class Pe{client;collectionName;dims;log;constructor(e,t,i){this.client=new b.QdrantClient({url:e.url,apiKey:e.apiKey,checkCompatibility:!1}),this.collectionName=e.collectionName,this.dims=t,this.log=B(i)}async ensureCollection(){try{const e=await this.client.getCollections();e.collections.some((e=>e.name===this.collectionName))||await this.client.createCollection(this.collectionName,{vectors:{size:this.dims,distance:"Cosine"}})}catch(e){throw new Error(`Failed to ensure collection "${this.collectionName}": ${String(e)}`)}}async upsert(e){0!==e.length&&await Z((async t=>{t>1&&this.log.warn({attempt:t,operation:"qdrant.upsert",points:e.length},"Retrying Qdrant upsert"),await this.client.upsert(this.collectionName,{wait:!0,points:e.map((e=>({id:e.id,vector:e.vector,payload:e.payload})))})}),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:e,delayMs:t,error:i})=>{this.log.warn({attempt:e,delayMs:t,operation:"qdrant.upsert",error:i},"Qdrant upsert failed; will retry")}})}async delete(e){0!==e.length&&await Z((async t=>{t>1&&this.log.warn({attempt:t,operation:"qdrant.delete",ids:e.length},"Retrying Qdrant delete"),await this.client.delete(this.collectionName,{wait:!0,points:e})}),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:e,delayMs:t,error:i})=>{this.log.warn({attempt:e,delayMs:t,operation:"qdrant.delete",error:i},"Qdrant delete failed; will retry")}})}async setPayload(e,t){0!==e.length&&await this.client.setPayload(this.collectionName,{wait:!0,points:e,payload:t})}async getPayload(e){try{const t=await this.client.retrieve(this.collectionName,{ids:[e],with_payload:!0,with_vector:!1});return 0===t.length?null:t[0].payload}catch{return null}}async search(e,t,i){return(await this.client.search(this.collectionName,{vector:e,limit:t,with_payload:!0,...i?{filter:i}:{}})).map((e=>({id:String(e.id),score:e.score,payload:e.payload})))}async*scroll(e,t=100){let i;for(;;){const r=await this.client.scroll(this.collectionName,{limit:t,with_payload:!0,with_vector:!1,...e?{filter:e}:{},...void 0!==i?{offset:i}:{}});for(const e of r.points)yield{id:String(e.id),payload:e.payload};const o=r.next_page_offset;if(null==o)break;if("string"!=typeof o&&"number"!=typeof o)break;i=o}}}class Se{config;queue;processor;logger;watcher;constructor(e,t,i,r){this.config=e,this.queue=t,this.processor=i,this.logger=r}start(){this.watcher=v.watch(this.config.paths,{ignored:this.config.ignored,usePolling:this.config.usePolling,interval:this.config.pollIntervalMs,awaitWriteFinish:!!this.config.stabilityThresholdMs&&{stabilityThreshold:this.config.stabilityThresholdMs},ignoreInitial:!1}),this.watcher.on("add",(e=>{this.logger.debug({path:e},"File added"),this.queue.enqueue({type:"create",path:e,priority:"normal"},(()=>this.processor.processFile(e)))})),this.watcher.on("change",(e=>{this.logger.debug({path:e},"File changed"),this.queue.enqueue({type:"modify",path:e,priority:"normal"},(()=>this.processor.processFile(e)))})),this.watcher.on("unlink",(e=>{this.logger.debug({path:e},"File removed"),this.queue.enqueue({type:"delete",path:e,priority:"normal"},(()=>this.processor.deleteFile(e)))})),this.watcher.on("error",(e=>{this.logger.error({error:e},"Watcher error")})),this.queue.process(),this.logger.info({paths:this.config.paths},"Filesystem watcher started")}async stop(){this.watcher&&(await this.watcher.close(),this.watcher=void 0,this.logger.info("Filesystem watcher stopped"))}}class ke{options;watcher;debounce;constructor(e){this.options=e}start(){this.options.enabled&&(this.watcher=v.watch(this.options.configPath,{ignoreInitial:!0}),this.watcher.on("change",(()=>{this.debounce&&clearTimeout(this.debounce),this.debounce=setTimeout((()=>{this.options.onChange()}),this.options.debounceMs)})),this.watcher.on("error",(e=>{this.options.logger.error({error:e},"Config watcher error")})),this.options.logger.info({configPath:this.options.configPath,debounceMs:this.options.debounceMs},"Config watcher started"))}async stop(){this.debounce&&(clearTimeout(this.debounce),this.debounce=void 0),this.watcher&&(await this.watcher.close(),this.watcher=void 0)}}const xe={loadConfig:V,createLogger:te,createEmbeddingProvider:ee,createVectorStoreClient:(e,t,i)=>new Pe(e,t,i),compileRules:me,createDocumentProcessor:(e,t,i,r,o)=>new ve(e,t,i,r,o),createEventQueue:e=>new Me(e),createFileSystemWatcher:(e,t,i,r)=>new Se(e,t,i,r),createApiServer:I};class ze{config;configPath;factories;logger;watcher;queue;server;processor;configWatcher;constructor(e,t,i={}){this.config=e,this.configPath=t,this.factories={...xe,...i}}async start(){const e=this.factories.createLogger(this.config.logging);let t;this.logger=e;try{t=this.factories.createEmbeddingProvider(this.config.embedding,e)}catch(t){throw e.fatal({error:t},"Failed to create embedding provider"),t}const i=this.factories.createVectorStoreClient(this.config.vectorStore,t.dimensions,e);await i.ensureCollection();const r=this.factories.compileRules(this.config.inferenceRules??[]),o={metadataDir:this.config.metadataDir??".jeeves-metadata",chunkSize:this.config.embedding.chunkSize,chunkOverlap:this.config.embedding.chunkOverlap,maps:this.config.maps},n=this.factories.createDocumentProcessor(o,t,i,r,e);this.processor=n;const s=this.factories.createEventQueue({debounceMs:this.config.watch.debounceMs??2e3,concurrency:this.config.embedding.concurrency??5,rateLimitPerMinute:this.config.embedding.rateLimitPerMinute});this.queue=s;const a=this.factories.createFileSystemWatcher(this.config.watch,s,n,e);this.watcher=a;const c=this.factories.createApiServer({processor:n,vectorStore:i,embeddingProvider:t,queue:s,config:this.config,logger:e});this.server=c,await c.listen({host:this.config.api?.host??"127.0.0.1",port:this.config.api?.port??3456}),a.start(),this.startConfigWatch(),e.info("jeeves-watcher started")}async stop(){if(await this.stopConfigWatch(),this.watcher&&await this.watcher.stop(),this.queue){const e=this.config.shutdownTimeoutMs??1e4;await Promise.race([this.queue.drain().then((()=>!0)),new Promise((t=>{setTimeout((()=>{t(!1)}),e)}))])||this.logger?.warn({timeoutMs:e},"Queue drain timeout hit, forcing shutdown")}this.server&&await this.server.close(),this.logger?.info("jeeves-watcher stopped")}startConfigWatch(){const e=this.logger;if(!e)return;const t=this.config.configWatch?.enabled??!0;if(!t)return;if(!this.configPath)return void e.debug("Config watch enabled, but no config path was provided");const i=this.config.configWatch?.debounceMs??1e4;this.configWatcher=new ke({configPath:this.configPath,enabled:t,debounceMs:i,logger:e,onChange:async()=>this.reloadConfig()}),this.configWatcher.start()}async stopConfigWatch(){this.configWatcher&&(await this.configWatcher.stop(),this.configWatcher=void 0)}async reloadConfig(){const e=this.logger,t=this.processor;if(e&&t&&this.configPath){e.info({configPath:this.configPath},"Config change detected, reloading...");try{const i=await this.factories.loadConfig(this.configPath);this.config=i;const r=this.factories.compileRules(i.inferenceRules??[]);t.updateRules(r),e.info({configPath:this.configPath,rules:r.length},"Config reloaded")}catch(t){e.error({error:t},"Failed to reload config")}}}}e.DocumentProcessor=ve,e.EventQueue=Me,e.FileSystemWatcher=Se,e.JeevesWatcher=ze,e.VectorStoreClient=Pe,e.apiConfigSchema=$,e.applyRules=fe,e.buildAttributes=pe,e.compileRules=me,e.configWatchConfigSchema=L,e.contentHash=ie,e.createApiServer=I,e.createEmbeddingProvider=ee,e.createLogger=te,e.deleteMetadata=R,e.embeddingConfigSchema=O,e.extractText=he,e.inferenceRuleSchema=J,e.jeevesWatcherConfigSchema=G,e.loadConfig=V,e.loggingConfigSchema=K,e.metadataPath=j,e.pointId=oe,e.readMetadata=C,e.startFromConfig=async function(e){const t=await V(e),i=new ze(t,e);return function(e){const t=async()=>{await e(),process.exit(0)};process.on("SIGTERM",(()=>{t()})),process.on("SIGINT",(()=>{t()}))}((()=>i.stop())),await i.start(),i},e.vectorStoreConfigSchema=Q,e.watchConfigSchema=_,e.writeMetadata=F}(this["jeeves-watcher"]=this["jeeves-watcher"]||{},Fastify,promises,node_path,picomatch,radash,node_crypto,cosmiconfig,zod,jsonmap,googleGenai,pino,uuid,cheerio,yaml,mammoth,Ajv,addFormats,textsplitters,jsClientRest,chokidar);
|
|
1
|
+
!function(e,t,i,r,o,n,s,a,c,l,d,h,u,g,f,p,m,y,w,b,v){"use strict";function M(e){var t=Object.create(null);return e&&Object.keys(e).forEach((function(i){if("default"!==i){var r=Object.getOwnPropertyDescriptor(e,i);Object.defineProperty(t,i,r.get?r:{enumerable:!0,get:function(){return e[i]}})}})),t.default=e,Object.freeze(t)}var P=M(g);function S(e){if(e instanceof Error)return e;if("string"==typeof e)return new Error(e);const t=String("object"==typeof e&&null!==e&&"message"in e?e.message:e),i=new Error(t);return i.cause=e,i}function k(e){const t=e.replace(/\\/g,"/"),i=t.search(/[*?\[]/);if(-1===i)return r.resolve(e);const o=t.slice(0,i),n=o.endsWith("/")?o.slice(0,-1):r.dirname(o);return r.resolve(n)}async function*x(e){let t;try{t=(await i.readdir(e,{withFileTypes:!0})).map((e=>({name:e.name,isDirectory:e.isDirectory()})))}catch{return}for(const o of t){const t=r.resolve(e,o.name);if(o.isDirectory)yield*x(t);else try{(await i.stat(t)).isFile()&&(yield t)}catch{}}}async function z(e,t,i,r){const n=await async function(e,t=[]){const i=e.map((e=>e.replace(/\\/g,"/"))),r=t.map((e=>e.replace(/\\/g,"/"))),n=o(i,{dot:!0}),s=r.length?o(r,{dot:!0}):()=>!1,a=Array.from(new Set(e.map(k))),c=new Set;for(const e of a)for await(const t of x(e)){const e=t.replace(/\\/g,"/");s(e)||n(e)&&c.add(t)}return Array.from(c)}(e,t);for(const e of n)await i[r](e);return n.length}function j(e,t=!1){let i=e.replace(/\\/g,"/").toLowerCase();return t&&(i=i.replace(/^([a-z]):/,((e,t)=>t))),i}function C(e,t){const i=j(e,!0),o=s.createHash("sha256").update(i,"utf8").digest("hex");return r.join(t,`${o}.meta.json`)}async function F(e,t){try{const r=await i.readFile(C(e,t),"utf8");return JSON.parse(r)}catch{return null}}async function R(e,t,o){const n=C(e,t);await i.mkdir(r.dirname(n),{recursive:!0}),await i.writeFile(n,JSON.stringify(o,null,2),"utf8")}async function T(e,t){try{await i.rm(C(e,t))}catch{}}const E=["file_path","chunk_index","total_chunks","content_hash","chunk_text"];function I(e){const{processor:i,vectorStore:r,embeddingProvider:o,logger:s,config:a}=e,c=t({logger:!1});var l;return c.get("/status",(()=>({status:"ok",uptime:process.uptime()}))),c.post("/metadata",(l={processor:i,logger:s},async(e,t)=>{try{const{path:t,metadata:i}=e.body;return await l.processor.processMetadataUpdate(t,i),{ok:!0}}catch(e){return l.logger.error({err:S(e)},"Metadata update failed"),t.status(500).send({error:"Internal server error"})}})),c.post("/search",function(e){return async(t,i)=>{try{const{query:i,limit:r=10}=t.body,o=await e.embeddingProvider.embed([i]);return await e.vectorStore.search(o[0],r)}catch(t){return e.logger.error({err:S(t)},"Search failed"),i.status(500).send({error:"Internal server error"})}}}({embeddingProvider:o,vectorStore:r,logger:s})),c.post("/reindex",function(e){return async(t,i)=>{try{const t=await z(e.config.watch.paths,e.config.watch.ignored,e.processor,"processFile");return await i.status(200).send({ok:!0,filesIndexed:t})}catch(t){return e.logger.error({err:S(t)},"Reindex failed"),await i.status(500).send({error:"Internal server error"})}}}({config:a,processor:i,logger:s})),c.post("/rebuild-metadata",function(e){return async(t,i)=>{try{const t=e.config.metadataDir??".jeeves-metadata",r=[...E];for await(const i of e.vectorStore.scroll()){const e=i.payload,o=e.file_path;if("string"!=typeof o||0===o.length)continue;const s=n.omit(e,r);await R(o,t,s)}return await i.status(200).send({ok:!0})}catch(t){return e.logger.error({err:S(t)},"Rebuild metadata failed"),await i.status(500).send({error:"Internal server error"})}}}({config:a,vectorStore:r,logger:s})),c.post("/config-reindex",function(e){return async(t,i)=>{try{const r=t.body.scope??"rules";return(async()=>{try{if("rules"===r){const t=await z(e.config.watch.paths,e.config.watch.ignored,e.processor,"processRulesUpdate");e.logger.info({scope:r,filesProcessed:t},"Config reindex (rules) completed")}else{const t=await z(e.config.watch.paths,e.config.watch.ignored,e.processor,"processFile");e.logger.info({scope:r,filesProcessed:t},"Config reindex (full) completed")}}catch(t){e.logger.error({err:S(t),scope:r},"Config reindex failed")}})(),await i.status(200).send({status:"started",scope:r})}catch(t){return e.logger.error({err:S(t)},"Config reindex request failed"),await i.status(500).send({error:"Internal server error"})}}}({config:a,processor:i,logger:s})),c}const D={metadataDir:".jeeves-watcher",shutdownTimeoutMs:1e4},A={enabled:!0,debounceMs:1e3},W={host:"127.0.0.1",port:3456},N={level:"info"},q={debounceMs:300,stabilityThresholdMs:500,usePolling:!1,pollIntervalMs:1e3},_={chunkSize:1e3,chunkOverlap:200,dimensions:3072,rateLimitPerMinute:300,concurrency:5},L=c.z.object({paths:c.z.array(c.z.string()).min(1).describe('Glob patterns for files to watch (e.g., "**/*.md"). At least one required.'),ignored:c.z.array(c.z.string()).optional().describe('Glob patterns to exclude from watching (e.g., "**/node_modules/**").'),pollIntervalMs:c.z.number().optional().describe("Polling interval in milliseconds when usePolling is enabled."),usePolling:c.z.boolean().optional().describe("Use polling instead of native file system events (for network drives)."),debounceMs:c.z.number().optional().describe("Debounce delay in milliseconds for file change events."),stabilityThresholdMs:c.z.number().optional().describe("Time in milliseconds a file must remain unchanged before processing.")}),O=c.z.object({enabled:c.z.boolean().optional().describe("Enable automatic reloading when config file changes."),debounceMs:c.z.number().optional().describe("Debounce delay in milliseconds for config file change detection.")}),Q=c.z.object({provider:c.z.string().default("gemini").describe('Embedding provider name (e.g., "gemini", "openai").'),model:c.z.string().default("gemini-embedding-001").describe('Embedding model identifier (e.g., "gemini-embedding-001", "text-embedding-3-small").'),chunkSize:c.z.number().optional().describe("Maximum chunk size in characters for text splitting."),chunkOverlap:c.z.number().optional().describe("Character overlap between consecutive chunks."),dimensions:c.z.number().optional().describe("Embedding vector dimensions (must match model output)."),apiKey:c.z.string().optional().describe("API key for embedding provider (supports ${ENV_VAR} substitution)."),rateLimitPerMinute:c.z.number().optional().describe("Maximum embedding API requests per minute (rate limiting)."),concurrency:c.z.number().optional().describe("Maximum concurrent embedding requests.")}),$=c.z.object({url:c.z.string().describe('Qdrant server URL (e.g., "http://localhost:6333").'),collectionName:c.z.string().describe("Qdrant collection name for vector storage."),apiKey:c.z.string().optional().describe("Qdrant API key for authentication (supports ${ENV_VAR} substitution).")}),K=c.z.object({host:c.z.string().optional().describe('Host address for API server (e.g., "127.0.0.1", "0.0.0.0").'),port:c.z.number().optional().describe("Port for API server (e.g., 3456).")}),J=c.z.object({level:c.z.string().optional().describe("Logging level (trace, debug, info, warn, error, fatal)."),file:c.z.string().optional().describe("Path to log file (logs to stdout if omitted).")}),G=c.z.object({match:c.z.record(c.z.string(),c.z.unknown()).describe("JSON Schema object to match against file attributes."),set:c.z.record(c.z.string(),c.z.unknown()).describe("Metadata fields to set when match succeeds."),map:c.z.union([l.jsonMapMapSchema,c.z.string()]).optional().describe("JsonMap transformation (inline definition or named map reference).")}),U=c.z.object({watch:L.describe("File system watch configuration."),configWatch:O.optional().describe("Configuration file watch settings."),embedding:Q.describe("Embedding model configuration."),vectorStore:$.describe("Qdrant vector store configuration."),metadataDir:c.z.string().optional().describe("Directory for persisted metadata sidecar files."),api:K.optional().describe("API server configuration."),extractors:c.z.record(c.z.string(),c.z.unknown()).optional().describe("Extractor configurations keyed by name."),inferenceRules:c.z.array(G).optional().describe("Rules for inferring metadata from file attributes."),maps:c.z.record(c.z.string(),l.jsonMapMapSchema).optional().describe("Reusable named JsonMap transformations."),logging:J.optional().describe("Logging configuration."),shutdownTimeoutMs:c.z.number().optional().describe("Timeout in milliseconds for graceful shutdown.")}),V=/\$\{([^}]+)\}/g;function B(e){if("string"==typeof e)return function(e){return e.replace(V,((e,t)=>{const i=process.env[t];return void 0===i?e:i}))}(e);if(Array.isArray(e))return e.map((e=>B(e)));if(null!==e&&"object"==typeof e){const t={};for(const[i,r]of Object.entries(e))t[i]=B(r);return t}return e}const H="jeeves-watcher";async function Y(e){const t=a.cosmiconfig(H),i=e?await t.load(e):await t.search();if(!i||i.isEmpty)throw new Error("No jeeves-watcher configuration found. Create a .jeeves-watcherrc or jeeves-watcher.config.{js,ts,json,yaml} file.");try{const e=U.parse(i.config);return B((r=e,{...D,...r,watch:{...q,...r.watch},configWatch:{...A,...r.configWatch},embedding:{..._,...r.embedding},api:{...W,...r.api},logging:{...N,...r.logging}}))}catch(e){if(e instanceof c.ZodError){const t=e.issues.map((e=>`${e.path.join(".")}: ${e.message}`)).join("; ");throw new Error(`Invalid jeeves-watcher configuration: ${t}`)}throw e}var r}function Z(e){return e||{warn(e,t){t?console.warn(e,t):console.warn(e)}}}function X(e,t){return e<=0?Promise.resolve():new Promise(((i,r)=>{const o=setTimeout((()=>{s(),i()}),e),n=()=>{s(),r(new Error("Retry sleep aborted"))},s=()=>{clearTimeout(o),t&&t.removeEventListener("abort",n)};if(t){if(t.aborted)return void n();t.addEventListener("abort",n,{once:!0})}}))}function ee(e,t,i,r=0){const o=Math.max(0,e-1),n=Math.min(i,t*2**o),s=r>0?1+Math.random()*r:1;return Math.round(n*s)}async function te(e,t){const i=Math.max(1,t.attempts);let r;for(let o=1;o<=i;o++)try{return await e(o)}catch(e){r=e;if(o>=i)break;const n=ee(o,t.baseDelayMs,t.maxDelayMs,t.jitter);t.onRetry?.({attempt:o,attempts:i,delayMs:n,error:e}),await X(n,t.signal)}throw r}const ie=new Map([["mock",function(e){return function(e){return{dimensions:e,embed:t=>Promise.resolve(t.map((t=>{const i=s.createHash("sha256").update(t,"utf8").digest(),r=[];for(let t=0;t<e;t++){const e=i[t%i.length];r.push(e/127.5-1)}return r})))}}(e.dimensions??768)}],["gemini",function(e,t){if(!e.apiKey)throw new Error("Gemini embedding provider requires config.embedding.apiKey");const i=e.dimensions??3072,r=Z(t),o=new d.GoogleGenerativeAIEmbeddings({apiKey:e.apiKey,model:e.model});return{dimensions:i,async embed(t){const n=await te((async i=>(i>1&&r.warn({attempt:i,provider:"gemini",model:e.model},"Retrying embedding request"),o.embedDocuments(t))),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:t,delayMs:i,error:o})=>{r.warn({attempt:t,delayMs:i,provider:"gemini",model:e.model,err:S(o)},"Embedding call failed; will retry")}});for(const e of n)if(e.length!==i)throw new Error(`Gemini embedding returned invalid dimensions: expected ${String(i)}, got ${String(e.length)}`);return n}}}]]);function re(e,t){const i=ie.get(e.provider);if(!i)throw new Error(`Unsupported embedding provider: ${e.provider}`);return i(e,t)}function oe(e){const t=e?.level??"info";if(e?.file){const i=h.transport({target:"pino/file",options:{destination:e.file,mkdir:!0}});return h({level:t},i)}return h({level:t})}function ne(e){return s.createHash("sha256").update(e,"utf8").digest("hex")}const se="6a6f686e-6761-4c74-ad6a-656576657321";function ae(e,t){const i=void 0!==t?`${j(e)}#${String(t)}`:j(e);return u.v5(i,se)}const ce=["content","body","text","snippet","subject","description","summary","transcript"];function le(e){if(!e||"object"!=typeof e)return JSON.stringify(e);const t=e;for(const e of ce){const i=t[e];if("string"==typeof i&&i.trim())return i}return JSON.stringify(e)}async function de(e){const t=await i.readFile(e,"utf8"),{frontmatter:r,body:o}=function(e){const t=e.replace(/^\uFEFF/,"");if(!/^\s*---/.test(t))return{body:e};const i=/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m.exec(t);if(!i)return{body:e};const[,r,o]=i,n=f.load(r);return{frontmatter:n&&"object"==typeof n&&!Array.isArray(n)?n:void 0,body:o}}(t);return{text:o,frontmatter:r}}async function he(e){return{text:await i.readFile(e,"utf8")}}async function ue(e){const t=await i.readFile(e,"utf8"),r=P.load(t);r("script, style").remove();return{text:r("body").text().trim()||r.text().trim()}}const ge=new Map([[".md",de],[".markdown",de],[".txt",he],[".text",he],[".json",async function(e){const t=await i.readFile(e,"utf8"),r=JSON.parse(t),o=r&&"object"==typeof r&&!Array.isArray(r)?r:void 0;return{text:le(r),json:o}}],[".pdf",async function(e){const t=await i.readFile(e),r=new Uint8Array(t),{extractText:o}=await import("unpdf"),{text:n}=await o(r);return{text:Array.isArray(n)?n.join("\n\n"):n}}],[".docx",async function(e){const t=await i.readFile(e);return{text:(await p.extractRawText({buffer:t})).value}}],[".html",ue],[".htm",ue]]);async function fe(e,t){const i=ge.get(t.toLowerCase());return i?i(e):he(e)}function pe(e,t){return"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,((e,i)=>{const r=n.get(t,i);return null==r?"":"string"==typeof r?r:JSON.stringify(r)}))}function me(e,t){const i={};for(const[r,o]of Object.entries(e))i[r]=pe(o,t);return i}async function ye(e,t,i,r){const o={split:(e,t)=>e.split(t),slice:(e,t,i)=>e.slice(t,i),join:(e,t)=>e.join(t),toLowerCase:e=>e.toLowerCase(),replace:(e,t,i)=>e.replace(t,i),get:(e,t)=>n.get(e,t)};let s={};const a=r??console;for(const{rule:r,validate:n}of e)if(n(t)){const e=me(r.set,t);if(s={...s,...e},r.map){let e;if("string"==typeof r.map){if(e=i?.[r.map],!e){a.warn(`Map reference "${r.map}" not found in named maps. Skipping map transformation.`);continue}}else e=r.map;try{const i=new l.JsonMap(e,o),r=await i.transform(t);r&&"object"==typeof r&&!Array.isArray(r)?s={...s,...r}:a.warn("JsonMap transformation did not return an object; skipping merge.")}catch(e){a.warn(`JsonMap transformation failed: ${e instanceof Error?e.message:String(e)}`)}}}return s}function we(e,t,i,o){const n=e.replace(/\\/g,"/"),s={file:{path:n,directory:r.dirname(n).replace(/\\/g,"/"),filename:r.basename(n),extension:r.extname(n),sizeBytes:t.size,modified:t.mtime.toISOString()}};return i&&(s.frontmatter=i),o&&(s.json=o),s}function be(e){const t=function(){const e=new m({allErrors:!0});return y(e),e.addKeyword({keyword:"glob",type:"string",schemaType:"string",validate:(e,t)=>o.isMatch(t,e)}),e}();return e.map(((e,i)=>({rule:e,validate:t.compile({$id:`rule-${String(i)}`,...e.match})})))}async function ve(e,t,o,n,s){const a=r.extname(e),c=await i.stat(e),l=await fe(e,a),d=we(e,c,l.frontmatter,l.json),h=await ye(t,d,n,s),u=await F(e,o);return{inferred:h,enrichment:u,metadata:{...h,...u??{}},attributes:d,extracted:l}}function Me(e,t){const i=[];for(let r=0;r<t;r++)i.push(ae(e,r));return i}function Pe(e,t=1){if(!e)return t;const i=e.total_chunks;return"number"==typeof i?i:t}class Se{config;embeddingProvider;vectorStore;compiledRules;logger;constructor(e,t,i,r,o){this.config=e,this.embeddingProvider=t,this.vectorStore=i,this.compiledRules=r,this.logger=o}async processFile(e){try{const t=r.extname(e),{metadata:i,extracted:o}=await ve(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger);if(!o.text.trim())return void this.logger.debug({filePath:e},"Skipping empty file");const n=ne(o.text),s=ae(e,0),a=await this.vectorStore.getPayload(s);if(a&&a.content_hash===n)return void this.logger.debug({filePath:e},"Content unchanged, skipping");const c=Pe(a),l=this.config.chunkSize??1e3,d=function(e,t,i){const r=e.toLowerCase();return".md"===r||".markdown"===r?new w.MarkdownTextSplitter({chunkSize:t,chunkOverlap:i}):new w.RecursiveCharacterTextSplitter({chunkSize:t,chunkOverlap:i})}(t,l,this.config.chunkOverlap??200),h=await d.splitText(o.text),u=await this.embeddingProvider.embed(h),g=h.map(((t,r)=>({id:ae(e,r),vector:u[r],payload:{...i,file_path:e.replace(/\\/g,"/"),chunk_index:r,total_chunks:h.length,content_hash:n,chunk_text:t}})));if(await this.vectorStore.upsert(g),c>h.length){const t=Me(e,c).slice(h.length);await this.vectorStore.delete(t)}this.logger.info({filePath:e,chunks:h.length},"File processed successfully")}catch(t){this.logger.error({filePath:e,err:S(t)},"Failed to process file")}}async deleteFile(e){try{const t=ae(e,0),i=await this.vectorStore.getPayload(t),r=Me(e,Pe(i));await this.vectorStore.delete(r),await T(e,this.config.metadataDir),this.logger.info({filePath:e},"File deleted from index")}catch(t){this.logger.error({filePath:e,err:S(t)},"Failed to delete file")}}async processMetadataUpdate(e,t){try{const i={...await F(e,this.config.metadataDir)??{},...t};await R(e,this.config.metadataDir,i);const r=ae(e,0),o=await this.vectorStore.getPayload(r);if(!o)return null;const n=Pe(o),s=Me(e,n);return await this.vectorStore.setPayload(s,i),this.logger.info({filePath:e,chunks:n},"Metadata updated"),i}catch(t){return this.logger.error({filePath:e,err:S(t)},"Failed to update metadata"),null}}async processRulesUpdate(e){try{const t=ae(e,0),i=await this.vectorStore.getPayload(t);if(!i)return this.logger.debug({filePath:e},"File not indexed, skipping"),null;const{metadata:r}=await ve(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger),o=Pe(i),n=Me(e,o);return await this.vectorStore.setPayload(n,r),this.logger.info({filePath:e,chunks:o},"Rules re-applied"),r}catch(t){return this.logger.error({filePath:e,err:S(t)},"Failed to re-apply rules"),null}}updateRules(e){this.compiledRules=e,this.logger.info({rules:e.length},"Inference rules updated")}}class ke{debounceMs;concurrency;rateLimitPerMinute;started=!1;active=0;debounceTimers=new Map;latestByKey=new Map;normalQueue=[];lowQueue=[];tokens;lastRefillMs=Date.now();drainWaiters=[];constructor(e){this.debounceMs=e.debounceMs,this.concurrency=e.concurrency,this.rateLimitPerMinute=e.rateLimitPerMinute,this.tokens=this.rateLimitPerMinute??Number.POSITIVE_INFINITY}enqueue(e,t){const i=`${e.priority}:${e.path}`;this.latestByKey.set(i,{event:e,fn:t});const r=this.debounceTimers.get(i);r&&clearTimeout(r);const o=setTimeout((()=>{this.debounceTimers.delete(i);const e=this.latestByKey.get(i);e&&(this.latestByKey.delete(i),this.push(e),this.pump())}),this.debounceMs);this.debounceTimers.set(i,o)}process(){this.started=!0,this.pump()}async drain(){this.isIdle()||await new Promise((e=>{this.drainWaiters.push(e)}))}push(e){"low"===e.event.priority?this.lowQueue.push(e):this.normalQueue.push(e)}refillTokens(e){if(void 0===this.rateLimitPerMinute)return;const t=Math.max(0,e-this.lastRefillMs)*(this.rateLimitPerMinute/6e4);this.tokens=Math.min(this.rateLimitPerMinute,this.tokens+t),this.lastRefillMs=e}takeToken(){const e=Date.now();return this.refillTokens(e),!(this.tokens<1)&&(this.tokens-=1,!0)}nextItem(){return this.normalQueue.shift()??this.lowQueue.shift()}pump(){if(this.started){for(;this.active<this.concurrency;){const e=this.nextItem();if(!e)break;if(!this.takeToken()){"low"===e.event.priority?this.lowQueue.unshift(e):this.normalQueue.unshift(e),setTimeout((()=>{this.pump()}),250);break}this.active+=1,Promise.resolve().then((()=>e.fn(e.event))).finally((()=>{this.active-=1,this.pump(),this.maybeResolveDrain()}))}this.maybeResolveDrain()}}isIdle(){return 0===this.active&&0===this.normalQueue.length&&0===this.lowQueue.length&&0===this.debounceTimers.size&&0===this.latestByKey.size}maybeResolveDrain(){if(!this.isIdle())return;const e=this.drainWaiters;this.drainWaiters=[];for(const t of e)t()}}class xe{client;collectionName;dims;log;constructor(e,t,i){this.client=new b.QdrantClient({url:e.url,apiKey:e.apiKey,checkCompatibility:!1}),this.collectionName=e.collectionName,this.dims=t,this.log=Z(i)}async ensureCollection(){try{const e=await this.client.getCollections();e.collections.some((e=>e.name===this.collectionName))||await this.client.createCollection(this.collectionName,{vectors:{size:this.dims,distance:"Cosine"}})}catch(e){throw new Error(`Failed to ensure collection "${this.collectionName}": ${String(e)}`)}}async upsert(e){0!==e.length&&await te((async t=>{t>1&&this.log.warn({attempt:t,operation:"qdrant.upsert",points:e.length},"Retrying Qdrant upsert"),await this.client.upsert(this.collectionName,{wait:!0,points:e.map((e=>({id:e.id,vector:e.vector,payload:e.payload})))})}),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:e,delayMs:t,error:i})=>{this.log.warn({attempt:e,delayMs:t,operation:"qdrant.upsert",err:S(i)},"Qdrant upsert failed; will retry")}})}async delete(e){0!==e.length&&await te((async t=>{t>1&&this.log.warn({attempt:t,operation:"qdrant.delete",ids:e.length},"Retrying Qdrant delete"),await this.client.delete(this.collectionName,{wait:!0,points:e})}),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:e,delayMs:t,error:i})=>{this.log.warn({attempt:e,delayMs:t,operation:"qdrant.delete",err:S(i)},"Qdrant delete failed; will retry")}})}async setPayload(e,t){0!==e.length&&await this.client.setPayload(this.collectionName,{wait:!0,points:e,payload:t})}async getPayload(e){try{const t=await this.client.retrieve(this.collectionName,{ids:[e],with_payload:!0,with_vector:!1});return 0===t.length?null:t[0].payload}catch{return null}}async search(e,t,i){return(await this.client.search(this.collectionName,{vector:e,limit:t,with_payload:!0,...i?{filter:i}:{}})).map((e=>({id:String(e.id),score:e.score,payload:e.payload})))}async*scroll(e,t=100){let i;for(;;){const r=await this.client.scroll(this.collectionName,{limit:t,with_payload:!0,with_vector:!1,...e?{filter:e}:{},...void 0!==i?{offset:i}:{}});for(const e of r.points)yield{id:String(e.id),payload:e.payload};const o=r.next_page_offset;if(null==o)break;if("string"!=typeof o&&"number"!=typeof o)break;i=o}}}class ze{config;queue;processor;logger;watcher;constructor(e,t,i,r){this.config=e,this.queue=t,this.processor=i,this.logger=r}start(){this.watcher=v.watch(this.config.paths,{ignored:this.config.ignored,usePolling:this.config.usePolling,interval:this.config.pollIntervalMs,awaitWriteFinish:!!this.config.stabilityThresholdMs&&{stabilityThreshold:this.config.stabilityThresholdMs},ignoreInitial:!1}),this.watcher.on("add",(e=>{this.logger.debug({path:e},"File added"),this.queue.enqueue({type:"create",path:e,priority:"normal"},(()=>this.processor.processFile(e)))})),this.watcher.on("change",(e=>{this.logger.debug({path:e},"File changed"),this.queue.enqueue({type:"modify",path:e,priority:"normal"},(()=>this.processor.processFile(e)))})),this.watcher.on("unlink",(e=>{this.logger.debug({path:e},"File removed"),this.queue.enqueue({type:"delete",path:e,priority:"normal"},(()=>this.processor.deleteFile(e)))})),this.watcher.on("error",(e=>{this.logger.error({err:S(e)},"Watcher error")})),this.queue.process(),this.logger.info({paths:this.config.paths},"Filesystem watcher started")}async stop(){this.watcher&&(await this.watcher.close(),this.watcher=void 0,this.logger.info("Filesystem watcher stopped"))}}class je{options;watcher;debounce;constructor(e){this.options=e}start(){this.options.enabled&&(this.watcher=v.watch(this.options.configPath,{ignoreInitial:!0}),this.watcher.on("change",(()=>{this.debounce&&clearTimeout(this.debounce),this.debounce=setTimeout((()=>{this.options.onChange()}),this.options.debounceMs)})),this.watcher.on("error",(e=>{this.options.logger.error({err:S(e)},"Config watcher error")})),this.options.logger.info({configPath:this.options.configPath,debounceMs:this.options.debounceMs},"Config watcher started"))}async stop(){this.debounce&&(clearTimeout(this.debounce),this.debounce=void 0),this.watcher&&(await this.watcher.close(),this.watcher=void 0)}}const Ce={loadConfig:Y,createLogger:oe,createEmbeddingProvider:re,createVectorStoreClient:(e,t,i)=>new xe(e,t,i),compileRules:be,createDocumentProcessor:(e,t,i,r,o)=>new Se(e,t,i,r,o),createEventQueue:e=>new ke(e),createFileSystemWatcher:(e,t,i,r)=>new ze(e,t,i,r),createApiServer:I};class Fe{config;configPath;factories;logger;watcher;queue;server;processor;configWatcher;constructor(e,t,i={}){this.config=e,this.configPath=t,this.factories={...Ce,...i}}async start(){const e=this.factories.createLogger(this.config.logging);let t;this.logger=e;try{t=this.factories.createEmbeddingProvider(this.config.embedding,e)}catch(t){throw e.fatal({err:S(t)},"Failed to create embedding provider"),t}const i=this.factories.createVectorStoreClient(this.config.vectorStore,t.dimensions,e);await i.ensureCollection();const r=this.factories.compileRules(this.config.inferenceRules??[]),o={metadataDir:this.config.metadataDir??".jeeves-metadata",chunkSize:this.config.embedding.chunkSize,chunkOverlap:this.config.embedding.chunkOverlap,maps:this.config.maps},n=this.factories.createDocumentProcessor(o,t,i,r,e);this.processor=n;const s=this.factories.createEventQueue({debounceMs:this.config.watch.debounceMs??2e3,concurrency:this.config.embedding.concurrency??5,rateLimitPerMinute:this.config.embedding.rateLimitPerMinute});this.queue=s;const a=this.factories.createFileSystemWatcher(this.config.watch,s,n,e);this.watcher=a;const c=this.factories.createApiServer({processor:n,vectorStore:i,embeddingProvider:t,queue:s,config:this.config,logger:e});this.server=c,await c.listen({host:this.config.api?.host??"127.0.0.1",port:this.config.api?.port??3456}),a.start(),this.startConfigWatch(),e.info("jeeves-watcher started")}async stop(){if(await this.stopConfigWatch(),this.watcher&&await this.watcher.stop(),this.queue){const e=this.config.shutdownTimeoutMs??1e4;await Promise.race([this.queue.drain().then((()=>!0)),new Promise((t=>{setTimeout((()=>{t(!1)}),e)}))])||this.logger?.warn({timeoutMs:e},"Queue drain timeout hit, forcing shutdown")}this.server&&await this.server.close(),this.logger?.info("jeeves-watcher stopped")}startConfigWatch(){const e=this.logger;if(!e)return;const t=this.config.configWatch?.enabled??!0;if(!t)return;if(!this.configPath)return void e.debug("Config watch enabled, but no config path was provided");const i=this.config.configWatch?.debounceMs??1e4;this.configWatcher=new je({configPath:this.configPath,enabled:t,debounceMs:i,logger:e,onChange:async()=>this.reloadConfig()}),this.configWatcher.start()}async stopConfigWatch(){this.configWatcher&&(await this.configWatcher.stop(),this.configWatcher=void 0)}async reloadConfig(){const e=this.logger,t=this.processor;if(e&&t&&this.configPath){e.info({configPath:this.configPath},"Config change detected, reloading...");try{const i=await this.factories.loadConfig(this.configPath);this.config=i;const r=this.factories.compileRules(i.inferenceRules??[]);t.updateRules(r),e.info({configPath:this.configPath,rules:r.length},"Config reloaded")}catch(t){e.error({err:S(t)},"Failed to reload config")}}}}e.DocumentProcessor=Se,e.EventQueue=ke,e.FileSystemWatcher=ze,e.JeevesWatcher=Fe,e.VectorStoreClient=xe,e.apiConfigSchema=K,e.applyRules=ye,e.buildAttributes=we,e.compileRules=be,e.configWatchConfigSchema=O,e.contentHash=ne,e.createApiServer=I,e.createEmbeddingProvider=re,e.createLogger=oe,e.deleteMetadata=T,e.embeddingConfigSchema=Q,e.extractText=fe,e.inferenceRuleSchema=G,e.jeevesWatcherConfigSchema=U,e.loadConfig=Y,e.loggingConfigSchema=J,e.metadataPath=C,e.pointId=ae,e.readMetadata=F,e.startFromConfig=async function(e){const t=await Y(e),i=new Fe(t,e);return function(e){const t=async()=>{await e(),process.exit(0)};process.on("SIGTERM",(()=>{t()})),process.on("SIGINT",(()=>{t()}))}((()=>i.stop())),await i.start(),i},e.vectorStoreConfigSchema=$,e.watchConfigSchema=L,e.writeMetadata=R}(this["jeeves-watcher"]=this["jeeves-watcher"]||{},Fastify,promises,node_path,picomatch,radash,node_crypto,cosmiconfig,zod,jsonmap,googleGenai,pino,uuid,cheerio,yaml,mammoth,Ajv,addFormats,textsplitters,jsClientRest,chokidar);
|
package/dist/mjs/index.js
CHANGED
|
@@ -19,6 +19,31 @@ import { MarkdownTextSplitter, RecursiveCharacterTextSplitter } from '@langchain
|
|
|
19
19
|
import { QdrantClient } from '@qdrant/js-client-rest';
|
|
20
20
|
import chokidar from 'chokidar';
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @module util/normalizeError
|
|
24
|
+
*
|
|
25
|
+
* Normalizes unknown thrown values into proper Error objects for pino serialization.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Convert an unknown thrown value into a proper Error with message, stack, and cause.
|
|
29
|
+
* Pino's built-in `err` serializer requires an Error instance to extract message/stack.
|
|
30
|
+
*
|
|
31
|
+
* @param error - The caught value (may not be an Error).
|
|
32
|
+
* @returns A proper Error instance.
|
|
33
|
+
*/
|
|
34
|
+
function normalizeError(error) {
|
|
35
|
+
if (error instanceof Error)
|
|
36
|
+
return error;
|
|
37
|
+
if (typeof error === 'string')
|
|
38
|
+
return new Error(error);
|
|
39
|
+
const message = typeof error === 'object' && error !== null && 'message' in error
|
|
40
|
+
? String(error.message)
|
|
41
|
+
: String(error);
|
|
42
|
+
const normalized = new Error(message);
|
|
43
|
+
normalized.cause = error;
|
|
44
|
+
return normalized;
|
|
45
|
+
}
|
|
46
|
+
|
|
22
47
|
/**
|
|
23
48
|
* Best-effort base directory inference for a glob pattern.
|
|
24
49
|
*
|
|
@@ -141,13 +166,13 @@ function createConfigReindexHandler(deps) {
|
|
|
141
166
|
}
|
|
142
167
|
}
|
|
143
168
|
catch (error) {
|
|
144
|
-
deps.logger.error({ error, scope }, 'Config reindex failed');
|
|
169
|
+
deps.logger.error({ err: normalizeError(error), scope }, 'Config reindex failed');
|
|
145
170
|
}
|
|
146
171
|
})();
|
|
147
172
|
return await reply.status(200).send({ status: 'started', scope });
|
|
148
173
|
}
|
|
149
174
|
catch (error) {
|
|
150
|
-
deps.logger.error({ error }, 'Config reindex request failed');
|
|
175
|
+
deps.logger.error({ err: normalizeError(error) }, 'Config reindex request failed');
|
|
151
176
|
return await reply.status(500).send({ error: 'Internal server error' });
|
|
152
177
|
}
|
|
153
178
|
};
|
|
@@ -170,7 +195,7 @@ function createMetadataHandler(deps) {
|
|
|
170
195
|
return { ok: true };
|
|
171
196
|
}
|
|
172
197
|
catch (error) {
|
|
173
|
-
deps.logger.error({ error }, 'Metadata update failed');
|
|
198
|
+
deps.logger.error({ err: normalizeError(error) }, 'Metadata update failed');
|
|
174
199
|
return reply.status(500).send({ error: 'Internal server error' });
|
|
175
200
|
}
|
|
176
201
|
};
|
|
@@ -293,7 +318,7 @@ function createRebuildMetadataHandler(deps) {
|
|
|
293
318
|
return await reply.status(200).send({ ok: true });
|
|
294
319
|
}
|
|
295
320
|
catch (error) {
|
|
296
|
-
deps.logger.error({ error }, 'Rebuild metadata failed');
|
|
321
|
+
deps.logger.error({ err: normalizeError(error) }, 'Rebuild metadata failed');
|
|
297
322
|
return await reply.status(500).send({ error: 'Internal server error' });
|
|
298
323
|
}
|
|
299
324
|
};
|
|
@@ -315,7 +340,7 @@ function createReindexHandler(deps) {
|
|
|
315
340
|
return await reply.status(200).send({ ok: true, filesIndexed: count });
|
|
316
341
|
}
|
|
317
342
|
catch (error) {
|
|
318
|
-
deps.logger.error({ error }, 'Reindex failed');
|
|
343
|
+
deps.logger.error({ err: normalizeError(error) }, 'Reindex failed');
|
|
319
344
|
return await reply.status(500).send({ error: 'Internal server error' });
|
|
320
345
|
}
|
|
321
346
|
};
|
|
@@ -339,7 +364,7 @@ function createSearchHandler(deps) {
|
|
|
339
364
|
return results;
|
|
340
365
|
}
|
|
341
366
|
catch (error) {
|
|
342
|
-
deps.logger.error({ error }, 'Search failed');
|
|
367
|
+
deps.logger.error({ err: normalizeError(error) }, 'Search failed');
|
|
343
368
|
return reply.status(500).send({ error: 'Internal server error' });
|
|
344
369
|
}
|
|
345
370
|
};
|
|
@@ -621,6 +646,49 @@ const jeevesWatcherConfigSchema = z.object({
|
|
|
621
646
|
.describe('Timeout in milliseconds for graceful shutdown.'),
|
|
622
647
|
});
|
|
623
648
|
|
|
649
|
+
/**
|
|
650
|
+
* @module config/substituteEnvVars
|
|
651
|
+
*
|
|
652
|
+
* Deep-walks config objects and replaces `${VAR_NAME}` patterns with environment variable values.
|
|
653
|
+
*/
|
|
654
|
+
const ENV_PATTERN = /\$\{([^}]+)\}/g;
|
|
655
|
+
/**
|
|
656
|
+
* Replace `${VAR_NAME}` patterns in a string with `process.env.VAR_NAME`.
|
|
657
|
+
*
|
|
658
|
+
* @param value - The string to process.
|
|
659
|
+
* @returns The string with resolved env vars; unresolvable expressions left untouched.
|
|
660
|
+
*/
|
|
661
|
+
function substituteString(value) {
|
|
662
|
+
return value.replace(ENV_PATTERN, (match, varName) => {
|
|
663
|
+
const envValue = process.env[varName];
|
|
664
|
+
if (envValue === undefined)
|
|
665
|
+
return match;
|
|
666
|
+
return envValue;
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Deep-walk a value and substitute `${VAR_NAME}` patterns in all string values.
|
|
671
|
+
*
|
|
672
|
+
* @param value - The value to walk (object, array, or primitive).
|
|
673
|
+
* @returns A new value with all env var references resolved.
|
|
674
|
+
*/
|
|
675
|
+
function substituteEnvVars(value) {
|
|
676
|
+
if (typeof value === 'string') {
|
|
677
|
+
return substituteString(value);
|
|
678
|
+
}
|
|
679
|
+
if (Array.isArray(value)) {
|
|
680
|
+
return value.map((item) => substituteEnvVars(item));
|
|
681
|
+
}
|
|
682
|
+
if (value !== null && typeof value === 'object') {
|
|
683
|
+
const result = {};
|
|
684
|
+
for (const [key, val] of Object.entries(value)) {
|
|
685
|
+
result[key] = substituteEnvVars(val);
|
|
686
|
+
}
|
|
687
|
+
return result;
|
|
688
|
+
}
|
|
689
|
+
return value;
|
|
690
|
+
}
|
|
691
|
+
|
|
624
692
|
const MODULE_NAME = 'jeeves-watcher';
|
|
625
693
|
/**
|
|
626
694
|
* Merge sensible defaults into a loaded configuration.
|
|
@@ -656,7 +724,8 @@ async function loadConfig(configPath) {
|
|
|
656
724
|
}
|
|
657
725
|
try {
|
|
658
726
|
const validated = jeevesWatcherConfigSchema.parse(result.config);
|
|
659
|
-
|
|
727
|
+
const withDefaults = applyDefaults(validated);
|
|
728
|
+
return substituteEnvVars(withDefaults);
|
|
660
729
|
}
|
|
661
730
|
catch (error) {
|
|
662
731
|
if (error instanceof ZodError) {
|
|
@@ -823,7 +892,7 @@ function createGeminiProvider(config, logger) {
|
|
|
823
892
|
delayMs,
|
|
824
893
|
provider: 'gemini',
|
|
825
894
|
model: config.model,
|
|
826
|
-
error,
|
|
895
|
+
err: normalizeError(error),
|
|
827
896
|
}, 'Embedding call failed; will retry');
|
|
828
897
|
},
|
|
829
898
|
});
|
|
@@ -932,6 +1001,9 @@ function pointId(filePath, chunkIndex) {
|
|
|
932
1001
|
*/
|
|
933
1002
|
function extractMarkdownFrontmatter(markdown) {
|
|
934
1003
|
const trimmed = markdown.replace(/^\uFEFF/, '');
|
|
1004
|
+
// Only attempt frontmatter parsing if the file starts with ---
|
|
1005
|
+
if (!/^\s*---/.test(trimmed))
|
|
1006
|
+
return { body: markdown };
|
|
935
1007
|
const match = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m.exec(trimmed);
|
|
936
1008
|
if (!match)
|
|
937
1009
|
return { body: markdown };
|
|
@@ -1389,7 +1461,7 @@ class DocumentProcessor {
|
|
|
1389
1461
|
this.logger.info({ filePath, chunks: chunks.length }, 'File processed successfully');
|
|
1390
1462
|
}
|
|
1391
1463
|
catch (error) {
|
|
1392
|
-
this.logger.error({ filePath, error }, 'Failed to process file');
|
|
1464
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to process file');
|
|
1393
1465
|
}
|
|
1394
1466
|
}
|
|
1395
1467
|
/**
|
|
@@ -1409,7 +1481,7 @@ class DocumentProcessor {
|
|
|
1409
1481
|
this.logger.info({ filePath }, 'File deleted from index');
|
|
1410
1482
|
}
|
|
1411
1483
|
catch (error) {
|
|
1412
|
-
this.logger.error({ filePath, error }, 'Failed to delete file');
|
|
1484
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to delete file');
|
|
1413
1485
|
}
|
|
1414
1486
|
}
|
|
1415
1487
|
/**
|
|
@@ -1437,7 +1509,7 @@ class DocumentProcessor {
|
|
|
1437
1509
|
return merged;
|
|
1438
1510
|
}
|
|
1439
1511
|
catch (error) {
|
|
1440
|
-
this.logger.error({ filePath, error }, 'Failed to update metadata');
|
|
1512
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to update metadata');
|
|
1441
1513
|
return null;
|
|
1442
1514
|
}
|
|
1443
1515
|
}
|
|
@@ -1467,7 +1539,7 @@ class DocumentProcessor {
|
|
|
1467
1539
|
return metadata;
|
|
1468
1540
|
}
|
|
1469
1541
|
catch (error) {
|
|
1470
|
-
this.logger.error({ filePath, error }, 'Failed to re-apply rules');
|
|
1542
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to re-apply rules');
|
|
1471
1543
|
return null;
|
|
1472
1544
|
}
|
|
1473
1545
|
}
|
|
@@ -1695,7 +1767,12 @@ class VectorStoreClient {
|
|
|
1695
1767
|
maxDelayMs: 10_000,
|
|
1696
1768
|
jitter: 0.2,
|
|
1697
1769
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1698
|
-
this.log.warn({
|
|
1770
|
+
this.log.warn({
|
|
1771
|
+
attempt,
|
|
1772
|
+
delayMs,
|
|
1773
|
+
operation: 'qdrant.upsert',
|
|
1774
|
+
err: normalizeError(error),
|
|
1775
|
+
}, 'Qdrant upsert failed; will retry');
|
|
1699
1776
|
},
|
|
1700
1777
|
});
|
|
1701
1778
|
}
|
|
@@ -1721,7 +1798,12 @@ class VectorStoreClient {
|
|
|
1721
1798
|
maxDelayMs: 10_000,
|
|
1722
1799
|
jitter: 0.2,
|
|
1723
1800
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1724
|
-
this.log.warn({
|
|
1801
|
+
this.log.warn({
|
|
1802
|
+
attempt,
|
|
1803
|
+
delayMs,
|
|
1804
|
+
operation: 'qdrant.delete',
|
|
1805
|
+
err: normalizeError(error),
|
|
1806
|
+
}, 'Qdrant delete failed; will retry');
|
|
1725
1807
|
},
|
|
1726
1808
|
});
|
|
1727
1809
|
}
|
|
@@ -1875,7 +1957,7 @@ class FileSystemWatcher {
|
|
|
1875
1957
|
this.queue.enqueue({ type: 'delete', path, priority: 'normal' }, () => this.processor.deleteFile(path));
|
|
1876
1958
|
});
|
|
1877
1959
|
this.watcher.on('error', (error) => {
|
|
1878
|
-
this.logger.error({ error }, 'Watcher error');
|
|
1960
|
+
this.logger.error({ err: normalizeError(error) }, 'Watcher error');
|
|
1879
1961
|
});
|
|
1880
1962
|
this.queue.process();
|
|
1881
1963
|
this.logger.info({ paths: this.config.paths }, 'Filesystem watcher started');
|
|
@@ -1920,7 +2002,7 @@ class ConfigWatcher {
|
|
|
1920
2002
|
}, this.options.debounceMs);
|
|
1921
2003
|
});
|
|
1922
2004
|
this.watcher.on('error', (error) => {
|
|
1923
|
-
this.options.logger.error({ error }, 'Config watcher error');
|
|
2005
|
+
this.options.logger.error({ err: normalizeError(error) }, 'Config watcher error');
|
|
1924
2006
|
});
|
|
1925
2007
|
this.options.logger.info({
|
|
1926
2008
|
configPath: this.options.configPath,
|
|
@@ -2004,7 +2086,7 @@ class JeevesWatcher {
|
|
|
2004
2086
|
embeddingProvider = this.factories.createEmbeddingProvider(this.config.embedding, logger);
|
|
2005
2087
|
}
|
|
2006
2088
|
catch (error) {
|
|
2007
|
-
logger.fatal({ error }, 'Failed to create embedding provider');
|
|
2089
|
+
logger.fatal({ err: normalizeError(error) }, 'Failed to create embedding provider');
|
|
2008
2090
|
throw error;
|
|
2009
2091
|
}
|
|
2010
2092
|
const vectorStore = this.factories.createVectorStoreClient(this.config.vectorStore, embeddingProvider.dimensions, logger);
|
|
@@ -2111,7 +2193,7 @@ class JeevesWatcher {
|
|
|
2111
2193
|
logger.info({ configPath: this.configPath, rules: compiledRules.length }, 'Config reloaded');
|
|
2112
2194
|
}
|
|
2113
2195
|
catch (error) {
|
|
2114
|
-
logger.error({ error }, 'Failed to reload config');
|
|
2196
|
+
logger.error({ err: normalizeError(error) }, 'Failed to reload config');
|
|
2115
2197
|
}
|
|
2116
2198
|
}
|
|
2117
2199
|
}
|
package/package.json
CHANGED