@bojackduy/opencode-telescope 0.1.26 → 0.1.28
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/package.json +2 -1
- package/search-worker.ts +34 -0
- package/telescope.tsx +90 -26
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@bojackduy/opencode-telescope",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.28",
|
|
5
5
|
"description": "OpenCode TUI plugin for fuzzy and semantic search across local conversation history, session transcripts, and AI coding chats",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"tui.tsx",
|
|
55
55
|
"telescope.tsx",
|
|
56
56
|
"search.ts",
|
|
57
|
+
"search-worker.ts",
|
|
57
58
|
"search",
|
|
58
59
|
"components",
|
|
59
60
|
"ui",
|
package/search-worker.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { performSearch, recentSessionMessages } from "./search"
|
|
2
|
+
|
|
3
|
+
let activeId = -1
|
|
4
|
+
|
|
5
|
+
self.onmessage = async (event: MessageEvent) => {
|
|
6
|
+
const msg = event.data
|
|
7
|
+
if (msg.type === "search" || msg.type === "recent") {
|
|
8
|
+
activeId = msg.id
|
|
9
|
+
try {
|
|
10
|
+
const batch = msg.type === "search"
|
|
11
|
+
? await performSearch(msg.query, {
|
|
12
|
+
limit: msg.limit,
|
|
13
|
+
offset: msg.offset ?? 0,
|
|
14
|
+
dbPath: msg.dbPath,
|
|
15
|
+
directory: msg.directory,
|
|
16
|
+
role: msg.role,
|
|
17
|
+
})
|
|
18
|
+
: recentSessionMessages({
|
|
19
|
+
limit: msg.limit,
|
|
20
|
+
offset: msg.offset ?? 0,
|
|
21
|
+
dbPath: msg.dbPath,
|
|
22
|
+
directory: msg.directory,
|
|
23
|
+
role: msg.role,
|
|
24
|
+
})
|
|
25
|
+
if (msg.id === activeId) {
|
|
26
|
+
self.postMessage({ type: "search-result", id: msg.id, result: batch, limit: msg.limit })
|
|
27
|
+
}
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (msg.id === activeId) {
|
|
30
|
+
self.postMessage({ type: "error", id: msg.id, error: err instanceof Error ? err.message : String(err) })
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
package/telescope.tsx
CHANGED
|
@@ -97,10 +97,22 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
97
97
|
let resultPrefetchTimer: ReturnType<typeof setTimeout> | undefined
|
|
98
98
|
let resultPreviousTimer: ReturnType<typeof setTimeout> | undefined
|
|
99
99
|
let searchTimer: ReturnType<typeof setTimeout> | undefined
|
|
100
|
+
let searchWatchdogTimer: ReturnType<typeof setTimeout> | undefined
|
|
100
101
|
let lastFiredQuery = ""
|
|
101
102
|
let searchRequestId = 0
|
|
103
|
+
let fallbackSearchRequestId: number | undefined
|
|
102
104
|
let searchWorker: Worker | undefined
|
|
103
105
|
const SEARCH_DEBOUNCE_MS = 200
|
|
106
|
+
const SEARCH_WORKER_TIMEOUT_MS = 3000
|
|
107
|
+
type SearchRequest = {
|
|
108
|
+
id: number
|
|
109
|
+
q: string
|
|
110
|
+
role: SearchRole | undefined
|
|
111
|
+
db: string
|
|
112
|
+
dir: string
|
|
113
|
+
limit: number
|
|
114
|
+
}
|
|
115
|
+
let pendingSearchRequest: SearchRequest | undefined
|
|
104
116
|
type PreviewBeforeIntent = "passive-prefetch" | "explicit-scroll"
|
|
105
117
|
type PendingPreviewBefore = {
|
|
106
118
|
id: number
|
|
@@ -133,6 +145,60 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
133
145
|
setPrefetchingPreviewAfter(false)
|
|
134
146
|
setLoadingPreviewMore(false)
|
|
135
147
|
}
|
|
148
|
+
const clearSearchWatchdog = () => {
|
|
149
|
+
if (searchWatchdogTimer) clearTimeout(searchWatchdogTimer)
|
|
150
|
+
searchWatchdogTimer = undefined
|
|
151
|
+
}
|
|
152
|
+
const commitSearchBatch = (request: SearchRequest, batch: SearchResult[], source: string) => {
|
|
153
|
+
if (request.id !== searchRequestId) return
|
|
154
|
+
clearSearchWatchdog()
|
|
155
|
+
if (fallbackSearchRequestId === request.id) fallbackSearchRequestId = undefined
|
|
156
|
+
pendingSearchRequest = undefined
|
|
157
|
+
debug.log("bootstrap:search:done", { rows: batch.length, limit: request.limit, mode: searchMode(), source })
|
|
158
|
+
solidBatch(() => {
|
|
159
|
+
setResults(batch)
|
|
160
|
+
setResultBaseOffset(0)
|
|
161
|
+
setNextResultOffset(batch.length)
|
|
162
|
+
setHasMore(batch.length >= request.limit)
|
|
163
|
+
setResultPageInfo({ loadedUntil: batch.length, hasMore: batch.length >= request.limit, pageSize: request.limit, lastOffset: 0, lastAdded: batch.length })
|
|
164
|
+
setSelected(0)
|
|
165
|
+
})
|
|
166
|
+
setBusy(false)
|
|
167
|
+
setLoading(false)
|
|
168
|
+
debug.timeEnd("query:search")
|
|
169
|
+
}
|
|
170
|
+
const failSearch = (request: SearchRequest, error: string) => {
|
|
171
|
+
if (request.id !== searchRequestId) return
|
|
172
|
+
clearSearchWatchdog()
|
|
173
|
+
if (fallbackSearchRequestId === request.id) fallbackSearchRequestId = undefined
|
|
174
|
+
pendingSearchRequest = undefined
|
|
175
|
+
debug.log("bootstrap:search:error", error)
|
|
176
|
+
solidBatch(() => {
|
|
177
|
+
setResults([])
|
|
178
|
+
setResultBaseOffset(0)
|
|
179
|
+
setNextResultOffset(0)
|
|
180
|
+
setResultPageInfo({ loadedUntil: 0, hasMore: false, pageSize: request.limit, lastOffset: 0, lastAdded: 0 })
|
|
181
|
+
})
|
|
182
|
+
setError(error)
|
|
183
|
+
setBusy(false)
|
|
184
|
+
setLoading(false)
|
|
185
|
+
debug.timeEnd("query:search")
|
|
186
|
+
}
|
|
187
|
+
const runSearchFallback = async (request: SearchRequest, reason: string) => {
|
|
188
|
+
if (request.id !== searchRequestId) return
|
|
189
|
+
if (fallbackSearchRequestId === request.id) return
|
|
190
|
+
fallbackSearchRequestId = request.id
|
|
191
|
+
clearSearchWatchdog()
|
|
192
|
+
debug.log("bootstrap:search:fallback", { reason, query: request.q || "(all recent)", limit: request.limit })
|
|
193
|
+
try {
|
|
194
|
+
const batch = request.q
|
|
195
|
+
? searchSessionMessages(request.q, { limit: request.limit, offset: 0, dbPath: request.db, directory: request.dir, role: request.role })
|
|
196
|
+
: recentSessionMessages({ limit: request.limit, offset: 0, dbPath: request.db, directory: request.dir, role: request.role })
|
|
197
|
+
commitSearchBatch(request, batch, `fallback:${reason}`)
|
|
198
|
+
} catch (err) {
|
|
199
|
+
failSearch(request, err instanceof Error ? err.message : String(err))
|
|
200
|
+
}
|
|
201
|
+
}
|
|
136
202
|
const ensureSearchWorker = () => {
|
|
137
203
|
if (searchWorker) return true
|
|
138
204
|
try {
|
|
@@ -141,38 +207,23 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
141
207
|
const msg = event.data
|
|
142
208
|
if (msg.type === "search-result") {
|
|
143
209
|
if (msg.id !== searchRequestId) return
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
solidBatch(() => {
|
|
148
|
-
setResults(batch)
|
|
149
|
-
setResultBaseOffset(0)
|
|
150
|
-
setNextResultOffset(batch.length)
|
|
151
|
-
setHasMore(batch.length >= limit)
|
|
152
|
-
setResultPageInfo({ loadedUntil: batch.length, hasMore: batch.length >= limit, pageSize: limit, lastOffset: 0, lastAdded: batch.length })
|
|
153
|
-
setSelected(0)
|
|
154
|
-
})
|
|
155
|
-
setBusy(false)
|
|
156
|
-
setLoading(false)
|
|
157
|
-
debug.timeEnd("query:search")
|
|
210
|
+
const request = pendingSearchRequest
|
|
211
|
+
if (!request || request.id !== msg.id) return
|
|
212
|
+
commitSearchBatch(request, msg.result, "worker")
|
|
158
213
|
}
|
|
159
214
|
if (msg.type === "error") {
|
|
160
215
|
if (msg.id !== searchRequestId) return
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
setResultBaseOffset(0)
|
|
165
|
-
setNextResultOffset(0)
|
|
166
|
-
setResultPageInfo({ loadedUntil: 0, hasMore: false, pageSize: 0, lastOffset: 0, lastAdded: 0 })
|
|
167
|
-
})
|
|
168
|
-
setError(msg.error)
|
|
169
|
-
setBusy(false)
|
|
170
|
-
setLoading(false)
|
|
171
|
-
debug.timeEnd("query:search")
|
|
216
|
+
const request = pendingSearchRequest
|
|
217
|
+
if (!request || request.id !== msg.id) return
|
|
218
|
+
runSearchFallback(request, `worker-message:${msg.error}`)
|
|
172
219
|
}
|
|
173
220
|
}
|
|
174
221
|
searchWorker.onerror = (err) => {
|
|
175
222
|
debug.log("worker:error", err.message)
|
|
223
|
+
const request = pendingSearchRequest
|
|
224
|
+
searchWorker?.terminate()
|
|
225
|
+
searchWorker = undefined
|
|
226
|
+
if (request) runSearchFallback(request, `worker-error:${err.message}`)
|
|
176
227
|
}
|
|
177
228
|
return true
|
|
178
229
|
} catch (err) {
|
|
@@ -184,6 +235,7 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
184
235
|
onCleanup(() => {
|
|
185
236
|
if (resultPrefetchTimer) clearTimeout(resultPrefetchTimer)
|
|
186
237
|
if (resultPreviousTimer) clearTimeout(resultPreviousTimer)
|
|
238
|
+
clearSearchWatchdog()
|
|
187
239
|
cancelPreviewPrefetch()
|
|
188
240
|
searchWorker?.terminate()
|
|
189
241
|
searchWorker = undefined
|
|
@@ -273,14 +325,26 @@ export const Telescope = (props: { api: TuiPluginApi; config: TelescopeConfig; o
|
|
|
273
325
|
setSearchMode(detectSearchMode())
|
|
274
326
|
debug.time("query:search")
|
|
275
327
|
const id = ++searchRequestId
|
|
328
|
+
fallbackSearchRequestId = undefined
|
|
329
|
+
const request: SearchRequest = { id, q, role, db, dir, limit }
|
|
330
|
+
pendingSearchRequest = request
|
|
276
331
|
if (!ensureSearchWorker()) {
|
|
277
|
-
|
|
332
|
+
runSearchFallback(request, "worker-create-failed")
|
|
278
333
|
return
|
|
279
334
|
}
|
|
280
335
|
const msg = q
|
|
281
336
|
? { type: "search" as const, id, query: q, limit, offset: 0, directory: dir, role, dbPath: db }
|
|
282
337
|
: { type: "recent" as const, id, limit, offset: 0, directory: dir, role, dbPath: db }
|
|
283
338
|
debug.log("bootstrap:search:start", { limit, directory: dir, role, query: q || "(all recent)", worker: true })
|
|
339
|
+
clearSearchWatchdog()
|
|
340
|
+
searchWatchdogTimer = setTimeout(() => {
|
|
341
|
+
if (request.id !== searchRequestId) return
|
|
342
|
+
debug.log("worker:timeout", { id: request.id, ms: SEARCH_WORKER_TIMEOUT_MS })
|
|
343
|
+
searchWorker?.terminate()
|
|
344
|
+
searchWorker = undefined
|
|
345
|
+
runSearchFallback(request, "worker-timeout")
|
|
346
|
+
}, SEARCH_WORKER_TIMEOUT_MS)
|
|
347
|
+
;(searchWatchdogTimer as { unref?: () => void }).unref?.()
|
|
284
348
|
searchWorker!.postMessage(msg)
|
|
285
349
|
}
|
|
286
350
|
|