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

@ -49,9 +49,9 @@ pub struct SuggestionRow {
}
impl SuggestionRow {
fn from_command(trigger: &CommandTrigger) -> Self {
fn from_command(trigger: &CommandTrigger, takes_args: bool) -> Self {
let mut insert_text = trigger.display.clone();
if trigger.takes_args {
if takes_args {
insert_text.push(' ');
}
Self {
@ -258,6 +258,9 @@ pub struct SlashController {
hide_session_scoped: bool,
/// Offer `/announcements` when session announcements (critical or promo) exist.
has_session_announcements: bool,
/// Consumer billing surface — gates `/usage` subcommands. Default `true`.
billing_surface_visible: bool,
workflows_available: bool,
/// Effective render mode of this process (immutable after startup — it only
/// changes via a full `/minimal`-`/fullscreen` re-exec). Injected via
/// [`Self::set_screen_mode`] wherever prompts are created; gates the
@ -293,6 +296,8 @@ impl SlashController {
cwd,
hide_session_scoped: false,
has_session_announcements: false,
billing_surface_visible: true,
workflows_available: false,
screen_mode: crate::app::ScreenMode::Fullscreen,
mru,
}
@ -313,6 +318,22 @@ impl SlashController {
self.has_session_announcements
}
pub fn set_billing_surface_visible(&mut self, visible: bool) {
self.billing_surface_visible = visible;
}
pub fn billing_surface_visible(&self) -> bool {
self.billing_surface_visible
}
pub fn set_workflows_available(&mut self, available: bool) {
self.workflows_available = available;
}
pub fn workflows_available(&self) -> bool {
self.workflows_available
}
/// Record the process's effective screen mode (see the field doc).
pub(crate) fn set_screen_mode(&mut self, mode: crate::app::ScreenMode) {
self.screen_mode = mode;
@ -327,6 +348,8 @@ impl SlashController {
models,
cwd: &self.cwd,
has_session_announcements: self.has_session_announcements,
billing_surface_visible: self.billing_surface_visible,
workflows_available: self.workflows_available,
screen_mode: self.screen_mode,
}
}
@ -630,11 +653,8 @@ impl SlashController {
else {
return snapshot;
};
let visible = {
let ctx = self.app_ctx(models);
command.visible(&ctx)
};
if !visible || !command.takes_args() {
let ctx = self.app_ctx(models);
if !command.visible(&ctx) || !command.takes_args_now(&ctx) {
return snapshot;
}
@ -822,7 +842,12 @@ impl SlashController {
continue;
}
if seen.insert(trigger.command_index) {
rows.push(SuggestionRow::from_command(trigger));
let takes = self
.registry
.commands_by_index(trigger.command_index)
.map(|cmd| cmd.takes_args_now(&ctx))
.unwrap_or(false);
rows.push(SuggestionRow::from_command(trigger, takes));
}
}
return rows;
@ -851,8 +876,7 @@ impl SlashController {
// Deduplicate: keep the best-scoring trigger per command.
// At equal fuzzy scores the tiebreaker is:
// 1. Exact match on match_text wins (e.g. alias "/m" for query "m")
// 2. Canonical name beats aliases (e.g. "/terminal-setup" over
// "/terminal-check" for query "terminal")
// 2. Canonical name beats aliases
// 3. Lexicographic display order as final fallback
let mut best_per_command: HashMap<usize, (u32, usize)> = HashMap::new();
for (visible_idx, score) in hits {
@ -885,10 +909,22 @@ impl SlashController {
}
let mut deduped: Vec<(u32, usize)> = best_per_command.into_values().collect();
let mut rows: Vec<SuggestionRow> = visible_triggers
.iter()
.map(|t| SuggestionRow::from_command(t))
.collect();
// Re-borrow after rank so takes_args_now can see AppCtx without
// overlapping the matcher mut borrow.
let mut rows: Vec<SuggestionRow> = {
let ctx = self.app_ctx(models);
visible_triggers
.iter()
.map(|t| {
let takes = self
.registry
.commands_by_index(t.command_index)
.map(|cmd| cmd.takes_args_now(&ctx))
.unwrap_or(false);
SuggestionRow::from_command(t, takes)
})
.collect()
};
let sort_meta: Vec<(String, CommandSource)> = visible_triggers
.iter()
.map(|t| (t.canonical.clone(), t.source))
@ -954,10 +990,10 @@ impl SlashController {
models: &ModelState,
query: &str,
) -> Vec<SuggestionRow> {
if !command.takes_args() {
let ctx = self.app_ctx(models);
if !command.takes_args_now(&ctx) {
return Vec::new();
}
let ctx = self.app_ctx(models);
let Some(items) = command.suggest_args(&ctx, query) else {
return Vec::new();
};
@ -1849,6 +1885,7 @@ mod tests {
"/btw",
"/session-info",
"/find",
"/doctor",
] {
assert!(
!names.contains(&hide),
@ -1873,6 +1910,7 @@ mod tests {
.collect();
assert!(names.iter().any(|d| d == "/compact"));
assert!(names.iter().any(|d| d == "/fork"));
assert!(names.iter().any(|d| d == "/doctor"));
}
/// `/cd` is dashboard-only: it appears in the dropdown on the
@ -2658,28 +2696,33 @@ mod tests {
}
#[test]
fn dedup_prefers_canonical_over_alias_at_equal_score() {
// When the query matches both the canonical name and an alias
// equally well, the dropdown should show the canonical name.
// Regression: "terminal" used to show "/terminal-check" (alias)
// instead of "/terminal-setup" (canonical) because the alias
// sorts lexicographically first.
fn doctor_completion_prefers_canonical_but_honors_exact_aliases() {
let mut ctrl = SlashController::with_builtins(std::path::PathBuf::from("."));
let state = SlashState::default();
let models = ModelState::default();
let text = "/terminal";
let text = "/doctor";
ctrl.refresh(&state, text, text.len(), &models);
let snap = state.snapshot();
assert!(snap.open);
let displays: Vec<&str> = snap.matches.iter().map(|r| r.display.as_str()).collect();
let snapshot = state.snapshot();
let displays: Vec<&str> = snapshot
.matches
.iter()
.map(|row| row.display.as_str())
.collect();
assert!(displays.contains(&"/doctor"), "matches: {displays:?}");
assert!(!displays.contains(&"/terminal-setup"));
let text = "/terminal-setup";
ctrl.refresh(&state, text, text.len(), &models);
let snapshot = state.snapshot();
let displays: Vec<&str> = snapshot
.matches
.iter()
.map(|row| row.display.as_str())
.collect();
assert!(
displays.contains(&"/terminal-setup"),
"expected /terminal-setup (canonical) in matches, got: {displays:?}"
);
assert!(
!displays.contains(&"/terminal-check"),
"/terminal-check (alias) should be deduplicated in favor of canonical"
"matches: {displays:?}"
);
}
}