Synced from monorepo

Synced from monorepo

Changes:
- Temporarily disable session share link creation in the TUI
- Do not approve plan on empty Enter from the revise prompt
- Expose chat product Skills via ACP available_commands_update
- Return immediately from a blocking wait on an already-completed ACP task
- Split headless pager module for clearer structure
- Stop git worktree prune from removing user registrations on resume
- Use compaction sampler tokenizer for item token counts
- Opt-in extra root CAs via GROK_EXTRA_CA_BUNDLE
- Cancel all session subagents when the user stops
- Let the session persistence actor exit when its session ends
- Make fullscreen terminal resize much cheaper on long sessions
- Report honestly from kill_task when an ACP task does not exist
- Hide /usage for external-auth deployments
- Forward the history-load trailer’s computer_reason to the client
- Remove ineffective no-op tool reminder
- Declare slash-command screen-mode support in one place
- Keep settings enum picker on the committed value until Enter
- Reap a PTY’s full process tree
- Stream tool calls from headless mode over ACP
- Bridge gateway task lifecycle to ACP for chat session background tasks
- Don’t warn about truncated history on a suppressed replay
- Fit full-replace summarizer input and recover on context-length errors
- Stop dropping agents over an unrecognized frontmatter color
- Add /undo as a slash alias for /rewind
- Harden sleep/wake token-refresh paths against forced re-login
- Add session/list ACP method
- Give each sampling backend its own conversion module
- Treat an unenrolled child process as a lint error
- Suppress the cancelled marker on send-now wake turns
- Stop tearing down Roslyn on every edit, and read C# diagnostics

Source-Revision: 2a28b4a86cfc4a4c133c35b7fc2a6a9964387c39
This commit is contained in:
grokkybara[bot] 2026-07-30 19:07:40 +00:00
commit dd04f397b1
367 changed files with 29489 additions and 10051 deletions

View file

@ -1,7 +1,7 @@
//! Throttled automatic worktree GC (feature `metadata`).
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use anyhow::Result;
@ -9,7 +9,6 @@ use crate::CleanupReport;
use crate::api::gc::{GcOptions, GcReport, age_path_enabled, gc_worktrees};
use crate::db::{ListFilter, WorktreeDb, WorktreeKind, now_epoch_secs, resolve_grok_home};
use crate::discovery::{RebuildReport, rebuild_worktree_db};
use crate::git::checkout::git_command;
pub const META_LAST_AUTO_GC_AT: &str = "last_auto_gc_at";
/// Independent throttle stamp for optional DB rebuild (not shared with GC).
@ -58,7 +57,7 @@ pub struct WorktreeAutoGcLayer {
pub dry_run: Option<bool>,
pub include_orphan_snapshots: Option<bool>,
pub max_age_by_kind: BTreeMap<WorktreeKind, Option<u64>>,
/// Optional discovery rebuild + stale `.git/worktrees/` prune (default off).
/// Optional discovery rebuild + grok-scoped stale `.git/worktrees/` scrub (default off).
pub include_rebuild: Option<bool>,
/// Independent rebuild throttle; absent ⇒ 24h.
pub rebuild_min_interval_secs: Option<u64>,
@ -459,7 +458,7 @@ pub fn maybe_auto_gc(db: &WorktreeDb, auto_opts: &AutoGcOptions) -> Result<AutoG
let (overlay, btrfs) = run_orphan_cleaners(dry_run, auto_opts.include_orphan_snapshots);
// Prune each full pass when opted in (cheap vs discovery; not rebuild-throttled).
// Scrub each full pass when opted in (cheap vs discovery; not rebuild-throttled).
let stale_registrations_cleaned = if include_rebuild && !dry_run {
prune_stale_git_worktree_registrations(&prune_repos)
} else {
@ -633,65 +632,29 @@ fn collect_source_repos_for_prune(db: &WorktreeDb) -> BTreeSet<PathBuf> {
.collect()
}
/// Scrub stale grok-owned registrations from each known source repo,
/// scoped to worktrees under the grok home to prove ownership (see
/// [`crate::git::remove_stale_worktree_registrations`] for why a blanket
/// `git worktree prune` is unsafe here).
fn prune_stale_git_worktree_registrations(repos: &BTreeSet<PathBuf>) -> u64 {
let Ok(grok_home) = resolve_grok_home() else {
tracing::warn!("auto worktree registration scrub skipped: grok home unresolved");
return 0;
};
let cleaned: u64 = repos
.iter()
.filter(|repo| repo.is_dir())
.map(|repo| prune_stale_registrations_in_repo(repo))
.map(|repo| crate::git::remove_stale_worktree_registrations_under(repo, &grok_home))
.fold(0u64, u64::saturating_add);
if cleaned > 0 {
tracing::info!(
stale_registrations_cleaned = cleaned,
"auto worktree stale git registrations pruned"
"auto worktree stale git registrations scrubbed"
);
}
cleaned
}
fn count_git_worktree_registrations(git_worktrees_dir: &Path) -> u64 {
let Ok(entries) = std::fs::read_dir(git_worktrees_dir) else {
return 0;
};
entries
.filter_map(Result::ok)
.filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
.count() as u64
}
fn prune_stale_registrations_in_repo(source_repo: &Path) -> u64 {
let git_worktrees = source_repo.join(".git").join("worktrees");
let before = count_git_worktree_registrations(&git_worktrees);
let output = git_command()
.args(["worktree", "prune"])
.current_dir(source_repo)
.output();
match output {
Ok(o) if o.status.success() => {
let after = count_git_worktree_registrations(&git_worktrees);
before.saturating_sub(after)
}
Ok(o) => {
tracing::warn!(
source_repo = %source_repo.display(),
status = %o.status,
stderr = %String::from_utf8_lossy(&o.stderr),
"git worktree prune failed"
);
0
}
Err(e) => {
tracing::warn!(
source_repo = %source_repo.display(),
error = %e,
"git worktree prune failed to spawn"
);
0
}
}
}
fn run_orphan_cleaners(
dry_run: bool,
include_orphan_snapshots: bool,
@ -735,6 +698,7 @@ pub fn maybe_auto_gc_default() -> Result<AutoGcReport> {
mod tests {
use super::*;
use crate::db::{WorktreeRecord, WorktreeStatus};
use std::path::Path;
use std::sync::{Mutex, MutexGuard};
static ENV_LOCK: Mutex<()> = Mutex::new(());
@ -2095,6 +2059,48 @@ mod tests {
assert!(count_regs(&repo) < before);
}
#[test]
fn prune_keeps_foreign_registrations_outside_grok_home() {
let _g = env_guard();
clear_auto_gc_env();
let fx = crate::db::GrokHomeFixture::new();
let db = WorktreeDb::open(&fx.home).unwrap();
let repo = fx.home.join("foreign-src");
let user_dir = tempfile::TempDir::new().unwrap();
let user_wt = user_dir.path().join("user-wt");
plant_stale_git_worktree(&repo, &user_wt);
let before = count_regs(&repo);
assert!(before >= 1);
let tracked = fx.home.join("foreign-tracked");
std::fs::create_dir_all(&tracked).unwrap();
let mut rec = make_rec("fk-t", tracked, WorktreeKind::Session, now_epoch_secs());
rec.source_repo = repo.clone();
db.register(&rec).unwrap();
let report = maybe_auto_gc(
&db,
&AutoGcOptions {
min_interval_secs: 0,
include_orphan_snapshots: false,
include_rebuild: true,
rebuild_min_interval_secs: 0,
..AutoGcOptions::default()
},
)
.unwrap();
assert_eq!(
report.stale_registrations_cleaned, 0,
"foreign registrations must never be scrubbed"
);
assert_eq!(
count_regs(&repo),
before,
"user registrations outside grok home must survive"
);
}
#[test]
fn rebuild_unparseable_stamp_fails_open() {
let _g = env_guard();