Skip to content

Commit 1bcda39

Browse files
committed
Normalize path handling
1 parent 1022c43 commit 1bcda39

25 files changed

Lines changed: 812 additions & 510 deletions

anycode-backend/src/acp.rs

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::acp_fs::AcpFsCommand;
22
use crate::acp_history::AcpHistoryManager;
3-
use crate::utils::relative_to_current_dir;
43
use agent_client_protocol::{self as acp, Agent as _, Client};
54
use agent_client_protocol_schema::{ProtocolVersion, SessionId};
65
use anyhow::{Context, Result, anyhow};
@@ -310,13 +309,11 @@ impl Client for AcpClientImpl {
310309
serde_json::json!({})
311310
};
312311

313-
// Extract locations from tool call update (use relative path if possible)
312+
// Keep ACP locations absolute to match the protocol and avoid path identity mismatches.
314313
let locations = tool_call_update.fields.locations.as_ref().map(|locs| {
315314
locs.iter()
316315
.map(|loc| AcpLocation {
317-
path: relative_to_current_dir(&loc.path)
318-
.map(|p| p.to_string_lossy().to_string())
319-
.unwrap_or_else(|| loc.path.to_string_lossy().to_string()),
316+
path: loc.path.to_string_lossy().to_string(),
320317
line: loc.line,
321318
})
322319
.collect()
@@ -462,7 +459,10 @@ impl Client for AcpClientImpl {
462459
Err(acp::Error::internal_error())
463460
}
464461
Err(_) => {
465-
error!("ACP fs response channel dropped for agent {}", self.agent_id);
462+
error!(
463+
"ACP fs response channel dropped for agent {}",
464+
self.agent_id
465+
);
466466
Err(acp::Error::internal_error())
467467
}
468468
}
@@ -515,7 +515,10 @@ impl Client for AcpClientImpl {
515515
Err(acp::Error::internal_error())
516516
}
517517
Err(_) => {
518-
error!("ACP fs response channel dropped for agent {}", self.agent_id);
518+
error!(
519+
"ACP fs response channel dropped for agent {}",
520+
self.agent_id
521+
);
519522
Err(acp::Error::internal_error())
520523
}
521524
}
@@ -782,12 +785,13 @@ impl AcpClientImpl {
782785
if let Some(raw_input) = tool_call.raw_input.as_ref() {
783786
if let Some(cmd) = raw_input.get("cmd").and_then(|value| value.as_str()) {
784787
tool_command = Some(cmd.to_string());
785-
} else if let Some(command) = raw_input.get("command").and_then(|value| value.as_str()) {
788+
} else if let Some(command) = raw_input.get("command").and_then(|value| value.as_str())
789+
{
786790
tool_command = Some(command.to_string());
787791
}
788792
}
789793

790-
// Extract locations from tool call (use relative path if possible)
794+
// Keep ACP locations absolute to match the protocol and avoid path identity mismatches.
791795
let locations = if tool_call.locations.is_empty() {
792796
None
793797
} else {
@@ -796,9 +800,7 @@ impl AcpClientImpl {
796800
.locations
797801
.iter()
798802
.map(|loc| AcpLocation {
799-
path: relative_to_current_dir(&loc.path)
800-
.map(|p| p.to_string_lossy().to_string())
801-
.unwrap_or_else(|| loc.path.to_string_lossy().to_string()),
803+
path: loc.path.to_string_lossy().to_string(),
802804
line: loc.line,
803805
})
804806
.collect(),
@@ -1148,7 +1150,12 @@ pub struct AcpAgent {
11481150
}
11491151

11501152
impl AcpAgent {
1151-
pub fn new(agent_id: String, agent_name: String, permission_mode: Arc<AtomicU8>, fs_sender: mpsc::Sender<AcpFsCommand>) -> Self {
1153+
pub fn new(
1154+
agent_id: String,
1155+
agent_name: String,
1156+
permission_mode: Arc<AtomicU8>,
1157+
fs_sender: mpsc::Sender<AcpFsCommand>,
1158+
) -> Self {
11521159
// Initialize history manager with current working directory and agent ID
11531160
let project_root = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
11541161
let mut history_manager = AcpHistoryManager::new(&project_root, &agent_id);
@@ -1343,18 +1350,18 @@ impl AcpAgent {
13431350
Self::spawn_stderr_reader(stderr, message_sender.clone(), history_for_stderr);
13441351

13451352
// Initialize connection and create session
1346-
let bootstrap =
1347-
match Self::initialize_connection(&conn, &agent_id, resume_session_id).await {
1348-
Ok(bootstrap) => {
1349-
let _ = session_tx.send(bootstrap.session_id.clone()).await;
1350-
ready.store(true, Ordering::SeqCst);
1351-
Some(bootstrap)
1352-
}
1353-
Err(e) => {
1354-
error!("Failed to initialize ACP agent {}: {}", agent_id, e);
1355-
None
1356-
}
1357-
};
1353+
let bootstrap = match Self::initialize_connection(&conn, &agent_id, resume_session_id).await
1354+
{
1355+
Ok(bootstrap) => {
1356+
let _ = session_tx.send(bootstrap.session_id.clone()).await;
1357+
ready.store(true, Ordering::SeqCst);
1358+
Some(bootstrap)
1359+
}
1360+
Err(e) => {
1361+
error!("Failed to initialize ACP agent {}: {}", agent_id, e);
1362+
None
1363+
}
1364+
};
13581365

13591366
// Handle prompts in a loop
13601367
if let Some(bootstrap) = bootstrap {
@@ -1463,7 +1470,10 @@ impl AcpAgent {
14631470
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
14641471
let bootstrap = Self::create_or_resume_session(conn, resume_session_id, cwd).await?;
14651472

1466-
info!("Session ready for agent {}: {}", agent_id, bootstrap.session_id);
1473+
info!(
1474+
"Session ready for agent {}: {}",
1475+
agent_id, bootstrap.session_id
1476+
);
14671477
Ok(bootstrap)
14681478
}
14691479

@@ -1481,9 +1491,7 @@ impl AcpAgent {
14811491
} => {
14821492
error!(
14831493
"Failed to restore ACP session {}. load_session error: {}. resume_session error: {}. Falling back to creating a new session.",
1484-
resume_session_id,
1485-
load_err,
1486-
resume_err
1494+
resume_session_id, load_err, resume_err
14871495
);
14881496
}
14891497
}
@@ -1671,7 +1679,9 @@ impl AcpAgent {
16711679

16721680
Ok(SessionConfigSelectors {
16731681
model_selector: Self::parse_model_selector(Some(response.config_options.as_slice())),
1674-
reasoning_selector: Self::parse_reasoning_selector(Some(response.config_options.as_slice())),
1682+
reasoning_selector: Self::parse_reasoning_selector(Some(
1683+
response.config_options.as_slice(),
1684+
)),
16751685
})
16761686
}
16771687

@@ -2053,10 +2063,7 @@ impl AcpAgent {
20532063
Ok(())
20542064
}
20552065

2056-
pub async fn set_session_config_option(
2057-
&self,
2058-
option: AcpSelectOption,
2059-
) -> Result<()> {
2066+
pub async fn set_session_config_option(&self, option: AcpSelectOption) -> Result<()> {
20602067
let config_tx = self
20612068
.config_sender
20622069
.as_ref()

anycode-backend/src/acp_fs.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::sync::Arc;
55
use anyhow::{Context, Result};
66
use serde_json::json;
77
use socketioxide::SocketIo;
8-
use tokio::sync::{mpsc, oneshot, Mutex};
8+
use tokio::sync::{Mutex, mpsc, oneshot};
99
use tracing::{error, info};
1010

1111
use crate::code::Code;
@@ -78,11 +78,10 @@ async fn handle_write(
7878
content: &str,
7979
file2code: &Arc<Mutex<HashMap<String, Code>>>,
8080
lsp_manager: &Arc<Mutex<LspManager>>,
81-
config: &Config,
81+
_config: &Config,
8282
io: &Arc<SocketIo>,
8383
) -> Result<()> {
84-
let abs_path = abs_file(&path.to_string_lossy())
85-
.context("Failed to resolve absolute path")?;
84+
let abs_path = abs_file(&path.to_string_lossy()).context("Failed to resolve absolute path")?;
8685

8786
// Lock file2code and check if file is open
8887
let mut f2c = file2code.lock().await;
@@ -184,8 +183,7 @@ async fn handle_read(
184183
path: &PathBuf,
185184
file2code: &Arc<Mutex<HashMap<String, Code>>>,
186185
) -> Result<String> {
187-
let abs_path = abs_file(&path.to_string_lossy())
188-
.context("Failed to resolve absolute path")?;
186+
let abs_path = abs_file(&path.to_string_lossy()).context("Failed to resolve absolute path")?;
189187

190188
// Check if file is open in file2code
191189
let f2c = file2code.lock().await;

anycode-backend/src/acp_history.rs

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use anyhow::{anyhow, Context, Result};
2-
use git2::{build::CheckoutBuilder, IndexAddOption, Repository, ResetType, Signature};
1+
use anyhow::{Context, Result, anyhow};
2+
use git2::{IndexAddOption, Repository, ResetType, Signature, build::CheckoutBuilder};
33
use std::path::{Path, PathBuf};
4-
use tracing::{info, warn, debug};
4+
use tracing::{debug, info, warn};
55

66
/// Checkpoint representing a state before a user message
77
#[derive(Debug, Clone)]
@@ -93,8 +93,7 @@ impl AcpHistoryManager {
9393
}
9494

9595
// Create history directory if needed
96-
std::fs::create_dir_all(&self.history_dir)
97-
.context("Failed to create history directory")?;
96+
std::fs::create_dir_all(&self.history_dir).context("Failed to create history directory")?;
9897

9998
self.sync_shadow_gitignore()?;
10099

@@ -110,7 +109,10 @@ impl AcpHistoryManager {
110109
}
111110
Err(e) => {
112111
// Repository is corrupted or invalid, reinitialize
113-
warn!("Existing repository invalid ({}), removing and reinitializing", e);
112+
warn!(
113+
"Existing repository invalid ({}), removing and reinitializing",
114+
e
115+
);
114116
std::fs::remove_dir_all(&git_dir)
115117
.context("Failed to remove corrupted git directory")?;
116118
self.initialize_new_repo()?;
@@ -127,11 +129,14 @@ impl AcpHistoryManager {
127129

128130
/// Initialize a new git repository
129131
fn initialize_new_repo(&self) -> Result<()> {
130-
info!("Initializing new history repository at {:?}", self.git_dir());
132+
info!(
133+
"Initializing new history repository at {:?}",
134+
self.git_dir()
135+
);
131136

132137
// Initialize repository at git_dir
133-
let _repo = Repository::init(&self.git_dir())
134-
.context("Failed to initialize git repository")?;
138+
let _repo =
139+
Repository::init(&self.git_dir()).context("Failed to initialize git repository")?;
135140

136141
// Now set the workdir to the project root
137142
let repo = self.open_repo()?;
@@ -146,8 +151,7 @@ impl AcpHistoryManager {
146151

147152
/// Open the repository with workdir set to project root
148153
fn open_repo(&self) -> Result<Repository> {
149-
let repo = Repository::open(&self.git_dir())
150-
.context("Failed to open git repository")?;
154+
let repo = Repository::open(&self.git_dir()).context("Failed to open git repository")?;
151155

152156
// Set workdir to project root
153157
repo.set_workdir(&self.project_root, false)
@@ -167,7 +171,11 @@ impl AcpHistoryManager {
167171
IndexAddOption::DEFAULT,
168172
Some(&mut |path, _matched_spec| {
169173
let path_str = path.to_string_lossy();
170-
if path_str.starts_with(".anycode/") { 1 } else { 0 }
174+
if path_str.starts_with(".anycode/") {
175+
1
176+
} else {
177+
0
178+
}
171179
}),
172180
)?;
173181
index.write()?;
@@ -213,10 +221,8 @@ impl AcpHistoryManager {
213221
checkpoints.push(Checkpoint {
214222
commit_hash: oid.to_string(),
215223
prompt: prompt.clone(),
216-
created_at: chrono::DateTime::from_timestamp(
217-
commit.time().seconds(),
218-
0
219-
).unwrap_or_else(chrono::Utc::now),
224+
created_at: chrono::DateTime::from_timestamp(commit.time().seconds(), 0)
225+
.unwrap_or_else(chrono::Utc::now),
220226
});
221227
}
222228
}
@@ -246,7 +252,11 @@ impl AcpHistoryManager {
246252
IndexAddOption::DEFAULT,
247253
Some(&mut |path, _matched_spec| {
248254
let path_str = path.to_string_lossy();
249-
if path_str.starts_with(".anycode/") { 1 } else { 0 }
255+
if path_str.starts_with(".anycode/") {
256+
1
257+
} else {
258+
0
259+
}
250260
}),
251261
)?;
252262

@@ -268,14 +278,8 @@ impl AcpHistoryManager {
268278
let sig = Signature::now("AnyCode", "anycode@local")?;
269279
let commit_message = format!("checkpoint:{}", prompt);
270280

271-
let commit_id = repo.commit(
272-
Some("HEAD"),
273-
&sig,
274-
&sig,
275-
&commit_message,
276-
&tree,
277-
&[&head],
278-
)?;
281+
let commit_id =
282+
repo.commit(Some("HEAD"), &sig, &sig, &commit_message, &tree, &[&head])?;
279283

280284
commit_id.to_string()
281285
};
@@ -303,17 +307,19 @@ impl AcpHistoryManager {
303307
pub fn restore_to_commit(&self, commit_hash: &str) -> Result<()> {
304308
let repo = self.open_repo()?;
305309

306-
let oid = git2::Oid::from_str(commit_hash)
307-
.context("Invalid commit hash")?;
308-
let commit = repo.find_commit(oid)
309-
.context("Commit not found")?;
310+
let oid = git2::Oid::from_str(commit_hash).context("Invalid commit hash")?;
311+
let commit = repo.find_commit(oid).context("Commit not found")?;
310312

311313
// Hard reset to the target commit
312314
let mut checkout_opts = CheckoutBuilder::new();
313315
checkout_opts.force();
314316

315-
repo.reset(commit.as_object(), ResetType::Hard, Some(&mut checkout_opts))
316-
.context("Failed to reset to checkpoint")?;
317+
repo.reset(
318+
commit.as_object(),
319+
ResetType::Hard,
320+
Some(&mut checkout_opts),
321+
)
322+
.context("Failed to reset to checkpoint")?;
317323

318324
info!("Restored project to commit {}", commit_hash);
319325

0 commit comments

Comments
 (0)