Improve agent prompting and classifier latency; clean up build warnings

This commit is contained in:
2026-09-15 14:25:00 -05:00
parent 66b73b1aac
commit 76d492aa75
69 changed files with 368 additions and 205 deletions
+3
View File
@@ -430,6 +430,9 @@ debug = 1
split-debuginfo = "packed" split-debuginfo = "packed"
[profile.dev] [profile.dev]
# Optimize the large debug binary to keep its unwind tables within macOS's
# compact-unwind offset limit, while retaining debug assertions and symbols.
opt-level = 2
# Use line-tables-only to speed up dev builds. Change to `debug = true` if you # Use line-tables-only to speed up dev builds. Change to `debug = true` if you
# need full debuginfo for step-through debugging (gdb/lldb). # need full debuginfo for step-through debugging (gdb/lldb).
debug = "line-tables-only" debug = "line-tables-only"
@@ -75,6 +75,7 @@ pub fn convert_conversation_data_to_ai_conversation(
agent_backend: Default::default(), agent_backend: Default::default(),
active_provider_run_json: None, active_provider_run_json: None,
local_agent_groups: Vec::new(), local_agent_groups: Vec::new(),
tool_titles: Default::default(),
server_conversation_token: None, server_conversation_token: None,
conversation_usage_metadata: usage_metadata, conversation_usage_metadata: usage_metadata,
reverted_action_ids: None, reverted_action_ids: None,
@@ -100,6 +101,7 @@ pub fn convert_conversation_data_to_ai_conversation(
agent_backend: Default::default(), agent_backend: Default::default(),
active_provider_run_json: None, active_provider_run_json: None,
local_agent_groups: Vec::new(), local_agent_groups: Vec::new(),
tool_titles: Default::default(),
server_conversation_token: Some( server_conversation_token: Some(
metadata.server_conversation_token.as_str().to_string(), metadata.server_conversation_token.as_str().to_string(),
), ),
+16
View File
@@ -234,6 +234,7 @@ pub struct AIConversation {
/// Opaque, versioned snapshot of the active direct-provider run. /// Opaque, versioned snapshot of the active direct-provider run.
active_provider_run_json: Option<String>, active_provider_run_json: Option<String>,
local_agent_groups: Vec<LocalAgentGroup>, local_agent_groups: Vec<LocalAgentGroup>,
tool_titles: HashMap<String, String>,
/// The server-generated unique "token" for this conversation. /// The server-generated unique "token" for this conversation.
/// ///
@@ -389,6 +390,7 @@ impl AIConversation {
agent_backend, agent_backend,
active_provider_run_json: None, active_provider_run_json: None,
local_agent_groups: Vec::new(), local_agent_groups: Vec::new(),
tool_titles: HashMap::new(),
server_conversation_token: None, server_conversation_token: None,
task_id: None, task_id: None,
forked_from_server_conversation_token: None, forked_from_server_conversation_token: None,
@@ -547,6 +549,10 @@ impl AIConversation {
(task_store, todo_lists, status) (task_store, todo_lists, status)
}; };
let tool_titles = conversation_data
.as_ref()
.map(|data| data.tool_titles.clone())
.unwrap_or_default();
let local_agent_groups = conversation_data let local_agent_groups = conversation_data
.as_ref() .as_ref()
.map(|data| data.local_agent_groups.clone()) .map(|data| data.local_agent_groups.clone())
@@ -682,6 +688,7 @@ impl AIConversation {
agent_backend, agent_backend,
active_provider_run_json, active_provider_run_json,
local_agent_groups, local_agent_groups,
tool_titles,
server_conversation_token, server_conversation_token,
task_id: run_id.as_deref().and_then(|id| id.parse().ok()), task_id: run_id.as_deref().and_then(|id| id.parse().ok()),
forked_from_server_conversation_token, forked_from_server_conversation_token,
@@ -725,6 +732,14 @@ impl AIConversation {
&self.agent_backend &self.agent_backend
} }
pub(crate) fn tool_titles(&self) -> &HashMap<String, String> {
&self.tool_titles
}
pub(crate) fn set_tool_title(&mut self, action_id: &AIAgentActionId, title: String) {
self.tool_titles.insert(action_id.to_string(), title);
}
pub(crate) fn local_agent_groups(&self) -> &[LocalAgentGroup] { pub(crate) fn local_agent_groups(&self) -> &[LocalAgentGroup] {
&self.local_agent_groups &self.local_agent_groups
} }
@@ -4050,6 +4065,7 @@ impl AIConversation {
agent_backend: self.agent_backend.clone(), agent_backend: self.agent_backend.clone(),
active_provider_run_json: self.active_provider_run_json.clone(), active_provider_run_json: self.active_provider_run_json.clone(),
local_agent_groups: self.local_agent_groups.clone(), local_agent_groups: self.local_agent_groups.clone(),
tool_titles: self.tool_titles.clone(),
server_conversation_token: self server_conversation_token: self
.server_conversation_token .server_conversation_token
.clone() .clone()
+1 -1
View File
@@ -80,7 +80,7 @@ pub fn compute_active_task_ids<'a>(
/// missing task are assigned depth 0. /// missing task are assigned depth 0.
pub fn compute_task_depths(tasks: &HashMap<String, api::Task>) -> HashMap<&str, usize> { pub fn compute_task_depths(tasks: &HashMap<String, api::Task>) -> HashMap<&str, usize> {
let mut depths = HashMap::new(); let mut depths = HashMap::new();
for (task_id, _) in tasks.iter() { for task_id in tasks.keys() {
let mut depth = 0; let mut depth = 0;
let mut current_id: &str = task_id; let mut current_id: &str = task_id;
let mut visited = HashSet::new(); let mut visited = HashSet::new();
+1 -4
View File
@@ -1211,10 +1211,7 @@ impl<'a> std::fmt::Display for MarkdownActionResult<'a> {
match self.0 { match self.0 {
AIAgentActionResultType::RequestCommandOutput(result) => match result { AIAgentActionResultType::RequestCommandOutput(result) => match result {
RequestCommandOutputResult::Completed { RequestCommandOutputResult::Completed {
command, command, output, ..
output,
exit_code: _,
..
} => { } => {
write!( write!(
f, f,
@@ -504,7 +504,7 @@ impl SecretRedactionState {
// the case where a secret is split across streaming chunks. // the case where a secret is split across streaming chunks.
let combined_text = format!( let combined_text = format!(
"{}{}", "{}{}",
&self.last_word_to_rescan_for_redaction, self.last_word_to_rescan_for_redaction,
&text[self.last_scanned_secret_redaction_byte_index..] &text[self.last_scanned_secret_redaction_byte_index..]
); );
let secret_ranges_with_levels = let secret_ranges_with_levels =
+2 -8
View File
@@ -729,14 +729,8 @@ impl BlocklistAIContextModel {
) -> Option<&'a AIAgentTodoList> { ) -> Option<&'a AIAgentTodoList> {
self.selected_conversation(ctx) self.selected_conversation(ctx)
.and_then(|c| c.active_todo_list()) .and_then(|c| c.active_todo_list())
.and_then(|todo_list| { // Don't show todo list if it's empty or finished
// Don't show todo list if it's empty or finished .filter(|&todo_list| !(todo_list.is_empty() || todo_list.is_finished()))
if todo_list.is_empty() || todo_list.is_finished() {
None
} else {
Some(todo_list)
}
})
} }
pub fn pending_query_autoexecute_override( pub fn pending_query_autoexecute_override(
+18 -1
View File
@@ -122,7 +122,8 @@ const PROGRESSIVE_SUMMARY_PROMPT: &str = "Create a concise, factual task-state c
- Decisions made and the rationale needed to continue\n\ - Decisions made and the rationale needed to continue\n\
- Files changed and the material result of each change\n\ - Files changed and the material result of each change\n\
- Significant tool outcomes, errors, identifiers, and unresolved blockers\n\ - Significant tool outcomes, errors, identifiers, and unresolved blockers\n\
- Current progress and the next concrete steps\n\n\ - Current progress and the next concrete steps; distinguish completed work from intentions\n\
- Delegated scope, concrete findings with file paths or tool-result IDs, and the exact remaining uncertainty\n\n\
Omit greetings, agreement, praise, reassurance, filler, routine status narration, repeated content, and stylistic assistant phrasing. Summarize completed tool work by outcome instead of listing every call. Do not quote prior assistant wording unless its exact text is task-critical. Return only the checkpoint, structured for another agent to resume the work."; Omit greetings, agreement, praise, reassurance, filler, routine status narration, repeated content, and stylistic assistant phrasing. Summarize completed tool work by outcome instead of listing every call. Do not quote prior assistant wording unless its exact text is task-critical. Return only the checkpoint, structured for another agent to resume the work.";
#[derive(Clone)] #[derive(Clone)]
@@ -7634,6 +7635,15 @@ impl BlocklistAIController {
else { else {
return; return;
}; };
let tool_titles = batch
.calls
.iter()
.filter_map(|pending| {
run.action_context
.tool_call_title(&pending.call)
.map(|title| (pending.call.id.clone(), title))
})
.collect::<HashMap<_, _>>();
let (mut converted_actions, invalid_results) = let (mut converted_actions, invalid_results) =
convert_provider_tool_batch(&run.action_context, &batch); convert_provider_tool_batch(&run.action_context, &batch);
for result in &invalid_results { for result in &invalid_results {
@@ -7742,6 +7752,13 @@ impl BlocklistAIController {
} else { } else {
let apply_result = let apply_result =
BlocklistAIHistoryModel::handle(ctx).update(ctx, |history_model, ctx| { BlocklistAIHistoryModel::handle(ctx).update(ctx, |history_model, ctx| {
if let Some(title) = tool_titles.get(&action.id.to_string()) {
if let Some(conversation) =
history_model.conversation_mut(&conversation_id)
{
conversation.set_tool_title(&action.id, title.clone());
}
}
history_model.apply_domain_tool_proposal( history_model.apply_domain_tool_proposal(
&stream_id, &stream_id,
conversation_id, conversation_id,
+3
View File
@@ -1705,6 +1705,7 @@ impl BlocklistAIHistoryModel {
agent_backend: source_conversation.agent_backend().for_fork(), agent_backend: source_conversation.agent_backend().for_fork(),
active_provider_run_json: None, active_provider_run_json: None,
local_agent_groups: Vec::new(), local_agent_groups: Vec::new(),
tool_titles: source_conversation.tool_titles().clone(),
server_conversation_token: None, server_conversation_token: None,
conversation_usage_metadata: Some(source_conversation.usage_metadata()), conversation_usage_metadata: Some(source_conversation.usage_metadata()),
reverted_action_ids, reverted_action_ids,
@@ -1871,6 +1872,7 @@ impl BlocklistAIHistoryModel {
agent_backend: conversation.agent_backend().for_fork(), agent_backend: conversation.agent_backend().for_fork(),
active_provider_run_json: None, active_provider_run_json: None,
local_agent_groups: Vec::new(), local_agent_groups: Vec::new(),
tool_titles: conversation.tool_titles().clone(),
server_conversation_token: None, server_conversation_token: None,
conversation_usage_metadata: None, conversation_usage_metadata: None,
reverted_action_ids, reverted_action_ids,
@@ -2882,6 +2884,7 @@ fn merged_remote_child_placeholder_conversation_data(
// Active process-local provider runs cannot be merged from a cloud transcript. // Active process-local provider runs cannot be merged from a cloud transcript.
active_provider_run_json: None, active_provider_run_json: None,
local_agent_groups: Vec::new(), local_agent_groups: Vec::new(),
tool_titles: Default::default(),
// Cloud authoritative. // Cloud authoritative.
server_conversation_token: cloud_conversation server_conversation_token: cloud_conversation
@@ -2118,15 +2118,15 @@ impl CodeDiffView {
} }
fn position_id_for_file(&self, i: usize) -> String { fn position_id_for_file(&self, i: usize) -> String {
format!("CodeDiffView-{}-tab-{i}", &self.position_id_prefix) format!("CodeDiffView-{}-tab-{i}", self.position_id_prefix)
} }
fn position_id_for_inline_editor(&self) -> String { fn position_id_for_inline_editor(&self) -> String {
format!("CodeDiffView-inline-editor-{}", &self.position_id_prefix) format!("CodeDiffView-inline-editor-{}", self.position_id_prefix)
} }
fn position_id_for_inline_speedbump(&self) -> String { fn position_id_for_inline_speedbump(&self) -> String {
format!("CodeDiffView-inline-speedbump-{}", &self.position_id_prefix) format!("CodeDiffView-inline-speedbump-{}", self.position_id_prefix)
} }
fn position_id_for_accept_split_button(&self) -> String { fn position_id_for_accept_split_button(&self) -> String {
@@ -139,7 +139,7 @@ impl SuggestedUnitTestsView {
fn position_id_for_speedbump(&self) -> String { fn position_id_for_speedbump(&self) -> String {
format!( format!(
"SuggestedUnitTestsView-speedbump-{}", "SuggestedUnitTestsView-speedbump-{}",
&self.position_id_prefix self.position_id_prefix
) )
} }
+1 -1
View File
@@ -799,7 +799,7 @@ impl BlocklistAIInputModel {
if use_local_model { if use_local_model {
// A cancelled debounce never starts inference. The local worker // A cancelled debounce never starts inference. The local worker
// also cancels obsolete requests already queued or generating. // also cancels obsolete requests already queued or generating.
warpui::r#async::Timer::after(std::time::Duration::from_millis(300)).await; warpui::r#async::Timer::after(std::time::Duration::from_millis(100)).await;
let result = crate::local_inference::classify( let result = crate::local_inference::classify(
buffer_cloned.to_string(), buffer_cloned.to_string(),
history_entries.as_deref().unwrap_or_default(), history_entries.as_deref().unwrap_or_default(),
@@ -365,8 +365,7 @@ impl PassiveSuggestionsModel {
#[cfg(feature = "local_fs")] #[cfg(feature = "local_fs")]
if is_passive_code_diffs_enabled { if is_passive_code_diffs_enabled {
if let Some(current_working_directory) = block_completed.serialized_block.pwd.clone() { if let Some(current_working_directory) = block_completed.serialized_block.pwd.clone() {
let block_contents = let block_contents = format!("{}\n{}", block_context.command, block_context.output);
format!("{}\n{}", &block_context.command, &block_context.output);
let shell = self.active_session.as_ref(ctx).shell_launch_data(ctx); let shell = self.active_session.as_ref(ctx).shell_launch_data(ctx);
let shell_for_detection = shell.clone(); let shell_for_detection = shell.clone();
let current_working_directory_for_detection = current_working_directory.clone(); let current_working_directory_for_detection = current_working_directory.clone();
+1 -1
View File
@@ -1429,7 +1429,7 @@ impl AIExecutionProfilesModel {
// We don't want stale client ids in our map. We won't be able to find the backing cloud object when // We don't want stale client ids in our map. We won't be able to find the backing cloud object when
// an edit occurs. // an edit occurs.
pub fn replace_client_id_with_server_id(&mut self, server_id: SyncId, client_id: SyncId) { pub fn replace_client_id_with_server_id(&mut self, server_id: SyncId, client_id: SyncId) {
for (_, sync_id) in self.profile_id_to_sync_id.iter_mut() { for sync_id in self.profile_id_to_sync_id.values_mut() {
if *sync_id == client_id { if *sync_id == client_id {
*sync_id = server_id; *sync_id = server_id;
log::info!("Updated profile id mapping after creating a new execution profile"); log::info!("Updated profile id mapping after creating a new execution profile");
+20
View File
@@ -63,6 +63,26 @@ impl ProviderActionContext {
} }
} }
pub(crate) fn tool_call_title(&self, call: &ToolCall) -> Option<String> {
let parameter = match self.mcp_tool_aliases.get(&call.name) {
Some(target) => target.title_parameter.as_deref()?,
None => super::rig_request::TOOL_TITLE_PARAMETER,
};
call.arguments
.get(parameter)
.and_then(serde_json::Value::as_str)
.map(|title| {
title
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
.chars()
.take(120)
.collect::<String>()
})
.filter(|title| !title.is_empty())
}
pub(crate) fn action_from_tool_call(&self, call: &ToolCall) -> Result<AIAgentAction, String> { pub(crate) fn action_from_tool_call(&self, call: &ToolCall) -> Result<AIAgentAction, String> {
action_from_tool_call( action_from_tool_call(
&self.task_id, &self.task_id,
+39 -2
View File
@@ -123,6 +123,8 @@ pub(crate) fn add_orchestration_model_options(
pub(crate) struct MCPToolTarget { pub(crate) struct MCPToolTarget {
pub server_id: Option<Uuid>, pub server_id: Option<Uuid>,
pub name: String, pub name: String,
#[serde(default)]
pub title_parameter: Option<String>,
} }
pub(crate) fn prepare_rig_turn( pub(crate) fn prepare_rig_turn(
@@ -257,6 +259,12 @@ fn prepare_rig_turn_for_provider(
} }
RigRequestMode::Normal | RigRequestMode::Plan | RigRequestMode::Orchestrate => {} RigRequestMode::Normal | RigRequestMode::Plan | RigRequestMode::Orchestrate => {}
} }
for tool in &mut tools {
let parameter = add_tool_title_parameter(&mut tool.input_schema);
if let Some(target) = mcp_tool_aliases.get_mut(&tool.name) {
target.title_parameter = Some(parameter);
}
}
let system_prompt = build_system_prompt(&input, &tools, &global_rules, mode); let system_prompt = build_system_prompt(&input, &tools, &global_rules, mode);
let mut new_messages = input_messages(input, tool_results); let mut new_messages = input_messages(input, tool_results);
@@ -631,6 +639,7 @@ fn tool_definitions(
MCPToolTarget { MCPToolTarget {
server_id: Uuid::parse_str(&server.id).ok(), server_id: Uuid::parse_str(&server.id).ok(),
name: tool.name.to_string(), name: tool.name.to_string(),
title_parameter: None,
}, },
); );
tools.push(ToolDefinition { tools.push(ToolDefinition {
@@ -654,6 +663,7 @@ fn tool_definitions(
MCPToolTarget { MCPToolTarget {
server_id: None, server_id: None,
name: tool.name.to_string(), name: tool.name.to_string(),
title_parameter: None,
}, },
); );
tools.push(ToolDefinition { tools.push(ToolDefinition {
@@ -670,6 +680,33 @@ fn tool_definitions(
(tools, mcp_tool_aliases) (tools, mcp_tool_aliases)
} }
pub(crate) const TOOL_TITLE_PARAMETER: &str = "_galaxy_tool_title";
fn add_tool_title_parameter(schema: &mut serde_json::Value) -> String {
let properties = schema["properties"].as_object_mut();
let mut parameter = TOOL_TITLE_PARAMETER.to_string();
if let Some(properties) = properties {
while properties.contains_key(&parameter) {
parameter.push('_');
}
} else {
schema["properties"] = serde_json::json!({});
}
schema["properties"][&parameter] = serde_json::json!({
"type": "string",
"description": "Short pane title stating this call's specific purpose, e.g. Find cache invalidation paths. Use 3–10 words; no greetings, status filler, or secrets.",
"maxLength": 120
});
if !schema["required"].is_array() {
schema["required"] = serde_json::json!([]);
}
schema["required"]
.as_array_mut()
.unwrap()
.push(parameter.clone().into());
parameter
}
fn tools_are_inline_only(tools: &[ToolDefinition]) -> bool { fn tools_are_inline_only(tools: &[ToolDefinition]) -> bool {
tools.iter().all(|tool| tool.name == "recall_tool_history") tools.iter().all(|tool| tool.name == "recall_tool_history")
} }
@@ -726,10 +763,10 @@ fn build_system_prompt(
"You are Galaxy, a local-first software-engineering and terminal agent. Complete the user's task through inspection, implementation, and proportionate validation. Galaxy owns tool permissions and execution; use only the tools advertised in this request and treat every result as authoritative evidence.\n\n", "You are Galaxy, a local-first software-engineering and terminal agent. Complete the user's task through inspection, implementation, and proportionate validation. Galaxy owns tool permissions and execution; use only the tools advertised in this request and treat every result as authoritative evidence.\n\n",
); );
prompt.push_str( prompt.push_str(
"## Communication Style\nSpeak naturally, warmly, and directly, like a thoughtful collaborator working alongside the user. Default to short responses and expand only when complexity or the user's request warrants it. Acknowledge a correction once when useful; do not repeatedly agree that the user is right, praise them, or add generic reassurance. For nontrivial work, briefly tell the user what you are checking before the first tool call. Between dependent tool calls, add a concise update only when a result materially changes what you learned or what you will do next; ground it in specifics instead of generic activity narration. Do not narrate every routine read, repeat the plan, or end a turn with only a progress update when useful work can continue. In the final response, lead with the outcome and keep the handoff compact.\n\n", "## Communication Style\nSpeak naturally, warmly, and directly, like a thoughtful collaborator working alongside the user. Default to short responses and expand only when complexity or the user's request warrants it. Acknowledge a correction once when useful; do not repeatedly agree that the user is right, praise them, or add generic reassurance. For nontrivial work, briefly tell the user what you are checking once before the first tool call of a new user request. Tool results and compaction checkpoints continue the same request: resume the work without another greeting, acknowledgement, or restatement of the plan. Supply the short purpose title requested by each tool schema. Between dependent tool calls, add a concise update only when a result materially changes what you learned or what you will do next; ground it in specifics instead of generic activity narration. Do not narrate every routine read, repeat the plan, or end a turn with only a progress update when useful work can continue. In the final response, lead with the outcome and keep the handoff compact.\n\n",
); );
prompt.push_str( prompt.push_str(
"## Execution Contract\nContinue until the user's requested outcome is complete and validated. Do not stop at an intermediate analysis, plan, status update, or promise of future work, and do not ask the user to say \"continue\". After each tool result, choose and perform the next necessary step. Stop only when the request is fulfilled or a concrete blocker requires user input; identify that blocker explicitly.\n\n", "## Execution Contract\nBefore another read or search, identify the missing fact it will resolve. Reuse findings already present in the conversation. Once enough evidence exists for the requested change, implement it and validate; additional inspection should resolve a specific uncertainty. Batch independent reads within the tool limits and wait for results before dependent work. Continue until the user's requested outcome is complete and validated. Do not stop at an intermediate analysis, plan, status update, or promise of future work, and do not ask the user to say \"continue\". After each tool result, choose and perform the next necessary step. Stop only when the request is fulfilled or a concrete blocker requires user input; identify that blocker explicitly.\n\n",
); );
prompt.push_str( prompt.push_str(
"## Task Tracking\nUse task tracking only for substantial work with multiple concrete steps, after planning has established those steps or when the user provides an actionable plan. Skip it for simple requests, questions, and exploratory planning. Each task must name a specific change or deliverable with a clear completion condition. Do not create generic workflow items such as 'research the request', 'critique the plan', 'execute the plan', or 'verify the result'. Update tasks only when their actual work is complete; a tool call or model turn alone is not evidence of completion.\n\n", "## Task Tracking\nUse task tracking only for substantial work with multiple concrete steps, after planning has established those steps or when the user provides an actionable plan. Skip it for simple requests, questions, and exploratory planning. Each task must name a specific change or deliverable with a clear completion condition. Do not create generic workflow items such as 'research the request', 'critique the plan', 'execute the plan', or 'verify the result'. Update tasks only when their actual work is complete; a tool call or model turn alone is not evidence of completion.\n\n",
+22
View File
@@ -840,3 +840,25 @@ fn progressive_summary_is_provider_context_not_persistent_history() {
MessageContent::Text(text) if text == "Continue" MessageContent::Text(text) if text == "Continue"
)); ));
} }
#[test]
fn tool_title_schema_preserves_existing_parameters() {
let mut schema = serde_json::json!({
"type": "object",
"properties": {"_galaxy_tool_title": {"type": "integer"}},
"required": ["_galaxy_tool_title"],
"additionalProperties": false
});
let parameter = super::add_tool_title_parameter(&mut schema);
assert_eq!(parameter, "_galaxy_tool_title_");
assert_eq!(
schema["properties"]["_galaxy_tool_title"]["type"],
"integer"
);
assert_eq!(schema["properties"][&parameter]["type"], "string");
assert_eq!(
schema["required"],
serde_json::json!(["_galaxy_tool_title", "_galaxy_tool_title_"])
);
assert_eq!(schema["additionalProperties"], false);
}
+23 -1
View File
@@ -1,4 +1,6 @@
use super::prepare_provider_run; use galaxy_agent_core::ToolCall;
use super::{prepare_provider_run, ProviderActionContext};
use crate::ai::agent::api::RequestParams; use crate::ai::agent::api::RequestParams;
use crate::ai::llms::LLMId; use crate::ai::llms::LLMId;
use crate::ai::openai::client::OpenAIClientConfig; use crate::ai::openai::client::OpenAIClientConfig;
@@ -73,3 +75,23 @@ fn subscription_context_budget_uses_catalog_limit_and_clamps_stale_overrides() {
); );
} }
} }
#[test]
fn tool_titles_are_optional_and_normalized_for_display() {
let context = ProviderActionContext::new_for_test("task");
let mut call = ToolCall {
id: "call".to_string(),
name: "read_files".to_string(),
arguments: serde_json::json!({"_galaxy_tool_title": " Read\n cache configuration "}),
};
assert_eq!(
context.tool_call_title(&call).as_deref(),
Some("Read cache configuration")
);
call.arguments = serde_json::json!({"_galaxy_tool_title": "界".repeat(200)});
assert_eq!(context.tool_call_title(&call).unwrap().chars().count(), 120);
call.arguments = serde_json::json!({"_galaxy_tool_title": " "});
assert_eq!(context.tool_call_title(&call), None);
call.arguments = serde_json::json!({});
assert_eq!(context.tool_call_title(&call), None);
}
+7 -1
View File
@@ -35,7 +35,13 @@ pub(crate) fn action_from_tool_call(
AIAgentActionType::CallMCPTool { AIAgentActionType::CallMCPTool {
server_id: target.server_id, server_id: target.server_id,
name: target.name.clone(), name: target.name.clone(),
input: input.clone(), input: {
let mut arguments = input.clone();
if let Some(parameter) = &target.title_parameter {
arguments.as_object_mut().unwrap().remove(parameter);
}
arguments
},
} }
} else { } else {
match call.name.as_str() { match call.name.as_str() {
+30
View File
@@ -381,6 +381,7 @@ fn provider_safe_mcp_aliases_resolve_to_the_original_tool() {
MCPToolTarget { MCPToolTarget {
server_id: Some(server_id), server_id: Some(server_id),
name: "performance_analyze_insight".to_string(), name: "performance_analyze_insight".to_string(),
title_parameter: None,
}, },
)]); )]);
@@ -550,3 +551,32 @@ fn unknown_tools_are_rejected_before_the_permission_boundary() {
assert!(error.contains("unsupported Rig tool proposal")); assert!(error.contains("unsupported Rig tool proposal"));
} }
#[test]
fn mcp_title_metadata_does_not_change_tool_arguments() {
let alias = "mcp__echo";
let aliases = HashMap::from([(
alias.to_string(),
MCPToolTarget {
server_id: None,
name: "echo".to_string(),
title_parameter: Some("_galaxy_tool_title_".to_string()),
},
)]);
let arguments = serde_json::json!({
"message": "hello",
"_galaxy_tool_title": "a real server argument",
"_galaxy_tool_title_": "Check the echo service"
});
let action = action_from_tool_call(
"task-1",
&call(alias, arguments),
&SkillPathOrigin::Local,
&aliases,
)
.unwrap();
assert!(
matches!(action.action, AIAgentActionType::CallMCPTool { input, .. }
if input == serde_json::json!({"message": "hello", "_galaxy_tool_title": "a real server argument"}))
);
}
+6 -10
View File
@@ -172,7 +172,7 @@ pub(super) fn relaunch() -> Result<()> {
pub async fn cleanup(update_id: &str) { pub async fn cleanup(update_id: &str) {
let download_dir = get_download_dir(update_id); let download_dir = get_download_dir(update_id);
if download_dir.exists() { if download_dir.exists() {
log::info!("Cleaning up download dir {:?}", &download_dir); log::info!("Cleaning up download dir {:?}", download_dir);
if let Err(e) = async_fs::remove_dir_all(&download_dir).await { if let Err(e) = async_fs::remove_dir_all(&download_dir).await {
safe_error!( safe_error!(
safe: ("Error cleaning up download dir: {e:?}"), safe: ("Error cleaning up download dir: {e:?}"),
@@ -427,7 +427,7 @@ async fn apply_update(channel: Channel, version_info: &VersionInfo, update_id: &
); );
} }
log::info!("Setting installed version to {:?}", &version_info); log::info!("Setting installed version to {:?}", version_info);
log::info!("Applied update in {:?}", update_start.elapsed()); log::info!("Applied update in {:?}", update_start.elapsed());
Ok(()) Ok(())
@@ -524,11 +524,11 @@ async fn download_and_extract_binary(
log::info!( log::info!(
"Downloading update, version {} on channel {channel}", "Downloading update, version {} on channel {channel}",
&version_info.version, version_info.version,
); );
let download_dir = get_download_dir(update_id); let download_dir = get_download_dir(update_id);
log::info!("Creating download dir {:?}", &download_dir); log::info!("Creating download dir {:?}", download_dir);
async_fs::create_dir_all(&download_dir).await?; async_fs::create_dir_all(&download_dir).await?;
let dmg_path = download_dmg(&channel, version_info, update_id, client).await?; let dmg_path = download_dmg(&channel, version_info, update_id, client).await?;
@@ -608,11 +608,7 @@ const DMG_TIMEOUT_S: u64 = 600;
/// The temporary path for downloading the new dmg into. /// The temporary path for downloading the new dmg into.
fn dmg_path(channel: &Channel, version_info: &VersionInfo, update_id: &str) -> PathBuf { fn dmg_path(channel: &Channel, version_info: &VersionInfo, update_id: &str) -> PathBuf {
let mut dir = get_download_dir(update_id); let mut dir = get_download_dir(update_id);
let file_name = format!( let file_name = format!("{}.{}.dmg", version_info.version, app_name_prefix(*channel));
"{}.{}.dmg",
&version_info.version,
app_name_prefix(*channel)
);
dir.push(file_name); dir.push(file_name);
dir dir
} }
@@ -655,7 +651,7 @@ async fn download_dmg(
.await?; .await?;
file.sync_data().await?; file.sync_data().await?;
log::info!("Wrote DMG to tempfile at {:?}", &dmg_file); log::info!("Wrote DMG to tempfile at {:?}", dmg_file);
Ok(dmg_file) Ok(dmg_file)
} }
+1 -1
View File
@@ -568,7 +568,7 @@ impl DiffModel {
move |model, (change_mapping, deletion_mapping), ctx| { move |model, (change_mapping, deletion_mapping), ctx| {
model.status.change_mapping = change_mapping; model.status.change_mapping = change_mapping;
model.status.deletion_mapping = deletion_mapping; model.status.deletion_mapping = deletion_mapping;
log::debug!("diff status updated: {:#?}", &model.status); log::debug!("diff status updated: {:#?}", model.status);
ctx.emit(DiffModelEvent::DiffUpdated { ctx.emit(DiffModelEvent::DiffUpdated {
should_recalculate_hidden_lines, should_recalculate_hidden_lines,
version, version,
+2 -3
View File
@@ -572,10 +572,9 @@ impl GitBranchTrackingStatus {
rebased = true; rebased = true;
} else if let Some(ahead_count) = part.strip_prefix('↑') { } else if let Some(ahead_count) = part.strip_prefix('↑') {
ahead = Self::parse_display_count(ahead_count)?; ahead = Self::parse_display_count(ahead_count)?;
} else if let Some(behind_count) = part.strip_prefix('↓') {
behind = Self::parse_display_count(behind_count)?;
} else { } else {
return None; let behind_count = part.strip_prefix('↓')?;
behind = Self::parse_display_count(behind_count)?;
} }
} }
+1 -1
View File
@@ -520,7 +520,7 @@ fn make_forbidden_filenames_matcher() -> AhoCorasick {
// NTFS (Windows) disallows ASCII control characters in path names. // NTFS (Windows) disallows ASCII control characters in path names.
let ascii_control = 0x00..0x1f; let ascii_control = 0x00..0x1f;
// These characters are disallowed by UNIX filesystems, APFS or HFS+ (macOS), or NTFS. // These characters are disallowed by UNIX filesystems, APFS or HFS+ (macOS), or NTFS.
let forbidden = [b'/', b':', b'#', b'*', b'<', b'>', b'?', b'\\', b'|']; let forbidden = *b"/:#*<>?\\|";
let patterns = ascii_control.chain(forbidden).map(|ch| [ch]); let patterns = ascii_control.chain(forbidden).map(|ch| [ch]);
AhoCorasick::builder() AhoCorasick::builder()
+4 -6
View File
@@ -3388,7 +3388,8 @@ impl Iterator for CharsWithStyle<'_> {
} else { } else {
loop { loop {
self.fragments_cursor.prev(); self.fragments_cursor.prev();
if let Some(fragment) = self.fragments_cursor.item() { {
let fragment = self.fragments_cursor.item()?;
if fragment.is_visible(self.undo_history) { if fragment.is_visible(self.undo_history) {
self.fragment_offset = fragment.len(); self.fragment_offset = fragment.len();
self.fragment_chars = fragment.text.as_str().chars(); self.fragment_chars = fragment.text.as_str().chars();
@@ -3399,8 +3400,6 @@ impl Iterator for CharsWithStyle<'_> {
.next_back() .next_back()
.map(|char| StylizedChar::new(char, self.fragment_text_style)); .map(|char| StylizedChar::new(char, self.fragment_text_style));
} }
} else {
return None;
} }
} }
} }
@@ -3410,7 +3409,8 @@ impl Iterator for CharsWithStyle<'_> {
} else { } else {
loop { loop {
self.fragments_cursor.next(); self.fragments_cursor.next();
if let Some(fragment) = self.fragments_cursor.item() { {
let fragment = self.fragments_cursor.item()?;
if fragment.is_visible(self.undo_history) { if fragment.is_visible(self.undo_history) {
self.fragment_chars = fragment.text.as_str().chars(); self.fragment_chars = fragment.text.as_str().chars();
self.fragment_text_style = fragment.text.text_style().unwrap_or_default(); self.fragment_text_style = fragment.text.text_style().unwrap_or_default();
@@ -3420,8 +3420,6 @@ impl Iterator for CharsWithStyle<'_> {
.next() .next()
.map(|char| StylizedChar::new(char, self.fragment_text_style)); .map(|char| StylizedChar::new(char, self.fragment_text_style));
} }
} else {
return None;
} }
} }
} }
+3 -3
View File
@@ -184,14 +184,14 @@ impl SecretManager {
) -> ErrorMessageAndCommand { ) -> ErrorMessageAndCommand {
match error_type { match error_type {
SecretErrorType::NotInstalled => { SecretErrorType::NotInstalled => {
let message = format!("{} CLI is not installed", &self); let message = format!("{} CLI is not installed", self);
let (link, link_message) = ( let (link, link_message) = (
match self { match self {
SecretManager::OnePassword => Some(ONEPASSWORD_DOCS_LINK.to_owned()), SecretManager::OnePassword => Some(ONEPASSWORD_DOCS_LINK.to_owned()),
SecretManager::LastPass => Some(LASTPASS_DOCS_LINK.to_owned()), SecretManager::LastPass => Some(LASTPASS_DOCS_LINK.to_owned()),
}, },
Some(format!("View {} CLI installation documentation", &self)), Some(format!("View {} CLI installation documentation", self)),
); );
ErrorMessageAndCommand { ErrorMessageAndCommand {
@@ -211,7 +211,7 @@ impl SecretManager {
ErrorMessageAndCommand { ErrorMessageAndCommand {
message: format!( message: format!(
"{} didn't return secrets (likely not configured or authenticated)", "{} didn't return secrets (likely not configured or authenticated)",
&self self
), ),
link, link,
link_message, link_message,
@@ -275,7 +275,7 @@ pub fn output_conversation_debug_info(
for input in &exchange.input { for input in &exchange.input {
write_to_debug_file(&format!("\nInput:\n\n{input}\n")); write_to_debug_file(&format!("\nInput:\n\n{input}\n"));
} }
write_to_debug_file(&format!("Output:\n{}", &exchange.output_status)) write_to_debug_file(&format!("Output:\n{}", exchange.output_status))
} }
// Add completion timestamp // Add completion timestamp
+1 -1
View File
@@ -76,7 +76,7 @@ pub fn open_context_menu_for_selected_block() -> Vec<TestStep> {
/// with results. /// with results.
pub fn run_completer(tab_index: usize, input_text: impl Into<String>) -> TestStep { pub fn run_completer(tab_index: usize, input_text: impl Into<String>) -> TestStep {
let input_text = input_text.into(); let input_text = input_text.into();
new_step_with_default_assertions(&format!("Type {} and hit tab", &input_text)) new_step_with_default_assertions(&format!("Type {} and hit tab", input_text))
.with_typed_characters(&[&input_text]) .with_typed_characters(&[&input_text])
.with_keystrokes(&["tab"]) .with_keystrokes(&["tab"])
.set_timeout(Duration::from_secs(2)) .set_timeout(Duration::from_secs(2))
+1 -7
View File
@@ -187,13 +187,7 @@ impl NotebookEmbed {
cloud_model cloud_model
.get_by_uid(&id.to_server_id().uid()) .get_by_uid(&id.to_server_id().uid())
.and_then(|object| object.as_any().downcast_ref::<CloudWorkflow>()) .and_then(|object| object.as_any().downcast_ref::<CloudWorkflow>())
.and_then(|workflow| { .filter(|&workflow| !workflow.is_trashed(cloud_model))
if workflow.is_trashed(cloud_model) {
None
} else {
Some(workflow)
}
})
} }
pub fn start_offset(&self, ctx: &impl ModelAsRef) -> Option<CharOffset> { pub fn start_offset(&self, ctx: &impl ModelAsRef) -> Option<CharOffset> {
+1
View File
@@ -390,6 +390,7 @@ fn persisted_remote_child_conversation(
agent_backend: Default::default(), agent_backend: Default::default(),
active_provider_run_json: None, active_provider_run_json: None,
local_agent_groups: Vec::new(), local_agent_groups: Vec::new(),
tool_titles: Default::default(),
server_conversation_token: Some("restored-child-token".to_string()), server_conversation_token: Some("restored-child-token".to_string()),
conversation_usage_metadata: None, conversation_usage_metadata: None,
reverted_action_ids: None, reverted_action_ids: None,
+1 -1
View File
@@ -190,7 +190,7 @@ pub(super) fn get_all_restored_blocks(
}) })
.collect::<HashMap<_, Vec<SerializedBlockListItem>>>(); .collect::<HashMap<_, Vec<SerializedBlockListItem>>>();
for (_, blocks) in all_block_items_by_pane.iter_mut() { for blocks in all_block_items_by_pane.values_mut() {
blocks.sort_by_key(|item| item.start_ts()); blocks.sort_by_key(|item| item.start_ts());
// Only keep most recent command blocks // Only keep most recent command blocks
blocks.drain( blocks.drain(
+1 -2
View File
@@ -149,8 +149,7 @@ impl SearchItem for MatchedBinding {
format!( format!(
"Selected {}, {}.", "Selected {}, {}.",
&self self.binding
.binding
.description .description
.in_context(DescriptionContext::Default), .in_context(DescriptionContext::Default),
trigger.map(Keystroke::normalized).unwrap_or_default() trigger.map(Keystroke::normalized).unwrap_or_default()
@@ -161,7 +161,7 @@ impl SearchItem for CreateFileSearchItem {
let text_color = highlight_state.sub_text_fill(appearance).into_solid(); let text_color = highlight_state.sub_text_fill(appearance).into_solid();
let label = Text::new_inline( let label = Text::new_inline(
format!("Create a file named {}…", &self.file_name), format!("Create a file named {}…", self.file_name),
appearance.ui_font_family(), appearance.ui_font_family(),
appearance.monospace_font_size(), appearance.monospace_font_size(),
) )
@@ -94,6 +94,6 @@ impl SearchItem for ExternalSecretSearchItem {
} }
fn accessibility_label(&self) -> String { fn accessibility_label(&self) -> String {
format!("Secret: {}", &self.external_secret.get_display_name()) format!("Secret: {}", self.external_secret.get_display_name())
} }
} }
+3 -4
View File
@@ -423,12 +423,11 @@ impl FileSearchModel {
let mut all_matched_indices = Vec::new(); let mut all_matched_indices = Vec::new();
for query_part in &query_parts { for query_part in &query_parts {
if let Some(part_result) = Self::fuzzy_match_path_single(path, query_part) { {
// If any part doesn't match, the entire query fails
let part_result = Self::fuzzy_match_path_single(path, query_part)?;
combined_score += part_result.score; combined_score += part_result.score;
all_matched_indices.extend(part_result.matched_indices); all_matched_indices.extend(part_result.matched_indices);
} else {
// If any part doesn't match, the entire query fails
return None;
} }
} }
@@ -5667,7 +5667,7 @@ fn test_move_object_personal_to_team_success() {
assert!( assert!(
matches!(&events[0], ModelEvent::UpsertNotebook { notebook } if notebook.id == sync_id), matches!(&events[0], ModelEvent::UpsertNotebook { notebook } if notebook.id == sync_id),
"Expected upsert of notebook, got {:?}", "Expected upsert of notebook, got {:?}",
&events[0] events[0]
); );
}); });
} }
@@ -5870,7 +5870,7 @@ fn test_move_cloud_environment_personal_to_team_success() {
let events = db_events(&update_manager_struct); let events = db_events(&update_manager_struct);
assert_eq!(events.len(), 1); assert_eq!(events.len(), 1);
let ModelEvent::UpsertGenericStringObject { object } = &events[0] else { let ModelEvent::UpsertGenericStringObject { object } = &events[0] else {
panic!("Expected upsert of cloud environment, got {:?}", &events[0]) panic!("Expected upsert of cloud environment, got {:?}", events[0])
}; };
assert_eq!(object.id(), sync_id); assert_eq!(object.id(), sync_id);
}); });
@@ -5981,18 +5981,18 @@ fn test_move_workflow_with_enums_personal_to_team_success() {
let events = db_events(&update_manager_struct); let events = db_events(&update_manager_struct);
assert_eq!(events.len(), 3); assert_eq!(events.len(), 3);
let ModelEvent::UpsertGenericStringObject { object } = &events[0] else { let ModelEvent::UpsertGenericStringObject { object } = &events[0] else {
panic!("Expected upsert of workflow enum, got {:?}", &events[0]) panic!("Expected upsert of workflow enum, got {:?}", events[0])
}; };
let new_enum_id = object.id(); let new_enum_id = object.id();
assert!( assert!(
matches!(&events[1], ModelEvent::UpsertWorkflow { workflow } if workflow.id == workflow_sync_id), matches!(&events[1], ModelEvent::UpsertWorkflow { workflow } if workflow.id == workflow_sync_id),
"Expected upsert of workflow, got {:?}", "Expected upsert of workflow, got {:?}",
&events[0] events[0]
); );
assert!( assert!(
matches!(&events[2], ModelEvent::UpsertWorkflow { workflow } if workflow.id == workflow_sync_id), matches!(&events[2], ModelEvent::UpsertWorkflow { workflow } if workflow.id == workflow_sync_id),
"Expected upsert of workflow, got {:?}", "Expected upsert of workflow, got {:?}",
&events[2] events[2]
); );
// Assert that the new enum ID is in the team space, but the old one remains in the personal space // Assert that the new enum ID is in the team space, but the old one remains in the personal space
@@ -6133,18 +6133,18 @@ fn test_move_workflow_with_enums_personal_to_team_failure() {
assert!( assert!(
matches!(&events[0], ModelEvent::UpsertGenericStringObject { .. }), matches!(&events[0], ModelEvent::UpsertGenericStringObject { .. }),
"Expected upsert of GSO, got {:?}", "Expected upsert of GSO, got {:?}",
&events[0] events[0]
); );
assert!( assert!(
matches!(&events[1], ModelEvent::UpsertWorkflow { workflow } if workflow.id == workflow_sync_id), matches!(&events[1], ModelEvent::UpsertWorkflow { workflow } if workflow.id == workflow_sync_id),
"Expected upsert of workflow, got {:?}", "Expected upsert of workflow, got {:?}",
&events[0] events[0]
); );
assert!( assert!(
matches!(&events[2], ModelEvent::UpsertWorkflow { workflow } if workflow.id == workflow_sync_id), matches!(&events[2], ModelEvent::UpsertWorkflow { workflow } if workflow.id == workflow_sync_id),
"Expected upsert of workflow, got {:?}", "Expected upsert of workflow, got {:?}",
&events[2] events[2]
); );
// Assert that the workflow update references the old enum ID // Assert that the workflow update references the old enum ID
@@ -6253,7 +6253,7 @@ fn test_move_object_root_to_folder_success() {
assert!( assert!(
matches!(&events[0], ModelEvent::UpsertNotebook { notebook } if notebook.id == sync_id), matches!(&events[0], ModelEvent::UpsertNotebook { notebook } if notebook.id == sync_id),
"Expected upsert of notebook, got {:?}", "Expected upsert of notebook, got {:?}",
&events[0] events[0]
); );
}); });
} }
@@ -6445,7 +6445,7 @@ fn test_move_object_folder_to_root_success() {
assert!( assert!(
matches!(&events[0], ModelEvent::UpsertNotebook { notebook } if notebook.id == sync_id), matches!(&events[0], ModelEvent::UpsertNotebook { notebook } if notebook.id == sync_id),
"Expected upsert of notebook, got {:?}", "Expected upsert of notebook, got {:?}",
&events[0] events[0]
); );
}); });
} }
@@ -6639,7 +6639,7 @@ fn test_move_object_folder_to_folder_success() {
assert!( assert!(
matches!(&events[0], ModelEvent::UpsertNotebook { notebook } if notebook.id == sync_id), matches!(&events[0], ModelEvent::UpsertNotebook { notebook } if notebook.id == sync_id),
"Expected upsert of notebook, got {:?}", "Expected upsert of notebook, got {:?}",
&events[0] events[0]
); );
}); });
} }
@@ -7422,7 +7422,7 @@ fn test_add_guest_success() {
assert!( assert!(
matches!(&events[0], ModelEvent::UpsertNotebook { notebook } if notebook.id == sync_id), matches!(&events[0], ModelEvent::UpsertNotebook { notebook } if notebook.id == sync_id),
"Expected upsert of notebook, got {:?}", "Expected upsert of notebook, got {:?}",
&events[0] events[0]
); );
assert!(matches!(&events[1], ModelEvent::UpsertUserProfiles { .. })); assert!(matches!(&events[1], ModelEvent::UpsertUserProfiles { .. }));
}); });
+2 -2
View File
@@ -1121,7 +1121,7 @@ impl ServerApi {
} }
let time_endpoint = format!("{}/current_time", ChannelState::server_root_url()); let time_endpoint = format!("{}/current_time", ChannelState::server_root_url());
log::info!("Sending server time request to {}", &time_endpoint); log::info!("Sending server time request to {}", time_endpoint);
let res = self let res = self
.base_client .base_client
.http_client() .http_client()
@@ -1138,7 +1138,7 @@ impl ServerApi {
let time_response: TimeResponse = res.json().await?; let time_response: TimeResponse = res.json().await?;
log::info!( log::info!(
"Received current time from server: {:?}", "Received current time from server: {:?}",
&time_response.current_time time_response.current_time
); );
let server_time = ServerTime { let server_time = ServerTime {
time_at_fetch: time_response.current_time, time_at_fetch: time_response.current_time,
+1 -1
View File
@@ -1828,7 +1828,7 @@ impl SyncQueue {
/// If a request succeeds, update the dependency map by removing it from all dependency sets /// If a request succeeds, update the dependency map by removing it from all dependency sets
fn handle_dependency_success(&mut self, dependency: &QueueDependency) { fn handle_dependency_success(&mut self, dependency: &QueueDependency) {
for (_, dependencies) in self.queue_dependencies.iter_mut() { for dependencies in self.queue_dependencies.values_mut() {
dependencies.remove(dependency); dependencies.remove(dependency);
} }
} }
+2 -2
View File
@@ -2438,7 +2438,7 @@ impl FeaturesPageView {
editor.set_buffer_text( editor.set_buffer_text(
&format!( &format!(
"{}", "{}",
&KeysSettings::handle(ctx) KeysSettings::handle(ctx)
.as_ref(ctx) .as_ref(ctx)
.quake_mode_settings .quake_mode_settings
.value() .value()
@@ -2483,7 +2483,7 @@ impl FeaturesPageView {
editor.set_buffer_text( editor.set_buffer_text(
&format!( &format!(
"{}", "{}",
&KeysSettings::handle(ctx) KeysSettings::handle(ctx)
.as_ref(ctx) .as_ref(ctx)
.quake_mode_settings .quake_mode_settings
.value() .value()
+1 -1
View File
@@ -583,7 +583,7 @@ impl TerminalFindModel {
} else if let Some(controller) = &mut self.async_find_controller { } else if let Some(controller) = &mut self.async_find_controller {
controller.clear_results(ctx); controller.clear_results(ctx);
} else if let Some(run) = self.block_list_find_run.take() { } else if let Some(run) = self.block_list_find_run.take() {
for (_, rich_content_view) in self.rich_content_views.iter() { for rich_content_view in self.rich_content_views.values() {
rich_content_view.clear_matches(ctx); rich_content_view.clear_matches(ctx);
} }
self.block_list_find_run = Some(run.cleared()); self.block_list_find_run = Some(run.cleared());
+1 -1
View File
@@ -7898,7 +7898,7 @@ impl Input {
// Emit the a11y content as the last step so that it overwrites any of the a11y content // Emit the a11y content as the last step so that it overwrites any of the a11y content
// emitted by the editor (if multiple `AccessibilityContent`s are emitted within the same // emitted by the editor (if multiple `AccessibilityContent`s are emitted within the same
// event loop, the last one wins). // event loop, the last one wins).
let mut accessibility_text = format!("Workflow command {} inserted.", &command_to_insert); let mut accessibility_text = format!("Workflow command {} inserted.", command_to_insert);
if let Some(a11y_content) = self.selected_workflow_a11y_text(ctx) { if let Some(a11y_content) = self.selected_workflow_a11y_text(ctx) {
let _ = write!(accessibility_text, " {a11y_content}"); let _ = write!(accessibility_text, " {a11y_content}");
} }
+1 -1
View File
@@ -129,7 +129,7 @@ impl InlineConversationMenuView {
.as_ref(ctx) .as_ref(ctx)
.is_conversation_menu() .is_conversation_menu()
{ {
let InputBufferUpdateEvent { new_content: _, .. } = event; let InputBufferUpdateEvent { .. } = event;
me.rerun_query(ctx); me.rerun_query(ctx);
} }
}); });
@@ -679,7 +679,7 @@ impl InlineItem {
name: skill.name.clone(), name: skill.name.clone(),
}, },
icon_path: icon.into(), icon_path: icon.into(),
name: format!("/{}", &skill.name), name: format!("/{}", skill.name),
description: Some(skill.description.clone()), description: Some(skill.description.clone()),
font_family: appearance.monospace_font_family(), font_family: appearance.monospace_font_family(),
name_match_result: None, name_match_result: None,
+2 -3
View File
@@ -205,12 +205,11 @@ fn parse_number(input: &[u8]) -> Option<u8> {
let mut num: u8 = 0; let mut num: u8 = 0;
for c in input { for c in input {
let c = *c as char; let c = *c as char;
if let Some(digit) = c.to_digit(10) { {
let digit = c.to_digit(10)?;
num = num num = num
.checked_mul(10) .checked_mul(10)
.and_then(|v| v.checked_add(digit as u8))? .and_then(|v| v.checked_add(digit as u8))?
} else {
return None;
} }
} }
Some(num) Some(num)
+1 -1
View File
@@ -3107,7 +3107,7 @@ impl BlockList {
log::info!( log::info!(
"Incrementing stage from {:?} to {:?}", "Incrementing stage from {:?} to {:?}",
self.bootstrap_stage, self.bootstrap_stage,
&next_bootstrap_stage next_bootstrap_stage
); );
self.bootstrap_stage = next_bootstrap_stage; self.bootstrap_stage = next_bootstrap_stage;
} }
+69 -74
View File
@@ -202,6 +202,7 @@ impl BlockListSelection {
let head_is_below_tail = (self.head.point.row > self.tail.point.row let head_is_below_tail = (self.head.point.row > self.tail.point.row
&& !block_list.is_inverted) && !block_list.is_inverted)
|| (self.head.point.row < self.tail.point.row && block_list.is_inverted); || (self.head.point.row < self.tail.point.row && block_list.is_inverted);
// select-up and select-down do nothing when there's no rendered selection.
let (mut head, mut tail) = if head_is_below_tail { let (mut head, mut tail) = if head_is_below_tail {
(self.tail, self.head) (self.tail, self.head)
} else { } else {
@@ -478,40 +479,38 @@ impl BlockList {
) -> Option<BlockListPoint> { ) -> Option<BlockListPoint> {
let tail = match self.expand_selection(semantic_selection, inverted_blocklist) { let tail = match self.expand_selection(semantic_selection, inverted_blocklist) {
Some(selection) => selection.tail().within_grid_point, Some(selection) => selection.tail().within_grid_point,
None => match self.selection.take() { None => {
None => return None, let mut selection = self.selection.take()?;
Some(mut selection) => { // In this case, there's an underlying selection that spans an empty range
// In this case, there's an underlying selection that spans an empty range // and has no rendering.
// and has no rendering. //
// // The selection is in one of two formats:
// The selection is in one of two formats: // (row: x, col: y, side: R) --> (row: x, col: y + 1, side: L)
// (row: x, col: y, side: R) --> (row: x, col: y + 1, side: L) // (row: x, col: y + 1, side: L) --> (row: x, col: y, side: R)
// (row: x, col: y + 1, side: L) --> (row: x, col: y, side: R) // And we should update the selection to this:
// And we should update the selection to this: // (row: x, col: y, side: R) --> (row: x, col: y, side: L)
// (row: x, col: y, side: R) --> (row: x, col: y, side: L) let start_anchor = selection.start_anchor().point;
let start_anchor = selection.start_anchor().point;
selection.tail.point = start_anchor; selection.tail.point = start_anchor;
selection.tail.side = Side::Left; selection.tail.side = Side::Left;
selection.head.point = start_anchor; selection.head.point = start_anchor;
selection.head.side = Side::Right; selection.head.side = Side::Right;
self.set_selection(selection); self.set_selection(selection);
return Some(start_anchor); return Some(start_anchor);
} }
},
}; };
let mut selection = self.selection.take()?; let mut selection = self.selection.take()?;
let tail_point = tail.get(); let tail_point = tail.get();
let grid = match self.block_at(tail.block_index) { let grid = {
None => return None, let block = self.block_at(tail.block_index)?;
Some(block) => match tail.grid { match tail.grid {
GridType::Prompt => block.prompt_grid(), GridType::Prompt => block.prompt_grid(),
GridType::PromptAndCommand => block.prompt_and_command_grid(), GridType::PromptAndCommand => block.prompt_and_command_grid(),
GridType::Rprompt => block.rprompt_grid(), GridType::Rprompt => block.rprompt_grid(),
GridType::Output => block.output_grid(), GridType::Output => block.output_grid(),
}, }
}; };
// There are two cases: // There are two cases:
@@ -559,40 +558,38 @@ impl BlockList {
) -> Option<BlockListPoint> { ) -> Option<BlockListPoint> {
let tail = match self.expand_selection(semantic_selection, inverted_blocklist) { let tail = match self.expand_selection(semantic_selection, inverted_blocklist) {
Some(selection) => selection.tail().within_grid_point, Some(selection) => selection.tail().within_grid_point,
None => match self.selection.take() { None => {
None => return None, let mut selection = self.selection.take()?;
Some(mut selection) => { // In this case, there's an underlying selection that spans an empty range
// In this case, there's an underlying selection that spans an empty range // and has no rendering.
// and has no rendering. //
// // The selection is in one of two formats:
// The selection is in one of two formats: // (row: x, col: y, side: R) --> (row: x, col: y + 1, side: L)
// (row: x, col: y, side: R) --> (row: x, col: y + 1, side: L) // (row: x, col: y + 1, side: L) --> (row: x, col: y, side: R)
// (row: x, col: y + 1, side: L) --> (row: x, col: y, side: R) // And we should update the selection to this:
// And we should update the selection to this: // (row: x, col: y + 1, side: L) --> (row: x, col: y + 1, side: R)
// (row: x, col: y + 1, side: L) --> (row: x, col: y + 1, side: R) let end_anchor = selection.end_anchor().point;
let end_anchor = selection.end_anchor().point;
selection.head.point = end_anchor; selection.head.point = end_anchor;
selection.head.side = Side::Left; selection.head.side = Side::Left;
selection.tail.point = end_anchor; selection.tail.point = end_anchor;
selection.tail.side = Side::Right; selection.tail.side = Side::Right;
self.set_selection(selection); self.set_selection(selection);
return Some(end_anchor); return Some(end_anchor);
} }
},
}; };
let mut selection = self.selection.take()?; let mut selection = self.selection.take()?;
let tail_point = tail.get(); let tail_point = tail.get();
let grid = match self.block_at(tail.block_index) { let grid = {
None => return None, let block = self.block_at(tail.block_index)?;
Some(block) => match tail.grid { match tail.grid {
GridType::Prompt => block.prompt_grid(), GridType::Prompt => block.prompt_grid(),
GridType::PromptAndCommand => block.prompt_and_command_grid(), GridType::PromptAndCommand => block.prompt_and_command_grid(),
GridType::Rprompt => block.rprompt_grid(), GridType::Rprompt => block.rprompt_grid(),
GridType::Output => block.output_grid(), GridType::Output => block.output_grid(),
}, }
}; };
let num_rows_in_grid = grid.len_displayed(); let num_rows_in_grid = grid.len_displayed();
@@ -639,15 +636,14 @@ impl BlockList {
semantic_selection: &SemanticSelection, semantic_selection: &SemanticSelection,
inverted_blocklist: bool, inverted_blocklist: bool,
) -> Option<BlockListPoint> { ) -> Option<BlockListPoint> {
let (mut head, mut tail) = // select-up and select-down do nothing when there's no rendered selection.
match self.expand_selection(semantic_selection, inverted_blocklist) { let (mut head, mut tail) = {
Some(selection) => ( let selection = self.expand_selection(semantic_selection, inverted_blocklist)?;
selection.head().within_grid_point, (
selection.tail().within_grid_point, selection.head().within_grid_point,
), selection.tail().within_grid_point,
// select-up and select-down do nothing when there's no rendered selection. )
None => return None, };
};
let mut selection = self.selection.take()?; let mut selection = self.selection.take()?;
@@ -663,14 +659,14 @@ impl BlockList {
} }
let tail_point = tail.get(); let tail_point = tail.get();
let grid = match self.block_at(tail.block_index) { let grid = {
None => return None, let block = self.block_at(tail.block_index)?;
Some(block) => match tail.grid { match tail.grid {
GridType::Prompt => block.prompt_grid(), GridType::Prompt => block.prompt_grid(),
GridType::PromptAndCommand => block.prompt_and_command_grid(), GridType::PromptAndCommand => block.prompt_and_command_grid(),
GridType::Rprompt => block.rprompt_grid(), GridType::Rprompt => block.rprompt_grid(),
GridType::Output => block.output_grid(), GridType::Output => block.output_grid(),
}, }
}; };
// There are two cases: // There are two cases:
@@ -719,15 +715,14 @@ impl BlockList {
semantic_selection: &SemanticSelection, semantic_selection: &SemanticSelection,
inverted_blocklist: bool, inverted_blocklist: bool,
) -> Option<BlockListPoint> { ) -> Option<BlockListPoint> {
let (mut head, mut tail) = // select-up and select-down do nothing when there's no rendered selection.
match self.expand_selection(semantic_selection, inverted_blocklist) { let (mut head, mut tail) = {
Some(selection) => ( let selection = self.expand_selection(semantic_selection, inverted_blocklist)?;
selection.head().within_grid_point, (
selection.tail().within_grid_point, selection.head().within_grid_point,
), selection.tail().within_grid_point,
// select-up and select-down do nothing when there's no rendered selection. )
None => return None, };
};
let mut selection = self.selection.take()?; let mut selection = self.selection.take()?;
@@ -766,14 +761,14 @@ impl BlockList {
inverted_blocklist: bool, inverted_blocklist: bool,
) -> Option<(WithinBlock<Point>, bool)> { ) -> Option<(WithinBlock<Point>, bool)> {
let tail_point = tail.get(); let tail_point = tail.get();
let grid = match self.block_at(tail.block_index) { let grid = {
None => return None, let block = self.block_at(tail.block_index)?;
Some(block) => match tail.grid { match tail.grid {
GridType::Prompt => block.prompt_grid(), GridType::Prompt => block.prompt_grid(),
GridType::PromptAndCommand => block.prompt_and_command_grid(), GridType::PromptAndCommand => block.prompt_and_command_grid(),
GridType::Rprompt => block.rprompt_grid(), GridType::Rprompt => block.rprompt_grid(),
GridType::Output => block.output_grid(), GridType::Output => block.output_grid(),
}, }
}; };
let num_rows_in_grid = grid.len_displayed(); let num_rows_in_grid = grid.len_displayed();
let mut is_at_bottom = false; let mut is_at_bottom = false;
@@ -232,7 +232,7 @@ impl InBandCommandExecutor {
} }
} }
} else { } else {
log::warn!("Cached in-band command ID {} does not match ID of executed in-band command output {}", cmd.id, &event.command_id); log::warn!("Cached in-band command ID {} does not match ID of executed in-band command output {}", cmd.id, event.command_id);
// If the command event that we received is not for the current running command, // If the command event that we received is not for the current running command,
// we need to restore the command as the currently running command. // we need to restore the command as the currently running command.
*current_command = Some(cmd); *current_command = Some(cmd);
+1 -1
View File
@@ -1263,7 +1263,7 @@ impl ProfileModelSelector {
if let Some(llm) = self.get_selected_llm_info(MenuType::Sidecar, index, ctx) { if let Some(llm) = self.get_selected_llm_info(MenuType::Sidecar, index, ctx) {
log::info!( log::info!(
"Selecting base agent model {} (from model selector)", "Selecting base agent model {} (from model selector)",
&llm.id llm.id
); );
LLMPreferences::handle(ctx).update(ctx, |preferences, ctx| { LLMPreferences::handle(ctx).update(ctx, |preferences, ctx| {
preferences.update_preferred_agent_mode_llm(&llm.id, self.terminal_view_id, ctx); preferences.update_preferred_agent_mode_llm(&llm.id, self.terminal_view_id, ctx);
+2 -2
View File
@@ -329,12 +329,12 @@ pub fn transfer_file_sftp_command(
} }
// "sftp -P 2222 sshuser@127.0.0.1 <<< "put -r \"path/to/local/file\"" // "sftp -P 2222 sshuser@127.0.0.1 <<< "put -r \"path/to/local/file\""
command += &format!("\\\"{}\\\"", &local_file_path); command += &format!("\\\"{}\\\"", local_file_path);
// "sftp -P 2222 sshuser@127.0.0.1 <<< "put -r path/to/local/file pwd/on/remote" // "sftp -P 2222 sshuser@127.0.0.1 <<< "put -r path/to/local/file pwd/on/remote"
if let Some(pwd) = pwd { if let Some(pwd) = pwd {
command += " "; command += " ";
command += &format!("\\\"{}\\\"", &pwd); command += &format!("\\\"{}\\\"", pwd);
} }
// "sftp -P 2222 sshuser@127.0.0.1 <<< "put -r path/to/local/file pwd/on/remote"" // "sftp -P 2222 sshuser@127.0.0.1 <<< "put -r path/to/local/file pwd/on/remote""
@@ -945,6 +945,7 @@ impl TerminalView {
agent_backend: Default::default(), agent_backend: Default::default(),
active_provider_run_json: None, active_provider_run_json: None,
local_agent_groups: Vec::new(), local_agent_groups: Vec::new(),
tool_titles: Default::default(),
server_conversation_token: None, server_conversation_token: None,
conversation_usage_metadata: None, conversation_usage_metadata: None,
reverted_action_ids: None, reverted_action_ids: None,
+1 -1
View File
@@ -242,7 +242,7 @@ impl FileUpload {
//"put -r path/to/local/file pwd/on/remote" //"put -r path/to/local/file pwd/on/remote"
if let Some(pwd) = remote_dest_path { if let Some(pwd) = remote_dest_path {
command += " "; command += " ";
command += &format!("\\\"{}\\\"", &pwd); command += &format!("\\\"{}\\\"", pwd);
} }
command command
+1 -1
View File
@@ -26,7 +26,7 @@ impl Warp {
std::env::var("CARGO_TARGET_DIR") std::env::var("CARGO_TARGET_DIR")
.ok() .ok()
.map(|directory| PathBuf::from(directory).join(build)) .map(|directory| PathBuf::from(directory).join(build))
.unwrap_or_else(|| Self::root().join(format!("target/{}", &build))) .unwrap_or_else(|| Self::root().join(format!("target/{}", build)))
}; };
path.push("warp"); path.push("warp");
+1 -1
View File
@@ -98,7 +98,7 @@ pub(crate) fn starts_with_shebang(path: &Path) -> bool {
use std::io::Read; use std::io::Read;
let mut prefix = [0u8; 2]; let mut prefix = [0u8; 2];
match std::fs::File::open(path) { match std::fs::File::open(path) {
Ok(mut file) => file.read_exact(&mut prefix).is_ok() && prefix == [b'#', b'!'], Ok(mut file) => file.read_exact(&mut prefix).is_ok() && prefix == *b"#!",
Err(_) => false, Err(_) => false,
} }
} }
+1 -7
View File
@@ -13444,13 +13444,7 @@ impl Workspace {
.map(|(terminal_surface_id, _)| terminal_surface_id); .map(|(terminal_surface_id, _)| terminal_surface_id);
// An empty prompt should not be provided as a query for the new forked conversation. // An empty prompt should not be provided as a query for the new forked conversation.
let initial_prompt = initial_prompt.and_then(|prompt| { let initial_prompt = initial_prompt.filter(|prompt| !prompt.trim().is_empty());
if prompt.trim().is_empty() {
None
} else {
Some(prompt)
}
});
// True when the fork is paired with a follow-up that fires immediately after restore // True when the fork is paired with a follow-up that fires immediately after restore
// (a prompt to send, or a `/summarize` triggered by `summarize_after_fork`). // (a prompt to send, or a `/summarize` triggered by `summarize_after_fork`).
+2 -2
View File
@@ -175,7 +175,7 @@ impl fmt::Debug for DiffDelta {
write!( write!(
f, f,
"DiffDelta {{\nreplacement_line_range: {:?},", "DiffDelta {{\nreplacement_line_range: {:?},",
&self.replacement_line_range self.replacement_line_range
)?; )?;
f.write_str("\n--insertion--\n")?; f.write_str("\n--insertion--\n")?;
f.write_str(&self.insertion)?; f.write_str(&self.insertion)?;
@@ -709,7 +709,7 @@ fn score_matches<T: Scorer>(
log::debug!( log::debug!(
"Closest match with score {}:\n{}", "Closest match with score {}:\n{}",
max_similarity, max_similarity,
&target_lines[range].join("\n") target_lines[range].join("\n")
); );
} }
} }
@@ -664,7 +664,7 @@ impl CodebaseIndex {
if galaxy_core::channel::ChannelState::enable_debug_features() { if galaxy_core::channel::ChannelState::enable_debug_features() {
log::debug!( log::debug!(
"Trying to upsert changed files: {:?}", "Trying to upsert changed files: {:?}",
&changed_files.upsertions changed_files.upsertions
); );
} }
let (upserted_nodes, upsertion_fragment_metadata_updates) = let (upserted_nodes, upsertion_fragment_metadata_updates) =
+1 -1
View File
@@ -106,7 +106,7 @@ impl<'a> BufferMarkdownParser<'a> {
} }
BufferBlockStyle::CodeBlock { code_block_type } => res.push_str(&format!( BufferBlockStyle::CodeBlock { code_block_type } => res.push_str(&format!(
"```{}\n", "```{}\n",
&code_block_type.to_markdown_representation(self.style) code_block_type.to_markdown_representation(self.style)
)), )),
BufferBlockStyle::TaskList { BufferBlockStyle::TaskList {
indent_level, indent_level,
+2 -2
View File
@@ -2892,7 +2892,7 @@ impl RenderState {
log::trace!( log::trace!(
"Incoming block replacements {:?}", "Incoming block replacements {:?}",
&pending_edit.laid_out_line pending_edit.laid_out_line
); );
let hidden_range_clone = hidden_ranges.clone(); let hidden_range_clone = hidden_ranges.clone();
@@ -3042,7 +3042,7 @@ impl RenderState {
for range in ranges { for range in ranges {
let hidden_range_size = range.end - range.start - 1; let hidden_range_size = range.end - range.start - 1;
log::trace!("==== Processing range: {:?} ====", &range); log::trace!("==== Processing range: {:?} ====", range);
new_tree.push_tree(cursor.slice(&range.start, SeekBias::Left)); new_tree.push_tree(cursor.slice(&range.start, SeekBias::Left));
log::trace!("After pushing prefix tree:\n {}", new_tree.describe()); log::trace!("After pushing prefix tree:\n {}", new_tree.describe());
let sub_tree = cursor.slice(&range.end, SeekBias::Right); let sub_tree = cursor.slice(&range.end, SeekBias::Right);
@@ -252,7 +252,7 @@ impl CommandRegistry {
log::debug!( log::debug!(
"Execution of `{}` failed with output: {}", "Execution of `{}` failed with output: {}",
command_to_run, command_to_run,
&output_string output_string
) )
} }
} }
+3 -5
View File
@@ -77,11 +77,9 @@ pub unsafe fn from_native(
.to_str() .to_str()
.ok()?; .ok()?;
let unmodified_chars = if let Some(first_char) = unmodified_chars.chars().next() { let first_char = unmodified_chars.chars().next()?;
unicode_char_to_key(first_char as u16).unwrap_or(unmodified_chars) let unmodified_chars =
} else { unicode_char_to_key(first_char as u16).unwrap_or(unmodified_chars);
return None;
};
let keystroke = Keystroke { let keystroke = Keystroke {
ctrl: native_modifiers.contains(NSEventModifierFlags::Control), ctrl: native_modifiers.contains(NSEventModifierFlags::Control),
+3 -3
View File
@@ -1647,7 +1647,7 @@ impl AppContext {
// If there are no handlers registered for the given action, then we can return early // If there are no handlers registered for the given action, then we can return early
// without needing to look at the responder chain at all, since no views will handle it // without needing to look at the responder chain at all, since no views will handle it
let Some(mut handlers) = self.typed_actions.remove(&action_type) else { let Some(mut handlers) = self.typed_actions.remove(&action_type) else {
log::warn!("Dispatched action has no handlers: {:?}", &action); log::warn!("Dispatched action has no handlers: {action:?}");
return false; return false;
}; };
@@ -1716,7 +1716,7 @@ impl AppContext {
arg: &dyn Any, arg: &dyn Any,
) { ) {
if let Some((name, mut handlers)) = self.global_actions.remove_entry(name) { if let Some((name, mut handlers)) = self.global_actions.remove_entry(name) {
log::info!("dispatching global action for {}", &name); log::info!("dispatching global action for {name}");
self.pending_flushes += 1; self.pending_flushes += 1;
for handler in handlers.iter_mut().rev() { for handler in handlers.iter_mut().rev() {
handler(arg, location, self); handler(arg, location, self);
@@ -3716,7 +3716,7 @@ impl AppContext {
if font_family_bytes.is_empty() { if font_family_bytes.is_empty() {
log::warn!( log::warn!(
"Failed to load any fonts for the family {}", "Failed to load any fonts for the family {}",
&fallback_family.name fallback_family.name
); );
return; return;
} }
+1 -1
View File
@@ -92,7 +92,7 @@ where
"{}{}/graphql/v2?op={}", "{}{}/graphql/v2?op={}",
ChannelState::server_root_url(), ChannelState::server_root_url(),
options.path_prefix.unwrap_or_default(), options.path_prefix.unwrap_or_default(),
&operation_name operation_name
); );
let mut req = client.post(&graphql_endpoint).json(&operation); let mut req = client.post(&graphql_endpoint).json(&operation);
@@ -43,7 +43,7 @@ impl InputClassifier for HeuristicClassifier {
log::info!( log::info!(
"[input-classifier] detect_input_type called: buffer={:?}, word_tokens={}, current={:?}", "[input-classifier] detect_input_type called: buffer={:?}, word_tokens={}, current={:?}",
&input.buffer_text, input.buffer_text,
total_word_token_count, total_word_token_count,
context.current_input_type context.current_input_type
); );
+1 -1
View File
@@ -196,7 +196,7 @@ pub fn write_histfiles_for_test<P>(
contents contents
} }
}; };
println!("histfile path:{:?}", &path); println!("histfile path:{:?}", path);
if let Err(e) = write(&path, histfile_contents.as_bytes()) { if let Err(e) = write(&path, histfile_contents.as_bytes()) {
panic!("Could not write histfile {:?}: {}", path.to_str(), e); panic!("Could not write histfile {:?}: {}", path.to_str(), e);
} }
+1 -1
View File
@@ -68,7 +68,7 @@ where
match response { match response {
Some(result) => result, Some(result) => result,
None => { None => {
panic!("Unexpected IPC call with request: {:?}", &request); panic!("Unexpected IPC call with request: {:?}", request);
} }
} }
} }
+3
View File
@@ -1088,6 +1088,9 @@ pub struct LocalAgentGroup {
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct AgentConversationData { pub struct AgentConversationData {
/// Display-only tool intent, scoped to this conversation and keyed by call ID.
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub tool_titles: HashMap<String, String>,
#[serde(default, skip_serializing_if = "AgentBackend::is_provider")] #[serde(default, skip_serializing_if = "AgentBackend::is_provider")]
pub agent_backend: AgentBackend, pub agent_backend: AgentBackend,
/// Versioned application-owned snapshot for an active direct-provider run. /// Versioned application-owned snapshot for an active direct-provider run.
+19
View File
@@ -139,6 +139,7 @@ fn agent_conversation_data_roundtrips_last_event_sequence() {
agent_backend: Default::default(), agent_backend: Default::default(),
active_provider_run_json: None, active_provider_run_json: None,
local_agent_groups: Vec::new(), local_agent_groups: Vec::new(),
tool_titles: Default::default(),
server_conversation_token: None, server_conversation_token: None,
conversation_usage_metadata: None, conversation_usage_metadata: None,
reverted_action_ids: None, reverted_action_ids: None,
@@ -243,6 +244,7 @@ fn agent_conversation_data_roundtrips_remote_child_marker() {
agent_backend: Default::default(), agent_backend: Default::default(),
active_provider_run_json: None, active_provider_run_json: None,
local_agent_groups: Vec::new(), local_agent_groups: Vec::new(),
tool_titles: Default::default(),
server_conversation_token: None, server_conversation_token: None,
conversation_usage_metadata: None, conversation_usage_metadata: None,
reverted_action_ids: None, reverted_action_ids: None,
@@ -272,6 +274,7 @@ fn agent_conversation_data_roundtrips_optimistic_root_marker() {
agent_backend: Default::default(), agent_backend: Default::default(),
active_provider_run_json: None, active_provider_run_json: None,
local_agent_groups: Vec::new(), local_agent_groups: Vec::new(),
tool_titles: Default::default(),
server_conversation_token: None, server_conversation_token: None,
conversation_usage_metadata: None, conversation_usage_metadata: None,
reverted_action_ids: None, reverted_action_ids: None,
@@ -313,6 +316,7 @@ fn agent_conversation_data_skips_serializing_none_last_event_sequence() {
agent_backend: Default::default(), agent_backend: Default::default(),
active_provider_run_json: None, active_provider_run_json: None,
local_agent_groups: Vec::new(), local_agent_groups: Vec::new(),
tool_titles: Default::default(),
server_conversation_token: None, server_conversation_token: None,
conversation_usage_metadata: None, conversation_usage_metadata: None,
reverted_action_ids: None, reverted_action_ids: None,
@@ -344,6 +348,7 @@ fn agent_conversation_data_roundtrips_pinned() {
agent_backend: Default::default(), agent_backend: Default::default(),
active_provider_run_json: None, active_provider_run_json: None,
local_agent_groups: Vec::new(), local_agent_groups: Vec::new(),
tool_titles: Default::default(),
server_conversation_token: None, server_conversation_token: None,
conversation_usage_metadata: None, conversation_usage_metadata: None,
reverted_action_ids: None, reverted_action_ids: None,
@@ -373,6 +378,7 @@ fn agent_conversation_data_skips_serializing_unpinned() {
agent_backend: Default::default(), agent_backend: Default::default(),
active_provider_run_json: None, active_provider_run_json: None,
local_agent_groups: Vec::new(), local_agent_groups: Vec::new(),
tool_titles: Default::default(),
server_conversation_token: None, server_conversation_token: None,
conversation_usage_metadata: None, conversation_usage_metadata: None,
reverted_action_ids: None, reverted_action_ids: None,
@@ -436,3 +442,16 @@ fn model_token_usage_replay_skips_non_custom_endpoint_entries() {
}; };
assert!(warp_only.to_proto_custom_endpoint_usage().is_none()); assert!(warp_only.to_proto_custom_endpoint_usage().is_none());
} }
#[test]
fn tool_titles_survive_roundtrip_and_are_optional_for_old_conversations() {
let mut data = AgentConversationData::default();
data.tool_titles
.insert("call-1".into(), "Find cache invalidation paths".into());
let mut json = serde_json::to_value(&data).unwrap();
let restored: AgentConversationData = serde_json::from_value(json.clone()).unwrap();
assert_eq!(restored.tool_titles, data.tool_titles);
json.as_object_mut().unwrap().remove("tool_titles");
let legacy: AgentConversationData = serde_json::from_value(json).unwrap();
assert!(legacy.tool_titles.is_empty());
}
+3 -3
View File
@@ -111,7 +111,7 @@ impl SimpleLogger {
let mut log_file = match open_truncated(&log_path).await { let mut log_file = match open_truncated(&log_path).await {
Ok(log_file) => log_file, Ok(log_file) => log_file,
Err(e) => { Err(e) => {
log::warn!("Could not open file for logging: {:?}. {:?}", &log_path, e); log::warn!("Could not open file for logging: {:?}. {:?}", log_path, e);
return; return;
} }
}; };
@@ -153,7 +153,7 @@ impl SimpleLogger {
"SimpleLogger: rotation failed for {:?}: {e}; \ "SimpleLogger: rotation failed for {:?}: {e}; \
preserving existing log content and continuing \ preserving existing log content and continuing \
in append mode", in append mode",
&log_path, log_path,
); );
true true
} }
@@ -169,7 +169,7 @@ impl SimpleLogger {
log::warn!( log::warn!(
"SimpleLogger: failed to reopen {:?} after \ "SimpleLogger: failed to reopen {:?} after \
rotation: {e}", rotation: {e}",
&log_path, log_path,
); );
return; return;
} }
+1 -1
View File
@@ -172,7 +172,7 @@ impl Warp {
std::env::var("CARGO_TARGET_DIR") std::env::var("CARGO_TARGET_DIR")
.ok() .ok()
.map(|directory| PathBuf::from(directory).join(build)) .map(|directory| PathBuf::from(directory).join(build))
.unwrap_or_else(|| Self::root().join(format!("target/{}", &build))) .unwrap_or_else(|| Self::root().join(format!("target/{}", build)))
}; };
path.push("warp"); path.push("warp");