feat(0144a): tool registry framework para device-mesh
Anade pkg/tools/devicemesh con Client HTTP al device_agent + ToolRegistry con 16 tools standard (exec, fs.*, git.*, docker.*, proc.*, pkg.*, shell.eval). RegisterBuiltins filtra por mode user/sudo via RequiresApproval flag. Hook al pkg/decision con ActionKindDeviceMesh + DeviceMeshAction. Runner soporta dispatch via NewRunnerWithDeviceMesh (back-compat NewRunner). Tests: 25 nuevos en devicemesh + 4 en runner. Build clean.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
package effects
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/enmanuel/agents/pkg/decision"
|
||||
)
|
||||
|
||||
// stubMeshCaller is a minimal DeviceMeshCaller for runner tests.
|
||||
type stubMeshCaller struct {
|
||||
tool string
|
||||
input map[string]any
|
||||
result any
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *stubMeshCaller) Call(_ context.Context, toolName string, input map[string]any) (any, error) {
|
||||
s.tool = toolName
|
||||
s.input = input
|
||||
return s.result, s.err
|
||||
}
|
||||
|
||||
func newSilentLogger() *slog.Logger {
|
||||
return slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
}
|
||||
|
||||
func TestRunner_DeviceMesh_Success(t *testing.T) {
|
||||
stub := &stubMeshCaller{result: map[string]any{"stdout": "hello", "exit_code": 0}}
|
||||
r := NewRunnerWithDeviceMesh(nil, nil, stub, newSilentLogger())
|
||||
|
||||
results := r.Execute(context.Background(), "!room", []decision.Action{{
|
||||
Kind: decision.ActionKindDeviceMesh,
|
||||
DeviceMesh: &decision.DeviceMeshAction{
|
||||
Tool: "exec",
|
||||
Input: map[string]any{"argv": []string{"echo", "hello"}},
|
||||
},
|
||||
}})
|
||||
|
||||
if len(results) != 1 {
|
||||
t.Fatalf("expected 1 result, got %d", len(results))
|
||||
}
|
||||
res := results[0]
|
||||
if res.Err != nil {
|
||||
t.Fatalf("expected no error, got %v", res.Err)
|
||||
}
|
||||
if stub.tool != "exec" {
|
||||
t.Errorf("stub.tool=%q", stub.tool)
|
||||
}
|
||||
if !strings.Contains(res.Output, "hello") {
|
||||
t.Errorf("output missing 'hello': %q", res.Output)
|
||||
}
|
||||
if !strings.Contains(res.Output, "exit_code") {
|
||||
t.Errorf("output should be JSON containing exit_code: %q", res.Output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunner_DeviceMesh_PropagatesError(t *testing.T) {
|
||||
stub := &stubMeshCaller{err: errors.New("approval timeout")}
|
||||
r := NewRunnerWithDeviceMesh(nil, nil, stub, newSilentLogger())
|
||||
results := r.Execute(context.Background(), "!room", []decision.Action{{
|
||||
Kind: decision.ActionKindDeviceMesh,
|
||||
DeviceMesh: &decision.DeviceMeshAction{Tool: "pkg.install", Input: map[string]any{"name": "jq"}},
|
||||
}})
|
||||
if results[0].Err == nil {
|
||||
t.Fatalf("expected error to propagate")
|
||||
}
|
||||
if !strings.Contains(results[0].Err.Error(), "approval") {
|
||||
t.Errorf("error mismatch: %v", results[0].Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunner_DeviceMesh_NilAction(t *testing.T) {
|
||||
r := NewRunnerWithDeviceMesh(nil, nil, &stubMeshCaller{}, newSilentLogger())
|
||||
results := r.Execute(context.Background(), "!room", []decision.Action{{
|
||||
Kind: decision.ActionKindDeviceMesh,
|
||||
// DeviceMesh field is nil
|
||||
}})
|
||||
if results[0].Err == nil {
|
||||
t.Fatalf("expected error for nil DeviceMesh field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunner_DeviceMesh_NoCaller(t *testing.T) {
|
||||
// Using NewRunner (legacy) — should fail gracefully on DeviceMesh action.
|
||||
r := NewRunner(nil, nil, newSilentLogger())
|
||||
results := r.Execute(context.Background(), "!room", []decision.Action{{
|
||||
Kind: decision.ActionKindDeviceMesh,
|
||||
DeviceMesh: &decision.DeviceMeshAction{Tool: "exec", Input: map[string]any{"argv": []string{"x"}}},
|
||||
}})
|
||||
if results[0].Err == nil {
|
||||
t.Fatalf("expected error when Runner has no DeviceMeshCaller")
|
||||
}
|
||||
if !strings.Contains(results[0].Err.Error(), "DeviceMeshCaller") {
|
||||
t.Errorf("error should mention DeviceMeshCaller: %v", results[0].Err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user