Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 63 additions & 23 deletions app/src/ai/agent_conversations_model.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#[allow(dead_code)]
pub mod entry;

pub use entry::AgentConversationEntry;

use crate::ai::active_agent_views_model::ActiveAgentViewsModel;
use crate::ai::agent::api::ServerConversationToken;
use crate::ai::agent::conversation::{AIConversationId, ConversationStatus};
Expand Down Expand Up @@ -273,7 +278,7 @@ impl AgentRunDisplayStatus {
return Self::from_task_state(task);
}
let history_model = BlocklistAIHistoryModel::as_ref(app);
AgentConversationsModel::conversation_id_shadowed_by_task(task, history_model)
entry::conversation_id_shadowed_by_task(task, history_model)
.and_then(|conversation_id| history_model.conversation(&conversation_id))
.map(|conversation| Self::from_conversation_status(conversation.status()))
.unwrap_or_else(|| Self::from_task_state(task))
Expand Down Expand Up @@ -1418,32 +1423,67 @@ impl AgentConversationsModel {
}
}

/// Returns the local conversation ID represented by the given task, if this task and a
/// conversation entry both point at the same underlying local run.
///
/// We first match using the orchestration agent ID (task ID / run ID under v2), and fall back
/// to the server conversation token for cases where the task only carries conversation identity
/// through `conversation_id`.
fn conversation_id_shadowed_by_task(
task: &AmbientAgentTask,
history_model: &BlocklistAIHistoryModel,
) -> Option<AIConversationId> {
history_model
.conversation_id_for_agent_id(&task.run_id().to_string())
.or_else(|| {
task.conversation_id().and_then(|conversation_id| {
history_model.find_conversation_id_by_server_token(
&ServerConversationToken::new(conversation_id.to_string()),
)
})
})
}

fn conversation_ids_shadowed_by_tasks(&self, app: &AppContext) -> HashSet<AIConversationId> {
let history_model = BlocklistAIHistoryModel::as_ref(app);
self.tasks
.values()
.filter_map(|task| Self::conversation_id_shadowed_by_task(task, history_model))
.filter_map(|task| entry::conversation_id_shadowed_by_task(task, history_model))
.collect()
}

/// Returns normalized, owned entries for agent management/navigation surfaces.
///
/// This projection keeps task, local conversation, and cloud metadata identity together while
/// leaving the current `ConversationOrTask` call sites unchanged.
#[allow(dead_code)]
pub fn get_entries(
&self,
filters: &AgentManagementFilters,
app: &AppContext,
) -> Vec<AgentConversationEntry> {
let history_model = BlocklistAIHistoryModel::as_ref(app);
let mut entries = Vec::new();
let mut attached_conversation_ids = HashSet::new();
let mut emitted_conversation_ids = HashSet::new();

for task in self.tasks.values() {
let entry = entry::entry_for_task(task, history_model, app);
if let Some(conversation_id) = entry.identity.local_conversation_id {
attached_conversation_ids.insert(conversation_id);
}
entries.push(entry);
}

for metadata in self.conversations.values() {
let conversation_id = metadata.nav_data.id;
if attached_conversation_ids.contains(&conversation_id) {
continue;
}
let entry = entry::entry_for_conversation(metadata, history_model, app);
emitted_conversation_ids.insert(conversation_id);
entries.push(entry);
}

for metadata in history_model.get_local_conversations_metadata() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] get_local_conversations_metadata() excludes metadata with ambient_agent_task_id, so cloud ambient conversations without a current task row are dropped from get_entries; add an ambient-metadata pass or iterate all metadata and only skip child conversations explicitly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is intended for this increment.

if attached_conversation_ids.contains(&metadata.id)
|| emitted_conversation_ids.contains(&metadata.id)
{
continue;
}
let nav_data =
ConversationNavigationData::from_historical_conversation_metadata(metadata);
entries.push(entry::entry_for_historical_metadata(
metadata,
nav_data,
history_model,
app,
));
}

entries
.into_iter()
.filter(|entry| entry.matches_filters(filters, app))
.sorted_by(|a, b| b.display.last_updated.cmp(&a.display.last_updated))
.collect()
}

Expand Down
Loading
Loading