@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 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.26",
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",
@@ -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 batch = msg.result
145
- const limit = msg.limit
146
- debug.log("bootstrap:search:done", { rows: batch.length, limit, mode: searchMode() })
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
- debug.log("bootstrap:search:error", msg.error)
162
- solidBatch(() => {
163
- setResults([])
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
- debug.timeEnd("query:search")
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