10 "github.com/stretchr/testify/require"
13 func TestNewAgentKeepsItsIDAcrossOpens(t *testing.T) {
14 dir := filepath.Join(t.TempDir(), "agent")
15 agent, err := openAgent(dir)
16 require.NoError(t, err)
17 require.Len(t, agent.config.ID, 2*agentIDBytes)
18 require.Equal(t, "gpt-6.1-sol", agent.config.Model)
19 require.True(t, filepath.IsAbs(agent.config.Workdir))
21 again, err := openAgent(dir)
22 require.NoError(t, err)
23 require.Equal(t, agent.config, again.config)
25 info, err := os.Stat(dir)
26 require.NoError(t, err)
27 require.Equal(t, os.FileMode(0o700), info.Mode().Perm())
30 func TestNewAgentsGetDifferentIDs(t *testing.T) {
31 first, err := openAgent(t.TempDir())
32 require.NoError(t, err)
34 second, err := openAgent(t.TempDir())
35 require.NoError(t, err)
36 require.Equal(t, first.config.Name, second.config.Name)
37 require.NotEqual(t, first.CacheKey(), second.CacheKey())
40 func TestRenamedAgentKeepsItsCacheKey(t *testing.T) {
42 agent, err := openAgent(dir)
43 require.NoError(t, err)
45 key := agent.CacheKey()
46 require.Equal(t, "onechat-"+agent.config.ID, key)
48 config := agent.config
50 agentWriteTestConfig(t, dir, config)
52 renamed, err := openAgent(dir)
53 require.NoError(t, err)
54 require.Equal(t, "Lena", renamed.config.Name)
55 require.Equal(t, key, renamed.CacheKey())
58 func TestAgentConfigErrors(t *testing.T) {
59 testCases := []struct {
65 name: "unknown field",
66 config: `{"id": "00112233445566778899aabbccddeeff", "nmae": "Lena"}`,
67 err: `unknown field "nmae"`,
71 config: `{"name": "Lena"}`,
72 err: "id must be 32 hexadecimal digits",
75 name: "relative workdir",
76 config: `{"id": "00112233445566778899aabbccddeeff", "workdir": "src"}`,
77 err: `workdir "src" is not an absolute path`,
80 name: "name of two lines",
81 config: `{"id": "00112233445566778899aabbccddeeff", "name": "Le\nna"}`,
82 err: "name must be one line of text",
86 for _, tc := range testCases {
87 t.Run(tc.name, func(t *testing.T) {
89 require.NoError(t, os.WriteFile(filepath.Join(dir, "config.json"), []byte(tc.config), 0o600))
91 _, err := openAgent(dir)
92 require.ErrorContains(t, err, tc.err)
97 func TestAgentPromptReadsTheInstructionsEachTime(t *testing.T) {
99 agent, err := openAgent(dir)
100 require.NoError(t, err)
102 text, err := agent.Prompt()
103 require.NoError(t, err)
104 require.Equal(t, prompt(agent.config.Name, filepath.Join(dir, agentInstructionsFileName), ""), text)
106 require.NoError(t, os.WriteFile(filepath.Join(dir, agentInstructionsFileName), []byte("I am Lena.\n"), 0o600))
108 text, err = agent.Prompt()
109 require.NoError(t, err)
110 require.Equal(t, prompt(agent.config.Name, filepath.Join(dir, agentInstructionsFileName), "I am Lena."), text)
113 func TestAgentPromptUsesAnAbsoluteInstructionsPath(t *testing.T) {
115 workdir, err := os.Getwd()
116 require.NoError(t, err)
118 relative, err := filepath.Rel(workdir, dir)
119 require.NoError(t, err)
120 require.False(t, filepath.IsAbs(relative))
122 agent, err := openAgent(relative)
123 require.NoError(t, err)
125 text, err := agent.Prompt()
126 require.NoError(t, err)
127 require.Contains(t, text, "The user's instructions live in the file "+filepath.Join(dir, agentInstructionsFileName)+",")
130 func TestChildPromptDefinesItsAreaAndReports(t *testing.T) {
132 agent, err := openAgent(dir)
133 require.NoError(t, err)
135 config := agent.config
136 config.Name = "Garden"
137 config.Parent = "OneChat"
138 config.Responsibility = "Keep the garden plans and decisions."
139 agentWriteTestConfig(t, dir, config)
140 require.NoError(t, os.WriteFile(filepath.Join(dir, agentInstructionsFileName), []byte("Ask before editing plans."), 0o600))
142 agent, err = openAgent(dir)
143 require.NoError(t, err)
145 text, err := agent.Prompt()
146 require.NoError(t, err)
147 require.Contains(t, text, "You are Garden, a child agent of OneChat.")
148 require.Contains(t, text, "You are responsible for: Keep the garden plans and decisions.")
149 require.Contains(t, text, "Work only in this area.")
150 require.Contains(t, text, "Report to OneChat only what needs the parent or the")
151 require.Contains(t, text, "End your turn with an empty reply when nothing needs the parent or the user.")
152 require.Contains(t, text, "Send nothing to WhatsApp. Draft replies in your report instead")
153 require.Contains(t, text, filepath.Join(dir, agentInstructionsFileName))
154 require.True(t, strings.HasSuffix(text, "Ask before editing plans."))
155 require.NotContains(t, text, "You act again only when the user writes.")
158 func TestChildConfigNeedsAParentAndAnArea(t *testing.T) {
159 testCases := []struct {
162 responsibility string
165 name: "a parent needs an area",
169 name: "an area needs a parent",
170 responsibility: "Keep the garden plans.",
173 for _, tc := range testCases {
174 t.Run(tc.name, func(t *testing.T) {
176 agent, err := openAgent(dir)
177 require.NoError(t, err)
179 config := agent.config
180 config.Parent = tc.parent
181 config.Responsibility = tc.responsibility
182 agentWriteTestConfig(t, dir, config)
184 _, err = openAgent(dir)
185 require.ErrorContains(t, err, "a child needs both responsibility and parent")
190 // agentWriteTestConfig writes config as the config.json of the agent in dir.
191 func agentWriteTestConfig(t *testing.T, dir string, config AgentConfig) {
194 data, err := json.Marshal(config)
195 require.NoError(t, err)
196 require.NoError(t, os.WriteFile(filepath.Join(dir, "config.json"), data, 0o600))