feat(plugin): 组合根装配「MCP 服务器」页(声明 + 动作处理器成对)
- wirePanelHub 在 AttachPanels / declareBuiltinPanels 之后调 AttachMCPPanel: 一处完成「声明页面」+「登记 7 个动作处理器」,缺任一步就是面板在、动作全 501。 - wire_panels_test 的两条「内置面板恰好 1 条」断言改成按 pluginID 定位 (web-search/providers),并加钉区块形状(form+table;viewer 只剩 table)。 接线落地后目录里确实是两条(mcp/10 + web-search/20),旧断言是事实过期而非回归。
This commit is contained in:
1 parent
9654ce8861
commit
2421a8dac9
2 files changed
+36
-6
No files matched your search
@@ -203,6 +203,13 @@ func wirePanelHub(a *app) {
|
||||
})
|
||||
a.plugins.AttachPanels(hub)
|
||||
declareBuiltinPanels(hub, log)
|
||||
// 「MCP 服务器」页(切片 M20):声明 + 动作处理器成对装配,且必须在中枢接线之后
|
||||
// (AttachMCPPanel 经插件管理器取中枢,未接线时会响亮报错而不是静默不挂)。
|
||||
// 它归属插件管理器自己的领域数据(Statuses/Install/SetEnabled/Uninstall),因此
|
||||
// 与 web-search 的纯声明不同:这里一处调用同时完成声明与动作登记。
|
||||
if err := a.plugins.AttachMCPPanel(); err != nil {
|
||||
log.Warn("mcp server panel unavailable; its actions will answer 501", "error", err)
|
||||
}
|
||||
// browser 插件(design/28 §8 B4)的面板声明 + 六个动作:它自己就把「先声明、后登记
|
||||
// 动作」这一步走完(wire_browser_panel.go 一处),因此与 search 的纯声明并列。
|
||||
//
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/auth"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/bus"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/paneldecl"
|
||||
pluginmgr "git.yeij.top/AskaEth/Cyrene_v2/internal/capability/plugin"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/registry"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk"
|
||||
@@ -99,23 +100,45 @@ func TestWirePanelHubDeclaresBuiltinSearchPanel(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("List: %v", err)
|
||||
}
|
||||
if len(entries) != 1 || entries[0].PluginID != search.PanelPluginID ||
|
||||
entries[0].Panel.ID != search.PanelIDProviders {
|
||||
t.Fatalf("内置面板 = %+v,期望 web-search/providers 一条", entries)
|
||||
// 按 pluginID 定位,而不是数「恰好一条」:内置提供者会越来越多(本片新增
|
||||
// mcp/servers 管理页),数总数的断言每加一页就假红一次,而它真正想守的是
|
||||
// 「web-search/providers 在目录里,且形状仍是声明的那一版」。
|
||||
entry, ok := findPanelEntry(entries, search.PanelPluginID, search.PanelIDProviders)
|
||||
if !ok {
|
||||
t.Fatalf("内置面板里没有 web-search/providers:%+v", entries)
|
||||
}
|
||||
// 形状也钉住:一个 form(写面)+ 一个 table(读面)。
|
||||
if len(entry.Panel.Sections) != 2 || entry.Panel.Sections[0].Type != sdk.SectionForm ||
|
||||
entry.Panel.Sections[1].Type != sdk.SectionTable {
|
||||
t.Fatalf("web-search/providers 的区块形状 = %+v,期望 form+table", entry.Panel.Sections)
|
||||
}
|
||||
for _, name := range search.ActionNames() {
|
||||
if _, ok := hub.Action(search.PanelPluginID, name); !ok {
|
||||
t.Fatalf("内置插件声明的动作 %q 未进白名单", name)
|
||||
}
|
||||
}
|
||||
// viewer 拿到的必须是裁掉 form 的只读投影。
|
||||
// viewer 拿到的必须是裁掉 form 的只读投影(只剩那个表格区块)。
|
||||
viewer, err := hub.List(auth.RoleViewer)
|
||||
if err != nil {
|
||||
t.Fatalf("List(viewer): %v", err)
|
||||
}
|
||||
if len(viewer) != 1 || len(viewer[0].Panel.Sections) != 1 {
|
||||
t.Fatalf("viewer 的只读投影 = %+v,期望只剩表格区块", viewer)
|
||||
viewerEntry, ok := findPanelEntry(viewer, search.PanelPluginID, search.PanelIDProviders)
|
||||
if !ok {
|
||||
t.Fatalf("viewer 快照里没有 web-search/providers:%+v", viewer)
|
||||
}
|
||||
if len(viewerEntry.Panel.Sections) != 1 || viewerEntry.Panel.Sections[0].Type != sdk.SectionTable {
|
||||
t.Fatalf("viewer 的只读投影 = %+v,期望只剩表格区块", viewerEntry.Panel.Sections)
|
||||
}
|
||||
}
|
||||
|
||||
// findPanelEntry 在目录快照里按 (pluginId, panelId) 定位那一条。
|
||||
func findPanelEntry(entries []paneldecl.Entry, pluginID, panelID string) (paneldecl.Entry, bool) {
|
||||
for _, e := range entries {
|
||||
if e.PluginID == pluginID && e.Panel.ID == panelID {
|
||||
return e, true
|
||||
}
|
||||
}
|
||||
return paneldecl.Entry{}, false
|
||||
}
|
||||
|
||||
// TestWirePanelHubNilPluginsIsNoop 断言未构造插件管理器时接线是空操作(不 panic)。
|
||||
|
||||
Reference in new issue
Block a user