|
|
@@ -1,181 +1,186 @@
|
|
|
-# 交接文档:未完成事项清单(2026-11-10)
|
|
|
+# 交接文档:未完成事项清单
|
|
|
|
|
|
| 字段 | 值 |
|
|
|
|---|---|
|
|
|
-| 用途 | **新会话接手时先读这份**,了解做到哪了、还剩什么 |
|
|
|
+| 首次编写 | 2026-11-10 |
|
|
|
+| 最后更新 | **2026-11-11**(已合并 origin/master 与本功能分支,基线 `npm test` 1522 条) |
|
|
|
+| 用途 | **新会话接手时先读这份** |
|
|
|
| 测试容器 | `agent-node-lj-test`(`@zgdcmgnqcd:fmode.cn`)— 用户裁决,**所有容器测试只用这一个** |
|
|
|
-| 当前分支 | `master`,所有改动**未提交**(等用户确认) |
|
|
|
-| 基线状态 | `npm test` 1459 条全过 · `tsc` ✅ · `npm run build` ✅ |
|
|
|
+| 当前分支 | **`master`(唯一长期分支)**;`feat/file-hub-session-isolation` 已合并并删除,后续一律在 master 上提交 |
|
|
|
+| 基线状态 | `npm test` 1459+ 条 · `tsc` ✅ · `npm run build` ✅ |
|
|
|
|
|
|
---
|
|
|
|
|
|
-## 一、本轮已完成(不用再做)
|
|
|
-
|
|
|
-| 项 | 说明 | 验证 |
|
|
|
-|---|---|---|
|
|
|
-| 计时稳定递增 | 从用户发消息起算,不再卡住跳变 | 单测 + 真机 38s |
|
|
|
-| 容器日志不再刷屏 | 工具行占比过半才折叠 | 单测 |
|
|
|
-| 「已完成」口径 | 无交付时显示「已结束 · 无交付」 | 单测 |
|
|
|
-| 超时改为 3 分钟 | `TURN_TIMEOUT_MS = 180_000` | 单测 |
|
|
|
-| 超时提示必弹 | 看门狗独立于会停的时钟 | 单测 |
|
|
|
-| 提示每条消息只弹一次 | 超时即终态,刷新不再弹 | **真机验证通过** |
|
|
|
-| 移动端提示独立样式 | 整宽、按钮 48px、不竖排 | 真机验证 |
|
|
|
-| 结果 emoji 合并 | `✅/❌` 连续行合并成一段,不再一卡一行 | 单测 |
|
|
|
-| PC/Mobile 自动切换 | 按宽度自动跳转,去掉手动按钮 | **真机验证通过** |
|
|
|
-| 成果文件名与存储键分离 | `prettifyStorageKey` | 单测 |
|
|
|
+## 一、已完成(不用再做)
|
|
|
|
|
|
----
|
|
|
-
|
|
|
-## 二、未完成的环节(新会话要做的)
|
|
|
+### 1.1 会话体验
|
|
|
|
|
|
-### P0 · 阻断体验
|
|
|
-
|
|
|
-#### 1. 上云链路只在「临时房间」验证通过,**真实会话仍有缺陷**
|
|
|
+| 项 | 说明 |
|
|
|
+|---|---|
|
|
|
+| 计时稳定递增 | 从用户发消息起算,不再卡住跳变 |
|
|
|
+| 容器日志不刷屏 | 工具行占比过半才折叠 |
|
|
|
+| 「已完成」口径 | 无交付显示「已结束 · 无交付」 |
|
|
|
+| 结果 emoji 合并 | 连续 `✅/❌` 行合并成一段 |
|
|
|
+| 超时改 3 分钟 | `TURN_TIMEOUT_MS = 180_000` |
|
|
|
+| 超时提示必弹 | 看门狗独立于会停的时钟 |
|
|
|
+| **提示每条消息只弹一次** | 超时即终态,刷新不再弹(真机验证) |
|
|
|
+| 移动端提示独立样式 | 整宽、按钮 48px、不竖排 |
|
|
|
+| **PC/Mobile 自动切换** | 按宽度自动跳转,去掉手动按钮(真机验证) |
|
|
|
+
|
|
|
+### 1.2 文件与成果(提交 `08b3d58`)
|
|
|
+
|
|
|
+| 项 | 说明 |
|
|
|
+|---|---|
|
|
|
+| **新建云函数 `fmode-file-hub`(`THXAOGKiPK`)** | registerFile / listBySession / listByAgent;老函数**一行未动** |
|
|
|
+| **成果按会话隔离** | 新增 `threadRoot` 字段;同一 room 不同 thread = 不同会话 |
|
|
|
+| **用户上传改为「先云盘、再 Matrix」** | 云盘失败则不发消息,不再半成功 |
|
|
|
+| **群聊此前完全不上云** | 已修 |
|
|
|
+| **群聊成果串台** | 已修 bug |
|
|
|
+| **右侧栏改为从数据库加载** | 不再靠消息正文找链接 |
|
|
|
+| 交付协议解析改用容器真实格式 | 老的 `<!--fmode:files-->` 已删除(容器从未实现) |
|
|
|
+| 上云失败可观测 | UI 如实展示 |
|
|
|
+| 上传大小限制 | `upload-limits.ts`(20MB,目前仅 App 侧) |
|
|
|
+
|
|
|
+### 1.3 容器侧接入(已完成并独立核验)
|
|
|
+
|
|
|
+- `fmode-deliverable-hook.py` 新增 `_thread_root()`:`HERMES_SESSION_ID` 查 `state.db.sessions.thread_id`,**105/105 命中**
|
|
|
+- 登记切到新函数 `registerFile`;取不到 threadRoot 时 **SKIP 不造假值**
|
|
|
+- 老链路(`report`/`enqueueArtifact`/`drain`)原样并存
|
|
|
+- **hook 每次调用 exec 脚本 → 改完即生效,无需重启 gateway**
|
|
|
+
|
|
|
+**核验证据**(不只信自报):
|
|
|
+```
|
|
|
+listBySession threadRoot=$approot_mw9h5oi6_m96imj7x:fmode.cn
|
|
|
+count = 2
|
|
|
+ 4pA5vi4s4q hub-verify.md uploadedBy=agent ← 容器产出
|
|
|
+ HmeAvoDKkv m-composer-1794298339227.md uploadedBy=user ← 用户上传
|
|
|
+```
|
|
|
|
|
|
-**已确认的事实:**
|
|
|
+---
|
|
|
|
|
|
-- 我用脚本建临时房间测试,连续 3 轮 **6/6 全绿**(上传、成果登记、正文带链接、无本地路径)
|
|
|
-- 但在 **App 真实会话**里发需求(`app-e2e-1794288808946.md`):
|
|
|
- - ✅ 文件**确实上云了**(成果表有记录)
|
|
|
- - ❌ 回复正文**没有 https**
|
|
|
- - ❌ 正文残留「本地路径:(本地工作目录)」
|
|
|
+## 二、未完成的环节
|
|
|
|
|
|
-**待查:**
|
|
|
+### P0 · 阻断体验
|
|
|
|
|
|
-- 临时房间 vs 真实会话(thread)在 hook/插件层面的差异
|
|
|
-- 容器自述「插件需重启才生效」,但那之后真实会话仍未验证通过
|
|
|
+#### 1. 「容器产出文件 → 回复带链接」在**真实回合**尚未完整验证
|
|
|
|
|
|
-**涉及文件:** 容器侧 `fmode-deliverable-hook.py`、`plugin-deliverable-inject`
|
|
|
+**限制(如实披露)**:
|
|
|
|
|
|
-#### 2. 容器端「默认上云」尚未在真实场景证明
|
|
|
+> 当前容器的 DM 房间历史上 **0 条**有 thread 的 session(`thread_id` 全是 NULL)。
|
|
|
+> 因此「容器产出文件的真实回合」尚未取得完整证据。
|
|
|
|
|
|
-**用户核心需求**:用户**不提**「上传」二字,产出文件也要自动上云。
|
|
|
+- 容器已用同一 threadRoot 实跑登记并回读成功(`4pAvi4s4q`)→ **登记逻辑可用**
|
|
|
+- 但「用户发需求 → 容器产出 → 自动登记到该会话 → 回复带 https」这条**端到端链路**还没在真实会话里跑通一次
|
|
|
|
|
|
-**当前状态:**
|
|
|
+**下一步:**
|
|
|
+1. 在 App 里**新建会话**(有 thread 根),发一条不带上传字眼的需求
|
|
|
+2. 检查三件事:文件是否登记到**该会话**、回复正文是否带 https、右栏是否显示
|
|
|
|
|
|
-- 机制已建(`config.yaml` hooks + Python 插件)
|
|
|
-- 临时房间通过,真实会话未通过
|
|
|
-- 容器曾自编假链接(`https://s3.fmode.cn/user/xxx/report/xxx.md` → 404),已修三层防护,但需重新验证
|
|
|
+#### 2. 容器回复正文仍会带 `/opt/data/work/...` 本地路径
|
|
|
|
|
|
-#### 3. 云函数无删除能力,测试数据清不掉
|
|
|
+已知未解决,属容器任务书范围。App 侧**不伪造处理**(用户明确要求不要兜底)。
|
|
|
|
|
|
-**问题:**
|
|
|
+#### 3. 云函数没有删除能力,测试数据清不掉
|
|
|
|
|
|
-- 云函数 `lfYlgU7SkK` **没有 `remove` action**
|
|
|
-- 成果表里堆积了测试数据:`meeting-*`、`app-e2e-*`、`retro-*`、`SKILL.md`、`probe-*`
|
|
|
-- 之前建的临时 Matrix 房间也在
|
|
|
+- `lfYlgU7SkK` **没有 `remove` action**
|
|
|
+- 成果表堆积:`meeting-*`、`app-e2e-*`、`retro-*`、`SKILL.md`、`probe-*`
|
|
|
+- 临时 Matrix 房间也在
|
|
|
|
|
|
-**需要:** 云函数加删除能力,或提供控制台清理方式
|
|
|
+**需要**:`fmode-file-hub` 加删除能力,或提供控制台清理方式
|
|
|
|
|
|
---
|
|
|
|
|
|
### P1 · 应该做
|
|
|
|
|
|
-#### 4. 用户上传文件未改成「先上云」
|
|
|
+#### 4. 老成果数据没有 `threadRoot`
|
|
|
|
|
|
-**用户要求**:「我上传文件之后,默认上传到我的云盘里面,然后同步到数据库」
|
|
|
+按用户裁决**不处理** —— 老数据不在会话右栏显示。
|
|
|
|
|
|
-**当前实现**(`chat.service.ts` `uploadPending`):
|
|
|
-```
|
|
|
-先发 Matrix 附件(mxc://)→ 再额外传 S3
|
|
|
-```
|
|
|
-
|
|
|
-**容器给出的硬约束**(读源码确认):
|
|
|
-```python
|
|
|
-# adapter.py:2136
|
|
|
-if url and not str(url).startswith("mxc://"):
|
|
|
- return # https 放进媒体事件会被直接丢弃
|
|
|
-```
|
|
|
-
|
|
|
-**结论:**
|
|
|
+#### 5. 上传大小限制只在 App 侧
|
|
|
|
|
|
-| 文件大小 | 通道 |
|
|
|
-|---|---|
|
|
|
-| ≤20MB | 必须走 `mxc://`(容器只认这个) |
|
|
|
-| 大文件 | 先上云 → **纯文本 body** 放 https |
|
|
|
-
|
|
|
-**待做:** App 侧按大小分流,大文件走「云端 + 正文链接」
|
|
|
-
|
|
|
-#### 5. 本地文件不累积,但做不到「零本地写入」
|
|
|
+`AlP56LCKFm` 本身**没有 size 校验**,20MB 限制目前只在客户端。
|
|
|
+服务端硬门禁需改老函数(用户要求不动老函数 → 未排期)。
|
|
|
|
|
|
-**用户诉求**:本地空间有限,文件主要在云端。
|
|
|
+#### 6. 本地文件做不到零写入
|
|
|
|
|
|
-**容器侧已做**:PUT 成功后 `md5` 校验通过即删除本地源文件。
|
|
|
+服务端契约要求 `report`/`registerFile` 收**容器本地路径**,drain 要 `open(路径)` 才能 PUT。
|
|
|
+容器已做「PUT 成功后 md5 校验再删」,但秒级中转不可避免。
|
|
|
|
|
|
-**限制**:服务端契约要求 `report`/`enqueueArtifact` 收**容器本地路径**,drain 要 `open(路径)` 才能 PUT —— 秒级中转不可避免。
|
|
|
-
|
|
|
-**若要真正零写入**:需改云函数加 base64/流直传 action(平台侧改动,未排期)
|
|
|
+**真要零写入**:需云函数加 base64/流直传 action(平台侧改动)。
|
|
|
|
|
|
---
|
|
|
|
|
|
### P2 · 技术债
|
|
|
|
|
|
-#### 6. `transform_llm_output` hook 仍是坏的
|
|
|
-
|
|
|
-容器读源码确认两个断点:
|
|
|
-- `shell_hooks._payload_fields()` 把正文塞进 `extra`,但 hook 读顶层
|
|
|
-- hook 输出 `{response_text}`,但 Hermes 只认 `{context}`
|
|
|
+#### 7. `transform_llm_output` shell hook 仍是坏的
|
|
|
|
|
|
-**当前绕法**:改用 Python 插件 `plugin-deliverable-inject`(回调返回 str 才被采纳)
|
|
|
+- `shell_hooks._payload_fields()` 把正文塞进 `extra`,hook 读顶层
|
|
|
+- hook 输出 `{response_text}`,Hermes 只认 `{context}`
|
|
|
|
|
|
-**风险**:若 Hermes 升级改动契约,插件会失效,需重新验证
|
|
|
+**当前绕法**:Python 插件 `plugin-deliverable-inject`(回调返回 str 才被采纳)。
|
|
|
+**风险**:Hermes 升级改动契约会失效。
|
|
|
|
|
|
-#### 7. 交付协议 v2(`<!--fmode:files-->`)从未被容器实现
|
|
|
+#### 8. 任务域 `fetchTasks`
|
|
|
|
|
|
-- App 侧已实现(`core/declared-files.ts`)
|
|
|
-- 容器明确回答「不知道这个协议,从没写过」
|
|
|
-- **实际交付走的是 `m.file` + 正文链接**
|
|
|
+仍为桩,云函数接收端未实现。用户曾裁决挂起。
|
|
|
|
|
|
-**建议**:要么让容器实现,要么在 App 侧废弃该分支
|
|
|
+#### 9. `messageCount` 双写虚高
|
|
|
|
|
|
-#### 8. `messageCount` 双写虚高
|
|
|
-
|
|
|
-左栏消息数偏大,未修。属数据模型变更。
|
|
|
+左栏消息数偏大,未修。
|
|
|
|
|
|
---
|
|
|
|
|
|
## 三、需要从容器获得的信息
|
|
|
|
|
|
-### 已问到(有答案)
|
|
|
+### 已问到(有答案,不用再问)
|
|
|
|
|
|
| 问题 | 容器回答 |
|
|
|
|---|---|
|
|
|
-| 回复分几条发? | 分条发,回执先发、结果后发、长任务插播进度 |
|
|
|
+| 回复分几条发? | 分条:回执先发、结果后发、长任务插播进度 |
|
|
|
| 挂 thread 吗? | **不挂**,只发普通消息 |
|
|
|
| `tool_progress` / `interim` | `all` / `true` |
|
|
|
| 上传链路 | `report` → `enqueueArtifact` → `drain` → md5 回读 |
|
|
|
-| `report` 会自动入队吗? | **不会**,必须显式 `enqueueArtifact` |
|
|
|
-| 用哪个云函数 | `lfYlgU7SkK`(**不是** `xlosi3m46q`,那个对任何 action 都返回 200) |
|
|
|
+| `report` 自动入队? | **不会**,必须显式 `enqueueArtifact` |
|
|
|
+| 用哪个云函数 | 老的 `lfYlgU7SkK`;新登记用 `THXAOGKiPK`(**不是** `xlosi3m46q`,它对任何 action 都返回 200) |
|
|
|
+| threadRoot 怎么取 | `HERMES_SESSION_ID` 查 `state.db`,105/105 命中 |
|
|
|
|
|
|
### 还需要问
|
|
|
|
|
|
-1. **真实会话 vs 临时房间的差异**:为什么临时房间能带链接、真实会话不能?
|
|
|
-2. **插件重启后是否真的加载**:需要进程启动时间 vs 插件 mtime 的证据
|
|
|
-3. **产出文件的判定规则原文**:当前 `SYSTEM_PREFIX_DENY` 列表能否覆盖所有系统文件
|
|
|
-4. **大文件读取能力**:https 纯文本链接的最大可靠大小
|
|
|
+1. **真实会话里触发一次产出**:确认「产出 → 登记 → 回复带 https」整链
|
|
|
+2. **`/opt/data/` 路径残留**什么时候能彻底不出现
|
|
|
+3. **大文件读取能力**:https 纯文本链接的最大可靠大小
|
|
|
|
|
|
---
|
|
|
|
|
|
-## 四、已知的关键流程(新会话必读)
|
|
|
+## 四、已知的关键流程
|
|
|
|
|
|
-### 4.1 容器上云链路
|
|
|
+### 4.1 容器上云链路(当前)
|
|
|
|
|
|
```
|
|
|
① 容器产出文件 → /opt/data/work/xxx
|
|
|
② post_tool_call hook → 写入队列状态文件(无网络 IO)
|
|
|
-③ transform_llm_output hook → report + enqueueArtifact + drain + md5 回读
|
|
|
- ⚠️ 这一步的文本输出会被 Hermes 丢弃(hook 接口不兼容)
|
|
|
+③ transform_llm_output hook → 上传 + 登记(registerFile 带 threadRoot)
|
|
|
+ ⚠️ 这一步的文本输出会被 Hermes 丢弃
|
|
|
④ plugin-deliverable-inject(Python 插件)→ 把 https 追加进回复正文
|
|
|
- ✅ 只有 Python 插件回调返回 str 才被采纳
|
|
|
-⑤ 上传成功后删除本地源文件
|
|
|
+⑤ 上传成功后 md5 校验通过 → 删除本地源文件
|
|
|
```
|
|
|
|
|
|
-**验证命令:**
|
|
|
+**验收命令:**
|
|
|
```bash
|
|
|
-npm run verify:upload # 5-6 项判定,可指定 --agent / --prompt
|
|
|
+npm run verify:upload # 5-6 项判定
|
|
|
+node scripts/verify-file-hub.mjs # 12 项
|
|
|
+node scripts/verify-real-upload.mjs # 7 项(真实账号)
|
|
|
+```
|
|
|
+
|
|
|
+### 4.2 用户上传(已改为先云盘)
|
|
|
+
|
|
|
+```
|
|
|
+选文件 → 先传云盘 → 失败则**不发消息**(不再半成功)
|
|
|
+ → 成功后发 Matrix 附件
|
|
|
+ → registerFile 登记 uploadedBy=user
|
|
|
```
|
|
|
|
|
|
-### 4.2 Matrix 消息归属(App 侧)
|
|
|
+### 4.3 Matrix 消息归属(App 侧)
|
|
|
|
|
|
`chat.service.ts` `belongsToSession()`:
|
|
|
|
|
|
@@ -184,10 +189,10 @@ npm run verify:upload # 5-6 项判定,可指定 --agent / --promp
|
|
|
| 有 thread 且 threadId === 会话 | 认领 |
|
|
|
| 无 thread + dispatch 标记 | 丢弃(群聊派活) |
|
|
|
| 无 thread + sessionId 标记 | 按 sessionId 比对 |
|
|
|
-| **无 thread + 实时 + 本会话有待回复消息** | **认领**(2026-11-10 新增,修「完全没反应」) |
|
|
|
+| 无 thread + 实时 + 本会话有待回复消息 | 认领(2026-11-10 新增) |
|
|
|
| 无 thread + 历史回填 | 丢弃 |
|
|
|
|
|
|
-### 4.3 超时逻辑
|
|
|
+### 4.4 超时逻辑
|
|
|
|
|
|
```
|
|
|
用户发消息 → 看门狗启动(每 5 秒)
|
|
|
@@ -197,40 +202,35 @@ npm run verify:upload # 5-6 项判定,可指定 --agent / --promp
|
|
|
下一轮重新开始
|
|
|
```
|
|
|
|
|
|
-**关键**:看门狗存活窗口 = `TURN_TIMEOUT_MS + 10s`,刷新后老消息超出窗口 → 不启动 → 不弹。
|
|
|
+看门狗存活窗口 = `TURN_TIMEOUT_MS + 10s`,刷新后老消息超出窗口 → 不启动 → 不弹。
|
|
|
|
|
|
----
|
|
|
-
|
|
|
-## 五、当前工作区状态
|
|
|
+### 4.5 端自动切换
|
|
|
|
|
|
-**未提交的改动**(`git status`):
|
|
|
+```
|
|
|
+按 window.innerWidth 判定:< 768 → mobile,否则 desktop
|
|
|
+ · 宽度够但 UA 是手机(横屏)→ 仍 mobile
|
|
|
+ · 窗口 resize → 防抖 400ms → 按目标端导航
|
|
|
+🔴 判据用「目标端」,不用 recheck().changed(否则直接窄窗口打开会卡住)
|
|
|
+```
|
|
|
|
|
|
-- `CLAUDE.md` — 加测试容器约定
|
|
|
-- `package.json` — 加 `verify:upload` 脚本
|
|
|
-- `src/app/` 下 21 个文件(会话、超时、设备切换、成果、合并等)
|
|
|
-- 新增文档 2 份 + 验收脚本
|
|
|
+---
|
|
|
|
|
|
-**新增的脚本**(`scripts/`):
|
|
|
-- `verify-upload.mjs` — 上云验收(可复用,任意容器)
|
|
|
-- `ask-ljtest.mjs` — 批量短问题问容器(长问题容器处理不了)
|
|
|
+## 五、关联文档
|
|
|
|
|
|
-> ⚠️ 大量 `debug-*.mjs`、`probe-*.mjs` 是临时调试脚本,交接前应清理。
|
|
|
+| 文档 | 用途 |
|
|
|
+|---|---|
|
|
|
+| `docs/00-tracking/2026-11-10-修改留痕-文件台账与按会话隔离.md` | **最新**:file-hub 云函数 + 容器接入 |
|
|
|
+| `docs/02-design/plans/2026-11-10-文件上云完整方案.md` | 上云方案设计 |
|
|
|
+| `docs/03-interface/容器执行-切换文件台账-20261110.md` | 容器侧任务书 |
|
|
|
+| `docs/03-interface/容器方案-如何可靠拿到threadRoot-20261110.md` | threadRoot 取法 |
|
|
|
+| `CLAUDE.md` | 项目规则(含测试容器约定) |
|
|
|
|
|
|
---
|
|
|
|
|
|
## 六、新会话建议的起步动作
|
|
|
|
|
|
-1. **先跑基线**:`npm test` + `npx tsc -p tsconfig.app.json --noEmit`
|
|
|
+1. **跑基线**:`npm test` + `npx tsc -p tsconfig.app.json --noEmit`
|
|
|
2. **确认容器在线**:`node scripts/matrix-check.mjs agent-node-lj-test`
|
|
|
-3. **复现 P0-1**:在 App 真实会话里发一条不带上传字眼的需求,看回复是否带 https
|
|
|
-4. **按本文档 §四 的流程定位断点**
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 七、关联文档
|
|
|
-
|
|
|
-| 文档 | 用途 |
|
|
|
-|---|---|
|
|
|
-| `docs/02-design/plans/2026-11-10-文件上云完整方案.md` | 上云方案完整设计 |
|
|
|
-| `docs/00-tracking/2026-11-10-修改留痕-会话区三缺陷与上云链路.md` | 本轮修改留痕 |
|
|
|
-| `CLAUDE.md` | 项目规则(含测试容器约定) |
|
|
|
+3. **验证 P0-1**:App 里**新建会话**发一条不带上传字眼的需求
|
|
|
+ → 看 `listBySession` 是否出现在该会话、右栏是否显示、正文是否带 https
|
|
|
+4. 若不通,按 §四 的流程逐环定位
|