|
|
892247946a
|
fix: staticcheck 24 条存量全部清零(含 4 个真实缺陷)
staticcheck 门接入时把存量设为基线 24 条,本次全部清掉,基线改为 0。
## 生产代码里的真实缺陷(4 个)
### ① memory/fusion.go —— BFS 的 depth 参数形同虚设
for d := 1; d <= depth; d++ {
neighbors, _ := g.NeighbourBlocks(seed.BlockID) // ← 永远是 seed
...
break // ← 只跑一轮
}
三处叠加:循环体末尾无条件 `break`;每层传的还是 `seed.BlockID`;
于是 depth=2 与 depth=3 召回结果**完全一样**。
staticcheck SA4008("变量在循环条件里从不改变")正是从这里报出来的 ——
它看出来 `d` 除了打分没有参与寻址。
改成真正的逐层 BFS(frontier 推进到上一层新发现的节点)。实测:
depth=1 → 2 个 [节点0 节点1]
depth=2 → 3 个 [节点0 节点1 节点2] ← 修复前停在这里
depth=3 → 4 个 [节点0 节点1 节点2 节点3]
补判据 `TestBFS召回_层深真实生效`:必须用**链**(n0-n1-n2-n3),
星型在 depth=1 就能召回全部叶子、断不出有没有走深。
变异(改回单层)⇒ 实测红:`★ depth=2 召回 2 个,期望 3`。
### ② nlp/extractor.go —— subjRels/objRels 只写不读
两个切片被 append 了 4 处,但**从未被读取**。三元组的关系类型来自
`relLabel := result.Tokens[vi]`,置信度来自 `result.DepRels[si]`,
与这两个切片无关。删除。
### ③ plugins/cmd/plugin.go —— 配置解析失败会把上限写成 0
if n, err := fmt.Sscanf(s, "%d", &p.maxOutput); err == nil && n > 0 {
} // ← 空分支
直接 Sscanf 到 &p.maxOutput:解析失败时它可能已被写成 0 或部分值,
而空分支无法回滚 ⇒ 配置写错一个字符就把输出上限变成 0
(下游 `maxLen := p.maxOutput` 直接拿它算截断长度)。
改为解析到临时变量、失败时保持默认并记日志。
### ④ cmd/homed/bootstrap.go —— 空的 else 分支
`if err != nil { log } else { }` —— 删掉 else。
## 测试判据里的真实缺陷(3 个)
### ⑤ stop_semantics_test —— 短路求值让第二次调用从不执行
if !a.sched.consumeCancel() || !a.sched.consumeCancel()
第一次返回 true 时第二次**根本不会执行**,于是"两条配额都可消费"
这条断言实际只测了一次。拆成两次独立调用(两处同型)。
### ⑥ seq/store_test —— 永假断言
if maxCallDepth != maxCallDepth { t.Fatal("maxCallDepth 不稳定") }
自己跟自己比,从诞生起就不可能失败。改成显式读两次比较。
### ⑦ pluginhost/catalog_test —— typed-nil 守卫永假
var nb *kstate.SyncBus
var iface kstate.SyncState = nb
if iface == nil { t.Skip(...) } // ← 恒 false
把 typed-nil 装进接口后,接口的 (type, value) 二元组都不为 nil。
这道前置守卫**从未生效** —— 而这正是该判据要演示的陷阱本身。
改用 `reflect.ValueOf(iface).IsNil()`。
## 其余(写法/冗余,无行为变化)
agent.go / context.go / toolresult_store_test / apply_regression_test
空 if 块(注释移到真实分支上方)
codec_chunkfast_golden_test 未使用的三个返回值 + 空 if
scheduler_critical/stack_test `if _, _ = f(); true { }` → 直接调用
overflow_test / upgrade_test 变量被整体覆盖,首次赋值未使用
dense_test m[string(x)] 替代临时变量
lex_rules_test range over string 而非 []rune(string)
## 验证
staticcheck -checks 'SA4*,SA5*,SA6*,SA9*' 0 条(基线 24 → 0)
go build ./... PASS
go vet ./... PASS
gofmt -l 干净
go test ./... -count=1 65 包全过,0 FAIL
干净检出(worktree) build + memory/nlp/core 全绿
|
2026-11-04 19:49:21 +08:00 |
|
|
|
4e70b8d5d8
|
refactor(module): 核心仓 Go module 路径改为 github.com/JianFeeeee/HologramAgent
批 1,不含 SDK 仓与目录改名。
- go.mod 第 1 行 module 路径
- 366 个 .go 文件的完整前缀 github.com/JianFeeeee/HomeAgent 机械替换
(857 处,仅完整前缀;展示名/注释里的 HomeAgent 字样留给批 5)
- 未改:third_party 目录名、SDK 自身 go.mod module、go.mod replace 目标、
homed/waiter 可执行名、.hmap 后缀
|
2026-10-21 12:17:19 +08:00 |
|
|
|
6d38a164eb
|
chore(module): Go module 路径随托管地迁移 gitcode → github
## 背景
仓库早已迁移到 GitHub(`github.com/JianFeeeee/HomeAgent`,CI/CD 都在那边跑),
gitcode 保留为**国内只读镜像**(`ci.yml` 的 sync-gitcode-main 快进推送)。
但 go.mod 的 module 路径一直是 `gitcode.com/JianFeeeee/...` —— 既与权威源
不符,`meta.SourceURL`(AGPL §13 的源码提供入口)也指向镜像而非实际运行的代码。
## 关键区分(这是本次改动安全的前提)
两个 module 是**不同**的,影响面差一个数量级:
| module | 谁依赖 | 改它的后果 |
|---|---|---|
| `.../HomeAgent`(主仓) | **只有主仓自己** | 内部重构,零外部影响 |
| `.../homeagent-sdk`(SDK 仓) | 主仓 49 文件 + 所有外部插件 | 需重编插件 |
★ 实测确认:**插件不 import 主仓 module**(`strings qq/plugin.bin | grep HomeAgent` = 0),
只依赖 SDK 仓。而插件与内核的边界是 **RPC 方法名字符串**
(`plugin.init` / `tool.invoke`…,见 proc/protocol.go),**与 Go module 名无关**。
⇒ module 改名**不改变任何 wire 协议**,现网已装的插件照常运行。
SDK 的 GitHub 仓名是 `homeagentsdk`(无连字符),module 路径必须与之精确匹配,
否则 `go get` 会因路径不符失败,故取 `github.com/JianFeeeee/homeagentsdk`。
## 改动
- `go.mod` / `go.sum`:两个 module 路径 + require/replace
- 277 个 Go 文件 + Makefile + 打包脚本的 import 与 ldflags 路径
- SDK 仓 `meta.CoreModule`、`tools/hmapdev` 的 module 声明与工程模板
- 对外文档/链接的仓库地址(README、PLUGIN_DEV、OVERVIEW 等)
## 明确**保留** gitcode 的地方(镜像机制,不能动)
- `GITCODE_TOKEN`、`GC_URL="…@gitcode.com"`(CI 同步 job)
- `gitcode.com/api/v5/...`(release 附件上传)
- git remote `gitcode`(镜像拉取/推送)
- `docs/zh/ci-cd-runbook.md`(整篇讲镜像运维)
## 验证
- `go build ./...` / `go vet ./...` 绿
- `go test ./... -count=1` → 48 包 ok / 0 FAIL
- hmapdev 工具链编译通过(`third_party/homeagent-sdk/tools/hmapdev`)
- 全仓残留检查:module 形态的 `gitcode.com/JianFeeeee` = 0
- 镜像机制抽验:GC_URL / @gitcode / api/v5 / remote 均完好
## 顺带修正
`hmapdev/templates.go` 的注释此前写「gitcode 的模块不在 proxy.golang.org 上」——
module 已迁 GitHub,该论断不再成立。改为如实说明**为何仍默认 replace 到本机 SDK**:
离线可构建、避免版本漂移、内网可用。理由与托管地无关,故 replace 保留。
|
2026-10-08 10:40:17 +08:00 |
|
|
|
236e54a812
|
fix(stop): 配额须计入停在输入 channel 的待处理消息
实测:停止后排队消息仍逐条跑完。原因是 armStop 只数 sched.queue,
而用户按下停止时调度器正忙于当前任务,其余消息大多还没被 pumpInbox
搬进队列、仍停在 inputCh ⇒ queued=0、配额归零。
- IOManager.PendingInputs():暴露 channel 中待处理条数。
- armStop(pending int):queued = len(s.queue) + pending。
- 补 TestStop_ArmCountsPendingChannelInputs 锁死该口径。
|
2026-09-18 11:39:04 +08:00 |
|
|
|
867848d0ba
|
fix(stop): 修自伤——interceptLoop 里 takeStop() 被 || 短路提前消费
上一版把 stop 标记在 interceptLoop 里消费掉了一部分:
`if n := armStop(); n > 0 || takeStop() { ... }`,queued=0 时短路到
takeStop(),标记先被吃掉,stepLLM 永远看不到 → 取消后照样重跑一轮。
实测:日志正确打出 stop requested ... queued=0,但生成仍跑到自然结束(5000+ 字全文落库)。
改为只 arm 不 take(takeStop 只由 stepLLM 消费),并补一条**经真实
interceptLoop** 的用例:它必须 a.Start()(第一版测试只调 New(),
interceptLoop 根本没跑,假绿)。已验证该用例在注入此 bug 时失败、修复后通过。
|
2026-09-18 11:32:20 +08:00 |
|
|
|
bd6aefa4b5
|
fix(stop): 停止按钮真正生效——停止 ≠ 空中断;鸿蒙 screensue 支持 HTML
两处鸿蒙端缺陷 + 一个跨端(WebUI/GUI/鸿蒙)的停止语义缺陷。
## 症状(实测取证)
1. **鸿蒙终止按钮按下没反应**。POST /chat/interrupt 带空 body,接口回 200
`{"status":"interrupted"}`,但 journalctl 零中断日志、生成继续跑到自然结束。
2. **鸿蒙 screensue 不解析 HTML**,把标签当普通字符串显示。
## 根因
停止按钮走的是「空内容中断」,而 interceptLoop 有一行
`if text == "" { continue }` —— 空内容被判为「无事发生」直接丢弃。
所以停止指令从未到达调度器;接口那个 200 是不诚实的。
另查明两条会放大症状的既有问题(停止后仍在跑):
- `chatStreamWithFallback`:流式连接失败时无条件回退非流式 `Chat`。
上下文已取消时这等于**再发一次完整请求**(停止后模型继续生成)。
- `stepLLM`:`context.Canceled` 一律 `outcomeContinue` 重跑本步。
这是给「被更高中断抢占」用的(现场要交出去、稍后继续),
但用户按停止是「不要了」,重跑就是停止没生效。
## 修法(按用户明确的设计)
停止 = ①立即结束当前 LLM 推理(不重试、不恢复);
②对**停止那一刻已排队**的 x 条消息,后续在 pre-action 阶段依次短路。
- scheduler:新增 `armStop`(登记快照配额并返回当时排队深度)/`takeStop`/
`consumeCancel`。配额取快照值(停止后新到的输入不受影响),
重复按停止取 max 不累加(两个客户端同时按不该翻倍)。
- `interceptLoop`:读 `stop` 标记。停止时 armStop + cancelCurrentLLM;
**纯停止不再进中断队列**(旧实现把它当空中断入队,所以停完还会活)。
带注释的停止(`/stop 换个话题`)仍走中断路径。
- `stepLLM`:取消 + `takeStop()` → 直接 `outcomeDone`(不再重跑)。
- `stepPrepare`:`consumeCancel()` 命中即在 pre-action 短路收尾。
- `chatStreamWithFallback`:以 **ctx.Err()** 为判据拒绝回退(不是「错误是不是
Canceled」——很多 provider 用 Canceled 表示「不支持流式」,那种必须继续回退,
否则会把探测误判成取消;这条区分是跑全量测试时才暴露的)。
- WebUI handler / CLI `/stop`:空消息时带 `stop:true`。
## 鸿蒙端
- `BridgeCaps.ets`:新增 `looksLikeHtml`(首字符 '<' + 字母开头标签名,
避免误判 "<3" 这类文本)、`screensueHtml`、`escapeHtmlText`。
- `ScreensuePage.ets`:HTML 走 **RichText**(只解析 HTML 子集、无脚本无网络),
纯文本仍走 Text。不用 Web 组件:agent 下发的是第三方内容,
Web 默认带 javaScriptAccess/fileAccess,等于让远端内容在客户端执行脚本。
注入主题前景色,避免 RichText 用系统默认色导致深色主题下黑字不可见。
- `ChatSession.ets`:`interruptChat` 改发 `{stop:true}`(含类型声明,
ArkTS 禁止无类型对象字面量),并在本地即时复位忙态 + 提示「已停止」。
## 验证
- 新增 `stop_semantics_test.go`:停止终结任务不重试(provider 调用次数恒为 1)、
配额是快照(x 条短路、随后新到的不受影响)、重复 arm 取 max。
- `go test ./internal/... ./cmd/...` 全绿。
- 鸿蒙 HAP 构建通过;unsigned 包已装进模拟器(signed 包受
READ_PASTEBOARD 授权限制装不上,与既有记录一致)。
|
2026-09-18 11:26:12 +08:00 |
|