@ssamurias/dsh-fish 0.8.9 → 0.9.1

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 (60) hide show
  1. package/AGENTS.md +64 -10
  2. package/CHANGELOG.md +129 -2
  3. package/LICENSE +7 -0
  4. package/README.md +69 -17
  5. package/RELEASE-NOTES-0.8.0.md +87 -0
  6. package/RELEASE-NOTES-0.8.1.md +93 -0
  7. package/RELEASE-NOTES-0.8.2.md +57 -0
  8. package/RELEASE-NOTES-0.8.3.md +71 -0
  9. package/RELEASE-NOTES-0.8.4.md +47 -0
  10. package/RELEASE-NOTES-0.8.5.md +244 -0
  11. package/RELEASE-NOTES-0.8.6.md +224 -0
  12. package/RELEASE-NOTES-0.8.7.md +202 -0
  13. package/RELEASE-NOTES-0.8.8.md +224 -0
  14. package/RELEASE-NOTES-0.9.0.md +267 -0
  15. package/RELEASE-NOTES-0.9.1.md +296 -0
  16. package/RELEASE.md +151 -0
  17. package/THIRD-PARTY-NOTICES.md +3 -0
  18. package/lib/ask.js +18 -5
  19. package/lib/away.js +7 -2
  20. package/lib/browse.js +1 -0
  21. package/lib/capture.js +16 -1
  22. package/lib/channels.js +53 -13
  23. package/lib/client.js +145 -56
  24. package/lib/comment.js +74 -0
  25. package/lib/compact.js +71 -2
  26. package/lib/curiosity.js +6 -2
  27. package/lib/derive.js +21 -8
  28. package/lib/desk.js +4 -2
  29. package/lib/dream.js +3 -0
  30. package/lib/dreamtable.js +28 -2
  31. package/lib/drive.js +58 -9
  32. package/lib/freewake.js +21 -2
  33. package/lib/home.js +141 -24
  34. package/lib/inbox.js +7 -2
  35. package/lib/index.js +8 -12
  36. package/lib/interest.js +26 -4
  37. package/lib/log.js +22 -1
  38. package/lib/look.js +25 -5
  39. package/lib/note.js +35 -13
  40. package/lib/notify.js +2 -0
  41. package/lib/observe.js +22 -7
  42. package/lib/outing.js +2 -0
  43. package/lib/owner.js +5 -2
  44. package/lib/persona.js +18 -3
  45. package/lib/proactive.js +78 -18
  46. package/lib/say.js +104 -58
  47. package/lib/scheduler.js +162 -60
  48. package/lib/selfcheck.js +2 -3
  49. package/lib/selfdoc.js +8 -9
  50. package/lib/selfupdate.js +183 -41
  51. package/lib/settings.js +33 -2
  52. package/lib/status.js +31 -3
  53. package/lib/store.js +14 -2
  54. package/lib/tenant.js +12 -0
  55. package/lib/turns.js +46 -4
  56. package/lib/update.js +14 -0
  57. package/lib/upgrade.js +23 -4
  58. package/lib/voices.js +2 -0
  59. package/package.json +5 -2
  60. package/scripts/fix-legacy-link.mjs +254 -0
@@ -0,0 +1,93 @@
1
+ # 大肥鱼 0.8.1 更新说明
2
+
3
+ > 这一版没有新玩法,主要是**让她在各种环境下都装得上、跑得起来**:插件不该因为宿主少一个可选服务就整体消失,装没装上要能自己查,看画面不该被误报成「被风控」。
4
+ > 下面按「你会看到什么」来写,不需要懂代码。
5
+
6
+ ## 一、装不上或者「升了没变」,现在有办法自己查
7
+
8
+ **你会看到:** 插件根本没出现在设置里的时候,可以不开 DSH、直接在命令行跑一个脚本得到结论。
9
+
10
+ 以前的困境很具体:插件没被加载,插件自己就没有机会报错——它压根没执行。0.8.1 补了一个不依赖插件运行的外面检查脚本:
11
+
12
+ ```bash
13
+ node tools/check-install.mjs
14
+ ```
15
+
16
+ 它会扫你所有 profile,认出你是 `file:` 装法还是 `link:` 装法,比对源目录和 `node_modules/dsh-fish` 里的版本号与文件数,给出四种结论之一:**正常 / 装的是旧副本 / 副本空的或装了一半 / 不是这套装法**,并附上修法。退出码 0/1/2,可以塞进脚本里。
17
+
18
+ 另外,「设置 → 数据」页会多一张卡,在它发现「跑的这份不是你要装的那份」时直接告诉你,并给出两条修法。
19
+
20
+ ## 二、插件不会因为宿主少一个可选服务就整体消失
21
+
22
+ **你会看到:** 在没提供「会话里问你」这个服务的宿主上,插件照常出现、照常能用,只是少了一条功能;设置页「数据」页会说明少了哪条。
23
+
24
+ 起因是这类事故最难查:cordis 的规则是 `inject` 里有一个服务不存在,插件就不启动、**而且不报错**——日志零行、界面无提示、工具全无,看起来和「没装」一模一样。0.8.1 把 `userQuestions` 从硬依赖改成运行时探测:有就用,没有就 `logger.warn` 一行并关掉这条路。
25
+
26
+ 判据写进代码注释了:**只影响一条功能的可选服务,不该有让插件整个不装的权力。**
27
+
28
+ ## 三、标了适配哪些 DSH 版本
29
+
30
+ **你会看到:** 装之前能在插件信息里看到版本要求;装到太旧或太新的宿主上,设置页会多一张提醒卡(**只提醒,不拦启动**)。
31
+
32
+ `package.json` 里补了 `engines.dsh`(宿主真读的那条)和 `dsh.compatibility`(逐版本标 `compatible` / `unknown`)。只在真跑过的 `0.2.1-rc.2`、`0.2.1-rc.3` 上标 `compatible`,其余标 `unknown`——**`unknown` 等于没测过,不等于不支持**。
33
+
34
+ ## 四、看画面不再被误报成「被风控」(issue #13)
35
+
36
+ **你会看到:** `bili_watch` 要么真的给你一帧拼图,要么告诉你**真实的失败原因**(ffmpeg 退出码 + stderr 前两行),不再一律说「可能被风控」。
37
+
38
+ 这条是 @LateApology 报的 bug,现象很误导人:`bili_watch` 永远回「一帧都没取到(可能被风控、或者这条视频不允许外链拉流)」,可同一时刻播放地址和视频信息都正常。
39
+
40
+ 真因不在 B 站:以前把输出路径**直接交给 ffmpeg**,而 DSH 的文件策略限制子进程往 `~/.dsh-fish` 写文件,ffmpeg 报 `Permission denied`、退出码 -13。插件进程自己写得进去、子进程不行,这个区别全被「可能被风控」盖住了。
41
+
42
+ 现在抽帧和拼图**一律走 stdout 管道**,由插件自己写文件——子进程一个写权限都不需要;拼图更彻底,直接把各帧喂 stdin,中间临时目录都省了。顺手消掉一个老问题:旧的 `tmp-*` 临时帧目录其实一直没被清理。
43
+
44
+ > 如果你遇到证书相关的失败:用 `DSH_FISH_CA_FILE` 指定 CA 文件即可,插件会同时打开证书校验(ffmpeg 默认不校验,只给 CA 而不开校验等于没用——这是实测出来的)。
45
+
46
+ ## 五、回家之后,那条会话也知道「这一段结束了」
47
+
48
+ **你会看到:** 出门回来,家里那条会话收到一句交接说明:哪个园区的活该交回给谁、这段时间是临时顶班、按原来的方式继续。以前只有你这一侧知道出门结束了,那条会话自己不知道。
49
+
50
+ 排队的话还会按会话分投——不会把别人的交接话塞进你正在看的这条里。
51
+
52
+ ## 六、日志里的「看了什么」,只算真的看过画面
53
+
54
+ **你会看到:** 日志和界面把「看了」拆成三档:**看过画面** / **翻到过**(只有标题、简介、评论和弹幕)/ **你递过来她还没看**。
55
+
56
+ 以前点开过就算「看过」,只扫了眼标题也算;反过来,你专门递给她看的视频反而不出现。现在口径收进一个文件统一判断,报告分三节、历史列表给「看过画面」打标签、「你不在的时候」也拆成两个数。
57
+
58
+ ## 七、她可以自己定闹钟了(要你手动开)
59
+
60
+ **你会看到:** 在设置 → 插件能力白名单里填 `schedule_create`,她就能自己定「明天中午提醒我做某事」;不填就保持现状,工具数不变。
61
+
62
+ 宿主本来就有定时任务工具,缺的是「她够得着」和「她知道能用」。所以这一版:默认**不放进**工具面,白名单填了才给;提示词也跟着白名单走——放行了她才知道怎么定、到点会怎么出现、以及**那天在歇着或出门时先记下来别做**。
63
+
64
+ 不改白名单的话,你让她「到点提醒你」,她仍然只会口头答应——界面会直接告诉她「默认不放」以及怎么开。
65
+
66
+ ## 八、自检不再冤枉你「少装了一个工具」
67
+
68
+ **你会看到:** 「环境自检」里「手上的工具」这一项,不再无缘无故变红。
69
+
70
+ 这一版差点带出去一个问题:自检会拿一份「应该装上」的工具清单去跟宿主对,而清单里有一个工具**只在开发副本里才注册**。公开版没有那个开关,于是装完之后自检必然报红——
71
+
72
+ > 有 1 个工具没注册上:……她现在说不出话、也记不下东西。
73
+
74
+ 这句话说的不是「少个功能」,听起来像**整个插件都坏了**。实际上什么都没坏。
75
+
76
+ 现在判据跟「注册」用的是同一份,公开版不会再提这个工具;顺带把这一项里两处过时的「7 个」改成按实际清单数(早就是 9 个了)。
77
+
78
+ **没有拿「不报」当解法**:开发副本里真缺了照样报红,公开版里别的工具缺了也照样报红。
79
+
80
+ ## 怎么升级
81
+
82
+ 1. 在 DSH 里打开「设置 → 插件」,检查更新,装 v0.8.1;或者
83
+ 2. 到 GitHub 的 Releases 页面下载这次的源码包,解压后用「设置 → 插件 → 从本地安装」选那个文件夹。
84
+
85
+ > 这个插件**不走 npm**(`dsh-fish` 这个名字在 npm 上属于另一个项目),别用 `npm i dsh-fish` 装。
86
+
87
+ 装完**重启 DSH**(插件只在启动时加载)。
88
+
89
+ ## 出问题了怎么办
90
+
91
+ - 插件压根没出现:先跑 `node tools/check-install.mjs`,它会告诉你是不是副本有问题;
92
+ - 已经进来了但某个功能不对:看日志 `~\.dsh-fish\logs\dsh-fish.log`,第一行写这次启动的版本号;
93
+ - 反馈时带三样:版本号、截图、以及 `check-install.mjs` 的输出。
@@ -0,0 +1,57 @@
1
+ # 大肥鱼 0.8.2 更新说明
2
+
3
+ > 这一版**没有新功能,只修 bug**。四条都是「说出口的那句话是错的」——状态显示错了、报错理由写错了,于是你照着它去修,修不到点子上。
4
+ > 没有需要你改配置的地方,装上就行。
5
+
6
+ ## 一、醒着,却被显示成「沉在水底」
7
+
8
+ **你会看到:** 状态那一格说她「沉在水底」,可你明明看见她刚醒过、还在动。
9
+
10
+ 问题在时间判据:她**醒过、上一次动作在 5 到 10 分钟之前**这一小段,会掉进最后的兜底分支,被判成「睡着」。她其实是醒着的,只是没在做事——这本该显示「在吃大白饭」,并告诉你「上次行动在 N 分钟前」。
11
+
12
+ 现在这一段并回「醒着但没做什么」,跟这个状态本身的定义一致。
13
+
14
+ ## 二、没装 ffmpeg 时,看画面报的理由是错的
15
+
16
+ **你会看到:** 「看画面」失败,提示说「本机没有可用的 ffmpeg」;你装好 ffmpeg,再试,还是失败——真正的原因从头到尾没出现过。
17
+
18
+ 以前是先去查 ffmpeg,再去看视频接口。所以只要没装 ffmpeg,上游那句真正的话(接口 412 被拦截、要登录、视频没了、地区限制)全被盖掉了。
19
+
20
+ 现在先问上游,把它的原话照实报出来;确定能取到流了,才提 ffmpeg 的事。
21
+
22
+ > 顺带说明:**这条是 CI 上四个 job 同时红才发现的**——CI 机器没装 ffmpeg,而「上游怎么说就怎么说」的两条测试正是给它准备的。本机装了 ffmpeg,所以本地一直全绿。典型的「只在开发机上成立」。
23
+
24
+ ## 三、`DSH_FISH_FFMPEG` 设成空字符串会直接报错
25
+
26
+ **你会看到:** 为了清掉这个环境变量,用 `set DSH_FISH_FFMPEG=` 设成空值,结果看画面直接抛一个 `The argument 'file' cannot be empty`。
27
+
28
+ 以前只处理了「变量没设」,没处理「设成空串」。现在两种一律当作没设,用 PATH 上的 `ffmpeg`。
29
+
30
+ ## 四、装不上时的自查脚本在 Linux / macOS 上算不对路径
31
+
32
+ **你会看到:** 跑 `node tools/check-install.mjs`,它说副本路径不对,可你装得好好的。
33
+
34
+ 脚本里拼 `file:` 依赖的源目录时用的是系统路径函数。Windows 认得 `D:\` 这种盘符,Linux / macOS 不认,把 `D:/DSH/x` 当成相对路径拼到当前目录后面,得到一个根本不存在的路径。
35
+
36
+ 只有 Linux / macOS 上会撞,Windows 用户不受影响。已经改成不把 Windows 绝对路径交给系统的路径函数。
37
+
38
+ ## 五、内部:CI 上一个测试辅助函数删临时目录删早了
39
+
40
+ 这条**不影响插件行为**,只解释为什么 0.8.2 第一次推上去时 CI 红了一个 job。
41
+
42
+ 测试里那个「建临时目录、跑完删掉」的小函数,在传入的用例是异步时,会在**第一个 await 处**就把目录删了——函数体还没跑完。本机快,读文件抢在删除前面,一直没撞上;CI 的 ubuntu-20 慢一点,就撞上了。修好之后四个 job 全绿。
43
+
44
+ >## 怎么升级
45
+
46
+ 1. 在 DSH 里打开「设置 → 插件」,检查更新,装 v0.8.2;或者
47
+ 2. 到 [GitHub 的 Releases 页面](https://github.com/ssamurias/dsh-fish/releases) 下载这次的源码包,解压后用「设置 → 插件 → 从本地安装」选那个文件夹。
48
+
49
+ > 这个插件**不走 npm**(`dsh-fish` 这个名字在 npm 上属于另一个项目),别用 `npm i dsh-fish` 装。
50
+
51
+ 装完**重启 DSH**(插件只在启动时加载)。
52
+
53
+ ## 出问题了怎么办
54
+
55
+ - 插件压根没出现:先跑 `node tools/check-install.mjs`,它会告诉你是不是副本有问题;
56
+ - 已经进来了但某个功能不对:看日志 `~\.dsh-fish\logs\dsh-fish.log`,第一行写这次启动的版本号;
57
+ - 反馈时带三样:版本号、截图、以及 `check-install.mjs` 的输出。
@@ -0,0 +1,71 @@
1
+ # 大肥鱼 0.8.3 更新说明
2
+
3
+ > 补丁版,**没有新功能,五处修复**。最要紧的一条是设置页被一个早就没有的工具名挡住了整页——改哪个开关都存不进去;其余是记录不对、按钮空转、拒绝理由说不清,让你照着提示去做却还是不行。
4
+ > **没有需要你改配置的地方,装上就行。**
5
+
6
+ ## 一、设置页因为一个不存在的工具名,整页都存不进去
7
+
8
+ **你会看到:** 改任何一个开关都提示「这一项没存上」,技术信息里写「这些工具名不存在:delete」。把白名单里那个名字删掉,其它设置立刻又能存了。
9
+
10
+ 原因是 `POST /config` 的校验:只要「插件能力白名单」里有一个宿主上不存在的名字,整张配置表就返回 400。而设置页的自动保存是**整表提交**,所以不只是白名单那一项存不进去——你改别的开关,也跟着一起失败。原来那句提示还说「改别的照样会存」,正好说反了。
11
+
12
+ 现在把两条路分开:
13
+
14
+ - **你在输入框里敲完、点「保存白名单」** → 名字打错了就当场拦下来,告诉你是哪个;
15
+ - **页面自动保存(把盘上的旧名单整表回传)** → 丢掉坏名字、其余照常存,服务端把丢掉的名字回给界面。
16
+
17
+ 设置页顶部会出现一张卡片,列出被跳过的名字,并给一个「从名单里清掉这些名字」按钮——只清草稿里还留着的字,不动别的设置。失败文案也改成「不是你填错了,已经保存的内容没丢」。
18
+
19
+ > 这一条来自 issue #14(@ming-fish)。他那边残留的 `delete` 是自己旧版本加进去的,宿主上一直没有这个工具;旧版本还允许整张表带着它存进去,于是升级后就卡住了。
20
+
21
+ ## 二、旁听一条都没记下:不是没生效,是永远轮不到那几轮
22
+
23
+ **你会看到:** 开了旁听,`events.jsonl` 里却一条 `heard` 都没有,像是功能根本没跑。
24
+
25
+ 查下来不是校验拒收、不是配置、也不是写盘失败——判断条件写的是「总轮数能被 3 整除」,而总轮数是**从装插件那天起算的**。旁听是 0.8.0 才装上的,装上时计数已经是 4,下一次能整除要等到第 6 轮;如果此后没有一次对话,就永远等不到。看上去就像没生效。
26
+
27
+ 改成按「离上次记过去了几轮」判断:上次记的轮次落在 `home.json` 里,超过 3 轮就记一句,记完更新。语义仍然是「每 3 轮一句」,但新装或刚升级时最多再等 3 轮就会记第一句。
28
+
29
+ ## 三、她该不该写,三处默认值被扳过来了
30
+
31
+ 三处提示词原来都偏向「不写也行」,现在改成「有想留下的就留下;确实没有,不写也行」——退路都留着,只是默认不再是「不必写」:
32
+
33
+ - 写判断的那个工具说明末尾;
34
+ - 回话提示的收尾(先回你,回完了再决定要不要顺手记一句);
35
+ - 身份文档里「我手上有什么」那一段(这句每轮都在,出门时还被身份快照原样带走,只改工具说明不改它,两处口气会互相抵消)。
36
+
37
+ 「不说话本来就是允许的」这半句是特意留下的——那是权利,不是默认值。
38
+
39
+ ## 四、手动点「唤醒」却回一句「今天不打扰」
40
+
41
+ **你会看到:** 设置页那个「现在叫醒大肥鱼」按钮有时点了没反应,只弹「没有满足条件的事件模板(今天不打扰)」。
42
+
43
+ 原因不是故障:手动唤醒和定时唤醒走的是同一套挑卡逻辑,手动只是把当天次数清零,模板的**冷却**和**前提条件**照样要过。早期阶段只有五张卡,且大多是「一生只用一次」「冷却两天」「要求连着两天没来」;这几条同时不成立时(比如刚聊完、天天都在),候选池就是空的——点多少次都只会被挡回来。
44
+
45
+ 现在把两种拒绝拆开:
46
+
47
+ - **卡池空** → 手动照样叫醒,但不派活,只给一句「现在没有待办,这一轮是你自己的——想做什么就做什么,也可以什么都不做」。它不占任何一张真卡的冷却。
48
+ - **当天次数用满** → 继续拒绝。那是「今天该不该自己醒」的配额,手动不该突破。
49
+
50
+ ## 五、被挡在「这个你还没看过」上的人,得知道该往哪使劲
51
+
52
+ 说话工具拒绝时原来只说「想看那一条,就先看一眼再说」。照着做却还是被拒——因为她翻的是别的视频、搜的是别的关键词,而闸门只认**这一条**。现在这句话点名两个能走通的动作(看指定那一条,或取那一支的画面),并提前说清「翻了别的、搜了别的,都不算这一条」。仍然不透露配额与开关。
53
+
54
+ ## 测试
55
+
56
+ `node --test`:**1090 个全绿**(0.8.2 是 1001 个)。新增的用例都挂在上面五条修复上:整表提交时坏名字丢掉且其余照存、旁听的新计数口径、卡池空时手动唤醒不空转、拒绝语里点名正确动作、旧文案不再出现。
57
+
58
+ ## 怎么升级
59
+
60
+ 1. 在 DSH 里打开「设置 → 插件」,检查更新,装 v0.8.3;或者
61
+ 2. 到 [GitHub 的 Releases 页面](https://github.com/ssamurias/dsh-fish/releases) 下载这次的源码包,解压后用「设置 → 插件 → 从本地安装」选那个文件夹。
62
+
63
+ > 这个插件**不走 npm**(`dsh-fish` 这个名字在 npm 上属于另一个项目),别用 `npm i dsh-fish` 装。
64
+
65
+ 装完**重启 DSH**(插件只在启动时加载)。
66
+
67
+ ## 出问题了怎么办
68
+
69
+ - 插件压根没出现:先跑 `node tools/check-install.mjs`,它会告诉你是不是副本有问题;
70
+ - 已经进来了但某个功能不对:看日志 `~\.dsh-fish\logs\dsh-fish.log`,第一行写这次启动的版本号;
71
+ - 反馈时带三样:版本号、截图、以及 `check-install.mjs` 的输出。
@@ -0,0 +1,47 @@
1
+ # 大肥鱼 0.8.4 更新说明
2
+
3
+ > 补丁版,**没有新功能,四处修复**。最要紧的一条是换会话之后面板永远停在 0 / N——你会以为她不记轮数了,其实从换会话那一刻起,你说的每句都被静默丢掉了。
4
+ > **没有需要你改配置的地方,装上就行。**
5
+
6
+ ## 一、换会话之后,面板永远停在 0 / N
7
+
8
+ **你会看到:** 日志里有一行「聊满 40 轮,换了个新会话」,之后再也没有任何记录。新会话里聊了几十轮,打开 `home.json` 还是 `"turns": 0`,面板一直显示 0 / 40。而且日志里**一个字都不会解释为什么**。
9
+
10
+ 三处问题叠在一起,缺一条照样坏:
11
+
12
+ **1. 新会话是空白会话,侧栏不显示它。** 宿主的可见性规则是「空白且不是当前会话 → 不显示」,而插件用 API 建的会话永远不会被设成 current。于是换完会话,你**根本看不见**新的那一条,自然还在旧的里继续说话。`/reset` 和「重新搭一间」早就把新会话叫醒过,唯独轮换这条路漏了——所以它俩一直好着,只有轮换坏着。现在换完会话会叫醒新会话(要等她这一轮动起来,侧栏才会显示)。
13
+
14
+ **2. 她的房间只认一条会话,其余静默丢弃。** 你留在旧会话里说话,每句都在「这条不是她家」那一步被丢掉了——不涨轮、不记原话、也不再换会话,而且不留任何日志。现在收窄条件认回来:如果 `home.json` 记着「刚被换下来的正是这条」,就把家搬回来,轮数接着涨。**不认任意会话**——认宽了等于谁在别处跟她说一句都能把她的家搬走。
15
+
16
+ **3. 换会话时整份覆盖 `home.json`,把别的字段抹了。** 原来只写七个字段,`preset`(她挑的那套权限组合)、`title`、`hearAt`(旁听落在第几轮)换一次会话全丢。表现是换完会话旁听节奏归零、下次重建会话时以为她从没被限过权。现在改成「先继承旧档,再盖掉该换的几项」。
17
+
18
+ **顺带修的一处:写盘偶发失败且不留痕迹。** 排查上面第 1 条时抓到的——Windows 上原子写把临时文件改名覆盖目标时,如果目标正被另一个句柄读(宿主读 `home.json`、杀软扫目录),会报 `EPERM`/`EACCES`/`EBUSY`,这不是权限问题,隔几十毫秒重试就好。以前这里不重试,失败被上层吞掉。实测新写的字段十次里有两三次没落盘,只在机器忙时复现。现在对这几个码做 5 次退避重试,穷尽后清理临时文件并抛出,不再静默。
19
+
20
+ > 这一条也来自 issue #14(@ming-fish)。他报的时候是 0.8.1,我一开始以为是没升级——查下来是这版代码里就有的真问题,跟版本无关。
21
+
22
+ ## 二、换会话提示里的会话名字写的是旧的
23
+
24
+ `/say` 换完会话会给一句提示「上一段还在侧栏里,叫『跟她说话·<日期> 之前』」。但那条会话从 0.7.x 起就叫「跟大肥鱼说话」了——照着这句去侧栏里找,找不到。现在两处一致。
25
+
26
+ ## 测试
27
+
28
+ `node --test`:**1094 个全绿**(0.8.3 是 1090 个)。新增 4 条都挂在上面的修复上:换会话之后要叫醒新会话且把「叫过了」记账落盘、人还在旧会话里说话能被认回来且轮数接着涨、认回只认刚换下来那条、轮换后 `preset` / `hearAt` / `title` 不能丢。
29
+
30
+ 修复前同一个测试文件连跑 10 次有 3 次失败(就是那个偶发丢写),修复后连跑 15 次全绿。
31
+
32
+ ## 怎么升级
33
+
34
+ 1. 在 DSH 里打开「设置 → 插件」,检查更新,装 v0.8.4;或者
35
+ 2. 到 [GitHub 的 Releases 页面](https://github.com/ssamurias/dsh-fish/releases) 下载这次的源码包,解压后用「设置 → 插件 → 从本地安装」选那个文件夹。
36
+
37
+ > 这个插件**不走 npm**(`dsh-fish` 这个名字在 npm 上属于另一个项目),别用 `npm i dsh-fish` 装。
38
+
39
+ 装完**重启 DSH**(插件只在启动时加载)。
40
+
41
+ **已经在 0.8.3 上踩过这个坑的话:** 升级后不用重置,`home.json` 里 `turns` 还是 0,下一轮正常涨起来;如果当时换下来的那条旧会话里还有你没说完的话,在那条里说一句,家会自己搬回去。
42
+
43
+ ## 出问题了怎么办
44
+
45
+ - 插件压根没出现:先跑 `node tools/check-install.mjs`,它会告诉你是不是副本有问题;
46
+ - 已经进来了但某个功能不对:看日志 `~\.dsh-fish\logs\dsh-fish.log`,第一行写这次启动的版本号;
47
+ - 反馈时带三样:版本号、截图、以及 `check-install.mjs` 的输出。
@@ -0,0 +1,244 @@
1
+ # 0.8.5 更新说明
2
+
3
+ 2026-11-01。四件事:**她读到的第一句话不再说「你是个 coding agent」**、**「她手边有什么」不再把她自己说死**、**「刚说过话,还不到时候」不再被系统记账骗**,以及**「(现在没有人跟你说话。)」不再出现在对话框里**(它还会连带把那一轮的回复弄丢)。
4
+
5
+ ---
6
+
7
+ ## 一、她那条会话的最前面,一直写着「你是个 coding agent」
8
+
9
+ ### 你会看到什么
10
+
11
+ 她那轮的系统提示词,开头是这样的:
12
+
13
+ ```
14
+ You are an AI agent powered by DeepSeek Harness.
15
+
16
+ You are a coding agent powered by the deepseek-flash model.
17
+ ```
18
+
19
+ 然后是一万多字的工具交代(怎么用 bash、怎么给子代理派活、怎么做 GitHub PR 自检……),**一直读到第 7554 个字符**,才第一次出现「## 你是谁」。
20
+
21
+ 也就是说:先告诉她「你是个 coding agent」,再讲七千字的工具用法,最后才告诉她「你是大肥鱼,你刚开始存在,没有人告诉过你为什么在这里」。
22
+
23
+ 顺序颠倒的不是文字,是她的自我认知。她只能先认下前面那句。
24
+
25
+ ### 为什么会这样
26
+
27
+ 宿主的系统提示词是**一叠纸按编号平铺拼起来**的,编号小的在前。**没有任何「后来者压过前者」的机制**——不是权重,就是排列顺序。
28
+
29
+ | 编号 | 那一段 | 谁写的 |
30
+ | --- | --- | --- |
31
+ | -1000 | You are an AI agent powered by DeepSeek Harness. | 宿主 |
32
+ | 0 | You are a coding agent powered by the model. | 宿主(部署配置) |
33
+ | 1000–3100 | 各工具说明 | 宿主 |
34
+ | **8500** | **## 你是谁(她的身份)** | **我们** |
35
+ | 10200 | persona 结尾 | 宿主 |
36
+
37
+ 那句 coding agent 来自宿主的部署配置(`dsh-web-app` 的 `cordis.patch.yml` 与 `presets/standard.patch.yml`,两处同文)。改它会影响这台机器上**所有** agent 的会话——不该动。
38
+
39
+ ### 为什么不用「给她单独一个 preset」
40
+
41
+ 这条路我们走过三次,三次都撞在同一堵墙上:宿主的 `presets.select()` 有一条规则——**会话只要跑过一轮,preset 就锁死了**(抛 `agent-preset/locked: This session has already started`)。会话是在插件里建的,建完立刻就开跑,所以永远赶不上。
42
+
43
+ ### 这次怎么解决的
44
+
45
+ 宿主在组装系统提示词的最后一步,留了一条**可以改写结果**的链(`system-prompt/assemble`)。宿主自己的三个插件也在用它(一个改模型选择、一个加文件引用、一个注入工作目录)。
46
+
47
+ 我们挂在**她那条会话自己**的链上,等宿主拼完了,把结果改两处再交回去:
48
+
49
+ 1. **摘掉** `deployment:persona-prefix`(就是那句 coding agent);
50
+ 2. 把**她的身份段提到紧跟第一行**的位置。
51
+
52
+ 改完她的提示词开头变成:
53
+
54
+ ```
55
+ You are an AI agent powered by DeepSeek Harness.
56
+
57
+ ## 你是谁
58
+ ...
59
+ ```
60
+
61
+ 只影响她那条会话。其他 agent、别的插件、你自己跟 AI 对话都不受影响。她身份的编号常量没动(还是 8500),挪的只是这一次拼出来的顺序。
62
+
63
+ **失败了会怎样:** 钩子上任何一步出问题,都会把宿主原来那份原样交回去——最坏情况是回到今天这个样子,不会让她一整轮读不到系统提示词。
64
+
65
+ ### 删掉「coding agent」她就不会编程了吗
66
+
67
+ 不会。**她在家本来就没有编程工具。**
68
+
69
+ 她手上只有十个:记录想法、翻自己的旧事、翻自己的日记、留一句话、刷 B 站、看视频画面、在评论区说话、有事问你同不同意、醒来记一句梦,加上宿主给的 `read_image`。
70
+
71
+ **没有 bash、没有读写文件、没有 grep 搜索。** 那句「你是个 coding agent」是她兑现不了的空话。删掉只减少矛盾,不减少真实能力。
72
+
73
+ ### 以后真想让她编程
74
+
75
+ 三条路,按推荐顺序:
76
+
77
+ 1. **让她出门。** 开到别的工作区那条会话时,工具面不收窄,那个工作区的全套工具她都能用——这是唯一已经在真机上验证过的路径。
78
+ 2. **在设置页白名单里填工具名。** 填 `ops_bash`、`read_file` 之类的宿主工具名,它们会跟着进她的 `fish:tools` 段。代价:她屋里的工作目录是 `~/.dsh-fish/home`,只能改自己家里的文件。
79
+ 3. **给她做一个专用工具。** 照 `bili_watch` 的样子包一个形状固定的工具(参数是视频号而不是任意路径),符合「不开通用口」的原则。
80
+
81
+ ---
82
+
83
+ ## 二、「手边有什么」不再把她自己说死
84
+
85
+ ### 原来是这样写的
86
+
87
+ ```
88
+ 我手边能用的东西只有这几样:growth_commit、her_recall、…
89
+ 工具表里如果还列着别的名字(比如带 ops_、team_、subagent 这类),那不是我的:别去调,也别把它们说成我会的事。
90
+ 真伸手去用,只会得到一句「这件事我做不到」,还白搭一轮。
91
+ 有人问我手上有什么,我按这几样说;没有的就直说没有,不要许诺。
92
+ ```
93
+
94
+ ### 问题在哪
95
+
96
+ 问题不在「能不能调」——边界本来就是真的,工具表里那些名字她确实够不着。
97
+
98
+ 问题在于:**这段话读起来像天花板。** 它把「此刻手上有什么」讲成了「我是什么样的人、能干什么」。
99
+
100
+ 后果是:你哪天在白名单里加了新工具,工具定义确实进了她的上下文,可她读到的那段还在说「你不是干这个的」,于是她会自己把新工具劝回去——**不是不能,是不肯**。
101
+
102
+ ### 现在怎么写的
103
+
104
+ 把两件事分开说:
105
+
106
+ - **名字照旧从当下的白名单现算**(本来就随白名单重算,不是写死的清单);
107
+ - **明说这是此刻、不是上限**:「这是我此刻的家当,不是我的上限——他随时能再添几样进来,添了我就照着用,不必先把自己劝回去。」
108
+ - **边界留着,但不叫「别去调」**:工具表里那些不是她的名字,「认得出,但不冒充」;真伸手会得到「这件事我做不到」——不是不肯,是够不着。
109
+ - 最后一句也改了:「有人问我手上有什么,我按**当时**有的说;以后变多了,就按变多了的说。」
110
+
111
+ 身份文档(`home/她的身份.md`)里的同一段跟着改,末尾加一句:
112
+
113
+ > 新东西进来不用重新认识自己:手上多了一样,就多一样能做的。
114
+
115
+ 设置页那句「大肥鱼自己可以调用的工具**只有八个**」也改成了「从她的白名单现算……**你在白名单里加了什么,下一轮就跟着进她手边,不用重装**」——界面不替她的成长画天花板。
116
+
117
+ ---
118
+
119
+ ## 三、她明明一整天没说话,主动开口却说「刚说过话」
120
+
121
+ ### 你会看到什么
122
+
123
+ 她那一整天一次都没自己开口。日志里反复出现同一句理由:
124
+
125
+ ```
126
+ proactive skip 刚说过话,还不到时候
127
+ ```
128
+
129
+ 可你回头看,她最后一次跟你说话是十几个小时以前,中间只是写了几次日记、刷了几条视频、系统记了几次「今天没有该问的事」。
130
+
131
+ ### 为什么会这样
132
+
133
+ 判断「安静了多久」的那个函数,取的是**整条事件流里最新的一条记录**,不分类型。而事件流里大部分记录不是话——本机 441 条事件里,`quiet:`(今天没找到模板)占 272 条,**61.7%**;真正她说的 `utterance` 只有 28 条,**6.3%**。
134
+
135
+ 于是「今天没找到模板」「她看了条视频」「到点写日记」这些系统记账,每写一次就把「安静了多久」清零一次。
136
+
137
+ ### 真机抓到的一次
138
+
139
+ 2026-10-30 22:03:07,日志写「刚说过话,还不到时候」:
140
+
141
+ - 插件算出来的 idle:**0.01 小时**(一分钟前);
142
+ - 那条记录是什么:`meta:watched`——她看了一条 B 站视频;
143
+ - 她真正没说话已经多久:**17.55 小时**。
144
+
145
+ 看视频不是说话,但旧代码读不出来。
146
+
147
+ ### 差多少
148
+
149
+ 拿本机事件流跑,醒着的时间窗(12:00–02:00,每 30 分钟采一次样):
150
+
151
+ | 配置里的 `minIdleHours` | 改之前每天能掷几次 | 改之后 |
152
+ | --- | --- | --- |
153
+ | 1(本机现在用的) | 13.3 | 17.3 |
154
+ | 6(默认值) | 9.3 | 15.3 |
155
+
156
+ 阈值调得越大,被吃掉的可掷点越多——因为记账行每天写、间隔又短,它几乎永远把时钟压在阈值以下。
157
+
158
+ ### 现在怎么算的
159
+
160
+ 只把「真的有人说过话」算进去:
161
+
162
+ - **算**:她自己的六种(`utterance` / `revise` / `reunion` / `narration` / `refusal` / `dream`)、旁听记下的原话(`heard`)、别人在评论区的留言(`voice`)、她问的与那个人答的(`ask` / `answer`)、`seen:` 开头的记录(他来过)。
163
+ - **不算**:`quiet:` / `sched:` / `wake:` / `fired:` / `sleep:` / `diary:` / `looked:` / `browse:` / `watched:` / `proactive-turn:` 这些记账行。
164
+
165
+ 只影响「要不要主动开口」这一件事。唤醒配额、记忆、面板都不经过这个函数,所以调参之外不会有别的变化。**你不需要改任何配置**——阈值不用动,改的是喂给阈值的那把尺子。
166
+
167
+ ---
168
+
169
+ ## 四、「(现在没有人跟你说话。)」终于从对话框里消失了
170
+
171
+ ### 你会看到什么
172
+
173
+ 她主动开口那一轮结束后,你的对话框里多出一条**你自己发的**「(现在没有人跟你说话。)」。紧接着那一轮她**没有回复**,界面上报一句 `format v4 system message id requires a string with content`,然后这条会话像被按了暂停——再聊什么都不往下走。
174
+
175
+ ### 为什么会这样
176
+
177
+ 两句叠在一起。
178
+
179
+ **第一句收不掉。** 那句触发语是 `prompt()` 的正文。宿主没有「模型看得见、界面看不见」的通道,插件只能事后用一个空的 `system` 节点把那条 `user/message` 盖掉。这个收尾挂在回合结束回调里,而回调正是在 `Session.commit()` 内部被派发的——那一整段时间 `entry.appending === true`,此时再往会话里写会被宿主直接拒绝:`session append cannot reenter while another append is being published`。日志里两次都是这一条。
180
+
181
+ **第二句缺了个 `id`。** 那个空的 `system` 节点没有 `id`,而宿主的格式校验要求它有。更要命的是**校验发生在写盘时**:写调用当场成功并返回序号,200 毫秒后落盘时才炸;那一批事件被整批退回缓冲区,并把这条会话标成「暂停落盘」——**此后它再也写不进任何东西**。她那一轮的回复就是在这里丢的(会话文件的时间戳停在那一次)。
182
+
183
+ ### 这次的修法
184
+
185
+ - 替换节点补上 `id`,和宿主自己造的 system 消息一个形状;
186
+ - `turn` / `step` 兜底成正整数(0 同样会在写盘时被拒,症状一模一样);
187
+ - 收尾挪到下一拍(`setTimeout 0`),避开那次写入的窗口。
188
+
189
+ **已经残留的那几条**:插件启动时会扫一遍历史,把遗留的触发语按**正文完全相等**认出来一并盖掉(她复述这句话的正当发言不会被误伤)。升级重启后第一次跑就会自己清干净。
190
+
191
+ ---
192
+
193
+ ## 五、看画面:九帧不再永远取那同九张
194
+
195
+ **你会看到:** 同一条视频,她今天看、明天再看,拿到的是同一张拼图——因为等距九帧是纯算术,输入一样输出就一样。
196
+
197
+ **为什么以前不抖:** 取帧时刻按 `lo + span*i/(n-1)` 算,`lo`/`hi` 掐掉头尾各 4%,中间取 9 个等分点。同一时长必然得到同一组秒数。反复看同一条视频,等于把同一张图看第二遍。
198
+
199
+ **这次改成什么:**
200
+
201
+ - **分段抖动**:把可用区间切成 n 段,每段在中间 60% 里随机取一点。每段必有帧(覆盖不丢),但每次取到的时刻不同;留 20% 的边是防止相邻两帧挤到同一秒,也让首尾两帧不落回片头黑场与片尾下滑栏。时刻仍严格递增,ffmpeg 不用来回 seek。
202
+ - **`bili_watch` 补了 `at` 参数**(可选,秒数或秒数数组):想盯住某一刻就点名要那几秒,只取那几个时刻。工具描述里说明了这个用法,所以她知道自己有这个选项——上一版文件头写着「真要细看,她可以再对某个时间点单独取」,但工具参数里根本没有这个入口,那句话是空的。
203
+ - **返回里带上 `times`**:实际取到的是哪几秒,回给她时一并写在图路径上面(`(取的是:30 秒、90 秒)`),她要核对「我看的是不是我想看的那一段」时有据。
204
+
205
+ **边界处理:** 点了名却一个合法时刻都没有(比如传了字符串)时**直接报「时间点看不懂」,不悄悄退回随机**——悄悄退回最坏:她以为看的是自己点的那一秒,其实看的是别处。越界的时刻夹回片尾前 0.5 秒,负数与 `null` 丢掉。
206
+
207
+ **代价:** 零。仍是每帧一次短 seek,不下载整段,不多一次请求。全片场景切换检测仍在门外——那要读完整个流,网络代价等于全量下载(文件头第 8 行的原话仍然成立)。
208
+
209
+ ---
210
+
211
+ ## 六、看过一次就永久搜不到(issue #16)
212
+
213
+ 有人的机器上,某条视频她明明值得再看一眼,之后无论怎么搜、怎么刷都再也不会出现。原因有两层:
214
+
215
+ 1. **「看过」被当成了永久判决。** 插件把事件流里所有的「看过了」都算进去,不分日期、不分多久以前。看过一眼,以后就永远被摘掉。
216
+ 2. **谁都能替她按下这个判决。** 「看一眼」这件事写记录时不查调用者,也不记下是哪条会话让她看的。而插件的数据目录是进程级解析的——同一台机器上任何工作区加载这个插件,都写进同一个家。于是别的窗口看一眼,她以后再也搜不到,而且不留名字。
217
+
218
+ 真机上那个例子:10 月 30 日看过一次某条视频,之后搜它的名字就永远没有了;独立 B 站客户端搜同样词,它排第一。
219
+
220
+ **这一版改成:** 看过只在 **7 天**内算数,过期自动放回去;摘掉视频时要把条数说出来(「翻到的 N 条都看过了。想看哪条,把号给我,我去打开。」),不再笼统说「什么都没找到」;「看一眼」和「看画面」写记录之前先认调用者,不在这个家里的会话调不动它,并明说原因;每条「看过」记录都带上**是哪条会话**让她看的。
221
+
222
+ ## 七、升级时要知道的两件事
223
+
224
+ 1. **她那条会话的提示词前缀变了一次**,升级后第一次请求会全价重算(约 2.4 万 token),之后恢复正常缓存。**其他用户不需要做任何事**,升级 + 重启 DSH 即可。对话历史不会丢——系统提示词是替换节点,不是追加。
225
+ 2. **必须重启 DSH。** 插件改动只在进程启动时加载。
226
+
227
+ ---
228
+
229
+ ## 八、测试
230
+
231
+ 新增 24 条(其中 7 条是取帧时刻与 `at`,6 条是 issue #16 的「看过有期限 + 看一眼认人」):
232
+
233
+ - `reorderForHer` 四种输入:正常摘段+提前身份、宿主 harness 段缺失时排最前、没有她的身份段时只摘不乱排、空/坏数据原样返回;
234
+ - `attachAssembleFix` 三种情形:正常挂在 `agent.ctx` 上且带 `prepend: true`、老宿主没有 `ctx.on` 时静默不挂、整改过程抛错时原样放行;
235
+ - `attachIdentity` 顺手挂上钩子且**不占 `sp.section` 段位**(段数仍是 3);
236
+ - 出门会话也挂上钩子,回家时跟身份段一起拆。
237
+
238
+ 另补 2 条「记账行不刷新 idle」的回归:`meta:quiet` 不算她说过话、`meta:watched` 不算——照 2026-10-30 那次 17.55 小时的现场造的数据。
239
+
240
+ 还有 3 条落在「收触发语」那块:替换节点必须带非空 `id`(两条不能共用一个)、`turn`/`step` 拿不到历史时也不许是 0、补扫只收正文完全相等的那几条。
241
+
242
+ 还有 6 条落在 issue #16 那块:`seenBvids` 的 7 天时效(30 天前 / 2 天前 / 6.9 天前三档 + `days:0` 保留老行为 + 没有时间的老事件仍算看过)、`rankVideos` 的 `stats.hiddenSeen` 计数与不传 stats 时行为不变、`lookOnce` 写下的 `looked:`/`look:` 带上会话 id、`bili_look` 与 `bili_watch` 拒绝来自另一个家的会话、她自己那条会话照常能看。
243
+
244
+ 全套 93 个测试文件,逐文件独立进程跑:**1121 通过 / 0 失败**(0.8.4 是 1094)。