@karmaniverous/jeeves-watcher 0.2.1 → 0.2.2
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 +102 -18
- package/dist/cli/jeeves-watcher/index.js +102 -18
- package/dist/index.iife.js +102 -18
- package/dist/index.iife.min.js +1 -1
- package/dist/mjs/index.js +102 -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,51 @@ 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 env vars substituted.
|
|
681
|
+
* @throws If a referenced env var is not set.
|
|
682
|
+
*/
|
|
683
|
+
function substituteString(value) {
|
|
684
|
+
return value.replace(ENV_PATTERN, (match, varName) => {
|
|
685
|
+
const envValue = process.env[varName];
|
|
686
|
+
if (envValue === undefined) {
|
|
687
|
+
throw new Error(`Environment variable \${${varName}} referenced in config is not set.`);
|
|
688
|
+
}
|
|
689
|
+
return envValue;
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Deep-walk a value and substitute `${VAR_NAME}` patterns in all string values.
|
|
694
|
+
*
|
|
695
|
+
* @param value - The value to walk (object, array, or primitive).
|
|
696
|
+
* @returns A new value with all env var references resolved.
|
|
697
|
+
*/
|
|
698
|
+
function substituteEnvVars(value) {
|
|
699
|
+
if (typeof value === 'string') {
|
|
700
|
+
return substituteString(value);
|
|
701
|
+
}
|
|
702
|
+
if (Array.isArray(value)) {
|
|
703
|
+
return value.map((item) => substituteEnvVars(item));
|
|
704
|
+
}
|
|
705
|
+
if (value !== null && typeof value === 'object') {
|
|
706
|
+
const result = {};
|
|
707
|
+
for (const [key, val] of Object.entries(value)) {
|
|
708
|
+
result[key] = substituteEnvVars(val);
|
|
709
|
+
}
|
|
710
|
+
return result;
|
|
711
|
+
}
|
|
712
|
+
return value;
|
|
713
|
+
}
|
|
714
|
+
|
|
645
715
|
const MODULE_NAME = 'jeeves-watcher';
|
|
646
716
|
/**
|
|
647
717
|
* Merge sensible defaults into a loaded configuration.
|
|
@@ -677,7 +747,8 @@ async function loadConfig(configPath) {
|
|
|
677
747
|
}
|
|
678
748
|
try {
|
|
679
749
|
const validated = jeevesWatcherConfigSchema.parse(result.config);
|
|
680
|
-
|
|
750
|
+
const withDefaults = applyDefaults(validated);
|
|
751
|
+
return substituteEnvVars(withDefaults);
|
|
681
752
|
}
|
|
682
753
|
catch (error) {
|
|
683
754
|
if (error instanceof zod.ZodError) {
|
|
@@ -844,7 +915,7 @@ function createGeminiProvider(config, logger) {
|
|
|
844
915
|
delayMs,
|
|
845
916
|
provider: 'gemini',
|
|
846
917
|
model: config.model,
|
|
847
|
-
error,
|
|
918
|
+
err: normalizeError(error),
|
|
848
919
|
}, 'Embedding call failed; will retry');
|
|
849
920
|
},
|
|
850
921
|
});
|
|
@@ -953,6 +1024,9 @@ function pointId(filePath, chunkIndex) {
|
|
|
953
1024
|
*/
|
|
954
1025
|
function extractMarkdownFrontmatter(markdown) {
|
|
955
1026
|
const trimmed = markdown.replace(/^\uFEFF/, '');
|
|
1027
|
+
// Only attempt frontmatter parsing if the file starts with ---
|
|
1028
|
+
if (!/^\s*---/.test(trimmed))
|
|
1029
|
+
return { body: markdown };
|
|
956
1030
|
const match = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m.exec(trimmed);
|
|
957
1031
|
if (!match)
|
|
958
1032
|
return { body: markdown };
|
|
@@ -1410,7 +1484,7 @@ class DocumentProcessor {
|
|
|
1410
1484
|
this.logger.info({ filePath, chunks: chunks.length }, 'File processed successfully');
|
|
1411
1485
|
}
|
|
1412
1486
|
catch (error) {
|
|
1413
|
-
this.logger.error({ filePath, error }, 'Failed to process file');
|
|
1487
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to process file');
|
|
1414
1488
|
}
|
|
1415
1489
|
}
|
|
1416
1490
|
/**
|
|
@@ -1430,7 +1504,7 @@ class DocumentProcessor {
|
|
|
1430
1504
|
this.logger.info({ filePath }, 'File deleted from index');
|
|
1431
1505
|
}
|
|
1432
1506
|
catch (error) {
|
|
1433
|
-
this.logger.error({ filePath, error }, 'Failed to delete file');
|
|
1507
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to delete file');
|
|
1434
1508
|
}
|
|
1435
1509
|
}
|
|
1436
1510
|
/**
|
|
@@ -1458,7 +1532,7 @@ class DocumentProcessor {
|
|
|
1458
1532
|
return merged;
|
|
1459
1533
|
}
|
|
1460
1534
|
catch (error) {
|
|
1461
|
-
this.logger.error({ filePath, error }, 'Failed to update metadata');
|
|
1535
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to update metadata');
|
|
1462
1536
|
return null;
|
|
1463
1537
|
}
|
|
1464
1538
|
}
|
|
@@ -1488,7 +1562,7 @@ class DocumentProcessor {
|
|
|
1488
1562
|
return metadata;
|
|
1489
1563
|
}
|
|
1490
1564
|
catch (error) {
|
|
1491
|
-
this.logger.error({ filePath, error }, 'Failed to re-apply rules');
|
|
1565
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to re-apply rules');
|
|
1492
1566
|
return null;
|
|
1493
1567
|
}
|
|
1494
1568
|
}
|
|
@@ -1716,7 +1790,12 @@ class VectorStoreClient {
|
|
|
1716
1790
|
maxDelayMs: 10_000,
|
|
1717
1791
|
jitter: 0.2,
|
|
1718
1792
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1719
|
-
this.log.warn({
|
|
1793
|
+
this.log.warn({
|
|
1794
|
+
attempt,
|
|
1795
|
+
delayMs,
|
|
1796
|
+
operation: 'qdrant.upsert',
|
|
1797
|
+
err: normalizeError(error),
|
|
1798
|
+
}, 'Qdrant upsert failed; will retry');
|
|
1720
1799
|
},
|
|
1721
1800
|
});
|
|
1722
1801
|
}
|
|
@@ -1742,7 +1821,12 @@ class VectorStoreClient {
|
|
|
1742
1821
|
maxDelayMs: 10_000,
|
|
1743
1822
|
jitter: 0.2,
|
|
1744
1823
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1745
|
-
this.log.warn({
|
|
1824
|
+
this.log.warn({
|
|
1825
|
+
attempt,
|
|
1826
|
+
delayMs,
|
|
1827
|
+
operation: 'qdrant.delete',
|
|
1828
|
+
err: normalizeError(error),
|
|
1829
|
+
}, 'Qdrant delete failed; will retry');
|
|
1746
1830
|
},
|
|
1747
1831
|
});
|
|
1748
1832
|
}
|
|
@@ -1896,7 +1980,7 @@ class FileSystemWatcher {
|
|
|
1896
1980
|
this.queue.enqueue({ type: 'delete', path, priority: 'normal' }, () => this.processor.deleteFile(path));
|
|
1897
1981
|
});
|
|
1898
1982
|
this.watcher.on('error', (error) => {
|
|
1899
|
-
this.logger.error({ error }, 'Watcher error');
|
|
1983
|
+
this.logger.error({ err: normalizeError(error) }, 'Watcher error');
|
|
1900
1984
|
});
|
|
1901
1985
|
this.queue.process();
|
|
1902
1986
|
this.logger.info({ paths: this.config.paths }, 'Filesystem watcher started');
|
|
@@ -1941,7 +2025,7 @@ class ConfigWatcher {
|
|
|
1941
2025
|
}, this.options.debounceMs);
|
|
1942
2026
|
});
|
|
1943
2027
|
this.watcher.on('error', (error) => {
|
|
1944
|
-
this.options.logger.error({ error }, 'Config watcher error');
|
|
2028
|
+
this.options.logger.error({ err: normalizeError(error) }, 'Config watcher error');
|
|
1945
2029
|
});
|
|
1946
2030
|
this.options.logger.info({
|
|
1947
2031
|
configPath: this.options.configPath,
|
|
@@ -2025,7 +2109,7 @@ class JeevesWatcher {
|
|
|
2025
2109
|
embeddingProvider = this.factories.createEmbeddingProvider(this.config.embedding, logger);
|
|
2026
2110
|
}
|
|
2027
2111
|
catch (error) {
|
|
2028
|
-
logger.fatal({ error }, 'Failed to create embedding provider');
|
|
2112
|
+
logger.fatal({ err: normalizeError(error) }, 'Failed to create embedding provider');
|
|
2029
2113
|
throw error;
|
|
2030
2114
|
}
|
|
2031
2115
|
const vectorStore = this.factories.createVectorStoreClient(this.config.vectorStore, embeddingProvider.dimensions, logger);
|
|
@@ -2132,7 +2216,7 @@ class JeevesWatcher {
|
|
|
2132
2216
|
logger.info({ configPath: this.configPath, rules: compiledRules.length }, 'Config reloaded');
|
|
2133
2217
|
}
|
|
2134
2218
|
catch (error) {
|
|
2135
|
-
logger.error({ error }, 'Failed to reload config');
|
|
2219
|
+
logger.error({ err: normalizeError(error) }, 'Failed to reload config');
|
|
2136
2220
|
}
|
|
2137
2221
|
}
|
|
2138
2222
|
}
|
|
@@ -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,51 @@ 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 env vars substituted.
|
|
684
|
+
* @throws If a referenced env var is not set.
|
|
685
|
+
*/
|
|
686
|
+
function substituteString(value) {
|
|
687
|
+
return value.replace(ENV_PATTERN, (match, varName) => {
|
|
688
|
+
const envValue = process.env[varName];
|
|
689
|
+
if (envValue === undefined) {
|
|
690
|
+
throw new Error(`Environment variable \${${varName}} referenced in config is not set.`);
|
|
691
|
+
}
|
|
692
|
+
return envValue;
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Deep-walk a value and substitute `${VAR_NAME}` patterns in all string values.
|
|
697
|
+
*
|
|
698
|
+
* @param value - The value to walk (object, array, or primitive).
|
|
699
|
+
* @returns A new value with all env var references resolved.
|
|
700
|
+
*/
|
|
701
|
+
function substituteEnvVars(value) {
|
|
702
|
+
if (typeof value === 'string') {
|
|
703
|
+
return substituteString(value);
|
|
704
|
+
}
|
|
705
|
+
if (Array.isArray(value)) {
|
|
706
|
+
return value.map((item) => substituteEnvVars(item));
|
|
707
|
+
}
|
|
708
|
+
if (value !== null && typeof value === 'object') {
|
|
709
|
+
const result = {};
|
|
710
|
+
for (const [key, val] of Object.entries(value)) {
|
|
711
|
+
result[key] = substituteEnvVars(val);
|
|
712
|
+
}
|
|
713
|
+
return result;
|
|
714
|
+
}
|
|
715
|
+
return value;
|
|
716
|
+
}
|
|
717
|
+
|
|
648
718
|
const MODULE_NAME = 'jeeves-watcher';
|
|
649
719
|
/**
|
|
650
720
|
* Merge sensible defaults into a loaded configuration.
|
|
@@ -680,7 +750,8 @@ async function loadConfig(configPath) {
|
|
|
680
750
|
}
|
|
681
751
|
try {
|
|
682
752
|
const validated = jeevesWatcherConfigSchema.parse(result.config);
|
|
683
|
-
|
|
753
|
+
const withDefaults = applyDefaults(validated);
|
|
754
|
+
return substituteEnvVars(withDefaults);
|
|
684
755
|
}
|
|
685
756
|
catch (error) {
|
|
686
757
|
if (error instanceof ZodError) {
|
|
@@ -847,7 +918,7 @@ function createGeminiProvider(config, logger) {
|
|
|
847
918
|
delayMs,
|
|
848
919
|
provider: 'gemini',
|
|
849
920
|
model: config.model,
|
|
850
|
-
error,
|
|
921
|
+
err: normalizeError(error),
|
|
851
922
|
}, 'Embedding call failed; will retry');
|
|
852
923
|
},
|
|
853
924
|
});
|
|
@@ -956,6 +1027,9 @@ function pointId(filePath, chunkIndex) {
|
|
|
956
1027
|
*/
|
|
957
1028
|
function extractMarkdownFrontmatter(markdown) {
|
|
958
1029
|
const trimmed = markdown.replace(/^\uFEFF/, '');
|
|
1030
|
+
// Only attempt frontmatter parsing if the file starts with ---
|
|
1031
|
+
if (!/^\s*---/.test(trimmed))
|
|
1032
|
+
return { body: markdown };
|
|
959
1033
|
const match = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m.exec(trimmed);
|
|
960
1034
|
if (!match)
|
|
961
1035
|
return { body: markdown };
|
|
@@ -1413,7 +1487,7 @@ class DocumentProcessor {
|
|
|
1413
1487
|
this.logger.info({ filePath, chunks: chunks.length }, 'File processed successfully');
|
|
1414
1488
|
}
|
|
1415
1489
|
catch (error) {
|
|
1416
|
-
this.logger.error({ filePath, error }, 'Failed to process file');
|
|
1490
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to process file');
|
|
1417
1491
|
}
|
|
1418
1492
|
}
|
|
1419
1493
|
/**
|
|
@@ -1433,7 +1507,7 @@ class DocumentProcessor {
|
|
|
1433
1507
|
this.logger.info({ filePath }, 'File deleted from index');
|
|
1434
1508
|
}
|
|
1435
1509
|
catch (error) {
|
|
1436
|
-
this.logger.error({ filePath, error }, 'Failed to delete file');
|
|
1510
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to delete file');
|
|
1437
1511
|
}
|
|
1438
1512
|
}
|
|
1439
1513
|
/**
|
|
@@ -1461,7 +1535,7 @@ class DocumentProcessor {
|
|
|
1461
1535
|
return merged;
|
|
1462
1536
|
}
|
|
1463
1537
|
catch (error) {
|
|
1464
|
-
this.logger.error({ filePath, error }, 'Failed to update metadata');
|
|
1538
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to update metadata');
|
|
1465
1539
|
return null;
|
|
1466
1540
|
}
|
|
1467
1541
|
}
|
|
@@ -1491,7 +1565,7 @@ class DocumentProcessor {
|
|
|
1491
1565
|
return metadata;
|
|
1492
1566
|
}
|
|
1493
1567
|
catch (error) {
|
|
1494
|
-
this.logger.error({ filePath, error }, 'Failed to re-apply rules');
|
|
1568
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to re-apply rules');
|
|
1495
1569
|
return null;
|
|
1496
1570
|
}
|
|
1497
1571
|
}
|
|
@@ -1719,7 +1793,12 @@ class VectorStoreClient {
|
|
|
1719
1793
|
maxDelayMs: 10_000,
|
|
1720
1794
|
jitter: 0.2,
|
|
1721
1795
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1722
|
-
this.log.warn({
|
|
1796
|
+
this.log.warn({
|
|
1797
|
+
attempt,
|
|
1798
|
+
delayMs,
|
|
1799
|
+
operation: 'qdrant.upsert',
|
|
1800
|
+
err: normalizeError(error),
|
|
1801
|
+
}, 'Qdrant upsert failed; will retry');
|
|
1723
1802
|
},
|
|
1724
1803
|
});
|
|
1725
1804
|
}
|
|
@@ -1745,7 +1824,12 @@ class VectorStoreClient {
|
|
|
1745
1824
|
maxDelayMs: 10_000,
|
|
1746
1825
|
jitter: 0.2,
|
|
1747
1826
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1748
|
-
this.log.warn({
|
|
1827
|
+
this.log.warn({
|
|
1828
|
+
attempt,
|
|
1829
|
+
delayMs,
|
|
1830
|
+
operation: 'qdrant.delete',
|
|
1831
|
+
err: normalizeError(error),
|
|
1832
|
+
}, 'Qdrant delete failed; will retry');
|
|
1749
1833
|
},
|
|
1750
1834
|
});
|
|
1751
1835
|
}
|
|
@@ -1899,7 +1983,7 @@ class FileSystemWatcher {
|
|
|
1899
1983
|
this.queue.enqueue({ type: 'delete', path, priority: 'normal' }, () => this.processor.deleteFile(path));
|
|
1900
1984
|
});
|
|
1901
1985
|
this.watcher.on('error', (error) => {
|
|
1902
|
-
this.logger.error({ error }, 'Watcher error');
|
|
1986
|
+
this.logger.error({ err: normalizeError(error) }, 'Watcher error');
|
|
1903
1987
|
});
|
|
1904
1988
|
this.queue.process();
|
|
1905
1989
|
this.logger.info({ paths: this.config.paths }, 'Filesystem watcher started');
|
|
@@ -1944,7 +2028,7 @@ class ConfigWatcher {
|
|
|
1944
2028
|
}, this.options.debounceMs);
|
|
1945
2029
|
});
|
|
1946
2030
|
this.watcher.on('error', (error) => {
|
|
1947
|
-
this.options.logger.error({ error }, 'Config watcher error');
|
|
2031
|
+
this.options.logger.error({ err: normalizeError(error) }, 'Config watcher error');
|
|
1948
2032
|
});
|
|
1949
2033
|
this.options.logger.info({
|
|
1950
2034
|
configPath: this.options.configPath,
|
|
@@ -2028,7 +2112,7 @@ class JeevesWatcher {
|
|
|
2028
2112
|
embeddingProvider = this.factories.createEmbeddingProvider(this.config.embedding, logger);
|
|
2029
2113
|
}
|
|
2030
2114
|
catch (error) {
|
|
2031
|
-
logger.fatal({ error }, 'Failed to create embedding provider');
|
|
2115
|
+
logger.fatal({ err: normalizeError(error) }, 'Failed to create embedding provider');
|
|
2032
2116
|
throw error;
|
|
2033
2117
|
}
|
|
2034
2118
|
const vectorStore = this.factories.createVectorStoreClient(this.config.vectorStore, embeddingProvider.dimensions, logger);
|
|
@@ -2135,7 +2219,7 @@ class JeevesWatcher {
|
|
|
2135
2219
|
logger.info({ configPath: this.configPath, rules: compiledRules.length }, 'Config reloaded');
|
|
2136
2220
|
}
|
|
2137
2221
|
catch (error) {
|
|
2138
|
-
logger.error({ error }, 'Failed to reload config');
|
|
2222
|
+
logger.error({ err: normalizeError(error) }, 'Failed to reload config');
|
|
2139
2223
|
}
|
|
2140
2224
|
}
|
|
2141
2225
|
}
|
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,51 @@
|
|
|
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 env vars substituted.
|
|
661
|
+
* @throws If a referenced env var is not set.
|
|
662
|
+
*/
|
|
663
|
+
function substituteString(value) {
|
|
664
|
+
return value.replace(ENV_PATTERN, (match, varName) => {
|
|
665
|
+
const envValue = process.env[varName];
|
|
666
|
+
if (envValue === undefined) {
|
|
667
|
+
throw new Error(`Environment variable \${${varName}} referenced in config is not set.`);
|
|
668
|
+
}
|
|
669
|
+
return envValue;
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Deep-walk a value and substitute `${VAR_NAME}` patterns in all string values.
|
|
674
|
+
*
|
|
675
|
+
* @param value - The value to walk (object, array, or primitive).
|
|
676
|
+
* @returns A new value with all env var references resolved.
|
|
677
|
+
*/
|
|
678
|
+
function substituteEnvVars(value) {
|
|
679
|
+
if (typeof value === 'string') {
|
|
680
|
+
return substituteString(value);
|
|
681
|
+
}
|
|
682
|
+
if (Array.isArray(value)) {
|
|
683
|
+
return value.map((item) => substituteEnvVars(item));
|
|
684
|
+
}
|
|
685
|
+
if (value !== null && typeof value === 'object') {
|
|
686
|
+
const result = {};
|
|
687
|
+
for (const [key, val] of Object.entries(value)) {
|
|
688
|
+
result[key] = substituteEnvVars(val);
|
|
689
|
+
}
|
|
690
|
+
return result;
|
|
691
|
+
}
|
|
692
|
+
return value;
|
|
693
|
+
}
|
|
694
|
+
|
|
625
695
|
const MODULE_NAME = 'jeeves-watcher';
|
|
626
696
|
/**
|
|
627
697
|
* Merge sensible defaults into a loaded configuration.
|
|
@@ -657,7 +727,8 @@
|
|
|
657
727
|
}
|
|
658
728
|
try {
|
|
659
729
|
const validated = jeevesWatcherConfigSchema.parse(result.config);
|
|
660
|
-
|
|
730
|
+
const withDefaults = applyDefaults(validated);
|
|
731
|
+
return substituteEnvVars(withDefaults);
|
|
661
732
|
}
|
|
662
733
|
catch (error) {
|
|
663
734
|
if (error instanceof zod.ZodError) {
|
|
@@ -824,7 +895,7 @@
|
|
|
824
895
|
delayMs,
|
|
825
896
|
provider: 'gemini',
|
|
826
897
|
model: config.model,
|
|
827
|
-
error,
|
|
898
|
+
err: normalizeError(error),
|
|
828
899
|
}, 'Embedding call failed; will retry');
|
|
829
900
|
},
|
|
830
901
|
});
|
|
@@ -933,6 +1004,9 @@
|
|
|
933
1004
|
*/
|
|
934
1005
|
function extractMarkdownFrontmatter(markdown) {
|
|
935
1006
|
const trimmed = markdown.replace(/^\uFEFF/, '');
|
|
1007
|
+
// Only attempt frontmatter parsing if the file starts with ---
|
|
1008
|
+
if (!/^\s*---/.test(trimmed))
|
|
1009
|
+
return { body: markdown };
|
|
936
1010
|
const match = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m.exec(trimmed);
|
|
937
1011
|
if (!match)
|
|
938
1012
|
return { body: markdown };
|
|
@@ -1390,7 +1464,7 @@
|
|
|
1390
1464
|
this.logger.info({ filePath, chunks: chunks.length }, 'File processed successfully');
|
|
1391
1465
|
}
|
|
1392
1466
|
catch (error) {
|
|
1393
|
-
this.logger.error({ filePath, error }, 'Failed to process file');
|
|
1467
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to process file');
|
|
1394
1468
|
}
|
|
1395
1469
|
}
|
|
1396
1470
|
/**
|
|
@@ -1410,7 +1484,7 @@
|
|
|
1410
1484
|
this.logger.info({ filePath }, 'File deleted from index');
|
|
1411
1485
|
}
|
|
1412
1486
|
catch (error) {
|
|
1413
|
-
this.logger.error({ filePath, error }, 'Failed to delete file');
|
|
1487
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to delete file');
|
|
1414
1488
|
}
|
|
1415
1489
|
}
|
|
1416
1490
|
/**
|
|
@@ -1438,7 +1512,7 @@
|
|
|
1438
1512
|
return merged;
|
|
1439
1513
|
}
|
|
1440
1514
|
catch (error) {
|
|
1441
|
-
this.logger.error({ filePath, error }, 'Failed to update metadata');
|
|
1515
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to update metadata');
|
|
1442
1516
|
return null;
|
|
1443
1517
|
}
|
|
1444
1518
|
}
|
|
@@ -1468,7 +1542,7 @@
|
|
|
1468
1542
|
return metadata;
|
|
1469
1543
|
}
|
|
1470
1544
|
catch (error) {
|
|
1471
|
-
this.logger.error({ filePath, error }, 'Failed to re-apply rules');
|
|
1545
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to re-apply rules');
|
|
1472
1546
|
return null;
|
|
1473
1547
|
}
|
|
1474
1548
|
}
|
|
@@ -1696,7 +1770,12 @@
|
|
|
1696
1770
|
maxDelayMs: 10_000,
|
|
1697
1771
|
jitter: 0.2,
|
|
1698
1772
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1699
|
-
this.log.warn({
|
|
1773
|
+
this.log.warn({
|
|
1774
|
+
attempt,
|
|
1775
|
+
delayMs,
|
|
1776
|
+
operation: 'qdrant.upsert',
|
|
1777
|
+
err: normalizeError(error),
|
|
1778
|
+
}, 'Qdrant upsert failed; will retry');
|
|
1700
1779
|
},
|
|
1701
1780
|
});
|
|
1702
1781
|
}
|
|
@@ -1722,7 +1801,12 @@
|
|
|
1722
1801
|
maxDelayMs: 10_000,
|
|
1723
1802
|
jitter: 0.2,
|
|
1724
1803
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1725
|
-
this.log.warn({
|
|
1804
|
+
this.log.warn({
|
|
1805
|
+
attempt,
|
|
1806
|
+
delayMs,
|
|
1807
|
+
operation: 'qdrant.delete',
|
|
1808
|
+
err: normalizeError(error),
|
|
1809
|
+
}, 'Qdrant delete failed; will retry');
|
|
1726
1810
|
},
|
|
1727
1811
|
});
|
|
1728
1812
|
}
|
|
@@ -1876,7 +1960,7 @@
|
|
|
1876
1960
|
this.queue.enqueue({ type: 'delete', path, priority: 'normal' }, () => this.processor.deleteFile(path));
|
|
1877
1961
|
});
|
|
1878
1962
|
this.watcher.on('error', (error) => {
|
|
1879
|
-
this.logger.error({ error }, 'Watcher error');
|
|
1963
|
+
this.logger.error({ err: normalizeError(error) }, 'Watcher error');
|
|
1880
1964
|
});
|
|
1881
1965
|
this.queue.process();
|
|
1882
1966
|
this.logger.info({ paths: this.config.paths }, 'Filesystem watcher started');
|
|
@@ -1921,7 +2005,7 @@
|
|
|
1921
2005
|
}, this.options.debounceMs);
|
|
1922
2006
|
});
|
|
1923
2007
|
this.watcher.on('error', (error) => {
|
|
1924
|
-
this.options.logger.error({ error }, 'Config watcher error');
|
|
2008
|
+
this.options.logger.error({ err: normalizeError(error) }, 'Config watcher error');
|
|
1925
2009
|
});
|
|
1926
2010
|
this.options.logger.info({
|
|
1927
2011
|
configPath: this.options.configPath,
|
|
@@ -2005,7 +2089,7 @@
|
|
|
2005
2089
|
embeddingProvider = this.factories.createEmbeddingProvider(this.config.embedding, logger);
|
|
2006
2090
|
}
|
|
2007
2091
|
catch (error) {
|
|
2008
|
-
logger.fatal({ error }, 'Failed to create embedding provider');
|
|
2092
|
+
logger.fatal({ err: normalizeError(error) }, 'Failed to create embedding provider');
|
|
2009
2093
|
throw error;
|
|
2010
2094
|
}
|
|
2011
2095
|
const vectorStore = this.factories.createVectorStoreClient(this.config.vectorStore, embeddingProvider.dimensions, logger);
|
|
@@ -2112,7 +2196,7 @@
|
|
|
2112
2196
|
logger.info({ configPath: this.configPath, rules: compiledRules.length }, 'Config reloaded');
|
|
2113
2197
|
}
|
|
2114
2198
|
catch (error) {
|
|
2115
|
-
logger.error({ error }, 'Failed to reload config');
|
|
2199
|
+
logger.error({ err: normalizeError(error) }, 'Failed to reload config');
|
|
2116
2200
|
}
|
|
2117
2201
|
}
|
|
2118
2202
|
}
|
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.")}),$=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).")}),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:$.describe("Embedding model configuration."),vectorStore:Q.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];if(void 0===i)throw new Error(`Environment variable \${${t}} referenced in config is not set.`);return 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=$,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=Q,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,51 @@ 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 env vars substituted.
|
|
660
|
+
* @throws If a referenced env var is not set.
|
|
661
|
+
*/
|
|
662
|
+
function substituteString(value) {
|
|
663
|
+
return value.replace(ENV_PATTERN, (match, varName) => {
|
|
664
|
+
const envValue = process.env[varName];
|
|
665
|
+
if (envValue === undefined) {
|
|
666
|
+
throw new Error(`Environment variable \${${varName}} referenced in config is not set.`);
|
|
667
|
+
}
|
|
668
|
+
return envValue;
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Deep-walk a value and substitute `${VAR_NAME}` patterns in all string values.
|
|
673
|
+
*
|
|
674
|
+
* @param value - The value to walk (object, array, or primitive).
|
|
675
|
+
* @returns A new value with all env var references resolved.
|
|
676
|
+
*/
|
|
677
|
+
function substituteEnvVars(value) {
|
|
678
|
+
if (typeof value === 'string') {
|
|
679
|
+
return substituteString(value);
|
|
680
|
+
}
|
|
681
|
+
if (Array.isArray(value)) {
|
|
682
|
+
return value.map((item) => substituteEnvVars(item));
|
|
683
|
+
}
|
|
684
|
+
if (value !== null && typeof value === 'object') {
|
|
685
|
+
const result = {};
|
|
686
|
+
for (const [key, val] of Object.entries(value)) {
|
|
687
|
+
result[key] = substituteEnvVars(val);
|
|
688
|
+
}
|
|
689
|
+
return result;
|
|
690
|
+
}
|
|
691
|
+
return value;
|
|
692
|
+
}
|
|
693
|
+
|
|
624
694
|
const MODULE_NAME = 'jeeves-watcher';
|
|
625
695
|
/**
|
|
626
696
|
* Merge sensible defaults into a loaded configuration.
|
|
@@ -656,7 +726,8 @@ async function loadConfig(configPath) {
|
|
|
656
726
|
}
|
|
657
727
|
try {
|
|
658
728
|
const validated = jeevesWatcherConfigSchema.parse(result.config);
|
|
659
|
-
|
|
729
|
+
const withDefaults = applyDefaults(validated);
|
|
730
|
+
return substituteEnvVars(withDefaults);
|
|
660
731
|
}
|
|
661
732
|
catch (error) {
|
|
662
733
|
if (error instanceof ZodError) {
|
|
@@ -823,7 +894,7 @@ function createGeminiProvider(config, logger) {
|
|
|
823
894
|
delayMs,
|
|
824
895
|
provider: 'gemini',
|
|
825
896
|
model: config.model,
|
|
826
|
-
error,
|
|
897
|
+
err: normalizeError(error),
|
|
827
898
|
}, 'Embedding call failed; will retry');
|
|
828
899
|
},
|
|
829
900
|
});
|
|
@@ -932,6 +1003,9 @@ function pointId(filePath, chunkIndex) {
|
|
|
932
1003
|
*/
|
|
933
1004
|
function extractMarkdownFrontmatter(markdown) {
|
|
934
1005
|
const trimmed = markdown.replace(/^\uFEFF/, '');
|
|
1006
|
+
// Only attempt frontmatter parsing if the file starts with ---
|
|
1007
|
+
if (!/^\s*---/.test(trimmed))
|
|
1008
|
+
return { body: markdown };
|
|
935
1009
|
const match = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m.exec(trimmed);
|
|
936
1010
|
if (!match)
|
|
937
1011
|
return { body: markdown };
|
|
@@ -1389,7 +1463,7 @@ class DocumentProcessor {
|
|
|
1389
1463
|
this.logger.info({ filePath, chunks: chunks.length }, 'File processed successfully');
|
|
1390
1464
|
}
|
|
1391
1465
|
catch (error) {
|
|
1392
|
-
this.logger.error({ filePath, error }, 'Failed to process file');
|
|
1466
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to process file');
|
|
1393
1467
|
}
|
|
1394
1468
|
}
|
|
1395
1469
|
/**
|
|
@@ -1409,7 +1483,7 @@ class DocumentProcessor {
|
|
|
1409
1483
|
this.logger.info({ filePath }, 'File deleted from index');
|
|
1410
1484
|
}
|
|
1411
1485
|
catch (error) {
|
|
1412
|
-
this.logger.error({ filePath, error }, 'Failed to delete file');
|
|
1486
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to delete file');
|
|
1413
1487
|
}
|
|
1414
1488
|
}
|
|
1415
1489
|
/**
|
|
@@ -1437,7 +1511,7 @@ class DocumentProcessor {
|
|
|
1437
1511
|
return merged;
|
|
1438
1512
|
}
|
|
1439
1513
|
catch (error) {
|
|
1440
|
-
this.logger.error({ filePath, error }, 'Failed to update metadata');
|
|
1514
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to update metadata');
|
|
1441
1515
|
return null;
|
|
1442
1516
|
}
|
|
1443
1517
|
}
|
|
@@ -1467,7 +1541,7 @@ class DocumentProcessor {
|
|
|
1467
1541
|
return metadata;
|
|
1468
1542
|
}
|
|
1469
1543
|
catch (error) {
|
|
1470
|
-
this.logger.error({ filePath, error }, 'Failed to re-apply rules');
|
|
1544
|
+
this.logger.error({ filePath, err: normalizeError(error) }, 'Failed to re-apply rules');
|
|
1471
1545
|
return null;
|
|
1472
1546
|
}
|
|
1473
1547
|
}
|
|
@@ -1695,7 +1769,12 @@ class VectorStoreClient {
|
|
|
1695
1769
|
maxDelayMs: 10_000,
|
|
1696
1770
|
jitter: 0.2,
|
|
1697
1771
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1698
|
-
this.log.warn({
|
|
1772
|
+
this.log.warn({
|
|
1773
|
+
attempt,
|
|
1774
|
+
delayMs,
|
|
1775
|
+
operation: 'qdrant.upsert',
|
|
1776
|
+
err: normalizeError(error),
|
|
1777
|
+
}, 'Qdrant upsert failed; will retry');
|
|
1699
1778
|
},
|
|
1700
1779
|
});
|
|
1701
1780
|
}
|
|
@@ -1721,7 +1800,12 @@ class VectorStoreClient {
|
|
|
1721
1800
|
maxDelayMs: 10_000,
|
|
1722
1801
|
jitter: 0.2,
|
|
1723
1802
|
onRetry: ({ attempt, delayMs, error }) => {
|
|
1724
|
-
this.log.warn({
|
|
1803
|
+
this.log.warn({
|
|
1804
|
+
attempt,
|
|
1805
|
+
delayMs,
|
|
1806
|
+
operation: 'qdrant.delete',
|
|
1807
|
+
err: normalizeError(error),
|
|
1808
|
+
}, 'Qdrant delete failed; will retry');
|
|
1725
1809
|
},
|
|
1726
1810
|
});
|
|
1727
1811
|
}
|
|
@@ -1875,7 +1959,7 @@ class FileSystemWatcher {
|
|
|
1875
1959
|
this.queue.enqueue({ type: 'delete', path, priority: 'normal' }, () => this.processor.deleteFile(path));
|
|
1876
1960
|
});
|
|
1877
1961
|
this.watcher.on('error', (error) => {
|
|
1878
|
-
this.logger.error({ error }, 'Watcher error');
|
|
1962
|
+
this.logger.error({ err: normalizeError(error) }, 'Watcher error');
|
|
1879
1963
|
});
|
|
1880
1964
|
this.queue.process();
|
|
1881
1965
|
this.logger.info({ paths: this.config.paths }, 'Filesystem watcher started');
|
|
@@ -1920,7 +2004,7 @@ class ConfigWatcher {
|
|
|
1920
2004
|
}, this.options.debounceMs);
|
|
1921
2005
|
});
|
|
1922
2006
|
this.watcher.on('error', (error) => {
|
|
1923
|
-
this.options.logger.error({ error }, 'Config watcher error');
|
|
2007
|
+
this.options.logger.error({ err: normalizeError(error) }, 'Config watcher error');
|
|
1924
2008
|
});
|
|
1925
2009
|
this.options.logger.info({
|
|
1926
2010
|
configPath: this.options.configPath,
|
|
@@ -2004,7 +2088,7 @@ class JeevesWatcher {
|
|
|
2004
2088
|
embeddingProvider = this.factories.createEmbeddingProvider(this.config.embedding, logger);
|
|
2005
2089
|
}
|
|
2006
2090
|
catch (error) {
|
|
2007
|
-
logger.fatal({ error }, 'Failed to create embedding provider');
|
|
2091
|
+
logger.fatal({ err: normalizeError(error) }, 'Failed to create embedding provider');
|
|
2008
2092
|
throw error;
|
|
2009
2093
|
}
|
|
2010
2094
|
const vectorStore = this.factories.createVectorStoreClient(this.config.vectorStore, embeddingProvider.dimensions, logger);
|
|
@@ -2111,7 +2195,7 @@ class JeevesWatcher {
|
|
|
2111
2195
|
logger.info({ configPath: this.configPath, rules: compiledRules.length }, 'Config reloaded');
|
|
2112
2196
|
}
|
|
2113
2197
|
catch (error) {
|
|
2114
|
-
logger.error({ error }, 'Failed to reload config');
|
|
2198
|
+
logger.error({ err: normalizeError(error) }, 'Failed to reload config');
|
|
2115
2199
|
}
|
|
2116
2200
|
}
|
|
2117
2201
|
}
|
package/package.json
CHANGED