first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
+139 -47
View File
@@ -12,6 +12,7 @@ pub(super) mod read_mcp_resource;
pub(super) mod read_skill;
pub(super) mod request_computer_use;
pub(super) mod request_file_edits;
pub(super) mod run_agents;
pub(super) mod search_codebase;
pub(super) mod send_message;
pub(super) mod shell_command;
@@ -20,6 +21,12 @@ pub(super) mod suggest_new_conversation;
pub(super) mod suggest_prompt;
pub(super) mod upload_artifact;
pub(super) mod use_computer;
pub(super) mod wait_for_events;
use std::any::Any;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
use ai::agent::action_result::{InsertReviewCommentsResult, RequestCommandOutputResult};
pub use ask_user_question::AskUserQuestionExecutor;
@@ -29,77 +36,74 @@ use create_documents::CreateDocumentsExecutor;
use edit_documents::EditDocumentsExecutor;
use fetch_conversation::FetchConversationExecutor;
use file_glob::FileGlobExecutor;
use futures::{future::BoxFuture, FutureExt};
use galaxy_core::{execution_mode::AppExecutionMode, features::FeatureFlag};
use futures::future::BoxFuture;
#[cfg(feature = "local_fs")]
use futures::AsyncReadExt;
use futures::FutureExt;
use grep::GrepExecutor;
use notebooks::NotebookExecutor;
#[cfg(feature = "local_fs")]
use mime_guess::from_path;
use parking_lot::FairMutex;
use read_documents::ReadDocumentsExecutor;
pub(super) use read_files::ReadFilesExecutor;
use read_mcp_resource::ReadMCPResourceExecutor;
use read_skill::ReadSkillExecutor;
use request_computer_use::RequestComputerUseExecutor;
pub(crate) use request_file_edits::apply_edits;
pub(crate) use request_file_edits::FileReadResult;
pub(crate) use request_file_edits::MalformedFinalLineProxyEvent;
pub(crate) use request_file_edits::{apply_edits, FileReadResult, MalformedFinalLineProxyEvent};
pub use request_file_edits::{
EditAcceptAndContinueClickedEvent, EditAcceptClickedEvent, EditResolvedEvent, EditStats,
RequestFileEditsExecutor, RequestFileEditsFormatKind, RequestFileEditsTelemetryEvent,
};
#[cfg(test)]
pub use run_agents::{compose_run_agents_child_prompt, run_agents_to_start_agent_mode};
pub use run_agents::{RunAgentsExecutor, RunAgentsExecutorEvent, RunAgentsSpawningSnapshot};
pub use send_message::SendMessageToAgentExecutor;
use serde::{Deserialize, Serialize};
pub use shell_command::{ShellCommandExecutor, ShellCommandExecutorEvent};
pub use start_agent::{StartAgentExecutor, StartAgentExecutorEvent, StartAgentRequest};
pub use start_agent::{
StartAgentExecutor, StartAgentExecutorEvent, StartAgentRequest, StartAgentRequestId,
};
pub use suggest_new_conversation::NewConversationDecision;
use suggest_new_conversation::SuggestNewConversationExecutor;
pub use suggest_prompt::PromptSuggestionExecutor;
use upload_artifact::UploadArtifactExecutor;
use use_computer::UseComputerExecutor;
#[cfg(feature = "local_fs")]
use crate::util::openable_file_type::is_binary_file;
#[cfg(feature = "local_fs")]
use futures::AsyncReadExt;
use wait_for_events::WaitForEventsExecutor;
use galaxy_core::execution_mode::AppExecutionMode;
use galaxy_core::features::FeatureFlag;
#[cfg(feature = "local_fs")]
use galaxy_files::{FileModel, TextFileReadResult};
#[cfg(feature = "local_fs")]
use galaxy_util::file::FileLoadError;
#[cfg(feature = "local_fs")]
use galaxy_util::file_type::is_buffer_binary;
use galaxyui::{
r#async::{Spawnable, SpawnableOutput},
AppContext, Entity, EntityId, ModelContext, ModelHandle, SingletonEntity,
};
use std::{any::Any, path::PathBuf, pin::Pin, sync::Arc};
use galaxyui::r#async::{Spawnable, SpawnableOutput};
use galaxyui::{AppContext, Entity, EntityId, ModelContext, ModelHandle, SingletonEntity};
use self::search_codebase::SearchCodebaseExecutor;
use crate::ai::agent::conversation::AIConversationId;
use crate::ai::agent::{
AIAgentAction, AIAgentActionId, AIAgentActionResult, AIAgentActionResultType,
AIAgentActionType, AIAgentActionTypeDiscriminants, CancellationReason, FileContext,
FileLocations, ServerOutputId,
};
use crate::ai::ambient_agents::AmbientAgentTaskId;
use crate::ai::get_relevant_files::controller::GetRelevantFilesController;
#[cfg(feature = "local_fs")]
use crate::ai::{agent::AnyFileContent, paths::host_native_absolute_path};
use crate::terminal::model::session::active_session::ActiveSession;
use crate::terminal::model::session::command_executor::shell_quote_arg;
use crate::terminal::model::session::{ExecuteCommandOptions, Session};
use crate::terminal::model_events::ModelEventDispatcher;
use crate::terminal::shell::ShellType;
use crate::terminal::{ShellLaunchData, TerminalModel};
#[cfg(feature = "local_fs")]
use crate::util::image::{
is_supported_image_mime_type, process_image_for_agent, ProcessImageResult,
};
#[cfg(feature = "local_fs")]
use mime_guess::from_path;
use self::search_codebase::SearchCodebaseExecutor;
#[cfg(feature = "local_fs")]
use crate::ai::{agent::AnyFileContent, paths::host_native_absolute_path};
use crate::{
ai::{
agent::{
conversation::AIConversationId, AIAgentAction, AIAgentActionId, AIAgentActionResult,
AIAgentActionResultType, AIAgentActionType, CancellationReason, FileContext,
FileLocations, ServerOutputId,
},
ambient_agents::AmbientAgentTaskId,
get_relevant_files::controller::GetRelevantFilesController,
},
terminal::{
model::session::{active_session::ActiveSession, ExecuteCommandOptions, Session},
model_events::ModelEventDispatcher,
shell::ShellType,
ShellLaunchData, TerminalModel,
},
BlocklistAIHistoryModel,
};
use crate::util::openable_file_type::is_binary_file;
use crate::BlocklistAIHistoryModel;
/// Types of actions that can be executed in parallel.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -261,8 +265,10 @@ pub struct BlocklistAIActionExecutor {
read_skill_executor: ModelHandle<ReadSkillExecutor>,
fetch_conversation_executor: ModelHandle<FetchConversationExecutor>,
start_agent_executor: ModelHandle<StartAgentExecutor>,
run_agents_executor: ModelHandle<RunAgentsExecutor>,
send_message_executor: ModelHandle<SendMessageToAgentExecutor>,
ask_user_question_executor: ModelHandle<AskUserQuestionExecutor>,
wait_for_events_executor: ModelHandle<WaitForEventsExecutor>,
/// The actions currently executing asynchronously, keyed by action ID.
/// We track them per action rather than as a single slot so multiple actions from the same
/// parallel phase can complete independently.
@@ -324,12 +330,16 @@ impl BlocklistAIActionExecutor {
let use_computer_executor = ctx.add_model(|_| UseComputerExecutor::new());
let request_computer_use_executor =
ctx.add_model(|_| RequestComputerUseExecutor::new(terminal_view_id));
let read_skill_executor = ctx.add_model(|_| ReadSkillExecutor::new());
let read_skill_executor = ctx.add_model(|_| ReadSkillExecutor::new(active_session.clone()));
let fetch_conversation_executor = ctx.add_model(|_| FetchConversationExecutor::new());
let start_agent_executor = ctx.add_model(StartAgentExecutor::new);
let run_agents_executor = ctx
.add_model(|_| RunAgentsExecutor::new(start_agent_executor.clone(), terminal_view_id));
let send_message_executor = ctx.add_model(|_| SendMessageToAgentExecutor::new());
let ask_user_question_executor =
ctx.add_model(|_| AskUserQuestionExecutor::new(terminal_view_id));
let wait_for_events_executor =
ctx.add_model(|ctx| WaitForEventsExecutor::new(terminal_view_id, ctx));
Self {
shell_command_executor,
read_files_executor,
@@ -353,8 +363,10 @@ impl BlocklistAIActionExecutor {
read_skill_executor,
fetch_conversation_executor,
start_agent_executor,
run_agents_executor,
send_message_executor,
ask_user_question_executor,
wait_for_events_executor,
}
}
@@ -364,6 +376,29 @@ impl BlocklistAIActionExecutor {
.map(|running| &running.action)
}
/// Returns the action_id of any running WaitForEvents action for the
/// given conversation. There is at most one (wait_for_events is
/// documented as exclusive within a turn).
pub(super) fn find_running_wait_for_events(
&self,
conversation_id: AIConversationId,
) -> Option<AIAgentActionId> {
self.async_executing_actions
.iter()
.find_map(|(action_id, running)| {
if running.conversation_id == conversation_id
&& matches!(
running.action.action,
AIAgentActionType::WaitForEvents { .. }
)
{
Some(action_id.clone())
} else {
None
}
})
}
pub fn shell_command_executor(&self) -> &ModelHandle<ShellCommandExecutor> {
&self.shell_command_executor
}
@@ -390,6 +425,10 @@ impl BlocklistAIActionExecutor {
&self.start_agent_executor
}
pub fn run_agents_executor(&self) -> &ModelHandle<RunAgentsExecutor> {
&self.run_agents_executor
}
pub fn action_phase(&self, action: &AIAgentAction, ctx: &AppContext) -> RunningActionPhase {
match &action.action {
AIAgentActionType::ReadFiles(..)
@@ -426,6 +465,9 @@ impl BlocklistAIActionExecutor {
id: Option<AmbientAgentTaskId>,
ctx: &mut ModelContext<Self>,
) {
self.send_message_executor.update(ctx, |executor, _| {
executor.set_ambient_agent_task_id(id);
});
self.request_computer_use_executor
.update(ctx, |executor, _| {
executor.set_ambient_agent_task_id(id);
@@ -540,6 +582,12 @@ impl BlocklistAIActionExecutor {
AIAgentActionType::AskUserQuestion { .. } => self
.ask_user_question_executor
.update(ctx, |executor, ctx| executor.preprocess_action(input, ctx)),
AIAgentActionType::RunAgents(_) => self
.run_agents_executor
.update(ctx, |executor, ctx| executor.preprocess_action(input, ctx)),
AIAgentActionType::WaitForEvents { .. } => self
.wait_for_events_executor
.update(ctx, |executor, ctx| executor.preprocess_action(input, ctx)),
}
}
@@ -768,6 +816,14 @@ impl BlocklistAIActionExecutor {
.ask_user_question_executor
.update(ctx, |executor, ctx| executor.execute(input, ctx))
.into(),
AIAgentActionType::RunAgents(_) => self
.run_agents_executor
.update(ctx, |executor, ctx| executor.execute(input, ctx))
.into(),
AIAgentActionType::WaitForEvents { .. } => self
.wait_for_events_executor
.update(ctx, |executor, ctx| executor.execute(input, ctx))
.into(),
};
let action_id = action_clone.id.clone();
@@ -885,6 +941,11 @@ impl BlocklistAIActionExecutor {
return;
}
if let Some(running) = self.async_executing_actions.remove(action_id) {
let action_kind = AIAgentActionTypeDiscriminants::from(&running.action.action);
log::info!(
"Canceling running async action of type {action_kind:?} action_id={action_id:?}, reason={reason:?}, backtrace=\n{}",
std::backtrace::Backtrace::force_capture()
);
if running.is_shell_command_action() {
self.shell_command_executor.update(ctx, |executor, ctx| {
executor.cancel_execution(&running.action.id, ctx);
@@ -893,6 +954,19 @@ impl BlocklistAIActionExecutor {
self.search_codebase_executor.update(ctx, |executor, ctx| {
executor.cancel_execution(&running.action.id, ctx);
});
} else if matches!(running.action.action, AIAgentActionType::RunAgents(..)) {
self.run_agents_executor.update(ctx, |executor, ctx| {
executor.cancel_execution(&running.action.id, ctx);
});
} else if let AIAgentActionType::WaitForEvents { tool_call_id, .. } =
&running.action.action
{
// Drop the executor's pending entry; the shared cancel
// path emits FinishedAction(Cancelled).
let tool_call_id = tool_call_id.clone();
self.wait_for_events_executor.update(ctx, |executor, _| {
executor.cancel_execution(&tool_call_id);
});
}
ctx.emit(BlocklistAIActionExecutorEvent::FinishedAction {
result: Arc::new(AIAgentActionResult {
@@ -1016,6 +1090,12 @@ impl BlocklistAIActionExecutor {
AIAgentActionType::AskUserQuestion { .. } => self
.ask_user_question_executor
.update(ctx, |executor, ctx| executor.should_autoexecute(input, ctx)),
AIAgentActionType::RunAgents(_) => self
.run_agents_executor
.update(ctx, |executor, ctx| executor.should_autoexecute(input, ctx)),
AIAgentActionType::WaitForEvents { .. } => self
.wait_for_events_executor
.update(ctx, |executor, ctx| executor.should_autoexecute(input, ctx)),
}
}
@@ -1300,14 +1380,26 @@ async fn read_binary_file_context(
})
}
fn build_is_file_path_command(path: &str, shell_type: ShellType) -> String {
let escaped_path = shell_quote_arg(path, shell_type);
if shell_type == ShellType::PowerShell {
format!("if (Test-Path -PathType Leaf {escaped_path}) {{ exit 0 }} else {{ exit 1 }}")
} else {
format!("test -f {escaped_path}")
}
}
fn build_is_git_repository_command(absolute_path: &str, shell_type: ShellType) -> String {
format!(
"git -C {} rev-parse",
shell_quote_arg(absolute_path, shell_type)
)
}
/// Returns true if the given path is a regular file on the session's filesystem.
/// Runs a shell command on the session so it works for both local and remote sessions.
async fn is_file_path(path: &str, session: &Session) -> bool {
let command = if session.shell().shell_type() == ShellType::PowerShell {
format!("if (Test-Path -PathType Leaf \"{path}\") {{ exit 0 }} else {{ exit 1 }}")
} else {
format!("test -f \"{path}\"")
};
let command = build_is_file_path_command(path, session.shell().shell_type());
session
.execute_command(&command, None, None, ExecuteCommandOptions::default())
.await
@@ -1317,7 +1409,7 @@ async fn is_file_path(path: &str, session: &Session) -> bool {
/// Returns true if git is installed and the given path is in a git repository.
async fn is_git_repository(absolute_path: &str, session: &Session) -> anyhow::Result<bool> {
let git_command = format!("git -C \"{absolute_path}\" rev-parse");
let git_command = build_is_git_repository_command(absolute_path, session.shell().shell_type());
let command_output = session
.execute_command(
git_command.as_str(),