first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user