Add goal TUI UX (5 / 5) (#18077)

Adds the TUI user experience for goals on top of the core runtime from
PR 4.

## Why

Users need a direct TUI control surface for long-running goals. The UI
should make the current goal visible, support common goal actions
without waiting for a model turn, and avoid confusing end-of-turn
notifications while an active goal is immediately continuing.

## What changed

- Added `/goal` summary rendering for the current goal, including
active, paused, budget-limited, and complete states.
- Added `/goal <objective>` creation/replacement through the app-server
goal API rather than a model prompt.
- Added `/goal clear`, `/goal pause`, and `/goal unpause` command
variants.
- Added a confirmation menu when the user enters a new goal while
another goal already exists.
- Updated `/goal` help and summary tip text so it reflects the supported
command variants without advertising slash-command token budgets.
- Added footer/statusline goal indicators, including elapsed time and
token budget display when a budget exists from API/tool-created goals.
- Consumes goal updated/cleared notifications so the TUI stays in sync
with external app-server changes.
- Suppresses end-of-turn desktop notifications only when a goal is still
active and follow-up work is expected.
- Preserves slash-command history behavior and avoids leaking queued
`/goal` state into unrelated submissions.

## Verification

- Added TUI unit and snapshot coverage for goal command availability,
summary rendering, control commands, replacement menu behavior,
status/footer display, notification handling, and command history.
This commit is contained in:
Eric Traut
2026-04-24 21:16:45 -07:00
committed by GitHub
Unverified
parent 4167628622
commit f1c963d77e
32 changed files with 2709 additions and 177 deletions
+9
View File
@@ -31,6 +31,7 @@ pub enum SlashCommand {
Init,
Compact,
Plan,
Goal,
Collab,
Agent,
Side,
@@ -104,6 +105,7 @@ impl SlashCommand {
SlashCommand::Realtime => "toggle realtime voice mode (experimental)",
SlashCommand::Settings => "configure realtime microphone/speaker",
SlashCommand::Plan => "switch to Plan mode",
SlashCommand::Goal => "set or view the goal for a long-running task",
SlashCommand::Collab => "change collaboration mode (experimental)",
SlashCommand::Agent | SlashCommand::MultiAgents => "switch the active agent thread",
SlashCommand::Side => "start a side conversation in an ephemeral fork",
@@ -137,6 +139,7 @@ impl SlashCommand {
SlashCommand::Review
| SlashCommand::Rename
| SlashCommand::Plan
| SlashCommand::Goal
| SlashCommand::Fast
| SlashCommand::Mcp
| SlashCommand::Side
@@ -186,6 +189,7 @@ impl SlashCommand {
| SlashCommand::DebugConfig
| SlashCommand::Ps
| SlashCommand::Stop
| SlashCommand::Goal
| SlashCommand::Mcp
| SlashCommand::Apps
| SlashCommand::Plugins
@@ -239,4 +243,9 @@ mod tests {
fn clean_alias_parses_to_stop_command() {
assert_eq!(SlashCommand::from_str("clean"), Ok(SlashCommand::Stop));
}
#[test]
fn goal_command_is_available_during_task() {
assert!(SlashCommand::Goal.available_during_task());
}
}