v0.3.93 0192a2e

swarm_spawn_test.go RawHistoryFolder

1package tools
2
3import (
4	"context"
5	"encoding/json"
6	"path/filepath"
7	"strings"
8	"testing"
9
10	"github.com/patriceckhart/zot/packages/agent/swarm"
11	"github.com/patriceckhart/zot/packages/provider"
12)
13
14type noopSwarmRunner struct{}
15
16func (noopSwarmRunner) Run(context.Context, swarm.Sink) error { return nil }
17
18func newTestSwarm(t *testing.T) *swarm.Swarm {
19	t.Helper()
20	root := t.TempDir()
21	return swarm.New(swarm.Config{
22		Root:     filepath.Join(root, "swarm"),
23		RepoRoot: root,
24		NewRunner: func(*swarm.Agent) swarm.Runner {
25			return noopSwarmRunner{}
26		},
27	})
28}
29
30func TestSwarmSpawnInheritsHostModelAndProviderWhenOmitted(t *testing.T) {
31	tool := &SwarmSpawnTool{
32		Swarm:           newTestSwarm(t),
33		Enabled:         func() bool { return true },
34		DefaultModel:    func() string { return "gpt-5" },
35		DefaultProvider: func() string { return "openai-codex" },
36	}
37
38	res, err := tool.Execute(context.Background(), json.RawMessage(`{"task":"research docs"}`), nil)
39	if err != nil {
40		t.Fatal(err)
41	}
42	if res.IsError {
43		t.Fatalf("unexpected tool error: %s", textResult(res.Content))
44	}
45	details, ok := res.Details.(map[string]any)
46	if !ok {
47		t.Fatalf("details type = %T, want map[string]any", res.Details)
48	}
49	if got := details["model"]; got != "gpt-5" {
50		t.Fatalf("model detail = %v, want gpt-5", got)
51	}
52	if got := details["provider"]; got != "openai-codex" {
53		t.Fatalf("provider detail = %v, want openai-codex", got)
54	}
55	text := textResult(res.Content)
56	if !strings.Contains(text, "model: gpt-5") || !strings.Contains(text, "provider: openai-codex") {
57		t.Fatalf("result text missing inherited model/provider:\n%s", text)
58	}
59
60	agents := tool.Swarm.List()
61	if len(agents) != 1 {
62		t.Fatalf("spawned agents = %d, want 1", len(agents))
63	}
64	if agents[0].Model != "gpt-5" || agents[0].Provider != "openai-codex" {
65		t.Fatalf("agent model/provider = %q/%q, want gpt-5/openai-codex", agents[0].Model, agents[0].Provider)
66	}
67}
68
69func TestSwarmSpawnRejectsPartialModelProviderOverride(t *testing.T) {
70	tool := &SwarmSpawnTool{
71		Swarm:           newTestSwarm(t),
72		Enabled:         func() bool { return true },
73		DefaultModel:    func() string { return "gpt-5" },
74		DefaultProvider: func() string { return "openai-codex" },
75	}
76
77	res, err := tool.Execute(context.Background(), json.RawMessage(`{"task":"research docs","provider":"openai"}`), nil)
78	if err != nil {
79		t.Fatal(err)
80	}
81	if !res.IsError {
82		t.Fatalf("expected partial override to fail")
83	}
84	if got := textResult(res.Content); !strings.Contains(got, "omit both model/provider") {
85		t.Fatalf("error text = %q", got)
86	}
87	if got := len(tool.Swarm.List()); got != 0 {
88		t.Fatalf("spawned agents = %d, want 0", got)
89	}
90}
91
92func textResult(content []provider.Content) string {
93	if len(content) == 0 {
94		return ""
95	}
96	if tb, ok := content[0].(provider.TextBlock); ok {
97		return tb.Text
98	}
99	return ""
100}

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 477

↑↓ moveenter openesc close