Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 6 additions & 3 deletions src/agent/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -944,9 +944,11 @@ impl Channel {
"/quiet" | "/observe" => {
self.set_response_mode(ResponseMode::Observe).await;
self.send_builtin_text(
"observe mode enabled. i'll learn from this conversation but won't respond.".to_string(),
"observe mode enabled. i'll learn from this conversation but won't respond."
.to_string(),
"observe",
).await;
)
.await;
return Ok(true);
}
"/active" => {
Expand Down Expand Up @@ -976,7 +978,8 @@ impl Channel {
"- /tasks: ready task list".to_string(),
"- /digest: one-shot day digest (00:00 -> now)".to_string(),
"- /observe: learn from conversation, never respond".to_string(),
"- /mention-only: only respond when @mentioned, replied to, or given a command".to_string(),
"- /mention-only: only respond when @mentioned, replied to, or given a command"
.to_string(),
"- /active: normal reply mode".to_string(),
"- /agent-id: runtime agent id".to_string(),
];
Expand Down
4 changes: 3 additions & 1 deletion src/config/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ fn parse_response_mode(
// Backwards compat: listen_only_mode maps to response_mode
match listen_only_mode {
Some(true) => {
tracing::warn!("listen_only_mode is deprecated, use response_mode = \"observe\" instead");
tracing::warn!(
"listen_only_mode is deprecated, use response_mode = \"observe\" instead"
);
Some(ResponseMode::Observe)
}
Some(false) => Some(ResponseMode::Active),
Expand Down
26 changes: 20 additions & 6 deletions src/llm/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2348,26 +2348,40 @@ fn parse_streamed_tool_arguments(
return Ok(serde_json::json!({}));
}

let direct_parse_error = match serde_json::from_str::<serde_json::Value>(raw_arguments) {
Ok(arguments) => return Ok(arguments),
Err(error) => error,
let mut iter =
serde_json::Deserializer::from_str(raw_arguments).into_iter::<serde_json::Value>();
let direct_parse_error = match iter.next() {
Some(Ok(arguments)) => return Ok(arguments),
Some(Err(error)) => error,
None => {
return Err(CompletionError::ProviderError(format!(
"invalid streamed tool arguments for '{tool_name}': empty JSON stream"
)));
}
};

let sanitized_arguments = escape_control_characters_in_json_strings(raw_arguments);
if sanitized_arguments != raw_arguments {
match serde_json::from_str::<serde_json::Value>(&sanitized_arguments) {
Ok(arguments) => {
let mut sanitized_iter = serde_json::Deserializer::from_str(&sanitized_arguments)
.into_iter::<serde_json::Value>();
match sanitized_iter.next() {
Some(Ok(arguments)) => {
tracing::warn!(
tool_name,
"normalized control characters in streamed tool arguments"
);
return Ok(arguments);
}
Err(sanitized_parse_error) => {
Some(Err(sanitized_parse_error)) => {
return Err(CompletionError::ProviderError(format!(
"invalid streamed tool arguments for '{tool_name}': {direct_parse_error}; after sanitization: {sanitized_parse_error}"
)));
}
None => {
return Err(CompletionError::ProviderError(format!(
"invalid streamed tool arguments for '{tool_name}': {direct_parse_error}; after sanitization: empty JSON stream"
)));
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/projects/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ pub async fn remove_worktree(repo_path: &Path, worktree_path: &Path) -> anyhow::
.context("worktree path is not valid UTF-8")?;

let output = Command::new("git")
.args(["worktree", "remove", worktree_str])
.args(["worktree", "remove", "--force", worktree_str])
.current_dir(repo_path)
.output()
.await
Expand Down
10 changes: 6 additions & 4 deletions src/tools/project_manage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,9 +640,8 @@ impl ProjectManageTool {
let repo_abs_path = root.join(&repo.path);
let is_single_repo = repo.path == ".";

// For single-repo projects, place worktrees in the parent directory
// (as siblings of the repo). For multi-repo projects, place them
// inside the project root.
// For multi-repo projects, place them inside the project root,
// prefixed with `.worktrees/repo_name-worktree_name` to avoid conflicts.
let (worktree_abs_path, worktree_db_path) = if is_single_repo {
let parent = root.parent().ok_or_else(|| {
ProjectManageError("single-repo project root has no parent directory".into())
Expand All @@ -652,7 +651,10 @@ impl ProjectManageTool {
format!("../{worktree_dir_name}"),
)
} else {
(root.join(&worktree_dir_name), worktree_dir_name.clone())
// Include repo name in the worktree directory name to avoid conflicts
// with other repos or their worktrees in a multi-repo project.
let dir_name = format!("{}-{}", repo.name, worktree_dir_name);
(root.join(&dir_name), dir_name)
};

// Create the git worktree (branch from HEAD of the repo)
Expand Down