Synced from monorepo

Synced from monorepo

Changes:
- Shell: accept target response id on rewind execute
- Shell: stamp response id on chat user message chunks
- Worktree: optional rebuild and stale git registration cleanup in auto-GC
- Worktree: kind-aware auto-GC TTLs and config knobs
- Worktree: macOS process CWD scan and Unix PID liveness for GC guards
- Worktree: automatic throttled GC on startup (Linux age-based; non-Linux dead-only)
- Pager: add `[ui].combine_queued_prompts` to batch queued follow-ups
- Shell: stop overwriting user skills
- Tools: read markdown in `skills/` directories untruncated
- `/usage` shows per-session token and dollar usage in the TUI
- Security: prompt on environment-dumping `ps` variants
- Security: always-safe `kubectl` no longer runs arbitrary kubeconfig credential plugins without permission
- Tools: make scheduler deletion durable
- Shell: add relocation storage primitives
- Shell: give side model calls their own conversation ids
- Fix five workflow-runtime bugs (budget, pause, cancel, reconnect)
- Security: peel `env -S` / `--split-string` operands in the Bash permission gate (managed deny/ask)
- Pager: expose doctor in the TUI
- Security: block unauthorized RCE via abused safe commands
- Pager idle watcher cue: "1 subagent still running" instead of "watching · 1 subagent"
- Security: block `rg --pre` arbitrary code execution in auto-mode
- Voice: diagnose silent-mic failures (macOS permission) and add doctor/terminal-setup Voice section
- App builder deployer: `allow_forking` and `show_built_with_grok`
- Pager: stop stacking duplicate "Worked for" markers on parked turns
- Shell: support `max` as a distinct reasoning effort tier
- Tools: serialize background `/loop` fires on the whole work unit
- Shell: add working-directory relocation state primitives
- Proto: `ClientToolResult` and `ChatConfig` client-side tools
- Shell: model providers
- Chat: select App Builder product on the Build path
- Shell: attach author identity to feedback when the deployment opts in
- Doctor: fix for SSH wrap setup
- Workflow authoring skills: create-workflow and import-claude-workflow docs
- Add read-only grok doctor
- Sandbox: apply Landlock without a controlling TTY
- Pager: recover image paste over grok wrap on headless remotes
- Pager: make actions screen-mode aware
- Shell: resume sessions when the working directory moves
- Pager: centralize terminal diagnostics
- Workspace: gate inline shell file access
- Pager: centralize terminal probes
- Pager: edit minimal prompts in an external editor
- Pager: standardize backgrounding on Ctrl+B
- Shell: recap rides the parent turn's prompt cache
- Tools: add scheduler lifecycle version clock

Source-Revision: 0f4d7c91b8b2b408333f6de1e8a76cb8eaa71899
This commit is contained in:
grokkybara[bot] 2026-07-21 18:10:23 +00:00
commit 3af4d5d398
556 changed files with 56609 additions and 21892 deletions

View file

@ -32,7 +32,7 @@ use crate::theme::Theme;
pub(crate) const SPINNER_DIVISOR: u64 = 4;
/// Show each monitor-pulse frame for this many animation ticks — twice the
/// [`SPINNER_DIVISOR`] dwell (~3.75 fps). The idle "watching" cue should
/// [`SPINNER_DIVISOR`] dwell (~3.75 fps). The idle still-running cue should
/// breathe calmly rather than read like the active turn spinner, so its
/// `○ ◎ ◉ ◎` cycle runs at roughly half the speed (~1.07s per loop).
pub(crate) const MONITOR_PULSE_DIVISOR: u64 = 8;
@ -81,7 +81,7 @@ pub struct TurnStatusOutput {
/// `Some(_)` to [`render_turn_status`] renders the buttons; passing `None`
/// marks a keyboard-only host (minimal mode has no mouse capture) and suppresses
/// both — that host cancels the turn via `Ctrl+C` and sends to background via
/// `Ctrl+G` instead.
/// `Ctrl+B` instead.
#[derive(Debug, Clone, Copy, Default)]
pub struct MouseButtons {
/// Whether the mouse is over the `[stop]` cancel button.
@ -93,7 +93,7 @@ pub struct MouseButtons {
/// Counts of idle-surviving "watcher" work — background jobs that can wake
/// the agent for a new turn while it sits idle (commands and monitors on
/// completion/events, `/loop` tasks on a timer, background subagents on
/// finish). They share one persistent "watching" cue above the prompt.
/// finish). They share one persistent still-running cue above the prompt.
/// Broader than the tasks-pane `Watchers` group (monitors + loops only).
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Watchers {
@ -107,59 +107,63 @@ pub struct Watchers {
/// subagent is a background one — a foreground subagent would keep the
/// parent in `TurnRunning`.
pub subagents: usize,
pub workflows: usize,
}
impl Watchers {
/// Total watcher count across all kinds.
pub fn total(self) -> usize {
self.commands + self.monitors + self.loops + self.subagents
self.commands + self.monitors + self.loops + self.subagents + self.workflows
}
/// Awaitable in-flight work — the kinds a blocking `wait_tasks` /
/// `get_task_output` wait can resolve on (commands, monitors, subagents;
/// scheduled `/loop` tasks are timers, not awaitable work).
/// scheduled `/loop` tasks and workflows are not task waits).
pub fn awaitable_work(self) -> usize {
self.commands + self.monitors + self.subagents
}
}
/// Build the "watching · …" label for the idle watcher cue, listing only the
/// non-zero kinds with correct singular/plural nouns — e.g.
/// `"watching · 1 command · 2 monitors · 1 loop · 1 subagent"`. Assumes
/// `watchers.total() > 0`.
fn watching_label(watchers: Watchers) -> String {
/// Format a counts-first `"… still running"` cue from `(count, noun)` pairs,
/// listing only the non-zero kinds (plain-`s` plurals) — e.g.
/// `"1 command · 2 monitors still running"`. `None` when every count is
/// zero. Single owner of the format mechanics so the agent view's idle cue
/// and the dashboard's background-work label cannot drift.
pub(crate) fn format_still_running<'a>(
kinds: impl IntoIterator<Item = (usize, &'a str)>,
) -> Option<String> {
use std::fmt::Write as _;
let mut label = String::with_capacity(32);
label.push_str("watching");
if watchers.commands > 0 {
let noun = if watchers.commands == 1 {
"command"
} else {
"commands"
};
let _ = write!(label, " \u{00b7} {} {noun}", watchers.commands);
let mut label = String::with_capacity(48);
for (count, noun) in kinds {
if count == 0 {
continue;
}
if !label.is_empty() {
label.push_str(" \u{00b7} ");
}
let plural = if count == 1 { "" } else { "s" };
let _ = write!(label, "{count} {noun}{plural}");
}
if watchers.monitors > 0 {
let noun = if watchers.monitors == 1 {
"monitor"
} else {
"monitors"
};
let _ = write!(label, " \u{00b7} {} {noun}", watchers.monitors);
if label.is_empty() {
return None;
}
if watchers.loops > 0 {
let noun = if watchers.loops == 1 { "loop" } else { "loops" };
let _ = write!(label, " \u{00b7} {} {noun}", watchers.loops);
}
if watchers.subagents > 0 {
let noun = if watchers.subagents == 1 {
"subagent"
} else {
"subagents"
};
let _ = write!(label, " \u{00b7} {} {noun}", watchers.subagents);
}
label
label.push_str(" still running");
Some(label)
}
/// The idle watcher cue's label — e.g.
/// `"1 command · 2 monitors · 1 loop · 1 subagent still running"`. Leads
/// with the counts (not an ambient "watching") so a glance under a
/// "Worked for X" marker still reads as unfinished work. `None` when no
/// watchers are live.
fn still_running_label(watchers: Watchers) -> Option<String> {
format_still_running([
(watchers.commands, "command"),
(watchers.monitors, "monitor"),
(watchers.loops, "loop"),
(watchers.subagents, "subagent"),
(watchers.workflows, "workflow"),
])
}
/// Whether the turn is blocked in a wait the shell aborts as soon as the
@ -199,7 +203,7 @@ pub fn is_sendable_wait(activity: &Option<TurnActivity>) -> bool {
/// - `total_tokens`: Total tokens used (context window usage), shown as `⇣Nk`.
/// - `parked`: the turn is parked on a sendable wait and renders the stopped
/// look (`AgentView::renders_parked`). The running-turn chrome is suppressed;
/// only the "watching · …" cue renders (the parked turn is by definition
/// only the "… still running" cue renders (the parked turn is by definition
/// waiting on background work, so the cue explains the idle-looking chrome).
/// - `flat_background`: when `true`, right-side timer/buttons use a transparent
/// (`Color::Reset`) background instead of `theme.bg_base`, so the row blends
@ -274,13 +278,15 @@ pub fn render_turn_status(
return TurnStatusOutput::default();
}
// Idle or parked with watchers: persistent watching cue (not scrollback
// — it must never scroll away). Lower priority than the starting-session
// and drain-blocked cues above.
if (state.is_idle() || parked) && watchers.total() > 0 {
// Idle or parked with watchers: persistent still-running cue (not
// scrollback — it must never scroll away). Lower priority than the
// starting-session and drain-blocked cues above.
if (state.is_idle() || parked)
&& let Some(cue) = still_running_label(watchers)
{
// Pulsing concentric circle (○ ◎ ◉ ◎) on a calm ambient cadence:
// the agent is idle, so this "watching" breath runs slower than the
// active turn spinner (see MONITOR_PULSE_DIVISOR).
// the agent is idle, so this breath runs slower than the active
// turn spinner (see MONITOR_PULSE_DIVISOR).
let frames = crate::glyphs::monitor_icon_frames();
let frame_idx = (tick / MONITOR_PULSE_DIVISOR) as usize % frames.len();
let spans = vec![
@ -288,7 +294,7 @@ pub fn render_turn_status(
format!("{} ", frames[frame_idx]),
Style::default().fg(theme.accent_system),
),
Span::styled(watching_label(watchers), Style::default().fg(theme.gray)),
Span::styled(cue, Style::default().fg(theme.gray)),
];
buf.set_line(area.x, area.y, &Line::from(spans), area.width);
return TurnStatusOutput::default();
@ -758,7 +764,7 @@ fn render_starting_session(
///
/// A parked turn (`parked` — the stopped look while blocked on a sendable
/// wait) suppresses the running-turn chrome entirely: the row shows only when
/// watchers exist, rendering the "watching · …" cue.
/// watchers exist, rendering the "… still running" cue.
///
/// Real MCP progress (`total > 0`) renders as a compact chip in the top status
/// bar instead, so it does not affect this row.
@ -1004,7 +1010,7 @@ mod tests {
#[test]
fn should_show_when_watchers_running() {
// Idle but a watcher (command, monitor, loop, or subagent) is still
// running → row stays visible so the persistent "watching · …" cue
// running → row stays visible so the persistent "… still running" cue
// can show.
for watchers in [
Watchers {
@ -1039,7 +1045,7 @@ mod tests {
#[test]
fn should_show_parked_only_with_watchers() {
// Parked (turn running but rendering the stopped look): the row shows
// only to carry the "watching · …" cue — never the running chrome.
// only to carry the "… still running" cue — never the running chrome.
assert!(should_show(
&AgentState::TurnRunning,
false,
@ -1149,7 +1155,12 @@ mod tests {
/// Invoke `render_turn_status` for an idle agent with the given watcher
/// counts at animation tick `tick`.
fn render_idle_with_watchers_at_tick(watchers: Watchers, tick: u64) -> String {
let area = Rect::new(0, 0, 60, 1);
render_idle_with_watchers_in_width(watchers, tick, 72)
}
/// [`render_idle_with_watchers_at_tick`] with an explicit row width.
fn render_idle_with_watchers_in_width(watchers: Watchers, tick: u64, width: u16) -> String {
let area = Rect::new(0, 0, width, 1);
let mut buf = Buffer::empty(area);
render_turn_status(
&mut buf,
@ -1179,7 +1190,7 @@ mod tests {
/// Invoke `render_turn_status` for a PARKED running turn (the stopped
/// look) with the given watcher counts.
fn render_parked_with_watchers(watchers: Watchers) -> String {
let area = Rect::new(0, 0, 60, 1);
let area = Rect::new(0, 0, 72, 1);
let mut buf = Buffer::empty(area);
render_turn_status(
&mut buf,
@ -1231,11 +1242,11 @@ mod tests {
}
#[test]
fn idle_with_monitors_renders_watching_line() {
fn idle_with_monitors_renders_still_running_cue() {
let text = render_idle_with_monitors(2);
assert!(
text.contains("watching") && text.contains("2 monitors"),
"idle with monitors must render the watching cue, got: {text:?}"
text.contains("2 monitors still running"),
"idle with monitors must render the still-running cue, got: {text:?}"
);
}
@ -1243,7 +1254,7 @@ mod tests {
fn idle_with_one_monitor_uses_singular() {
let text = render_idle_with_monitors(1);
assert!(
text.contains("watching \u{00b7} 1 monitor") && !text.contains("monitors"),
text.contains("1 monitor still running") && !text.contains("monitors"),
"single monitor must use the singular noun, got: {text:?}"
);
}
@ -1258,14 +1269,14 @@ mod tests {
}
#[test]
fn idle_with_loops_renders_watching_line() {
fn idle_with_loops_renders_still_running_cue() {
let text = render_idle_with_watchers(Watchers {
loops: 2,
..Watchers::default()
});
assert!(
text.contains("watching") && text.contains("2 loops"),
"idle with loops must render the watching cue, got: {text:?}"
text.contains("2 loops still running"),
"idle with loops must render the still-running cue, got: {text:?}"
);
}
@ -1276,20 +1287,20 @@ mod tests {
..Watchers::default()
});
assert!(
text.contains("watching \u{00b7} 1 loop") && !text.contains("loops"),
text.contains("1 loop still running") && !text.contains("loops"),
"single loop must use the singular noun, got: {text:?}"
);
}
#[test]
fn idle_with_subagents_renders_watching_line() {
fn idle_with_subagents_renders_still_running_cue() {
let text = render_idle_with_watchers(Watchers {
subagents: 2,
..Watchers::default()
});
assert!(
text.contains("watching") && text.contains("2 subagents"),
"idle with subagents must render the watching cue, got: {text:?}"
text.contains("2 subagents still running"),
"idle with subagents must render the still-running cue, got: {text:?}"
);
}
@ -1300,11 +1311,20 @@ mod tests {
..Watchers::default()
});
assert!(
text.contains("watching \u{00b7} 1 subagent") && !text.contains("subagents"),
text.contains("1 subagent still running") && !text.contains("subagents"),
"single subagent must use the singular noun, got: {text:?}"
);
}
#[test]
fn idle_with_one_workflow_counts_run_once() {
let text = render_idle_with_watchers(Watchers {
workflows: 1,
..Watchers::default()
});
assert!(text.contains("1 workflow still running"), "got: {text:?}");
}
#[test]
fn idle_with_monitors_and_loops_lists_both() {
// Both watcher kinds present → one cue lists monitors then loops,
@ -1315,7 +1335,7 @@ mod tests {
..Watchers::default()
});
assert!(
text.contains("watching \u{00b7} 1 monitor \u{00b7} 2 loops"),
text.contains("1 monitor \u{00b7} 2 loops still running"),
"both kinds must be listed in one cue, got: {text:?}"
);
}
@ -1329,17 +1349,37 @@ mod tests {
monitors: 2,
loops: 1,
subagents: 3,
workflows: 0,
});
assert!(
text.contains(
"watching \u{00b7} 1 command \u{00b7} 2 monitors \u{00b7} 1 loop \u{00b7} 3 subagents"
"1 command \u{00b7} 2 monitors \u{00b7} 1 loop \u{00b7} 3 subagents still running"
),
"all kinds must be listed in one cue, got: {text:?}"
);
}
#[test]
fn idle_with_commands_renders_watching_line() {
fn narrow_area_clips_cue_tail_keeping_counts() {
// 40 cols with three kinds: the row tail-clips with no ellipsis, so
// the leading counts survive and the trailing suffix is what gets
// cut. Pins the narrow-pane tradeoff of leading with the counts; a
// smarter compact fallback would be a behavior change.
let watchers = Watchers {
commands: 1,
monitors: 2,
loops: 1,
..Watchers::default()
};
let text = render_idle_with_watchers_in_width(watchers, 0, 40);
assert!(
text.contains("1 command \u{00b7} 2 monitors \u{00b7} 1 loop"),
"the counts must survive the clip, got: {text:?}"
);
}
#[test]
fn idle_with_commands_renders_still_running_cue() {
// Plain background commands (non-monitor bg tasks) count as watchers:
// they wake the agent with a task-completed turn, so the cue must show.
let text = render_idle_with_watchers(Watchers {
@ -1347,22 +1387,22 @@ mod tests {
..Watchers::default()
});
assert!(
text.contains("watching \u{00b7} 2 commands"),
"idle with bg commands must render the watching cue, got: {text:?}"
text.contains("2 commands still running"),
"idle with bg commands must render the still-running cue, got: {text:?}"
);
let text = render_idle_with_watchers(Watchers {
commands: 1,
..Watchers::default()
});
assert!(
text.contains("watching \u{00b7} 1 command") && !text.contains("commands"),
text.contains("1 command still running") && !text.contains("commands"),
"single command must use the singular noun, got: {text:?}"
);
}
#[test]
fn parked_with_watchers_renders_watching_not_running_chrome() {
// A parked running turn renders the watching cue — never the busy
fn parked_with_watchers_renders_cue_not_running_chrome() {
// A parked running turn renders the still-running cue — never the busy
// spinner/timers/[stop] chrome (the wait aborts as soon as the user
// types, so that chrome would lie).
let text = render_parked_with_watchers(Watchers {
@ -1370,8 +1410,8 @@ mod tests {
..Watchers::default()
});
assert!(
text.contains("watching \u{00b7} 2 commands"),
"parked with bg work must render the watching cue, got: {text:?}"
text.contains("2 commands still running"),
"parked with bg work must render the still-running cue, got: {text:?}"
);
assert!(
!text.contains("Waiting") && !text.contains("[stop]"),
@ -1431,52 +1471,57 @@ mod tests {
}
#[test]
fn watching_label_lists_only_nonzero_kinds() {
fn still_running_label_lists_only_nonzero_kinds() {
assert_eq!(
watching_label(Watchers {
still_running_label(Watchers {
commands: 2,
..Watchers::default()
}),
"watching \u{00b7} 2 commands"
Some("2 commands still running".into())
);
assert_eq!(
watching_label(Watchers {
still_running_label(Watchers {
monitors: 2,
..Watchers::default()
}),
"watching \u{00b7} 2 monitors"
Some("2 monitors still running".into())
);
assert_eq!(
watching_label(Watchers {
still_running_label(Watchers {
loops: 1,
..Watchers::default()
}),
"watching \u{00b7} 1 loop"
Some("1 loop still running".into())
);
assert_eq!(
watching_label(Watchers {
still_running_label(Watchers {
subagents: 1,
..Watchers::default()
}),
"watching \u{00b7} 1 subagent"
Some("1 subagent still running".into())
);
assert_eq!(
watching_label(Watchers {
still_running_label(Watchers {
monitors: 1,
loops: 2,
..Watchers::default()
}),
"watching \u{00b7} 1 monitor \u{00b7} 2 loops"
Some("1 monitor \u{00b7} 2 loops still running".into())
);
assert_eq!(
watching_label(Watchers {
still_running_label(Watchers {
commands: 1,
monitors: 1,
loops: 1,
subagents: 2,
workflows: 0,
}),
"watching \u{00b7} 1 command \u{00b7} 1 monitor \u{00b7} 1 loop \u{00b7} 2 subagents"
Some(
"1 command \u{00b7} 1 monitor \u{00b7} 1 loop \u{00b7} 2 subagents still running"
.into()
)
);
assert_eq!(still_running_label(Watchers::default()), None);
}
#[test]