ci / vet-and-test (pull_request) Successful in 43s
Why: RDM's capabilities are rdm.* commands; the agent run has no bridge to them, so the V0.1 end-to-end goal (full-conversation operation) and the agent-driven two-trees-one-net access are unreachable (Q4 option A). Evidence: SELF - red tests first: TestCommandToolsProjected and TestCommandToolDispatchesThroughBus failed on undefined symbols, then green; TestCommandToolsFiltering and TestCommandToolDispatchEnvelope cover projection filtering (risk default low, description falls back to title, unexposed commands absent) and the full dispatch receipt with Entrance=agent preserved through CanonicalEntrance; go test ./... 32/32 packages ok. Impact: agent-command pin cc1ad77 -> 5c6dd0c (library PRs #4/#5: optional ExposeAsTool field + "agent" entrance). cmdbus gains CommandTool view and Bus.CommandTools; runmgr gains SetCommandSource with projection into runCtx.Tools (dedup: registry tools win) and routing in ExecuteTool - model calls dispatch through the bus with Entrance=agent, Actor/Owner = conversation owner, ConversationID set; high-danger commands keep the inline-confirm-card flow via the bus; unregistered source = current registry behavior. Composition root wires a busAdapter. No pkg/extension change. Refs: proposal 2026-10-09-ext-g1-b1-b3 section 2; Agent-Command #4/#5
75 lines
2.6 KiB
Go
75 lines
2.6 KiB
Go
package cmdbus
|
||
|
||
// EXT-14:Bus.CommandTools 只投影带 ExposeAsTool 的已启用命令;risk 缺省 low、
|
||
// description 缺省取 title;未暴露/未启用的命令不出现。
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"testing"
|
||
"time"
|
||
|
||
agentcommand "agent-command"
|
||
)
|
||
|
||
func newToolBus(t *testing.T) *Bus {
|
||
t.Helper()
|
||
return New(nopAudit{}, nil, func() time.Time { return time.Now().UTC() })
|
||
}
|
||
|
||
func TestCommandToolsFiltering(t *testing.T) {
|
||
b := newToolBus(t)
|
||
specs := []json.RawMessage{
|
||
[]byte(`{"id":"rdm.trace","title":"追溯查询","description":"查网","paramsSchema":{"type":"object"},"exposeAsTool":{"risk":"medium"}}`),
|
||
[]byte(`{"id":"rdm.baseline","title":"基线视图","exposeAsTool":{}}`),
|
||
[]byte(`{"id":"rdm.hidden","title":"未暴露","description":"不出现"}`),
|
||
}
|
||
if err := b.RegisterPluginCommands(specs); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
tools := b.CommandTools()
|
||
byID := map[string]CommandTool{}
|
||
for _, ct := range tools {
|
||
byID[ct.ID] = ct
|
||
}
|
||
if len(byID) != 2 {
|
||
t.Fatalf("projected = %+v, want 2(rdm.trace + rdm.baseline)", tools)
|
||
}
|
||
if ct := byID["rdm.trace"]; ct.Risk != "medium" || ct.Description != "查网" || string(ct.Params) != `{"type":"object"}` {
|
||
t.Fatalf("rdm.trace = %+v", ct)
|
||
}
|
||
if ct := byID["rdm.baseline"]; ct.Risk != "low" || ct.Description != "基线视图" {
|
||
t.Fatalf("rdm.baseline = %+v(risk 缺省 low、description 缺省 title)", ct)
|
||
}
|
||
if _, ok := byID["rdm.hidden"]; ok {
|
||
t.Fatal("未声明 exposeAsTool 的命令不得投影")
|
||
}
|
||
}
|
||
|
||
func TestCommandToolDispatchEnvelope(t *testing.T) {
|
||
b := newToolBus(t)
|
||
// 注册一个带处理器的暴露命令,经 Dispatch 分发验证完整回执链路。
|
||
var spec agentcommand.CommandSpec
|
||
if err := json.Unmarshal([]byte(`{"id":"rdm.echo","title":"回声","exposeAsTool":{"risk":"low"}}`), &spec); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := b.RegisterPluginCommand(spec, func(ctx context.Context, req Request) (any, error) {
|
||
return map[string]any{"echo": req.Params["v"], "actor": req.Actor, "entrance": req.Entrance}, nil
|
||
}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
resp := b.Dispatch(context.Background(), Request{ID: "rdm.echo", Params: map[string]any{"v": 1},
|
||
Entrance: "agent", Actor: "bob", Owner: "bob", ConversationID: "cnv-1"})
|
||
if resp.Receipt.State != "committed" {
|
||
t.Fatalf("receipt = %+v (%s)", resp.Receipt, resp.Receipt.Reason)
|
||
}
|
||
m, _ := resp.Payload.(map[string]any)
|
||
if m["entrance"] != "agent" || m["actor"] != "bob" {
|
||
t.Fatalf("payload = %+v(Entrance=agent 原样入审计)", m)
|
||
}
|
||
}
|
||
|
||
type nopAudit struct{}
|
||
|
||
func (nopAudit) AppendAudit(owner string, env agentcommand.AuditEnvelope) error { return nil }
|