detached 7368d56

Merge branch 'main' into doc/mcpo-example

commit
7368d56f94a0f9a5c7d116f800a3e24ba61389cf
author
Patric Eckhart · · 2026-07-03 17:43 UTC
committer
GitHub · · 2026-07-03 17:43 UTC
merge of
7f35cae + 141b2c1 · diff against the first parent
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
refs
tag: v0.2.56
4 files · +46 -8
M README.md +1 -1
@@ -302,7 +302,7 @@ Opens a dialog with every persistent setting. `up`/`down` to navigate, `enter` o
302302 - **render images when supported** — draw screenshots / `read`-returned images inline using the terminal's image protocol, or fall back to a text placeholder. Auto-detected from `TERM_PROGRAM`; the toggle overrides the detection. The row is greyed out and forced off on terminals that don't speak any image protocol.
303303 - **auto-swarm** — let the main agent spawn background sub-agents in parallel via a built-in `swarm_spawn` tool. Off by default. When on, the tool is registered with the running agent, the system prompt gains a short addendum telling the model to delegate independent sub-tasks proactively, and zot watches every sub-agent the main agent spawns. As soon as the last sub-agent in a batch finishes its initial task, an `[auto-swarm update]` message is injected back into the chat with each agent's status / task / transcript tail, so the main agent can summarise the collective outcome. Flipping off mid-session removes the tool from the live agent and strips the addendum on the next turn — the model stops trying to delegate. See `/swarm` for the dashboard that lets you monitor, message, kill, or remove the spawned agents.
304304 - **compact transcript rendering**: reduce visual chrome in the chat transcript. Tool calls render as a quiet header plus indented output instead of a bordered box, and sent messages render without padded background bubbles. Off by default. Changes apply immediately and persist to `config.json` as `compact_mode`.
305-- **TUI settings**: opens a sub-view for input layout and status placement. **Input style** can be `plain` (default prompt line) or `lines` (separator lines above and below the input). **Status position** places model, usage, and working-directory information above or below the input. **Working spinner position** places the busy spinner above or below the input. Changes apply immediately and persist to `config.json` as `tui_input_style`, `tui_status_position`, and `tui_working_position` (`above_input` or `below_input` for the position fields).
305+- **TUI settings**: opens a sub-view for input layout and status placement. **Input style** can be `plain` (default prompt line), `lines` (separator lines above and below the input), or `block` (a user-bubble-style input block). **Status position** places model, usage, and working-directory information above or below the input. **Working spinner position** places the busy spinner above or below the input. Changes apply immediately and persist to `config.json` as `tui_input_style`, `tui_status_position`, and `tui_working_position` (`above_input` or `below_input` for the position fields).
306306 - **thinking level** — choose reasoning for supported models: off (default; no reasoning), minimum (~1k tokens), low (~2k), medium (~8k), high (~16k), maximum (~32k). The change is persisted to `config.json` and applied to the running agent's next model call.
307307 - **color theme** — choose the built-in auto/dark/light theme or any JSON theme discovered under `$ZOT_HOME/themes` or a loaded extension. Theme files can override any subset of UI colors, syntax colors, and spinner frames/messages. Changes apply immediately; if a selected theme file is deleted, zot resets to auto. See [docs/themes.md](docs/themes.md).
308308 - **model shortcuts** — opens a sub-view with nine slots (`model 1` ... `model 9`). `enter` on a slot opens the same `/model` selector and binds the chosen provider/model to that slot; `backspace` clears a slot. Once assigned, press `Ctrl+1` ... `Ctrl+9` from the editor to switch the active model instantly (the same cross-provider swap `/model` performs, transcript and cost carried over). Assigning a shortcut does not change the current model. Shortcuts are skipped while a turn is running.
@@ -302,7 +302,7 @@ Opens a dialog with every persistent setting. `up`/`down` to navigate, `enter` o
302 - **render images when supported** — draw screenshots / `read`-returned images inline using the terminal's image protocol, or fall back to a text placeholder. Auto-detected from `TERM_PROGRAM`; the toggle overrides the detection. The row is greyed out and forced off on terminals that don't speak any image protocol.302 - **render images when supported** — draw screenshots / `read`-returned images inline using the terminal's image protocol, or fall back to a text placeholder. Auto-detected from `TERM_PROGRAM`; the toggle overrides the detection. The row is greyed out and forced off on terminals that don't speak any image protocol.
303 - **auto-swarm** — let the main agent spawn background sub-agents in parallel via a built-in `swarm_spawn` tool. Off by default. When on, the tool is registered with the running agent, the system prompt gains a short addendum telling the model to delegate independent sub-tasks proactively, and zot watches every sub-agent the main agent spawns. As soon as the last sub-agent in a batch finishes its initial task, an `[auto-swarm update]` message is injected back into the chat with each agent's status / task / transcript tail, so the main agent can summarise the collective outcome. Flipping off mid-session removes the tool from the live agent and strips the addendum on the next turn — the model stops trying to delegate. See `/swarm` for the dashboard that lets you monitor, message, kill, or remove the spawned agents.303 - **auto-swarm** — let the main agent spawn background sub-agents in parallel via a built-in `swarm_spawn` tool. Off by default. When on, the tool is registered with the running agent, the system prompt gains a short addendum telling the model to delegate independent sub-tasks proactively, and zot watches every sub-agent the main agent spawns. As soon as the last sub-agent in a batch finishes its initial task, an `[auto-swarm update]` message is injected back into the chat with each agent's status / task / transcript tail, so the main agent can summarise the collective outcome. Flipping off mid-session removes the tool from the live agent and strips the addendum on the next turn — the model stops trying to delegate. See `/swarm` for the dashboard that lets you monitor, message, kill, or remove the spawned agents.
304 - **compact transcript rendering**: reduce visual chrome in the chat transcript. Tool calls render as a quiet header plus indented output instead of a bordered box, and sent messages render without padded background bubbles. Off by default. Changes apply immediately and persist to `config.json` as `compact_mode`.304 - **compact transcript rendering**: reduce visual chrome in the chat transcript. Tool calls render as a quiet header plus indented output instead of a bordered box, and sent messages render without padded background bubbles. Off by default. Changes apply immediately and persist to `config.json` as `compact_mode`.
305-- **TUI settings**: opens a sub-view for input layout and status placement. **Input style** can be `plain` (default prompt line) or `lines` (separator lines above and below the input). **Status position** places model, usage, and working-directory information above or below the input. **Working spinner position** places the busy spinner above or below the input. Changes apply immediately and persist to `config.json` as `tui_input_style`, `tui_status_position`, and `tui_working_position` (`above_input` or `below_input` for the position fields).305+- **TUI settings**: opens a sub-view for input layout and status placement. **Input style** can be `plain` (default prompt line), `lines` (separator lines above and below the input), or `block` (a user-bubble-style input block). **Status position** places model, usage, and working-directory information above or below the input. **Working spinner position** places the busy spinner above or below the input. Changes apply immediately and persist to `config.json` as `tui_input_style`, `tui_status_position`, and `tui_working_position` (`above_input` or `below_input` for the position fields).
306 - **thinking level** — choose reasoning for supported models: off (default; no reasoning), minimum (~1k tokens), low (~2k), medium (~8k), high (~16k), maximum (~32k). The change is persisted to `config.json` and applied to the running agent's next model call.306 - **thinking level** — choose reasoning for supported models: off (default; no reasoning), minimum (~1k tokens), low (~2k), medium (~8k), high (~16k), maximum (~32k). The change is persisted to `config.json` and applied to the running agent's next model call.
307 - **color theme** — choose the built-in auto/dark/light theme or any JSON theme discovered under `$ZOT_HOME/themes` or a loaded extension. Theme files can override any subset of UI colors, syntax colors, and spinner frames/messages. Changes apply immediately; if a selected theme file is deleted, zot resets to auto. See [docs/themes.md](docs/themes.md).307 - **color theme** — choose the built-in auto/dark/light theme or any JSON theme discovered under `$ZOT_HOME/themes` or a loaded extension. Theme files can override any subset of UI colors, syntax colors, and spinner frames/messages. Changes apply immediately; if a selected theme file is deleted, zot resets to auto. See [docs/themes.md](docs/themes.md).
308 - **model shortcuts** — opens a sub-view with nine slots (`model 1` ... `model 9`). `enter` on a slot opens the same `/model` selector and binds the chosen provider/model to that slot; `backspace` clears a slot. Once assigned, press `Ctrl+1` ... `Ctrl+9` from the editor to switch the active model instantly (the same cross-provider swap `/model` performs, transcript and cost carried over). Assigning a shortcut does not change the current model. Shortcuts are skipped while a turn is running.308 - **model shortcuts** — opens a sub-view with nine slots (`model 1` ... `model 9`). `enter` on a slot opens the same `/model` selector and binds the chosen provider/model to that slot; `backspace` clears a slot. Once assigned, press `Ctrl+1` ... `Ctrl+9` from the editor to switch the active model instantly (the same cross-provider swap `/model` performs, transcript and cost carried over). Assigning a shortcut does not change the current model. Shortcuts are skipped while a turn is running.
M packages/agent/config.go +1 -1
@@ -76,7 +76,7 @@ type Config struct {
7676 CompactMode *bool `json:"compact_mode,omitempty"`
7777
7878 // TUIInputStyle controls the main input rendering. Supported values:
79- // "plain" (default) and "lines".
79+ // "plain" (default), "lines", and "block".
8080 TUIInputStyle string `json:"tui_input_style,omitempty"`
8181
8282 // TUIStatusPosition controls whether model, usage, and cwd information
@@ -76,7 +76,7 @@ type Config struct {
76 CompactMode *bool `json:"compact_mode,omitempty"`76 CompactMode *bool `json:"compact_mode,omitempty"`
77 77
78 // TUIInputStyle controls the main input rendering. Supported values:78 // TUIInputStyle controls the main input rendering. Supported values:
79- // "plain" (default) and "lines".79+ // "plain" (default), "lines", and "block".
80 TUIInputStyle string `json:"tui_input_style,omitempty"`80 TUIInputStyle string `json:"tui_input_style,omitempty"`
81 81
82 // TUIStatusPosition controls whether model, usage, and cwd information82 // TUIStatusPosition controls whether model, usage, and cwd information
M packages/agent/modes/interactive.go +13 -5
@@ -1251,7 +1251,8 @@ func (i *Interactive) redraw() {
12511251 Telegram: i.telegramBridge != nil && i.telegramBridge.Active(),
12521252 Cols: cols,
12531253 })
1254- if tui.NormalizeInputStyle(i.cfg.TUIInputStyle) == tui.InputStyleLines {
1254+ inputStyle := tui.NormalizeInputStyle(i.cfg.TUIInputStyle)
1255+ if inputStyle == tui.InputStyleLines || inputStyle == tui.InputStyleBlock {
12551256 i.ed.Prompt = ""
12561257 } else {
12571258 i.ed.Prompt = i.cfg.Theme.AccentBar(i.cfg.Theme.Accent)
@@ -1262,9 +1263,15 @@ func (i *Interactive) redraw() {
12621263 workingLines = []string{" " + busyPrefix}
12631264 }
12641265 inputCursorOffset := 0
1265- if tui.NormalizeInputStyle(i.cfg.TUIInputStyle) == tui.InputStyleLines {
1266+ inputCursorColOffset := 0
1267+ switch inputStyle {
1268+ case tui.InputStyleLines:
12661269 edLines = tui.InputLines(i.cfg.Theme, edLines, cols)
12671270 inputCursorOffset = 1
1271+ case tui.InputStyleBlock:
1272+ edLines = tui.InputBlock(i.cfg.Theme, edLines, cols)
1273+ inputCursorOffset = 1
1274+ inputCursorColOffset = 2
12681275 }
12691276
12701277 // "Sliding in" chips for messages the user typed while a turn is
@@ -1313,7 +1320,7 @@ func (i *Interactive) redraw() {
13131320 if !i.swarmDialog.Active() {
13141321 bottom = append(bottom, suggest...)
13151322 bottom = append(bottom, queue...)
1316- lineInput := tui.NormalizeInputStyle(i.cfg.TUIInputStyle) == tui.InputStyleLines
1323+ lineInput := inputStyle == tui.InputStyleLines
13171324 statusBelow := statusPosition == tui.StatusPositionBelowInput
13181325 workingBelow := workingPosition == tui.WorkingPositionBelowInput
13191326
@@ -1506,7 +1513,7 @@ func (i *Interactive) redraw() {
15061513 dialogLead = 1
15071514 }
15081515 cursorRow := inputStartRow + inputCursorOffset + curR
1509- cursorCol := curC
1516+ cursorCol := curC + inputCursorColOffset
15101517 if i.btwDialog.Active() {
15111518 if r, c := i.btwDialog.CursorPos(cols); r >= 0 {
15121519 cursorRow = dialogLead + r
@@ -2957,6 +2964,7 @@ func (i *Interactive) openSettingsDialog() {
29572964 inputStyleOptions := []settingsOption{
29582965 {value: tui.InputStylePlain, label: "plain", desc: "render the input as the normal prompt line"},
29592966 {value: tui.InputStyleLines, label: "lines", desc: "draw separator lines above and below the input"},
2967+ {value: tui.InputStyleBlock, label: "block", desc: "render the input as a user-bubble-style block"},
29602968 }
29612969 inputStyleChoice := 0
29622970 for idx, opt := range inputStyleOptions {
@@ -3032,7 +3040,7 @@ func (i *Interactive) openSettingsDialog() {
30323040 {
30333041 key: "tui_input_style",
30343042 label: "input style",
3035- desc: "choose between the plain prompt and a lined input area",
3043+ desc: "choose between the plain prompt, lines, and a block input area",
30363044 options: inputStyleOptions,
30373045 choice: inputStyleChoice,
30383046 },
@@ -1251,7 +1251,8 @@ func (i *Interactive) redraw() {
1251 Telegram: i.telegramBridge != nil && i.telegramBridge.Active(),1251 Telegram: i.telegramBridge != nil && i.telegramBridge.Active(),
1252 Cols: cols,1252 Cols: cols,
1253 })1253 })
1254- if tui.NormalizeInputStyle(i.cfg.TUIInputStyle) == tui.InputStyleLines {1254+ inputStyle := tui.NormalizeInputStyle(i.cfg.TUIInputStyle)
1255+ if inputStyle == tui.InputStyleLines || inputStyle == tui.InputStyleBlock {
1255 i.ed.Prompt = ""1256 i.ed.Prompt = ""
1256 } else {1257 } else {
1257 i.ed.Prompt = i.cfg.Theme.AccentBar(i.cfg.Theme.Accent)1258 i.ed.Prompt = i.cfg.Theme.AccentBar(i.cfg.Theme.Accent)
@@ -1262,9 +1263,15 @@ func (i *Interactive) redraw() {
1262 workingLines = []string{" " + busyPrefix}1263 workingLines = []string{" " + busyPrefix}
1263 }1264 }
1264 inputCursorOffset := 01265 inputCursorOffset := 0
1265- if tui.NormalizeInputStyle(i.cfg.TUIInputStyle) == tui.InputStyleLines {1266+ inputCursorColOffset := 0
1267+ switch inputStyle {
1268+ case tui.InputStyleLines:
1266 edLines = tui.InputLines(i.cfg.Theme, edLines, cols)1269 edLines = tui.InputLines(i.cfg.Theme, edLines, cols)
1267 inputCursorOffset = 11270 inputCursorOffset = 1
1271+ case tui.InputStyleBlock:
1272+ edLines = tui.InputBlock(i.cfg.Theme, edLines, cols)
1273+ inputCursorOffset = 1
1274+ inputCursorColOffset = 2
1268 }1275 }
1269 1276
1270 // "Sliding in" chips for messages the user typed while a turn is1277 // "Sliding in" chips for messages the user typed while a turn is
@@ -1313,7 +1320,7 @@ func (i *Interactive) redraw() {
1313 if !i.swarmDialog.Active() {1320 if !i.swarmDialog.Active() {
1314 bottom = append(bottom, suggest...)1321 bottom = append(bottom, suggest...)
1315 bottom = append(bottom, queue...)1322 bottom = append(bottom, queue...)
1316- lineInput := tui.NormalizeInputStyle(i.cfg.TUIInputStyle) == tui.InputStyleLines1323+ lineInput := inputStyle == tui.InputStyleLines
1317 statusBelow := statusPosition == tui.StatusPositionBelowInput1324 statusBelow := statusPosition == tui.StatusPositionBelowInput
1318 workingBelow := workingPosition == tui.WorkingPositionBelowInput1325 workingBelow := workingPosition == tui.WorkingPositionBelowInput
1319 1326
@@ -1506,7 +1513,7 @@ func (i *Interactive) redraw() {
1506 dialogLead = 11513 dialogLead = 1
1507 }1514 }
1508 cursorRow := inputStartRow + inputCursorOffset + curR1515 cursorRow := inputStartRow + inputCursorOffset + curR
1509- cursorCol := curC1516+ cursorCol := curC + inputCursorColOffset
1510 if i.btwDialog.Active() {1517 if i.btwDialog.Active() {
1511 if r, c := i.btwDialog.CursorPos(cols); r >= 0 {1518 if r, c := i.btwDialog.CursorPos(cols); r >= 0 {
1512 cursorRow = dialogLead + r1519 cursorRow = dialogLead + r
@@ -2957,6 +2964,7 @@ func (i *Interactive) openSettingsDialog() {
2957 inputStyleOptions := []settingsOption{2964 inputStyleOptions := []settingsOption{
2958 {value: tui.InputStylePlain, label: "plain", desc: "render the input as the normal prompt line"},2965 {value: tui.InputStylePlain, label: "plain", desc: "render the input as the normal prompt line"},
2959 {value: tui.InputStyleLines, label: "lines", desc: "draw separator lines above and below the input"},2966 {value: tui.InputStyleLines, label: "lines", desc: "draw separator lines above and below the input"},
2967+ {value: tui.InputStyleBlock, label: "block", desc: "render the input as a user-bubble-style block"},
2960 }2968 }
2961 inputStyleChoice := 02969 inputStyleChoice := 0
2962 for idx, opt := range inputStyleOptions {2970 for idx, opt := range inputStyleOptions {
@@ -3032,7 +3040,7 @@ func (i *Interactive) openSettingsDialog() {
3032 {3040 {
3033 key: "tui_input_style",3041 key: "tui_input_style",
3034 label: "input style",3042 label: "input style",
3035- desc: "choose between the plain prompt and a lined input area",3043+ desc: "choose between the plain prompt, lines, and a block input area",
3036 options: inputStyleOptions,3044 options: inputStyleOptions,
3037 choice: inputStyleChoice,3045 choice: inputStyleChoice,
3038 },3046 },
M packages/tui/layout.go +31 -1
@@ -5,6 +5,7 @@ import "strings"
55 const (
66 InputStylePlain = "plain"
77 InputStyleLines = "lines"
8+ InputStyleBlock = "block"
89
910 StatusPositionAboveInput = "above_input"
1011 StatusPositionBelowInput = "below_input"
@@ -15,8 +16,10 @@ const (
1516
1617 func NormalizeInputStyle(v string) string {
1718 switch strings.ToLower(strings.TrimSpace(v)) {
18- case InputStyleLines, "line", "boxed", "box":
19+ case InputStyleLines, "line":
1920 return InputStyleLines
21+ case InputStyleBlock, "bubble", "boxed", "box":
22+ return InputStyleBlock
2023 default:
2124 return InputStylePlain
2225 }
@@ -52,6 +55,33 @@ func InputLines(th Theme, lines []string, width int) []string {
5255 return out
5356 }
5457
58+func InputBlock(th Theme, lines []string, width int) []string {
59+ if width < 1 {
60+ width = 1
61+ }
62+ bubbleW := width - 2
63+ if bubbleW < 1 {
64+ bubbleW = 1
65+ }
66+ row := func(content string) string {
67+ visible := visibleWidth(content)
68+ if visible < bubbleW {
69+ content += strings.Repeat(" ", bubbleW-visible)
70+ }
71+ bg := sgrBGColor(th.UserBubbleBG)
72+ bar := bg + th.FG256(th.Accent, "▌ ")
73+ body := bg + strings.ReplaceAll(content, reset, reset+bg) + reset
74+ return bar + body
75+ }
76+ out := make([]string, 0, len(lines)+2)
77+ out = append(out, row(""))
78+ for _, line := range lines {
79+ out = append(out, row(line))
80+ }
81+ out = append(out, row(""))
82+ return out
83+}
84+
5585 func CursorColor256(index int) string {
5686 r, g, b := xterm256RGB(index)
5787 return "\x1b]12;rgb:" + hexByte(r) + "/" + hexByte(g) + "/" + hexByte(b) + "\x07"
@@ -5,6 +5,7 @@ import "strings"
5 const (5 const (
6 InputStylePlain = "plain"6 InputStylePlain = "plain"
7 InputStyleLines = "lines"7 InputStyleLines = "lines"
8+ InputStyleBlock = "block"
8 9
9 StatusPositionAboveInput = "above_input"10 StatusPositionAboveInput = "above_input"
10 StatusPositionBelowInput = "below_input"11 StatusPositionBelowInput = "below_input"
@@ -15,8 +16,10 @@ const (
15 16
16 func NormalizeInputStyle(v string) string {17 func NormalizeInputStyle(v string) string {
17 switch strings.ToLower(strings.TrimSpace(v)) {18 switch strings.ToLower(strings.TrimSpace(v)) {
18- case InputStyleLines, "line", "boxed", "box":19+ case InputStyleLines, "line":
19 return InputStyleLines20 return InputStyleLines
21+ case InputStyleBlock, "bubble", "boxed", "box":
22+ return InputStyleBlock
20 default:23 default:
21 return InputStylePlain24 return InputStylePlain
22 }25 }
@@ -52,6 +55,33 @@ func InputLines(th Theme, lines []string, width int) []string {
52 return out55 return out
53 }56 }
54 57
58+func InputBlock(th Theme, lines []string, width int) []string {
59+ if width < 1 {
60+ width = 1
61+ }
62+ bubbleW := width - 2
63+ if bubbleW < 1 {
64+ bubbleW = 1
65+ }
66+ row := func(content string) string {
67+ visible := visibleWidth(content)
68+ if visible < bubbleW {
69+ content += strings.Repeat(" ", bubbleW-visible)
70+ }
71+ bg := sgrBGColor(th.UserBubbleBG)
72+ bar := bg + th.FG256(th.Accent, "▌ ")
73+ body := bg + strings.ReplaceAll(content, reset, reset+bg) + reset
74+ return bar + body
75+ }
76+ out := make([]string, 0, len(lines)+2)
77+ out = append(out, row(""))
78+ for _, line := range lines {
79+ out = append(out, row(line))
80+ }
81+ out = append(out, row(""))
82+ return out
83+}
84+
55 func CursorColor256(index int) string {85 func CursorColor256(index int) string {
56 r, g, b := xterm256RGB(index)86 r, g, b := xterm256RGB(index)
57 return "\x1b]12;rgb:" + hexByte(r) + "/" + hexByte(g) + "/" + hexByte(b) + "\x07"87 return "\x1b]12;rgb:" + hexByte(r) + "/" + hexByte(g) + "/" + hexByte(b) + "\x07"

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