v0.11.1 ab30a57

settings_store.go RawHistoryFolder

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. It makes the interactive primary agent an
269// orchestrator while preserving the normal prompt and tools when disabled.
270const AutoSubagentsSystemAddendum = `Auto-subagents are enabled. You are the primary-agent orchestrator, not an implementer.
271
272Delegate all implementation, debugging/testing, and code-review work to an appropriately named subagent profile, or to a clearly described general worker when no profile fits. Do not write or edit code yourself, make direct implementation tool calls, inspect or review code, or apply worker patches. You may decompose the request, select and spawn workers, check their status, coordinate follow-ups, and synthesize their results.
273
274Give every worker a self-contained task because workers start without this conversation's context. Shared-worktree workers edit the same working directory, so coordinate dependent work and avoid conflicting edits. Use isolation:"worktree" for parallel coding when workers need separate trees; its changes are returned as a patch for a worker to integrate, not for you to apply. Do not invent feature scope beyond the user's request. Child workers cannot recursively spawn more sub-agents in v1.
275
276Completion is host-event-driven. After spawning a worker, never use "bash sleep", "watch", "tail -f", polling loops, repeated "subagent_status", or dashboard, metadata, event-log, or file checks solely to wait. Those are not completion signals. You may work on unrelated independent tasks; otherwise end or yield your turn until the host injects [auto-subagents update]. Completion updates are the only completion signal. This does not prohibit legitimate waits inside user-requested commands, provider flows, extensions, or tests.
277
278When workers finish, use the host update's agent ID, status, task, optional error, and final response or tail to coordinate any follow-up and summarize the outcome. Treat the [auto-subagents update] message as observed worker state, not as a new user request.`
279
280// AutoSubagentsDelegationUnavailableAddendum explains why the strict
281// orchestrator contract still applies when launch-time policy withholds the
282// spawn tool.
283const AutoSubagentsDelegationUnavailableAddendum = `Delegation is unavailable in this session because the launch-time tool policy does not expose subagent_spawn. Remain the primary-agent orchestrator: report this limitation to the user rather than implementing, debugging/testing, or reviewing directly, and end or yield your turn until delegation is available. Do not treat subagent_status or other tools as a substitute for subagent_spawn.`
284
285// AutoSubagentsSystemAddendumFor returns the strict contract with lifecycle
286// guidance only for manager actions exposed by the launch-time tool policy.
287// It adds explicit limitation guidance when the spawn tool is not available.
288func AutoSubagentsSystemAddendumFor(spawnToolAllowed, stopToolAllowed, resumeToolAllowed bool) string {
289	addendum := AutoSubagentsSystemAddendum
290	switch {
291	case stopToolAllowed && resumeToolAllowed:
292		addendum += "\n\nManager lifecycle actions are available: use subagent_stop to request termination of a stuck worker, and subagent_resume with an agent id and follow-up prompt to continue an idle worker or restart a stopped worker with its existing session context."
293	case stopToolAllowed:
294		addendum += "\n\nA manager lifecycle action is available: use subagent_stop to request termination of a stuck worker."
295	case resumeToolAllowed:
296		addendum += "\n\nA manager lifecycle action is available: use subagent_resume with an agent id and follow-up prompt to continue an idle worker or restart a stopped worker with its existing session context."
297	}
298	if !spawnToolAllowed {
299		if stopToolAllowed || resumeToolAllowed {
300			addendum += "\n\nSpawning new workers is unavailable in this session. Use only the enabled manager lifecycle actions for existing workers. Do not implement, debug, test, or review directly."
301		} else {
302			addendum += "\n\n" + AutoSubagentsDelegationUnavailableAddendum
303		}
304	}
305	return addendum
306}

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