Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,954 changes: 1,670 additions & 1,284 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,12 @@ generic-array = "0.14.9"
matchit = "0.8.6"
pdqselect = "0.1.1"

# AutoAgents
autoagents = "*"
autoagents-derive = "*"
# AutoAgents. Pinned to a specific git rev — earlier revs in the repo contain a
# file whose name is invalid on NTFS (`docs/theme/catppuccin-admonish.css .`)
# which breaks `cargo install` on Windows; the registry tarballs are also
# incomplete. The local ChatProvider adapter is written against the v0.3.7 API.
autoagents = { git = "https://github.com/liquidos-ai/AutoAgents", rev = "57ebeaa4" }
autoagents-derive = { git = "https://github.com/liquidos-ai/AutoAgents", rev = "57ebeaa4" }

# Moved MCP crate dependencies
nix = { version = "0.30.1", features = ["process", "signal"] }
Expand Down
9 changes: 7 additions & 2 deletions crates/codegraph-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ clap = { workspace = true }
colored = { workspace = true }
indicatif = { workspace = true }
once_cell = { workspace = true }
tikv-jemallocator = { workspace = true }
hashbrown = { workspace = true }
rustc-hash = { workspace = true }
sha2 = { workspace = true }
Expand All @@ -58,9 +57,15 @@ libc = "0.2"
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = [
"Win32_Foundation",
"Win32_System_Memory"
"Win32_System_Memory",
"Win32_System_Threading"
] }

# jemalloc only builds on non-MSVC targets (tikv-jemalloc-sys cannot
# build with the MSVC toolchain on Windows).
[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = { workspace = true }

[dev-dependencies]
tokio-test = { workspace = true }
tempfile = { workspace = true }
Expand Down
8 changes: 5 additions & 3 deletions crates/codegraph-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ pub mod watch;

pub use advanced_config::*;
pub use buffer_pool::*;
pub use compression::*;
pub use config::ConfigManager as ServerConfigManager;
pub use config::LoggingConfig as ServerLoggingConfig;
pub use config::{
crypto, DatabaseBackend, DatabaseConfig, SecretsConfig, SecurityConfig, ServerConfig, Settings,
SurrealDbConfig, VectorConfig,
};
pub use config_manager::*;
pub use compression::*;
pub use embedding_config::*;
pub use error::*;
pub use incremental::*;
Expand All @@ -51,7 +51,9 @@ pub use types::*;
pub use versioning::*;
pub use watch::*;

// Use jemalloc as the global allocator when the feature is enabled
#[cfg(feature = "jemalloc")]
// Use jemalloc as the global allocator when the feature is enabled.
// jemalloc-sys cannot build on Windows MSVC, so skip it there even if the
// feature is on — falls back to the system allocator.
#[cfg(all(feature = "jemalloc", not(target_env = "msvc")))]
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
8 changes: 4 additions & 4 deletions crates/codegraph-core/src/mmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ fn prefetch_range_impl(m: &MappedFile, offset: usize, len: usize) {

#[cfg(windows)]
fn prefetch_range_impl(m: &MappedFile, offset: usize, len: usize) {
use core::mem::size_of;
use windows_sys::Win32::System::Memory::{PrefetchVirtualMemory, _WIN32_MEMORY_RANGE_ENTRY};
use windows_sys::Win32::System::Memory::{PrefetchVirtualMemory, WIN32_MEMORY_RANGE_ENTRY};
use windows_sys::Win32::System::Threading::GetCurrentProcess;

let end = offset.saturating_add(len).min(m.len);
if end <= offset {
Expand All @@ -180,13 +180,13 @@ fn prefetch_range_impl(m: &MappedFile, offset: usize, len: usize) {
let ptr = unsafe { m.mmap.as_ptr().add(offset) } as *mut core::ffi::c_void;
let bytes = end - offset;

let mut range = _WIN32_MEMORY_RANGE_ENTRY {
let mut range = WIN32_MEMORY_RANGE_ENTRY {
VirtualAddress: ptr,
NumberOfBytes: bytes,
};
unsafe {
// Best-effort; ignore failure.
let _ = PrefetchVirtualMemory(0, 1, &mut range as *mut _ as *mut _, 0);
let _ = PrefetchVirtualMemory(GetCurrentProcess(), 1, &mut range as *mut _ as *mut _, 0);
}
}

Expand Down
13 changes: 9 additions & 4 deletions crates/codegraph-mcp-autoagents/src/autoagents/agent_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,10 @@ impl CodeGraphChatAdapter {

#[async_trait]
impl ChatProvider for CodeGraphChatAdapter {
async fn chat(
// In autoagents 0.3.7 the trait split: `chat` (no tools) has a default impl
// that delegates to `chat_with_tools`, and `chat_with_tools` is the required
// method. So we implement `chat_with_tools` directly here.
async fn chat_with_tools(
&self,
messages: &[ChatMessage],
tools: Option<&[Tool]>,
Expand Down Expand Up @@ -348,10 +351,12 @@ impl ChatProvider for CodeGraphChatAdapter {
}))
}

// 0.3.7 trait: `chat_stream(messages, json_schema)` — no `tools` here. The
// `_with_tools` variant returns a different stream type (StreamChunk); we
// don't support streaming at all, so override the non-tools variant.
async fn chat_stream(
&self,
_messages: &[ChatMessage],
_tools: Option<&[Tool]>,
_json_schema: Option<autoagents::llm::chat::StructuredOutputFormat>,
) -> Result<
std::pin::Pin<Box<dyn futures::Stream<Item = Result<String, LLMError>> + Send>>,
Expand Down Expand Up @@ -649,11 +654,11 @@ impl<T: AgentDeriveT + AgentHooks + Clone> TierAwareReActAgent<T> {
impl<T: AgentDeriveT + AgentHooks + Clone> AgentDeriveT for TierAwareReActAgent<T> {
type Output = T::Output;

fn description(&self) -> &'static str {
fn description(&self) -> &str {
self.inner_derive.description()
}

fn name(&self) -> &'static str {
fn name(&self) -> &str {
self.inner_derive.name()
}

Expand Down
10 changes: 9 additions & 1 deletion crates/codegraph-mcp-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ thiserror = { workspace = true }
tracing = { workspace = true }
async-trait = { workspace = true }
anyhow = { workspace = true }
nix = { workspace = true, features = ["process", "signal"] }
regex = { workspace = true }
tokio = { workspace = true }
uuid = { workspace = true }
Expand All @@ -27,6 +26,15 @@ tokio-tungstenite = { workspace = true }
url = { workspace = true }
toml = { workspace = true }

[target.'cfg(unix)'.dependencies]
nix = { workspace = true, features = ["process", "signal"] }

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = [
"Win32_Foundation",
"Win32_System_Threading",
] }

[dev-dependencies]
tempfile = { workspace = true }
serial_test = "3.2"
62 changes: 62 additions & 0 deletions crates/codegraph-mcp-core/src/process.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::error::Result;
use anyhow::Context;
use dashmap::DashMap;
#[cfg(unix)]
use nix::sys::signal::{self, Signal};
#[cfg(unix)]
use nix::unistd::Pid;
use std::fs;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -287,6 +289,7 @@ impl ProcessManager {
Err(anyhow::anyhow!("No running server found").into())
}

#[cfg(unix)]
fn is_process_running(&self, pid: u32) -> Result<bool> {
match signal::kill(Pid::from_raw(pid as i32), None) {
Ok(_) => Ok(true),
Expand All @@ -295,6 +298,31 @@ impl ProcessManager {
}
}

#[cfg(windows)]
fn is_process_running(&self, pid: u32) -> Result<bool> {
use windows_sys::Win32::Foundation::{CloseHandle, STILL_ACTIVE};
use windows_sys::Win32::System::Threading::{
GetExitCodeProcess, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION,
};

unsafe {
let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid);
if handle.is_null() {
// OpenProcess fails if the process does not exist (or we lack rights).
// Treat as "not running" — the caller uses this to clean up stale PID files.
return Ok(false);
Comment on lines +310 to +313
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Distinguish access-denied from process-not-running

Treating any OpenProcess failure as Ok(false) conflates “process is gone” with “process is running but inaccessible” (for example, daemon started elevated and status checked from a non-elevated shell). In that case, status/cleanup paths will incorrectly report the daemon as stopped and can trigger stale-PID behavior; ERROR_ACCESS_DENIED should be surfaced (or handled separately) instead of being mapped to “not running”.

Useful? React with 👍 / 👎.

}
let mut exit_code: u32 = 0;
let ok = GetExitCodeProcess(handle, &mut exit_code as *mut u32);
CloseHandle(handle);
if ok == 0 {
return Err(anyhow::anyhow!("GetExitCodeProcess failed for pid {}", pid).into());
}
Ok(exit_code == STILL_ACTIVE as u32)
}
}

#[cfg(unix)]
fn graceful_shutdown(&self, pid: u32) -> Result<()> {
info!("Sending SIGTERM to process {}", pid);
signal::kill(Pid::from_raw(pid as i32), Signal::SIGTERM)
Expand All @@ -314,13 +342,47 @@ impl ProcessManager {
Ok(())
}

#[cfg(windows)]
fn graceful_shutdown(&self, pid: u32) -> Result<()> {
// Windows has no portable SIGTERM equivalent for non-console processes;
// fall back to TerminateProcess.
info!(
"Terminating process {} (Windows has no graceful signal)",
pid
);
self.force_kill(pid)
}

#[cfg(unix)]
fn force_kill(&self, pid: u32) -> Result<()> {
info!("Sending SIGKILL to process {}", pid);
signal::kill(Pid::from_raw(pid as i32), Signal::SIGKILL)
.context("Failed to send SIGKILL")?;
Ok(())
}

#[cfg(windows)]
fn force_kill(&self, pid: u32) -> Result<()> {
use windows_sys::Win32::Foundation::CloseHandle;
use windows_sys::Win32::System::Threading::{
OpenProcess, TerminateProcess, PROCESS_TERMINATE,
};

info!("TerminateProcess for pid {}", pid);
unsafe {
let handle = OpenProcess(PROCESS_TERMINATE, 0, pid);
if handle.is_null() {
return Err(anyhow::anyhow!("OpenProcess({}) failed", pid).into());
}
let ok = TerminateProcess(handle, 1);
CloseHandle(handle);
if ok == 0 {
return Err(anyhow::anyhow!("TerminateProcess({}) failed", pid).into());
}
}
Ok(())
}

pub async fn cleanup(&self) -> Result<()> {
let pid_files = self.pid_files.read().await;
for pid_file in pid_files.iter() {
Expand Down
8 changes: 8 additions & 0 deletions crates/codegraph-mcp-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,15 @@ codegraph-mcp = { path = "../codegraph-mcp" }
tracing-appender = { workspace = true }
dirs = "6.0.0"
dotenv = "0.15"

[target.'cfg(unix)'.dependencies]
nix = { version = "0.30.1", features = ["process", "signal"] }

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = [
"Win32_Foundation",
"Win32_System_Threading",
] }

[dev-dependencies]
serial_test = "3.2"
35 changes: 29 additions & 6 deletions crates/codegraph-mcp-server/src/bin/codegraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2334,9 +2334,6 @@ mod cli_command_tests {

#[cfg(feature = "daemon")]
async fn handle_daemon_stop(path: PathBuf) -> Result<()> {
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;

let project_root = std::fs::canonicalize(&path)
.with_context(|| format!("Invalid project path: {:?}", path))?;

Expand All @@ -2350,9 +2347,7 @@ async fn handle_daemon_stop(path: PathBuf) -> Result<()> {
format!("🛑 Stopping daemon (PID: {})...", pid).yellow()
);

// Send SIGTERM
let pid = Pid::from_raw(pid as i32);
match kill(pid, Signal::SIGTERM) {
match send_stop_signal(pid) {
Ok(_) => {
println!("{}", "✅ Stop signal sent successfully".green());

Expand Down Expand Up @@ -2384,6 +2379,34 @@ async fn handle_daemon_stop(path: PathBuf) -> Result<()> {
Ok(())
}

#[cfg(all(feature = "daemon", unix))]
fn send_stop_signal(pid: u32) -> anyhow::Result<()> {
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
kill(Pid::from_raw(pid as i32), Signal::SIGTERM)
.map_err(|e| anyhow::anyhow!("kill(SIGTERM) failed: {e}"))
}

#[cfg(all(feature = "daemon", windows))]
fn send_stop_signal(pid: u32) -> anyhow::Result<()> {
// Windows has no SIGTERM for non-console processes; TerminateProcess is the
// closest equivalent. The daemon does not get to run shutdown handlers.
use windows_sys::Win32::Foundation::CloseHandle;
use windows_sys::Win32::System::Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE};
unsafe {
let handle = OpenProcess(PROCESS_TERMINATE, 0, pid);
if handle.is_null() {
anyhow::bail!("OpenProcess({pid}) failed");
}
let ok = TerminateProcess(handle, 1);
CloseHandle(handle);
if ok == 0 {
anyhow::bail!("TerminateProcess({pid}) failed");
}
}
Ok(())
}

#[cfg(feature = "daemon")]
async fn handle_daemon_status(path: PathBuf, json: bool) -> Result<()> {
let project_root = std::fs::canonicalize(&path)
Expand Down
7 changes: 4 additions & 3 deletions crates/codegraph-mcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@ tokenizers = { workspace = true } # Qwen2.5-Coder tokenizer for accurate token
# codegraph-api not used directly here; avoid pulling heavy deps
## core-rag-mcp-server intentionally not linked to keep binary lean

# AutoAgents framework for agentic workflows
autoagents = { git = "https://github.com/liquidos-ai/AutoAgents", optional = true }
autoagents-derive = { git = "https://github.com/liquidos-ai/AutoAgents", optional = true }
# AutoAgents framework for agentic workflows. See workspace Cargo.toml for the
# rev pin (a post-fix rev that no longer contains the NTFS-invalid path).
autoagents = { workspace = true, optional = true }
autoagents-derive = { workspace = true, optional = true }
codegraph-mcp-core = { path = "../codegraph-mcp-core" }
codegraph-mcp-tools = { path = "../codegraph-mcp-tools", optional = true }
unicode-bom = "2.0.3"
Expand Down
17 changes: 13 additions & 4 deletions crates/codegraph-vector/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@ futures = { workspace = true }
num_cpus = { workspace = true }
unicode-normalization = "0.1"

# Local embeddings (Candle) - optional via feature
candle-core = { version = "0.9.1", optional = true, default-features = false, features = ["metal"] }
candle-nn = { version = "0.9.1", optional = true, default-features = false }
candle-transformers = { version = "0.9.1", optional = true, default-features = false }
# Local embeddings (Candle) - optional via feature.
# Per-target split: enable the metal backend on macOS; everywhere else build CPU-only
# (the objc2 backend used by `metal` is Apple-only). See the target tables below.
tokenizers = { workspace = true } # Always available for token counting in Jina provider
hf-hub = { version = "0.4", optional = true, default-features = true, features = ["tokio"] }
lru = { workspace = true }
Expand All @@ -47,7 +46,17 @@ ort = { version = "2.0.0-rc.10", optional = true, default-features = false, feat
semchunk-rs = { workspace = true }
fxhash = "0.2.1"

# Apple-only: enable candle's metal backend on macOS.
[target.'cfg(target_os = "macos")'.dependencies]
candle-core = { version = "0.9.1", optional = true, default-features = false, features = ["metal"] }
candle-nn = { version = "0.9.1", optional = true, default-features = false }
candle-transformers = { version = "0.9.1", optional = true, default-features = false }

# Non-Apple: CPU-only candle so local-embeddings still builds on Windows/Linux.
[target.'cfg(not(target_os = "macos"))'.dependencies]
candle-core = { version = "0.9.1", optional = true, default-features = false }
candle-nn = { version = "0.9.1", optional = true, default-features = false }
candle-transformers = { version = "0.9.1", optional = true, default-features = false }

[dev-dependencies]
tokio-test = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/codegraph-vector/src/simd_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ mod tests {
let a = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let b = vec![8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0];

let _scalar_result = SIMDVectorOps::cosine_similarity_scalar(&a, &b).unwrap();
let scalar_result = SIMDVectorOps::cosine_similarity_scalar(&a, &b).unwrap();

#[cfg(target_arch = "x86_64")]
{
Expand Down
Loading