Files
HomeAgent/internal/kernelmod/lua/mod.go
JianFeeeee 41926e770d fix(lua): 适配器永久落后 —— 修死代码 + 改为显式 opt-in 同步
## 缺陷:适配器会永久卡在旧版

writeBundledAdapters 用清单里的 prevHash 判断「盘上有没有被人动过」,
被动的那个就保留。但清单只存 hash ——「内置旧版本的 hash」与
「用户改过的 hash」在清单里长得一模一样。于是:

    记了盘上那份当新基线 ⇒ 下次 onDisk == prevHash
      ⇒ 被误判成「没跟上新版本」⇒ 覆盖掉用户改动
    而保留分支里的 cur[name] = onDisk 又会

## 被循环末尾无条件 `cur[name] = sum` 冲掉

⇒ 判「用户改过」后清单仍记内置 hash ⇒ 下次再判一遍「用户改过」
⇒ **永久**停在旧版。日志只有一句「保留用户版本」,
看起来像用户自己的选择,完全看不出是卡住。

本机实测 8 个适配器中招(openai/anthropic/github/groq/mistral/ollama/
kimicode/server),落后内置版 3000~6500 字节,缺的是用量归一化
(cache_read / reasoning_tokens)、流式 tool_calls 等真实修复。
症状最终表现为「模型不听话」「工具调不动」——指不到适配器身上。

## 为什么不能靠「自动覆盖」解决

「旧发行版残留」与「用户手工改过」在文件内容上无法区分(都只是
hash 不等于内置版)。擅自改用户盘上的文件,比落后一个版本更糟。

所以改成**显式 opt-in**:

    core.lua.adapter_sync = off(默认)  不碰文件,但告警必须可操作
    core.lua.adapter_sync = auto         差异即备份 .bak-<hash8> 后覆盖

零值 = off:忘记注入配置时宁可不动用户文件,也不能默默全量覆盖。

## 告警要可操作,不是「保留用户版本」一句话

教训(2026-10-26 + 2026-11-11 两次):这类静默落后最难查,因为症状
指向模型而不是适配器。现在会给出:数量、名单、影响、三条处理路径
(跟随内核 / 长期定制改 adapter_path / 删掉旧文件)。

长期定制的正道是 `core.llm.sources.<name>.adapter_path`(每源独立可配),
不需要改内置位置的同名文件 —— 这一点也写进告警里。

## 判据 7 条,均经变异验证

    TestAdapterSyncDefaultsToOff            零值/未注入必须是 off
      (变异:return "off" → "auto" ⇒ 红)
    TestOffModeLeavesDiskUntouched          off 不碰文件、不产生备份
      (变异:case mode=="auto" → case mode!="" ⇒ 红)
    TestAutoModeUpdatesWithBackup           auto 更新且备份可回滚
    TestAutoModeNoRedundantBackup           一致时不重复堆 .bak
    TestAdapterStateStableAcrossRestarts    两种模式启停 3 次不漂移
    TestManifestCoversAllBundledAdapters    清单不漏项
    TestFirstInstallHappensInBothModes      off 只「不动已存在文件」,≠不安装

    go build / vet / gofmt / go test -race  全绿

## 生产现状(已按用户确认保留更新)

    适配器 10/10 与内置一致,8 个 .bak-<hash8> 可回滚
    服务 active,NRestarts=0
2026-11-11 21:39:03 +08:00

46 lines
1.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

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.

// Package lua 是 lua 模块:Lua VM(LLM 协议适配)。
//
// 依赖 config:`initLuaVM(cfg)` 读 cfg.Daemon.DataDir(adapter 目录)。
package lua
import (
"log"
"path/filepath"
"github.com/JianFeeeee/HologramAgent/internal/kernelmod"
"github.com/JianFeeeee/HologramAgent/internal/kstate"
luapkg "github.com/JianFeeeee/HologramAgent/internal/lua"
)
// Module 提供 Lua 协议适配 VM。
type Module struct{}
func (Module) Name() string { return "lua" }
func (Module) Requires() []string { return []string{kernelmod.FacConfig} }
func (Module) Provides() []string { return []string{kernelmod.FacLua} }
func (Module) Init(ks *kstate.State) error {
luaVM := luapkg.NewVM(filepath.Join(ks.Cfg.Daemon.DataDir, "adapters"))
// 适配器同步策略(core.lua.adapter_sync,默认 off)。
//
// 读不到就保持零值 = off(保守):忘记注入时宁可「不动用户文件」,
// 也不能默默把用户的 adapters/ 全量覆盖一遍。
if ks.CfgReg != nil {
if v, err := ks.CfgReg.Get("core.lua.adapter_sync"); err == nil && v != nil {
if s, ok := v.(string); ok {
luaVM.SetAdapterSync(s)
}
}
}
if err := luaVM.Start(); err != nil {
log.Printf("[homed] warning: lua vm init failed: %v", err)
ks.LuaVM = luaVM
return nil // 与原语义一致:起不来只告警,仍交回实例
}
ks.LuaVM = luaVM
ks.AddCloser(luaVM.Stop)
return nil
}
func init() { kernelmod.Register(Module{}) }