feat(0144c): launcher wiring + adapter al tool-use loop LLM

Schema DeviceMeshConfig en AgentConfig. Adapter ToolsForLLM convierte
ToolSpec → tools.Tool transparente al LLM existente. URL via env var
override. tools_allowed filter. agent-wsl-lucas blank import en launcher.

LLM ve los tools como cualquier otra herramienta. Effects runner ya
soporta ActionKindDeviceMesh como fallback. Build + tests verdes.
This commit is contained in:
2026-05-24 14:07:13 +02:00
parent 4c5bf95def
commit 61606d450d
6 changed files with 457 additions and 2 deletions
+144
View File
@@ -171,3 +171,147 @@ func assertToolNotRegistered(t *testing.T, reg interface{ Names() []string }, na
}
}
}
func TestBuildToolRegistry_DeviceMeshDisabled(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
cfg := &config.AgentConfig{
Agent: config.AgentMeta{ID: "test-agent"},
DeviceMesh: nil,
}
roomCtx := &toolmemory.RoomContext{}
reg := buildToolRegistry(cfg, nil, nil, nil, nil, nil, nil, nil, nil, roomCtx, logger)
// None of the device_mesh tool names should appear when the block is nil.
assertToolNotRegistered(t, reg, "exec")
assertToolNotRegistered(t, reg, "shell.eval")
assertToolNotRegistered(t, reg, "fs.read")
}
func TestBuildDeviceMeshRegistry_NoURLReturnsNil(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
cfg := &config.AgentConfig{
Agent: config.AgentMeta{ID: "agent-x"},
DeviceMesh: &config.DeviceMeshConfig{
Enabled: true,
Mode: "user",
// no URL, no URLEnv
},
}
if got := buildDeviceMeshRegistry(cfg, logger); got != nil {
t.Errorf("expected nil registry when no URL is set, got %d tools", got.Len())
}
}
func TestBuildDeviceMeshRegistry_URLEnvOverride(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
t.Setenv("TEST_DM_URL", "http://10.42.0.99:7474")
cfg := &config.AgentConfig{
Agent: config.AgentMeta{ID: "agent-x"},
DeviceMesh: &config.DeviceMeshConfig{
Enabled: true,
Mode: "user",
DeviceAgentURL: "http://stale-url",
URLEnv: "TEST_DM_URL",
},
}
reg := buildDeviceMeshRegistry(cfg, logger)
if reg == nil {
t.Fatalf("expected non-nil registry")
}
if reg.Client().BaseURL != "http://10.42.0.99:7474" {
t.Errorf("URLEnv override failed: got %q", reg.Client().BaseURL)
}
}
func TestBuildDeviceMeshRegistry_UserModeFiltersApproval(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
cfg := &config.AgentConfig{
Agent: config.AgentMeta{ID: "agent-x"},
DeviceMesh: &config.DeviceMeshConfig{
Enabled: true,
Mode: "user",
DeviceAgentURL: "http://dummy:7474",
},
}
reg := buildDeviceMeshRegistry(cfg, logger)
if reg == nil {
t.Fatalf("expected non-nil registry")
}
for _, n := range reg.Names() {
// User mode: pkg.install (requires approval) must not be present.
if n == "pkg.install" {
t.Errorf("user mode leaked approval-only tool: %s", n)
}
}
}
func TestBuildDeviceMeshRegistry_SudoModeKeepsOnlyApproval(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
cfg := &config.AgentConfig{
Agent: config.AgentMeta{ID: "agent-x-sudo"},
DeviceMesh: &config.DeviceMeshConfig{
Enabled: true,
Mode: "sudo",
DeviceAgentURL: "http://dummy:7474",
},
}
reg := buildDeviceMeshRegistry(cfg, logger)
if reg == nil {
t.Fatalf("expected non-nil registry")
}
// pkg.install MUST be there in sudo mode.
assertToolRegistered(t, reg, "pkg.install")
// shell.eval is always registered (special-cased) and promoted to approval.
spec, ok := reg.Get("shell.eval")
if !ok {
t.Fatalf("shell.eval should be registered in sudo mode too")
}
if !spec.RequiresApproval {
t.Errorf("shell.eval in sudo mode should have RequiresApproval=true")
}
}
func TestBuildDeviceMeshRegistry_ToolsAllowedNarrows(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
cfg := &config.AgentConfig{
Agent: config.AgentMeta{ID: "agent-x"},
DeviceMesh: &config.DeviceMeshConfig{
Enabled: true,
Mode: "user",
DeviceAgentURL: "http://dummy:7474",
ToolsAllowed: []string{"exec", "fs.read", "zzz.unknown"},
},
}
reg := buildDeviceMeshRegistry(cfg, logger)
if reg == nil {
t.Fatalf("expected non-nil registry")
}
if reg.Len() != 2 {
t.Errorf("expected 2 tools after filter, got %d: %v", reg.Len(), reg.Names())
}
assertToolRegistered(t, reg, "exec")
assertToolRegistered(t, reg, "fs.read")
}
func TestBuildToolRegistry_DeviceMeshAdaptedIntoMainRegistry(t *testing.T) {
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
cfg := &config.AgentConfig{
Agent: config.AgentMeta{ID: "agent-x"},
DeviceMesh: &config.DeviceMeshConfig{
Enabled: true,
Mode: "user",
DeviceAgentURL: "http://dummy:7474",
ToolsAllowed: []string{"exec"},
},
}
roomCtx := &toolmemory.RoomContext{}
reg := buildToolRegistry(cfg, nil, nil, nil, nil, nil, nil, nil, nil, roomCtx, logger)
// The "exec" tool should appear in the main agent tool registry, alongside
// the always-on tools, ready for the LLM tool-use loop to invoke.
assertToolRegistered(t, reg, "exec")
assertToolRegistered(t, reg, "current_time")
}