1package agent
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8
9 "github.com/bnema/zut/packages/agent/subagents"
10 "github.com/bnema/zut/packages/agent/tools"
11)
12
13func TestResolveSeparatesInteractiveProactiveAndHeadlessStrictPolicies(t *testing.T) {
14 root := t.TempDir()
15 zutHome := filepath.Join(root, "zut-home")
16 home := filepath.Join(root, "home")
17 project := filepath.Join(root, "project")
18 t.Setenv("ZUT_HOME", zutHome)
19 t.Setenv("HOME", home)
20 t.Setenv("USERPROFILE", home)
21 if err := os.MkdirAll(project, 0o755); err != nil {
22 t.Fatal(err)
23 }
24 profilesDir := filepath.Join(home, ".agents", "agents")
25 if err := os.MkdirAll(profilesDir, 0o755); err != nil {
26 t.Fatal(err)
27 }
28 if err := os.WriteFile(filepath.Join(profilesDir, "reviewer.md"), []byte("---\nname: reviewer\ndescription: Review delegated work\n---\nReview the requested scope."), 0o600); err != nil {
29 t.Fatal(err)
30 }
31 enabled := true
32 if err := SaveConfig(Config{AutoSubagentsEnabled: &enabled}); err != nil {
33 t.Fatal(err)
34 }
35
36 base := Args{
37 Provider: "ollama",
38 Model: "any-local-model",
39 CWD: project,
40 NoLSP: true,
41 NoSkill: true,
42 }
43 interactive, err := Resolve(base, false)
44 if err != nil {
45 t.Fatalf("resolve interactive: %v", err)
46 }
47 if !strings.Contains(interactive.SystemPrompt, ProactiveSubagentsSystemAddendum) || strings.Contains(interactive.SystemPrompt, StrictOrchestratorSystemAddendum) || !strings.Contains(interactive.SystemPrompt, "[subagents_list]") || !strings.Contains(interactive.SystemPrompt, "- reviewer: Review delegated work") {
48 t.Fatalf("interactive prompt did not select proactive primary policy and reviewer profile:\n%s", interactive.SystemPrompt)
49 }
50
51 childArgs := residentChildArgs(base, "ollama", subagents.ResidentChildSpec{Provider: "ollama", Model: "any-local-model"})
52 child, err := Resolve(childArgs, false)
53 if err != nil {
54 t.Fatalf("resolve resident child: %v", err)
55 }
56 if strings.Contains(child.SystemPrompt, ProactiveSubagentsSystemAddendum) || strings.Contains(child.SystemPrompt, StrictOrchestratorSystemAddendum) || strings.Contains(child.SystemPrompt, "[subagents_list]") {
57 t.Fatalf("resident child inherited primary delegation policy or profiles:\n%s", child.SystemPrompt)
58 }
59
60 headlessArgs := base
61 headlessArgs.Mode = ModePrint
62 headlessArgs.Orchestrate = true
63 headless, err := Resolve(headlessArgs, false)
64 if err != nil {
65 t.Fatalf("resolve headless orchestrator: %v", err)
66 }
67 if !strings.Contains(headless.SystemPrompt, StrictOrchestratorSystemAddendum) || strings.Contains(headless.SystemPrompt, ProactiveSubagentsSystemAddendum) {
68 t.Fatalf("headless prompt did not select strict policy:\n%s", headless.SystemPrompt)
69 }
70}
71
72func TestOrchestratorAlwaysReceivesResearchTools(t *testing.T) {
73 for _, tc := range []struct {
74 name string
75 args Args
76 wantResearch bool
77 wantWeb bool
78 wantWriteTool bool
79 }{
80 {
81 name: "requested allowlist",
82 args: Args{Orchestrate: true, ToolsSet: true, Tools: []string{"subagent_spawn"}},
83 wantResearch: true,
84 wantWeb: true,
85 wantWriteTool: false,
86 },
87 {
88 name: "web search policy disabled",
89 args: Args{Orchestrate: true, WebSearchPolicy: subagents.WebSearchDeny},
90 wantResearch: true,
91 wantWeb: true,
92 wantWriteTool: true,
93 },
94 {
95 name: "permission ceiling",
96 args: Args{Orchestrate: true, PermissionSet: &tools.PermissionSet{}},
97 wantResearch: true,
98 wantWeb: false,
99 wantWriteTool: true,
100 },
101 {
102 name: "tools disabled",
103 args: Args{Orchestrate: true, NoTools: true, ToolsSet: true, Tools: []string{"write"}},
104 wantResearch: false,
105 wantWeb: false,
106 wantWriteTool: false,
107 },
108 {
109 name: "normal session",
110 args: Args{ToolsSet: true, Tools: []string{"write"}},
111 wantResearch: false,
112 wantWeb: false,
113 wantWriteTool: true,
114 },
115 } {
116 t.Run(tc.name, func(t *testing.T) {
117 registry := buildToolRegistry(tc.args, t.TempDir(), nil, false, false, false)
118
119 if got := orchestratorExplorationToolsAllowed(tc.args); got != tc.wantResearch {
120 t.Fatalf("orchestratorExplorationToolsAllowed = %v, want %v", got, tc.wantResearch)
121 }
122 _, gotRead := registry["read"]
123 if gotRead != tc.wantResearch {
124 t.Fatalf("read present = %v, want %v", gotRead, tc.wantResearch)
125 }
126 if _, ok := registry["grep"].(*tools.GrepTool); ok != tc.wantResearch {
127 t.Fatalf("grep = %T, want research tool present = %v", registry["grep"], tc.wantResearch)
128 }
129 for _, name := range tools.WebCapabilityNames {
130 _, gotWebTool := registry[name]
131 if gotWebTool != tc.wantWeb {
132 t.Fatalf("%s present = %v, want web tool present = %v", name, gotWebTool, tc.wantWeb)
133 }
134 }
135 _, gotWrite := registry["write"]
136 if gotWrite != tc.wantWriteTool {
137 t.Fatalf("write present = %v, want %v", gotWrite, tc.wantWriteTool)
138 }
139 })
140 }
141}