13492 lines · 502.6 KB · Rust
RawDownload
use std::{    cell::Cell,    fmt,    path::PathBuf,    rc::Rc,    sync::{        Arc,        atomic::{AtomicBool, Ordering},    },    time::Duration,}; use acp_thread::{AcpThread, AcpThreadEvent, MentionUri, ThreadStatus, line_range_suffix};use agent::{ContextServerRegistry, SharedThread, ThreadStore};use agent_client_protocol::schema::v1 as acp;use agent_servers::AgentServer;use agent_settings::UserAgentsMd;use collections::HashSet;use db::kvp::{Dismissable, KeyValueStore};use itertools::Itertools;use project::{AgentId, ProjectItem};use serde::{Deserialize, Serialize}; use zed_actions::{    DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize,    agent::{        AddSelectionToThread, ConflictContent, LogoutAgent, OpenSettings, ReauthenticateAgent,        ResetAgentZoom, ResetOnboarding, ResolveConflictedFilesWithAgent,        ResolveConflictsWithAgent, ReviewBranchDiff, SelectAgent,    },    assistant::{        FocusAgent, ManageSkills, OpenGlobalAgentsMdRules, OpenProjectAgentsMdRules, Toggle,        ToggleFocus,    },}; use crate::ExpandMessageEditor;use crate::ManageProfiles;use crate::agent_connection_store::AgentConnectionStore;use crate::completion_provider::{AgentContextSelection, AgentContextSource};use crate::terminal_thread_metadata_store::{    TerminalThreadMetadata, TerminalThreadMetadataStore, compose_terminal_thread_title,    terminal_title_without_prefix,};use crate::thread_metadata_store::{ThreadId, ThreadMetadataStore, ThreadMetadataStoreEvent};use crate::{    Agent, AgentInitialContent, AgentThreadSource, ExternalSourcePrompt, NewExternalAgentThread,    NewNativeAgentThreadFromSummary,};use crate::{    AgentDiffPane, ConversationView, CopyThreadToClipboard, Follow, LoadThreadFromClipboard,    NewTerminalThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, ResetFastModeWarnings,    ResetTrialEndUpsell, ResetTrialUpsell, ShowAllSidebarThreadMetadata, ShowThreadMetadata,    ToggleNewThreadMenu, ToggleOptionsMenu,    conversation_view::{        AcpThreadViewEvent, RootThreadUpdated, ThreadView, reset_fast_mode_warnings,    },    ui::{AgentNotification, AgentNotificationEvent, EndTrialUpsell},};use agent_settings::AgentSettings;use ai_onboarding::AgentPanelOnboarding;use anyhow::{Context as _, Result, anyhow};#[cfg(feature = "audio")]use audio::{Audio, Sound};use chrono::{DateTime, Utc};use client::UserStore;use cloud_api_types::Plan;use collections::HashMap;use editor::{Editor, MultiBuffer};use extension_host::ExtensionStore;use feature_flags::{CreateThreadToolFeatureFlag, FeatureFlagAppExt as _}; use fs::Fs;use futures::FutureExt as _;use gpui::{    Action, Anchor, Animation, AnimationExt, AnyElement, App, AsyncWindowContext, ClipboardItem,    Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels,    PlatformDisplay, Subscription, Task, TaskExt, WeakEntity, WindowHandle, prelude::*,    pulsating_between,};use language::LanguageRegistry;use language_model::LanguageModelRegistry;use notifications::status_toast::StatusToast;use project::{Project, ProjectPath, Worktree};use settings::TerminalDockPosition;use settings::{NotifyWhenAgentWaiting, Settings, update_settings_file}; use search::{BufferSearchBar, buffer_search::Deploy as DeployBufferSearch};use terminal::{Event as TerminalEvent, terminal_settings::TerminalSettings};use terminal_view::{TerminalView, terminal_panel::TerminalPanel};use text::OffsetRangeExt;use theme_settings::ThemeSettings;use ui::{    ContextMenu, ContextMenuEntry, GradientFade, IconButton, KeyBinding, PopoverMenu,    PopoverMenuHandle, ProjectEmptyState, Tab, Tooltip, prelude::*, utils::WithRemSize,};use util::ResultExt as _;use workspace::{    CollaboratorId, DraggedSelection, DraggedTab, MultiWorkspace, PathList, SerializedPathList,    ToggleWorkspaceSidebar, ToggleZoom, ToolbarItemView, Workspace, WorkspaceId,    dock::{DockPosition, Panel, PanelEvent},    item::{ItemEvent, ItemHandle},}; const AGENT_PANEL_KEY: &str = "agent_panel";const MIN_PANEL_WIDTH: Pixels = px(300.);const LAST_USED_AGENT_KEY: &str = "agent_panel__last_used_external_agent";const LAST_CREATED_ENTRY_KIND_KEY: &str = "agent_panel__last_created_entry_kind";const TERMINAL_AGENT_TELEMETRY_ID: &str = "terminal";const TERMINAL_INIT_COMMAND_STARTUP_TIMEOUT: Duration = Duration::from_secs(5);const KNOWN_TERMINAL_AGENT_COMMANDS: &[&str] = &[    "agent", // Unfortunately, both Cursor cli + grok    "agy",    "aider",    "amp",    "claude",    "codex",    "copilot",    "crush",    "devin",    "droid",    "gemini",    "goose",    "grok",    "openhands",    "opencode",    "pi",    "qwen",]; fn is_known_terminal_agent_command(command: &str) -> bool {    KNOWN_TERMINAL_AGENT_COMMANDS.contains(&command)} fn terminal_program_to_report(    last_observed_program: &mut Option<String>,    current_program: Option<String>,) -> Option<String> {    let current_program =        current_program.filter(|program| is_known_terminal_agent_command(program));    let program_to_report =        if current_program.is_some() && current_program != *last_observed_program {            current_program.clone()        } else {            None        };    *last_observed_program = current_program;    program_to_report} /// Maximum number of idle threads kept in the agent panel's retained list./// Set as a GPUI global to override; otherwise defaults to 5.pub struct MaxIdleRetainedThreads(pub usize);impl gpui::Global for MaxIdleRetainedThreads {} impl MaxIdleRetainedThreads {    pub fn global(cx: &App) -> usize {        cx.try_global::<Self>().map_or(5, |g| g.0)    }} #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]pub struct TerminalId(uuid::Uuid); impl TerminalId {    pub(crate) fn new() -> Self {        Self(uuid::Uuid::new_v4())    }     pub(crate) fn to_key_string(self) -> String {        self.0.hyphenated().to_string()    }     pub(crate) fn from_key_string(key: &str) -> anyhow::Result<Self> {        Ok(Self(uuid::Uuid::parse_str(key)?))    }} impl fmt::Display for TerminalId {    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {        self.0.fmt(formatter)    }} #[derive(Clone, Debug)]pub struct AgentPanelTerminalInfo {    pub id: TerminalId,    pub title: SharedString,    pub created_at: DateTime<Utc>,    pub has_notification: bool,    pub custom_title: Option<SharedString>,    pub working_directory: Option<PathBuf>,} #[derive(Serialize, Deserialize)]struct LastUsedAgent {    agent: Agent,} #[derive(Serialize, Deserialize)]struct LastCreatedEntryKind {    entry_kind: AgentPanelEntryKind,} struct SourcePanelInitialization {    agent: Agent,    initial_content: Option<AgentInitialContent>,} /// Reads the most recently used agent across all workspaces. Used as a fallback/// when opening a workspace that has no per-workspace agent preference yet.fn read_global_last_used_agent(kvp: &KeyValueStore) -> Option<Agent> {    kvp.read_kvp(LAST_USED_AGENT_KEY)        .log_err()        .flatten()        .and_then(|json| serde_json::from_str::<LastUsedAgent>(&json).log_err())        .map(|entry| entry.agent)} async fn write_global_last_used_agent(kvp: KeyValueStore, agent: Agent) {    if let Some(json) = serde_json::to_string(&LastUsedAgent { agent }).log_err() {        kvp.write_kvp(LAST_USED_AGENT_KEY.to_string(), json)            .await            .log_err();    }} fn read_global_last_created_entry_kind(kvp: &KeyValueStore) -> Option<AgentPanelEntryKind> {    kvp.read_kvp(LAST_CREATED_ENTRY_KIND_KEY)        .log_err()        .flatten()        .and_then(|json| serde_json::from_str::<LastCreatedEntryKind>(&json).log_err())        .map(|entry| entry.entry_kind)} fn project_agents_md_path(    project: &Entity<Project>,    require_existing_file: bool,    cx: &App,) -> Option<PathBuf> {    let rel_path = util::rel_path::RelPath::from_unix_str("AGENTS.md").ok()?;    project        .read(cx)        .visible_worktrees(cx)        .next()        .and_then(|worktree| {            let worktree = worktree.read(cx);             if require_existing_file {                let entry = worktree.entry_for_path(rel_path)?;                if !entry.is_file() {                    return None;                }            }             Some(worktree.absolutize(rel_path))        })} fn open_global_rules(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {    workspace        .open_abs_path(            paths::agents_file().clone(),            workspace::OpenOptions {                focus: Some(true),                ..Default::default()            },            window,            cx,        )        .detach_and_log_err(cx);} fn open_project_rules(workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {    if let Some(path) = project_agents_md_path(workspace.project(), false, cx) {        workspace            .open_abs_path(                path,                workspace::OpenOptions {                    focus: Some(true),                    ..Default::default()                },                window,                cx,            )            .detach_and_log_err(cx);    }} async fn write_global_last_created_entry_kind(kvp: KeyValueStore, entry_kind: AgentPanelEntryKind) {    if let Some(json) = serde_json::to_string(&LastCreatedEntryKind { entry_kind }).log_err() {        kvp.write_kvp(LAST_CREATED_ENTRY_KIND_KEY.to_string(), json)            .await            .log_err();    }} fn read_serialized_panel(    workspace_id: workspace::WorkspaceId,    kvp: &KeyValueStore,) -> Option<SerializedAgentPanel> {    let scope = kvp.scoped(AGENT_PANEL_KEY);    let key = i64::from(workspace_id).to_string();    scope        .read(&key)        .log_err()        .flatten()        .and_then(|json| serde_json::from_str::<SerializedAgentPanel>(&json).log_err())} async fn save_serialized_panel(    workspace_id: workspace::WorkspaceId,    panel: SerializedAgentPanel,    kvp: KeyValueStore,) -> Result<()> {    let scope = kvp.scoped(AGENT_PANEL_KEY);    let key = i64::from(workspace_id).to_string();    scope.write(key, serde_json::to_string(&panel)?).await?;    Ok(())} /// Migration: reads the original single-panel format stored under the/// `"agent_panel"` KVP key before per-workspace keying was introduced.fn read_legacy_serialized_panel(kvp: &KeyValueStore) -> Option<SerializedAgentPanel> {    kvp.read_kvp(AGENT_PANEL_KEY)        .log_err()        .flatten()        .and_then(|json| serde_json::from_str::<SerializedAgentPanel>(&json).log_err())} #[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum ThreadTitleRegenerationResult {    NotOpen,    Started,    NoModel,    AlreadyGenerating,} #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]enum AgentPanelEntryKind {    #[default]    Thread,    Terminal,} #[derive(Serialize, Deserialize, Debug)]struct SerializedAgentPanel {    selected_agent: Option<Agent>,    #[serde(default)]    last_created_entry_kind: AgentPanelEntryKind,    #[serde(default)]    last_active_thread: Option<SerializedActiveThread>,    #[serde(default)]    last_active_terminal_id: Option<String>,    #[serde(default)]    new_draft_thread_id: Option<ThreadId>,} #[derive(Serialize, Deserialize, Debug)]struct SerializedActiveThread {    /// For drafts this is `None`; use `thread_id` to address them instead.    session_id: Option<String>,    /// Optional for back-compat with older serialized payloads that only carried `session_id`.    #[serde(default)]    thread_id: Option<ThreadId>,    agent_type: Agent,    title: Option<String>,    work_dirs: Option<SerializedPathList>,} pub fn init(cx: &mut App) {    cx.observe_new(        |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {            workspace                .register_action(|workspace, _: &NewThread, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        panel.update(cx, |panel, cx| {                            panel.new_thread_with_workspace(Some(workspace), window, cx)                        });                        workspace.focus_panel::<AgentPanel>(window, cx);                    }                })                .register_action(|workspace, _: &NewTerminalThread, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        panel.update(cx, |panel, cx| {                            panel.new_terminal(                                Some(workspace),                                AgentThreadSource::AgentPanel,                                window,                                cx,                            )                        });                        workspace.focus_panel::<AgentPanel>(window, cx);                    }                })                .register_action(                    |workspace, action: &NewNativeAgentThreadFromSummary, window, cx| {                        if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                            panel.update(cx, |panel, cx| {                                panel.new_native_agent_thread_from_summary(action, window, cx)                            });                            workspace.focus_panel::<AgentPanel>(window, cx);                        }                    },                )                .register_action(|workspace, _: &ExpandMessageEditor, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        workspace.focus_panel::<AgentPanel>(window, cx);                        panel.update(cx, |panel, cx| panel.expand_message_editor(window, cx));                    }                })                .register_action(|workspace, _: &OpenSettings, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        workspace.focus_panel::<AgentPanel>(window, cx);                        panel.update(cx, |panel, cx| panel.open_configuration(window, cx));                    }                })                .register_action(|workspace, action: &NewExternalAgentThread, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        workspace.focus_panel::<AgentPanel>(window, cx);                        panel.update(cx, |panel, cx| {                            panel.new_external_agent_thread(action, window, cx);                        });                    }                })                .register_action(|workspace, action: &SelectAgent, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        panel.update(cx, |panel, cx| {                            let agent = AgentId::new(action.agent.clone()).into();                            panel.select_agent(agent, window, cx);                        });                    }                })                .register_action(|workspace, action: &ManageSkills, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        workspace.focus_panel::<AgentPanel>(window, cx);                        panel.update(cx, |panel, cx| panel.manage_skills(action, window, cx));                    }                })                .register_action(|workspace, _: &OpenGlobalAgentsMdRules, window, cx| {                    open_global_rules(workspace, window, cx);                })                .register_action(|workspace, _: &OpenProjectAgentsMdRules, window, cx| {                    open_project_rules(workspace, window, cx);                })                .register_action(|workspace, _: &Follow, window, cx| {                    workspace.follow(CollaboratorId::Agent, window, cx);                })                .register_action(|workspace, _: &OpenAgentDiff, window, cx| {                    let thread = workspace                        .panel::<AgentPanel>(cx)                        .and_then(|panel| panel.read(cx).active_conversation_view().cloned())                        .and_then(|conversation| {                            conversation                                .read(cx)                                .root_thread_view()                                .map(|r| r.read(cx).thread.clone())                        });                     if let Some(thread) = thread {                        AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);                    }                })                .register_action(|workspace, _: &ToggleOptionsMenu, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        workspace.focus_panel::<AgentPanel>(window, cx);                        panel.update(cx, |panel, cx| {                            panel.toggle_options_menu(&ToggleOptionsMenu, window, cx);                        });                    }                })                .register_action(|workspace, _: &ToggleNewThreadMenu, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        workspace.focus_panel::<AgentPanel>(window, cx);                        panel.update(cx, |panel, cx| {                            panel.toggle_new_thread_menu(&ToggleNewThreadMenu, window, cx);                        });                    }                })                .register_action(|_workspace, _: &ResetOnboarding, window, cx| {                    window.dispatch_action(workspace::RestoreBanner.boxed_clone(), cx);                    window.refresh();                })                .register_action(|workspace, _: &ResetTrialUpsell, _window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        panel.update(cx, |panel, _| {                            panel                                .new_user_onboarding_upsell_dismissed                                .store(false, Ordering::Release);                        });                    }                    OnboardingUpsell::set_dismissed(false, cx);                })                .register_action(|_workspace, _: &ResetTrialEndUpsell, _window, cx| {                    TrialEndUpsell::set_dismissed(false, cx);                })                .register_action(|_workspace, _: &ResetFastModeWarnings, _window, cx| {                    reset_fast_mode_warnings(cx);                })                .register_action(|workspace, _: &ResetAgentZoom, window, cx| {

Lines 1–500 of 13492.