|
|
817f6c59ae
|
fix(update): 重启交还 guard、OTA 支持 release/包两种来源、修三处真 bug
## 一、重启不再由 update 自己 systemctl(用户口径:应该通知 guard)
原 internal/update 直接 `systemctl restart homeagent`。三个问题:
① 杀掉整个 cgroup(unit 里 KillMode=control-group)⇒ **guard 跟着死**,
而 guard 存在的意义就是不让自己死。
② 丢失 exit 42 语义:guard 的 failures/failbackRound/failbackDone 全丢。
③ 自毁竞态:发命令的进程正是被重启杀掉的进程 ⇒ CombinedOutput 可能报错,
**一次成功的升级被误判失败而回滚**。
现在 internal/update 只到"产物就绪":预检 → 备份 → 写 pending → 原子替换。
重启由调用方走**既有**通道(restartCh → exit 42 → guard 用新二进制重拉):
· webui h.kernelCtl.RequestKernelRestart(本来就有,且是白名单内)
· pluginmgr p.sdk.KernelControl().RequestKernelRestart(见下面第三条)
· CLI 外部管理命令,restart unit 后按**版本自报**核对是否真生效
## 二、OTA 支持两种来源(release 自动更新 / 指定安装包)
新增 internal/update/source.go:
Source{Kind: FromPackage, Path} → 直接用本地包
Source{Kind: FromRelease} → 查最新 Release → 下载 → 校验 digest
ParseSource(pkg, release) 统一判空规则(pkg 优先),避免三端各写一套。
两条来源解析后进**同一条** Apply,不存在两份升级实现。
## 三、修三处已提交代码里的真 bug
① **HEAD 编译不过**:d17e261 提交了 kernelmod/update/mod.go(引用 ks.Update),
但 kernelboot/kernelstate.go 的字段改动从未提交。干净 worktree 实测
`ks.Update undefined` —— 推上去的仓库是坏的。本次补上。
② **kernelDataDir 算错一级**:Dir(Dir("/home/newqqagent/plugins")) = "/home",
而第 0 层 update 模块从 ks.DataDir=/home/newqqagent 读 pending
⇒ **agent 发起的升级一旦启动失败就永不回滚**(静默失效)。改为 Dir 一次。
③ **pluginmgr 的 KernelControl 恒为 nil**:它不在 kernelControlAllowed 里,
于是 `if kc := p.sdk.KernelControl(); kc != nil` 永不成立 ⇒
换完二进制**从不重启** ⇒ pending 留着 ⇒ 下次重启被判"未确认"而回滚。
修法**不是**把 pluginmgr 加进完整白名单 —— 那等于让 LLM 能关机整个服务
(kernel_control_gate_test 明确拦这个)。而是拆出**仅重启**授权:
kernelControlAllowed = {webui} 完整(含关机)
kernelRestartAllowed = {pluginmgr} 仅重启(shutdown 直接报错)
两者的危险程度差一个量级:重启几秒后 guard 自己拉回来,关机不会自己回来。
## 四、EventBus:插件请求更新不必 import internal/update
新增 events.EventUpdateRequest,pluginmgr 订阅并受理:
scope=kernel → kernelUpdateApply;scope=plugin → pluginUpdate(apply)。
它在 internal/plugins/webui 的 SSE 守卫里显式登记为**内核内部控制事件**
(不该推给浏览器:前端要的是 update_progress/update_result,不是"谁发了请求")。
## 五、顺手修掉的静默失败
· webui /kernel/update 原实现:**先**写 202 Accepted,**再**解析 body,
之后每个错误都直接 `return` —— 用户看到"已受理"然后什么都没发生。
改为同步 Apply(失败可如实报 HTTP 状态),成功后才请求重启。
· pluginmgr 拿不到控制通道时不再假装成功,而是回报 applied=true + 明确原因
(否则用户以为已升级,进程却还在跑旧镜像)。
· update 事件 publishResult 的 errMsg 原先是死代码(`_ = errMsg`),
按 payload 约定写回 error 字段。
|
2026-10-26 13:45:54 +08:00 |
|
|
|
d65b742bd7
|
fix(plugin): 修两处持锁自死锁(CI 双红真因)+ tools_ready 事件声明
## 死锁(这是 CI 连续两次红的真因)
`StopAll` 与 `StopPlugin` 都在持有 `r.mu` **写锁**时调用
`RemoveBuiltinSkill` → `skillDirOf` → `r.mu.RLock()` ⇒ **自死锁**。
后果不是报错而是**卡死**:
- internal/plugins 卡 300s 超时(TestCapability_RestrictedPluginStillLoads)
- internal/plugin 卡 840s 超时(TestStopPluginDoesNotTouchPersistentState)
- CI 的 `go test ./...` 与 `go test -race` 双双红
修法:拆出**不加锁**的内部版本,持锁路径只走它:
- `skillDirFrom(dataDir, name)`(纯函数)
- `removeBuiltinSkillLocked(pluginName, p, dataDir)`(显式传 dataDir,
避免"看起来没取锁、一改就取锁"的隐患)
- `announceSkillRemovedLocked`(持锁下直读 r.evBus)
修后:internal/plugins 300s → 12s,internal/plugin 840s → 0.7s。
## 防回归判据
`TestStopAllAndStopPluginDoNotDeadlock`:死锁**不返回**,普通断言抓不到,
所以用**超时**当判据(5 秒未返回即失败),并指明应改用 Locked 版本。
## tools_ready 事件声明
新增的 tools_ready 触发 `TestEveryPublishedEventHasAnOutwardPath`
(该判据防"事件发了、客户端收不到"的静默失效)。
tools_ready 是**内核内部**就绪信号(plugin-host → seq 等订阅者),
不是对话流程事件,前端无需感知 ⇒ 在 allowed 里显式声明理由。
## 验证
go build ./... / go vet ./... / go test -p 1 ./... / -race 全通过。
|
2026-10-25 18:25:37 +08:00 |
|
|
|
f256098c9f
|
refactor: 全面改名为 HologramAgent + 提示词技术内容下沉到 skill/工具描述 + 修 21 处乱码
## ① 全面改名 HomeAgent → HologramAgent
仓库已改名,但代码与文档里仍有大量旧名。本次按"用户能看到的优先"全量替换:
- 用户可见:登录页标题/页脚、默认人格第一句、CLI 状态输出、启动日志、
plugin_install 的工具描述、AI 图片插件的 User-Agent
- 文档与脚本:README、assets/docs、docs/zh、deploy/*.sh、.github/workflows
- 鸿蒙工程:cmd/ohos/HomeAgent → cmd/ohos/HologramAgent(git mv)
目录内的 module/app 描述同步;**证书路径原样保留**
(default_HomeAgent_xxx.cer/p7b/p12 是签名材料的实际文件名,改了会找不到证书)
- 插件元数据:plugin.json 的 author
★ 必须保持原样的两处(改了会坏):
· legacySeededPromptSignature = "你是 HomeAgent 的看板娘「小宅」…"
—— 那是识别历史播种人格卡的**原文锚点**,改了就识别不出旧数据
· 公开 SDK 的 ProxyAuthHomeAgent = "homeagent"
—— 那是 SDK 契约里的值,属独立仓库,本仓不改
## ② 修 21 处乱码(U+FFFD)
这些是历史编辑留下的编码破坏,全在注释里,例如:
「赌注重下���遍」→「赌注重下一遍」
「「不��滤」」 →「「不过滤」」
「**待��退场**」→「**待退场**」
★ 保留 providers/chineseclip/tokenizer.go 里的 2 处:那是**故意**用
U+FFFD 演示"非法 UTF-8 字节被归一成 U+FFFD",不是乱码。
## ③ 提示词技术内容下沉(用户:技术性内容应该收敛到 skill 与 -help 中)
实测系统提示词 2309 字,其中两段几乎全是**模型无法据以行动的实现细节**:
【工具执行顺序】1272 字(ParallelSafe 声明、批内降级、batchRunnable 判据…)
【三层记忆】 1069 字(L1/L2/L3 编号、消费式删除、向量空间、指纹…)
收敛后 **2309 → 1723 字(-25%)**,保留的全是"模型据此做什么不同的事":
工具顺序:默认并行 / 不要依赖先后 / 同通道保序 / 写类工具可能串行
三层记忆:时序里能看最近 / doc_query 把历史事件按原始时间戳插回时序 /
关系用 memory_recall / 成文知识用 knowledge_search
判断口径写进判据:新加一段提示词前先问"模型会据此做什么不同的事",
答不上来就该进 skill 或工具描述。
## 判据
prompt_memory_test.go 新增 2 条:
- TestSystemPromptHasNoImplementationJargon
黑名单:ParallelSafe / batchRunnable / 消费式 / DenseCosine / fingerprint /
向量空间 / 批内降级 / 自激
- TestSystemPromptStaysCompact:上限 2000 字(当前 1723)
变异:把 "ParallelSafe 声明、批内降级" 加回工具顺序段 ⇒ 第一条立刻红。
## 验证
- go build ./... / go vet ./... 干净
- go test ./... -count=1:58 包 0 失败
- make check-client-versions:客户端与内核版本一致
|
2026-10-22 20:49:44 +08:00 |
|
|
|
50ef27891f
|
fix(clients): 三方客户端与内核的 SSE 协议一致性 —— 幽灵类型、发了没人收、客户端漏接
一次三方(CLI/GUI/鸿蒙)与内核的协议审计,发现四处**全部静默**的不一致
(无任何测试或日志会报),本次一并修复并把它们变成可执行判据。
## 四处不一致
1. `agent_error` 是**幽灵类型**:SSE 白名单订阅了它、鸿蒙还写了处理分支
(显示「本轮处理失败」),但全仓**无任何发布者** —— events 包与 SDK 都
没有这个常量。于是那段分支永远不可达,而真正的失败帧叫 `error`
(webui/cli 都发 `{type:"error"}`),鸿蒙从未接住。
2. `system` **发了没人收**:internal/plugin/dynamic_proc.go 一直在发插件崩溃
(注释还写着「webui/诊断插件可订阅」),却不在 SSE 白名单里 ⇒ 事件发出来
就掉地上,插件崩溃对用户完全不可见。
3. CLI 缺 `stage`/`system`:CLI 是唯一看不到阶段进展的客户端,agent 跑长任务
时终端长时间静默,用户分不清「在干活」还是「卡住了」。
4. 鸿蒙设备桥**静默丢弃**:只处理 bind_ack 与 cmd,服务端下行的
push / cmd_speech_start / cmd_speech_end / hello_ack 直接 return ——
「收到了但不支持」与「根本没收到」在日志上完全一样,无从排查。
## 修法(注意:三方客户端的通信模型不同,清单不可一刀切)
CLI 走 InjectTextSync **同步拿最终回复**(response 帧)+ 事件订阅做过程输出,
**不做 SSE 重连** ⇒ 它需要 stage/tool_call/reasoning/delta,
但**不需要** agent_output(正文来自同步回执)与 sync_required(那是 SSE
断线重连专用的补拉信号)。GUI/鸿蒙是真 SSE 客户端,两者都需要全量对话事件。
第一版判据按同一份清单要求三者,误报了 CLI —— 那是判据的错,不是代码的错。
- handler_chat.go:subTypes 删 `agent_error`、补 `system`
- dashboard.js / ChatSse.ets:补 `system` 消费(WebUI 用 toast,鸿蒙用 setStage)
- ChatSse.ets:幽灵分支 `agent_error` 改为真实类型 `error`
- cli/plugin.go:补 `stage`(过滤 `_consolidation_`)与 `system` 订阅
- cmd/waiter:补 Phase/Event/Plugin 字段与 stage/system 渲染
- DeviceBridge.ets:静默丢弃改为带 hilog 日志(区分「不支持」与「没收到」)
## 判据(internal/plugins/webui/handler_sse_protocol_test.go,7 条)
TestSSESubTypesAreRealEventTypes(幽灵类型)/
TestEveryPublishedEventHasAnOutwardPath(发了没人收,例外须显式声明理由)/
TestClientsRecognizeServerEventTypes(按客户端分档)/
TestNoPhantomFrameTypes / TestStageFrameFieldsMatchServer /
TestSseWhitelistParsesToExpectedSet(**解析器反向自检** —— 解析器失效会让整组
判据静默变成空集并全绿,是最危险的假绿)/
TestDeviceBridgeOpCoverage。已接入 `make check-client-protocol`(进 make test)。
变异 M1–M8 全部验证。其中 M5 首轮未报红:判据只查前缀 `json:"phase`,
把 tag 改成 `phaseX` 仍匹配 ⇒ 改为匹配**完整 tag** 后才咬住。
## 端到端实测(不只看测试绿)
- 真实 SSE 流收到 stage×15 / content_delta×111 / memory_access×3 /
agent_llm_chain / tool_call×2,**agent_error 0 次**(幽灵已清)
- kill 一个 plugin.bin → SSE 收到 `event: system`
`{"event":"plugin_crashed","plugin":"a2a"}`,日志确认「自动重启 a2a」
- WebUI 真弹出 toast「插件 acp 异常退出,内核正在重启它」(MutationObserver 捕获)
|
2026-10-13 16:20:28 +08:00 |
|