dsh-fish-buddy 0.8.1 → 0.8.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.
Files changed (2) hide show
  1. package/lib/host.js +102 -8
  2. package/package.json +48 -44
package/lib/host.js CHANGED
@@ -16,6 +16,7 @@ import { homedir } from 'node:os'
16
16
  import { join, dirname } from 'node:path'
17
17
  import { fileURLToPath } from 'node:url'
18
18
  import { randomUUID } from 'node:crypto'
19
+ import * as zlib from 'node:zlib'
19
20
 
20
21
  export const name = 'fish-buddy'
21
22
 
@@ -304,20 +305,113 @@ export function apply(ctx, inlineConfig) {
304
305
 
305
306
  // ---------- 会话清单(设置页的下拉用) ----------
306
307
  const 会话清单 = () => {
308
+ // ⚠ 2026-11-03:原来只列「活着的会话」(sessions.list) —— 窗口一关就选不到了,
309
+ // 未分组那批更是永远选不到。改成扫磁盘,跨所有工作区,按同一套规矩过滤。
307
310
  const 出 = []
308
311
  try {
309
- const 会话服务 = 取服务('sessions')
310
- const 标题服务 = 取服务('sessionTitle')
311
- if (会话服务 && typeof 会话服务.list === 'function') {
312
- for (const s of 会话服务.list()) {
313
- let 标题 = ''
312
+ const 根 = join(目录, 'sessions')
313
+ const 缓存 = join(目录, 'storages', 'session_projcache', 'sessions')
314
+ const 归档 = new Set()
315
+ try {
316
+ const wr = 取服务('workspaceRegistry')
317
+ const ids = wr && wr.archivedSessionIds
318
+ if (Array.isArray(ids)) for (const x of ids) 归档.add(String(x))
319
+ } catch (error) {}
320
+ try {
321
+ const w = JSON.parse(readFileSync(join(目录, 'storages', 'workspace.json'), 'utf8'))
322
+ for (const x of (w && w.global && w.global.archivedSessionIds) || []) 归档.add(String(x))
323
+ } catch (error) {}
324
+
325
+ let 一层们 = []
326
+ try {
327
+ 一层们 = readdirSync(根, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name)
328
+ } catch (error) {
329
+ return 出
330
+ }
331
+ const 位置 = []
332
+ for (const 一层 of 一层们) {
333
+ if (existsSync(join(根, 一层, 'session.v4.jsonl.zstd'))) {
334
+ 位置.push({ ws: '', sid: 一层 })
335
+ continue
336
+ }
337
+ let 子们 = []
338
+ try {
339
+ 子们 = readdirSync(join(根, 一层), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name)
340
+ } catch (error) {
341
+ continue
342
+ }
343
+ for (const sid of 子们) 位置.push({ ws: 一层, sid })
344
+ }
345
+
346
+ const 行们 = []
347
+ for (const { ws, sid } of 位置) {
348
+ const 日志 = join(根, ws, sid, 'session.v4.jsonl.zstd')
349
+ if (!existsSync(日志)) continue
350
+ if (归档.has(sid)) continue
351
+ let 大小 = 0
352
+ try {
353
+ 大小 = statSync(日志).size
354
+ } catch (error) {}
355
+ if (大小 < 1024) continue // 空会话
356
+ let 标题 = ''
357
+ let 空 = false
358
+ let 子代理 = false
359
+ let 时 = 0
360
+ try {
361
+ const r = JSON.parse(readFileSync(join(缓存, sid + '.json'), 'utf8'))?.record?.rows || {}
362
+ 标题 = String((r.title && r.title.val) || '')
363
+ const meta = (r.sessionListMetadata && r.sessionListMetadata.val) || {}
364
+ 时 = Number(meta.lastPromptAt || 0)
365
+ 空 = Boolean(meta.blank)
366
+ 子代理 = Boolean(r.subagent && r.subagent.val && r.subagent.val.identity)
367
+ } catch (error) {}
368
+ if (空) continue
369
+ if (!标题 || 子代理) {
370
+ // 没缓存或缓存没说清楚 → 看日志头:子代理/深度>0 直接排除,顺便捞第一句话当标题
314
371
  try {
315
- const 快照 = 标题服务 && typeof 标题服务.get === 'function' ? 标题服务.get(s) : undefined
316
- 标题 = 快照 && 快照.title ? String(快照.title) : ''
372
+ const 缓 = readFileSync(日志).subarray(0, 524288)
373
+ const 帧们 = []
374
+ for (let i = 0; i + 4 <= 缓.length; i++) {
375
+ if (缓[i] === 0x28 && 缓[i + 1] === 0xb5 && 缓[i + 2] === 0x2f && 缓[i + 3] === 0xfd) 帧们.push(i)
376
+ }
377
+ for (let k = 0; k < Math.min(帧们.length, 64); k++) {
378
+ const 段 = 缓.subarray(帧们[k], k + 1 < 帧们.length ? 帧们[k + 1] : 缓.length)
379
+ let 文 = ''
380
+ try {
381
+ 文 = zlib.zstdDecompressSync(段).toString('utf8')
382
+ } catch (error) {
383
+ continue
384
+ }
385
+ for (const 行 of 文.split(String.fromCharCode(10))) {
386
+ if (!行.trim()) continue
387
+ let e
388
+ try {
389
+ e = JSON.parse(行)
390
+ } catch (error) {
391
+ continue
392
+ }
393
+ if (e.type === 'session') {
394
+ if (e.origin === 'subagent' || Number(e.delegationDepth || 0) > 0) 子代理 = true
395
+ if (!时) 时 = Number(e.createdAt || 0)
396
+ continue
397
+ }
398
+ if (标题) continue
399
+ if (e.type !== 'user/message') continue
400
+ const 源 = (e.data && e.data.source && e.data.source.kind) || ''
401
+ if (源 !== 'user') continue
402
+ const c = e.data && e.data.content
403
+ const t = (Array.isArray(c) ? c : []).filter((x) => x && x.type === 'text').map((x) => x.text).join(' ').trim()
404
+ if (t) 标题 = t.slice(0, 40)
405
+ }
406
+ if (子代理 && 标题) break
407
+ }
317
408
  } catch (error) {}
318
- 出.push({ id: String(s.id), 标题 })
319
409
  }
410
+ if (子代理) continue
411
+ 行们.push({ id: sid, 标题: 标题 || ('未命名会话 · ' + (时 ? new Date(时).toLocaleDateString('zh-CN') : sid.slice(0, 8))), at: 时 })
320
412
  }
413
+ 行们.sort((a, b) => b.at - a.at)
414
+ for (const r of 行们) 出.push({ id: r.id, 标题: r.标题 })
321
415
  } catch (error) {
322
416
  记账('列会话出错:' + 说(error))
323
417
  }
package/package.json CHANGED
@@ -1,44 +1,48 @@
1
- {
2
- "name": "dsh-fish-buddy",
3
- "version": "0.8.1",
4
- "description": "鱼搭子 · DSH 这半边:桌面小挂件说的话送进你绑定的会话,她的回话再递回挂件。挂件是独立程序,这里只负责接线。",
5
- "license": "SEE LICENSE IN LICENSE",
6
- "type": "module",
7
- "keywords": [
8
- "deepseek-harness",
9
- "dsh-plugin",
10
- "companion",
11
- "voice",
12
- "call"
13
- ],
14
- "main": "lib/host.js",
15
- "exports": {
16
- ".": "./lib/host.js",
17
- "./client": "./lib/client.js",
18
- "./package.json": "./package.json",
19
- "./locale/*.json": "./locale/*.json",
20
- "./icon": "./icon.png"
21
- },
22
- "icon": "./icon.png",
23
- "files": [
24
- "lib",
25
- "locale",
26
- "icon.png",
27
- "cordis.patch.yml",
28
- "README.md"
29
- ],
30
- "dsh": {
31
- "bundle": {
32
- "patch": "./cordis.patch.yml"
33
- },
34
- "client": {
35
- "platform": "web",
36
- "inject": [
37
- "slots"
38
- ],
39
- "external": [
40
- "react"
41
- ]
42
- }
43
- }
44
- }
1
+ {
2
+ "name": "dsh-fish-buddy",
3
+ "version": "0.8.2",
4
+ "description": "鱼搭子 · DSH 这半边:桌面小挂件说的话送进你绑定的会话,她的回话再递回挂件。挂件是独立程序,这里只负责接线。",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "type": "module",
7
+ "keywords": [
8
+ "deepseek-harness",
9
+ "dsh-plugin",
10
+ "companion",
11
+ "voice",
12
+ "call"
13
+ ],
14
+ "main": "lib/host.js",
15
+ "exports": {
16
+ ".": "./lib/host.js",
17
+ "./client": "./lib/client.js",
18
+ "./package.json": "./package.json",
19
+ "./locale/*.json": "./locale/*.json",
20
+ "./icon": "./icon.png"
21
+ },
22
+ "icon": "./icon.png",
23
+ "files": [
24
+ "lib",
25
+ "locale",
26
+ "icon.png",
27
+ "cordis.patch.yml",
28
+ "README.md"
29
+ ],
30
+ "dsh": {
31
+ "bundle": {
32
+ "patch": "./cordis.patch.yml"
33
+ },
34
+ "client": {
35
+ "platform": "web",
36
+ "inject": [
37
+ "slots"
38
+ ],
39
+ "external": [
40
+ "react"
41
+ ]
42
+ }
43
+ },
44
+ "engines": {
45
+ "node": ">=22",
46
+ "dsh": ">=0.2.1-rc.2"
47
+ }
48
+ }