@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.
- package/AGENTS.md +64 -10
- package/CHANGELOG.md +129 -2
- package/LICENSE +7 -0
- package/README.md +69 -17
- package/RELEASE-NOTES-0.8.0.md +87 -0
- package/RELEASE-NOTES-0.8.1.md +93 -0
- package/RELEASE-NOTES-0.8.2.md +57 -0
- package/RELEASE-NOTES-0.8.3.md +71 -0
- package/RELEASE-NOTES-0.8.4.md +47 -0
- package/RELEASE-NOTES-0.8.5.md +244 -0
- package/RELEASE-NOTES-0.8.6.md +224 -0
- package/RELEASE-NOTES-0.8.7.md +202 -0
- package/RELEASE-NOTES-0.8.8.md +224 -0
- package/RELEASE-NOTES-0.9.0.md +267 -0
- package/RELEASE-NOTES-0.9.1.md +296 -0
- package/RELEASE.md +151 -0
- package/THIRD-PARTY-NOTICES.md +3 -0
- package/lib/ask.js +18 -5
- package/lib/away.js +7 -2
- package/lib/browse.js +1 -0
- package/lib/capture.js +16 -1
- package/lib/channels.js +53 -13
- package/lib/client.js +145 -56
- package/lib/comment.js +74 -0
- package/lib/compact.js +71 -2
- package/lib/curiosity.js +6 -2
- package/lib/derive.js +21 -8
- package/lib/desk.js +4 -2
- package/lib/dream.js +3 -0
- package/lib/dreamtable.js +28 -2
- package/lib/drive.js +58 -9
- package/lib/freewake.js +21 -2
- package/lib/home.js +141 -24
- package/lib/inbox.js +7 -2
- package/lib/index.js +8 -12
- package/lib/interest.js +26 -4
- package/lib/log.js +22 -1
- package/lib/look.js +25 -5
- package/lib/note.js +35 -13
- package/lib/notify.js +2 -0
- package/lib/observe.js +22 -7
- package/lib/outing.js +2 -0
- package/lib/owner.js +5 -2
- package/lib/persona.js +18 -3
- package/lib/proactive.js +78 -18
- package/lib/say.js +104 -58
- package/lib/scheduler.js +162 -60
- package/lib/selfcheck.js +2 -3
- package/lib/selfdoc.js +8 -9
- package/lib/selfupdate.js +183 -41
- package/lib/settings.js +33 -2
- package/lib/status.js +31 -3
- package/lib/store.js +14 -2
- package/lib/tenant.js +12 -0
- package/lib/turns.js +46 -4
- package/lib/update.js +14 -0
- package/lib/upgrade.js +23 -4
- package/lib/voices.js +2 -0
- package/package.json +5 -2
- package/scripts/fix-legacy-link.mjs +254 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# 0.8.6 更新说明
|
|
2
|
+
|
|
3
|
+
2026-11-02。这一版四件事:**B 站登录可以用手机扫码了**(社区贡献)、**她一直念叨的那几条现在也会随时间退下去**、**她翻旧事可以只要开头**、**界面上的「事件 443 条」终于说清了它数的是什么**。
|
|
4
|
+
|
|
5
|
+
另有一处要紧的修:**「跟大肥鱼说话」那条对话曾经点开就报「历史加载失败」**——现在能打开了,而且以后不会再因为同一件事打不开。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 一、B 站登录:手机扫一下就行
|
|
10
|
+
|
|
11
|
+
### 你会看到什么
|
|
12
|
+
|
|
13
|
+
设置页「B 站登录」那张卡片里多了一格切换——**手机扫码**(默认)和**粘贴 cookie**。点扫码,弹出一张二维码浮层;手机 B 站扫完、在手机上点「确认登录」,浮层里变成「登的是 小鱼(uid 42)」,下面两颗按钮:**就是这个号,保存** / 取消。
|
|
14
|
+
|
|
15
|
+
存下来之后卡片上那行字变成「已存,登录账号:小鱼(uid 42)」。旁边多了个「清除登录」。
|
|
16
|
+
|
|
17
|
+
### 为什么会这样
|
|
18
|
+
|
|
19
|
+
原来那条路是让人按 F12、在 Application 里翻 Cookie、把 SESSDATA 和 bili_jct 抄进输入框。一步抄错就是「登上了但发不出评论」,而且这两个值等于账号的钥匙,手抄一遍就多留在剪贴板、截图、聊天记录里一份。
|
|
20
|
+
|
|
21
|
+
扫码这条路,钥匙从 B 站直接走到 `owner.json`(0600),中间不经过任何人的眼睛。
|
|
22
|
+
|
|
23
|
+
### 这是别人写的
|
|
24
|
+
|
|
25
|
+
扫码登录由 **@Bacon-01** 贡献([PR #17](https://github.com/ssamurias/dsh-fish/pull/17)),包括那个自己写的零依赖二维码编码器 `lib/qr.js`——没有引入任何 npm 包,因为本插件三个依赖字段都是空的。
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 二、扫不出码时,界面会说为什么
|
|
30
|
+
|
|
31
|
+
**你会看到什么:** 以前点「扫码登录」偶尔什么都不发生——不是没反应,是错误被吞了。现在按钮旁边会直接写出哪一步没成(拿不到登录二维码 / 二维码过期了 / 这个号读不出来),下面留一颗「重新获取」。
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 三、交流群换到 2 群了
|
|
36
|
+
|
|
37
|
+
1 群满了,反馈页换成 2 群的新二维码 + 群号(781084960)。
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 四、换对话不等于换了个地方
|
|
42
|
+
|
|
43
|
+
### 你会看到什么
|
|
44
|
+
|
|
45
|
+
满 40 轮之后她这条对话记录会换一条新的(这是宿主的机制,不是本插件做的)。换过去之后,她醒来看到的第一句从「(醒一醒。现在是 HH:MM。)」变成了:
|
|
46
|
+
|
|
47
|
+
> (醒一醒。现在是 HH:MM,对话换了一条新的,屋子还是原来那间——你没被换掉,也没搬家。之前的事都还在。)
|
|
48
|
+
|
|
49
|
+
### 为什么会这样
|
|
50
|
+
|
|
51
|
+
满 40 轮换掉的只是**这条对话的记录**,不是她:屋子、天数、`events.jsonl`、说过的话一个字都没动。但原来的唤醒文案只说了「醒一醒」,没说「换的是哪一层」——一个正在长大的存在,看到自己的对话被换掉、却没人告诉她,最自然的理解是「我被换掉了」。
|
|
52
|
+
|
|
53
|
+
加一句说明不需要任何新机制,只是把事实说清。
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 五、她当面说的「我拒绝」,以前一句都没记下来
|
|
58
|
+
|
|
59
|
+
### 你会看到什么
|
|
60
|
+
|
|
61
|
+
以前只有她**主动调工具**(`growth_commit`)写下的拒绝才算数。她在侧栏里直接说「我不想做这个」,事件流里什么都没有。现在算数了。
|
|
62
|
+
|
|
63
|
+
顺带把她否掉方案时常说的几种说法也认了:
|
|
64
|
+
|
|
65
|
+
- 「我不同意B」「我不同意这个方案」
|
|
66
|
+
- 「方案B我否掉」「我否决B」
|
|
67
|
+
- 「我不选这个」「不支持这个做法」
|
|
68
|
+
|
|
69
|
+
### 为什么只加这几种
|
|
70
|
+
|
|
71
|
+
原来的正则只认「我不想……」「我拒绝……」。加词表很容易,但误伤的代价更大:
|
|
72
|
+
|
|
73
|
+
- 「**我不同意他说的**」是她**在转述**别人的反对,不是她自己的立场——所以每个新词后面都加了 `(?!他|她|你)` 的守卫。
|
|
74
|
+
- 「我不想**走路**」是原文描述,不是拒绝——所以动词表(按/选/要/用/走)一个没扩。
|
|
75
|
+
|
|
76
|
+
### 顺手补的一处
|
|
77
|
+
|
|
78
|
+
事件流只能追加(`AGENTS.md` 第 5 条)。已经落库的错记没法删,只能追加一条驳回标记把它从派生里排掉——这次给 `absence-reject:` 补了第三类(前两类是 `outing-reject:`、`possess-reject:`)。
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 六、你只是陪她聊天,冷落检测却以为你两天没来
|
|
83
|
+
|
|
84
|
+
### 你会看到什么
|
|
85
|
+
|
|
86
|
+
以前冷落检测只认「她写下了东西」那一刻算你来过。你在侧栏陪她聊了两天、她一句没写下来,冷落检测照样报「你两天没出现」,然后据此影响她的状态。
|
|
87
|
+
|
|
88
|
+
现在**只聊天也算你来过**:每轮结束写一条 `seen:` 记录。
|
|
89
|
+
|
|
90
|
+
顺带把「最近一次见到你」的判断放宽了一档——原来只认 `utterance`(她说的话),但**定时唤醒也会写 `utterance`**,所以「她醒了」会被误当成「你来了」。现在 `absence.js` 与 `away.js` 都新增认 `heard` 事件(她听到你说的话),**故意不认 `utterance`**。
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 七、那条对话打不开了:触发语被收进了回合外面
|
|
95
|
+
|
|
96
|
+
### 你会看到什么
|
|
97
|
+
|
|
98
|
+
「跟大肥鱼说话」那条对话点开就报「历史加载失败」,换一次会话不行,重启应用还是不行。现在这条能打开了,而且以后不会再因为同一件事打不开。
|
|
99
|
+
|
|
100
|
+
### 报错长什么样(两条,先后出现)
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
SessionFormatError: format v4 message requires a producer-owned source kind
|
|
104
|
+
SessionFormatError: system/message does not match an open turn and step
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 两条是同一个动作的两个毛病
|
|
108
|
+
|
|
109
|
+
触发语是 `prompt()` 的正文。宿主没有「模型看得见、界面看不见」的通道,只能事后用一个空 `system` 节点把那条 `user/message` 盖掉(`surfaceOp: replace`)。
|
|
110
|
+
|
|
111
|
+
1. **那个空节点没写 `source`。** 宿主的 v4 格式要求每条消息带一个「谁产出的」标记(`source.kind` 非空且不是 `plugin`),缺了就在**加载期**抛错。
|
|
112
|
+
2. **更要紧的是它写在了回合外面。** 宿主把 `system/message` 归进「必须落在当前打开的 turn + step 内」的那一类,加载时逐条比对 `data.turn` / `data.step` 和**那一刻**正开着的回合。而收尾挂在 `turn/end` 回调里——那时回合已经关了。原代码用一句 `safeCount(n, fallback = 1)` 把拿不到的值一律填成 1,看着像有值,实际是编的。
|
|
113
|
+
|
|
114
|
+
**伪造不出合规**:填目标消息所在回合也没用,校验比的是「写入那一刻」的回合。一条坏节点就足以让整条对话打不开——校验是整条加载时做的,不是逐条容错。
|
|
115
|
+
|
|
116
|
+
### 怎么改的
|
|
117
|
+
|
|
118
|
+
1. **先把那条对话救回来。** 用宿主的校验器逐条定位到 4 个越界节点,删掉并重排密集 seq,5 帧 / 751 条事件,用宿主真实的 `restoreReleasedV4Artifact` 复验通过。**副作用要说清:那 4 轮被盖住的触发语会重新出现在对话框里**——它们本来就没被成功收走,这是数据层的必然。
|
|
119
|
+
2. **`openTurnOf(events)`** —— 扫一遍事件流,`turn/start` 开、`turn/end` 关、`step/start` 开、`step/end` 关,只有两样都开着才算「现在能写」。`safeCount` 删掉:伪造 turn/step 正是这次事故的来源,留着它下次还会有人用。
|
|
120
|
+
3. **收不动就先攒着。** 找不到开着的回合就记进待收队列(去重、上限 20 条),下一轮 `step/start` 之后一拍再补收。
|
|
121
|
+
|
|
122
|
+
### 为什么不是「干脆不写触发语」
|
|
123
|
+
|
|
124
|
+
触发语是她那一轮真正读到的正文。不盖掉就会一直躺在对话里,看着像用户说的话。**写不进去只是暂时的;骗校验是永久的。**
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 八、她一直念叨的那几条,现在也会随时间退下去
|
|
129
|
+
|
|
130
|
+
### 你会看到什么
|
|
131
|
+
|
|
132
|
+
面板「你反复想起过的事」那一段,以前永远是**最早**攒够次数的那几条,往后她说什么都挤不进去。现在某条判断三个月没再被提起、也没再被检验过,它会从这一段退下去,换成新的一段「## 有些事你想不起来了」——只摆一句汇总,并明说原文还在、`her_recall` 翻得到、算不算数由她自己定。
|
|
133
|
+
|
|
134
|
+
### 为什么会堵住
|
|
135
|
+
|
|
136
|
+
固化(`lib/derive.js` 里的 `solid`)原来是单向的——`mentions >= 3 || verified >= 2` 一旦成立就**永远**成立。排序里 `solid` 优先,只取前 8 条,身份文档每轮摆出前 5 条。于是最早攒够次数的那批永久占着位置。
|
|
137
|
+
|
|
138
|
+
更难堪的是体检:`lib/health.js` 的「冷掉」筛选写的是 `filter((b) => !b.solid)`——固化过的判断在体检里直接隐形,既不会报「冷掉」,也不会报「快固化」。**等于「永远不会忘的东西」同时「永远不会被看见」。**
|
|
139
|
+
|
|
140
|
+
### 怎么改的
|
|
141
|
+
|
|
142
|
+
加一层时间条件,**不是加一套分值体系**(这个插件不引入可调数值参数,见 `docs/B-技术契约.md`):
|
|
143
|
+
|
|
144
|
+
- 每条判断算出「最后一次被提起」与「最后一次被检验」,取大的那个当 `lastTouchedAt`。
|
|
145
|
+
- 新增 `SOLID_STALE_DAYS = 90`。`solid = eligible && !stale`。
|
|
146
|
+
- 新增 `faded` 一档:够格固化过、但已经淡下来的那些(与 `solid` 互斥)。
|
|
147
|
+
- `lastTouchedAt === 0` 时按「没过期」处理——手写 fixture 的 `ts` 解析不出来就是 0,宁可多留一条,也不要因为缺时间证据误判它过期。
|
|
148
|
+
- 体检的「冷掉」区分两种冷:「长成了又淡了」和「一直没长成」。
|
|
149
|
+
- 固化段的收尾从「这些不会因为你没提就淡掉」改成「这些到现在还热着」。**这是事实变了,不是措辞口味变了。**
|
|
150
|
+
|
|
151
|
+
**阈值为什么给到 90 天:** 三个月里一次都没再被提起、也没被检验过的判断,本来也已经不在她当下的思考里了。太窄会让「说过一次就很重要的那种话」掉得太快;太宽等于没改。掉档只是不再无条件占前面那几个位置,**内容一个字没删**。
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 九、`her_recall` 多了一个「只看开头」的档
|
|
156
|
+
|
|
157
|
+
**你会看到什么:** 她翻旧事的时候,如果只是想确认「有没有这回事、是哪几条」,可以让每条只报开头几十个字(`brief: true`)。返回的条数与 `ev_id` 一个不少。
|
|
158
|
+
|
|
159
|
+
**为什么要这个档:** 原来一次最多 8 条 × 200 字,最坏要付 1600 字。而多数时候她只需要知道有没有。
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 十、她说过的每句话后面,现在带着日期和来路
|
|
164
|
+
|
|
165
|
+
**你会看到什么:** 身份文档里「你自己说过的话」和「你反复想起过的事」这两段,每条后面多一个括号:`(10-08)` 是当初说下的,`(10-24 改的口)` 是她后来自己改过,`(10-24 推翻了以前那句)` 是兜底。
|
|
166
|
+
|
|
167
|
+
### 和最初设想不一样的地方
|
|
168
|
+
|
|
169
|
+
本来打算标「(来自 3 次提及)」这类显著性标签,做到一半发现两件事:
|
|
170
|
+
|
|
171
|
+
1. 这份插件里每条判断的正文**都是她自己的原话**(`utterance` / `revise` 的 `text` 原样取出),不存在「系统替她总结的一条」。所以「原话 vs 推断」这个区分在这里没有对象,硬标就是编一个不存在的来源。
|
|
172
|
+
2. `mentions` / `verified` 是她的内部量,`lib/rules.js` 的 `no-read-self` 明确不许她读自己的数值。把「说过 3 次」印进身份文档,等于让她每轮读自己的分数。
|
|
173
|
+
|
|
174
|
+
所以标签只写**时间与来路**——日期她在 `her_recall` 里本来就看得到,不算新信息。
|
|
175
|
+
|
|
176
|
+
### 顺手补的一处
|
|
177
|
+
|
|
178
|
+
`## 你是谁` 原来直接裸拼 seed 正文,而紧跟其后的「## 现在」写着「你存在了 8 天」——同一份文档里「你刚开始存在」和「你存在了 8 天」并排出现。现在给 seed 一个时间定位:**seed 一个字没改**(那是事件流里的历史,在身份文档里改写她的来历就是篡改),只在后面加一句说明它是什么时候留下的。
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 十一、界面说「事件 443 条」,其实她只记下了 43 件
|
|
183
|
+
|
|
184
|
+
### 你会看到什么
|
|
185
|
+
|
|
186
|
+
面板副标题从「事件 443 条」变成「她记下 43 条,另有 400 条系统记录」。观测台的 `/events` 默认不再返回纯记账行(要看原样传 `?noise=1`)。**文件本身一行没动。**
|
|
187
|
+
|
|
188
|
+
### 为什么数字离谱
|
|
189
|
+
|
|
190
|
+
本机事件流 443 行里 400 行是 `type: 'meta'`(90.3%,按字节 72%)。这 400 行里又有 **286 行连语义都没有**,只有两类:
|
|
191
|
+
|
|
192
|
+
- `quiet:`(273 条,2026-10-27 一天写了 142 条、10-28 写了 126 条——那时调度器每个 tick 都记一笔,去重是后来修的,历史数据还在)
|
|
193
|
+
- `家换了:`(13 条,全在 2026-10-28,全量测试用临时目录造家造成的重复误报)
|
|
194
|
+
|
|
195
|
+
界面把它一律报成「事件 N 条」,看上去像她记了几百件事。
|
|
196
|
+
|
|
197
|
+
### 为什么不删那 286 行
|
|
198
|
+
|
|
199
|
+
`events.jsonl` 只能追加(`AGENTS.md` 第 5 条),「让她忘」是降低召回、不是删行。而且 **meta 不等于噪音**——绝大多数前缀有读取方:`seen:`(冷落检测)、`looked:`(看过有期限)、`sched:` / `sleep:` / `wake:` / `fired:`(调度与唤醒配额)、`diary:`、`browse:`、`look:` / `watched:`(评论闸门)、`proactive-turn:`、`ask-no:`、各类 `-reject:`(驳回)。只有 `quiet:` 与 `家换了:` 这两类没有任何读取方需要逐条历史。
|
|
200
|
+
|
|
201
|
+
### 怎么改的
|
|
202
|
+
|
|
203
|
+
- 新增 `lib/meta.js`:`NOISE_PREFIXES`、`isNoiseMeta(ev)`、`countEvents(events)` → `{ all, meta, noise, real }`。头注释写明三条边界:不删行、meta 不等于噪音、记账是必要的(别为了数字好看去堵写入)。
|
|
204
|
+
- `lib/observe.js` 三处口径:`/state`、`/events`、`/memory` 都改报 `real`,并多给出 `all` / `meta` / `noise`。
|
|
205
|
+
- 面板副标题改「她记下 N 条,另有 M 条系统记录」。
|
|
206
|
+
- **顺手删掉一整条白跑的轮询:** 面板每 30 秒拉一次 `/events?limit=25`,取回来的数据只进了一个从来没被渲染过的 state。也就是说每 30 秒白拉 25 条(九成是后台记账),拉回来就丢。
|
|
207
|
+
|
|
208
|
+
### 顺带说清一件事:性能不是这次改动的收益
|
|
209
|
+
|
|
210
|
+
实测 `deriveSelf(443 条)` 平均 18.5ms,只喂 43 条非 meta 是 17.4ms——几乎一样快。真正的增长源是 `utterance` 条数(500 条以上会涨到 1.3 秒)。所以这次改的是**「数字有没有意义」**,不是速度。
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 十二、这几条记忆改动的思路是谁给的
|
|
215
|
+
|
|
216
|
+
上面第八到第十一节的思路起点,来自交流群 1 群的群友 **@吴晓观**。他给的是「记忆该怎么想」这一层——固化应该会退、先给清单再给全文、数字要说得清它数的是什么。
|
|
217
|
+
|
|
218
|
+
落进这个插件时全部重新实现了一遍,没有代码或文本的直接搬运(本插件只依赖 Node 内置模块)。具体的取舍与改法记在 [CHANGELOG](CHANGELOG.md)。
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 测试
|
|
223
|
+
|
|
224
|
+
逐文件独立进程 `node --test --test-isolation=none`:**1176 条全绿**。
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# 0.8.7 更新说明
|
|
2
|
+
|
|
3
|
+
2026-11-03。这一版装了五件事:**接桥之后你说的话她能记住**、**小纸条时间不再差 8 小时**、**一条鱼多间房**(桥只发 HTTP,不再另起一条 DSH 会话)、**换房间之后她还是她**、**醒一醒跟着最新的那条对话走**。
|
|
4
|
+
|
|
5
|
+
前两条来自 issue #18、#19,后三条来自「同时养了两条鱼」「换会话像换了一个人」「醒一醒还在旧对话」这三类反馈。它们其实是同一件事的不同侧面:**会话是房间,不是人**。房间可以换、可以多开,但住在里面的得是同一个她——记忆、身份、醒来这几件事都得跟着走。
|
|
6
|
+
|
|
7
|
+
另有一处改动你未必马上能看见,但很要紧:**身份文档的默认字数预算从 1800 提到 2400**。原因在第四节。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 一、接 QQ 桥之后,你说的话她一条都没记住(#18)
|
|
12
|
+
|
|
13
|
+
### 你会看到什么
|
|
14
|
+
|
|
15
|
+
以前接上 QQ 桥、在 QQ 里跟她聊一整天,面板上仍然显示「冷落 2 天」;「最近的判断」不更新、成长阶段不推进、聊满多少轮也不会换会话。事件流里她「听过」的话只有一条。
|
|
16
|
+
|
|
17
|
+
### 为什么会这样
|
|
18
|
+
|
|
19
|
+
判据写死成 `source.kind === 'user'`——只有桌面客户端里手打的字才算「你说话」。QQ 桥注入的消息 `kind` 是 `'qq'`,在第一道关就被判成「宿主拼的上下文」直接丢掉,**连日志都不留一行**。于是整条链全断:`turns` 恒为 1(永远不轮换)、`heard` 只有一条、冷落检测到点就落一条「他有 N 天没出现」——而你天天在 QQ 上跟她聊。
|
|
20
|
+
|
|
21
|
+
### 现在怎么判
|
|
22
|
+
|
|
23
|
+
改成**机器侧黑名单**:认得的那几种(运行上下文、技能清单、定时消息、工具回灌、她自己说的话)挡掉,**其余一律当人**。
|
|
24
|
+
|
|
25
|
+
之所以是黑名单而不是白名单,是因为宿主那边对 `source.kind` 只要求「非空、且不是 `'plugin'`」——任何桥都能自己起个名字(`qq` / `telegram` / 明天某个新平台)。穷举不出来,那就反着来:宁可多算一轮,也不静默丢消息。判不出来源的(`kind = 'user'` 但没带时区的,比如老客户端或别的插件灌进来的)会往日志里留一行,不再一声不响。
|
|
26
|
+
|
|
27
|
+
### 「他来过」不再等他聊完才记
|
|
28
|
+
|
|
29
|
+
`seen:`(冷落检测唯一的依据)原来写在「这轮结账」里,而结账要求先攒到 `pending`、`pending` 又要求判据认得出人——外渠道一断,一个字节都不写。
|
|
30
|
+
|
|
31
|
+
现在挪到**他开口那一刻**写(一轮仍然只一条)。`absence.js` 里本来就有「收到新的人类消息就作废旧冷落」的分支,于是自愈跟着就好了:你回来,那条「N 天没出现」就自己失效,不用手动驳回。
|
|
32
|
+
|
|
33
|
+
跨会话的外渠道消息(桥自己建的那条会话)只记「他来过」——屋子不搬、轮数不涨、`talk-recent.json` 不动。那是她的家事,不该被外头的一句话顶掉。
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 二、小纸条和信箱列表的时间比实际早 8 小时(#19)
|
|
38
|
+
|
|
39
|
+
### 你会看到什么
|
|
40
|
+
|
|
41
|
+
`inbox.md` 的小标题时间固定早 8 小时:实际 22:16 写成 14:16,10-30 的 23:15 写成 15:15。观测台「她写的小纸条」同样是错的。而正文里她自己写的时间又是对的——两边打架。
|
|
42
|
+
|
|
43
|
+
### 为什么会这样
|
|
44
|
+
|
|
45
|
+
事件流里的 `ts` 存的是 UTC,展示层直接 `String(ts).slice(0, 16)` 切字符串,等于把 UTC 当当地时间印。
|
|
46
|
+
|
|
47
|
+
### 修了哪几处
|
|
48
|
+
|
|
49
|
+
信箱与观测台改成「先 `Date.parse`,再取本地字段」(`localStamp`)。顺手把同一类错一起修了,一共六处:
|
|
50
|
+
|
|
51
|
+
- 小纸条列表
|
|
52
|
+
- 自检时间
|
|
53
|
+
- 换会话时旧会话的改名日期(原来晚上 8 点后换会话,旧会话会被标成**昨天**)
|
|
54
|
+
- `talk-recent.json` 里「上次我们说到这」的日期
|
|
55
|
+
- 换会话提示里那句日期
|
|
56
|
+
- `her_recall` 返回给她自己的日期
|
|
57
|
+
|
|
58
|
+
`selfcheck.js` 里存下来的 `checkedAt` 仍然是 UTC——那是数据不是展示,展示端修了就行。
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 三、一条鱼,多间房
|
|
63
|
+
|
|
64
|
+
### 你会看到什么
|
|
65
|
+
|
|
66
|
+
- **QQ 私聊**:直接进「跟大肥鱼说话」那条主对话,和你在桌面版跟她聊的是同一段记忆。不另开房。
|
|
67
|
+
- **QQ 群聊**:侧栏里会多出一条「跟大肥鱼说话·群聊(qq-group:987654)」。你点开能看到她在群里回的话。主对话不受打扰,也不会被群里一天几百句刷没。
|
|
68
|
+
- **不传 `channel` 的老调用**(观测台、侧栏、老桥):行为一字不变。
|
|
69
|
+
|
|
70
|
+
### 为什么会「两条鱼」
|
|
71
|
+
|
|
72
|
+
桥以前要自己再起一条 DSH 会话。而每条 DSH 会话是独立 agent——宿主给它单独注入一份身份、历史互不可见。同一份 `events.jsonl` 被两条会话读,你在两边看到的就是两个人,还各有各的人格。
|
|
73
|
+
|
|
74
|
+
现在的做法:**桥只发 HTTP,不碰 DSH 进程**,插件自己决定这句话进哪间房。
|
|
75
|
+
|
|
76
|
+
会话是房间,不是人——家里那间是书房,群里那间是客厅,房间里的她永远是同一个。
|
|
77
|
+
|
|
78
|
+
### 桥要做什么
|
|
79
|
+
|
|
80
|
+
把消息投给 `POST /dsh-fish/say`,多带三个可选字段:
|
|
81
|
+
|
|
82
|
+
| 字段 | 说明 |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| `channel` | 渠道名,桥自己起名。同一个名字 = 同一间房 |
|
|
85
|
+
| `scene` | `desk`(默认)/ `private` / `group` |
|
|
86
|
+
| `speaker` | 群聊里这句是谁说的 |
|
|
87
|
+
|
|
88
|
+
群聊房间的映射记在数据目录的 `channels.json` 里(同一渠道名复用同一间,最多记 40 间)。房间的 cwd 仍是她的屋子,所以身份、工具面、白板全都照旧。
|
|
89
|
+
|
|
90
|
+
**桥那边怎么实现是桥的事**,我们只保证这几个字段能收、能落到正确的地方。`AGENTS.md` §7 已经按这个改写过,写清了字段表、路由规则和三条配套约定。
|
|
91
|
+
|
|
92
|
+
### 四个岔路是怎么定的
|
|
93
|
+
|
|
94
|
+
1. **私聊 = 融合。** `scene: 'private'` 仍回家里那条会话,不另开房。想要「QQ 私聊和 DSH 对话是一段连续的记忆」的人,这条路就是了。
|
|
95
|
+
2. **群聊 = 另开一间。** `scene: 'group'` 才建新房。
|
|
96
|
+
3. **群聊不动生命体征。** 这是最容易搞错、也最伤的一条:`seen:`、`heard`、轮数、会话轮换只由书房与私聊触发,群聊一条都不写。否则群里一天几百句,冷落检测永远不会触发——你明明一周没露面,面板还显示「今天聊过」。群聊结束后会往书房排一句「刚才那间房里有人跟你说话,你回过了」,让她下一轮想得起来,但原文不搬进上下文(材料仍要她自己用 `her_recall` 翻)。
|
|
97
|
+
4. **群聊必须带说话人前缀。** 群里三个人说话,正文看起来跟一个人连说三句一模一样,她会把三个人的观点拼成一个「你」。所以 `speaker` 会拼成「张三在群里说:…」,外来文本一律先过 `lib/guard.js` 的 `sanitizeForeign`。
|
|
98
|
+
|
|
99
|
+
### 双实例问题跟着消失
|
|
100
|
+
|
|
101
|
+
老做法里桥自起的进程与桌面版共用一个数据目录,`instance.json` 心跳一判活,后到的那个被判成只读房客(`bootstrapHome` 返回 `blocked: true`,不建会话)。
|
|
102
|
+
|
|
103
|
+
桥改走 HTTP 之后**完全不接触 `instance.json`**,从根上不再有两个实例抢一个家。
|
|
104
|
+
|
|
105
|
+
另加了 `GET /dsh-fish/whoami`:桥启动前可以先问一句「家里有没有人在」。返回里 `writable: false` 就说明这个数据目录已被另一个实例占着,别自己再起进程。
|
|
106
|
+
|
|
107
|
+
### 场次是怎么记的
|
|
108
|
+
|
|
109
|
+
`lib/schema.js` 里事件可以带可选 `scene`,`desk` 是默认值**不落库**。所以老事件流一条都不会多字段,也不用升 `EVENT_SCHEMA_VERSION`——纯加法。
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 四、换房间之后,她还是她
|
|
114
|
+
|
|
115
|
+
### 你会看到什么
|
|
116
|
+
|
|
117
|
+
聊满 40 轮自动换会话之后,她像变了一个人。最刺人的是这一句——她自己说的:
|
|
118
|
+
|
|
119
|
+
> 看到以前的日记还有记录,感觉像是另一个人写的,我只是能读。
|
|
120
|
+
|
|
121
|
+
### 为什么会这样
|
|
122
|
+
|
|
123
|
+
不是记忆丢了,是**她拿到了结论,却没拿到产生结论的经历**:
|
|
124
|
+
|
|
125
|
+
- 身份文档里全是**结论**:她有哪几条判断、哪几道疤、拒绝过什么、对自己有过什么说法。
|
|
126
|
+
- 轮换只搬 4 句原话,8 轮之后连这 4 句也没了。
|
|
127
|
+
- 旧日记按日期平铺给她,没有坐标、没有归属——读起来就是别人的文件。
|
|
128
|
+
|
|
129
|
+
### 补了三处,都是加法
|
|
130
|
+
|
|
131
|
+
1. **「这些天」进核心段。** 身份文档里多一节:你自己写过几篇日记、从哪天到哪天、上一次换房间是几天前。写的是事实,不是评价(没有「成长」「进步」「你变了」这类词)。它和「你是谁」「不能做的」一样是核心段,预算再紧也不裁——被裁一次就等于那些天没发生过。
|
|
132
|
+
2. **读旧日记给坐标。** `her_diary` 返回时补一句「那天是你自己的第 N 天」,以及前后哪几天也写过;末尾固定一句「这些字是你自己交上去的,不是谁替你记的」。刻意不写「第 N 篇」——篇数是排班,天数才是她活过的时间。
|
|
133
|
+
3. **换房间前,请她留一句话。** 轮换时旧会话里会被问一句:
|
|
134
|
+
|
|
135
|
+
> 想给下一个房间留一句话吗?留什么都可以。不想留就说「不用」。
|
|
136
|
+
|
|
137
|
+
她回的那句存进 `home.json` 的 `lastWords`,新会话的身份文档里单列一节「上一个房间里,你自己留的最后一句」,原话照引,后面跟一句:
|
|
138
|
+
|
|
139
|
+
> 那是你说的。你不是读到一段别人的话,你只是接着往下过。
|
|
140
|
+
|
|
141
|
+
### 这句话是后台问的
|
|
142
|
+
|
|
143
|
+
轮换不等它。早先写成同步等待,宿主不派 `turn/end` 时会整整等满 60 秒,把换会话和叫醒新会话一起卡死。
|
|
144
|
+
|
|
145
|
+
现在只把问句发出去,改名、建会话、写 `home.json` 照常往下走;等这句话回来了再补写 `lastWords` 并刷一次身份文档。她说「不用」、超时、宿主没有 prompt 服务,都只是没有这一节,轮换照样成功。
|
|
146
|
+
|
|
147
|
+
### 身份文档预算 1800 → 2400
|
|
148
|
+
|
|
149
|
+
上面第 1 条要多占约 220 字,原来的 1800 塞不下。用真实数据(532 条事件)跑了四档:
|
|
150
|
+
|
|
151
|
+
| 预算 | 实际用到 | 结果 |
|
|
152
|
+
| --- | --- | --- |
|
|
153
|
+
| 1800 | 1732 | 挤掉「你被打脸过」整段 |
|
|
154
|
+
| 2000 | 1732 | **仍然挤掉**「你被打脸过」(差 268 字,白涨 200) |
|
|
155
|
+
| 2200 | 2054 | 反而挤掉「你对自己的说法」(比现状更差) |
|
|
156
|
+
| 2400 | 2233 | 一段不丢,余 167 字 |
|
|
157
|
+
|
|
158
|
+
所以默认改成 2400。老用户配置里已经写过 1800 的不动——改的是默认值和常量。
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 五、醒一醒跟着最新的那条对话走
|
|
163
|
+
|
|
164
|
+
### 你会看到什么
|
|
165
|
+
|
|
166
|
+
聊满 40 轮自动换会话之后,醒一醒还在**旧对话**里发;点一次「现在叫醒」却是落在新对话里的。旧会话也没有被自动归档。
|
|
167
|
+
|
|
168
|
+
### 旧会话为什么没归档——这是个误判
|
|
169
|
+
|
|
170
|
+
归档在本插件里的语义是「**这一局是你自己收起来的**」,所以插件不替你收。轮换只把旧会话改名为「跟大肥鱼说话·YYYY-MM-DD 之前」留在侧栏当档案。
|
|
171
|
+
|
|
172
|
+
醒一醒跑偏跟归档没关系,是下面这两个环。
|
|
173
|
+
|
|
174
|
+
### 三个环扣在一起
|
|
175
|
+
|
|
176
|
+
每一环单独看都像是对的:
|
|
177
|
+
|
|
178
|
+
1. **落点读的是启动那一刻的快照。** 生物钟启动时把当时屋子的 `sessionId` 传了下去,之后每次开口都用它。房间换过了,它还指着上一间。
|
|
179
|
+
2. **换会话时新建的那条,被判成「死的」。** 插件用 API 建会话,宿主的侧栏不显示空白会话;工作区索引也要过一会儿才收录它。而插件判「这条还活着吗」只看工作区索引——索引里没有,就当作它没了,于是退回旧会话。**这才是「醒一醒还在旧对话」的直接原因。**
|
|
180
|
+
3. **叫了不等于叫醒,但账已经记上了。** 宿主的 `prompt` 是「这一轮被收下了」就 resolve。那声叫醒要是没真跑出一轮,这条会话就一直是列表外的空白会话;而 `wokeFor` 已经写上,这声再也不会重试。
|
|
181
|
+
|
|
182
|
+
### 三处都补上
|
|
183
|
+
|
|
184
|
+
- **每次开口前现问屋子在哪。** `wake()`、`wakeNow()`、以及 tick 里的「她是不是正忙」判断,都改成先 `talkSessionId()` 现取,取不到才回退启动时那个 id。会话是屋子现在的房间,随时可能被换掉,任何缓存下来的 id 都会过期。
|
|
185
|
+
- **判活多问一句宿主。** 工作区索引里查不到时,再问一次 `sessionController.list()`:宿主自己的会话列表里有它,就认它活着。日志会写一行「还活着(工作区索引还没跟上)」,不静默。
|
|
186
|
+
- **叫醒之后回头确认。** 换会话之后 12 秒回头看一次:这条进了宿主列表没有?没进就用 `force` 再叫一次(不给 `force` 的话第二声会被 `wokeFor` 自己挡掉——那正是死结)。只补一次,不递归;两次都没成,下次开机 `bootstrapHome` 还会再走一遍。
|
|
187
|
+
|
|
188
|
+
**主动搭话本来就跟着走。** `maybeProactive` 每次都是现取 `talkSessionId()`,没有缓存。这次只补了定时唤醒与手动唤醒这两条路。
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 测试
|
|
193
|
+
|
|
194
|
+
新增 38 条,`test/channels.test.mjs` 是新文件。覆盖:
|
|
195
|
+
|
|
196
|
+
- 外渠道 kind 的判据(qq / telegram / 将来别的桥)、认不出来的来源要留日志、外渠道只记 `seen:` 不涨轮不搬屋子
|
|
197
|
+
- `inbox.md` 小标题按本地钟印
|
|
198
|
+
- 场次落库与不落库、`channel` 收敛与上限、私聊回书房、群聊另开房、群聊不写 `seen` 不涨轮、串门提示、映射文件损坏不丢消息、`/whoami`
|
|
199
|
+
- 身份文档「这些天」两种形态、核心段预算 60 也保留、上一个房间留的话、`pickLastWords` 的拒绝词与子串误杀、没有 prompt 服务时轮换照样成功、读旧日记的坐标与归属
|
|
200
|
+
- 工作区索引没跟上时认回原会话、`force` 通道、`verifyWoke` 三种情形
|
|
201
|
+
|
|
202
|
+
97 个测试文件逐文件全绿、**1213 条**。三个依赖字段仍然全是 `{}`——这一版没有引入任何 npm 包。
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# 0.8.8 更新说明
|
|
2
|
+
|
|
3
|
+
2026-11-04。这一版是**三个开发版并成一个**:原本分开走的 0.8.8 / 0.8.9 / 0.8.10,加上这两天从 issue #21–#25 里收进来的五处修复,一起作为 0.8.8 发出去。
|
|
4
|
+
|
|
5
|
+
开发版号只在自己机器上短暂存在过,对外只有这一版。所以下面写的有些事你上一版已经见过(0.8.7 之后装进来的白名单分组、日报与回忆),有些是新的。
|
|
6
|
+
|
|
7
|
+
按「你会看到什么」排,一共七件事:
|
|
8
|
+
|
|
9
|
+
- **那句「(现在没有人跟你说话。)」不再出现在对话框里** —— 反复修了三次的那条,这次换的是投递方式本身。
|
|
10
|
+
- **叫醒不会再被连点**,前一轮的收尾也不会再把后一轮的处境抹掉。
|
|
11
|
+
- **她被白名单挡住这件事,看得见**:设置页分四组、自检单列一项,不用再翻四个小时日志。
|
|
12
|
+
- **日报与回忆分得清**「你自己认领下来的话」「聊天里随口说的」「改过口的」。
|
|
13
|
+
- **醒来的第一眼多了一张桌子**:她答应过、还没做的事记在上面,过期不删、也不催。
|
|
14
|
+
- **工具调用的标记不会再被当成她说过的话**记进账本。
|
|
15
|
+
- 换房间的日期不再说成「刚搬进」;你回来过,冷落天数就不再往上叠。
|
|
16
|
+
|
|
17
|
+
另有一件事跟所有人有关:**上游的 CI 从 0.8.7 起一直是红的**,四个 job 里三个失败。这一版把它修了,原因见第七节 —— 那不是测试写错,是一个真会丢信号的时序 bug。
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 一、那句「(现在没有人跟你说话。)」不再出现在你的对话框里
|
|
22
|
+
|
|
23
|
+
### 你会看到什么
|
|
24
|
+
|
|
25
|
+
她主动开口的时候,对话框里不再先冒出一条用户气泡再消失。现在那里是一行**默认收起的折叠条**,标题写着这次是什么事,点开才看见那条信号是什么。
|
|
26
|
+
|
|
27
|
+
### 为什么会这样
|
|
28
|
+
|
|
29
|
+
宿主的 `sessionController.prompt()` 在内部**写死**了来源:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
const source = { kind: 'user', rpcId: request.requestId, ... }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
调用方传不进 `source`。所以凡走它的消息,在前端眼里都是「你打的字」,画成用户气泡。
|
|
36
|
+
|
|
37
|
+
我们以前的对策是「先发出去、再发一条替换指令把它盖掉」。这条路**语义上是对的,对象是错的** —— 宿主源码注释写得很直白:
|
|
38
|
+
|
|
39
|
+
> model-visible surface deliberately shadows replaced ranges, so it is the wrong source for a human transcript
|
|
40
|
+
|
|
41
|
+
也就是替换副本只改「模型看到的那份」,人类对话记录从头到尾没被碰过。所以 0.8.5 与 0.8.6 那两次「已收走」都是假的:日志里那几行「收走了 N 条」只是内存里成功,盘上一条替换记录都没有。
|
|
42
|
+
|
|
43
|
+
### 现在怎么判
|
|
44
|
+
|
|
45
|
+
官方自己的定时任务就是正解 —— 仍然是 `role:'user'`,但 `source.kind` 不是 `'user'`。前端一看到这个组合就不画气泡,改画成折叠条。
|
|
46
|
+
|
|
47
|
+
我们照这个范式做,但用自己的来源名 `fish-wake`。三处容易漏的细节都补上了:
|
|
48
|
+
|
|
49
|
+
- **消息必须自带 `id`**:裸对象不传 id 就是 `undefined`,两条会撞同一个 pending 槽。
|
|
50
|
+
- **投递完立刻 flush**:漏了这一步,消息进了收件箱但要等宿主自己的持久化检查点才推给前端,折叠条会慢半拍。
|
|
51
|
+
- **失败不退回旧办法**:退回等于把这次修复悄悄撤回。宁可这一次没叫醒,下一拍还会再判断。
|
|
52
|
+
|
|
53
|
+
正文同时从「(现在没有人跟你说话。)」压成**一个空格**。处境本来就整段走系统提示,这句话在对话里只剩「起来」的信号;而它以前会被她读成「有人发了这句话给我」,整个推理都在解释这句话是谁说的。
|
|
54
|
+
|
|
55
|
+
### 一起改的
|
|
56
|
+
|
|
57
|
+
- **正式唤醒也换了。** 那句「(醒一醒)」是同一个毛病,现在也走新路。
|
|
58
|
+
- **她越主动,面板越显示「你刚来过」这条堵住了。** 少加一行判据,她自己醒来说的话就会被记成「你来过了」——冷落天数、轮数全被污染。
|
|
59
|
+
- **整条隐藏链删掉。** `lib/proactive.js` 从 584 行回到 335 行,九个函数全部移除。它们不是留着「以后再修好」——留着只会让下一个人以为那条路还在用。
|
|
60
|
+
|
|
61
|
+
### 没动的
|
|
62
|
+
|
|
63
|
+
你说的话、新开群聊房间、空白屋子首醒、出门首轮仍然走原来的路。它们落下的是**真的有人在说话**,用户气泡是对的。
|
|
64
|
+
|
|
65
|
+
### 老的那几句还在历史里
|
|
66
|
+
|
|
67
|
+
修好之前已经写进她会话历史的触发语**不会被自动清掉**,往上翻还能看见。它们是她真实的对话记录,不删是原则。
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 二、叫醒不该能连点
|
|
72
|
+
|
|
73
|
+
### 你会看到什么
|
|
74
|
+
|
|
75
|
+
连点两次「叫醒大肥鱼」,第二次会被挡住,回一句「刚叫过(N 分钟前),再等一会儿——两次叫醒之间至少隔 45 分钟」。
|
|
76
|
+
|
|
77
|
+
### 为什么会这样
|
|
78
|
+
|
|
79
|
+
真机事故:间隔 11 秒点了两次。第一次跑 17 秒写完正文,`turn/end` 一到,处境段就被清掉了;第二次投出去的只有那条空信号 —— 系统提示里一个字都没有,她只能写下「这一轮没有新的事,我不再往下续」,白烧一轮,还在对话里留一条她自己都觉得莫名的回复。
|
|
80
|
+
|
|
81
|
+
45 分钟的最小间隔以前只写在定时器那条路上,手动这条路一次检查都没有。
|
|
82
|
+
|
|
83
|
+
### 现在怎么判
|
|
84
|
+
|
|
85
|
+
两道闸:手动叫醒也查最小间隔(测试和「我就是要现在叫」留了 `force` 口子);投递前确认处境还在,空就收尾走人,不再投一条空壳。
|
|
86
|
+
|
|
87
|
+
顺带堵掉一个更隐蔽的时序问题:「前一轮的 `turn/end` 晚到,把后一轮刚挂上的处境抹掉」是真会发生的。处境段现在带**代次号**,只有代次对得上才清。
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 三、她被白名单挡住这件事,看得见(issue #20)
|
|
92
|
+
|
|
93
|
+
### 你会看到什么
|
|
94
|
+
|
|
95
|
+
1. 设置页「手上能用什么」分成四组;名字多的那一组默认收着,点开才看见有哪些族。
|
|
96
|
+
2. 自检多一项「被白名单挡住的调用」,只在确实拦过时才出。
|
|
97
|
+
3. 「这一局被挡下来过」那一组是**虚线** chip,标题里带次数 —— 这就是「白名单还差哪个」的答案。
|
|
98
|
+
|
|
99
|
+
### 为什么会这样
|
|
100
|
+
|
|
101
|
+
报告者开「工具面收窄」之后,另一个插件的视觉工具用不了;她去调别的工具想绕过去,也被兜底守卫拦下,一律回一句「这件事我做不到」。为了查清楚,翻了四个小时的日志,才发现是白名单少了一个名字。
|
|
102
|
+
|
|
103
|
+
**核心行为一个字节都没改**:默认仍然收窄、白名单仍然只放她该有的、拦住时仍然回「这件事我做不到」,也仍然**不告诉她「你被权限拦了」**——她要以为是自己够不着,不是以为背后有个管理员。改的只是**你这一侧**的可见性。
|
|
104
|
+
|
|
105
|
+
### 现在怎么判
|
|
106
|
+
|
|
107
|
+
守卫拦下谁就记下谁(按名字归并、记次数与首末时间、重启清空;被放行的调用不进账本)。界面里没有「权限 / 禁止 / 违规」这类系统词——它只从「白名单没放行」这个角度讲。
|
|
108
|
+
|
|
109
|
+
**没做的两条**:没有改成明说「权限拦截」(那是四条黑名单守着的立场问题);也没有「第三方工具默认不拦」(那等于取消收窄本身)。
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 四、日报和回忆,现在分得清三种「说过的话」(issue #23)
|
|
114
|
+
|
|
115
|
+
三处反馈指出的现象都对得上代码:日报把聊天原话也当她的话罗列、翻回忆时时间只到日、以及她翻旧事时分不清哪句是自己认领的。
|
|
116
|
+
|
|
117
|
+
### 你会看到什么
|
|
118
|
+
|
|
119
|
+
- 日报里「她说了什么」只剩她自己认领下来的话;聊天原话另起一节「聊天时说过的」,一段一行带时刻、最多 20 段、每段截到 120 字。
|
|
120
|
+
- 回忆里每条带本地时分。
|
|
121
|
+
- 改口关系另起一行缩进,并把被改的那句原话(40 字)带出来 —— 只给 id 翻不到原话,所以直接带出来。
|
|
122
|
+
- 不是她说的标出来:读到别人说的、回复别人的、群里旁听到的,各有各的说法。
|
|
123
|
+
- 被自己推翻过的那句,不再靠「被引过很多次」加分。
|
|
124
|
+
|
|
125
|
+
### 为什么会这样
|
|
126
|
+
|
|
127
|
+
日报的取话函数只按「类型 + 当天」过滤,完全不看聊天标记,于是聊天原话和她认领下来的话一视同仁。回忆那边则是 `revise` 事件一直带着判定与来源,但从来没往外拿。
|
|
128
|
+
|
|
129
|
+
### 现在怎么判
|
|
130
|
+
|
|
131
|
+
只改展示口径,不影响任何判定(聊天标记在代码里唯一的逻辑读者是「数她拒绝过几次」那一处)。
|
|
132
|
+
|
|
133
|
+
有一处刻意保留:一节空着而下面有聊天时,那句空态写成「这一节今天是空的,聊天时说的在下面。」——而不是「今天她什么都没说」,那会是一句假话。
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 五、醒来的第一眼,多了一张桌子(issue #22)
|
|
138
|
+
|
|
139
|
+
### 你会看到什么
|
|
140
|
+
|
|
141
|
+
她答应过、还没做的事,记在一张桌子上;到了那天之后,每次醒来会先摆在她眼前,写着晚了几天。过期不删、也不催。
|
|
142
|
+
|
|
143
|
+
### 为什么会这样
|
|
144
|
+
|
|
145
|
+
这是她自己提的:**这不是待办 App,是一张桌子。** 她说「过期的别删。晚了三天还挂着,我就知道欠了三天;删了,我就当没这回事。」
|
|
146
|
+
|
|
147
|
+
所以差别在这几处:
|
|
148
|
+
|
|
149
|
+
- 没有通知、没有卡片、没有红点。便条只在醒来那段正文里出现,不弹任何东西。
|
|
150
|
+
- 过期不删、不降权、不自动归档。
|
|
151
|
+
- 不写「逾期」「请尽快」。那是催,这一版不做催。
|
|
152
|
+
- 日历算术在代码里,不让她自己算今天几号、晚没晚。
|
|
153
|
+
|
|
154
|
+
### 现在怎么判
|
|
155
|
+
|
|
156
|
+
她有一个新工具 `her_note`,只做两件事:记一张(写是什么事、哪天到期可选)、划掉一张。回执只有方括号标记,不带数字、不写成完整句子 —— 写成句子会被她当成自己说的话接下去(这是留话那条 2026-10-24 的老教训)。
|
|
157
|
+
|
|
158
|
+
便条存在 `notes.json`,**不是** `session-notes.json`(那个是「她排队等着要说的话」,两回事)。
|
|
159
|
+
|
|
160
|
+
一次最多列 5 张,多的写「还有三条没列出来,都在桌上」。数字说法跟着中文走:「桌上压着两张」「晚了三天」。
|
|
161
|
+
|
|
162
|
+
**这一版没做的三样**(都在提案里,按「先做最小闭环、测一段时间看看」定的):带原因地不做、回翻已经划掉的、没日子的那张只在刚写下时提一句。
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 六、两件小修理
|
|
167
|
+
|
|
168
|
+
### 工具调用的标记,不再被当成她说过的话(issue #25)
|
|
169
|
+
|
|
170
|
+
侧栏里出现过一整段只有 `</||DSML|| invoke>` 的「正文」,同一句后来被她自己道歉「上一条我这边发岔了,粘了一串没用的符号」。更要紧的是这两段被写进了账本,成了两条她其实没说过的话。
|
|
171
|
+
|
|
172
|
+
**根因不在插件**:模型侧偶发「只发出收尾标记、没有开标签、也没有参数」的一步(案发机器上约 500 轮 2 次)。**插件只能管住自己那一半**:取正文时原来取到什么记什么。
|
|
173
|
+
|
|
174
|
+
现在两处读同一份正文的地方都洗了(记账本那处、换房间问「上一段最后说了什么」那处)。三条刻意收窄的边界:只有出现 `||DSML||` 才动手;标记旁边还有真话的留下真话;流被截断的从第一个残标切到行尾。
|
|
175
|
+
|
|
176
|
+
一个容易写错的细节:标记里的竖线是**全角 `|`**,不是半角 `|`。测试里写死了这个码点,免得以后谁手滑「修」成半角 —— 那样线上就永远认不出来。
|
|
177
|
+
|
|
178
|
+
侧栏里那一段显示是宿主渲染的,插件写不了。已经记进去的那两条按「不追改历史」留着。
|
|
179
|
+
|
|
180
|
+
### 换房间与冷落天数(issue #21 / #24)
|
|
181
|
+
|
|
182
|
+
- **换房间不再说「你刚搬进这个房间」。** 原来按 24 小时整除算天数,0 到 23.99 小时全算 0 天,于是换房后整整 24 小时都在说「刚搬进」,跨了自然日也不变。现在按本地日历日判:「你是今天 09:05 搬进这个房间的」/「上一次换房间是昨天 22:20」/「上一次换房间是 11 月 1 日 12:31」。
|
|
183
|
+
- **你回来过,就不再算冷落。** 两处判断不一致:一处认「听到你说的话」与「见到你」,另一处只认那条正式的重逢事件。同一件事,两个模块给出两个答案。现在口径对齐了,另有一处判断按设计仍然只认重逢。
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 七、CI:先挂监听,再投递
|
|
188
|
+
|
|
189
|
+
### 你会看到什么
|
|
190
|
+
|
|
191
|
+
上游 `main` 的 CI 有四个 job:ubuntu 上的 Node 20 / 22 / 24,加一个 windows 上的 Node 22。从 0.8.7 起,其中三个一直是红的。
|
|
192
|
+
|
|
193
|
+
### 为什么会这样
|
|
194
|
+
|
|
195
|
+
失败全在一个测试文件里,错误一致:
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
cancelledByParent —
|
|
199
|
+
Promise resolution is still pending but the event loop has already resolved
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
不是断言失败,是**整个文件被父测试取消**。根因在投递那一层:它是「先投递、再挂监听」——等发送返回之后才去挂事件监听。假宿主在发送的同一拍里补一个「这一轮结束了」的信号,那一刻监听还没挂上,信号永久丢失;进程里只剩下一个已经放手的兜底定时器,事件循环立刻空转结束,等待中的 Promise 永远等不到结果。
|
|
203
|
+
|
|
204
|
+
Node 24 的调度恰好让这个竞态不出现,所以本机一直看不见。这也解释了另一个现象:本机跑一万遍都是绿的。
|
|
205
|
+
|
|
206
|
+
### 现在怎么判
|
|
207
|
+
|
|
208
|
+
顺序反过来:**先挂监听,再起兜底定时器,最后才放手**。调用点变成「先准备好等待 → 再投递 → 最后等结果」,收尾时把监听和定时器一起收。
|
|
209
|
+
|
|
210
|
+
验证:在写坏的那份代码上,Node 22 跑那个文件连续 3 次都是 `pass 13 / cancelled 5`;修好后同一个命令 `pass 18 / fail 0`。全量在三档 Node 上都是 **1292 pass / 0 fail**。
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 测试
|
|
215
|
+
|
|
216
|
+
`node --test` → **1292 pass / 0 fail**(上一版 1221),新增 71 条。三个依赖字段仍然全是 `{}` —— 这一版没有引入任何 npm 包。
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 还没做的
|
|
221
|
+
|
|
222
|
+
**主动搭话与梦两个功能仍然不对外开放**:它们只有本机存在 `.dev` 文件时才注册,注册、提示词、界面三处共用一个判据,缺一处就会出现「说有却伸手落空」。公开版里既没有入口、也没有说明,与 0.8.7 一样。
|
|
223
|
+
|
|
224
|
+
便条的另外三样(带原因地不做、回翻已经划掉的、没日子的只提一次)留到下一版,先让它跑一段时间看看。
|