mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 06:47:29 +00:00
1. 内核控制通道晚绑定(真机 503 实测) POST /api/v1/kernel/restart 一直返回 503:webui 由 plugin-host 在装配期 拉起,而 main() 里 SetKernelControl 还没执行 ⇒ 插件 Start 拿到 nil。 依赖图保证不了 main 里的语句顺序,所以改成让插件拿**解析器** (lazyKernelControl),调用那一刻再取真实通道;授权仍在每次调用时 按 kernelControlAllowed/kernelRestartAllowed 复核,不因晚绑定放宽。 2. WebUI 重启/关机按钮布局 原先用 .theme-btn(顶栏图标样式:pill + margin-right + 无文字)混在 侧栏底部的文字按钮里 ⇒ 大小不一、挤在一起、关机看不出是危险操作。 改为与「退出登录」同族的 btn-sm,带图标+文字,关机用 danger 悬停。 3. plugin_start 失败报错 原来只说加载出错,或插件不存在,一个可用名字都不给,模型只能盲猜 (实测把 files 写成 file)。现在给:失败原因分类(目录不存在/没清单/ 自身加载失败)、最接近的名字(Levenshtein ≤2)、已安装清单(最多 12)。 4. scripts/migrate-plugins-to-org.sh:插件迁到 GitHub 组织独立仓的脚本收口 测试:go build ./... 通过;go test ./internal/plugin ./internal/plugins/webui 全绿。
74 lines
2.2 KiB
Go
74 lines
2.2 KiB
Go
package plugin
|
||
|
||
// 内核控制通道**晚绑定**的判据。
|
||
//
|
||
// 现场(2026-10-26 真机):WebUI 重启按钮点击返回 503。
|
||
// 日志:POST /api/v1/kernel/restart auth=session status=503
|
||
//
|
||
// 根因不是授权,而是**时序**:webui 由 plugin-host 在装配期拉起,
|
||
// 而 main() 里 SetKernelControl 那一行还没执行 ⇒ 插件 Start 时拿到 nil。
|
||
// 靠"调整顺序"修不了(依赖图不保证 main 里的语句顺序),
|
||
// 只能让插件拿**解析器**,调用那一刻再取真实通道。
|
||
|
||
import (
|
||
"testing"
|
||
|
||
sdk "github.com/JianFeeeee/HologramAgent/internal/sdk"
|
||
)
|
||
|
||
type fakeCtl struct {
|
||
restarts int
|
||
shutdowns int
|
||
}
|
||
|
||
func (f *fakeCtl) RequestKernelRestart(reason string) error {
|
||
f.restarts++
|
||
return nil
|
||
}
|
||
func (f *fakeCtl) RequestKernelShutdown(reason string) error {
|
||
f.shutdowns++
|
||
return nil
|
||
}
|
||
|
||
// ① 先给通道、后注入 ⇒ 调用时仍能成功(这是真机那条修复的核心)。
|
||
func TestLazyKernelControlWorksWhenInjectedLater(t *testing.T) {
|
||
r := NewRegistry()
|
||
ctl := kernelControlFor("webui", r) // 此时 registry 里还没有通道
|
||
if ctl == nil {
|
||
t.Fatal("按授权表 webui 应拿到控制通道(晚绑定)")
|
||
}
|
||
|
||
// 注入前调用 ⇒ 明确报不可用(不能静默成功)
|
||
if err := ctl.RequestKernelRestart("before"); err == nil {
|
||
t.Fatal("未注入时应报错,不能假装成功")
|
||
}
|
||
|
||
fake := &fakeCtl{}
|
||
r.SetKernelControl(fake)
|
||
if err := ctl.RequestKernelRestart("after"); err != nil {
|
||
t.Fatalf("注入后应成功,实际: %v", err)
|
||
}
|
||
if fake.restarts != 1 {
|
||
t.Fatalf("应调用真实通道 1 次,实际 %d", fake.restarts)
|
||
}
|
||
}
|
||
|
||
// ② 晚绑定**不放宽授权**:未获授权的插件仍然拿不到通道。
|
||
func TestLazyKernelControlRespectsAuthorization(t *testing.T) {
|
||
r := NewRegistry()
|
||
if ctl := kernelControlFor("some-random-plugin", r); ctl != nil {
|
||
t.Fatal("未授权插件不应拿到控制通道")
|
||
}
|
||
// pluginmgr 只有重启权限:关机必须被拒
|
||
r.SetKernelControl(&fakeCtl{})
|
||
pm := kernelControlFor("pluginmgr", r)
|
||
if pm == nil {
|
||
t.Fatal("pluginmgr 应有仅重启通道")
|
||
}
|
||
if err := pm.RequestKernelShutdown("x"); err == nil {
|
||
t.Fatal("pluginmgr 不应能关机")
|
||
}
|
||
}
|
||
|
||
var _ sdk.KernelControlAPI = lazyKernelControl{}
|