fix(webui): 运行态面板不再闪、通道分配带归属(含驻留子)、改图形化

三个用户可见问题,逐个说明根因与改法。

1) 首页「一闪一闪」——运行态每 3s 轮询一次,renderRuntime 无条件重建
   #rt-panel 的 innerHTML:数据没变也把整块 DOM(含各级条的 transition)
   推倒重来。改法:缓存数据签名(**不含 uptime**——它每秒都变,带上等于没缓存),
   签名相同直接 return,一个字节都不动。另:renderOverview 会整块重建
   #rt-panel(面板本身是空的),所以那里必须让签名失效,否则空面板填不上。

2) 通道分配只显示内核/根 agent,看不见驻留子——根因是状态面只暴露了设备能力
   (KernelStatus.Channels,来自 iom.ListChannels),而「这条输入归谁」是
   ChannelRegistry 的属性(InputChannel.Owner/Capacity/Output),从未出过内核。
   而登记表本来就是根 agent 与驻留子**共用同一份**,所以数据一直都在,只是没画。
   改法:KernelStatus 新增 InputChannels(+ sdk.InputChannelInfo),
   /api/v1/runtime 带出 input_channels;前端把它按 owner 分进「归属容器」,
   驻留子即使一条 inputch 都没划到也照样出现在图里(否则"子存在但看不见"
   与"子不存在"无法区分),并显示其 allowed_outputs / 轮次 / 上下文满标记。

3) 「这些信息明明可以图形化」——四级中断的登记/抢占由纯文本改成并排迷你条;
   通道分配用归属容器 + 容量滑块(轨道/填充/把手/读数),并把回程通道、
   注册插件做成胶囊标签。设备能力拓扑(原有)保留。

验证:go build/vet 干净;go test ./internal/agent/... ./internal/plugin/...
./internal/sdk/... ./cmd/... 全绿;node --check dashboard.js 语法通过。
TestRuntimeEndpoint 扩展为同时钉住 input_channels 的归属与「驻留子划走的那条」。
This commit is contained in:
JianFeeeee
2026-09-14 10:55:45 +08:00
parent c321388a21
commit 43536ba326
6 changed files with 326 additions and 5 deletions

View File

@ -1409,6 +1409,13 @@ func TestRuntimeEndpoint(t *testing.T) {
},
Residents: []sdk.ResidentStatus{{ID: "r1", State: "running", Rounds: 3}},
Channels: []sdk.ChannelInfo{{Name: "webui", Type: "1", Direction: "out", Ready: true, OutputCaps: 7, CapsText: "[text file image]"}},
// inputch 的登记与归属:必须带出**驻留子划走的那条**owner=子 id——
// 只回设备能力channels就无法回答「这条输入归谁」驻留子的通道分配
// 在界面上完全不可见(本次修的缺口)。
InputChannels: []sdk.InputChannelInfo{
{Name: "qq", Plugin: "qq", Capacity: 32, Output: "qq"},
{Name: "timer", Plugin: "timer", Owner: "r1", Capacity: 8},
},
}
s := testSDK(sdk.SDKConfig{
Settings: sdk.NewSettings("webui", internalConfig.NewConfigRegistry("")),
@ -1424,9 +1431,10 @@ func TestRuntimeEndpoint(t *testing.T) {
t.Fatalf("expected 200, got %d", w.Code)
}
var out struct {
Scheduler sdk.SchedulerStatus `json:"scheduler"`
Residents []sdk.ResidentStatus `json:"residents"`
Channels []sdk.ChannelInfo `json:"channels"`
Scheduler sdk.SchedulerStatus `json:"scheduler"`
Residents []sdk.ResidentStatus `json:"residents"`
Channels []sdk.ChannelInfo `json:"channels"`
InputChannels []sdk.InputChannelInfo `json:"input_channels"`
}
if err := json.Unmarshal(w.Body.Bytes(), &out); err != nil {
t.Fatalf("unmarshal: %v", err)
@ -1446,6 +1454,25 @@ func TestRuntimeEndpoint(t *testing.T) {
if len(out.Channels) != 1 || out.Channels[0].Direction != "out" {
t.Fatalf("通道方向丢失:%+v", out.Channels)
}
// 通道分配:既要带归属,也要带**驻留子划走的那条**
if len(out.InputChannels) != 2 {
t.Fatalf("input_channels 应回 2 条,实际 %+v", out.InputChannels)
}
var childCh, rootCh *sdk.InputChannelInfo
for i := range out.InputChannels {
switch out.InputChannels[i].Owner {
case "":
rootCh = &out.InputChannels[i]
case "r1":
childCh = &out.InputChannels[i]
}
}
if rootCh == nil || rootCh.Name != "qq" || rootCh.Capacity != 32 || rootCh.Output != "qq" {
t.Fatalf("根 agent 的 inputch 归属/容量/回程丢失:%+v", rootCh)
}
if childCh == nil || childCh.Name != "timer" || childCh.Capacity != 8 {
t.Fatalf("驻留子划走的 inputch 未带出(这正是本次修的缺口):%+v", childCh)
}
// 只回运行态:不该把 tools/plugins 这类大块带上
if strings.Contains(w.Body.String(), "\"tools\"") || strings.Contains(w.Body.String(), "\"plugins\"") {
t.Fatal("/runtime 不应携带 tools/plugins那是 /kernel 的内容)")