fix(state): jangan bangun mention index di mode attach

AppStateRest::new() men-spawn thread ignore::Walk untuk mention_index
tanpa syarat, padahal mode --attach cuma dipakai untuk render lokal —
handle_key dan logika mention berjalan di sisi daemon lewat IPC, jadi
index di client attach tidak pernah dipakai. Ini bikin setiap
--attach melakukan full workspace walk (sampai 50.000 entry) sia-sia.

Pindahkan thread-spawn itu ke method terpisah
spawn_mention_index_build(), dipanggil eksplisit dari
run_single_process() dan run_daemon() setelah AppStateRest::new(),
tapi sengaja tidak dipanggil dari run_attach().
This commit is contained in:
asepharyana
2026-07-15 06:32:57 +07:00
parent dcfc5b9ec0
commit e0d0860d7b
2 changed files with 48 additions and 37 deletions
+46 -37
View File
@@ -206,45 +206,54 @@ impl AppStateRest {
}); });
} }
// Fire-and-forget background file index build for `@file` mention state
// autocomplete. }
//
// Flow: spawn OS thread -> `ignore::Walk` each workspace root, /// Spawn the background thread that walks every workspace root and
// collecting file paths (workspace-index-prefixed for roots beyond /// populates `mention_index` for `@file` mention autocomplete.
// the first, matching `resolve_path`'s `[N]path` convention) -> stop ///
// once 50,000 entries are collected -> store the result in /// Why a separate method, not called from `new()`: the attach-only
// `mention_index`. /// TUI client also constructs an `AppStateRest` (for local rendering
// /// state) but never runs tools or `handle_key` locally — it forwards
// Why a raw thread and not a background tokio task: there is no /// keystrokes to the daemon over IPC, which has its own `AppStateRest`
// persistent async runtime driving the render loop, and this is /// with its own index. Spawning this walk in the attach client would
// blocking filesystem I/O -- a dedicated thread keeps startup /// waste a full workspace scan for an index nothing there consumes.
// non-blocking. Not joined, same rationale as the LSP provisioning /// Callers that DO need the index (single-process mode, the daemon)
// thread above: a slow/huge repo must not delay the TUI appearing. /// call this explicitly after construction.
{ ///
let mention_index = state.mention_index.clone(); /// Flow: spawn OS thread -> `ignore::Walk` each workspace root,
let roots = state.workspace_roots.clone(); /// collecting file paths (workspace-index-prefixed for roots beyond
std::thread::spawn(move || { /// the first, matching `resolve_path`'s `[N]path` convention) -> stop
const MAX_MENTION_ENTRIES: usize = 50_000; /// once 50,000 entries are collected -> store the result in
let mut paths = Vec::new(); /// `mention_index`.
'roots: for (i, root) in roots.iter().enumerate() { ///
for entry in ignore::Walk::new(root).flatten() { /// Why a raw thread and not a background tokio task: there is no
if !entry.path().is_file() { /// persistent async runtime driving the render loop, and this is
continue; /// blocking filesystem I/O -- a dedicated thread keeps startup
} /// non-blocking. Not joined, same rationale as the LSP provisioning
let rel = entry.path().strip_prefix(root).unwrap_or(entry.path()); /// thread above: a slow/huge repo must not delay the TUI appearing.
let rel_str = rel.display().to_string(); pub fn spawn_mention_index_build(&self) {
let formatted = if i == 0 { rel_str } else { format!("[{i}]{rel_str}") }; let mention_index = self.mention_index.clone();
paths.push(formatted); let roots = self.workspace_roots.clone();
if paths.len() >= MAX_MENTION_ENTRIES { std::thread::spawn(move || {
break 'roots; const MAX_MENTION_ENTRIES: usize = 50_000;
} let mut paths = Vec::new();
'roots: for (i, root) in roots.iter().enumerate() {
for entry in ignore::Walk::new(root).flatten() {
if !entry.path().is_file() {
continue;
}
let rel = entry.path().strip_prefix(root).unwrap_or(entry.path());
let rel_str = rel.display().to_string();
let formatted = if i == 0 { rel_str } else { format!("[{i}]{rel_str}") };
paths.push(formatted);
if paths.len() >= MAX_MENTION_ENTRIES {
break 'roots;
} }
} }
mention_index.set(paths); }
}); mention_index.set(paths);
} });
state
} }
/// Whether an agent turn is currently running. /// Whether an agent turn is currently running.
+2
View File
@@ -109,6 +109,7 @@ fn run_single_process() -> Result<()> {
&session_dir, &session_dir,
store.memory_dir, store.memory_dir,
); );
state.spawn_mention_index_build();
state.sessions = model::session::Session::list(&store.base_dir); state.sessions = model::session::Session::list(&store.base_dir);
@@ -442,6 +443,7 @@ fn run_daemon() -> Result<()> {
&session_dir, &session_dir,
store.memory_dir, store.memory_dir,
); );
state.spawn_mention_index_build();
state.sessions = model::session::Session::list(&store.base_dir); state.sessions = model::session::Session::list(&store.base_dir);
let _rt = tokio::runtime::Runtime::new()?; let _rt = tokio::runtime::Runtime::new()?;