Files
Agent-System-Backend/internal/runmgr/command_tools_test.go
T
weichen 6337cd87f8
ci / vet-and-test (pull_request) Successful in 40s
Decouple runmgr from cmdbus via view types in CommandSource
Why: the EXT-14 wiring made runmgr import cmdbus for the CommandSource
interface signatures, which adds a new edge backend/runmgr ->
backend/cmdbus outside the architecture baseline ratchet; the Dev
umbrella lint (lint-architecture) rejects the gitlink bump because of
it. The dependency direction was always meant to be inverted: runmgr
defines the port, the composition root adapts the bus.

Evidence: go build/vet clean; go test ./... 32/32 packages green;
grep confirms no internal/cmdbus import left in non-test runmgr files;
Dev-side lint re-run scheduled on the gitlink bump PR.

Impact: runmgr.CommandTool/CommandRequest/CommandResponse are local
view types; app.busAdapter now converts cmdbus types at the boundary
(Entrance still fixed to agent by runmgr). No behavior change; the
runmgr tests speak the view types instead of cmdbus ones.

Refs: Dev umbrella make test failure 'architecture: new dependency
edges backend/runmgr backend/cmdbus'; proposal 2026-10-09-ext-g1-b1-b3
(EXT-14); Backend #114.
2026-10-09 16:31:43 +00:00

83 lines
2.8 KiB
Go

package runmgr
// EXT-14 红测(提案 2026-10-09-ext-g1-b1-b3 §2):带 exposeAsTool 的命令投影为
// 回合内工具;模型调用经命令总分发(Entrance="agent"、同审计信封);不带字段的
// 命令不出现;高危命令在对话内弹内联确认卡(HITL 收口不变)。
import (
"context"
"testing"
"agent-system-backend/internal/tools"
core "agent-system-core"
)
// fakeBusSource EXT-14 测试桩:命令清单 + 分发记录(说话用 runmgr 视图类型,
// 与生产代码同口径——runmgr 不依赖 cmdbus)。
type fakeBusSource struct {
cmds []CommandTool
lastReq CommandRequest
resp CommandResponse
}
func (f *fakeBusSource) CommandTools() []CommandTool { return f.cmds }
func (f *fakeBusSource) DispatchCommand(ctx context.Context, req CommandRequest) CommandResponse {
f.lastReq = req
return f.resp
}
func TestCommandToolsProjected(t *testing.T) {
rc0, _ := newExecEnv(t)
m := rc0.mgr
src := &fakeBusSource{cmds: []CommandTool{
{ID: "rdm.trace", Description: "查追溯网", Params: []byte(`{"type":"object"}`), Risk: "low"},
}}
m.SetCommandSource(src)
rc := rc0
tools := rc.Tools()
found := map[string]bool{}
for _, d := range tools {
found[d.Name] = true
}
if !found["rdm.trace"] {
t.Fatalf("exposeAsTool 命令未投影为工具: %+v", tools)
}
if found["rdm.hidden"] {
t.Fatal("命令源之外的工具不得出现")
}
}
func TestCommandToolDispatchesThroughBus(t *testing.T) {
rc0, _ := newExecEnv(t)
m := rc0.mgr
src := &fakeBusSource{
cmds: []CommandTool{{ID: "rdm.trace", Params: []byte(`{"type":"object"}`), Risk: "low"}},
resp: CommandResponse{State: "committed"},
}
m.SetCommandSource(src)
rc := rc0
res := rc.ExecuteTool(core.ToolCallRequest{CallID: "c1", Name: "rdm.trace", Arguments: []byte(`{"q":"x"}`)})
if res.Status != "ok" {
t.Fatalf("dispatch result = %s %s", res.Status, res.Output)
}
req := src.lastReq
if req.ID != "rdm.trace" || req.Entrance != "agent" || req.Actor != rc.run.Owner || req.Owner != rc.run.Owner || req.ConversationID != rc.run.ConversationID {
t.Fatalf("bus request = %+v(须 Entrance=agent、Actor/Owner=会话 owner、带会话)", req)
}
if req.Params["q"] != "x" {
t.Fatalf("params = %+v", req.Params)
}
// 未注册命令源时:调用按注册表口径报错(现状),不 panic。
reg2 := tools.NewRegistry()
if err := reg2.RegisterBuiltin(); err != nil {
t.Fatal(err)
}
m2 := New(nil, nil, reg2, nil, nil, nil, nil, nil, nil)
rc2 := &runCtx{mgr: m2, run: core.Run{Owner: "bob"}, prof: core.AgentProfile{Tools: core.ToolPolicy{Allow: []string{"*"}}}}
if res := rc2.ExecuteTool(core.ToolCallRequest{CallID: "c2", Name: "rdm.trace", Arguments: []byte(`{}`)}); res.Status != "error" {
t.Fatalf("no source: %s(须 registry miss 报错)", res.Status)
}
}