mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 00:03:01 +03:00
refactor(tui): clarify active composer actions (#5538)
* fix(tui): queue busy submissions on Enter * fix(tui): remove Tab queue shortcut * refactor(tui): clarify active composer actions * refactor(tui): simplify active composer hint
This commit is contained in:
@@ -212,7 +212,7 @@ nanobot agent
|
||||
This opens the native terminal client with the launch directory as its workspace. It shares saved conversations and the local gateway with the WebUI.
|
||||
|
||||
- Type `/` to discover commands, `/sessions` to switch conversations, or `@` to mention an app, MCP server, or saved session.
|
||||
- Press `Enter` to send or steer, `Tab` to queue a follow-up, and `Shift+Enter` to add a newline (`Ctrl+J` works in terminals that cannot distinguish modified Enter keys).
|
||||
- Press `Enter` to send. While nanobot is working, `Enter` sends now and `Tab` sends after the current response. Press `Shift+Enter` to add a newline (`Ctrl+J` works in terminals that cannot distinguish modified Enter keys).
|
||||
- Use `/detach` to leave the current task running, or start with `nanobot gateway --background` when nanobot should stay online after all local clients exit.
|
||||
|
||||
Each launch starts a new session by default. Use `--session` to resume one and `--workspace` to choose another workspace. See the [CLI reference](./docs/cli-reference.md#agent-cli) for session branching, diffs, history, shortcuts, gateway lifecycle, and compatibility options.
|
||||
|
||||
@@ -127,7 +127,7 @@ Interactive mode uses nanobot's native TypeScript terminal UI. It talks to the s
|
||||
|
||||
The default `--theme auto` mode paints first with the terminal's default background, probes the real foreground and background colors asynchronously, and follows supported live appearance changes. Use `--theme light` or `--theme dark` when a terminal or multiplexer does not report its colors reliably. The model preset and workspace access labels above the composer can be clicked to open their selectors; arrow keys, `Enter`, and `Esc` provide the same controls without a mouse. Access changes still pass through the gateway's local-trust and active-turn policy checks.
|
||||
|
||||
`Enter` sends the current message. While a turn is active, `Enter` steers it immediately, `Tab` queues a visible follow-up for the next turn, and `Option+Up` on macOS (`Alt+Up` on Windows/Linux) returns the latest queued message to the composer. Press `Shift+Enter` to add a newline; `Ctrl+J` is the universal fallback when a terminal cannot distinguish modified Enter keys. `Alt+Enter` and `Ctrl+Enter` are also accepted when distinguishable. Use `Up`/`Down` at the composer edge to recall prompts from the current saved session. Large pastes appear as a compact placeholder in the composer but are sent unchanged. Type `/` to discover nanobot commands and terminal navigation in one palette, or type `@` to complete installed apps, configured MCP servers, and saved sessions. Use the arrow keys to choose an item and `Tab` to complete it. `/sessions` opens a searchable conversation picker, `/new-chat` preserves the current conversation and starts another one, and `/branch` forks from a completed reply. `/diff` opens a read-only unified diff for the newest turn; use `Left`/`Right` to switch edits and `Esc` to close it. The core `/new` command retains its cross-channel behavior and resets the current chat. `Ctrl+C` copies a selection, stops a running turn, clears a non-empty composer, or exits when idle. Use `PageUp`/`PageDown` to scroll, `Ctrl+Home`/`Ctrl+End` to jump to the transcript edges, and `Ctrl+O` to expand or collapse long tool traces. When you leave the bottom, the TUI shows a scrollbar and a `Ctrl+End` hint until you return. The footer reports provider token/cache usage when available. Selections copy through OSC 52 when the terminal supports it. The transcript reflows when the terminal is resized, and exiting restores the previous screen.
|
||||
`Enter` sends the current message. While nanobot is working, `Enter` sends immediately, `Tab` waits until the current response is finished, and `Option+Up` on macOS (`Alt+Up` on Windows/Linux) returns the latest waiting message to the composer. Press `Shift+Enter` to add a newline; `Ctrl+J` is the universal fallback when a terminal cannot distinguish modified Enter keys. `Alt+Enter` and `Ctrl+Enter` are also accepted when distinguishable. Use `Up`/`Down` at the composer edge to recall prompts from the current saved session. Large pastes appear as a compact placeholder in the composer but are sent unchanged. Type `/` to discover nanobot commands and terminal navigation in one palette, or type `@` to complete installed apps, configured MCP servers, and saved sessions. Use the arrow keys to choose an item and `Tab` to complete it. `/sessions` opens a searchable conversation picker, `/new-chat` preserves the current conversation and starts another one, and `/branch` forks from a completed reply. `/diff` opens a read-only unified diff for the newest turn; use `Left`/`Right` to switch edits and `Esc` to close it. The core `/new` command retains its cross-channel behavior and resets the current chat. `Ctrl+C` copies a selection, stops a running turn, clears a non-empty composer, or exits when idle. Use `PageUp`/`PageDown` to scroll, `Ctrl+Home`/`Ctrl+End` to jump to the transcript edges, and `Ctrl+O` to expand or collapse long tool traces. When you leave the bottom, the TUI shows a scrollbar and a `Ctrl+End` hint until you return. The footer reports provider token/cache usage when available. Selections copy through OSC 52 when the terminal supports it. The transcript reflows when the terminal is resized, and exiting restores the previous screen.
|
||||
|
||||
Packaged releases fetch a version-matched, checksummed terminal archive for macOS (Apple Silicon and Intel), Linux (x64 and ARM64), or Windows x64 on first use. The cache keeps the executable together with its licenses, third-party notices, source offer, relinking instructions, and corresponding TUI source. Windows ARM64 currently falls back to the classic prompt because the Bun runtime disables the FFI required by OpenTUI on that platform. Set `NANOBOT_TUI_NO_DOWNLOAD=1` or pass `--classic` to keep the Python-only path. A local source install requires Bun and runs its own `tui/` source while the original checkout remains available; it never silently falls back to a release binary.
|
||||
|
||||
|
||||
+3
-3
@@ -113,9 +113,9 @@ nanobot agent
|
||||
```
|
||||
|
||||
In interactive mode, `Enter` sends and `Shift+Enter` inserts a newline (`Ctrl+J` is the
|
||||
universal fallback). While a turn is running,
|
||||
`Enter` steers it, `Tab` queues a follow-up, and `Option+Up` on macOS (`Alt+Up` on
|
||||
Windows/Linux) edits the latest queued message. Exit
|
||||
universal fallback). While nanobot is working, `Enter` sends immediately, `Tab` waits until the
|
||||
current response is finished, and `Option+Up` on macOS (`Alt+Up` on Windows/Linux) edits the
|
||||
latest waiting message. Exit
|
||||
with `exit`, `/exit`, `:q`, or `Ctrl+D`.
|
||||
|
||||
## Choose One Next Step
|
||||
|
||||
+8
-7
@@ -31,18 +31,19 @@ When you scroll away from the latest output, the scrollbar and `Ctrl+End` hint a
|
||||
you return to the bottom. Large pastes are represented by a short editable placeholder in the
|
||||
composer; nanobot sends the original text unchanged.
|
||||
|
||||
While a turn is running, the composer remains available for steering and uses `Steer this turn…`
|
||||
as its prompt. The footer shows the lifecycle and elapsed time without repeating the latest tool
|
||||
activity already visible in the transcript.
|
||||
While nanobot is working, the composer prompt becomes
|
||||
`Enter send now · Tab send next`; narrow terminals shorten it to `Enter now · Tab next`.
|
||||
The footer shows progress and elapsed time without repeating the latest tool activity already
|
||||
visible in the transcript.
|
||||
|
||||
Type `/` to discover slash commands published by the connected gateway. Use the arrow keys
|
||||
to move, `Tab` to complete, and `Esc` to close the menu.
|
||||
|
||||
Type `@` to complete installed CLI apps, configured MCP servers, or saved sessions through the
|
||||
same gateway metadata used by the WebUI. While nanobot is working, `Enter` steers the current
|
||||
turn, `Tab` queues a follow-up for the next turn, and `Option+Up` on macOS (`Alt+Up` on
|
||||
Windows/Linux) returns the latest queued message to the composer for editing. The pending queue
|
||||
stays visible above the composer.
|
||||
same gateway metadata used by the WebUI. While nanobot is working, `Enter` sends immediately,
|
||||
`Tab` waits until the current response is finished, and `Option+Up` on macOS (`Alt+Up` on
|
||||
Windows/Linux) returns the latest waiting message to the composer for editing. Waiting messages
|
||||
stay visible above the composer.
|
||||
Use `Shift+Enter` for a newline; `Ctrl+J` is the universal fallback when a terminal cannot
|
||||
distinguish modified Enter keys. `Alt+Enter` and `Ctrl+Enter` are also accepted when distinguishable.
|
||||
Unsent prompts return to the composer if the turn stops or fails.
|
||||
|
||||
+16
-5
@@ -322,6 +322,7 @@ describe("NanobotTui layout", () => {
|
||||
composer: TextareaRenderable
|
||||
mentionCandidates: Array<Record<string, unknown>>
|
||||
queuePreview: { root: { visible: boolean } }
|
||||
status: { plainText: string }
|
||||
}
|
||||
await waitUntil(() => ui.ready)
|
||||
ui.mentionCandidates = [{
|
||||
@@ -334,10 +335,17 @@ describe("NanobotTui layout", () => {
|
||||
ui.composer.setText("first")
|
||||
ui.composer.submit()
|
||||
await waitUntil(() => sent.length === 1)
|
||||
expect(ui.composer.placeholder).toBe("Enter send now · Tab send next")
|
||||
|
||||
ui.composer.setText("one more detail")
|
||||
await setup.flush()
|
||||
expect(ui.composer.placeholder).toBeNull()
|
||||
|
||||
ui.composer.setText("ask @github next")
|
||||
ui.composer.submit()
|
||||
await waitUntil(() => sent.length === 2)
|
||||
expect(ui.status.plainText).not.toContain("Steering")
|
||||
expect(ui.composer.placeholder).toBe("Enter send now · Tab send next")
|
||||
expect(sentOptions[1]).toEqual({
|
||||
cliApps: [{ name: "github" }],
|
||||
mcpPresets: [],
|
||||
@@ -1643,9 +1651,12 @@ describe("NanobotTui layout", () => {
|
||||
expect(setup.renderer.width).toBe(width)
|
||||
expect(setup.renderer.height).toBe(height)
|
||||
expect(frame).not.toContain("undefined")
|
||||
expect(occurrences(frame, "Steer this turn…")).toBeLessThanOrEqual(1)
|
||||
if (width >= 30 && height >= 9) {
|
||||
expect(occurrences(frame, "Steer this turn…")).toBe(1)
|
||||
expect(frame).not.toContain("Steer this turn…")
|
||||
expect(frame).not.toContain("Ask a follow-up…")
|
||||
if (width >= 40 && height >= 9) {
|
||||
expect(occurrences(frame, "Enter send now · Tab send next")).toBe(1)
|
||||
} else if (width >= 28 && height >= 9) {
|
||||
expect(occurrences(frame, "Enter now · Tab next")).toBe(1)
|
||||
}
|
||||
expect(occurrences(frame, "nanobot · test/model")).toBe(height >= 14 ? 1 : 0)
|
||||
}
|
||||
@@ -2176,7 +2187,7 @@ describe("NanobotTui layout", () => {
|
||||
}
|
||||
const status = ui.status
|
||||
expect(status.plainText).toMatch(/^Thinking\s+0s/u)
|
||||
expect(ui.composer.placeholder).toBe("Steer this turn…")
|
||||
expect(ui.composer.placeholder).toBe("Enter send now · Tab send next")
|
||||
expect(ui.composerFrame.height).toBe(3)
|
||||
const shimmerColors = new Set(
|
||||
status.content.chunks
|
||||
@@ -2703,7 +2714,7 @@ describe("NanobotTui in a Herdr pane", () => {
|
||||
const activeFrame = setup.captureCharFrame()
|
||||
expect(occurrences(activeFrame, "› Ship the Herdr integration")).toBe(1)
|
||||
expect(occurrences(activeFrame, "app.ts")).toBe(1)
|
||||
expect(ui.composer.placeholder).toBe("Steer this turn…")
|
||||
expect(ui.composer.placeholder).toBe("Enter send now · Tab send next")
|
||||
expect(ui.composerFrame.height).toBe(3)
|
||||
app.accept({
|
||||
event: "turn_end",
|
||||
|
||||
+8
-3
@@ -178,7 +178,8 @@ const LIGHT: Palette = {
|
||||
}
|
||||
|
||||
const COMPOSER_PLACEHOLDER = "Ask nanobot anything"
|
||||
const ACTIVE_COMPOSER_PLACEHOLDER = "Steer this turn…"
|
||||
const ACTIVE_COMPOSER_PLACEHOLDER = "Enter send now · Tab send next"
|
||||
const COMPACT_ACTIVE_COMPOSER_PLACEHOLDER = "Enter now · Tab next"
|
||||
const SHIMMER_PAUSE = 16
|
||||
const SHIMMER_BAND = 4
|
||||
const SHIMMER_INTERVAL_MS = 80
|
||||
@@ -959,7 +960,7 @@ export class NanobotTui {
|
||||
this.hostBlocked = false
|
||||
this.setCurrentTask(prompt.content)
|
||||
if (steering) {
|
||||
this.status.content = `Steering current turn${this.promptQueue.length ? ` · ${this.promptQueue.length} queued` : ""}`
|
||||
this.renderActiveStatus()
|
||||
this.updateMeta()
|
||||
return true
|
||||
}
|
||||
@@ -1796,6 +1797,7 @@ export class NanobotTui {
|
||||
|
||||
private handleResize = (): void => {
|
||||
this.resizeComposer()
|
||||
this.syncComposerPlaceholder()
|
||||
this.contextPanel.resize(this.renderer.height)
|
||||
this.diffViewer.resize(this.renderer.width)
|
||||
if (!this.host.hosted) this.title.visible = this.renderer.height >= 14
|
||||
@@ -1974,13 +1976,16 @@ export class NanobotTui {
|
||||
// OpenTUI normally suppresses placeholder glyphs while the editor is not
|
||||
// empty. Explicitly removing them also invalidates their old cells, which
|
||||
// prevents stale placeholder text in differential/embedded terminals.
|
||||
const activePlaceholder = this.renderer.width >= 40
|
||||
? ACTIVE_COMPOSER_PLACEHOLDER
|
||||
: COMPACT_ACTIVE_COMPOSER_PLACEHOLDER
|
||||
const placeholder = this.composer.plainText
|
||||
? null
|
||||
: this.sessionMenu.visible
|
||||
? "Search sessions"
|
||||
: this.branchMenu.visible
|
||||
? "Search branch points"
|
||||
: this.activeTurn ? ACTIVE_COMPOSER_PLACEHOLDER : COMPOSER_PLACEHOLDER
|
||||
: this.activeTurn ? activePlaceholder : COMPOSER_PLACEHOLDER
|
||||
if (this.composer.placeholder !== placeholder) this.composer.placeholder = placeholder
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user