Synced from monorepo

Changes:
- Gate session-lifecycle heap steady state with a dhat soak
- Unbreak merge lifecycle e2e after default model → grok-4.5
- Scan home-scope rules dirs at <root>/rules
- Complete text-input paste and terminal parity
- Gate project roles and personas
- Use canonical editing in dialogs
- Use canonical editing in search bars
- Reject ambiguous MCP tool IDs
- Harden Git operands for plugins
- Simplify queue drain API
- Pass RFC 9207 iss through MCP OAuth token exchange
- Show leader roster when local agents map is empty
- Use canonical editing in Persona views
- Remove marketplace default-skills auto-install and purge old installs
- Use canonical editing in extension forms
- Add canonical dashboard text editing
- Use canonical editing in settings
- Add /summarize as a /recap alias
- Restore previous agent when exiting dashboard
- Use tool_choice auto for compaction
- Settings toggle for snap-prompt-to-top on send
- Update default models to grok-4.5
- Source login shell once for local bash (env + alias/function snapshot)
- Template hardcoded param names in server-native tool descriptions
- Fix System-Reminder XML tag injection in CLAUDE.md via agents_md
- Fix remote workspace-server hardcoding LSP trust (repo code execution risk)
- Clear orphaned tool-call updates at turn end
- Suppress task wake after cancel
- Send x-grok-client-identifier on direct API tool calls
- Harden dashboard peek lease transitions
- Host /btw side panel in live region (minimal mode)
- Bound scroll presentation latency
- Highlight multi-line constructs correctly in diffs and the file viewer
- Block web_fetch non-public IPs; local opt-in is explicit-host only
- Seed coding_data_retention_opt_out=false for OAuth e2es in pty-harness
- Follow up clipboard delivery feedback
- Use canonical editing in pickers
- Route TextArea through canonical editor
- Persistent "watching" status row; quieter turn markers
- Gate sensitive edit targets
- Expose agent registry counts and gate session churn on them
- Default coding data sharing to opt-out until server preference applies
- Wire chat attachment ids through gateway prompts
- On auth refresh failure, issue retry
- Forward preview provenance and computer lifecycle state
- Document independent privacy controls and scope /privacy output
- Strip SamplingError Display prefix on rate-limit UI copy
- Stop dumping Cloudflare HTML into Retry failed
- Disable in-place prompt edit (scroll jank on enter)
- Strip forced ANSI color from gh pr view JSON
- Plumb bash tool description onto ToolUsageCard wire
This commit is contained in:
grokkybara[bot] 2026-07-18 19:48:28 +01:00
commit 7cfcb20d2b
292 changed files with 23315 additions and 9209 deletions

View file

@ -91,48 +91,54 @@ pub struct MouseButtons {
}
/// Counts of idle-surviving "watcher" work — background jobs that can wake
/// the agent for a new turn while it sits idle. Running `monitor` tasks emit
/// events, scheduled `/loop` tasks fire prompts on a timer, and running
/// background subagents inject a `subagent-completed-…` turn when they finish
/// — each can start a new turn, so they share one persistent "watching" cue
/// above the prompt. Broader than the tasks-pane `Watchers` group (which is
/// monitors + loops only); subagents are included here because they too
/// auto-wake the agent.
/// 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.
/// Broader than the tasks-pane `Watchers` group (monitors + loops only).
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Watchers {
/// Running background commands (non-monitor `background: true` tasks).
pub commands: usize,
/// Running `monitor` background tasks.
pub monitors: usize,
/// Active scheduled `/loop` tasks.
pub loops: usize,
/// Running background subagents (they auto-wake the parent on completion).
/// While the agent is idle, any running subagent is a background one — a
/// foreground subagent would keep the parent in `TurnRunning`.
/// Running background subagents. While the agent is idle, any running
/// subagent is a background one — a foreground subagent would keep the
/// parent in `TurnRunning`.
pub subagents: usize,
}
impl Watchers {
/// Total watcher count across all kinds.
pub fn total(self) -> usize {
self.monitors + self.loops + self.subagents
self.commands + self.monitors + self.loops + self.subagents
}
/// 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).
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 · 2 monitors · 1 loop · 1 subagent"`. Assumes
/// `"watching · 1 command · 2 monitors · 1 loop · 1 subagent"`. Assumes
/// `watchers.total() > 0`.
///
/// Every scheduled task can wake the agent, so all of them are counted as
/// `loops` (today every scheduled task is `/loop`-tagged; see
/// `ScheduledTaskInfo`). The label is built in a single `String` — no
/// intermediate `Vec`/`join` — because the turn-status line re-renders every
/// frame, keeping the idle cue's churn to one allocation (cf. the
/// static-`&str` right-side arms below).
fn watching_label(watchers: Watchers) -> String {
use std::fmt::Write as _;
// "watching" stem, then " · N noun" appended for each non-zero kind.
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);
}
if watchers.monitors > 0 {
let noun = if watchers.monitors == 1 {
"monitor"
@ -191,6 +197,10 @@ pub fn is_sendable_wait(activity: &Option<TurnActivity>) -> bool {
/// `[stop]` / `[↓]` buttons with their hover state; `None` for a keyboard-only
/// host (minimal mode — no mouse capture), which suppresses both buttons.
/// - `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
/// 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
/// with the terminal's own background (minimal mode).
@ -215,6 +225,7 @@ pub fn render_turn_status(
is_pending_user_input: bool,
goal_verifying: bool,
watchers: Watchers,
parked: bool,
flat_background: bool,
held_queue: usize,
held_queue_top_sendable: bool,
@ -263,14 +274,10 @@ pub fn render_turn_status(
return TurnStatusOutput::default();
}
// Special case: agent idle but background watchers (monitors, scheduled
// `/loop` tasks, and/or running background subagents) are still alive.
// Monitors emit events, loops fire prompts on a timer, and subagents
// inject a completion turn on finish — any can wake the agent for another
// turn — so keep a persistent cue above the prompt (unlike a scrollback
// line, it never scrolls away). Lower priority than the starting-session
// and drain-blocked cues handled above.
if state.is_idle() && watchers.total() > 0 {
// 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 {
// 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).
@ -287,6 +294,13 @@ pub fn render_turn_status(
return TurnStatusOutput::default();
}
// Parked with no watchers left: render nothing. The stopped look must
// never fall through to the running-turn chrome (spinner/timers/[stop])
// — the wait aborts the moment the user types, so that chrome would lie.
if parked {
return TurnStatusOutput::default();
}
// Determine if cancel button should be shown.
// Show when: TurnRunning or CommandRunning.
// Hide when: Idle, Cancelling (already cancelling), or a keyboard-only host
@ -738,9 +752,13 @@ fn render_starting_session(
/// is blocked (agent idle, waiting on user edit), while the MCP startup seed
/// is showing "Starting session…" (a fresh `total == 0` seed), or when the
/// agent is idle but background watchers are still running
/// (`watchers.total() > 0`) — running monitors emit events, scheduled `/loop`
/// tasks fire prompts, and background subagents inject a completion turn, any
/// of which can start a new turn.
/// (`watchers.total() > 0`) — running commands and monitors wake the agent on
/// completion/events, scheduled `/loop` tasks fire prompts, and background
/// subagents inject a completion turn, any of which can start a new turn.
///
/// 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.
///
/// Real MCP progress (`total > 0`) renders as a compact chip in the top status
/// bar instead, so it does not affect this row.
@ -749,7 +767,11 @@ pub fn should_show(
drain_blocked: bool,
mcp_init_progress: Option<&McpInitProgress>,
watchers: Watchers,
parked: bool,
) -> bool {
if parked {
return watchers.total() > 0;
}
!state.is_idle()
|| drain_blocked
|| starting_session_visible(mcp_init_progress)
@ -949,19 +971,22 @@ mod tests {
&AgentState::TurnRunning,
false,
None,
Watchers::default()
Watchers::default(),
false
));
assert!(should_show(
&AgentState::TurnCancelling,
false,
None,
Watchers::default()
Watchers::default(),
false
));
assert!(!should_show(
&AgentState::Idle,
false,
None,
Watchers::default()
Watchers::default(),
false
));
}
@ -971,47 +996,66 @@ mod tests {
&AgentState::Idle,
true,
None,
Watchers::default()
Watchers::default(),
false
));
}
#[test]
fn should_show_when_watchers_running() {
// Idle but a watcher (monitor, loop, or subagent) is still running →
// row stays visible so the persistent "watching · …" cue can show.
assert!(should_show(
&AgentState::Idle,
false,
None,
// Idle but a watcher (command, monitor, loop, or subagent) is still
// running → row stays visible so the persistent "watching · …" cue
// can show.
for watchers in [
Watchers {
commands: 1,
..Watchers::default()
},
Watchers {
monitors: 1,
..Watchers::default()
}
));
assert!(should_show(
&AgentState::Idle,
false,
None,
},
Watchers {
loops: 1,
..Watchers::default()
}
));
assert!(should_show(
&AgentState::Idle,
false,
None,
},
Watchers {
subagents: 1,
..Watchers::default()
}
));
},
] {
assert!(should_show(&AgentState::Idle, false, None, watchers, false));
}
// Idle with no watchers and nothing else pending → hidden.
assert!(!should_show(
&AgentState::Idle,
false,
None,
Watchers::default()
Watchers::default(),
false
));
}
#[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.
assert!(should_show(
&AgentState::TurnRunning,
false,
None,
Watchers {
commands: 1,
..Watchers::default()
},
true
));
assert!(!should_show(
&AgentState::TurnRunning,
false,
None,
Watchers::default(),
true
));
}
@ -1027,7 +1071,8 @@ mod tests {
&AgentState::Idle,
false,
Some(&seed),
Watchers::default()
Watchers::default(),
false
));
// Real progress (total > 0) is the top-bar chip — it must NOT drive
@ -1041,7 +1086,8 @@ mod tests {
&AgentState::Idle,
false,
Some(&connecting),
Watchers::default()
Watchers::default(),
false
));
// An expired seed must not drive the row either.
@ -1054,7 +1100,8 @@ mod tests {
&AgentState::Idle,
false,
Some(&expired),
Watchers::default()
Watchers::default(),
false
));
}
@ -1092,6 +1139,7 @@ mod tests {
false,
Watchers::default(),
false,
false,
0,
false,
);
@ -1121,6 +1169,37 @@ mod tests {
false,
watchers,
false,
false,
0,
false,
);
buffer_text(&buf, area)
}
/// 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 mut buf = Buffer::empty(area);
render_turn_status(
&mut buf,
area,
&AgentState::TurnRunning,
&Some(TurnActivity::Waiting(WaitingReason::TasksComplete)),
Some(Duration::from_secs(5)),
None,
0,
false,
Some(MouseButtons::default()),
false,
None,
None,
false,
false,
false,
watchers,
true,
false,
0,
false,
);
@ -1243,19 +1322,72 @@ mod tests {
#[test]
fn idle_with_all_watcher_kinds_lists_all() {
// Monitors, loops, and subagents present → one cue lists all three in
// order (monitors → loops → subagents), middle-dot separated.
// Commands, monitors, loops, and subagents present → one cue lists
// all four in order, middle-dot separated.
let text = render_idle_with_watchers(Watchers {
commands: 1,
monitors: 2,
loops: 1,
subagents: 3,
});
assert!(
text.contains("watching \u{00b7} 2 monitors \u{00b7} 1 loop \u{00b7} 3 subagents"),
text.contains(
"watching \u{00b7} 1 command \u{00b7} 2 monitors \u{00b7} 1 loop \u{00b7} 3 subagents"
),
"all kinds must be listed in one cue, got: {text:?}"
);
}
#[test]
fn idle_with_commands_renders_watching_line() {
// 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 {
commands: 2,
..Watchers::default()
});
assert!(
text.contains("watching \u{00b7} 2 commands"),
"idle with bg commands must render the watching 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"),
"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
// 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 {
commands: 2,
..Watchers::default()
});
assert!(
text.contains("watching \u{00b7} 2 commands"),
"parked with bg work must render the watching cue, got: {text:?}"
);
assert!(
!text.contains("Waiting") && !text.contains("[stop]"),
"parked must not render the running-turn chrome, got: {text:?}"
);
}
#[test]
fn parked_without_watchers_renders_nothing() {
let text = render_parked_with_watchers(Watchers::default());
assert!(
text.trim().is_empty(),
"parked with no watchers must render nothing, got: {text:?}"
);
}
#[test]
fn idle_with_no_watchers_renders_nothing() {
let text = render_idle_with_watchers(Watchers::default());
@ -1287,6 +1419,7 @@ mod tests {
false,
Watchers::default(),
false,
false,
1,
true,
);
@ -1299,6 +1432,13 @@ mod tests {
#[test]
fn watching_label_lists_only_nonzero_kinds() {
assert_eq!(
watching_label(Watchers {
commands: 2,
..Watchers::default()
}),
"watching \u{00b7} 2 commands"
);
assert_eq!(
watching_label(Watchers {
monitors: 2,
@ -1330,11 +1470,12 @@ mod tests {
);
assert_eq!(
watching_label(Watchers {
commands: 1,
monitors: 1,
loops: 1,
subagents: 2,
}),
"watching \u{00b7} 1 monitor \u{00b7} 1 loop \u{00b7} 2 subagents"
"watching \u{00b7} 1 command \u{00b7} 1 monitor \u{00b7} 1 loop \u{00b7} 2 subagents"
);
}