v0.50.0 c4ec31f

build_orchestrator_test.go RawHistoryFolder

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			if _, ok := registry["glob"].(*tools.GlobTool); ok != tc.wantResearch {
130				t.Fatalf("glob = %T, want research tool present = %v", registry["glob"], tc.wantResearch)
131			}
132			for _, name := range tools.WebCapabilityNames {
133				_, gotWebTool := registry[name]
134				if gotWebTool != tc.wantWeb {
135					t.Fatalf("%s present = %v, want web tool present = %v", name, gotWebTool, tc.wantWeb)
136				}
137			}
138			_, gotWrite := registry["write"]
139			if gotWrite != tc.wantWriteTool {
140				t.Fatalf("write present = %v, want %v", gotWrite, tc.wantWriteTool)
141			}
142		})
143	}
144}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close