first pass of merging in warp (doesn't build)
This commit is contained in:
+305
-308
@@ -8,62 +8,9 @@ use galaxyui::scene::DropShadow;
|
||||
use galaxyui::ui_components::button::ButtonVariant;
|
||||
use lazy_static::lazy_static;
|
||||
use pathfinder_color::ColorU;
|
||||
use pathfinder_geometry::vector::vec2f;
|
||||
use settings::Setting;
|
||||
use siphasher::sip::SipHasher;
|
||||
|
||||
use crate::ai::agent::conversation::AIConversationId;
|
||||
use crate::ai::agent_conversations_model::{
|
||||
AgentConversationsModel, AgentConversationsModelEvent, AgentManagementFilters, ArtifactFilter,
|
||||
ConversationOrTask, CreatedOnFilter, CreatorFilter, EnvironmentFilter, HarnessFilter,
|
||||
OwnerFilter, SessionStatus, SourceFilter, StatusFilter,
|
||||
};
|
||||
use crate::ai::agent_management::agent_type_selector::{
|
||||
AgentType, AgentTypeSelector, AgentTypeSelectorEvent,
|
||||
};
|
||||
use crate::ai::agent_management::cloud_setup_guide_view::{
|
||||
CloudSetupGuideEvent, CloudSetupGuideView,
|
||||
};
|
||||
use crate::ai::agent_management::details_action_buttons::{
|
||||
ActionButtonsConfig, AgentDetailsButtonEvent, ConversationActionButtonsRow,
|
||||
};
|
||||
use crate::ai::agent_management::telemetry::{
|
||||
AgentManagementTelemetryEvent, ArtifactType, FilterType, OpenedFrom,
|
||||
};
|
||||
use crate::ai::ambient_agents::AmbientAgentTaskId;
|
||||
use crate::ai::ambient_agents::{cancel_task_with_toast, AgentSource};
|
||||
use crate::ai::artifacts::{Artifact, ArtifactButtonsRow, ArtifactButtonsRowEvent};
|
||||
use crate::ai::blocklist::history_model::BlocklistAIHistoryModel;
|
||||
use crate::ai::conversation_details_panel::{
|
||||
ConversationDetailsData, ConversationDetailsPanel, ConversationDetailsPanelEvent,
|
||||
};
|
||||
use crate::ai::conversation_status_ui::render_status_element;
|
||||
use crate::ai::harness_display;
|
||||
use crate::app_state::PersistedAgentManagementFilters;
|
||||
use crate::appearance::Appearance;
|
||||
use crate::auth::AuthStateProvider;
|
||||
use crate::editor::{
|
||||
EditorView, Event as EditorEvent, PropagateAndNoOpNavigationKeys,
|
||||
PropagateHorizontalNavigationKeys, SingleLineEditorOptions, TextOptions,
|
||||
};
|
||||
use crate::menu::{MenuItem, MenuItemFields};
|
||||
use crate::notebooks::NotebookId;
|
||||
use crate::settings::ai::AISettings;
|
||||
use crate::ui_components::avatar::{Avatar, AvatarContent};
|
||||
use crate::util::time_format::format_approx_duration_from_now_utc;
|
||||
use crate::view_components::action_button::{
|
||||
ActionButton, ButtonSize, NakedTheme, PrimaryTheme, SecondaryTheme,
|
||||
};
|
||||
use crate::view_components::compactible_action_button::{
|
||||
CompactibleActionButton, MEDIUM_SIZE_SWITCH_THRESHOLD,
|
||||
};
|
||||
use crate::view_components::dropdown::{Dropdown, DropdownAction, DropdownStyle};
|
||||
use crate::view_components::DismissibleToast;
|
||||
use crate::view_components::FilterableDropdown;
|
||||
use crate::workflows::WorkflowType;
|
||||
use crate::workspace::{ForkedConversationDestination, ToastStack};
|
||||
use crate::workspace::{RestoreConversationLayout, WorkspaceAction};
|
||||
use crate::workspaces::user_workspaces::UserWorkspaces;
|
||||
use crate::{send_telemetry_from_ctx, AgentModeEntrypoint};
|
||||
use galaxy_cli::agent::Harness;
|
||||
use galaxy_core::ui::icons::Icon;
|
||||
use galaxy_core::ui::theme::color::internal_colors;
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
@@ -79,15 +26,71 @@ use galaxyui::elements::{
|
||||
SizeConstraintCondition, SizeConstraintSwitch, Stack, Text, Wrap,
|
||||
};
|
||||
use galaxyui::fonts::{Properties, Weight};
|
||||
use galaxyui::keymap::FixedBinding;
|
||||
use galaxyui::platform::Cursor;
|
||||
use galaxyui::ui_components::components::UiComponent;
|
||||
use galaxyui::ui_components::components::UiComponentStyles;
|
||||
use galaxyui::ui_components::components::{UiComponent, UiComponentStyles};
|
||||
use galaxyui::{
|
||||
keymap::FixedBinding, Action, AppContext, Entity, FocusContext, ModelHandle, SingletonEntity,
|
||||
TypedActionView, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
AppContext, Entity, FocusContext, ModelHandle, SingletonEntity, TypedActionView, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use pathfinder_geometry::vector::vec2f;
|
||||
use settings::Setting;
|
||||
|
||||
use crate::ai::agent::conversation::AIConversationId;
|
||||
use crate::ai::agent_conversations_model::{
|
||||
AgentConversationEntry, AgentConversationEntryId, AgentConversationNavigationSubject,
|
||||
AgentConversationsModel, AgentConversationsModelEvent, AgentManagementFilters, ArtifactFilter,
|
||||
ConversationUpdateKind, CreatedOnFilter, CreatorFilter, EnvironmentFilter, HarnessFilter,
|
||||
OwnerFilter, SessionStatus, SourceFilter, StatusFilter,
|
||||
};
|
||||
use crate::ai::agent_management::agent_type_selector::{
|
||||
AgentType, AgentTypeSelector, AgentTypeSelectorEvent,
|
||||
};
|
||||
use crate::ai::agent_management::cloud_setup_guide_view::{
|
||||
CloudSetupGuideEvent, CloudSetupGuideView,
|
||||
};
|
||||
use crate::ai::agent_management::details_action_buttons::{
|
||||
ActionButtonsConfig, AgentDetailsButtonEvent, ConversationActionButtonsRow,
|
||||
};
|
||||
use crate::ai::agent_management::telemetry::{
|
||||
AgentManagementTelemetryEvent, ArtifactType, FilterType, OpenedFrom,
|
||||
};
|
||||
use crate::ai::ambient_agents::{cancel_task_with_toast, AgentSource};
|
||||
use crate::ai::artifacts::{Artifact, ArtifactButtonsRow, ArtifactButtonsRowEvent};
|
||||
use crate::ai::blocklist::format_credits;
|
||||
use crate::ai::conversation_details_panel::{
|
||||
ConversationDetailsData, ConversationDetailsPanel, ConversationDetailsPanelEvent,
|
||||
};
|
||||
use crate::ai::harness_availability::HarnessAvailabilityModel;
|
||||
use crate::ai::harness_display;
|
||||
use crate::app_state::PersistedAgentManagementFilters;
|
||||
use crate::appearance::Appearance;
|
||||
use crate::auth::AuthStateProvider;
|
||||
use crate::editor::{
|
||||
EditorView, Event as EditorEvent, PropagateAndNoOpNavigationKeys,
|
||||
PropagateHorizontalNavigationKeys, SingleLineEditorOptions, TextOptions,
|
||||
};
|
||||
use crate::menu::{MenuItem, MenuItemFields};
|
||||
use crate::notebooks::NotebookId;
|
||||
use crate::settings::ai::AISettings;
|
||||
use crate::ui_components::agent_icon::agent_conversation_entry_icon_variant;
|
||||
use crate::ui_components::avatar::{Avatar, AvatarContent};
|
||||
use crate::ui_components::icon_with_status::render_icon_with_status;
|
||||
use crate::util::time_format::format_approx_duration_from_now_utc;
|
||||
use crate::view_components::action_button::{
|
||||
ActionButton, ButtonSize, NakedTheme, PrimaryTheme, SecondaryTheme,
|
||||
};
|
||||
use crate::view_components::compactible_action_button::{
|
||||
CompactibleActionButton, MEDIUM_SIZE_SWITCH_THRESHOLD,
|
||||
};
|
||||
use crate::view_components::dropdown::{
|
||||
Dropdown, DropdownAction, DropdownItemAction, DropdownStyle,
|
||||
};
|
||||
use crate::view_components::{DismissibleToast, FilterableDropdown};
|
||||
use crate::workflows::WorkflowType;
|
||||
use crate::workspace::{
|
||||
ForkedConversationDestination, RestoreConversationLayout, ToastStack, WorkspaceAction,
|
||||
};
|
||||
use crate::workspaces::user_workspaces::UserWorkspaces;
|
||||
use crate::{send_telemetry_from_ctx, AgentModeEntrypoint};
|
||||
|
||||
lazy_static! {
|
||||
static ref HASHER: SipHasher = SipHasher::new_with_keys(0, 0);
|
||||
@@ -105,8 +108,9 @@ const CARD_CONTENT_PADDING: f32 = 12.;
|
||||
const CARD_BORDER_RADIUS: f32 = 4.;
|
||||
const CARD_MARGIN_BOTTOM: f32 = 8.;
|
||||
|
||||
const STATUS_ICON_SIZE: f32 = 12.;
|
||||
const BUTTON_SIZE: f32 = 20.;
|
||||
/// Total size of the agent icon-with-status component rendered in each card's header row.
|
||||
const CARD_AGENT_ICON_SIZE: f32 = 24.;
|
||||
const CREATOR_AVATAR_FONT_SIZE: f32 = 10.;
|
||||
|
||||
const SESSION_EXPIRED_TEXT: &str = "Sessions expire after one week and cannot be opened.";
|
||||
@@ -125,21 +129,7 @@ fn should_show_artifacts(artifacts: &[Artifact]) -> bool {
|
||||
!artifacts.is_empty() && FeatureFlag::ConversationArtifacts.is_enabled()
|
||||
}
|
||||
|
||||
/// Identifies a card item - either a task ID or a conversation ID
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ManagementCardItemId {
|
||||
Task(AmbientAgentTaskId),
|
||||
Conversation(AIConversationId),
|
||||
}
|
||||
|
||||
impl ManagementCardItemId {
|
||||
fn as_key(&self) -> String {
|
||||
match self {
|
||||
ManagementCardItemId::Task(id) => format!("task_{id}"),
|
||||
ManagementCardItemId::Conversation(id) => format!("conv_{id}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub type ManagementCardItemId = AgentConversationEntryId;
|
||||
|
||||
/// Store state for a given task row
|
||||
struct CardState {
|
||||
@@ -222,6 +212,13 @@ impl AgentManagementView {
|
||||
Self::handle_agent_management_model_event,
|
||||
);
|
||||
|
||||
ctx.subscribe_to_model(
|
||||
&HarnessAvailabilityModel::handle(ctx),
|
||||
|me, _, _event, ctx| {
|
||||
me.update_harness_dropdown(ctx);
|
||||
},
|
||||
);
|
||||
|
||||
let list_state = Self::construct_fresh_list_state(ctx.handle());
|
||||
|
||||
let all_filter_button = ctx.add_typed_action_view(|_ctx| {
|
||||
@@ -496,7 +493,7 @@ impl AgentManagementView {
|
||||
let make_status_option =
|
||||
|label: &str, action: AgentManagementViewAction, icon_data: Option<(Icon, Fill)>| {
|
||||
let mut fields = MenuItemFields::new(label)
|
||||
.with_on_select_action(DropdownAction::SelectActionAndClose(action));
|
||||
.with_on_select_action(DropdownAction::select_action_and_close(action));
|
||||
if let Some((icon, color)) = icon_data {
|
||||
fields = fields.with_icon(icon).with_override_icon_color(color);
|
||||
}
|
||||
@@ -546,7 +543,7 @@ impl AgentManagementView {
|
||||
}
|
||||
|
||||
/// Build the list of source filter items.
|
||||
fn build_source_dropdown_items() -> Vec<MenuItem<DropdownAction<AgentManagementViewAction>>> {
|
||||
fn build_source_dropdown_items() -> Vec<MenuItem<DropdownAction>> {
|
||||
// Build up the sources list
|
||||
let mut sources = vec![
|
||||
AgentSource::WebApp,
|
||||
@@ -564,14 +561,16 @@ impl AgentManagementView {
|
||||
}
|
||||
|
||||
let mut items = vec![MenuItem::Item(
|
||||
MenuItemFields::new("All").with_on_select_action(DropdownAction::SelectActionAndClose(
|
||||
AgentManagementViewAction::SetSourceFilter(SourceFilter::All),
|
||||
)),
|
||||
MenuItemFields::new("All").with_on_select_action(
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetSourceFilter(SourceFilter::All),
|
||||
),
|
||||
),
|
||||
)];
|
||||
for source in sources {
|
||||
items.push(MenuItem::Item(
|
||||
MenuItemFields::new(source.display_name()).with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetSourceFilter(SourceFilter::Specific(source)),
|
||||
),
|
||||
),
|
||||
@@ -601,22 +600,22 @@ impl AgentManagementView {
|
||||
|
||||
let items = vec![
|
||||
MenuItem::Item(MenuItemFields::new("All").with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetCreatedOnFilter(CreatedOnFilter::All),
|
||||
),
|
||||
)),
|
||||
MenuItem::Item(MenuItemFields::new("Last 24 hours").with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetCreatedOnFilter(CreatedOnFilter::Last24Hours),
|
||||
),
|
||||
)),
|
||||
MenuItem::Item(MenuItemFields::new("Past 3 days").with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetCreatedOnFilter(CreatedOnFilter::Past3Days),
|
||||
),
|
||||
)),
|
||||
MenuItem::Item(MenuItemFields::new("Last week").with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetCreatedOnFilter(CreatedOnFilter::LastWeek),
|
||||
),
|
||||
)),
|
||||
@@ -635,29 +634,29 @@ impl AgentManagementView {
|
||||
|
||||
let items = vec![
|
||||
MenuItem::Item(MenuItemFields::new("All").with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(AgentManagementViewAction::SetArtifactFilter(
|
||||
ArtifactFilter::All,
|
||||
)),
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetArtifactFilter(ArtifactFilter::All),
|
||||
),
|
||||
)),
|
||||
MenuItem::Item(MenuItemFields::new("Pull Request").with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(AgentManagementViewAction::SetArtifactFilter(
|
||||
ArtifactFilter::PullRequest,
|
||||
)),
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetArtifactFilter(ArtifactFilter::PullRequest),
|
||||
),
|
||||
)),
|
||||
MenuItem::Item(MenuItemFields::new("Plan").with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(AgentManagementViewAction::SetArtifactFilter(
|
||||
ArtifactFilter::Plan,
|
||||
)),
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetArtifactFilter(ArtifactFilter::Plan),
|
||||
),
|
||||
)),
|
||||
MenuItem::Item(MenuItemFields::new("Screenshot").with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(AgentManagementViewAction::SetArtifactFilter(
|
||||
ArtifactFilter::Screenshot,
|
||||
)),
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetArtifactFilter(ArtifactFilter::Screenshot),
|
||||
),
|
||||
)),
|
||||
MenuItem::Item(MenuItemFields::new("File").with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(AgentManagementViewAction::SetArtifactFilter(
|
||||
ArtifactFilter::File,
|
||||
)),
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetArtifactFilter(ArtifactFilter::File),
|
||||
),
|
||||
)),
|
||||
];
|
||||
|
||||
@@ -672,17 +671,27 @@ impl AgentManagementView {
|
||||
let mut dropdown = Dropdown::new(ctx);
|
||||
Self::setup_filter_menu(&mut dropdown, "Harness", ctx);
|
||||
|
||||
// "All" has no leading icon, matching the Status dropdown's "All" row.
|
||||
let items = Self::build_harness_dropdown_items(ctx);
|
||||
dropdown.set_rich_items(items, ctx);
|
||||
dropdown.set_selected_by_index(0, ctx);
|
||||
dropdown
|
||||
}
|
||||
|
||||
fn build_harness_dropdown_items(app: &AppContext) -> Vec<MenuItem<DropdownAction>> {
|
||||
let mut items = vec![MenuItem::Item(
|
||||
MenuItemFields::new("All").with_on_select_action(DropdownAction::SelectActionAndClose(
|
||||
AgentManagementViewAction::SetHarnessFilter(HarnessFilter::All),
|
||||
)),
|
||||
MenuItemFields::new("All").with_on_select_action(
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetHarnessFilter(HarnessFilter::All),
|
||||
),
|
||||
),
|
||||
)];
|
||||
|
||||
for harness in [Harness::Oz, Harness::Claude, Harness::Gemini] {
|
||||
let mut fields = MenuItemFields::new(harness_display::display_name(harness))
|
||||
let availability = HarnessAvailabilityModel::as_ref(app);
|
||||
for entry in availability.available_harnesses() {
|
||||
let harness = entry.harness;
|
||||
let mut fields = MenuItemFields::new(entry.display_name.clone())
|
||||
.with_icon(harness_display::icon_for(harness))
|
||||
.with_on_select_action(DropdownAction::SelectActionAndClose(
|
||||
.with_on_select_action(DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetHarnessFilter(HarnessFilter::Specific(harness)),
|
||||
));
|
||||
if let Some(color) = harness_display::brand_color(harness) {
|
||||
@@ -691,9 +700,7 @@ impl AgentManagementView {
|
||||
items.push(MenuItem::Item(fields));
|
||||
}
|
||||
|
||||
dropdown.set_rich_items(items, ctx);
|
||||
dropdown.set_selected_by_index(0, ctx);
|
||||
dropdown
|
||||
items
|
||||
}
|
||||
|
||||
fn create_environment_dropdown(
|
||||
@@ -732,7 +739,7 @@ impl AgentManagementView {
|
||||
}
|
||||
|
||||
// Initialize the dropdown menu for the filter dropdowns (status, source)
|
||||
fn setup_filter_menu<A: Action + Clone>(
|
||||
fn setup_filter_menu<A: DropdownItemAction>(
|
||||
dropdown: &mut Dropdown<A>,
|
||||
label_prefix: &'static str,
|
||||
ctx: &mut ViewContext<Dropdown<A>>,
|
||||
@@ -744,7 +751,7 @@ impl AgentManagementView {
|
||||
}
|
||||
|
||||
// Initialize the dropdown menu for the searchable filter dropdowns (creator)
|
||||
fn setup_searchable_filter_menu<A: Action + Clone>(
|
||||
fn setup_searchable_filter_menu<A: DropdownItemAction>(
|
||||
dropdown: &mut FilterableDropdown<A>,
|
||||
label_prefix: &'static str,
|
||||
ctx: &mut ViewContext<FilterableDropdown<A>>,
|
||||
@@ -755,6 +762,13 @@ impl AgentManagementView {
|
||||
dropdown.set_button_variant(ButtonVariant::Secondary);
|
||||
}
|
||||
|
||||
fn update_harness_dropdown(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
let items = Self::build_harness_dropdown_items(ctx);
|
||||
self.harness_dropdown.update(ctx, |dropdown, ctx| {
|
||||
dropdown.set_rich_items(items, ctx);
|
||||
});
|
||||
}
|
||||
|
||||
/// Since the valid set of environments depends on what tasks we have loaded in,
|
||||
/// we use this function to update the available options depending on the most recent
|
||||
/// set of tasks.
|
||||
@@ -771,7 +785,7 @@ impl AgentManagementView {
|
||||
self.environment_dropdown.update(ctx, |dropdown, ctx| {
|
||||
let mut items = vec![MenuItem::Item(
|
||||
MenuItemFields::new("All").with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetEnvironmentFilter(EnvironmentFilter::All),
|
||||
),
|
||||
),
|
||||
@@ -779,7 +793,7 @@ impl AgentManagementView {
|
||||
|
||||
items.push(MenuItem::Item(
|
||||
MenuItemFields::new("None").with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetEnvironmentFilter(
|
||||
EnvironmentFilter::NoEnvironment,
|
||||
),
|
||||
@@ -793,7 +807,7 @@ impl AgentManagementView {
|
||||
for (environment_id, environment_name) in sorted_envs {
|
||||
items.push(MenuItem::Item(
|
||||
MenuItemFields::new(environment_name).with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetEnvironmentFilter(
|
||||
EnvironmentFilter::Specific(environment_id),
|
||||
),
|
||||
@@ -818,7 +832,7 @@ impl AgentManagementView {
|
||||
self.creator_dropdown.update(ctx, |dropdown, ctx| {
|
||||
let mut items = vec![MenuItem::Item(
|
||||
MenuItemFields::new("All").with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetCreatorFilter(CreatorFilter::All),
|
||||
),
|
||||
),
|
||||
@@ -826,7 +840,7 @@ impl AgentManagementView {
|
||||
for (name, uid) in creators {
|
||||
items.push(MenuItem::Item(
|
||||
MenuItemFields::new(&name).with_on_select_action(
|
||||
DropdownAction::SelectActionAndClose(
|
||||
DropdownAction::select_action_and_close(
|
||||
AgentManagementViewAction::SetCreatorFilter(CreatorFilter::Specific {
|
||||
name,
|
||||
uid,
|
||||
@@ -938,46 +952,34 @@ impl AgentManagementView {
|
||||
let model = AgentConversationsModel::as_ref(ctx);
|
||||
let search_query = self.search_query.trim().to_lowercase();
|
||||
let cards: Vec<CardData> = model
|
||||
.get_tasks_and_conversations(&self.filters, ctx)
|
||||
.filter(|t| {
|
||||
.get_entries(&self.filters, ctx)
|
||||
.into_iter()
|
||||
.filter(|entry| {
|
||||
if search_query.is_empty() {
|
||||
return true;
|
||||
}
|
||||
match_indices_case_insensitive(&t.title(ctx), &search_query).is_some()
|
||||
match_indices_case_insensitive(&entry.display.title, &search_query).is_some()
|
||||
})
|
||||
.map(|t| {
|
||||
let item_id = match t {
|
||||
ConversationOrTask::Task(task) => ManagementCardItemId::Task(task.task_id),
|
||||
ConversationOrTask::Conversation(conversation) => {
|
||||
ManagementCardItemId::Conversation(conversation.nav_data.id)
|
||||
}
|
||||
};
|
||||
let artifacts = t.artifacts(ctx);
|
||||
|
||||
let copy_link_url = t.session_or_conversation_link(ctx);
|
||||
let mut config = match t {
|
||||
ConversationOrTask::Task(task) => ActionButtonsConfig::for_task(
|
||||
task.task_id,
|
||||
&t.display_status(ctx),
|
||||
None, // Don't show open button in card hover
|
||||
copy_link_url,
|
||||
),
|
||||
ConversationOrTask::Conversation(conversation) => {
|
||||
ActionButtonsConfig::for_conversation(
|
||||
conversation.nav_data.id,
|
||||
None, // Don't show open button in card hover
|
||||
copy_link_url,
|
||||
.map(|entry| {
|
||||
let item_id = entry.id;
|
||||
let copy_link_url = entry
|
||||
.capabilities
|
||||
.can_copy_link
|
||||
.then(|| {
|
||||
AgentConversationsModel::resolve_copy_link(
|
||||
AgentConversationNavigationSubject::Entry(entry.id),
|
||||
ctx,
|
||||
)
|
||||
}
|
||||
};
|
||||
// Show info button in card hover for ViewDetails if feature flag enabled
|
||||
})
|
||||
.flatten();
|
||||
let mut config = Self::action_buttons_config_for_entry(&entry, None, copy_link_url);
|
||||
if FeatureFlag::AgentManagementDetailsView.is_enabled() {
|
||||
config.view_details_item_id = Some(item_id.clone());
|
||||
config.view_details_item_id = Some(item_id);
|
||||
}
|
||||
|
||||
CardData {
|
||||
item_id,
|
||||
artifacts,
|
||||
artifacts: entry.display.artifacts,
|
||||
action_buttons_config: config,
|
||||
}
|
||||
})
|
||||
@@ -1026,11 +1028,8 @@ impl AgentManagementView {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let action_buttons_view = self.create_action_buttons_view(
|
||||
card.item_id.clone(),
|
||||
card.action_buttons_config,
|
||||
ctx,
|
||||
);
|
||||
let action_buttons_view =
|
||||
self.create_action_buttons_view(card.item_id, card.action_buttons_config, ctx);
|
||||
|
||||
new_items.push(CardState {
|
||||
hover_state: MouseStateHandle::default(),
|
||||
@@ -1074,6 +1073,29 @@ impl AgentManagementView {
|
||||
view
|
||||
}
|
||||
|
||||
fn action_buttons_config_for_entry(
|
||||
entry: &AgentConversationEntry,
|
||||
open_action: Option<WorkspaceAction>,
|
||||
copy_link_url: Option<String>,
|
||||
) -> ActionButtonsConfig {
|
||||
if let Some(task_id) = entry.identity.ambient_agent_task_id {
|
||||
ActionButtonsConfig::for_task(
|
||||
task_id,
|
||||
&entry.display.status,
|
||||
open_action,
|
||||
copy_link_url,
|
||||
)
|
||||
} else if let Some(conversation_id) = entry.identity.local_conversation_id {
|
||||
ActionButtonsConfig::for_conversation(conversation_id, open_action, copy_link_url)
|
||||
} else {
|
||||
ActionButtonsConfig {
|
||||
open_action,
|
||||
copy_link_url,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_action_buttons_event(
|
||||
&mut self,
|
||||
item_id: &ManagementCardItemId,
|
||||
@@ -1109,6 +1131,7 @@ impl AgentManagementView {
|
||||
summarize_after_fork: false,
|
||||
summarization_prompt: None,
|
||||
initial_prompt: None,
|
||||
initial_attachments: vec![],
|
||||
destination: ForkedConversationDestination::NewTab,
|
||||
});
|
||||
}
|
||||
@@ -1122,7 +1145,7 @@ impl AgentManagementView {
|
||||
);
|
||||
|
||||
self.update_details_panel_for_item(item_id, ctx);
|
||||
self.selected_item_id = Some(item_id.clone());
|
||||
self.selected_item_id = Some(*item_id);
|
||||
ctx.notify();
|
||||
}
|
||||
AgentDetailsButtonEvent::CopyLink { link } => {
|
||||
@@ -1136,7 +1159,7 @@ impl AgentManagementView {
|
||||
ctx
|
||||
);
|
||||
}
|
||||
ManagementCardItemId::Task(task_id) => {
|
||||
ManagementCardItemId::AmbientRun(task_id) => {
|
||||
send_telemetry_from_ctx!(
|
||||
AgentManagementTelemetryEvent::SessionLinkCopied {
|
||||
task_id: task_id.to_string(),
|
||||
@@ -1237,13 +1260,9 @@ impl AgentManagementView {
|
||||
self.refresh_details_panel_if_needed(ctx);
|
||||
self.get_tasks_from_model(ctx);
|
||||
}
|
||||
AgentConversationsModelEvent::ConversationUpdated => {
|
||||
self.get_tasks_from_model(ctx);
|
||||
self.refresh_details_panel_if_needed(ctx);
|
||||
ctx.notify();
|
||||
AgentConversationsModelEvent::ConversationUpdated { kind } => {
|
||||
self.handle_conversation_updated(*kind, ctx);
|
||||
}
|
||||
// TaskManuallyOpened is handled by the conversation list view, not here.
|
||||
AgentConversationsModelEvent::TaskManuallyOpened => {}
|
||||
AgentConversationsModelEvent::ConversationArtifactsUpdated { conversation_id } => {
|
||||
self.update_artifacts_for_conversation(*conversation_id, ctx);
|
||||
self.refresh_details_panel_if_needed(ctx);
|
||||
@@ -1253,11 +1272,48 @@ impl AgentManagementView {
|
||||
|
||||
/// Refresh the details panel if it's currently showing an item
|
||||
fn refresh_details_panel_if_needed(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
if let Some(item_id) = self.selected_item_id.clone() {
|
||||
if let Some(item_id) = self.selected_item_id {
|
||||
self.update_details_panel_for_item(&item_id, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Decide how much work a `ConversationUpdated` event requires, based on its kind and the
|
||||
/// active status filter:
|
||||
/// * `Restored`: the underlying status didn't change, so the visible cards don't change
|
||||
/// either. Just refresh the details panel.
|
||||
/// * `MetadataChanged`: rebuild the cards so metadata-derived actions update.
|
||||
/// * `TitleChanged`: rebuild the cards so filtering and titles update.
|
||||
/// * `StatusSet` that crosses the active status filter: rebuild the
|
||||
/// card list via `get_tasks_from_model`.
|
||||
/// * `StatusSet` that doesn't cross the active filter (or `All` is active):
|
||||
/// just refresh the details panel re-render so the status icon picks up the new value.
|
||||
fn handle_conversation_updated(
|
||||
&mut self,
|
||||
kind: ConversationUpdateKind,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match kind {
|
||||
ConversationUpdateKind::Restored => {}
|
||||
ConversationUpdateKind::MetadataChanged => self.get_tasks_from_model(ctx),
|
||||
ConversationUpdateKind::TitleChanged => self.get_tasks_from_model(ctx),
|
||||
ConversationUpdateKind::StatusSet {
|
||||
prev_filter,
|
||||
new_filter,
|
||||
} => {
|
||||
if self
|
||||
.filters
|
||||
.status
|
||||
.is_membership_crossed(prev_filter, new_filter)
|
||||
{
|
||||
self.get_tasks_from_model(ctx);
|
||||
} else {
|
||||
ctx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
self.refresh_details_panel_if_needed(ctx);
|
||||
}
|
||||
|
||||
/// Update the details panel with fresh data for the given item.
|
||||
fn update_details_panel_for_item(
|
||||
&mut self,
|
||||
@@ -1265,75 +1321,28 @@ impl AgentManagementView {
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let model = AgentConversationsModel::as_ref(ctx);
|
||||
|
||||
let data = match item_id {
|
||||
ManagementCardItemId::Task(task_id) => {
|
||||
let Some(task_wrapper) = model.get_task(task_id) else {
|
||||
return;
|
||||
};
|
||||
// Agent management view should always open in a new tab
|
||||
let open_action =
|
||||
task_wrapper.get_open_action(Some(RestoreConversationLayout::NewTab), ctx);
|
||||
let copy_link_url = task_wrapper.session_or_conversation_link(ctx);
|
||||
let Some(task) = model.get_task_data(task_id) else {
|
||||
return;
|
||||
};
|
||||
ConversationDetailsData::from_task(&task, open_action, copy_link_url, ctx)
|
||||
}
|
||||
ManagementCardItemId::Conversation(conversation_id) => {
|
||||
let Some(conversation) = model.get_conversation(conversation_id) else {
|
||||
return;
|
||||
};
|
||||
// Agent management view should always open in a new tab
|
||||
let open_action =
|
||||
conversation.get_open_action(Some(RestoreConversationLayout::NewTab), ctx);
|
||||
|
||||
let history_model = BlocklistAIHistoryModel::as_ref(ctx);
|
||||
let ai_conversation = conversation
|
||||
.navigation_data()
|
||||
.and_then(|nav| history_model.conversation(&nav.id));
|
||||
|
||||
let server_conv_id = ai_conversation
|
||||
.and_then(|c| c.server_conversation_token())
|
||||
.map(|t| t.as_str().to_string())
|
||||
.or_else(|| {
|
||||
conversation
|
||||
.navigation_data()
|
||||
.and_then(|nav| history_model.get_conversation_metadata(&nav.id))
|
||||
.and_then(|m| m.server_conversation_token.as_ref())
|
||||
.map(|t| t.as_str().to_string())
|
||||
});
|
||||
let artifacts = ai_conversation
|
||||
.map(|c| c.artifacts().to_vec())
|
||||
.unwrap_or_default();
|
||||
let status = Some(conversation.status(ctx));
|
||||
let navigation_data = conversation.navigation_data();
|
||||
let copy_link_url = conversation.session_or_conversation_link(ctx);
|
||||
|
||||
// Prefer server-reported harness when available; otherwise treat as a pure
|
||||
// local conversation (always Warp Agent).
|
||||
let harness = navigation_data
|
||||
.and_then(|nav| history_model.get_server_conversation_metadata(&nav.id))
|
||||
.map(|m| Harness::from(m.harness))
|
||||
.or(Some(Harness::Oz));
|
||||
|
||||
ConversationDetailsData::from_conversation_metadata(
|
||||
*conversation_id,
|
||||
conversation.title(ctx),
|
||||
conversation.creator_name(ctx),
|
||||
conversation.created_at().with_timezone(&chrono::Local),
|
||||
navigation_data.and_then(|n| n.initial_working_directory.clone()),
|
||||
conversation.request_usage(ctx),
|
||||
server_conv_id,
|
||||
artifacts,
|
||||
open_action,
|
||||
status,
|
||||
navigation_data.and_then(|n| n.initial_query.clone()),
|
||||
copy_link_url,
|
||||
harness,
|
||||
)
|
||||
}
|
||||
let Some(entry) = model.get_entry_by_id(item_id, ctx) else {
|
||||
return;
|
||||
};
|
||||
let open_action = AgentConversationsModel::resolve_open_action(
|
||||
AgentConversationNavigationSubject::Entry(*item_id),
|
||||
Some(RestoreConversationLayout::NewTab),
|
||||
ctx,
|
||||
);
|
||||
let copy_link_url = AgentConversationsModel::resolve_copy_link(
|
||||
AgentConversationNavigationSubject::Entry(*item_id),
|
||||
ctx,
|
||||
);
|
||||
let task = entry
|
||||
.identity
|
||||
.ambient_agent_task_id
|
||||
.and_then(|task_id| model.get_task_data(&task_id));
|
||||
let data = ConversationDetailsData::from_agent_conversation_entry(
|
||||
&entry,
|
||||
task.as_ref(),
|
||||
open_action,
|
||||
copy_link_url,
|
||||
);
|
||||
|
||||
self.details_panel.update(ctx, |p, ctx| {
|
||||
p.set_conversation_details(data, ctx);
|
||||
@@ -1347,19 +1356,14 @@ impl AgentManagementView {
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let model = AgentConversationsModel::as_ref(ctx);
|
||||
let Some(card_data) = model.get_conversation(&conversation_id) else {
|
||||
return;
|
||||
};
|
||||
let artifacts = card_data.artifacts(ctx);
|
||||
|
||||
// Find the index of the card for this conversation
|
||||
let Some(index) = self
|
||||
.items
|
||||
.iter()
|
||||
.position(|card| card.item_id == ManagementCardItemId::Conversation(conversation_id))
|
||||
else {
|
||||
let Some((index, entry)) = self.items.iter().enumerate().find_map(|(index, card)| {
|
||||
let entry = model.get_entry_by_id(&card.item_id, ctx)?;
|
||||
(entry.identity.local_conversation_id == Some(conversation_id))
|
||||
.then_some((index, entry))
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
let artifacts = entry.display.artifacts;
|
||||
|
||||
// Update the artifact buttons for this card
|
||||
if should_show_artifacts(&artifacts) {
|
||||
@@ -1497,7 +1501,7 @@ impl AgentManagementView {
|
||||
fn render_session_status_label(
|
||||
appearance: &Appearance,
|
||||
mouse_state: MouseStateHandle,
|
||||
session_status: SessionStatus,
|
||||
session_status: &SessionStatus,
|
||||
) -> Box<dyn Element> {
|
||||
let theme = appearance.theme();
|
||||
let font_family = appearance.ui_font_family();
|
||||
@@ -1604,21 +1608,17 @@ impl AgentManagementView {
|
||||
};
|
||||
|
||||
let model = AgentConversationsModel::as_ref(app);
|
||||
let card_data = match &card_state.item_id {
|
||||
ManagementCardItemId::Task(task_id) => model.get_task(task_id),
|
||||
ManagementCardItemId::Conversation(conv_id) => model.get_conversation(conv_id),
|
||||
};
|
||||
let Some(card_data) = card_data else {
|
||||
let Some(entry) = model.get_entry_by_id(&card_state.item_id, app) else {
|
||||
return Empty::new().finish();
|
||||
};
|
||||
|
||||
self.render_card(card_state, &card_data, appearance, app)
|
||||
self.render_card(card_state, &entry, appearance, app)
|
||||
}
|
||||
|
||||
fn render_card(
|
||||
&self,
|
||||
card_state: &CardState,
|
||||
card_data: &ConversationOrTask,
|
||||
entry: &AgentConversationEntry,
|
||||
appearance: &Appearance,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
@@ -1642,10 +1642,8 @@ impl AgentManagementView {
|
||||
let card_hoverable = Hoverable::new(card_state.hover_state.clone(), move |mouse_state| {
|
||||
let mut card_content = Flex::column()
|
||||
.with_spacing(CARD_ROW_SPACING)
|
||||
.with_child(Self::render_header_row(
|
||||
card_state, card_data, appearance, app,
|
||||
))
|
||||
.with_child(Self::render_metadata_row(card_data, appearance, app));
|
||||
.with_child(Self::render_header_row(card_state, entry, appearance))
|
||||
.with_child(Self::render_metadata_row(entry, appearance, app));
|
||||
|
||||
// Add artifacts row if there is a buttons view
|
||||
if let Some(buttons_element) = artifact_buttons_element {
|
||||
@@ -1706,18 +1704,12 @@ impl AgentManagementView {
|
||||
})
|
||||
.with_defer_events_to_children();
|
||||
|
||||
// Add click handler to open session if available
|
||||
let item_id = card_state.item_id.clone();
|
||||
let card_hoverable = if card_data
|
||||
.get_open_action(Some(RestoreConversationLayout::NewTab), app)
|
||||
.is_some()
|
||||
{
|
||||
let item_id = card_state.item_id;
|
||||
let card_hoverable = if entry.capabilities.can_open {
|
||||
card_hoverable
|
||||
.with_cursor(Cursor::PointingHand)
|
||||
.on_click(move |ctx, _, _| {
|
||||
ctx.dispatch_typed_action(AgentManagementViewAction::OpenSession {
|
||||
item_id: item_id.clone(),
|
||||
});
|
||||
ctx.dispatch_typed_action(AgentManagementViewAction::OpenSession { item_id });
|
||||
})
|
||||
} else {
|
||||
card_hoverable
|
||||
@@ -1728,29 +1720,30 @@ impl AgentManagementView {
|
||||
|
||||
fn render_header_row(
|
||||
card_state: &CardState,
|
||||
card_data: &ConversationOrTask,
|
||||
entry: &AgentConversationEntry,
|
||||
appearance: &Appearance,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let theme = appearance.theme();
|
||||
let font_family = appearance.ui_font_family();
|
||||
let font_size = appearance.ui_font_size();
|
||||
|
||||
let title = card_data.title(app);
|
||||
let title_text = Text::new_inline(title, font_family, font_size)
|
||||
let title_text = Text::new_inline(entry.display.title.clone(), font_family, font_size)
|
||||
.with_color(theme.active_ui_text_color().into());
|
||||
|
||||
let status_icon =
|
||||
render_status_element(&card_data.display_status(app), STATUS_ICON_SIZE, appearance);
|
||||
|
||||
// Build the time and avatar elements
|
||||
let last_updated = card_data.last_updated();
|
||||
let time_str = format_approx_duration_from_now_utc(last_updated);
|
||||
let status_icon = render_icon_with_status(
|
||||
agent_conversation_entry_icon_variant(entry),
|
||||
CARD_AGENT_ICON_SIZE,
|
||||
0.,
|
||||
theme,
|
||||
internal_colors::fg_overlay_1(theme),
|
||||
);
|
||||
let time_str = format_approx_duration_from_now_utc(entry.display.last_updated);
|
||||
let time_text = Text::new_inline(time_str, font_family, font_size)
|
||||
.with_color(theme.nonactive_ui_text_color().into());
|
||||
|
||||
let creator_name = card_data
|
||||
.creator_name(app)
|
||||
let creator_name = entry
|
||||
.display
|
||||
.creator
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| "Unknown".to_string());
|
||||
let avatar = Self::render_avatar_with_tooltip(
|
||||
&creator_name,
|
||||
@@ -1763,12 +1756,11 @@ impl AgentManagementView {
|
||||
.with_spacing(2.)
|
||||
.with_child(Container::new(status_icon).with_margin_right(4.).finish())
|
||||
.with_child(Expanded::new(1., title_text.finish()).finish());
|
||||
|
||||
let mut time_and_avatar = Flex::row()
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Center)
|
||||
.with_spacing(4.);
|
||||
|
||||
if let Some(session_status) = card_data.get_session_status() {
|
||||
if let Some(session_status) = &entry.display.session_status {
|
||||
time_and_avatar.add_child(Self::render_session_status_label(
|
||||
appearance,
|
||||
card_state.session_status_hover_state.clone(),
|
||||
@@ -1792,45 +1784,56 @@ impl AgentManagementView {
|
||||
}
|
||||
|
||||
fn render_metadata_row(
|
||||
card_data: &ConversationOrTask,
|
||||
entry: &AgentConversationEntry,
|
||||
appearance: &Appearance,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let theme = appearance.theme();
|
||||
let font_family = appearance.ui_font_family();
|
||||
let font_size = appearance.ui_font_size();
|
||||
|
||||
// Build metadata parts conditionally
|
||||
let mut metadata_parts = Vec::new();
|
||||
|
||||
if let Some(source) = card_data.source() {
|
||||
if let Some(source) = &entry.display.source {
|
||||
metadata_parts.push(format!("Source: {}", source.display_name()));
|
||||
}
|
||||
|
||||
if FeatureFlag::AgentHarness.is_enabled() {
|
||||
if let Some(harness) = card_data.harness() {
|
||||
let availability = HarnessAvailabilityModel::as_ref(app);
|
||||
if availability.should_show_harness_selector() {
|
||||
if let Some(harness) = entry.display.harness {
|
||||
metadata_parts.push(format!(
|
||||
"Harness: {}",
|
||||
harness_display::display_name(harness)
|
||||
availability.display_name_for(harness)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(run_time) = card_data.run_time() {
|
||||
if let Some(executor) = &entry.display.executor {
|
||||
let same_as_creator =
|
||||
executor.uid.is_some() && executor.uid == entry.display.creator.uid;
|
||||
if !same_as_creator {
|
||||
if let Some(name) = executor.name.as_deref().or(executor.uid.as_deref()) {
|
||||
let label = if executor
|
||||
.principal_type
|
||||
.is_some_and(|pt| pt.is_service_account())
|
||||
{
|
||||
"Agent"
|
||||
} else {
|
||||
"Executor"
|
||||
};
|
||||
metadata_parts.push(format!("{label}: {name}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(run_time) = &entry.display.run_time {
|
||||
metadata_parts.push(format!("Run time: {run_time}"));
|
||||
}
|
||||
|
||||
if let Some(usage) = card_data.display_request_usage(app) {
|
||||
if let Some(usage) = entry.display.request_usage.map(format_credits) {
|
||||
metadata_parts.push(format!("Credits used: {usage}"));
|
||||
}
|
||||
|
||||
if let Some(tokens) = card_data.display_total_tokens(app) {
|
||||
metadata_parts.push(format!("Tokens: {tokens}"));
|
||||
}
|
||||
|
||||
let metadata_text = metadata_parts.join(" • ");
|
||||
|
||||
Text::new(metadata_text, font_family, font_size)
|
||||
Text::new(metadata_parts.join(" • "), font_family, font_size)
|
||||
.with_color(theme.nonactive_ui_text_color().into())
|
||||
.finish()
|
||||
}
|
||||
@@ -1947,7 +1950,7 @@ impl AgentManagementView {
|
||||
.with_child(ChildView::new(&self.created_on_dropdown).finish())
|
||||
.with_child(ChildView::new(&self.artifact_dropdown).finish());
|
||||
|
||||
if FeatureFlag::AgentHarness.is_enabled() {
|
||||
if HarnessAvailabilityModel::as_ref(app).should_show_harness_selector() {
|
||||
filters_wrap.add_child(ChildView::new(&self.harness_dropdown).finish());
|
||||
}
|
||||
|
||||
@@ -2356,17 +2359,11 @@ impl TypedActionView for AgentManagementView {
|
||||
ctx.notify();
|
||||
}
|
||||
AgentManagementViewAction::OpenSession { item_id } => {
|
||||
let model = AgentConversationsModel::as_ref(ctx);
|
||||
let card_data = match item_id {
|
||||
ManagementCardItemId::Task(task_id) => model.get_task(task_id),
|
||||
ManagementCardItemId::Conversation(conv_id) => model.get_conversation(conv_id),
|
||||
};
|
||||
let Some(card_data) = card_data else {
|
||||
return;
|
||||
};
|
||||
let Some(action) =
|
||||
card_data.get_open_action(Some(RestoreConversationLayout::NewTab), ctx)
|
||||
else {
|
||||
let Some(action) = AgentConversationsModel::resolve_open_action(
|
||||
AgentConversationNavigationSubject::Entry(*item_id),
|
||||
Some(RestoreConversationLayout::NewTab),
|
||||
ctx,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -2380,7 +2377,7 @@ impl TypedActionView for AgentManagementView {
|
||||
ctx
|
||||
);
|
||||
}
|
||||
ManagementCardItemId::Task(task_id) => {
|
||||
ManagementCardItemId::AmbientRun(task_id) => {
|
||||
send_telemetry_from_ctx!(
|
||||
AgentManagementTelemetryEvent::CloudRunOpened {
|
||||
task_id: task_id.to_string(),
|
||||
|
||||
Reference in New Issue
Block a user