1package modes
2
3import (
4 "context"
5 "testing"
6
7 "github.com/bnema/zut/packages/agent/subagents"
8 "github.com/bnema/zut/packages/agent/tools"
9 "github.com/bnema/zut/packages/core"
10)
11
12// TestApplyAutoSubagentsToolReplacesOneFacadeEntry covers the interactive
13// refresh path: the previously installed facade is replaced by exactly one
14// entry whose non-nil fields are the currently granted actions, and unrelated
15// tools survive.
16func TestApplyAutoSubagentsToolReplacesOneFacadeEntry(t *testing.T) {
17 manager := subagents.NewResidentManager(t.TempDir(), nil)
18 t.Cleanup(func() { _ = manager.Close(context.Background()) })
19 preSeeded := &tools.SubagentTool{}
20 agent := core.NewAgent(nil, "model", "", core.Registry{
21 "read": &tools.GrepTool{},
22 // A previously installed facade, as a prior refresh would have left it.
23 "subagent": preSeeded,
24 })
25 allowed, resumeAllowed := true, true
26 interactive := NewInteractive(InteractiveConfig{
27 Agent: agent,
28 ResidentManager: manager,
29 AutoSubagentsToolAllowed: &allowed,
30 AutoSubagentsStatusToolAllowed: &allowed,
31 AutoSubagentsStopToolAllowed: &allowed,
32 AutoSubagentsResumeToolAllowed: &resumeAllowed,
33 // Interrupt is left unset, so it follows the spawn grant.
34 })
35
36 interactive.applyAutoSubagentsTool()
37
38 assertFacade := func(wantResume bool) {
39 t.Helper()
40 snapshot := agent.ToolsSnapshot()
41 if _, ok := snapshot["read"]; !ok {
42 t.Fatal("interactive refresh dropped an unrelated tool")
43 }
44 found := 0
45 for name := range snapshot {
46 switch name {
47 case tools.SubagentToolName:
48 found++
49 case "subagent_spawn", "subagent_status", "subagent_stop", "subagent_resume":
50 t.Fatalf("legacy tool key %q is present", name)
51 }
52 }
53 if found != 1 {
54 t.Fatalf("registry has %d %q entries, want 1", found, tools.SubagentToolName)
55 }
56 facade, ok := snapshot[tools.SubagentToolName].(*tools.SubagentTool)
57 if !ok {
58 t.Fatalf("registry entry %q = %T, want *tools.SubagentTool", tools.SubagentToolName, snapshot[tools.SubagentToolName])
59 }
60 if facade == preSeeded {
61 t.Fatal("interactive refresh reused the pre-seeded facade instead of replacing it")
62 }
63 if facade.Spawn == nil || facade.Status == nil || facade.Stop == nil || facade.Interrupt == nil {
64 t.Fatalf("facade actions = spawn:%v status:%v stop:%v interrupt:%v", facade.Spawn != nil, facade.Status != nil, facade.Stop != nil, facade.Interrupt != nil)
65 }
66 if (facade.Resume != nil) != wantResume {
67 t.Fatalf("facade resume = %v, want %v", facade.Resume != nil, wantResume)
68 }
69 }
70
71 assertFacade(true)
72
73 // A later policy change must replace the installed facade, not merge or
74 // duplicate it.
75 resumeAllowed = false
76 interactive.applyAutoSubagentsTool()
77 assertFacade(false)
78}