dsh-locator 0.7.1 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README-es.md +37 -5
- package/README-hi.md +35 -5
- package/README-pt.md +33 -5
- package/README.en.md +34 -4
- package/README.md +27 -5
- package/client.js +117 -17
- package/cordis.patch.yml +13 -0
- package/lib/config.d.ts +20 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +38 -4
- package/lib/config.js.map +1 -1
- package/lib/content-search-worker.d.ts +8 -0
- package/lib/content-search-worker.d.ts.map +1 -0
- package/lib/content-search-worker.js +54 -0
- package/lib/content-search-worker.js.map +1 -0
- package/lib/content-search.d.ts +15 -0
- package/lib/content-search.d.ts.map +1 -0
- package/lib/content-search.js +44 -0
- package/lib/content-search.js.map +1 -0
- package/lib/content.d.ts +83 -4
- package/lib/content.d.ts.map +1 -1
- package/lib/content.js +496 -77
- package/lib/content.js.map +1 -1
- package/lib/find.d.ts +1 -1
- package/lib/find.d.ts.map +1 -1
- package/lib/find.js +8 -3
- package/lib/find.js.map +1 -1
- package/lib/http.d.ts +1 -1
- package/lib/http.d.ts.map +1 -1
- package/lib/http.js +4 -2
- package/lib/http.js.map +1 -1
- package/lib/index.d.ts +5 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +106 -27
- package/lib/index.js.map +1 -1
- package/lib/reindex.d.ts +1 -1
- package/lib/reindex.d.ts.map +1 -1
- package/lib/reindex.js +2 -1
- package/lib/reindex.js.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +58 -4
- package/src/content-search-worker.ts +52 -0
- package/src/content-search.ts +45 -0
- package/src/content.ts +456 -71
- package/src/find.ts +8 -3
- package/src/http.ts +4 -2
- package/src/index.ts +106 -25
- package/src/reindex.ts +2 -1
package/client.js
CHANGED
|
@@ -277,23 +277,98 @@ window.__ModuleLoader__.load({
|
|
|
277
277
|
// ── panel ───────────────────────────────────────────────────────────────
|
|
278
278
|
let workspaceService = null
|
|
279
279
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
280
|
+
// ── chat anchors ────────────────────────────────────────────────────────
|
|
281
|
+
// The Chat flow renders one `[data-chat-node-key]` row per node and the key
|
|
282
|
+
// of a human input embeds the message id the content index stored
|
|
283
|
+
// (`"<n>:input-message<messageId>"`). Anchor on that id first: it lands on
|
|
284
|
+
// the exact message instead of whichever copy of the words comes first in
|
|
285
|
+
// the document (the sidebar title, for example).
|
|
286
|
+
|
|
287
|
+
function chatScope() {
|
|
288
|
+
return document.querySelector('[data-conversation-scroll]')
|
|
289
|
+
|| document.querySelector('[data-slot="conversation.chat.flow"]')
|
|
290
|
+
|| null
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function findMessageNode(messageId) {
|
|
294
|
+
if (!messageId) return null
|
|
295
|
+
const id = String(messageId)
|
|
296
|
+
if (id === '') return null
|
|
297
|
+
const rows = document.querySelectorAll('[data-chat-node-key]')
|
|
298
|
+
for (const row of rows) {
|
|
299
|
+
const key = row.getAttribute('data-chat-node-key') || ''
|
|
300
|
+
if (key === id || key.endsWith(id)) return row
|
|
301
|
+
}
|
|
302
|
+
return null
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function markElement(element) {
|
|
306
|
+
if (!element || typeof element.scrollIntoView !== 'function') return false
|
|
307
|
+
element.scrollIntoView({ block: 'center' })
|
|
308
|
+
const previous = element.style.outline
|
|
309
|
+
const previousOffset = element.style.outlineOffset
|
|
310
|
+
const previousRadius = element.style.borderRadius
|
|
311
|
+
element.style.outline = '2px solid #4f7cff'
|
|
312
|
+
element.style.outlineOffset = '3px'
|
|
313
|
+
element.style.borderRadius = '8px'
|
|
314
|
+
clearTimeout(element.__dshLocatorMark)
|
|
315
|
+
element.__dshLocatorMark = setTimeout(() => {
|
|
316
|
+
element.style.outline = previous
|
|
317
|
+
element.style.outlineOffset = previousOffset
|
|
318
|
+
element.style.borderRadius = previousRadius
|
|
319
|
+
}, 2400)
|
|
320
|
+
return true
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Progressively shorter fallbacks: a FTS snippet may be cut mid-word or
|
|
324
|
+
// separated by ellipses, so try the whole phrase before the leading words.
|
|
325
|
+
function phraseCandidates(snippet) {
|
|
326
|
+
const clean = String(snippet || '')
|
|
327
|
+
.replace(/[\u0001\u0002]/g, ' ')
|
|
328
|
+
.replace(/…/g, ' ')
|
|
329
|
+
.replace(/\s+/g, ' ')
|
|
330
|
+
.trim()
|
|
331
|
+
const out = []
|
|
332
|
+
const push = value => {
|
|
333
|
+
const text = String(value || '').trim()
|
|
334
|
+
if (text.length >= 2 && !out.includes(text)) out.push(text)
|
|
335
|
+
}
|
|
336
|
+
if (clean === '') return out
|
|
337
|
+
push(clean)
|
|
338
|
+
const words = clean.split(' ')
|
|
339
|
+
if (words.length > 1) {
|
|
340
|
+
push(words.slice(0, 8).join(' '))
|
|
341
|
+
push(words.slice(0, 4).join(' '))
|
|
342
|
+
push(words.slice(0, 2).join(' '))
|
|
343
|
+
}
|
|
344
|
+
push(clean.slice(0, 24))
|
|
345
|
+
push(clean.slice(0, 12))
|
|
346
|
+
return out
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function findPhraseNode(scope, phrase) {
|
|
350
|
+
if (!scope || phrase.length < 2) return null
|
|
351
|
+
const needle = phrase.toLowerCase()
|
|
352
|
+
const walker = document.createTreeWalker(scope, NodeFilter.SHOW_TEXT)
|
|
284
353
|
let node
|
|
285
354
|
while ((node = walker.nextNode())) {
|
|
286
355
|
const text = node.nodeValue || ''
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
356
|
+
if (text.toLowerCase().indexOf(needle) < 0) continue
|
|
357
|
+
// The scope is the conversation transcript, so the first match already
|
|
358
|
+
// belongs to the bubble that contains the phrase.
|
|
359
|
+
const parent = node.parentElement
|
|
360
|
+
if (parent) return parent
|
|
361
|
+
}
|
|
362
|
+
return null
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function jumpHighlight(snippet, messageId) {
|
|
366
|
+
const anchor = findMessageNode(messageId)
|
|
367
|
+
if (anchor !== null) return markElement(anchor)
|
|
368
|
+
const scope = chatScope()
|
|
369
|
+
for (const phrase of phraseCandidates(snippet)) {
|
|
370
|
+
const element = findPhraseNode(scope, phrase)
|
|
371
|
+
if (element !== null) return markElement(element)
|
|
297
372
|
}
|
|
298
373
|
return false
|
|
299
374
|
}
|
|
@@ -312,6 +387,8 @@ window.__ModuleLoader__.load({
|
|
|
312
387
|
const [total, setTotal] = useState(0)
|
|
313
388
|
const [took, setTook] = useState(0)
|
|
314
389
|
const [truncated, setTruncated] = useState(false)
|
|
390
|
+
const [contentBuilding, setContentBuilding] = useState(false)
|
|
391
|
+
const [refreshTick, setRefreshTick] = useState(0)
|
|
315
392
|
const [status, setStatus] = useState(null)
|
|
316
393
|
const [error, setError] = useState('')
|
|
317
394
|
const [loading, setLoading] = useState(false)
|
|
@@ -373,6 +450,7 @@ window.__ModuleLoader__.load({
|
|
|
373
450
|
setTotal(typeof data.total === 'number' ? data.total : nextHits.length)
|
|
374
451
|
setTook(typeof data.tookMs === 'number' ? data.tookMs : 0)
|
|
375
452
|
setTruncated(data.truncated === true)
|
|
453
|
+
setContentBuilding(contentMode && data.state != null && data.state.building === true)
|
|
376
454
|
setSelected(0)
|
|
377
455
|
setLoading(false)
|
|
378
456
|
}).catch(cause => {
|
|
@@ -384,7 +462,15 @@ window.__ModuleLoader__.load({
|
|
|
384
462
|
})
|
|
385
463
|
}, 110)
|
|
386
464
|
return () => clearTimeout(timer)
|
|
387
|
-
}, [open, query, sort, desc, filesOnly, dirsOnly, contentMode])
|
|
465
|
+
}, [open, query, sort, desc, filesOnly, dirsOnly, contentMode, refreshTick])
|
|
466
|
+
|
|
467
|
+
// The content index is built lazily on first use; poll until it settles so
|
|
468
|
+
// the first query fills in without the user retyping it.
|
|
469
|
+
useEffect(() => {
|
|
470
|
+
if (!open || !contentMode || !contentBuilding) return undefined
|
|
471
|
+
const timer = setTimeout(() => setRefreshTick(value => value + 1), 2500)
|
|
472
|
+
return () => clearTimeout(timer)
|
|
473
|
+
}, [open, contentMode, contentBuilding, refreshTick])
|
|
388
474
|
|
|
389
475
|
const toggleSort = useCallback(key => {
|
|
390
476
|
if (sort === key) {
|
|
@@ -425,10 +511,23 @@ window.__ModuleLoader__.load({
|
|
|
425
511
|
return
|
|
426
512
|
}
|
|
427
513
|
const snippet = hit.snippet
|
|
514
|
+
const messageId = hit.messageId
|
|
428
515
|
let tries = 0
|
|
516
|
+
let pulledEarlier = false
|
|
429
517
|
const timer = setInterval(() => {
|
|
430
518
|
tries++
|
|
431
|
-
if (jumpHighlight(snippet
|
|
519
|
+
if (jumpHighlight(snippet, messageId)) {
|
|
520
|
+
clearInterval(timer)
|
|
521
|
+
return
|
|
522
|
+
}
|
|
523
|
+
// A long session renders its tail first: nudge the transcript to the
|
|
524
|
+
// top once so paging can load the message the hit points at.
|
|
525
|
+
if (!pulledEarlier && tries === 15) {
|
|
526
|
+
pulledEarlier = true
|
|
527
|
+
const scroller = document.querySelector('[data-conversation-scroll]')
|
|
528
|
+
if (scroller !== null && scroller.scrollTop > 0) scroller.scrollTop = 0
|
|
529
|
+
}
|
|
530
|
+
if (tries > 100) clearInterval(timer)
|
|
432
531
|
}, 150)
|
|
433
532
|
}, [close, copy, notify])
|
|
434
533
|
|
|
@@ -593,7 +692,8 @@ window.__ModuleLoader__.load({
|
|
|
593
692
|
loading
|
|
594
693
|
? t('loading')
|
|
595
694
|
: t('results', { total: total.toLocaleString() }) + (truncated ? ' (' + t('showing', { n: hits.length }) + ')' : '') + ' · ' + took.toFixed(1) + ' ms'
|
|
596
|
-
+ (status && status.scanning ? ' · ' + t('indexing') : '')
|
|
695
|
+
+ (status && status.scanning ? ' · ' + t('indexing') : '')
|
|
696
|
+
+ (contentBuilding ? ' · ' + t('indexing') : '')),
|
|
597
697
|
h('span', { className: 'grow' }),
|
|
598
698
|
toast === '' ? null : h('span', { className: 'dsh-locator-toast' }, toast),
|
|
599
699
|
h('span', null, t('footer')),
|
package/cordis.patch.yml
CHANGED
|
@@ -38,6 +38,19 @@
|
|
|
38
38
|
# directory walk. Requires DSH to run as administrator; on any failure
|
|
39
39
|
# the engine falls back to the walk automatically.
|
|
40
40
|
windowsUsn: false
|
|
41
|
+
# Content search reads text bodies and DSH session messages into a
|
|
42
|
+
# local SQLite FTS5 database. The defaults are documented in the README;
|
|
43
|
+
# override them here (or in the profile patch) when needed.
|
|
44
|
+
# contentIndex: true
|
|
45
|
+
# contentMaxFileBytes: 1000000
|
|
46
|
+
# contentMaxTotalBytes: 1000000000
|
|
47
|
+
# contentMaxFiles: 200000
|
|
48
|
+
# contentMaxSessions: 100
|
|
49
|
+
# contentRefreshMs: 300000
|
|
50
|
+
# contentIncludeArchived: false
|
|
51
|
+
# contentActiveSessionDelayMs: 120000
|
|
52
|
+
# contentSessionDirs: []
|
|
53
|
+
# contentExcludes: [node_modules, .cargo, .bun, .pnpm, site-packages, toolchain]
|
|
41
54
|
# Snapshot directory. Empty means the platform cache directory
|
|
42
55
|
# (%LOCALAPPDATA%\dsh-locator, ~/Library/Application Support/dsh-locator,
|
|
43
56
|
# or $XDG_CACHE_HOME/dsh-locator).
|
package/lib/config.d.ts
CHANGED
|
@@ -25,6 +25,12 @@ export interface Config {
|
|
|
25
25
|
memoryLimitMB: number;
|
|
26
26
|
/** Index text-file bodies and DSH session messages into the content database. */
|
|
27
27
|
contentIndex: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Build the content index on the first content search instead of at plugin
|
|
30
|
+
* start. Lazy keeps DSH startup free of content indexing; set false to build
|
|
31
|
+
* eagerly in the background right after startup.
|
|
32
|
+
*/
|
|
33
|
+
contentLazy: boolean;
|
|
28
34
|
/** Maximum bytes read from one text file. */
|
|
29
35
|
contentMaxFileBytes: number;
|
|
30
36
|
/** Stop adding content after this many body bytes; 0 means unlimited. */
|
|
@@ -35,8 +41,22 @@ export interface Config {
|
|
|
35
41
|
contentMaxFiles: number;
|
|
36
42
|
/** Most recent session logs indexed; 0 means all. */
|
|
37
43
|
contentMaxSessions: number;
|
|
44
|
+
/** Index archived sessions too; the default leaves them out. */
|
|
45
|
+
contentIncludeArchived: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Session logs modified more recently than this are skipped until they have
|
|
48
|
+
* been quiet that long, so the session being written now is not re-read on
|
|
49
|
+
* every background refresh.
|
|
50
|
+
*/
|
|
51
|
+
contentActiveSessionDelayMs: number;
|
|
38
52
|
/** Extra session directories; empty means $DSH_HOME/sessions. */
|
|
39
53
|
contentSessionDirs: string[];
|
|
54
|
+
/**
|
|
55
|
+
* Path segments or substrings skipped by the content index only: dependency
|
|
56
|
+
* trees and toolchains would otherwise eat the body-bytes budget before a
|
|
57
|
+
* user's own documents. The name index still covers them.
|
|
58
|
+
*/
|
|
59
|
+
contentExcludes: string[];
|
|
40
60
|
/**
|
|
41
61
|
* Try the NTFS USN/MFT enumeration before the portable walk (Windows only,
|
|
42
62
|
* requires running DSH as administrator). Falls back automatically.
|
package/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAE7C,oCAAoC;AACpC,MAAM,WAAW,MAAM;IACrB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAA;IAChB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAA;IACd;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAA;IACzB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAA;IACnB,gFAAgF;IAChF,aAAa,EAAE,MAAM,CAAA;IACrB,iFAAiF;IACjF,YAAY,EAAE,OAAO,CAAA;IACrB,6CAA6C;IAC7C,mBAAmB,EAAE,MAAM,CAAA;IAC3B,yEAAyE;IACzE,oBAAoB,EAAE,MAAM,CAAA;IAC5B,0DAA0D;IAC1D,gBAAgB,EAAE,MAAM,CAAA;IACxB,4DAA4D;IAC5D,eAAe,EAAE,MAAM,CAAA;IACvB,qDAAqD;IACrD,kBAAkB,EAAE,MAAM,CAAA;IAC1B,iEAAiE;IACjE,kBAAkB,EAAE,MAAM,EAAE,CAAA;IAC5B;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAA;IACnB,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAA;IAChB,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAA;IAClB,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAA;IACxB,2CAA2C;IAC3C,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED,yEAAyE;AACzE,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAE7C,oCAAoC;AACpC,MAAM,WAAW,MAAM;IACrB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAA;IAChB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAA;IACd;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAA;IACzB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAA;IACnB,gFAAgF;IAChF,aAAa,EAAE,MAAM,CAAA;IACrB,iFAAiF;IACjF,YAAY,EAAE,OAAO,CAAA;IACrB;;;;OAIG;IACH,WAAW,EAAE,OAAO,CAAA;IACpB,6CAA6C;IAC7C,mBAAmB,EAAE,MAAM,CAAA;IAC3B,yEAAyE;IACzE,oBAAoB,EAAE,MAAM,CAAA;IAC5B,0DAA0D;IAC1D,gBAAgB,EAAE,MAAM,CAAA;IACxB,4DAA4D;IAC5D,eAAe,EAAE,MAAM,CAAA;IACvB,qDAAqD;IACrD,kBAAkB,EAAE,MAAM,CAAA;IAC1B,gEAAgE;IAChE,sBAAsB,EAAE,OAAO,CAAA;IAC/B;;;;OAIG;IACH,2BAA2B,EAAE,MAAM,CAAA;IACnC,iEAAiE;IACjE,kBAAkB,EAAE,MAAM,EAAE,CAAA;IAC5B;;;;OAIG;IACH,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAA;IACnB,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAA;IAChB,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAA;IAClB,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAA;IACxB,2CAA2C;IAC3C,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED,yEAAyE;AACzE,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,CAiEhC,CAAA"}
|
package/lib/config.js
CHANGED
|
@@ -23,12 +23,46 @@ export const Config = Schema.object({
|
|
|
23
23
|
scanWorkers: Schema.number().default(0),
|
|
24
24
|
memoryLimitMB: Schema.number().default(0),
|
|
25
25
|
contentIndex: Schema.boolean().default(true),
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
contentLazy: Schema.boolean().default(true),
|
|
27
|
+
contentMaxFileBytes: Schema.number().default(1_000_000),
|
|
28
|
+
contentMaxTotalBytes: Schema.number().default(1_000_000_000),
|
|
29
|
+
contentRefreshMs: Schema.number().default(300_000),
|
|
29
30
|
contentMaxFiles: Schema.number().default(200_000),
|
|
30
|
-
contentMaxSessions: Schema.number().default(
|
|
31
|
+
contentMaxSessions: Schema.number().default(100),
|
|
32
|
+
contentIncludeArchived: Schema.boolean().default(false),
|
|
33
|
+
contentActiveSessionDelayMs: Schema.number().default(120_000),
|
|
31
34
|
contentSessionDirs: Schema.array(Schema.string()).default([]),
|
|
35
|
+
contentExcludes: Schema.array(Schema.string()).default([
|
|
36
|
+
'node_modules',
|
|
37
|
+
'.git',
|
|
38
|
+
'.hg',
|
|
39
|
+
'.svn',
|
|
40
|
+
'.cache',
|
|
41
|
+
'__pycache__',
|
|
42
|
+
'.venv',
|
|
43
|
+
'venv',
|
|
44
|
+
'.next',
|
|
45
|
+
'.turbo',
|
|
46
|
+
'.yarn',
|
|
47
|
+
'.pnpm',
|
|
48
|
+
'.npm',
|
|
49
|
+
'.cargo',
|
|
50
|
+
'.rustup',
|
|
51
|
+
'.bun',
|
|
52
|
+
'.gradle',
|
|
53
|
+
'.m2',
|
|
54
|
+
'.nuget',
|
|
55
|
+
'.nvm',
|
|
56
|
+
'.pyenv',
|
|
57
|
+
'.conda',
|
|
58
|
+
'.asdf',
|
|
59
|
+
'toolchain',
|
|
60
|
+
'site-packages',
|
|
61
|
+
'dist-packages',
|
|
62
|
+
'/.local/lib/',
|
|
63
|
+
'/.local/share/pnpm/',
|
|
64
|
+
'/go/pkg/mod/',
|
|
65
|
+
]),
|
|
32
66
|
windowsUsn: Schema.boolean().default(false),
|
|
33
67
|
stateDir: Schema.string().default(''),
|
|
34
68
|
binaryPath: Schema.string().default(''),
|
package/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,MAAM,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search worker: the FTS5 query runs here so a slow content query can never
|
|
3
|
+
* stall the DSH host's event loop. A read-only connection makes the build's
|
|
4
|
+
* writes in the main thread safe to observe concurrently (WAL).
|
|
5
|
+
* @module dsh-locator/content-search-worker
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=content-search-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-search-worker.d.ts","sourceRoot":"","sources":["../src/content-search-worker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search worker: the FTS5 query runs here so a slow content query can never
|
|
3
|
+
* stall the DSH host's event loop. A read-only connection makes the build's
|
|
4
|
+
* writes in the main thread safe to observe concurrently (WAL).
|
|
5
|
+
* @module dsh-locator/content-search-worker
|
|
6
|
+
*/
|
|
7
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
8
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
9
|
+
import { searchContent } from "./content-search.js";
|
|
10
|
+
let db = null;
|
|
11
|
+
function open(path) {
|
|
12
|
+
try {
|
|
13
|
+
db?.close();
|
|
14
|
+
}
|
|
15
|
+
catch { /* ignore */ }
|
|
16
|
+
db = null;
|
|
17
|
+
try {
|
|
18
|
+
db = new DatabaseSync(path, { readOnly: true });
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
db = null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const initial = workerData?.dbPath;
|
|
25
|
+
if (initial !== undefined)
|
|
26
|
+
open(initial);
|
|
27
|
+
parentPort?.on('message', (request) => {
|
|
28
|
+
if (request.open !== undefined) {
|
|
29
|
+
open(request.open);
|
|
30
|
+
parentPort?.postMessage({ id: request.id, ok: true });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (request.close === true) {
|
|
34
|
+
try {
|
|
35
|
+
db?.close();
|
|
36
|
+
}
|
|
37
|
+
catch { /* ignore */ }
|
|
38
|
+
db = null;
|
|
39
|
+
parentPort?.postMessage({ id: request.id, ok: true });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
let hits = [];
|
|
43
|
+
let error = '';
|
|
44
|
+
if (db !== null) {
|
|
45
|
+
try {
|
|
46
|
+
hits = searchContent(db, request.query ?? '', request.limit ?? 30);
|
|
47
|
+
}
|
|
48
|
+
catch (cause) {
|
|
49
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
parentPort?.postMessage({ id: request.id, hits, error });
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=content-search-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-search-worker.js","sourceRoot":"","sources":["../src/content-search-worker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAWnD,IAAI,EAAE,GAAwB,IAAI,CAAA;AAElC,SAAS,IAAI,CAAC,IAAY;IACxB,IAAI,CAAC;QAAC,EAAE,EAAE,KAAK,EAAE,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAC1C,EAAE,GAAG,IAAI,CAAA;IACT,IAAI,CAAC;QAAC,EAAE,GAAG,IAAI,YAAY,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,EAAE,GAAG,IAAI,CAAA;IAAC,CAAC;AAC7E,CAAC;AAED,MAAM,OAAO,GAAI,UAA8C,EAAE,MAAM,CAAA;AACvE,IAAI,OAAO,KAAK,SAAS;IAAE,IAAI,CAAC,OAAO,CAAC,CAAA;AAExC,UAAU,EAAE,EAAE,CAAC,SAAS,EAAE,CAAC,OAAgB,EAAE,EAAE;IAC7C,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC/B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAClB,UAAU,EAAE,WAAW,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;QACrD,OAAM;IACR,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QAC3B,IAAI,CAAC;YAAC,EAAE,EAAE,KAAK,EAAE,CAAA;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;QAC1C,EAAE,GAAG,IAAI,CAAA;QACT,UAAU,EAAE,WAAW,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;QACrD,OAAM;IACR,CAAC;IACD,IAAI,IAAI,GAAiB,EAAE,CAAA;IAC3B,IAAI,KAAK,GAAG,EAAE,CAAA;IACd,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;QAChB,IAAI,CAAC;YAAC,IAAI,GAAG,aAAa,CAAC,EAAE,EAAE,OAAO,CAAC,KAAK,IAAI,EAAE,EAAE,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;QAAC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACxF,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAChE,CAAC;IACH,CAAC;IACD,UAAU,EAAE,WAAW,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;AAC1D,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content FTS5 query shared by the host thread and the search worker.
|
|
3
|
+
* @module dsh-locator/content-search
|
|
4
|
+
*/
|
|
5
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
6
|
+
import type { ContentHit } from './content.ts';
|
|
7
|
+
/**
|
|
8
|
+
* Run one content query against an open SQLite handle.
|
|
9
|
+
* @param db - open database (read-only in the worker).
|
|
10
|
+
* @param query - user query; shorter than three characters falls back to LIKE.
|
|
11
|
+
* @param limit - maximum hits.
|
|
12
|
+
* @returns hits, ordered by relevance.
|
|
13
|
+
*/
|
|
14
|
+
export declare function searchContent(db: DatabaseSync, query: string, limit: number): ContentHit[];
|
|
15
|
+
//# sourceMappingURL=content-search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-search.d.ts","sourceRoot":"","sources":["../src/content-search.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE,CA6B1F"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content FTS5 query shared by the host thread and the search worker.
|
|
3
|
+
* @module dsh-locator/content-search
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Run one content query against an open SQLite handle.
|
|
7
|
+
* @param db - open database (read-only in the worker).
|
|
8
|
+
* @param query - user query; shorter than three characters falls back to LIKE.
|
|
9
|
+
* @param limit - maximum hits.
|
|
10
|
+
* @returns hits, ordered by relevance.
|
|
11
|
+
*/
|
|
12
|
+
export function searchContent(db, query, limit) {
|
|
13
|
+
const text = query.trim();
|
|
14
|
+
if (text === '')
|
|
15
|
+
return [];
|
|
16
|
+
const capped = Math.max(1, Math.min(100, limit));
|
|
17
|
+
let rows;
|
|
18
|
+
if ([...text].length < 3) {
|
|
19
|
+
// A one- or two-character query cannot use the trigram index, so bound the
|
|
20
|
+
// payload as well as the row count: never ship a whole megabyte body.
|
|
21
|
+
rows = db.prepare(`SELECT kind, path, title, session_id, message_id,
|
|
22
|
+
substr(body, 1, 400) AS snippet, 0 AS score
|
|
23
|
+
FROM docs WHERE body LIKE '%' || ? || '%' LIMIT ?`).all(text, capped);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const match = text.split(/\s+/).filter(Boolean).map(term => '"' + term.replace(/"/g, '') + '"').join(' AND ');
|
|
27
|
+
// Keep MATCH in this statement: FTS5 snippet() and bm25() are only defined
|
|
28
|
+
// inside a matching query (a rowid subquery silently degrades both). The
|
|
29
|
+
// query runs in the search worker, so its cost stays off the host thread.
|
|
30
|
+
rows = db.prepare(`SELECT kind, path, title, session_id, message_id,
|
|
31
|
+
snippet(docs, 0, char(1), char(2), '…', 16) AS snippet, bm25(docs) AS score
|
|
32
|
+
FROM docs WHERE docs MATCH ? ORDER BY score LIMIT ?`).all(match, capped);
|
|
33
|
+
}
|
|
34
|
+
return rows.map(row => ({
|
|
35
|
+
kind: row.kind === 'session' ? 'session' : 'file',
|
|
36
|
+
path: String(row.path ?? ''),
|
|
37
|
+
title: String(row.title ?? ''),
|
|
38
|
+
snippet: String(row.snippet ?? row.body ?? '').replace(/[\u0001\u0002]/g, ''),
|
|
39
|
+
...(row.session_id ? { sessionId: String(row.session_id) } : {}),
|
|
40
|
+
...(row.message_id ? { messageId: String(row.message_id) } : {}),
|
|
41
|
+
score: Number(row.score ?? 0),
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=content-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-search.js","sourceRoot":"","sources":["../src/content-search.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,EAAgB,EAAE,KAAa,EAAE,KAAa;IAC1E,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;IACzB,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,EAAE,CAAA;IAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;IAChD,IAAI,IAAoC,CAAA;IACxC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,2EAA2E;QAC3E,sEAAsE;QACtE,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC;;wDAEkC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAmC,CAAA;IAC3G,CAAC;SAAM,CAAC;QACN,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAC7G,2EAA2E;QAC3E,yEAAyE;QACzE,0EAA0E;QAC1E,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC;;0DAEoC,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAmC,CAAA;IAC9G,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtB,IAAI,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM;QACjD,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;QAC5B,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9B,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC;QAC7E,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC;KAC9B,CAAC,CAAC,CAAA;AACL,CAAC"}
|
package/lib/content.d.ts
CHANGED
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
* `session.v4.jsonl.zstd`, and SQLite's trigram tokenizer gives substring and
|
|
7
7
|
* CJK search. A meta table stores each source's mtime so refresh() only
|
|
8
8
|
* re-reads what changed; rebuild() drops and rebuilds everything.
|
|
9
|
+
*
|
|
10
|
+
* Everything here runs inside the DSH host process, so the build must never
|
|
11
|
+
* monopolise its event loop: file reads and zstd decoding are asynchronous and
|
|
12
|
+
* the loops yield to the scheduler every ~8 ms of work. Session logs are read
|
|
13
|
+
* only once they have settled (an actively written session is skipped until it
|
|
14
|
+
* has been quiet for `activeSessionDelayMs`), and archived sessions are left
|
|
15
|
+
* alone unless `includeArchived` is set.
|
|
9
16
|
* @module dsh-locator/content
|
|
10
17
|
*/
|
|
11
18
|
import type { LocatorRuntime } from './runtime.ts';
|
|
@@ -30,10 +37,25 @@ export interface ContentState {
|
|
|
30
37
|
export interface ContentOptions {
|
|
31
38
|
stateDir: string;
|
|
32
39
|
sessionDirs?: string[] | undefined;
|
|
40
|
+
/** Path segments or substrings never read into the content index. */
|
|
41
|
+
excludes?: string[];
|
|
42
|
+
/** Index archived sessions too; false (the default) leaves them out. */
|
|
43
|
+
includeArchived?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Skip session logs modified more recently than this many milliseconds, so
|
|
46
|
+
* the session being written right now is not re-read on every refresh.
|
|
47
|
+
*/
|
|
48
|
+
activeSessionDelayMs?: number;
|
|
33
49
|
maxFileBytes: number;
|
|
34
50
|
maxTotalBytes?: number;
|
|
35
51
|
maxFiles: number;
|
|
36
52
|
maxSessions: number;
|
|
53
|
+
/**
|
|
54
|
+
* Run content queries in a worker thread so a slow search never blocks the
|
|
55
|
+
* DSH host event loop. Off by default so direct embedders and tests stay
|
|
56
|
+
* single-threaded.
|
|
57
|
+
*/
|
|
58
|
+
searchWorker?: boolean;
|
|
37
59
|
}
|
|
38
60
|
export declare class ContentIndex {
|
|
39
61
|
private readonly rt;
|
|
@@ -42,11 +64,23 @@ export declare class ContentIndex {
|
|
|
42
64
|
private readonly state;
|
|
43
65
|
private generation;
|
|
44
66
|
private totalBytes;
|
|
45
|
-
private
|
|
67
|
+
private sliceDeadline;
|
|
68
|
+
private worker;
|
|
69
|
+
private lockHeld;
|
|
70
|
+
private readonly lockToken;
|
|
71
|
+
private workerSeq;
|
|
72
|
+
private pendingStart;
|
|
73
|
+
private readonly pending;
|
|
46
74
|
constructor(rt: LocatorRuntime, options: ContentOptions);
|
|
47
75
|
status(): ContentState;
|
|
48
|
-
/**
|
|
49
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Report a lazy build as in progress before the asynchronous start reaches
|
|
78
|
+
* `boot()`, so the panel/agent sees "building" on the very first query
|
|
79
|
+
* instead of an empty result with no hint.
|
|
80
|
+
*/
|
|
81
|
+
markPending(): void;
|
|
82
|
+
/** Drop the pending flag when the lazy start failed before it began. */
|
|
83
|
+
clearPending(): void;
|
|
50
84
|
/** Queue a full rebuild; false when one is already running. */
|
|
51
85
|
rebuild(): boolean;
|
|
52
86
|
/** Queue an incremental refresh; false when one is already running. */
|
|
@@ -54,23 +88,68 @@ export declare class ContentIndex {
|
|
|
54
88
|
close(): void;
|
|
55
89
|
/** Build everything from scratch. */
|
|
56
90
|
start(): void;
|
|
91
|
+
/**
|
|
92
|
+
* Start at plugin boot: reuse an existing database and refresh it
|
|
93
|
+
* incrementally, so a restart does not re-read and re-index gigabytes of
|
|
94
|
+
* text. A missing or incompatible database falls back to a full build.
|
|
95
|
+
*/
|
|
96
|
+
boot(): void;
|
|
57
97
|
/** Re-read changed files/sessions; open the DB when this is the first run. */
|
|
58
98
|
refresh(): void;
|
|
99
|
+
/**
|
|
100
|
+
* Serialize content builds across plugin instances and DSH processes that
|
|
101
|
+
* share a state directory: a stale lock (dead pid or older than 30 minutes)
|
|
102
|
+
* is taken over, a live one keeps this instance from writing concurrently.
|
|
103
|
+
* @returns whether this instance owns the build lock.
|
|
104
|
+
*/
|
|
105
|
+
private acquireLock;
|
|
106
|
+
private releaseLock;
|
|
59
107
|
private begin;
|
|
60
108
|
private full;
|
|
61
109
|
private incremental;
|
|
62
110
|
private open;
|
|
111
|
+
private reopenWorker;
|
|
112
|
+
private stopWorker;
|
|
113
|
+
private ensureWorker;
|
|
114
|
+
/** Whether `path` is a SQLite database this plugin created (has docs_meta). */
|
|
115
|
+
private ownedDatabase;
|
|
116
|
+
private readFlag;
|
|
117
|
+
private writeFlag;
|
|
118
|
+
/**
|
|
119
|
+
* Body bytes already stored. Reading a persisted counter avoids a full scan
|
|
120
|
+
* of every FTS body (measured ~0.6 s for 1 GB) at the start of each refresh.
|
|
121
|
+
* A database written before the counter existed falls back to one scan.
|
|
122
|
+
*/
|
|
123
|
+
private readTotalBytes;
|
|
124
|
+
private writeTotalBytes;
|
|
125
|
+
/** Yield to the host event loop once the current ~8 ms work slice is spent. */
|
|
126
|
+
private breathe;
|
|
127
|
+
/** True when a session log has been quiet long enough to be read safely. */
|
|
128
|
+
private settled;
|
|
63
129
|
private finish;
|
|
64
130
|
private addDoc;
|
|
65
131
|
private deleteDoc;
|
|
66
132
|
private deletePath;
|
|
67
133
|
private indexFiles;
|
|
68
134
|
private refreshFiles;
|
|
135
|
+
/** Would storing a file of this size overflow the configured body budget? */
|
|
136
|
+
private wouldExceed;
|
|
69
137
|
private readFileDoc;
|
|
70
138
|
private indexSessions;
|
|
71
139
|
private refreshSessions;
|
|
72
140
|
private indexSessionFile;
|
|
141
|
+
/** Session logs to consider, newest first, minus archived ones by default. */
|
|
142
|
+
private sessionCandidates;
|
|
73
143
|
sessionRoots(): string[];
|
|
74
|
-
|
|
144
|
+
/**
|
|
145
|
+
* Search file bodies and session messages. With `searchWorker` enabled the
|
|
146
|
+
* query itself runs off the host thread; a failed or wedged worker falls back
|
|
147
|
+
* to the in-process query so a result is still produced.
|
|
148
|
+
* @param query - words or a phrase to find.
|
|
149
|
+
* @param limit - maximum hits.
|
|
150
|
+
* @returns matching documents ordered by relevance.
|
|
151
|
+
*/
|
|
152
|
+
search(query: string, limit: number): Promise<ContentHit[]>;
|
|
153
|
+
private searchSync;
|
|
75
154
|
}
|
|
76
155
|
//# sourceMappingURL=content.d.ts.map
|
package/lib/content.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"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"}
|