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
+118 -31
View File
@@ -1,17 +1,21 @@
use std::collections::{HashMap, HashSet};
use chrono::{DateTime, Utc};
use crate::ai::agent::conversation::AIConversationId;
use crate::ai::ambient_agents::AmbientAgentTaskId;
use crate::ai::blocklist::agent_view::{AgentViewController, AgentViewControllerEvent};
use crate::ai::blocklist::BlocklistAIHistoryModel;
use crate::terminal::model::session::active_session::ActiveSession;
use galaxyui::{
AppContext, Entity, EntityId, ModelContext, ModelHandle, SingletonEntity, WeakModelHandle,
WindowId,
};
use crate::ai::agent::conversation::AIConversationId;
use crate::ai::agent_conversations_model::{AgentConversationEntry, AgentConversationEntryId};
use crate::ai::ambient_agents::AmbientAgentTaskId;
use crate::ai::blocklist::agent_view::{AgentViewController, AgentViewControllerEvent};
use crate::ai::blocklist::orchestration_event_streamer::{
register_agent_event_consumer, unregister_agent_event_consumer,
};
use crate::ai::blocklist::BlocklistAIHistoryModel;
use crate::terminal::model::session::active_session::ActiveSession;
/// Contains the handles needed to track an active agent view.
struct ActiveAgentViewHandles {
controller: WeakModelHandle<AgentViewController>,
@@ -44,6 +48,17 @@ pub enum ConversationOrTaskId {
TaskId(AmbientAgentTaskId),
}
impl From<AgentConversationEntryId> for ConversationOrTaskId {
fn from(id: AgentConversationEntryId) -> Self {
match id {
AgentConversationEntryId::Conversation(conversation_id) => {
ConversationOrTaskId::ConversationId(conversation_id)
}
AgentConversationEntryId::AmbientRun(task_id) => ConversationOrTaskId::TaskId(task_id),
}
}
}
impl ConversationOrTaskId {
pub fn conversation_id(&self) -> Option<AIConversationId> {
match self {
@@ -94,6 +109,34 @@ impl ActiveAgentViewsModel {
}
}
fn update_focused_conversation_for_terminal(
&mut self,
terminal_view_id: EntityId,
conversation_id: Option<ConversationOrTaskId>,
) {
for focused_terminal_state in self.focused_terminal_states.values_mut() {
if focused_terminal_state.focused_terminal_id == terminal_view_id
&& !matches!(
focused_terminal_state.active_conversation_id,
Some(ConversationOrTaskId::TaskId(_))
)
{
focused_terminal_state.active_conversation_id = conversation_id;
}
}
if let Some(last_focused_terminal_state) = &mut self.last_focused_terminal_state {
if last_focused_terminal_state.focused_terminal_id == terminal_view_id
&& !matches!(
last_focused_terminal_state.active_conversation_id,
Some(ConversationOrTaskId::TaskId(_))
)
{
last_focused_terminal_state.active_conversation_id = conversation_id;
}
}
}
/// Register an agent view controller to track when the agent view is entered/exited.
#[cfg_attr(target_family = "wasm", allow(dead_code))]
pub fn register_agent_view_controller(
@@ -122,7 +165,19 @@ impl ActiveAgentViewsModel {
},
);
ctx.subscribe_to_model(controller, move |model, event, ctx| match event {
// On pane re-attach the controller's `agent_view_state` is still
// `Active` while the unregister path has already torn down the
// streamer consumer. Re-register here; the `EnteredAgentView`
// subscription only fires on subsequent state transitions.
if let Some(conversation_id) = controller
.as_ref(ctx)
.agent_view_state()
.active_conversation_id()
{
register_agent_event_consumer(conversation_id, terminal_view_id, ctx);
}
ctx.subscribe_to_model(controller, move |model, _, event, ctx| match event {
AgentViewControllerEvent::EnteredAgentView {
conversation_id, ..
} => {
@@ -132,37 +187,31 @@ impl ActiveAgentViewsModel {
// Update the focused conversation in whichever window owns this terminal view.
// We ignore agent view changes if we are focused on an ambient conversation,
// as ambient conversation navigation operates at the task level instead of the conversation level.
for focused_terminal_state in model.focused_terminal_states.values_mut() {
if focused_terminal_state.focused_terminal_id == terminal_view_id
&& !matches!(
focused_terminal_state.active_conversation_id,
Some(ConversationOrTaskId::TaskId(_))
)
{
focused_terminal_state.active_conversation_id = Some(conv_id);
}
}
model.update_focused_conversation_for_terminal(terminal_view_id, Some(conv_id));
// Bridge the controller's lifecycle into the streamer's
// per-conversation consumer registry.
register_agent_event_consumer(*conversation_id, terminal_view_id, ctx);
// Emit so subscribers can move this conversation to the Active section.
ctx.emit(ActiveAgentViewsEvent::TerminalViewFocused);
}
AgentViewControllerEvent::ExitedAgentView {
conversation_id, ..
conversation_id,
is_exit_before_new_entrance,
..
} => {
// Skip if this exit is part of an in-place switch — the follow-up
// entrance will register the new conversation's consumer.
if *is_exit_before_new_entrance {
return;
}
model
.last_opened_times
.remove(&ConversationOrTaskId::ConversationId(*conversation_id));
// Clear the focused conversation in whichever window owns this terminal view.
for state in model.focused_terminal_states.values_mut() {
if state.focused_terminal_id == terminal_view_id
&& !matches!(
state.active_conversation_id,
Some(ConversationOrTaskId::TaskId(_))
)
{
state.active_conversation_id = None;
}
}
model.update_focused_conversation_for_terminal(terminal_view_id, None);
unregister_agent_event_consumer(*conversation_id, terminal_view_id, ctx);
// Emit so subscribers can move this conversation to the Past section.
ctx.emit(ActiveAgentViewsEvent::ConversationClosed {
conversation_id: *conversation_id,
@@ -198,6 +247,9 @@ impl ActiveAgentViewsModel {
}
if let Some(conversation_id) = closed_conversation_id {
// The pane-close path bypasses exit_agent_view_internal, so
// unregister the streamer consumer here.
unregister_agent_event_consumer(conversation_id, terminal_pane_id, ctx);
ctx.emit(ActiveAgentViewsEvent::ConversationClosed { conversation_id });
}
}
@@ -252,6 +304,12 @@ impl ActiveAgentViewsModel {
.and_then(|state| state.active_conversation_id)
}
pub fn get_focused_terminal_view_id(&self, window_id: WindowId) -> Option<EntityId> {
self.focused_terminal_states
.get(&window_id)
.map(|state| state.focused_terminal_id)
}
/// Get the last focused terminal view id (persisted across non-terminal focus changes).
pub fn get_last_focused_terminal_id(&self) -> Option<EntityId> {
self.last_focused_terminal_state
@@ -259,6 +317,14 @@ impl ActiveAgentViewsModel {
.map(|state| state.focused_terminal_id)
}
/// Get the most recent focused conversation or ambient task ID, persisted
/// across non-terminal focus changes.
pub fn get_last_focused_conversation(&self) -> Option<ConversationOrTaskId> {
self.last_focused_terminal_state
.as_ref()
.and_then(|state| state.active_conversation_id)
}
/// Returns the focused conversation ID if it's a new/empty conversation view.
/// Only returns Some if the focused agent view was just created to start a new
/// conversation (i.e. has no exchanges yet).
@@ -307,6 +373,8 @@ impl ActiveAgentViewsModel {
task_id: AmbientAgentTaskId,
ctx: &mut ModelContext<Self>,
) {
self.ambient_sessions
.retain(|view_id, id| *view_id == terminal_view_id || *id != task_id);
let existing = self.ambient_sessions.insert(terminal_view_id, task_id);
if existing != Some(task_id) {
self.last_opened_times
@@ -323,9 +391,11 @@ impl ActiveAgentViewsModel {
ctx: &mut ModelContext<Self>,
) {
if let Some(task_id) = self.ambient_sessions.remove(&terminal_view_id) {
self.last_opened_times
.remove(&ConversationOrTaskId::TaskId(task_id));
ctx.emit(ActiveAgentViewsEvent::AmbientSessionClosed { task_id });
if !self.ambient_sessions.values().any(|id| *id == task_id) {
self.last_opened_times
.remove(&ConversationOrTaskId::TaskId(task_id));
ctx.emit(ActiveAgentViewsEvent::AmbientSessionClosed { task_id });
}
}
}
@@ -443,6 +513,23 @@ impl ActiveAgentViewsModel {
None
}
pub fn get_terminal_view_id_for_entry(
&self,
entry: &AgentConversationEntry,
ctx: &AppContext,
) -> Option<EntityId> {
if let Some(task_id) = entry.identity.ambient_agent_task_id {
if let Some(terminal_view_id) = self.get_terminal_view_id_for_ambient_task(task_id) {
return Some(terminal_view_id);
}
}
if let Some(conversation_id) = entry.identity.local_conversation_id {
return self.get_terminal_view_id_for_conversation(conversation_id, ctx);
}
None
}
/// Get all currently active conversation IDs.
/// A conversation is active if it is open and a query has been sent since it was last opened.
/// New (empty) conversations and ambient sessions are always considered active when open.