From 2421a8dac9e5c254bf425ce31f2a9f1dd62ec7da Mon Sep 17 00:00:00 2001 From: AskaEth Date: Sat, 24 Oct 2026 19:38:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(plugin):=20=E7=BB=84=E5=90=88=E6=A0=B9?= =?UTF-8?q?=E8=A3=85=E9=85=8D=E3=80=8CMCP=20=E6=9C=8D=E5=8A=A1=E5=99=A8?= =?UTF-8?q?=E3=80=8D=E9=A1=B5=EF=BC=88=E5=A3=B0=E6=98=8E=20+=20=E5=8A=A8?= =?UTF-8?q?=E4=BD=9C=E5=A4=84=E7=90=86=E5=99=A8=E6=88=90=E5=AF=B9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - wirePanelHub 在 AttachPanels / declareBuiltinPanels 之后调 AttachMCPPanel: 一处完成「声明页面」+「登记 7 个动作处理器」,缺任一步就是面板在、动作全 501。 - wire_panels_test 的两条「内置面板恰好 1 条」断言改成按 pluginID 定位 (web-search/providers),并加钉区块形状(form+table;viewer 只剩 table)。 接线落地后目录里确实是两条(mcp/10 + web-search/20),旧断言是事实过期而非回归。 --- cmd/cyrene/wire_frontend.go | 7 +++++++ cmd/cyrene/wire_panels_test.go | 35 ++++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/cmd/cyrene/wire_frontend.go b/cmd/cyrene/wire_frontend.go index d2cf104..c3538bf 100644 --- a/cmd/cyrene/wire_frontend.go +++ b/cmd/cyrene/wire_frontend.go @@ -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 的纯声明并列。 // diff --git a/cmd/cyrene/wire_panels_test.go b/cmd/cyrene/wire_panels_test.go index 2aa5481..233a856 100644 --- a/cmd/cyrene/wire_panels_test.go +++ b/cmd/cyrene/wire_panels_test.go @@ -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)。