Decouple runmgr from cmdbus via view types in CommandSource (EXT-14 lint fix) #116

Merged
weichen merged 1 commits from feature/ext14-cmdsource-view into main 2026-10-09 16:33:23 +00:00
3 changed files with 67 additions and 30 deletions
+20 -4
View File
@@ -707,10 +707,26 @@ func splitKeyPool(s string) []string {
return out
}
// busAdapter EXT-14:cmdbus.Bus 适配 runmgr.CommandSource(工具投影 + 命令分发)。
// busAdapter EXT-14:cmdbus.Bus 适配 runmgr.CommandSource。视图类型转换在此收口
// (runmgr 不依赖 cmdbus,边不进架构基线);Entrance 由 runmgr 固定为 agent。
type busAdapter struct{ bus *cmdbus.Bus }
func (a busAdapter) CommandTools() []cmdbus.CommandTool { return a.bus.CommandTools() }
func (a busAdapter) DispatchCommand(ctx context.Context, req cmdbus.Request) cmdbus.Response {
return a.bus.Dispatch(ctx, req)
func (a busAdapter) CommandTools() []runmgr.CommandTool {
src := a.bus.CommandTools()
out := make([]runmgr.CommandTool, len(src))
for i, ct := range src {
out[i] = runmgr.CommandTool{ID: ct.ID, Description: ct.Description, Params: ct.Params, Risk: ct.Risk}
}
return out
}
func (a busAdapter) DispatchCommand(ctx context.Context, req runmgr.CommandRequest) runmgr.CommandResponse {
resp := a.bus.Dispatch(ctx, cmdbus.Request{
ID: req.ID, Params: req.Params, Entrance: req.Entrance,
Actor: req.Actor, Owner: req.Owner, ConversationID: req.ConversationID,
})
return runmgr.CommandResponse{
State: string(resp.Receipt.State), Reason: resp.Receipt.Reason,
Payload: resp.Payload, Card: resp.Card != nil,
}
}
+11 -16
View File
@@ -8,26 +8,21 @@ import (
"context"
"testing"
agentcommand "agent-command"
"agent-system-backend/internal/cmdbus"
"agent-system-backend/internal/tools"
core "agent-system-core"
)
func agentcommandCommitted() agentcommand.ExecReceipt {
return agentcommand.ExecReceipt{State: agentcommand.StateCommitted}
}
// fakeBusSource EXT-14 测试桩:命令清单 + 分发记录。
// fakeBusSource EXT-14 测试桩:命令清单 + 分发记录(说话用 runmgr 视图类型,
// 与生产代码同口径——runmgr 不依赖 cmdbus)。
type fakeBusSource struct {
cmds []cmdbus.CommandTool
lastReq cmdbus.Request
resp cmdbus.Response
cmds []CommandTool
lastReq CommandRequest
resp CommandResponse
}
func (f *fakeBusSource) CommandTools() []cmdbus.CommandTool { return f.cmds }
func (f *fakeBusSource) CommandTools() []CommandTool { return f.cmds }
func (f *fakeBusSource) DispatchCommand(ctx context.Context, req cmdbus.Request) cmdbus.Response {
func (f *fakeBusSource) DispatchCommand(ctx context.Context, req CommandRequest) CommandResponse {
f.lastReq = req
return f.resp
}
@@ -35,8 +30,8 @@ func (f *fakeBusSource) DispatchCommand(ctx context.Context, req cmdbus.Request)
func TestCommandToolsProjected(t *testing.T) {
rc0, _ := newExecEnv(t)
m := rc0.mgr
src := &fakeBusSource{cmds: []cmdbus.CommandTool{
{ID: "rdm.trace", Title: "追溯查询", Description: "查追溯网", Params: []byte(`{"type":"object"}`), Risk: "low"},
src := &fakeBusSource{cmds: []CommandTool{
{ID: "rdm.trace", Description: "查追溯网", Params: []byte(`{"type":"object"}`), Risk: "low"},
}}
m.SetCommandSource(src)
rc := rc0
@@ -57,8 +52,8 @@ func TestCommandToolDispatchesThroughBus(t *testing.T) {
rc0, _ := newExecEnv(t)
m := rc0.mgr
src := &fakeBusSource{
cmds: []cmdbus.CommandTool{{ID: "rdm.trace", Title: "追溯查询", Params: []byte(`{"type":"object"}`), Risk: "low"}},
resp: cmdbus.Response{Receipt: agentcommandCommitted()},
cmds: []CommandTool{{ID: "rdm.trace", Params: []byte(`{"type":"object"}`), Risk: "low"}},
resp: CommandResponse{State: "committed"},
}
m.SetCommandSource(src)
rc := rc0
+36 -10
View File
@@ -24,7 +24,6 @@ import (
"agent-system-backend/internal/bus"
"agent-system-backend/internal/cardsvc"
"agent-system-backend/internal/cmdbus"
"agent-system-backend/internal/ingest"
"agent-system-backend/internal/intentgate"
"agent-system-backend/internal/profiles"
@@ -177,10 +176,38 @@ func (m *Manager) SetIndexer(i core.DocumentIndexer) { m.indexer = i }
func (m *Manager) SetWorkspaceRoot(dir string) { m.workspaceRoot = dir }
// CommandSource EXT-14:命令总线暴露给回合执行的两个面——工具投影清单与
// 经命令总的分发(同审计信封与 HITL 收口)。组合根以 cmdbus.Bus 适配注入。
// 经命令总的分发(同审计信封与 HITL 收口)。runmgr 只认本包视图类型,
// 组合根以 cmdbus.Bus 适配注入(app.busAdapter 负责转换),runmgr 不依赖总线包。
type CommandSource interface {
CommandTools() []cmdbus.CommandTool
DispatchCommand(ctx context.Context, req cmdbus.Request) cmdbus.Response
CommandTools() []CommandTool
DispatchCommand(ctx context.Context, req CommandRequest) CommandResponse
}
// CommandTool EXT-14 工具投影视图:组合根从命令总线的投影清单转换注入。
type CommandTool struct {
ID string
Description string
Params json.RawMessage
Risk string
}
// CommandRequest 一次命令工具分发请求(runmgr 固定以 Entrance=agent 发起,
// Actor/Owner 绑定会话 owner、带会话上下文)。
type CommandRequest struct {
ID string
Params map[string]any
Entrance string
Actor string
Owner string
ConversationID string
}
// CommandResponse 分发结果视图:回执状态/原因 + 负载 + 是否挂起确认卡。
type CommandResponse struct {
State string
Reason string
Payload any
Card bool
}
// sharedRetriever EXT-13:检索器具备共享通道(rag.Pipeline.RetrieveShared 同形)。
@@ -824,19 +851,18 @@ func (c *runCtx) ExecuteTool(call core.ToolCallRequest) core.ToolCallResult {
if len(call.Arguments) > 0 {
_ = json.Unmarshal(call.Arguments, &params)
}
resp := c.mgr.commands.DispatchCommand(c.ctx, cmdbus.Request{
resp := c.mgr.commands.DispatchCommand(c.ctx, CommandRequest{
ID: call.Name, Params: params, Entrance: "agent",
Actor: c.run.Owner, Owner: c.run.Owner, ConversationID: c.run.ConversationID,
})
out, _ := json.Marshal(resp.Payload)
state := string(resp.Receipt.State)
if resp.Card != nil && state == "accepted" {
if resp.Card && resp.State == "accepted" {
return core.ToolCallResult{CallID: call.CallID, Status: "ok",
Output: "已创建确认卡,等待用户确认后执行:" + resp.Receipt.Reason}
Output: "已创建确认卡,等待用户确认后执行:" + resp.Reason}
}
if state != "committed" && state != "accepted" {
if resp.State != "committed" && resp.State != "accepted" {
return core.ToolCallResult{CallID: call.CallID, Status: "error",
Output: "命令 " + call.Name + " " + state + ": " + resp.Receipt.Reason}
Output: "命令 " + call.Name + " " + resp.State + ": " + resp.Reason}
}
return core.ToolCallResult{CallID: call.CallID, Status: "ok", Output: string(out)}
}