1package agent
2
3import (
4 "fmt"
5
6 "github.com/bnema/zut/packages/provider"
7 "github.com/bnema/zut/packages/tui"
8)
9
10type configSettingsStore struct{}
11
12func (configSettingsStore) SetQuickModelShortcut(slot int, providerName, model string) error {
13 if slot < 1 || slot > 9 {
14 return nil
15 }
16 cfg, err := LoadConfig()
17 if err != nil {
18 return err
19 }
20 if len(cfg.QuickModelShortcuts) < slot {
21 next := make([]QuickModelShortcut, slot)
22 copy(next, cfg.QuickModelShortcuts)
23 cfg.QuickModelShortcuts = next
24 }
25 cfg.QuickModelShortcuts[slot-1] = QuickModelShortcut{Provider: providerName, Model: model}
26 // Trim trailing empty slots so config.json stays compact.
27 for len(cfg.QuickModelShortcuts) > 0 {
28 last := cfg.QuickModelShortcuts[len(cfg.QuickModelShortcuts)-1]
29 if last.Provider != "" || last.Model != "" {
30 break
31 }
32 cfg.QuickModelShortcuts = cfg.QuickModelShortcuts[:len(cfg.QuickModelShortcuts)-1]
33 }
34 return SaveConfig(cfg)
35}
36
37func (configSettingsStore) SetInlineImages(enabled bool) error {
38 cfg, err := LoadConfig()
39 if err != nil {
40 return err
41 }
42 cfg.InlineImagesEnabled = &enabled
43 return SaveConfig(cfg)
44}
45
46func (configSettingsStore) SetTerminalAlertsEnabled(enabled bool) error {
47 cfg, err := LoadConfig()
48 if err != nil {
49 return err
50 }
51 cfg.TerminalAlertsEnabled = &enabled
52 return SaveConfig(cfg)
53}
54
55func (configSettingsStore) SetTerminalTitleEnabled(enabled bool) error {
56 cfg, err := LoadConfig()
57 if err != nil {
58 return err
59 }
60 cfg.TerminalTitleEnabled = &enabled
61 return SaveConfig(cfg)
62}
63
64func (configSettingsStore) SetAutoSubagents(enabled bool) error {
65 cfg, err := LoadConfig()
66 if err != nil {
67 return err
68 }
69 cfg.AutoSubagentsEnabled = &enabled
70 return SaveConfig(cfg)
71}
72
73func (configSettingsStore) SetPonytailEnabled(enabled bool) error {
74 cfg, err := LoadConfig()
75 if err != nil {
76 return fmt.Errorf("load config for Ponytail setting: %w", err)
77 }
78 cfg.PonytailEnabled = &enabled
79 if err := SaveConfig(cfg); err != nil {
80 return fmt.Errorf("save Ponytail setting: %w", err)
81 }
82 return nil
83}
84
85func (configSettingsStore) SetWebSearchEnabled(enabled bool) error {
86 cfg, err := LoadConfig()
87 if err != nil {
88 return fmt.Errorf("load config for web search setting: %w", err)
89 }
90 cfg.WebSearchEnabled = &enabled
91 if err := SaveConfig(cfg); err != nil {
92 return fmt.Errorf("save web search setting: %w", err)
93 }
94 return nil
95}
96
97func (configSettingsStore) SetFastMode(enabled bool) error {
98 cfg, err := LoadConfig()
99 if err != nil {
100 return err
101 }
102 cfg.FastMode = &enabled
103 return SaveConfig(cfg)
104}
105
106func (configSettingsStore) SetLSPEnabled(enabled bool) error {
107 cfg, err := LoadConfig()
108 if err != nil {
109 return err
110 }
111 cfg.LSPEnabled = &enabled
112 return SaveConfig(cfg)
113}
114
115func (configSettingsStore) SetSubagentLSPEnabled(enabled bool) error {
116 cfg, err := LoadConfig()
117 if err != nil {
118 return err
119 }
120 cfg.SubagentLSPEnabled = &enabled
121 return SaveConfig(cfg)
122}
123
124func (configSettingsStore) SetAutoCompactThreshold(percent int) error {
125 cfg, err := LoadConfig()
126 if err != nil {
127 return err
128 }
129 cfg.AutoCompactThreshold = &percent
130 return SaveConfig(cfg)
131}
132
133func (configSettingsStore) SetJailByDefault(enabled bool) error {
134 cfg, err := LoadConfig()
135 if err != nil {
136 return err
137 }
138 cfg.JailByDefault = &enabled
139 return SaveConfig(cfg)
140}
141
142func (configSettingsStore) SetRecursiveFileSuggest(enabled bool) error {
143 cfg, err := LoadConfig()
144 if err != nil {
145 return err
146 }
147 cfg.RecursiveFileSuggest = &enabled
148 return SaveConfig(cfg)
149}
150
151func (configSettingsStore) SetRespectGitignore(enabled bool) error {
152 cfg, err := LoadConfig()
153 if err != nil {
154 return err
155 }
156 cfg.RespectGitignore = &enabled
157 return SaveConfig(cfg)
158}
159
160func (configSettingsStore) SetCompactMode(enabled bool) error {
161 cfg, err := LoadConfig()
162 if err != nil {
163 return err
164 }
165 cfg.CompactMode = &enabled
166 return SaveConfig(cfg)
167}
168
169func (configSettingsStore) SetShowInstructionsAtStartup(enabled bool) error {
170 cfg, err := LoadConfig()
171 if err != nil {
172 return err
173 }
174 cfg.ShowInstructionsAtStartup = &enabled
175 return SaveConfig(cfg)
176}
177
178func (configSettingsStore) SetTUIInputStyle(style string) error {
179 cfg, err := LoadConfig()
180 if err != nil {
181 return err
182 }
183 style = tui.NormalizeInputStyle(style)
184 if style == tui.InputStylePlain {
185 cfg.TUIInputStyle = ""
186 } else {
187 cfg.TUIInputStyle = style
188 }
189 return SaveConfig(cfg)
190}
191
192func (configSettingsStore) SetTUIStatusPosition(position string) error {
193 cfg, err := LoadConfig()
194 if err != nil {
195 return err
196 }
197 position = tui.NormalizeStatusPosition(position)
198 if position == tui.StatusPositionAboveInput {
199 cfg.TUIStatusPosition = ""
200 } else {
201 cfg.TUIStatusPosition = position
202 }
203 return SaveConfig(cfg)
204}
205
206func (configSettingsStore) SetTUIWorkingPosition(position string) error {
207 cfg, err := LoadConfig()
208 if err != nil {
209 return err
210 }
211 position = tui.NormalizeWorkingPosition(position)
212 if position == tui.WorkingPositionAboveInput {
213 cfg.TUIWorkingPosition = ""
214 } else {
215 cfg.TUIWorkingPosition = position
216 }
217 return SaveConfig(cfg)
218}
219
220func (configSettingsStore) SetTUISubagentPosition(position string) error {
221 cfg, err := LoadConfig()
222 if err != nil {
223 return err
224 }
225 position = tui.NormalizeSubagentPosition(position)
226 if position == tui.SubagentPositionBelowInput {
227 cfg.TUISubagentPosition = ""
228 } else {
229 cfg.TUISubagentPosition = position
230 }
231 return SaveConfig(cfg)
232}
233
234func (configSettingsStore) SetReasoning(level string) error {
235 cfg, err := LoadConfig()
236 if err != nil {
237 return err
238 }
239 cfg.Reasoning = provider.NormalizeReasoning(level)
240 return SaveConfig(cfg)
241}
242
243func (configSettingsStore) SetTheme(name string) error {
244 cfg, err := LoadConfig()
245 if err != nil {
246 return err
247 }
248 if name == "auto" {
249 name = ""
250 }
251 cfg.Theme = name
252 return SaveConfig(cfg)
253}
254
255// AutoSubagentsEnabled reads the current auto-subagents flag from config.
256// The build/CLI startup path uses it when initially injecting
257// the auto-subagent tools into the tool registry; interactive mode mirrors
258// the setting into the live tool registry when its settings toggle changes.
259func AutoSubagentsEnabled() bool {
260 cfg, err := LoadConfig()
261 if err != nil {
262 return false
263 }
264 return cfg.AutoSubagentsEnabled != nil && *cfg.AutoSubagentsEnabled
265}
266
267// AutoSubagentsSystemAddendum is appended to the system prompt when
268// auto-subagents is enabled, so the model knows it may delegate to
269// background sub-agents without the user having to mention the tool
270// by name. Kept short so it doesn't bloat the cached prompt prefix.
271const AutoSubagentsSystemAddendum = `Auto-subagents are enabled. When available, subagent_spawn starts background sub-agents in separate long-lived processes, and subagent_status reports their live state without waiting for completion.
272
273Use it proactively when the user's request naturally splits into independent sub-tasks that can run concurrently; prefer isolation:"worktree" for parallel coding (e.g. "refactor module A and module B", "write the implementation and the tests", "investigate three separate files"). Spawn one sub-agent per independent sub-task with a self-contained task description (sub-agents start with no context from this conversation). If [subagents_list] is present, choose the named profile whose description best matches the task and pass its name as the tool's agent field. Continue working on the remaining or coordinating work yourself in parallel; do not wait for sub-agents to finish before responding. Briefly tell the user which sub-agents you spawned and what each is doing. When workers finish, use the host update's agent ID, status, task, optional error, and final response or tail to summarize the outcome and decide any follow-up.
274
275Do NOT use subagent_spawn for trivial single-step work, for tasks that depend on each other sequentially, or when the user explicitly asked you to do the work yourself. Child workers cannot recursively spawn more sub-agents in v1.
276
277When every sub-agent you spawned reaches a terminal state, the host injects a single [auto-subagents update] message containing each agent's id, status, task, optional error, and final response (or a tail when the response is long). Treat that message as observed state (not as a new user request) and write a short follow-up summary referencing each agent by id.`