mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-06 09:17:31 +00:00
fix(plugin): 外部插件的 auto_start 从未生效 —— 补三态启动门
## 缺陷 `plugin.json` 的 `auto_start` 一路写得出来(hmapdev 打包会写), 但 **Load 的目录扫描从不读它**:`loadOne` 里只有持久禁用门与运行态门。 也就是说这个字段在**外部插件上完全不生效** —— 声明 `auto_start: false` 的插件照样开机自启。 (内置模块那条走 `kernelboot/scheduler.go` 的 `p.AutoStart`,与外部插件 是两条路,所以缺陷只暴露在外部插件上。) ## 三态语义(现在三条都成立) 自动启动 auto_start 缺省/true + 启动扫描 ⇒ 拉起 启用但不自启 auto_start=false + 启动扫描 ⇒ 跳过 同上 + plugin_start ⇒ 拉起 禁用 持久禁用清单 + 任何路径 ⇒ 拒绝(plugin_start 也拒) ★ `explicitStart` 是这次的关键设计点:`auto_start: false` 的含义是 "**别在开机时拉起我**",不是"永远不许启动"。少了这个区分, 按需加载的插件就再也起不来了 —— 比不声明更糟。 ## 判据(4 条) · auto_start=false 在启动扫描时被拦(且不调用 Start) · 同一插件经显式启动**真的加载并 Start**(plugin_start 可用) · 未声明 auto_start ⇒ 自启(不误伤存量插件) · plugin.json 读不到 ⇒ 默认自启(清单损坏不该让插件静默消失) ## ★ 判据第一版是假绿(变异不咬) 第一版直接断言 `loadOneOpt` 返回 true/false。但 **false 有两个来源**: ① 被 auto_start 门拦住(正确)② 没有 plugin.bin 加载失败(噪音)。 两者无法区分 ⇒ 删掉 auto_start 门后判据照样通过。 改成用 `RegisterNative` 注册一个**真能 Start 的**假插件: 返回 true 必然意味着门没拦它,返回 false 必然意味着门拦了它,语义唯一。 修完两个变异都准确咬住(删门 / 忽略 explicitStart)。
This commit is contained in:
@ -574,6 +574,21 @@ func pluginEntryHash(plgDir string) string {
|
||||
}
|
||||
|
||||
func (r *Registry) loadOne(plgDir, name string) bool {
|
||||
return r.loadOneOpt(plgDir, name, false)
|
||||
}
|
||||
|
||||
// loadOneOpt 是 loadOne 的显式版本。
|
||||
//
|
||||
// explicitStart=true 表示这是**agent 显式拉起**(plugin_start),
|
||||
// 此时**忽略 auto_start**:插件声明"不自动启动"是"别在开机时拉起我",
|
||||
// 不是"永远不许启动"。
|
||||
//
|
||||
// ★ 2026-10-18 补:在此之前**外部插件的 auto_start 完全不生效** ——
|
||||
// Load 的目录扫描无条件调 loadOne,没有任何地方读 plugin.json 的
|
||||
// auto_start。也就是说这个字段在 plg.json → plugin.json 一路写得出来,
|
||||
// 内核却从不看它:声明 auto_start=false 的插件照样开机自启。
|
||||
// (内置模块那条走的是 kernelboot/scheduler.go,与外部插件是两条路。)
|
||||
func (r *Registry) loadOneOpt(plgDir, name string, explicitStart bool) bool {
|
||||
if r.isDisabled(name) {
|
||||
log.Printf("[plugin] %s is disabled, skipping", name)
|
||||
r.mu.Lock()
|
||||
@ -593,6 +608,25 @@ func (r *Registry) loadOne(plgDir, name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ③ auto_start 门(2026-10-18 补:此前外部插件从不检查这个字段)。
|
||||
//
|
||||
// plugin.json 的 auto_start=false 表示"**启用**但不随内核自启",
|
||||
// 用于按需加载的插件(纯工具型,没人调用时不该占进程与工具槽位)。
|
||||
//
|
||||
// 三种启动形态在这里交汇:
|
||||
//
|
||||
// 自动启动 auto_start 缺省/true + 启动扫描 ⇒ 拉起
|
||||
// 启用但不自启 auto_start=false + 启动扫描 ⇒ **跳过**(本门)
|
||||
// 同上 + plugin_start ⇒ 拉起(explicitStart)
|
||||
// 禁用 持久禁用清单 ⇒ 上面的 isDisabled 门挡住,连 plugin_start 也拒
|
||||
//
|
||||
// ★ explicitStart 是关键:声明 false 是"别在开机时拉起我",
|
||||
// 不是"永远不许启动"。没有这个区分,按需插件就再也起不来了。
|
||||
if !explicitStart && !r.shouldAutoStart(plgDir, name) {
|
||||
log.Printf("[plugin] %s 声明 auto_start=false(按需加载),跳过启动扫描;用 plugin_start 可拉起", name)
|
||||
return false
|
||||
}
|
||||
|
||||
// 1) 查找工厂(init 自注册或 RegisterNative)
|
||||
r.mu.RLock()
|
||||
factory, hasFactory := r.factories[name]
|
||||
@ -1064,7 +1098,7 @@ func (r *Registry) ReloadOne(name string) error {
|
||||
r.detachPlugin(name)
|
||||
r.closeDynamic(removed)
|
||||
|
||||
ok := r.loadOne(plgDir, name)
|
||||
ok := r.loadOneOpt(plgDir, name, true)
|
||||
if !ok {
|
||||
return fmt.Errorf("reload plugin %s failed", name)
|
||||
}
|
||||
@ -1278,6 +1312,18 @@ func (r *Registry) DisablePlugin(name, by string) error {
|
||||
|
||||
func (r *Registry) EnablePlugin(name string) error { return r.Enable(name) }
|
||||
|
||||
// shouldAutoStart 读 plugin.json 的 auto_start(缺省 = true)。
|
||||
//
|
||||
// 读不到 manifest 时返回 true:宁可多启动一个(与旧行为一致),
|
||||
// 也不要因为清单文件损坏而让插件静默不启动 —— 后者更难查。
|
||||
func (r *Registry) shouldAutoStart(plgDir, name string) bool {
|
||||
mft := readManifest(plgDir)
|
||||
if mft == nil {
|
||||
return true
|
||||
}
|
||||
return mft.ShouldAutoStart()
|
||||
}
|
||||
|
||||
// isRuntimeStopped 报告插件是否处于**运行态关闭**(设计 §2.6.7.3)。
|
||||
func (r *Registry) isRuntimeStopped(name string) bool {
|
||||
r.mu.RLock()
|
||||
|
||||
Reference in New Issue
Block a user