mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Merge upstream/main: resolve format-only conflicts
Adopt upstream multi-line formatting for chat-history.js and tests/chat-history.mjs. Also brings in upstream .gitignore, settleExtractionStatusAfterHistoryRecovery null-safety fix, and getSmartTriggerDecision index-aware extraction filtering. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,353 +0,0 @@
|
|||||||
# ST-BME:旧楼层隐藏解耦 + Timer Blocker 修复计划
|
|
||||||
|
|
||||||
## 这份计划是给谁看的
|
|
||||||
|
|
||||||
给接手这件事的另一个 AI / 开发者快速建立上下文用。
|
|
||||||
目标是让对方**不用再向用户追问背景**,直接理解:
|
|
||||||
|
|
||||||
1. 用户真正想解决的痛点是什么
|
|
||||||
2. 目前代码已经修到哪一步了
|
|
||||||
3. 当前还卡着哪些 blocker
|
|
||||||
4. 下一步该按什么顺序、以多保守的方式去改
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 用户的真实痛点
|
|
||||||
|
|
||||||
用户要的不是单纯“修一个 bug”,而是下面这套行为最终成立:
|
|
||||||
|
|
||||||
1. **主 AI 节省 token**
|
|
||||||
旧楼层自动隐藏,主 AI 不再看到太老的消息
|
|
||||||
2. **BME 仍能正常提取**
|
|
||||||
即使旧楼层被隐藏,BME 也还能按固定上下文窗口读取所需消息,不会因为隐藏而读不到
|
|
||||||
3. **不要再因为隐藏状态变化误触发历史恢复**
|
|
||||||
4. **不要让 `is_system` 成为隐藏系统与提取系统之间的耦合桥梁**
|
|
||||||
|
|
||||||
用户想要的理想设计可以概括成一句话:
|
|
||||||
|
|
||||||
> 旧楼层隐藏只负责 `/hide` / `/unhide`,BME 提取自己按固定窗口读消息,两套逻辑解耦。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 当前已经确认的事实
|
|
||||||
|
|
||||||
### 1. 当前隐藏逻辑不是纯 `/hide`
|
|
||||||
|
|
||||||
当前仓库里的旧楼层隐藏是**双轨**:
|
|
||||||
|
|
||||||
1. 调宿主 `/hide N-M` / `/unhide N-M`
|
|
||||||
2. 同时本地改 `message.is_system`
|
|
||||||
3. 同时同步 DOM 上的 `is_system` attribute
|
|
||||||
|
|
||||||
证据:
|
|
||||||
|
|
||||||
- [hide-engine.js](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\hide-engine.js)
|
|
||||||
- `markManagedSystemRange`
|
|
||||||
- `restoreManagedSystemFlags`
|
|
||||||
- `syncSystemAttribute`
|
|
||||||
- `runHideApply`
|
|
||||||
|
|
||||||
### 2. 当前 BME 提取链路确实会按 `is_system` 跳过消息
|
|
||||||
|
|
||||||
这点非常关键,说明“只删隐藏引擎里的 `is_system` 双写”还不够。
|
|
||||||
|
|
||||||
证据:
|
|
||||||
|
|
||||||
- [chat-history.js](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\chat-history.js)
|
|
||||||
- `isAssistantChatMessage(message) => !message.is_user && !message.is_system`
|
|
||||||
- `buildExtractionMessages(...)` 中 `if (msg.is_system) continue`
|
|
||||||
- playableSeq / assistant floor 映射里也会跳过 `is_system`
|
|
||||||
- [index.js](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\index.js)
|
|
||||||
- 例如 `pendingMessages = ...filter((msg) => !msg.is_system)`
|
|
||||||
|
|
||||||
结论:
|
|
||||||
|
|
||||||
> 当前 BME 的“可提取消息集合”仍然受 `is_system` 影响。
|
|
||||||
|
|
||||||
### 3. 历史 hash 误恢复问题已经基本修过一轮
|
|
||||||
|
|
||||||
已经做过的修复:
|
|
||||||
|
|
||||||
1. `buildMessageHash` 已不再把 `isSystem` 计入 hash
|
|
||||||
2. 已加入 `processedMessageHashVersion` 迁移逻辑
|
|
||||||
3. 之前“隐藏状态变化 -> hash 脏 -> 误触发历史恢复”的链路,实测已明显缓解
|
|
||||||
|
|
||||||
证据:
|
|
||||||
|
|
||||||
- [runtime-state.js](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\runtime-state.js)
|
|
||||||
- [index.js](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\index.js)
|
|
||||||
|
|
||||||
结论:
|
|
||||||
|
|
||||||
> 现在“是否能做纯 `/hide` 设计”的主要阻碍,已经不再是 hash,而是提取链路仍依赖 `is_system`。
|
|
||||||
|
|
||||||
### 4. 当前还有独立的事件层 blocker,导致测试结果会被污染
|
|
||||||
|
|
||||||
已发现两类 `Illegal invocation`:
|
|
||||||
|
|
||||||
1. `MESSAGE_RECEIVED` 路径里的 `queueMicrotask` 借壳调用问题
|
|
||||||
2. `CHAT_CHANGED` 路径里的 `clearTimeout` 借壳调用问题
|
|
||||||
|
|
||||||
用户最新测试显示:
|
|
||||||
|
|
||||||
- 新聊天刚切换时就在 `onChatChangedController` 崩掉
|
|
||||||
- 面板出现“等待图谱加载”
|
|
||||||
- 这种状态下继续测自动提取 / 隐藏逻辑,结论不干净
|
|
||||||
|
|
||||||
证据:
|
|
||||||
|
|
||||||
- [event-binding.js](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\event-binding.js)
|
|
||||||
- `onChatChangedController`
|
|
||||||
- `scheduleSendIntentHookRetryController`
|
|
||||||
- `onMessageReceivedController`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 用户当前最关心的两个问题
|
|
||||||
|
|
||||||
### 问题 A:最终能不能做成“只 `/hide`,不碰 `is_system`”?
|
|
||||||
|
|
||||||
答案:
|
|
||||||
|
|
||||||
**可以朝这个方向改,但不能只改 hide-engine。**
|
|
||||||
|
|
||||||
如果只删:
|
|
||||||
|
|
||||||
- `markManagedSystemRange`
|
|
||||||
- `restoreManagedSystemFlags`
|
|
||||||
|
|
||||||
而不改提取链路,那么当前 BME 仍然可能因为:
|
|
||||||
|
|
||||||
1. 宿主 `/hide` 自己修改了 `is_system`
|
|
||||||
2. 或者 BME 当前仍按 `is_system` 过滤消息
|
|
||||||
|
|
||||||
而导致:
|
|
||||||
|
|
||||||
- 提取读不到被隐藏楼层
|
|
||||||
- assistant turn 识别错位
|
|
||||||
- 上下文窗口与可见消息集合混在一起
|
|
||||||
|
|
||||||
### 问题 B:现在能不能继续测聊天行为?
|
|
||||||
|
|
||||||
答案:
|
|
||||||
|
|
||||||
**不建议。**
|
|
||||||
|
|
||||||
因为 `onChatChangedController` 当前还有 `Illegal invocation`,会污染新聊天初始化流程。
|
|
||||||
在这个 blocker 修掉之前,继续测:
|
|
||||||
|
|
||||||
- 自动提取是否触发
|
|
||||||
- 图谱加载是否正常
|
|
||||||
- 隐藏与提取是否协同
|
|
||||||
|
|
||||||
得到的结果都不可靠。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 对另一个 AI 的核心提醒
|
|
||||||
|
|
||||||
### 不要误判为“只要去掉 hide-engine 的 `is_system` 双写就结束了”
|
|
||||||
|
|
||||||
真正需要拆开的,是两件事:
|
|
||||||
|
|
||||||
1. **主 AI 的上下文可见性**
|
|
||||||
由 `/hide` / `/unhide` 控制
|
|
||||||
2. **BME 提取的上下文读取**
|
|
||||||
应由 `extractContextTurns` 等窗口逻辑控制
|
|
||||||
|
|
||||||
当前代码里,这两件事都还部分依赖 `is_system`,所以必须一起梳理。
|
|
||||||
|
|
||||||
### 不要误判为“继续沿用 `runtime.clearTimeout(...)` 直接调用没问题”
|
|
||||||
|
|
||||||
当前已经出现实证:
|
|
||||||
|
|
||||||
- `queueMicrotask` 直接借 runtime 调用会触发 `Illegal invocation`
|
|
||||||
- `clearTimeout` 直接借 runtime 调用也会触发 `Illegal invocation`
|
|
||||||
|
|
||||||
说明这些原生 API 不适合直接裸调 runtime 透传引用。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 推荐方案总览
|
|
||||||
|
|
||||||
建议按两个阶段推进,而不是混成一个大改:
|
|
||||||
|
|
||||||
### 阶段 1:先清理事件层 blocker,恢复干净测试环境
|
|
||||||
|
|
||||||
#### 目标
|
|
||||||
|
|
||||||
修掉新聊天 / 收消息路径里的 `Illegal invocation`,确保后续功能测试有效。
|
|
||||||
|
|
||||||
#### 推荐修法
|
|
||||||
|
|
||||||
不要粗暴把所有 `runtime.setTimeout/clearTimeout` 改成 `globalThis.*`。
|
|
||||||
更稳的做法是:
|
|
||||||
|
|
||||||
1. 在 [event-binding.js](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\event-binding.js) 中引入一个本地 timer wrapper
|
|
||||||
2. 模式与 [hide-engine.js](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\hide-engine.js) 的 `getTimerApi()` 保持一致
|
|
||||||
3. 继续优先使用 runtime 提供的 timer
|
|
||||||
4. 但通过 `Reflect.apply(..., globalThis, args)` 安全调用
|
|
||||||
|
|
||||||
#### 理由
|
|
||||||
|
|
||||||
这样可以同时保留:
|
|
||||||
|
|
||||||
1. runtime 注入 timer 的可测试性 / 可替换性
|
|
||||||
2. 避免 `Illegal invocation`
|
|
||||||
3. 与仓库现有模式一致,降低风格分裂
|
|
||||||
|
|
||||||
#### 阶段 1 需要修改的点
|
|
||||||
|
|
||||||
- [event-binding.js](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\event-binding.js)
|
|
||||||
- 新增本地 `getTimerApi(runtime)` 或等价 helper
|
|
||||||
- `scheduleSendIntentHookRetryController`
|
|
||||||
- `onChatChangedController`
|
|
||||||
- 如有其他 runtime timer 裸调,也一起替换
|
|
||||||
|
|
||||||
#### 阶段 1 验收标准
|
|
||||||
|
|
||||||
1. 新开聊天不再报 `onChatChangedController ... Illegal invocation`
|
|
||||||
2. 收到 assistant 消息时不再报 `onMessageReceivedController ... Illegal invocation`
|
|
||||||
3. 新聊天可以正常进入图谱加载 / 自动提取链路
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 阶段 2:推进“纯 `/hide` + 提取解耦”
|
|
||||||
|
|
||||||
#### 目标
|
|
||||||
|
|
||||||
最终实现:
|
|
||||||
|
|
||||||
1. 隐藏系统只负责主 AI 可见性
|
|
||||||
2. BME 提取系统只负责按窗口读取上下文
|
|
||||||
3. `is_system` 不再是两者之间的耦合信号
|
|
||||||
|
|
||||||
#### 先做的确认
|
|
||||||
|
|
||||||
需要先确认宿主 ST 的 `/hide` / `/unhide` 真实语义:
|
|
||||||
|
|
||||||
1. `/hide` 是否会改消息对象的 `is_system`
|
|
||||||
2. `/unhide` 是否会恢复
|
|
||||||
3. 变化是 UI 层面的,还是底层 chat 数据层面的
|
|
||||||
|
|
||||||
这个确认很重要,因为它决定:
|
|
||||||
|
|
||||||
- 纯 `/hide` 后 BME 是否仍会在当前实现下跳过被隐藏消息
|
|
||||||
|
|
||||||
#### 改造顺序
|
|
||||||
|
|
||||||
##### 步骤 2-1:清点所有“提取链路按 `is_system` 过滤消息”的位置
|
|
||||||
|
|
||||||
重点:
|
|
||||||
|
|
||||||
- [chat-history.js](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\chat-history.js)
|
|
||||||
- `isAssistantChatMessage`
|
|
||||||
- `getAssistantTurns`
|
|
||||||
- `buildExtractionMessages`
|
|
||||||
- playableSeq / assistantSeq 映射
|
|
||||||
- [index.js](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\index.js)
|
|
||||||
- 所有影响 extraction 的 `!msg.is_system` 过滤
|
|
||||||
|
|
||||||
##### 步骤 2-2:把 extraction 上下文改成按窗口/索引读取
|
|
||||||
|
|
||||||
原则:
|
|
||||||
|
|
||||||
1. extraction 读取“聊天真实楼层窗口”
|
|
||||||
2. 上下文范围由 `extractContextTurns` 控制
|
|
||||||
3. 不再把“是否 hidden/system”当成提取可见性的主判据
|
|
||||||
|
|
||||||
##### 步骤 2-3:重新定义 assistant turn 识别
|
|
||||||
|
|
||||||
当前 assistant turn 识别依赖:
|
|
||||||
|
|
||||||
- `!message.is_user && !message.is_system`
|
|
||||||
|
|
||||||
这会把被隐藏的 assistant 楼层排除掉。
|
|
||||||
需要把“真正系统消息”和“被隐藏的普通历史楼层”区分开来。
|
|
||||||
|
|
||||||
##### 步骤 2-4:在确认 extraction 已解耦后,再收敛 hide-engine
|
|
||||||
|
|
||||||
届时才安全移除:
|
|
||||||
|
|
||||||
- `markManagedSystemRange`
|
|
||||||
- `restoreManagedSystemFlags`
|
|
||||||
- `syncSystemAttribute`
|
|
||||||
- `__st_bme_hide_managed` 相关逻辑
|
|
||||||
|
|
||||||
让 hide-engine 回归为:
|
|
||||||
|
|
||||||
1. 计算范围
|
|
||||||
2. 调 `/hide`
|
|
||||||
3. 调 `/unhide`
|
|
||||||
4. 不再改本地 `is_system`
|
|
||||||
|
|
||||||
#### 阶段 2 验收标准
|
|
||||||
|
|
||||||
1. 主 AI 仍然只看到最近 N 条消息
|
|
||||||
2. BME 在隐藏开启时仍能提取到所需上下文
|
|
||||||
3. 手动提取后继续聊天,不再误报历史变化
|
|
||||||
4. assistant turn 识别不因 hidden/system 混淆而错位
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 为什么不建议“现在直接删掉 `is_system` 双写试试看”
|
|
||||||
|
|
||||||
因为这会同时引入两个不确定性:
|
|
||||||
|
|
||||||
1. 宿主 `/hide` 是否自己也会改 `is_system`,尚未确认
|
|
||||||
2. BME 当前 extraction 仍依赖 `is_system` 过滤消息,已确认
|
|
||||||
|
|
||||||
如果现在直接删双写,出现问题时将很难判断到底是:
|
|
||||||
|
|
||||||
1. 宿主 `/hide` 的语义问题
|
|
||||||
2. extraction 过滤逻辑没拆干净
|
|
||||||
3. assistant turn 识别仍依赖 `is_system`
|
|
||||||
|
|
||||||
因此更稳的方式是:
|
|
||||||
|
|
||||||
1. 先恢复干净测试环境
|
|
||||||
2. 再把 extraction 与 `is_system` 的耦合逐层拆掉
|
|
||||||
3. 最后再收敛 hide-engine
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 这份计划背后的核心逻辑
|
|
||||||
|
|
||||||
### 逻辑 1:主 AI 的“可见性”与 BME 的“可提取性”是两套规则
|
|
||||||
|
|
||||||
如果继续让二者共享 `is_system` 这一信号,就会不断出现:
|
|
||||||
|
|
||||||
- 为了主 AI 节流而牺牲 BME 提取
|
|
||||||
- 或为了 BME 提取而破坏主 AI 隐藏
|
|
||||||
|
|
||||||
### 逻辑 2:当前最大的技术债不是 hash,而是耦合
|
|
||||||
|
|
||||||
hash 误恢复这部分已经修过一轮。
|
|
||||||
真正要彻底收尾,必须把:
|
|
||||||
|
|
||||||
- hide-engine
|
|
||||||
- extraction
|
|
||||||
- assistant turn 识别
|
|
||||||
|
|
||||||
从“共同依赖 `is_system`”改成“职责分离”。
|
|
||||||
|
|
||||||
### 逻辑 3:当前测试 blocker 必须先清掉
|
|
||||||
|
|
||||||
在 `CHAT_CHANGED` 和 `MESSAGE_RECEIVED` 都可能因原生 API 借壳调用而报 `Illegal invocation` 的情况下,继续测试高层行为没有意义。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 当前建议的执行顺序
|
|
||||||
|
|
||||||
1. 修 `event-binding.js` 中 timer / microtask 的安全调用问题
|
|
||||||
2. 验证新聊天初始化、图谱加载、自动提取链路恢复正常
|
|
||||||
3. 确认宿主 `/hide` / `/unhide` 的真实数据层语义
|
|
||||||
4. 梳理 extraction 对 `is_system` 的依赖
|
|
||||||
5. 改成按窗口读取提取上下文
|
|
||||||
6. 最后移除 hide-engine 的本地 `is_system` 双写
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 给另一个 AI 的一句话摘要
|
|
||||||
|
|
||||||
> 用户的目标不是单纯修 bug,而是把“主 AI 隐藏旧楼层”和“BME 读取提取上下文”彻底解耦:隐藏系统最终应只做 `/hide`/`/unhide`,BME 提取应按固定窗口读真实楼层;当前 blocker 是 `event-binding.js` 中 runtime 透传的原生 timer/microtask API 直接调用导致 `Illegal invocation`,需先用与 `hide-engine.js` 一致的安全 wrapper 修复测试环境,再推进 extraction 去 `is_system` 依赖,最后才能安全移除 hide-engine 的本地 `is_system` 双写。
|
|
||||||
@@ -1,363 +0,0 @@
|
|||||||
# ST-BME: 隐藏旧楼层 × 自动提取 × 历史恢复 问题修复计划(收紧版)
|
|
||||||
|
|
||||||
## 目标
|
|
||||||
|
|
||||||
把当前现象拆成两类问题分别处理,避免把“已证实的根因”和“待确认的假设”混在一起:
|
|
||||||
|
|
||||||
1. **已证实问题**:隐藏旧楼层会改动 `is_system`,而历史完整性哈希把 `is_system` 算进去,导致误判历史被改动,进而触发错误恢复,甚至形成恢复循环。
|
|
||||||
2. **待确认问题**:新聊天里自动提取不启动,当前最可疑的是图谱持久化就绪时序,但还没有足够证据证明它与上面的哈希误判属于同一条因果链。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 当前观察到的症状
|
|
||||||
|
|
||||||
### 症状 A:新聊天中自动提取似乎不启动
|
|
||||||
|
|
||||||
- 新开聊天,聊了多层后,“历史状态”仍显示“干净,已处理到楼层 -1”
|
|
||||||
- 手动点击“手动提取”后,`lastProcessedAssistantFloor` 才推进
|
|
||||||
|
|
||||||
### 症状 B:手动提取成功后,再聊一层会误触发历史恢复
|
|
||||||
|
|
||||||
- 提示类似:
|
|
||||||
|
|
||||||
> ⚠ 楼层 4 内容或 swipe 已变化
|
|
||||||
> 检测到楼层历史变化,将从楼层 4 之后自动恢复图谱
|
|
||||||
|
|
||||||
### 症状 C:触发恢复后界面表现为“提取卡住”
|
|
||||||
|
|
||||||
- 面板显示“ST-BME 提取 - AI 生成中...”
|
|
||||||
- Console 没有明显新的推进日志
|
|
||||||
- 更可能是恢复与后续自动提取反复触发,表现为“看起来挂住”
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 已确认的代码事实
|
|
||||||
|
|
||||||
### 1. 自动提取入口确实存在
|
|
||||||
|
|
||||||
- [`event-binding.js`](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\event-binding.js) 的 `onMessageReceivedController`
|
|
||||||
- 收到 assistant 消息后,会排一个 microtask 调用 `runExtraction()`
|
|
||||||
|
|
||||||
### 2. 自动提取确实会被图谱未就绪挡住
|
|
||||||
|
|
||||||
- [`extraction-controller.js`](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\extraction-controller.js) 的 `runExtractionController`
|
|
||||||
- Guard 3:
|
|
||||||
|
|
||||||
```js
|
|
||||||
if (!runtime.ensureGraphMutationReady("自动提取", { notify: false })) {
|
|
||||||
runtime.deferAutoExtraction?.("graph-not-ready");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [`index.js`](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\index.js) 的 `deferAutoExtraction` / `maybeResumePendingAutoExtraction` 也确实存在持续重试逻辑
|
|
||||||
|
|
||||||
### 3. 隐藏旧楼层会直接改 `message.is_system`
|
|
||||||
|
|
||||||
- [`hide-engine.js`](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\hide-engine.js) 的 `markManagedSystemRange`
|
|
||||||
- 会把消息对象直接写成 `message.is_system = true`
|
|
||||||
- 同时写入 `message.extra.__st_bme_hide_managed = true`
|
|
||||||
- [`restoreManagedSystemFlags`] 会把这些消息重新改回 `is_system = false`
|
|
||||||
|
|
||||||
### 4. 历史完整性哈希当前包含 `isSystem`
|
|
||||||
|
|
||||||
- [`runtime-state.js`](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\runtime-state.js) 的 `buildMessageHash`
|
|
||||||
|
|
||||||
```js
|
|
||||||
const payload = JSON.stringify({
|
|
||||||
isUser: Boolean(message?.is_user),
|
|
||||||
isSystem: managedHideMarker ? false : Boolean(message?.is_system),
|
|
||||||
text: String(message?.mes || ""),
|
|
||||||
swipeId,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 历史恢复就是基于这个哈希差异触发的
|
|
||||||
|
|
||||||
- [`runtime-state.js`](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\runtime-state.js) 的 `detectHistoryMutation`
|
|
||||||
- [`index.js`](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\index.js) 的 `inspectHistoryMutation` / `recoverHistoryIfNeeded`
|
|
||||||
|
|
||||||
因此,**“隐藏逻辑改了 `is_system`,而历史哈希又把 `is_system` 当成内容真相的一部分”这一矛盾是已坐实的。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 根因划分
|
|
||||||
|
|
||||||
### 根因 1(已确认):`is_system` 被错误纳入历史完整性判断
|
|
||||||
|
|
||||||
这是症状 B、C 的主根因。
|
|
||||||
|
|
||||||
#### 问题链条
|
|
||||||
|
|
||||||
1. 某次提取完成后,系统对已处理楼层拍快照
|
|
||||||
2. 快照中的哈希包含 `isSystem`
|
|
||||||
3. 旧楼层隐藏逻辑随后执行,会改动部分消息的 `is_system`
|
|
||||||
4. 后续完整性检查重新计算哈希时,发现同一楼层 hash 不一致
|
|
||||||
5. `detectHistoryMutation` 误以为消息内容或 swipe 发生变化
|
|
||||||
6. 触发 `recoverHistoryIfNeeded`
|
|
||||||
7. 若恢复后又再次遇到同类误判,就可能出现“恢复 -> 再检查 -> 再恢复”的循环
|
|
||||||
|
|
||||||
#### 关键结论
|
|
||||||
|
|
||||||
- `is_system` 在这里更像一种**展示/隐藏副作用**
|
|
||||||
- 它不应作为“消息历史真实性”的核心判据
|
|
||||||
- 否则隐藏功能会污染历史恢复机制
|
|
||||||
|
|
||||||
### 根因 2(待确认):自动提取在新聊天中的启动/恢复时序不稳
|
|
||||||
|
|
||||||
这是症状 A 的最可疑原因,但**目前还不能写死为已确认根因**。
|
|
||||||
|
|
||||||
#### 当前证据
|
|
||||||
|
|
||||||
- 自动提取入口存在
|
|
||||||
- Guard 3 会在 DB 未就绪时阻断
|
|
||||||
- 阻断后会走 `deferAutoExtraction`
|
|
||||||
- resume 逻辑理论上会持续重试,不是一次失败就永久放弃
|
|
||||||
|
|
||||||
#### 当前仍不确定的点
|
|
||||||
|
|
||||||
- 新聊天里究竟是否一直卡在 `ensureGraphMutationReady`
|
|
||||||
- 还是 `MESSAGE_RECEIVED` 实际没有按预期命中 assistant 消息
|
|
||||||
- 还是 defer/resume 被别的状态反复打断
|
|
||||||
- 还是 load state 在某些聊天中长期停在非 ready 状态
|
|
||||||
|
|
||||||
所以,症状 A 目前应视为一个**独立待诊断问题**,而不是直接并入“隐藏哈希误判”这条链。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 修复方案
|
|
||||||
|
|
||||||
### 修复 A(核心,直接做):从 `buildMessageHash` 中移除 `isSystem`
|
|
||||||
|
|
||||||
**目标文件**:[`runtime-state.js`](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\runtime-state.js)
|
|
||||||
|
|
||||||
#### 当前实现
|
|
||||||
|
|
||||||
```js
|
|
||||||
export function buildMessageHash(message) {
|
|
||||||
const managedHideMarker = Boolean(
|
|
||||||
message?.extra &&
|
|
||||||
typeof message.extra === "object" &&
|
|
||||||
message.extra.__st_bme_hide_managed === true,
|
|
||||||
);
|
|
||||||
const swipeId = Number.isFinite(message?.swipe_id) ? message.swipe_id : null;
|
|
||||||
const payload = JSON.stringify({
|
|
||||||
isUser: Boolean(message?.is_user),
|
|
||||||
isSystem: managedHideMarker ? false : Boolean(message?.is_system),
|
|
||||||
text: String(message?.mes || ""),
|
|
||||||
swipeId,
|
|
||||||
});
|
|
||||||
return String(stableHashString(payload));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 建议修改
|
|
||||||
|
|
||||||
```js
|
|
||||||
export function buildMessageHash(message) {
|
|
||||||
const swipeId = Number.isFinite(message?.swipe_id) ? message.swipe_id : null;
|
|
||||||
const payload = JSON.stringify({
|
|
||||||
isUser: Boolean(message?.is_user),
|
|
||||||
text: String(message?.mes || ""),
|
|
||||||
swipeId,
|
|
||||||
});
|
|
||||||
return String(stableHashString(payload));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 理由
|
|
||||||
|
|
||||||
- `is_system` 的变化不等于消息内容被编辑
|
|
||||||
- `text + swipeId + isUser` 已经足够覆盖绝大多数真正影响提取语义的变化
|
|
||||||
- 这能直接切断“隐藏副作用 -> hash 变化 -> 误恢复”的链路
|
|
||||||
|
|
||||||
#### 风险
|
|
||||||
|
|
||||||
- 如果用户真的手动把一条消息从普通消息改成系统消息,这个变化将不再被历史恢复逻辑视为“内容变更”
|
|
||||||
- 但这种操作相对罕见,而且比起当前误恢复问题,这个代价是可以接受的
|
|
||||||
|
|
||||||
### 修复 A-1(必须配套):加入快照哈希版本迁移
|
|
||||||
|
|
||||||
**这不是可选备注,而应作为正式方案的一部分。**
|
|
||||||
|
|
||||||
如果只改 `buildMessageHash` 而不做迁移:
|
|
||||||
|
|
||||||
- 旧快照是“含 `isSystem`”算法算出的
|
|
||||||
- 新代码会用“不含 `isSystem`”算法重新计算
|
|
||||||
- 第一次完整性检查几乎必然把所有已处理楼层判成 dirty
|
|
||||||
- 这会触发一次高代价恢复/重建
|
|
||||||
|
|
||||||
#### 推荐做法
|
|
||||||
|
|
||||||
给历史快照引入一个明确的 hash schema version,例如:
|
|
||||||
|
|
||||||
- `historyState.processedMessageHashVersion = 2`
|
|
||||||
|
|
||||||
加载图状态时:
|
|
||||||
|
|
||||||
1. 若版本缺失或旧于当前版本
|
|
||||||
2. 不走“历史被篡改”的判断
|
|
||||||
3. 直接清空旧 `processedMessageHashes`
|
|
||||||
4. 基于当前聊天内容重新拍一份新快照
|
|
||||||
5. 更新版本号
|
|
||||||
|
|
||||||
#### 目标
|
|
||||||
|
|
||||||
- 避免升级后第一次运行就误触发一次全量恢复
|
|
||||||
- 把这次变化当成“哈希算法升级”,而不是“聊天历史损坏”
|
|
||||||
|
|
||||||
### 修复 B(设计收敛,次优先):减少或移除 BME 对 `is_system` 的双写
|
|
||||||
|
|
||||||
**目标文件**:[`hide-engine.js`](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\hide-engine.js)
|
|
||||||
|
|
||||||
当前隐藏引擎已经调用宿主的 `/hide` 和 `/unhide`,但仍然自己改:
|
|
||||||
|
|
||||||
- `message.is_system`
|
|
||||||
- DOM 上的 `is_system` attribute
|
|
||||||
- `__st_bme_hide_managed`
|
|
||||||
|
|
||||||
这说明现在是“宿主隐藏 + BME 本地 system 标记”双轨并存,副作用偏大。
|
|
||||||
|
|
||||||
#### 建议
|
|
||||||
|
|
||||||
先确认宿主 `/hide` 的真实语义:
|
|
||||||
|
|
||||||
1. `/hide` 是否已经足以让主 AI 和 UI 正常隐藏旧消息
|
|
||||||
2. `/hide` 是否会自行管理 `is_system`
|
|
||||||
3. BME 是否还有任何逻辑依赖 `message.is_system` 来跳过消息
|
|
||||||
|
|
||||||
#### 若确认不再需要本地双写
|
|
||||||
|
|
||||||
则逐步去掉:
|
|
||||||
|
|
||||||
- `markManagedSystemRange`
|
|
||||||
- `restoreManagedSystemFlags`
|
|
||||||
- `syncSystemAttribute`
|
|
||||||
- `__st_bme_hide_managed` 相关用途
|
|
||||||
|
|
||||||
#### 注意
|
|
||||||
|
|
||||||
这一步是“降低副作用、收敛设计”的改进,不是修复症状 B/C 的前提。
|
|
||||||
**真正阻断误恢复的是修复 A。**
|
|
||||||
|
|
||||||
### 修复 C(诊断,尽快做):给自动提取 guard 和 resume 点加日志
|
|
||||||
|
|
||||||
**目标文件**:
|
|
||||||
|
|
||||||
- [`extraction-controller.js`](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\extraction-controller.js)
|
|
||||||
- [`index.js`](C:\Users\brian\OneDrive\Desktop\ST-Bionic-Memory-Ecology-past\index.js)
|
|
||||||
|
|
||||||
#### 建议打点位置
|
|
||||||
|
|
||||||
1. `runExtractionController` 每个 return 前
|
|
||||||
2. `ensureGraphMutationReady` 返回 false 的分支
|
|
||||||
3. `deferAutoExtraction`
|
|
||||||
4. `maybeResumePendingAutoExtraction`
|
|
||||||
5. `onMessageReceivedController` 命中 assistant 消息时
|
|
||||||
|
|
||||||
#### 目标不是长期保留大量日志
|
|
||||||
|
|
||||||
而是回答这几个问题:
|
|
||||||
|
|
||||||
- 新聊天时是否真的进入了 `runExtraction`
|
|
||||||
- 是否总卡在 `graph-not-ready`
|
|
||||||
- defer 是否持续排队
|
|
||||||
- resume 是否真的被触发
|
|
||||||
- resume 后是否又被 `extracting` / `history-recovering` / `graph-not-ready` 打回
|
|
||||||
|
|
||||||
只有拿到这些证据,才能决定症状 A 下一步是:
|
|
||||||
|
|
||||||
- 调整 load-ready 时机
|
|
||||||
- 在 chat loaded / chat changed 后主动 resume 一次
|
|
||||||
- 还是修正 assistant message 识别逻辑
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 不建议在文档里写死的说法
|
|
||||||
|
|
||||||
以下表述建议从“结论”降级为“待验证假设”:
|
|
||||||
|
|
||||||
### 1. “三个症状是同一条因果链”
|
|
||||||
|
|
||||||
建议改为:
|
|
||||||
|
|
||||||
- 症状 B/C 已有统一根因
|
|
||||||
- 症状 A 暂时独立排查
|
|
||||||
|
|
||||||
### 2. “自动提取比隐藏重算更早执行,因此会读到隐藏中间态”
|
|
||||||
|
|
||||||
从当前代码时序看,这个说法不够稳。
|
|
||||||
|
|
||||||
- 自动提取:`MESSAGE_RECEIVED` microtask
|
|
||||||
- 隐藏重算:`GENERATION_ENDED` 后 180ms 调度
|
|
||||||
|
|
||||||
更稳妥的写法是:
|
|
||||||
|
|
||||||
- 隐藏逻辑会改动消息对象的 `is_system`
|
|
||||||
- 历史哈希又把 `is_system` 算进去了
|
|
||||||
- 因此在后续任一完整性检查时都可能误判 dirty
|
|
||||||
|
|
||||||
不必把主要解释建立在“正好撞上中间态”之上。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 实施顺序
|
|
||||||
|
|
||||||
1. **先做修复 A**
|
|
||||||
从历史哈希中移除 `isSystem`
|
|
||||||
2. **立刻配套修复 A-1**
|
|
||||||
加入快照哈希版本迁移,避免升级后误触发全量恢复
|
|
||||||
3. **并行做修复 C**
|
|
||||||
加最小必要日志,单独定位症状 A
|
|
||||||
4. **最后评估修复 B**
|
|
||||||
收敛隐藏引擎,减少 `is_system` 相关副作用
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 验收标准
|
|
||||||
|
|
||||||
### 针对症状 B/C
|
|
||||||
|
|
||||||
1. 手动提取成功后,再聊一层
|
|
||||||
2. 不再出现“楼层 X 内容或 swipe 已变化”的误恢复提示
|
|
||||||
3. `recoverHistoryIfNeeded` 不会因为纯隐藏操作反复触发
|
|
||||||
4. 面板不会再卡在“AI 生成中...”但无实际推进
|
|
||||||
|
|
||||||
### 针对升级迁移
|
|
||||||
|
|
||||||
1. 更新到新版本后
|
|
||||||
2. 不因 hash 算法变化直接触发一次全量恢复
|
|
||||||
3. `processedMessageHashes` 能平滑迁移到新版本
|
|
||||||
|
|
||||||
### 针对症状 A
|
|
||||||
|
|
||||||
1. 新聊天中收到 assistant 消息后能看到明确日志链路
|
|
||||||
2. 能判断问题究竟发生在:
|
|
||||||
- 事件未命中
|
|
||||||
- graph-not-ready
|
|
||||||
- defer/resume 丢失
|
|
||||||
- history-recovering 打断
|
|
||||||
- 其他 guard
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 关键文件索引
|
|
||||||
|
|
||||||
| 文件 | 关键函数 | 用途 |
|
|
||||||
|------|----------|------|
|
|
||||||
| `runtime-state.js` | `buildMessageHash` | 历史完整性哈希计算 |
|
|
||||||
| `runtime-state.js` | `snapshotProcessedMessageHashes` | 拍快照 |
|
|
||||||
| `runtime-state.js` | `detectHistoryMutation` | hash 对比与 dirty 判断 |
|
|
||||||
| `hide-engine.js` | `markManagedSystemRange` | 本地写 `is_system` |
|
|
||||||
| `hide-engine.js` | `restoreManagedSystemFlags` | 撤销本地写 `is_system` |
|
|
||||||
| `hide-engine.js` | `syncSystemAttribute` | 同步 DOM `is_system` attribute |
|
|
||||||
| `hide-engine.js` | `runHideApply` | 隐藏主流程 |
|
|
||||||
| `extraction-controller.js` | `runExtractionController` | 自动提取主流程 |
|
|
||||||
| `event-binding.js` | `onMessageReceivedController` | 自动提取事件入口 |
|
|
||||||
| `index.js` | `ensureGraphMutationReady` | 图谱写入前置就绪判断 |
|
|
||||||
| `index.js` | `deferAutoExtraction` | 自动提取延迟重试 |
|
|
||||||
| `index.js` | `maybeResumePendingAutoExtraction` | 自动提取恢复 |
|
|
||||||
| `index.js` | `updateProcessedHistorySnapshot` | 更新处理后快照 |
|
|
||||||
| `index.js` | `inspectHistoryMutation` | 历史检查入口 |
|
|
||||||
| `index.js` | `recoverHistoryIfNeeded` | 历史恢复主流程 |
|
|
||||||
| `index.js` | `scheduleMessageHideApply` | 隐藏调度 |
|
|
||||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
node_modules/
|
||||||
|
.claude/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
@@ -7,8 +7,15 @@ import { sanitizePlannerMessageText } from "./planner-tag-utils.js";
|
|||||||
import { rollbackBatch } from "./runtime-state.js";
|
import { rollbackBatch } from "./runtime-state.js";
|
||||||
import { isInManagedHideRange } from "./hide-engine.js";
|
import { isInManagedHideRange } from "./hide-engine.js";
|
||||||
|
|
||||||
export function isBmeManagedHiddenMessage(message, { index = null, chat = null } = {}) {
|
export function isBmeManagedHiddenMessage(
|
||||||
if (Number.isFinite(index) && index > 0 && isInManagedHideRange(index, chat)) {
|
message,
|
||||||
|
{ index = null, chat = null } = {},
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
Number.isFinite(index) &&
|
||||||
|
index > 0 &&
|
||||||
|
isInManagedHideRange(index, chat)
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +36,10 @@ export function isSystemMessageForExtraction(
|
|||||||
return !isBmeManagedHiddenMessage(message, { index, chat });
|
return !isBmeManagedHiddenMessage(message, { index, chat });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAssistantChatMessage(message, { index = null, chat = null } = {}) {
|
export function isAssistantChatMessage(
|
||||||
|
message,
|
||||||
|
{ index = null, chat = null } = {},
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
Boolean(message) &&
|
Boolean(message) &&
|
||||||
!message.is_user &&
|
!message.is_user &&
|
||||||
@@ -113,7 +123,7 @@ export function resolveDirtyFloorFromMutationMeta(trigger, primaryArg, meta, cha
|
|||||||
const isDeleteTrigger = String(trigger || "").includes("message-deleted");
|
const isDeleteTrigger = String(trigger || "").includes("message-deleted");
|
||||||
const minExtractableFloor = getMinExtractableAssistantFloor(chat);
|
const minExtractableFloor = getMinExtractableAssistantFloor(chat);
|
||||||
|
|
||||||
// 删除后 chat 已是收缩后的状态,删除事件携带的 seq 更接近“被删区间起点”,
|
// 删除后 chat 已是收缩后的状态,删除事件携带的 seq 更接近"被删区间起点",
|
||||||
// 因此这里额外向前退一层,避免恢复仍停留在被删楼层对应的旧图谱边界。
|
// 因此这里额外向前退一层,避免恢复仍停留在被删楼层对应的旧图谱边界。
|
||||||
if (!isDeleteTrigger && Number.isFinite(meta.messageId)) {
|
if (!isDeleteTrigger && Number.isFinite(meta.messageId)) {
|
||||||
candidates.push({
|
candidates.push({
|
||||||
|
|||||||
48
index.js
48
index.js
@@ -4412,22 +4412,6 @@ function notifyExtractionIssue(message, title = "ST-BME 提取提示") {
|
|||||||
toastr.warning(message, title, { timeOut: 4500 });
|
toastr.warning(message, title, { timeOut: 4500 });
|
||||||
}
|
}
|
||||||
|
|
||||||
function settleExtractionStatusAfterHistoryRecovery(
|
|
||||||
text = "提取完成",
|
|
||||||
meta = "",
|
|
||||||
level = "success",
|
|
||||||
) {
|
|
||||||
const currentText = String(lastExtractionStatus?.text || "");
|
|
||||||
const currentLevel = String(lastExtractionStatus?.level || "");
|
|
||||||
if (currentText !== "AI 生成中" && currentLevel !== "running") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLastExtractionStatus(text, meta, level, {
|
|
||||||
syncRuntime: true,
|
|
||||||
toastKind: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchLocalWithTimeout(
|
async function fetchLocalWithTimeout(
|
||||||
url,
|
url,
|
||||||
options = {},
|
options = {},
|
||||||
@@ -5668,8 +5652,12 @@ const DEFAULT_TRIGGER_KEYWORDS = [
|
|||||||
export function getSmartTriggerDecision(chat, lastProcessed, settings) {
|
export function getSmartTriggerDecision(chat, lastProcessed, settings) {
|
||||||
const pendingMessages = chat
|
const pendingMessages = chat
|
||||||
.slice(Math.max(0, (lastProcessed ?? -1) + 1))
|
.slice(Math.max(0, (lastProcessed ?? -1) + 1))
|
||||||
.filter((msg) => !isSystemMessageForExtraction(msg))
|
.map((msg, offset) => ({
|
||||||
.map((msg) => ({
|
msg,
|
||||||
|
index: Math.max(0, (lastProcessed ?? -1) + 1) + offset,
|
||||||
|
}))
|
||||||
|
.filter(({ msg, index }) => !isSystemMessageForExtraction(msg, { index, chat }))
|
||||||
|
.map(({ msg }) => ({
|
||||||
role: msg.is_user ? "user" : "assistant",
|
role: msg.is_user ? "user" : "assistant",
|
||||||
content: msg.mes || "",
|
content: msg.mes || "",
|
||||||
}))
|
}))
|
||||||
@@ -7936,6 +7924,30 @@ async function recoverHistoryIfNeeded(trigger = "history-recovery") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function settleExtractionStatusAfterHistoryRecovery(
|
||||||
|
text = "提取完成",
|
||||||
|
meta = "",
|
||||||
|
level = "success",
|
||||||
|
) {
|
||||||
|
const statusSnapshot =
|
||||||
|
typeof lastExtractionStatus === "object" && lastExtractionStatus
|
||||||
|
? lastExtractionStatus
|
||||||
|
: null;
|
||||||
|
if (!statusSnapshot || typeof setLastExtractionStatus !== "function") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentText = String(statusSnapshot.text || "");
|
||||||
|
const currentLevel = String(statusSnapshot.level || "");
|
||||||
|
if (currentText !== "AI 生成中" && currentLevel !== "running") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLastExtractionStatus(text, meta, level, {
|
||||||
|
syncRuntime: true,
|
||||||
|
toastKind: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提取管线:处理未提取的对话楼层
|
* 提取管线:处理未提取的对话楼层
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -97,7 +97,10 @@ const autoHiddenChat = [
|
|||||||
{ is_user: true, is_system: false, mes: "user-3" },
|
{ is_user: true, is_system: false, mes: "user-3" },
|
||||||
{ is_user: false, is_system: false, mes: "assistant-3" },
|
{ is_user: false, is_system: false, mes: "assistant-3" },
|
||||||
];
|
];
|
||||||
await applyHideSettings({ enabled: true, hide_last_n: 2 }, createRuntime(autoHiddenChat));
|
await applyHideSettings(
|
||||||
|
{ enabled: true, hide_last_n: 2 },
|
||||||
|
createRuntime(autoHiddenChat),
|
||||||
|
);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
isInManagedHideRange(2, autoHiddenChat),
|
isInManagedHideRange(2, autoHiddenChat),
|
||||||
@@ -105,12 +108,18 @@ assert.equal(
|
|||||||
"auto-hidden ordinary floors should be queryable from hide-engine managed range",
|
"auto-hidden ordinary floors should be queryable from hide-engine managed range",
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
isSystemMessageForExtraction(autoHiddenChat[2], { index: 2, chat: autoHiddenChat }),
|
isSystemMessageForExtraction(autoHiddenChat[2], {
|
||||||
|
index: 2,
|
||||||
|
chat: autoHiddenChat,
|
||||||
|
}),
|
||||||
false,
|
false,
|
||||||
"auto-hidden ordinary floors inside managed range should remain extractable",
|
"auto-hidden ordinary floors inside managed range should remain extractable",
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
isSystemMessageForExtraction(autoHiddenChat[0], { index: 0, chat: autoHiddenChat }),
|
isSystemMessageForExtraction(autoHiddenChat[0], {
|
||||||
|
index: 0,
|
||||||
|
chat: autoHiddenChat,
|
||||||
|
}),
|
||||||
true,
|
true,
|
||||||
"greeting/system floor should still be treated as system even if hide range starts at 0",
|
"greeting/system floor should still be treated as system even if hide range starts at 0",
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user