//! Async task-result application: routes task results into state. use super::auth::{ ensure_login_method, handle_auth_complete, handle_auth_url_ready, handle_mcp_auth_trigger_done, }; use super::ctx::{find_agent_by_session_id, get_active_agent_mut}; use super::notes::{handle_btw_response, handle_memory_note_saved}; use super::prompt::{ defer_to_open_reload_window, handle_compact_complete, handle_prompt_response, handle_suggestion_debounce_expired, }; use super::rewind::{ dispatch_rewind_success, handle_rewind_execute_failed, handle_rewind_points_loaded, handle_rewind_preview_complete, handle_rewind_preview_failed, }; use super::router::{dispatch, dispatch_action_result}; use super::session::foreign::{ handle_foreign_sessions_scanned, handle_session_list_failed, handle_session_list_loaded, }; use super::session::fork::{ handle_fork_session_failed, handle_fork_session_ready, handle_worktree_forked, }; use super::session::lifecycle::{ dispatch_exit_session, handle_session_created, handle_switch_model_complete, handle_worktree_session_created, handle_worktree_session_failed, }; use super::session::load::{ handle_card_detail_loaded, handle_deep_search_results, handle_session_load_failed, handle_session_loaded, handle_session_restore_failed, handle_session_restored, handle_session_search_debounce_expired, remove_session_from_pickers, }; use super::settings::ui::apply_setting_rollback; use super::status::{handle_context_info_complete, handle_usage_fetched, scrub_error_for_toast}; use super::transcript::{ handle_hooks_list_loaded, handle_mcp_toggle_done, handle_plugins_list_loaded, handle_skills_toggle_done, }; use super::turn::handle_bg_task_killed; use crate::app::actions::{ ClipboardPasteCompletion, ClipboardPasteContext, ClipboardPasteFailure, ClipboardPasteTarget, Effect, ProbedAttachment, SubagentKillOutcome, TaskResult, }; use crate::app::app_view::{ActiveView, AppView, AuthState}; use crate::scrollback::block::RenderBlock; use agent_client_protocol as acp; pub(super) fn unregister_session_effect(session_id: Option) -> Vec { session_id .map(|sid| Effect::UnregisterActiveSession { session_id: sid }) .into_iter() .collect() } pub(super) fn unregister_all_active_sessions(app: &AppView) -> Vec { app.agents .values() .filter_map(|a| { a.session .session_id .as_ref() .map(|sid| Effect::UnregisterActiveSession { session_id: sid.clone(), }) }) .collect() } pub(super) const X11_PRIMARY_PASTE_HINT: &str = "Try Shift+Insert to paste selected text"; fn show_clipboard_toast(target: &ClipboardPasteTarget, message: &str, app: &mut AppView) { match target { ClipboardPasteTarget::AgentPrompt { agent_id, .. } => { if let Some(agent) = app.agents.get_mut(agent_id) { agent.show_toast(message); } } ClipboardPasteTarget::DashboardDispatch | ClipboardPasteTarget::DashboardPeek { .. } => { if let Some(dashboard) = app.dashboard.as_mut() { dashboard.error_toast = Some(message.to_owned()); } } } } pub(super) fn maybe_show_x11_primary_paste_hint( eligible: bool, completion: ClipboardPasteCompletion, target: &ClipboardPasteTarget, app: &mut AppView, ) { if !eligible || completion != ClipboardPasteCompletion::FullMiss { return; } show_clipboard_toast(target, X11_PRIMARY_PASTE_HINT, app); } pub(super) fn show_clipboard_failure( target: &ClipboardPasteTarget, failure: ClipboardPasteFailure, app: &mut AppView, ) { let message = match failure { ClipboardPasteFailure::AlreadyReported => return, ClipboardPasteFailure::TextRead => "Couldn't read clipboard text", ClipboardPasteFailure::AttachmentRead => "Couldn't read clipboard contents", ClipboardPasteFailure::TargetInsertion => "Couldn't paste clipboard contents", }; show_clipboard_toast(target, message, app); } fn apply_clipboard_paste_result( ctx: ClipboardPasteContext, image: ProbedAttachment, file_urls: Option, app: &mut AppView, ) -> ClipboardPasteCompletion { match ctx.target.clone() { ClipboardPasteTarget::AgentPrompt { agent_id, .. } => app .agents .get_mut(&agent_id) .map_or(ClipboardPasteCompletion::Dropped, |agent| { agent.complete_clipboard_attachment_paste(ctx, image, file_urls) }), ClipboardPasteTarget::DashboardDispatch | ClipboardPasteTarget::DashboardPeek { .. } => app .dashboard .as_mut() .map_or(ClipboardPasteCompletion::Dropped, |dashboard| { dashboard.complete_clipboard_attachment_paste(ctx, image, file_urls) }), } } fn drain_clipboard_target(target: &ClipboardPasteTarget, app: &mut AppView) -> Vec { match target { ClipboardPasteTarget::AgentPrompt { agent_id, .. } => { let is_active = app.active_view == ActiveView::Agent(*agent_id); let Some(agent) = app.agents.get_mut(agent_id) else { return vec![]; }; let resend = agent.take_deferred_send_after_paste(); let action = if is_active { resend.and_then(|kind| agent.build_deferred_send_action(kind)) } else { None }; let mut effects = std::mem::take(&mut agent.pending_effects); if let Some(action) = action { effects.extend(dispatch(action, app)); } effects } ClipboardPasteTarget::DashboardDispatch | ClipboardPasteTarget::DashboardPeek { .. } => { let Some(dashboard) = app.dashboard.as_mut() else { return vec![]; }; let resends = dashboard.take_deferred_sends_after_paste(); let mut effects = std::mem::take(&mut dashboard.pending_effects); if matches!(app.active_view, ActiveView::AgentDashboard) { for action in resends { effects.extend(dispatch(action, app)); } } effects } } } /// Handle a completed async task result. pub(super) fn dispatch_task_result(result: TaskResult, app: &mut AppView) -> Vec { match result { TaskResult::SessionCreated { agent_id, session_id, models: new_models, } => handle_session_created(app, agent_id, session_id, new_models), TaskResult::SessionFailed { agent_id, error } => { tracing::error!( agent = ? agent_id, error = % error, "Session creation failed" ); if let Some(agent) = app.agents.get_mut(&agent_id) { agent.pending_extensions_fetch = false; agent.session.prompt_history_loading = false; } vec![] } TaskResult::WorktreeSessionCreated { agent_id, session_id, worktree_path, session_cwd, models: new_models, } => handle_worktree_session_created( app, agent_id, session_id, worktree_path, session_cwd, new_models, ), TaskResult::WorktreeForked { agent_id, session_id, worktree_path, session_cwd, code_restored, restore_summary, restore_degree, } => handle_worktree_forked( app, agent_id, session_id, worktree_path, session_cwd, code_restored, restore_summary, restore_degree, ), TaskResult::WorktreeSessionFailed { agent_id, error } => { handle_worktree_session_failed(app, agent_id, error) } TaskResult::ForkSessionReady { agent_id, new_session_id, cwd, } => handle_fork_session_ready(app, agent_id, new_session_id, cwd), TaskResult::ForkSessionFailed { agent_id, error } => { handle_fork_session_failed(app, agent_id, error) } TaskResult::UsageFetched { agent_id, result } => { handle_usage_fetched(app, agent_id, result) } TaskResult::SessionLoaded { agent_id, session_id, models: new_models, code_restored, restore_summary, restore_degree, running_prompt_id, } => handle_session_loaded( app, agent_id, session_id, new_models, code_restored, restore_summary, restore_degree, running_prompt_id, ), TaskResult::SessionTitleFromDisk { agent_id, title } => { if let Some(agent) = app.agents.get_mut(&agent_id) && let Some((t, is_manual)) = title.filter(|(s, _)| !s.trim().is_empty()) { if is_manual && agent.display_name.is_none() { agent.display_name = Some(t.clone()); } agent.generated_session_title = Some(t); } vec![] } TaskResult::SessionLoadFailed { agent_id, session_id, error, } => handle_session_load_failed(app, agent_id, session_id, error), TaskResult::SessionListLoaded { sessions, seq, query, } => handle_session_list_loaded(app, sessions, seq, query), TaskResult::ForeignSessionsScanned { entries, seq } => { handle_foreign_sessions_scanned(app, entries, seq) } TaskResult::ForeignResumeCwdCanonicalized { requested_cwd, canonical_cwd, launch_token, } => { let accepted_cwd = canonical_cwd.clone(); if app.accept_foreign_resume_canonical_cwd(launch_token, &requested_cwd, canonical_cwd) && let Some(canonical_cwd) = accepted_cwd { vec![Effect::DetectForeignResumeHint { canonical_cwd, compat: app.foreign_session_compat, kigi_home: kigi_tools::util::kigi_home::kigi_home(), launch_token, }] } else { vec![] } } TaskResult::ForeignResumeHintDetected { canonical_cwd, launch_token, hint, } => { app.apply_foreign_resume_detection(launch_token, &canonical_cwd, hint); vec![] } TaskResult::SessionListFailed { error, seq, query } => { handle_session_list_failed(app, error, seq, query) } TaskResult::SessionSearchDebounceExpired { query, seq } => { handle_session_search_debounce_expired(app, query, seq) } TaskResult::RosterLoaded { sessions } => { app.leader_roster = sessions; vec![] } TaskResult::RosterFailed { error } => { tracing::debug!(error = % error, "leader roster fetch failed"); vec![] } TaskResult::DashboardSessionsLoaded { sessions } => { app.dashboard_local_sessions = sessions; app.dashboard_sessions_loading = false; vec![] } TaskResult::CardDetailLoaded { source, session_id, generation, detail, } => handle_card_detail_loaded(app, source, session_id, generation, detail), TaskResult::SessionRestored { agent_id, local_session_id, } => handle_session_restored(app, agent_id, local_session_id), TaskResult::SessionRestoreFailed { agent_id, error } => { handle_session_restore_failed(app, agent_id, error) } TaskResult::SessionRestoreProgress { agent_id, message } => { if let Some(agent) = app.agents.get_mut(&agent_id) && !defer_to_open_reload_window(agent, agent_id, "SessionRestoreProgress") { agent.scrollback.push_block(RenderBlock::system(message)); } vec![] } TaskResult::PromptResponse { agent_id, result, http_status, prompt_id, } => handle_prompt_response(app, agent_id, result, http_status, prompt_id), TaskResult::SendPromptNowFailed { agent_id, session_id, prompt_id, error, blocks, } => { let sid = session_id.0.to_string(); super::queue::retire_optimistic_echo( &mut app.optimistic_prompt_echoes, &mut app.shared_prompt_queues, &sid, &prompt_id, ); if let Some(agent) = app.agents.get_mut(&agent_id) { agent.shared_queue.retain(|e| e.id != prompt_id); agent.note_queue_echo_retired(&prompt_id); if agent.expect_send_now_cancel.as_deref() == Some(prompt_id.as_str()) || agent.follow_without_jump_prompt_id.as_deref() == Some(prompt_id.as_str()) { agent.clear_send_now_expectation(); } agent.retire_send_now_painted_block(&prompt_id); let text = blocks .iter() .find_map(|b| match b { acp::ContentBlock::Text(t) => Some(t.text.clone()), _ => None, }) .unwrap_or_default(); let id = agent.session.next_queue_id; agent.session.next_queue_id += 1; agent .session .pending_prompts .push_front(crate::app::agent::QueuedPrompt { wire_blocks: Some(blocks), ..crate::app::agent::QueuedPrompt::plain( id, &text, crate::app::agent::QueueEntryKind::Prompt, ) }); agent.show_toast(&format!("Send now failed — requeued: {error}")); } vec![] } TaskResult::PreferredModelPersisted { result } => { if let Err(err) = result && let Some(agent) = get_active_agent_mut(app) { agent.scrollback.push_block(RenderBlock::system(format!( "Couldn't save preferred model: {err} (still active for this session)" ))); } vec![] } TaskResult::CancelComplete => { tracing::trace!("Cancel notification sent successfully"); vec![] } TaskResult::KillSubagentComplete { session_id, subagent_id, outcome, } => { if let SubagentKillOutcome::NothingLive { status } = outcome { let status = status.as_deref().unwrap_or("cancelled"); crate::app::acp_handler::finalize_killed_subagent( app, &session_id, &subagent_id, status, ); } vec![] } TaskResult::CompactComplete { agent_id, result } => { handle_compact_complete(app, agent_id, result) } TaskResult::SwitchModelComplete { agent_id, model_id, effort, result, prev_model_id, } => handle_switch_model_complete(app, agent_id, model_id, effort, result, prev_model_id), TaskResult::BgTaskKilled { session_id, task_id, outcome, } => handle_bg_task_killed(app, session_id, task_id, outcome), TaskResult::BgTaskKillFailed { session_id, task_id, error, } => { tracing::warn!( task_id = % task_id, error = % error, "Failed to kill bg task" ); if let Some(agent) = find_agent_by_session_id(&mut app.agents, &session_id) && let Some(task) = agent.session.bg_tasks.get_mut(&task_id) { task.pending_kill = false; task.kill_requested_at = None; } vec![] } TaskResult::ChangelogFetched { markdown, entries } => { app.changelog_markdown = markdown; app.changelog_bullets = kigi_shell::util::changelog::bullets_from_entries(&entries, 3); vec![] } TaskResult::ClipboardAttachmentProbed { ctx, image, file_urls, } => { let is_clipboard_key = ctx.source.is_clipboard_key(); let primary_hint_eligible = is_clipboard_key && !app.screen_mode.is_minimal() && crate::clipboard::x11_primary_guidance_available(); let target = ctx.target.clone(); let wrap_text = if is_clipboard_key { ctx.source.text().map(str::to_owned) } else { None }; let completion = apply_clipboard_paste_result(ctx, image, file_urls, app); let wrap_request_emitted = completion == ClipboardPasteCompletion::FullMiss && is_clipboard_key && crate::wrap_clipboard_image::maybe_request_wrap_host_image( None, wrap_text.as_deref(), None, ); let effects = drain_clipboard_target(&target, app); maybe_show_x11_primary_paste_hint( primary_hint_eligible && !wrap_request_emitted, completion, &target, app, ); if let ClipboardPasteCompletion::Failed(failure) = completion { show_clipboard_failure(&target, failure, app); } effects } TaskResult::PromptImagePreviewPrepared => vec![], TaskResult::PromptHistoryLoaded { agent_id, prompts } => { use kigi_tools::implementations::skills::skill::extract_skill_display_text; if let Some(agent) = app.agents.get_mut(&agent_id) { agent.session.prompt_history_loading = false; agent.session.prompt_history = prompts .into_iter() .map(|p| extract_skill_display_text(&p).unwrap_or(p)) .collect(); if agent.prompt.history_search.is_active() { let history = agent.combined_prompt_history(); agent.prompt.history_search.refresh_items(&history); if !agent.prompt.history_search.is_browse() { let query = agent.prompt.text().to_owned(); agent.prompt.history_search.update_query(&query); } } } vec![] } TaskResult::AuthComplete { request_seq, meta } => { handle_auth_complete(app, request_seq, meta) } TaskResult::AuthFailed { request_seq, error } => { if let AuthState::Authenticating { request_seq: current_seq, .. } = &app.auth_state && *current_seq == request_seq { app.auth_state = AuthState::Pending { error: Some(error) }; app.auth_code_input.clear(); } vec![] } TaskResult::AuthUrlReady { request_seq, auth_url, external, mode, } => handle_auth_url_ready(app, request_seq, auth_url, external, mode), TaskResult::AuthCodeSubmitted { .. } => vec![], TaskResult::McpsListLoaded { agent_id, result } => { use crate::views::extensions_modal::TabDataState; if let Some(agent) = app.agents.get_mut(&agent_id) && let Some(ref mut modal) = agent.extensions_modal { modal.pending_action = None; modal.pending_entry_index = None; modal.mcps_data = match result { Ok(response) => TabDataState::Loaded(response), Err(e) => TabDataState::Error(e), }; } vec![] } TaskResult::McpAuthTriggerDone { agent_id, server_name, result, } => handle_mcp_auth_trigger_done(app, agent_id, server_name, result), TaskResult::HooksListLoaded { agent_id, result } => { handle_hooks_list_loaded(app, agent_id, result) } TaskResult::PluginsListLoaded { agent_id, result } => { handle_plugins_list_loaded(app, agent_id, result) } TaskResult::HooksActionResult { agent_id, result } | TaskResult::PluginsActionResult { agent_id, result } => { dispatch_action_result(app, agent_id, result) } TaskResult::McpToggleDone { agent_id, result } => { handle_mcp_toggle_done(app, agent_id, result) } TaskResult::SkillsListLoaded { agent_id, result } => { use crate::views::extensions_modal::TabDataState; if let Some(agent) = app.agents.get_mut(&agent_id) && let Some(ref mut modal) = agent.extensions_modal { modal.skills_data = match result { Ok(skills) => TabDataState::Loaded(skills), Err(e) => TabDataState::Error(e), }; modal.pending_action = None; modal.pending_entry_index = None; } vec![] } TaskResult::SkillsToggleDone { agent_id, result } => { handle_skills_toggle_done(app, agent_id, result) } TaskResult::SessionAgentNameResolved { agent_id, agent_name, } => { if let Some(agent) = app.agents.get_mut(&agent_id) { agent.session_agent_name = agent_name.clone(); if let Some(modal) = agent.agents_modal.as_mut() { modal.active_agent = agent_name; } } vec![] } TaskResult::SessionInfoComplete { agent_id, info, text, } => { if let Some(agent) = app.agents.get_mut(&agent_id) { agent.session_agent_name = info.data.agent_name.clone(); if let Some(modal) = agent.agents_modal.as_mut() { modal.active_agent = info.data.agent_name.clone(); } agent.apply_full_context_info(info.data.context); agent .scrollback .push_block(crate::scrollback::block::RenderBlock::system(text)); } vec![] } TaskResult::SessionInfoFailed { agent_id, error } => { if let Some(agent) = app.agents.get_mut(&agent_id) { agent .scrollback .push_block(crate::scrollback::block::RenderBlock::system(format!( "Couldn't load session info: {error}" ))); } vec![] } TaskResult::RenameSessionComplete { agent_id, title } => { if let Some(agent) = app.agents.get_mut(&agent_id) { let safe = crate::views::session_title::sanitize_display_text(&title); agent .scrollback .push_block(crate::scrollback::block::RenderBlock::system(format!( "Session renamed to \"{safe}\"" ))); } vec![] } TaskResult::RenameSessionFailed { agent_id, error } => { if let Some(agent) = app.agents.get_mut(&agent_id) { agent .scrollback .push_block(crate::scrollback::block::RenderBlock::system(format!( "Couldn't rename session: {error}" ))); } vec![] } TaskResult::DeleteSessionComplete { source, session_id } => { remove_session_from_pickers(app, &source, &session_id); app.show_toast("Session deleted"); vec![] } TaskResult::DeleteSessionFailed { source, session_id, error, } => { tracing::warn!( source, session_id = % session_id, error = % error, "session delete failed" ); app.show_toast(&format!("Couldn't delete session: {error}")); vec![] } TaskResult::ContextInfoComplete { agent_id, info } => { handle_context_info_complete(app, agent_id, info) } TaskResult::ContextInfoFailed { agent_id, error } => { if let Some(agent) = app.agents.get_mut(&agent_id) { agent .scrollback .push_block(crate::scrollback::block::RenderBlock::system(format!( "Couldn't load context info: {error}" ))); } vec![] } TaskResult::MemoryNoteSaved { agent_id, result } => { handle_memory_note_saved(app, agent_id, result) } TaskResult::MemoryNoteRewritten { agent_id, result, nonce, } => { if let Some(agent) = app.agents.get_mut(&agent_id) && let Ok(markdown) = result && let Some(crate::views::modal::ActiveModal::RememberNoteReview { ref mut enhanced_content, ref mut cached_lines, rewrite_nonce, .. }) = agent.active_modal && rewrite_nonce == nonce { *enhanced_content = Some(markdown); *cached_lines = None; } vec![] } TaskResult::BundleStatusReady { has_cache, version, personas, roles, agents, skills, persona_details, role_details, } => { app.bundle_state.has_cache = has_cache; app.bundle_state.version = version.unwrap_or_default(); app.bundle_state.personas = personas; app.bundle_state.roles = roles; app.bundle_state.agents = agents; app.bundle_state.skills = skills; app.bundle_state.persona_details = persona_details; app.bundle_state.role_details = role_details; vec![] } TaskResult::BundleStatusFailed { error } => { tracing::warn!(error = % error, "bundle status fetch failed"); vec![] } TaskResult::CatalogEntryReady { kind, name, content, } => { if let ActiveView::Agent(id) = app.active_view && let Some(agent) = app.agents.get_mut(&id) { let title = format!("{kind}: {name}"); agent.block_viewer = Some( crate::views::block_viewer::BlockViewerPane::for_plain_text(&title, &content), ); } vec![] } TaskResult::CatalogEntryFailed { error } => { tracing::warn!(error = % error, "catalog entry fetch failed"); if let ActiveView::Agent(id) = app.active_view && let Some(agent) = app.agents.get_mut(&id) { agent .scrollback .push_block(RenderBlock::system(format!("Couldn't load entry: {error}"))); } vec![] } TaskResult::BtwResponse { agent_id, result } => handle_btw_response(app, agent_id, result), TaskResult::InterjectQueued { .. } => vec![], TaskResult::RecapRequested { session_id, auto, error, } => { if let Some(error) = error { tracing::debug!(% error, "recap request failed"); if !auto && let Some(agent) = find_agent_by_session_id(&mut app.agents, &session_id.0) && let Some(pending_id) = agent.pending_recap_entry.take() { agent.scrollback.remove_entry(pending_id); agent.show_toast(super::recap_unavailable_toast( super::scrollback_has_user_messages(&agent.scrollback), )); } } vec![] } TaskResult::InterjectFailed { agent_id, error, text, blocks, } => { if let Some(agent) = app.agents.get_mut(&agent_id) { let id = agent.session.next_queue_id; agent.session.next_queue_id += 1; agent .session .pending_prompts .push_front(crate::app::agent::QueuedPrompt { id, text, kind: crate::app::agent::QueueEntryKind::Prompt, wire_blocks: blocks, images: Vec::new(), display_as_skill: false, task_id: None, human_schedule: None, chip_elements: Vec::new(), skill_token_ranges: Vec::new(), }); agent.show_toast(&format!("Interjection failed — requeued: {error}")); } vec![] } TaskResult::AvailableCommandsRefreshed { agent_id, commands } => { if !commands.is_empty() && let Some(agent) = app.agents.get_mut(&agent_id) { agent.session.available_commands = commands; agent.session.available_commands_generation += 1; } vec![] } TaskResult::AuthCopiedTimeout => { app.auth_clipboard_copied = false; vec![] } TaskResult::LogoutComplete => { app.auth_state = AuthState::Pending { error: None }; app.login_method_id = None; ensure_login_method(app); app.auth_clipboard_copied = false; let effects = dispatch_exit_session(app); app.welcome_prompt_focused = false; effects } TaskResult::DeepSearchResults { results, seq } => { handle_deep_search_results(app, results, seq) } TaskResult::RewindPointsLoaded { agent_id, points } => { handle_rewind_points_loaded(app, agent_id, points) } TaskResult::RewindPointsFailed { agent_id, error } => { let Some(agent) = app.agents.get_mut(&agent_id) else { return vec![]; }; agent.rewind_state = None; app.show_toast(&format!("Undo failed: {error}")); vec![] } TaskResult::RewindPreviewComplete { agent_id, response, target_prompt_index, mode, } => handle_rewind_preview_complete(app, agent_id, response, target_prompt_index, mode), TaskResult::RewindPreviewFailed { agent_id, error } => { handle_rewind_preview_failed(app, agent_id, error) } TaskResult::RewindExecuteComplete { agent_id, response } => { dispatch_rewind_success(app, agent_id, response) } TaskResult::RewindExecuteFailed { agent_id, error } => { handle_rewind_execute_failed(app, agent_id, error) } TaskResult::SuggestionDebounceExpired { agent_id, generation, } => handle_suggestion_debounce_expired(app, agent_id, generation), TaskResult::ShellSuggestionsLoaded { agent_id, response, request_text, request_cursor, } => { let Some(agent) = app.agents.get_mut(&agent_id) else { return vec![]; }; if agent.prompt_input_mode != crate::app::agent_view::PromptInputMode::Bash { return vec![]; } let generation = response.generation; agent .prompt .suggestions .on_suggestions_loaded(response, &request_text, request_cursor); let text = agent.prompt.text().to_owned(); agent.prompt.suggestions.set_last_request_text(&text); let mark = agent.pending_effects.len(); if agent.prompt.suggestions.take_pending_tab(generation) { agent.shell_completion_tab(); } agent.pending_effects.split_off(mark) } TaskResult::PromptSuggestionLoaded { agent_id, suggestion, generation, } => { if let Some(agent) = app.agents.get_mut(&agent_id) { agent .prompt .prompt_suggestion .on_loaded(suggestion, generation); agent.refresh_prompt_suggestion_gate(); } vec![] } TaskResult::SettingPersisted { key, value } => { tracing::trace!(target : "settings", ? key, ? value, "setting persisted"); vec![] } TaskResult::SettingPersistFailed { key, rollback_value, error, } => { let rollback_effects = apply_setting_rollback(app, key, &rollback_value); tracing::warn!( target : "settings", ? key, ? rollback_value, % error, "setting persist failed; rolled back" ); let scrubbed = scrub_error_for_toast(&error); app.show_toast(&format!("\u{2717} Could not save {key}: {scrubbed}")); rollback_effects } TaskResult::SettingPersistFailedBestEffort { key, error } => { tracing::warn!( target : "settings", ? key, % error, "setting persist failed (best-effort); in-memory state stays at optimistic value", ); let scrubbed = scrub_error_for_toast(&error); app.show_toast(&format!("\u{2717} Could not save {key}: {scrubbed}")); vec![] } } }