dsh-fish-buddy 0.8.1 → 0.8.3
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/lib/host.js +121 -9
- package/package.json +6 -2
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
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
|
316
|
-
|
|
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
|
}
|
|
@@ -737,11 +831,17 @@ export function apply(ctx, inlineConfig) {
|
|
|
737
831
|
* ⚠ 只在 **截图送法 = 攒着** 时生效:每张都说那一档,截图本来就会叫醒她,
|
|
738
832
|
* 再叠加搭话等于一轮变两轮、白花钱。
|
|
739
833
|
*/
|
|
834
|
+
// ⭐ 2026-11-04:最近一次往会话里塞图的时间 —— 给「游戏里自己搭话」让路(声明放前面,免得 TDZ)
|
|
835
|
+
let 上次塞图 = 0
|
|
740
836
|
const 游戏搭话 = () => {
|
|
741
837
|
try {
|
|
742
838
|
if (!游戏模式.开) return false
|
|
743
839
|
const 配置 = 读配置()
|
|
744
|
-
|
|
840
|
+
// ⚠⚠ 2026-11-04 修:这里原来判的是 配置.截图送法 —— 可全项目根本没有这个字段
|
|
841
|
+
// (「每张 / 攒着」是**挂件的设置**,插件这边只有每帧带的 存档/立即 标志)。
|
|
842
|
+
// 于是这一行永远为真 → 游戏搭话 永远 return false,攒着的图只能等她先开口。
|
|
843
|
+
// 现在改成「最近刚塞过图就让路」:每张都说那一档每隔几十秒就有图出去,
|
|
844
|
+
// 自然被挡掉;攒着那一档不塞图,所以能正常搭话。
|
|
745
845
|
// ⭐ 1.7.1:优先用**挂件报上来的**那个数,拿不到才用自己配置里的默认值
|
|
746
846
|
const 间隔 = 节奏('游戏搭话秒', Math.max(0, Number(配置.游戏搭话秒) || 0))
|
|
747
847
|
if (间隔 <= 0) return false
|
|
@@ -749,6 +849,8 @@ export function apply(ctx, inlineConfig) {
|
|
|
749
849
|
if (!键) return false
|
|
750
850
|
// 刚进游戏模式先安静一会儿,别一进去就开口
|
|
751
851
|
if (现在() - (游戏模式.时 || 0) < Math.min(间隔, 60) * 1000) return false
|
|
852
|
+
// 「每张都说」那档每隔几十秒刚发过图 → 让路,别一轮变两轮
|
|
853
|
+
if (上次塞图 && 现在() - 上次塞图 < 90000) return false
|
|
752
854
|
if (!要搭话({
|
|
753
855
|
间隔秒: 间隔,
|
|
754
856
|
最多秒: 0,
|
|
@@ -826,7 +928,13 @@ export function apply(ctx, inlineConfig) {
|
|
|
826
928
|
const 可能搭话 = () => {
|
|
827
929
|
try {
|
|
828
930
|
// ⭐ 0.7.7:在打游戏就别走「一起看片子」那条搭话 —— 两套提示词会打架
|
|
931
|
+
// ⚠⚠ 2026-11-04 修(用户反馈:游戏模式「攒着」永远不主动搭话):
|
|
932
|
+
// 这里以前是 **直接 return**,可游戏模式的主动搭话(游戏搭话)只有离看那条路
|
|
933
|
+
// 才会调 —— 于是「在打游戏」一旦命中,游戏搭话() 就永远走不到,
|
|
934
|
+
// 攒着的截图只能等她先开口才被带过去,跟"主动搭话"根本不是一回事。
|
|
935
|
+
// 现在改成:在打游戏 → 走游戏搭话;不在打游戏也没陪看 → 还是走游戏搭话(它自己会判条件)。
|
|
829
936
|
if (游戏模式.开 && 现在() - (游戏模式.时 || 0) < 6 * 3600 * 1000) {
|
|
937
|
+
try { 游戏搭话() } catch (error) {}
|
|
830
938
|
return
|
|
831
939
|
}
|
|
832
940
|
if (!陪看开着) {
|
|
@@ -942,6 +1050,7 @@ export function apply(ctx, inlineConfig) {
|
|
|
942
1050
|
// ⭐ 0.6.1:挂件会告诉我们「现在在不在游戏模式」—— 游戏模式也要能主动搭话
|
|
943
1051
|
const 游戏模式 = { 开: false, 时: 0 }
|
|
944
1052
|
const 攒图 = new Map()
|
|
1053
|
+
// ⭐ 2026-11-04:最近一次往会话里塞图的时间 —— 用来给「游戏里自己搭话」让路
|
|
945
1054
|
// ⭐ 0.7.2:**不限制张数**(以前的「最多 3 张、新的挤掉最旧的」是错的 ——
|
|
946
1055
|
// 挤掉的图等于白截了)。攒着就一直留着,等她说话 / 她自己看一眼时**全部**带过去。
|
|
947
1056
|
// ⚠ 只有个上限 60 张防跑飞(一小时 30 秒一张也就 120 张,实际轮不到)。
|
|
@@ -1520,6 +1629,7 @@ export function apply(ctx, inlineConfig) {
|
|
|
1520
1629
|
source: { kind: 源 === '热键' ? 'user' : 'fishbuddy' },
|
|
1521
1630
|
})
|
|
1522
1631
|
状态.送出条数 += 1
|
|
1632
|
+
上次塞图 = 现在() // ⭐ 2026-11-04:给「游戏里自己搭话」让路用
|
|
1523
1633
|
return '发过去了'
|
|
1524
1634
|
} catch (error) {
|
|
1525
1635
|
return '发失败了:' + 说(error)
|
|
@@ -1585,6 +1695,8 @@ export function apply(ctx, inlineConfig) {
|
|
|
1585
1695
|
source: { kind: 来源类别 },
|
|
1586
1696
|
})
|
|
1587
1697
|
状态.送出条数 += 1
|
|
1698
|
+
上次塞图 = 现在() // 带图说话也算刚塞过
|
|
1699
|
+
|
|
1588
1700
|
return '发过去了'
|
|
1589
1701
|
} catch (error) {
|
|
1590
1702
|
return '发失败了:' + 说(error)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-fish-buddy",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "鱼搭子 · DSH 这半边:桌面小挂件说的话送进你绑定的会话,她的回话再递回挂件。挂件是独立程序,这里只负责接线。",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
@@ -40,5 +40,9 @@
|
|
|
40
40
|
"react"
|
|
41
41
|
]
|
|
42
42
|
}
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=22",
|
|
46
|
+
"dsh": ">=0.2.1-rc.2"
|
|
43
47
|
}
|
|
44
|
-
}
|
|
48
|
+
}
|