Files
HomeAgent/plan.md
JianFeeeee 40669d7b9b docs(plan): 按源码核实重写——剔除过时/跨仓项,只留 10 项真待办
旧 plan.md(1474 行)是「历史工单 + 路线图」混合档,混入两类错误:
1. 已实现却仍标 TODO(§13.12 L3 多模态、§11.4 Lua 快照锁、§12.2 setToolBlocks、
   §13.11 主体 11 项)
2. 根本不属于本仓的跨项目工单(§13.9 在 llmsproxy 仓、§13.10 在 agentmail 仓)

本次回到源码逐项核实(grep 符号存在性 / go build / go test / go test -race),
新档只保留经证据确认的本仓待办,每条附 file:line 与可验证的验收条件。

- §一 结论速览(10 真待办 / 4 生产验证 / 8 已关闭 / 2 跨仓)
- §二 真待办 P0-P2:RuntimeManager 分组 worker、reload 语义说谎、arena 无
  Grow/Shrink、事件环零真实负载、工具超时措辞、handleAgentAction 501、
  Lua 独立 ABI、Windows 无真机验证、homed heap 常驻、3 个测试缺陷
- §三 已关闭 8 项(附源码证据,防复活)
- §四 生产部署后验证 4 项(代码已就绪)
- §五 跨仓工单归属说明
- §六 明确「不做」的决定
2026-09-25 09:17:11 +08:00

387 lines
18 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# HomeAgent 遗留问题清单
> **本文定位(2026-09-24 重写)**:本文**只写仍未完成、且经源码核实确属本仓库的问题**。
> 上一版 plan.md 是 1474 行的「历史工单 + 路线图」混合档,混入了大量已解决记录、
> 以及**根本不属于本仓库的跨项目工单**(见 §五),本次全部剔除。
>
> **每一条的判定方法**:不是继承旧档的复选框,而是回到源码逐项核实
> (`grep` 定符号存在性、`go build ./...`、`go test ./...`、`go test -race`)。
> 旧档里标记「未做」但代码已落地的项、以及引用别仓代码的项,统一进 §三/§五 并附证据。
>
> **本文不写**:已完成项的定位过程(那是 git log 和 commit message 的职责)、
> 无实测支撑的性能断言、任何凭据/生产路径。
---
## 一、结论速览
| 类别 | 数量 | 去向 |
|---|---|---|
| 真正待办(本仓库、代码层可动) | 10 项 | §二 |
| 生产部署后验证(代码已就绪,需现场跑) | 4 项 | §四 |
| 已关闭 / 已实现(旧档误标为 TODO) | 8 项 | §三 |
| 跨项目工单(不属本仓,已在别处解决或归档) | 2 项 | §五 |
当前健康度:`go build ./...` 通过;`go test ./...` 有 **3 个 FAIL**,全部是
测试自身缺陷(见 P2-10),非产品代码问题;`go test -race` 于 `internal/lua`、
`internal/plugin/**` 全绿。
---
## 二、真正待办
按「影响面 × 可验证性」排序。每条给出**证据**(file:line)与**验收**。
### P0-1 §13.7 RuntimeManager + 分组 worker(架构演进,唯一大件)
**现状**:`RuntimeManager` / `worker_group` / `WorkerGroup` 在全仓库
(含 .go / .json / .md,排除 plan.md 自身)**零引用** —— 该能力从未实现。
当前是**一插件一子进程**:`internal/plugin/proc/plugin.go:134` 的 `Spawn`
按插件各起一个进程。
**为什么要做**:每个外部插件一个进程 → N 个插件 = N 个常驻进程 + N 份
transport,进程数随插件线性涨。目标是一个 RuntimeManager + 少量 worker +
多插件共享 transport + 每插件独立 PluginContext(独立身份,共享管道)。
**证据**:
- `internal/plugin/proc/plugin.go:134`(`Spawn`,逐插件起进程)
- manifest 无 `worker_group` 字段:`third_party/homeagent-sdk/example/a2a/plugin.json`
的键集合为 `author/description/entry/name/name_en/name_zh/platforms/tags/version`
**子任务**:
1. `RuntimeManager` 类型:worker 池 + 调度
2. `Worker` 类型:一个进程,承载多个插件,共享 `RuntimeClient`
3. `PluginContext` 类型:每插件独立身份(能力门、ownerID、工具命名空间)
挂在同一 transport 上
4. manifest 增 `worker_group` 字段(缺省 = 全部归同一 worker,兼容迁移)
5. 高风险插件可声明独立 `worker_group`
**验收**:
- [ ] 缺省分组行为与现状逐字节等价(现有 proc 测试全绿,不改协议)
- [ ] 两个插件同 worker 时,各自 `ReclaimOwner` 只回收自己的 arena 块
- [ ] 一插件崩溃不带走同 worker 的另一插件(或明确:带走,并写进文档)
- [ ] manifest 无 `worker_group` 的旧插件可原样加载(向后兼容)
> 注:`.pi/subagents/missions/` 里有一份针对本项的只读架构评审产物,
> 可作为设计输入;其中结论尚未落地为代码。
---
### P0-2 §11.6 reload 语义仍在说谎
**现状**:`plugin_install` 依旧回 `reload_required`,而**不可热重载的插件
(Go `plugin.Open` 路径、`DF_1_NODELETE`)会被假装重载成功**。
**证据**:
- `internal/plugins/pluginmgr/plugin.go:528 / :548 / :755` —— 仍返回
`reload_required`
- 全仓库无 `DF_1_NODELETE` / `NODELETE` 检测(`grep` 为空)
- `internal/plugin/registry.go:823-824` 注释已自认:`Go plugin.Open 路径
(dynamicPlugin)不可卸载,跳过`
**子任务**:
1. ELF 检测 `DF_1_NODELETE` → 标记插件「不可热重载」
(`internal/plugin/dynamic_loader_unix.go`)
2. `ReloadOne` 对这类插件返回「需重启 homed」,停止假装成功(`registry.go:785`)
3. `plugin_install` 返回 `restart_required` 替代误导性的 `reload_required`
(`pluginmgr/plugin.go`)
**验收**:
- [ ] 装一个 `DF_1_NODELETE` 插件后,调用方拿到 `restart_required`,不是 `reload_required`
- [ ] `ReloadOne` 在该插件上返回明确错误,且**摘除旧注册面**(工具/stage/output)
仍已完成,不留悬空闭包
---
### P1-3 §13.2 arena 容量固定,无 Grow/Shrink
**现状**:内核独占的变长分配器已落地(first-fit + 邻块合并 + owner 校验),
但容量**固定 4MB**,用尽即调用失败,不再退回内联。
**证据**:
- `internal/plugin/proc/arena.go:99`:`const arenaDefaultCapacity = 4 * 1024 * 1024`
- `arena.go:263 / :283`:超限直接报错
**为什么当时不做**:跨进程 `remap` 会让正在读的对端 SIGSEGV。所以这不是
「补个函数」,而是要先解决**对端可见的地址稳定性**。
**可选路径**(择一,需先定方案再动手):
- a) 预映射大虚拟区间(未触碰页不占物理内存),容量「逻辑无限」
- b) 段表 + 多段拼接:新块分配在新段,不必 remap 旧段
- c) 维持固定容量,但**把超限错误做成可执行指引**(告诉插件该怎么分片)
**验收**(按所选路径定):
- [ ] 超过当前 4MB 的单次 payload 仍能送达,或收到带指引的明确错误
- [ ] 任何情况下对端不 SIGSEGV
---
### P1-4 §12.3 事件环:机制完成,零真实负载检验
**现状**:事件环(区内 segment + eventfd)与 `events.subscribe` 能力已完成,
但**没有一个真实外部插件订阅 `stage` / `tool_call` 事件**,`dropped` 计数
在长跑下的行为也无人观察。
**证据**:
- `internal/plugin/proc/capability.go:149-150`(subscribe/unsubscribe 已授权)
- `internal/plugin/proc/corehandler.go:46-58`(`EvtRingSubscriber` 接口)
- 无对应 example 插件(`third_party/homeagent-sdk/example/` 无事件订阅样例)
**子任务**:
1. 写一个订阅 `stage` / `tool_call` 事件的 example 插件,跑真实负载
2. 长跑观察 `dropped` 是否异常增长(环 cap 溢出告警是否够用)
**验收**:
- [ ] example 插件能稳定收到事件并正确反序列化
- [ ] 压测下 `dropped` 有上界且可观测
---
### P1-5 §11.2 工具超时措辞误导 + browser 插件超时聚集
**现状**:内核侧工具超时文案仍写「已取消」,但**进程内 cgo 插件根本取消不了**
(OS 线程永久占用)——这是措辞与事实不符。
**证据**:
- `internal/agent/core/toolcall.go:42`:
`fmt.Sprintf("工具 %s 执行超时(60秒),已取消", tc.Name)`
- `internal/plugin/proc/process.go:27 / :114 / :577`:注释自认 cgo 路径
「超时后 OS 线程永久占用,现网已泄漏 26 次」
**子任务**:
1. 措辞改「已放弃等待(插件仍在后台运行,其占用的线程无法回收)」
2. 排查 `browser` 插件为何频繁 60s 超时(旧档记录 22/26 次集中于此);
真取消能力依赖子进程模型(与 P0-1 相关)
**验收**:
- [ ] 超时文案不再暗示「已取消」
- [ ] browser 超时率下降到可解释水平,或给出根因
> 与 P0-1 的关系:把内部 cgo 插件也迁到子进程后,这条的严重性自动消除。
---
### P1-6 §13.11 尾项:`handleAgentAction` 直接 501
**现状**:WebUI 的 agent 操作接口**所有 action 一律返回 `501 Not Implemented`**,
是明确的未接线桩。
**证据**:
- `internal/plugins/webui/handler_agents.go:239`:
`writeJSON(w, http.StatusNotImplemented, ..."action not implemented by supervisor")`
**子任务**:决定该端点该做什么——要么接线到 supervisor 的真实动作
(stop/restart/snapshot 等),要么从 UI 撤掉入口,不要留一个必然失败的按钮。
**验收**:
- [ ] UI 上不存在「点了必 501」的入口,或该入口真的能动作
> 说明:旧档 §13.11 的「11 项」其余各项**已实现**(见 §三.6),仅此一项是真缺口。
---
### P2-7 §12.4 Lua 仍走独立 ABI(进程内解释器)
**现状**:Lua 插件在**内核进程内**跑 gopher-lua,不走 proc 通道,是三套
ABI 里唯一没收敛的。代价是:Lua 插件崩溃 = 内核崩溃,且无共享内存数据面。
**证据**:
- `internal/plugin/lua_plugin.go:947`(`makeStageHandler`,进程内)
- `internal/plugin/lua_plugin.go:128 / :329`(`register_stage` 直接注册到内核 SDK)
**子任务**:评估「Lua 走 proc 通道」的代价(解释器进程启动开销 vs 隔离收益),
据此决定收敛还是明确保留为独立 ABI 并写进文档。
**验收**:
- [ ] 有明确决策(收敛 / 永久保留),且文档与实际一致
---
### P2-8 §11.5 / §12.5 Windows 只有交叉编译,无真机验证
**现状**:Windows 侧共享内存(`CreateFileMappingW`)、事件通知
(`CreateEventW`)、命名对象传递均已实现(桩已合回平台中立文件),但
**从未在 Windows 真机端到端跑过**。
**证据**:
- `internal/plugin/dynamic_proc.go:17-27` 注释:平台差异已全部封装,
桩已删除
- 无 Windows CI / 真机记录
**子任务**:
1. 找一台 Windows 机器跑端到端
2. **特别验证命名对象的撞名防护**(名字带 PID + 递增序号)
**验收**:
- [ ] Windows 下 `homed` 能加载外部插件并完成工具调用往返
- [ ] 并发起多个插件时命名对象不撞
---
### P2-9 §11.9 homed 主 heap 常驻未解释
**现状**:旧档记录 homed 主 heap 有约 **2.36GB 常驻**,来源未定位。
配置侧已有缓解手段(`embedding_model_path` 支持 `#topN` 限词向量数量),
但**未确认现状是否仍存在**。
**证据**:
- `internal/config/registry.go:856`:`embedding_model_path` 描述已写明
`#topN` 可「控制常驻内存」
- 无 `GOMEMLIMIT` 相关设置(`grep` 为空)
**子任务**:
1. 现场 `pprof` 定位常驻来源(是否仍为双模型加载)
2. 若确认,评估是否加 `GOMEMLIMIT` 或默认 `#topN`
**验收**:
- [ ] 给出常驻内存的构成分解(哪块占多少)
- [ ] 有明确取舍结论(可接受 / 需优化 / 已优化)
---
### P2-10 仓库卫生:三个真实的测试缺陷(3 FAIL)
**现状**:`go test ./...` 有 **3 个 FAIL**,根因都是**测试自身缺陷**,
不是「环境玄学」,也都可修:
1. **PTY 用例要求可打开的 `/dev/ptmx`**:容器里节点存在但 `open` 被
`EACCES` 拒(实测 `PermissionError: [Errno 13]`),而用例**没有环境探测、
直接 `t.Fatal`** → 应用 `t.Skip` 或 build tag 隔离,而不是硬 FAIL
2. **测试端口硬编码 `127.0.0.1:9890`** → 并行/残留实例即 `bind: address
already in use`,波及无关用例
3. **`TestRestoreFileFromBaseline` 写真实系统路径且忽略错误**:
`internal/system/system_test.go:83` 的 target 是
`"/etc/RestoreFileFromBaseline.test.tmp"`,而用例内
`os.WriteFile(target, ...)` **不检查 err**(`os.WriteFile(target, []byte("v1"), 0644)`);
在 `/etc` 不可写的环境(实测本机 root 也被拒)里,前面的写全部静默失败,
留档内容为空/不存在,到 `RestoreFileFromBaseline` 就报
`expected restore to happen`。**根因是测试写死真实路径 + 吞错误**,
不是 `RestoreFileFromBaseline` 实现有问题。
**证据**:
- `internal/plugins/integration_test.go:262 / :318 / :376`(PTY 三例)
- `internal/plugins/remotedevice/plugin.go:27`:`const defaultAddr = "127.0.0.1:9890"`
(测试沿用固定端口)
- 实测输出:`open /dev/ptmx: permission denied`、
`listen tcp 127.0.0.1:9890: bind: address already in use`
**子任务**:
1. PTY 用例:无 `/dev/ptmx` 时 `t.Skip`(环境能力探测,不静默)
2. remotedevice 相关测试改用 `:0` 让 OS 分配端口,或测试内随机端口
3. `TestRestoreFileFromBaseline`:改用可写的临时路径(并保留
`IsProtectedPath` 语义所需的显式前缀,用 `IsProtectedPathExplicit`),
且**每一步 WriteFile 都检查 err**;顺带审计同类「写真实系统路径」的测试
**验收**:
- [ ] 在无 PTY 权限、`/etc` 不可写的环境里,`go test ./...` 全绿
(受限用例显式 skip,不是静默通过)
- [ ] 重复/并行跑不再端口冲突
---
## 三、已关闭 / 已实现(旧档误标,防复活)
逐条给出「旧档怎么说」与「源码实际怎样」,**不要再往待办里加**。
### 1. §13.12 L3 原生多模态 —— 已实现
- 旧档:标「未做」,要求 media 成为一等图节点 + contains/depicts/derived_from 原生边
- 实际:`internal/memory/graph.go:169` 起建 `memory_blocks`(含
`modality/payload_digest/mime/vector/fingerprint/scene`)+
`memory_block_edges`(`source_kind/target_kind/edge_type`),
以及 `scenes` / `scene_features` / `scene_refs`;
`internal/agent/core/graphmedia.go`(310 行)实现
`migrateLegacyGraphMedia` / `attachBlocksToSentence` /
`linkBlocksToDocument` / `commitTriplesWithMedia`;`graphmedia_test.go` 21 个测试
- 结论:**关闭**
### 2. §13.9 llmsproxy 上下文溢出感知 —— 不属本仓(见 §五.1)
### 3. §13.10 AgentMail 三个 bug —— 不属本仓(见 §五.2)
### 4. §11.4 Lua stage 快照缺读锁(DATA RACE)—— 已修
- 旧档:要求加 `sc.RLock()`/`sc.RUnlock()` 包裹快照构造
- 实际:`internal/plugin/proc/shmcodec.go:42 / :326 / :425` 已在
`captureLocal` / `WriteDirty` 前后持读锁
- 实测:`go test -race ./internal/lua/... ./internal/plugin/...` 全绿
- 结论:**关闭**
### 5. §12.2 `io.setToolBlocks` 内核侧是桩 —— 已实现
- 旧档:要求实现内核侧 handler + 补 example
- 实际:`internal/plugin/proc/corehandler_inject.go:132` 实现
`MethodIOSetToolBlocks`;模板 `putArena` → `blocks_ref`;
`e2e_template_test.go:371` 用**真实 SDK 模板**验证
- 结论:**关闭**
### 6. §13.11 WebUI 修复清单 —— 主体已实现,仅剩 P1-6
逐项核实:`Last-Event-ID` 重放(`handler_chat.go:710`)、请求超时
(`handler_chat.go:592` 等 300s)、XSS 消毒(`dashboard.js:252` DOMPurify)、
`renderAll` 增量(`dashboard.js:34 / :452 / :1361` 增量游标 + 流式增量)、
`handleKnowledge` 不再吞错(`handler_memory.go:122` 起逐分支返回错误)、
CSS/DesignSystem(`dashboard.css:3` 起 sakura/frost 令牌)、
GUI 重构(`cmd/gui/renderer/app.js`)—— 上述均已落地。
**仅 `handleAgentAction` 501 是真缺口**(已列 P1-6)。
### 7. §12.5 Windows 桩未删 / 旧档称「交叉编译通过」 —— 已收敛
- 实际:`internal/plugin/dynamic_proc.go` 已合为平台中立单文件,
平台差异封装在 `shmalloc_*` / `evtfd_*` / `shmpass_*` / `procattr_*`(各带
构建标签);旧桩已删。**真机验证仍缺**(已列 P2-8)
### 8. §12.7 `cmd/ohos/.../SettingsPage.ets` 有未提交改动 —— 已提交
- 实际:`git status --short` 于工作区全清(含 `cmd/ohos/`)
---
## 四、生产部署后验证(代码已就绪,本就无法在仓库内完成)
这些**不是待开发项**,是「必须落到生产实例才能确认」的验收。仓库内有
脚本 `scripts/verify_deploy.sh [data_dir]` 可一键检查前两条。
- [ ] **§0.1 healthcheck 隔离**:部署后 `knowledge/`、`memory/graph.db`、
`memory/documents/` 不再出现 `_hc_*` 残留
- [ ] **图记忆去重**:`relations` 重复率归零,跑一周不新增重复
- [ ] **§13.5 / §13.6 QQ 端到端**:真实 QQ 消息注入与输出经共享内存通道正常
(小 payload 内联、大 payload 走 `text_ref`/`frame`)
- [ ] **§0.2 agentcli 不泛滥**:QQ 消息在 agentcli 无自喂送风暴时能被正常响应
---
## 五、跨项目工单(**不属本仓**,旧档误并入)
旧 plan.md 把别仓的工单写成本仓 TODO,导致「查无此代码却挂着未完成」。
移出并说明归属:
### 1. §13.9「llmsproxy 上下文溢出感知」
- 旧档写:补 `OVERFLOW_PATTERNS`("Context window is full")、
AUTO 截断宽度 `80→160`、`go test ./internal/ai/...`
- 事实:本仓**没有 `internal/ai/`**;`OVERFLOW_PATTERNS` /
`clientUpstreamErr` / `OneLine(...,80)` 都在 **`/home/program/llmsproxy`**
(`internal/gateway/chat.go`)
- 现状:该仓已把宽度改成 160(`chat.go:634` 注释「160 而不是 80」)
- 归属:**llmsproxy 仓**,与本仓无关
### 2. §13.10「AgentMail 三个 bug」
- 旧档写:提示词修正 / `InReplyTo` / `relay_key ≤ 64 字节`
- 事实:AgentMail 是独立仓 **`/home/program/agentmail`**
(`relay_key` 见 `server/internal/handler/permission.go:32 / :90 / :382`)
- 现状:`relay_key` 上限已实现为 **160 字节**并带测试
(`permission.go:90`、`relay_test.go:55`),`in_reply_to` 语义见
`forward.go:225`
- 归属:**agentmail 仓**
> 若这两仓也要纳入统一管理,应各自建 plan,不要塞进本仓文档。
---
## 六、明确「不做」的决定(避免反复挂账)
- **§13.13 反向结果入共享内存**:原设想把 `doc.query` / `llm.chat` 的大结果
也搬进段。核实后:**本仓根本不存在 `llm.chat`**(`llm.*` 只映射
listSources/setSource/currentSource);唯一可能返回大结果的 `doc.query`
被 `CapDocMemory` 能力门挡着,且无任何外部插件使用。**不做**,
而不是留成永久 TODO。
---
*重写时间:2026-09-24 · 核实方式:源码 grep / build / test / race*
*旧档备份于 `/tmp/plan.md.bak-*`(仅作对照,内容已判定过时)*