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:
+46
-37
@@ -206,45 +206,54 @@ impl AppStateRest {
|
||||
});
|
||||
}
|
||||
|
||||
// Fire-and-forget background file index build for `@file` mention
|
||||
// autocomplete.
|
||||
//
|
||||
// Flow: spawn OS thread -> `ignore::Walk` each workspace root,
|
||||
// collecting file paths (workspace-index-prefixed for roots beyond
|
||||
// the first, matching `resolve_path`'s `[N]path` convention) -> stop
|
||||
// once 50,000 entries are collected -> store the result in
|
||||
// `mention_index`.
|
||||
//
|
||||
// Why a raw thread and not a background tokio task: there is no
|
||||
// persistent async runtime driving the render loop, and this is
|
||||
// blocking filesystem I/O -- a dedicated thread keeps startup
|
||||
// non-blocking. Not joined, same rationale as the LSP provisioning
|
||||
// thread above: a slow/huge repo must not delay the TUI appearing.
|
||||
{
|
||||
let mention_index = state.mention_index.clone();
|
||||
let roots = state.workspace_roots.clone();
|
||||
std::thread::spawn(move || {
|
||||
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;
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
/// Spawn the background thread that walks every workspace root and
|
||||
/// populates `mention_index` for `@file` mention autocomplete.
|
||||
///
|
||||
/// Why a separate method, not called from `new()`: the attach-only
|
||||
/// TUI client also constructs an `AppStateRest` (for local rendering
|
||||
/// state) but never runs tools or `handle_key` locally — it forwards
|
||||
/// keystrokes to the daemon over IPC, which has its own `AppStateRest`
|
||||
/// with its own index. Spawning this walk in the attach client would
|
||||
/// waste a full workspace scan for an index nothing there consumes.
|
||||
/// Callers that DO need the index (single-process mode, the daemon)
|
||||
/// call this explicitly after construction.
|
||||
///
|
||||
/// Flow: spawn OS thread -> `ignore::Walk` each workspace root,
|
||||
/// collecting file paths (workspace-index-prefixed for roots beyond
|
||||
/// the first, matching `resolve_path`'s `[N]path` convention) -> stop
|
||||
/// once 50,000 entries are collected -> store the result in
|
||||
/// `mention_index`.
|
||||
///
|
||||
/// Why a raw thread and not a background tokio task: there is no
|
||||
/// persistent async runtime driving the render loop, and this is
|
||||
/// blocking filesystem I/O -- a dedicated thread keeps startup
|
||||
/// non-blocking. Not joined, same rationale as the LSP provisioning
|
||||
/// thread above: a slow/huge repo must not delay the TUI appearing.
|
||||
pub fn spawn_mention_index_build(&self) {
|
||||
let mention_index = self.mention_index.clone();
|
||||
let roots = self.workspace_roots.clone();
|
||||
std::thread::spawn(move || {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
mention_index.set(paths);
|
||||
});
|
||||
}
|
||||
|
||||
/// Whether an agent turn is currently running.
|
||||
|
||||
@@ -109,6 +109,7 @@ fn run_single_process() -> Result<()> {
|
||||
&session_dir,
|
||||
store.memory_dir,
|
||||
);
|
||||
state.spawn_mention_index_build();
|
||||
state.sessions = model::session::Session::list(&store.base_dir);
|
||||
|
||||
|
||||
@@ -442,6 +443,7 @@ fn run_daemon() -> Result<()> {
|
||||
&session_dir,
|
||||
store.memory_dir,
|
||||
);
|
||||
state.spawn_mention_index_build();
|
||||
state.sessions = model::session::Session::list(&store.base_dir);
|
||||
|
||||
let _rt = tokio::runtime::Runtime::new()?;
|
||||
|
||||
Reference in New Issue
Block a user