Removes the `skills` feature flag now that Skills are ready to ship to all users. Cleans up all `cx.has_flag::<SkillsFeatureFlag>()` gates across the agent, agent_ui, prompt_store, and title_bar crates, drops the now-unused `feature_flags` dependency from `prompt_store` and `title_bar`, and removes the dead `update_flags(true, vec!["skills"])` calls from the agent's skills tests. Closes AI-269 Release Notes: - Enabled Skills for all users. ---------
Unified · Split
| @@ -13688,7 +13688,6 @@ dependencies = [ · 13687 unmodified lines | |||
| 13688 | "chrono", | 13688 | "chrono", |
| 13689 | "collections", | 13689 | "collections", |
| 13690 | "db", | 13690 | "db", |
| 13691 | "feature_flags", | ||
| 13692 | "fs", | 13691 | "fs", |
| 13693 | "futures 0.3.32", | 13692 | "futures 0.3.32", |
| 13694 | "fuzzy", | 13693 | "fuzzy", |
| @@ -18251,7 +18250,6 @@ dependencies = [ · 4556 unmodified lines | |||
| 18251 | "client", | 18250 | "client", |
| 18252 | "cloud_api_types", | 18251 | "cloud_api_types", |
| 18253 | "db", | 18252 | "db", |
| 18254 | "feature_flags", | ||
| 18255 | "fs", | 18253 | "fs", |
| 18256 | "git_ui", | 18254 | "git_ui", |
| 18257 | "gpui", | 18255 | "gpui", |
| @@ -38,7 +38,7 @@ use agent_skills::{ · 37 unmodified lines | |||
| 38 | use anyhow::{Context as _, Result, anyhow}; | 38 | use anyhow::{Context as _, Result, anyhow}; |
| 39 | use chrono::{DateTime, Utc}; | 39 | use chrono::{DateTime, Utc}; |
| 40 | use collections::{HashMap, HashSet, IndexMap}; | 40 | use collections::{HashMap, HashSet, IndexMap}; |
| 41 | use feature_flags::{FeatureFlagAppExt as _, SkillsFeatureFlag}; | 41 | |
| 42 | use fs::Fs; | 42 | use fs::Fs; |
| 43 | use futures::channel::{mpsc, oneshot}; | 43 | use futures::channel::{mpsc, oneshot}; |
| 44 | use futures::future::Shared; | 44 | use futures::future::Shared; |
| @@ -392,11 +392,11 @@ impl NativeAgent { · 347 unmodified lines | |||
| 392 | /// Kicks off a one-time scan of the global skills directory if one | 392 | /// Kicks off a one-time scan of the global skills directory if one |
| 393 | /// isn't already in progress and a watch isn't already active. | 393 | /// isn't already in progress and a watch isn't already active. |
| 394 | /// | 394 | /// |
| 395 | /// Idempotent and cheap: returns immediately if the user lacks the | 395 | /// Idempotent and cheap: returns immediately if a scan or watch is |
| 396 | /// skills feature flag, or if a scan or watch is already running. | 396 | /// already running. The expected callers are user-interaction events |
| 397 | /// The expected callers are user-interaction events from the agent | 397 | /// from the agent panel (input focus, slash autocomplete, conversation |
| 398 | /// panel (input focus, slash autocomplete, conversation submit); | 398 | /// submit); firing this from any of them is equivalent and safe to |
| 399 | /// firing this from any of them is equivalent and safe to repeat. | 399 | /// repeat. |
| 400 | /// | 400 | /// |
| 401 | /// The scan itself runs detached on the foreground executor. If | 401 | /// The scan itself runs detached on the foreground executor. If |
| 402 | /// `~/.agents/skills/` exists it transitions state to | 402 | /// `~/.agents/skills/` exists it transitions state to |
| @@ -405,9 +405,6 @@ impl NativeAgent { · 2 unmodified lines | |||
| 405 | /// next trigger retries (covering the case where the user creates | 405 | /// next trigger retries (covering the case where the user creates |
| 406 | /// the directory after the first scan). | 406 | /// the directory after the first scan). |
| 407 | pub fn ensure_skills_scan_started(&mut self, cx: &mut Context<Self>) { | 407 | pub fn ensure_skills_scan_started(&mut self, cx: &mut Context<Self>) { |
| 408 | if !cx.has_flag::<SkillsFeatureFlag>() { | ||
| 409 | return; | ||
| 410 | } | ||
| 411 | if !matches!(self.skills_state, SkillsState::Idle) { | 408 | if !matches!(self.skills_state, SkillsState::Idle) { |
| 412 | return; | 409 | return; |
| 413 | } | 410 | } |
| @@ -598,12 +595,10 @@ impl NativeAgent { · 184 unmodified lines | |||
| 598 | // after the thread is constructed are still visible to the | 595 | // after the thread is constructed are still visible to the |
| 599 | // model — without this, the catalog and tool would drift out | 596 | // model — without this, the catalog and tool would drift out |
| 600 | // of sync until the session was reopened. | 597 | // of sync until the session was reopened. |
| 601 | if cx.has_flag::<SkillsFeatureFlag>() { | 598 | thread.add_tool(SkillTool::new( |
| 602 | thread.add_tool(SkillTool::new( | 599 | skills_resolver_for_project(weak.clone(), project_id), |
| 603 | skills_resolver_for_project(weak.clone(), project_id), | 600 | self.fs.clone(), |
| 604 | self.fs.clone(), | 601 | )); |
| 605 | )); | ||
| 606 | } | ||
| 607 | }); | 602 | }); |
| 608 | 603 | ||
| 609 | let subscriptions = vec![ | 604 | let subscriptions = vec![ |
| @@ -822,12 +817,8 @@ impl NativeAgent { · 212 unmodified lines | |||
| 822 | }) | 817 | }) |
| 823 | .collect::<Vec<_>>(); | 818 | .collect::<Vec<_>>(); |
| 824 | 819 | ||
| 825 | // Skills are gated behind the "skills" feature flag. Without it we | ||
| 826 | // skip all on-disk lookups so users see no behavior change. | ||
| 827 | let skills_enabled = cx.has_flag::<SkillsFeatureFlag>(); | ||
| 828 | |||
| 829 | // Load global skills | 820 | // Load global skills |
| 830 | let global_skills_task = if skills_enabled { | 821 | let global_skills_task = { |
| 831 | let global_skills_dir = global_skills_dir(); | 822 | let global_skills_dir = global_skills_dir(); |
| 832 | let global_skills_fs = fs.clone(); | 823 | let global_skills_fs = fs.clone(); |
| 833 | cx.background_spawn(async move { | 824 | cx.background_spawn(async move { |
| @@ -838,8 +829,6 @@ impl NativeAgent { · 4 unmodified lines | |||
| 838 | ) | 829 | ) |
| 839 | .await | 830 | .await |
| 840 | }) | 831 | }) |
| 841 | } else { | ||
| 842 | Task::ready(Vec::new()) | ||
| 843 | }; | 832 | }; |
| 844 | 833 | ||
| 845 | // Load project-local skills, but only from worktrees the user has | 834 | // Load project-local skills, but only from worktrees the user has |
| @@ -852,7 +841,7 @@ impl NativeAgent { · 6 unmodified lines | |||
| 852 | // worktrees pick up their skills without restarting. | 841 | // worktrees pick up their skills without restarting. |
| 853 | let trusted_worktrees = TrustedWorktrees::try_get_global(cx); | 842 | let trusted_worktrees = TrustedWorktrees::try_get_global(cx); |
| 854 | let worktree_store = project.read(cx).worktree_store(); | 843 | let worktree_store = project.read(cx).worktree_store(); |
| 855 | let project_skills_task = if skills_enabled { | 844 | let project_skills_task = { |
| 856 | let project_skills_futures: Vec< | 845 | let project_skills_futures: Vec< |
| 857 | futures::future::BoxFuture<'static, Vec<Result<Skill, SkillLoadError>>>, | 846 | futures::future::BoxFuture<'static, Vec<Result<Skill, SkillLoadError>>>, |
| 858 | > = worktrees | 847 | > = worktrees |
| @@ -897,8 +886,6 @@ impl NativeAgent { · 38 unmodified lines | |||
| 897 | }) | 886 | }) |
| 898 | .collect(); | 887 | .collect(); |
| 899 | cx.background_spawn(async move { future::join_all(project_skills_futures).await }) | 888 | cx.background_spawn(async move { future::join_all(project_skills_futures).await }) |
| 900 | } else { | ||
| 901 | Task::ready(Vec::new()) | ||
| 902 | }; | 889 | }; |
| 903 | let default_user_rules_task = if let Some(prompt_store) = prompt_store.as_ref() { | 890 | let default_user_rules_task = if let Some(prompt_store) = prompt_store.as_ref() { |
| 904 | prompt_store.read_with(cx, |prompt_store, cx| { | 891 | prompt_store.read_with(cx, |prompt_store, cx| { |
| @@ -1107,7 +1094,7 @@ impl NativeAgent { · 202 unmodified lines | |||
| 1107 | &mut self, | 1094 | &mut self, |
| 1108 | project: Entity<Project>, | 1095 | project: Entity<Project>, |
| 1109 | event: &project::Event, | 1096 | event: &project::Event, |
| 1110 | cx: &mut Context<Self>, | 1097 | _cx: &mut Context<Self>, |
| 1111 | ) { | 1098 | ) { |
| 1112 | let project_id = project.entity_id(); | 1099 | let project_id = project.entity_id(); |
| 1113 | let Some(state) = self.projects.get_mut(&project_id) else { | 1100 | let Some(state) = self.projects.get_mut(&project_id) else { |
| @@ -1118,16 +1105,14 @@ impl NativeAgent { · 4 unmodified lines | |||
| 1118 | state.project_context_needs_refresh.send(()).ok(); | 1105 | state.project_context_needs_refresh.send(()).ok(); |
| 1119 | } | 1106 | } |
| 1120 | project::Event::WorktreeUpdatedEntries(_, items) => { | 1107 | project::Event::WorktreeUpdatedEntries(_, items) => { |
| 1121 | let skills_enabled = cx.has_flag::<SkillsFeatureFlag>(); | ||
| 1122 | if items.iter().any(|(path, _, _)| { | 1108 | if items.iter().any(|(path, _, _)| { |
| 1123 | let path_ref = path.as_ref(); | 1109 | let path_ref = path.as_ref(); |
| 1124 | RULES_FILE_REL_PATHS | 1110 | RULES_FILE_REL_PATHS |
| 1125 | .iter() | 1111 | .iter() |
| 1126 | .any(|rules_path| path_ref == rules_path.as_ref()) | 1112 | .any(|rules_path| path_ref == rules_path.as_ref()) |
| 1127 | || (skills_enabled | 1113 | || SKILLS_PREFIX |
| 1128 | && SKILLS_PREFIX | 1114 | .as_ref() |
| 1129 | .as_ref() | 1115 | .is_some_and(|prefix| path_ref.starts_with(prefix)) |
| 1130 | .is_some_and(|prefix| path_ref.starts_with(prefix))) | ||
| 1131 | }) { | 1116 | }) { |
| 1132 | state.project_context_needs_refresh.send(()).ok(); | 1117 | state.project_context_needs_refresh.send(()).ok(); |
| 1133 | } | 1118 | } |
| @@ -3583,9 +3568,6 @@ mod internal_tests { · 2449 unmodified lines | |||
| 3583 | #[gpui::test] | 3568 | #[gpui::test] |
| 3584 | async fn test_global_skills_load_and_reload(cx: &mut TestAppContext) { | 3569 | async fn test_global_skills_load_and_reload(cx: &mut TestAppContext) { |
| 3585 | init_test(cx); | 3570 | init_test(cx); |
| 3586 | cx.update(|cx| { | ||
| 3587 | cx.update_flags(true, vec!["skills".to_string()]); | ||
| 3588 | }); | ||
| 3589 | let fs = FakeFs::new(cx.executor()); | 3571 | let fs = FakeFs::new(cx.executor()); |
| 3590 | let skills_dir = global_skills_dir(); | 3572 | let skills_dir = global_skills_dir(); |
| 3591 | let initial_skill_dir = skills_dir.join("my-skill"); | 3573 | let initial_skill_dir = skills_dir.join("my-skill"); |
| @@ -3651,9 +3633,6 @@ mod internal_tests { · 59 unmodified lines | |||
| 3651 | #[gpui::test] | 3633 | #[gpui::test] |
| 3652 | async fn test_global_skills_dir_created_after_startup(cx: &mut TestAppContext) { | 3634 | async fn test_global_skills_dir_created_after_startup(cx: &mut TestAppContext) { |
| 3653 | init_test(cx); | 3635 | init_test(cx); |
| 3654 | cx.update(|cx| { | ||
| 3655 | cx.update_flags(true, vec!["skills".to_string()]); | ||
| 3656 | }); | ||
| 3657 | let fs = FakeFs::new(cx.executor()); | 3636 | let fs = FakeFs::new(cx.executor()); |
| 3658 | let skills_dir = global_skills_dir(); | 3637 | let skills_dir = global_skills_dir(); |
| 3659 | 3638 | ||
| @@ -3735,9 +3714,6 @@ mod internal_tests { · 75 unmodified lines | |||
| 3735 | #[gpui::test] | 3714 | #[gpui::test] |
| 3736 | async fn test_skills_added_after_session_visible_to_skill_tool(cx: &mut TestAppContext) { | 3715 | async fn test_skills_added_after_session_visible_to_skill_tool(cx: &mut TestAppContext) { |
| 3737 | init_test(cx); | 3716 | init_test(cx); |
| 3738 | cx.update(|cx| { | ||
| 3739 | cx.update_flags(true, vec!["skills".to_string()]); | ||
| 3740 | }); | ||
| 3741 | let fs = FakeFs::new(cx.executor()); | 3717 | let fs = FakeFs::new(cx.executor()); |
| 3742 | let skills_dir = global_skills_dir(); | 3718 | let skills_dir = global_skills_dir(); |
| 3743 | 3719 | ||
| @@ -3879,9 +3855,6 @@ mod internal_tests { · 135 unmodified lines | |||
| 3879 | #[gpui::test] | 3855 | #[gpui::test] |
| 3880 | async fn test_subagent_skills_lookup_matches_parent(cx: &mut TestAppContext) { | 3856 | async fn test_subagent_skills_lookup_matches_parent(cx: &mut TestAppContext) { |
| 3881 | init_test(cx); | 3857 | init_test(cx); |
| 3882 | cx.update(|cx| { | ||
| 3883 | cx.update_flags(true, vec!["skills".to_string()]); | ||
| 3884 | }); | ||
| 3885 | let fs = FakeFs::new(cx.executor()); | 3858 | let fs = FakeFs::new(cx.executor()); |
| 3886 | let skills_dir = global_skills_dir(); | 3859 | let skills_dir = global_skills_dir(); |
| 3887 | let skill_dir = skills_dir.join("shared-skill"); | 3860 | let skill_dir = skills_dir.join("shared-skill"); |
| @@ -3982,9 +3955,6 @@ mod internal_tests { · 94 unmodified lines | |||
| 3982 | #[gpui::test] | 3955 | #[gpui::test] |
| 3983 | async fn test_skills_appear_as_available_skills(cx: &mut TestAppContext) { | 3956 | async fn test_skills_appear_as_available_skills(cx: &mut TestAppContext) { |
| 3984 | init_test(cx); | 3957 | init_test(cx); |
| 3985 | cx.update(|cx| { | ||
| 3986 | cx.update_flags(true, vec!["skills".to_string()]); | ||
| 3987 | }); | ||
| 3988 | let fs = FakeFs::new(cx.executor()); | 3958 | let fs = FakeFs::new(cx.executor()); |
| 3989 | let skills_dir = global_skills_dir(); | 3959 | let skills_dir = global_skills_dir(); |
| 3990 | 3960 | ||
| @@ -4087,7 +4057,6 @@ mod internal_tests { · 96 unmodified lines | |||
| 4087 | 4057 | ||
| 4088 | init_test(cx); | 4058 | init_test(cx); |
| 4089 | cx.update(|cx| { | 4059 | cx.update(|cx| { |
| 4090 | cx.update_flags(true, vec!["skills".to_string()]); | ||
| 4091 | // The trust global isn't created by `init_test`. We need it | 4060 | // The trust global isn't created by `init_test`. We need it |
| 4092 | // for `Project::test_with_worktree_trust` to actually wire up | 4061 | // for `Project::test_with_worktree_trust` to actually wire up |
| 4093 | // trust tracking and for our subscription in | 4062 | // trust tracking and for our subscription in |
| @@ -61,7 +61,7 @@ use collections::HashMap; · 60 unmodified lines | |||
| 61 | use editor::{Editor, MultiBuffer}; | 61 | use editor::{Editor, MultiBuffer}; |
| 62 | use extension::ExtensionEvents; | 62 | use extension::ExtensionEvents; |
| 63 | use extension_host::ExtensionStore; | 63 | use extension_host::ExtensionStore; |
| 64 | use feature_flags::{FeatureFlagAppExt as _, SkillsFeatureFlag}; | 64 | |
| 65 | use fs::Fs; | 65 | use fs::Fs; |
| 66 | use gpui::{ | 66 | use gpui::{ |
| 67 | Action, Anchor, Animation, AnimationExt, AnyElement, App, AsyncWindowContext, ClipboardItem, | 67 | Action, Anchor, Animation, AnimationExt, AnyElement, App, AsyncWindowContext, ClipboardItem, |
| @@ -4714,12 +4714,6 @@ impl AgentPanel { · 4646 unmodified lines | |||
| 4714 | .with_handle(self.agent_panel_menu_handle.clone()) | 4714 | .with_handle(self.agent_panel_menu_handle.clone()) |
| 4715 | .menu({ | 4715 | .menu({ |
| 4716 | move |window, cx| { | 4716 | move |window, cx| { |
| 4717 | // When the Skills feature flag is on, hide the legacy Rules menu entry. | ||
| 4718 | // The flag is read from a global store populated asynchronously, and | ||
| 4719 | // this menu builder runs on every open, so the latest resolved value is | ||
| 4720 | // reflected when the user clicks the ellipsis. | ||
| 4721 | let skills_enabled = cx.has_flag::<SkillsFeatureFlag>(); | ||
| 4722 | |||
| 4723 | Some(ContextMenu::build(window, cx, |mut menu, _window, _| { | 4717 | Some(ContextMenu::build(window, cx, |mut menu, _window, _| { |
| 4724 | menu = menu.context(focus_handle.clone()); | 4718 | menu = menu.context(focus_handle.clone()); |
| 4725 | 4719 | ||
| @@ -4756,10 +4750,6 @@ impl AgentPanel { · 30 unmodified lines | |||
| 4756 | .action("Add Custom Server…", Box::new(AddContextServer)) | 4750 | .action("Add Custom Server…", Box::new(AddContextServer)) |
| 4757 | .separator(); | 4751 | .separator(); |
| 4758 | 4752 | ||
| 4759 | if !skills_enabled { | ||
| 4760 | menu = menu.action("Rules", Box::new(OpenRulesLibrary::default())); | ||
| 4761 | } | ||
| 4762 | |||
| 4763 | menu = menu.action("Profiles", Box::new(ManageProfiles::default())); | 4753 | menu = menu.action("Profiles", Box::new(ManageProfiles::default())); |
| 4764 | } | 4754 | } |
| 4765 | 4755 | ||
| @@ -43,7 +43,7 @@ use ::ui::IconName; · 42 unmodified lines | |||
| 43 | use agent_client_protocol::schema as acp; | 43 | use agent_client_protocol::schema as acp; |
| 44 | use agent_settings::{AgentProfileId, AgentSettings}; | 44 | use agent_settings::{AgentProfileId, AgentSettings}; |
| 45 | use command_palette_hooks::CommandPaletteFilter; | 45 | use command_palette_hooks::CommandPaletteFilter; |
| 46 | use feature_flags::{FeatureFlagAppExt as _, SkillsFeatureFlag}; | 46 | use feature_flags::FeatureFlagAppExt as _; |
| 47 | use fs::Fs; | 47 | use fs::Fs; |
| 48 | use gpui::{ | 48 | use gpui::{ |
| 49 | Action, App, Context, Entity, ImageSource, Resource, SharedString, SharedUri, Window, actions, | 49 | Action, App, Context, Entity, ImageSource, Resource, SharedString, SharedUri, Window, actions, |
| @@ -560,21 +560,7 @@ pub fn init( · 510 unmodified lines | |||
| 560 | _: &zed_actions::agent::OpenRulesToSkillsMigrationInfo, | 560 | _: &zed_actions::agent::OpenRulesToSkillsMigrationInfo, |
| 561 | window: &mut Window, | 561 | window: &mut Window, |
| 562 | cx: &mut Context<Workspace>| { | 562 | cx: &mut Context<Workspace>| { |
| 563 | // The banner is the only intended entry point and is | 563 | crate::ui::RulesToSkillsModal::toggle(workspace, window, cx); |
| 564 | // gated on the skills flag, but dispatch from the | ||
| 565 | // command palette or a keybind is still possible — only | ||
| 566 | // open the explainer if the flag is enabled so it never | ||
| 567 | // surfaces outside its intended audience. | ||
| 568 | // | ||
| 569 | // Race note: `has_flag` returns false before server | ||
| 570 | // flags are received, so a dispatch during that brief | ||
| 571 | // window is a no-op even for users who genuinely have | ||
| 572 | // the flag. The banner itself has the same race — it | ||
| 573 | // stays hidden until flags arrive — so a user who can | ||
| 574 | // see the banner has, by definition, already passed it. | ||
| 575 | if cx.has_flag::<SkillsFeatureFlag>() { | ||
| 576 | crate::ui::RulesToSkillsModal::toggle(workspace, window, cx); | ||
| 577 | } | ||
| 578 | }, | 564 | }, |
| 579 | ); | 565 | ); |
| 580 | }) | 566 | }) |
| @@ -608,10 +594,16 @@ pub fn init( · 27 unmodified lines | |||
| 608 | }) | 594 | }) |
| 609 | .detach(); | 595 | .detach(); |
| 610 | 596 | ||
| 611 | // Once the `skills` feature flag has resolved, kick off the one-time | 597 | // Kick off the one-time migration of non-Default Rules to global |
| 612 | // migration of non-Default Rules to global Skills. Idempotent and | 598 | // Skills, deferred until server feature flags arrive. |
| 613 | // self-gated on the flag, so it's safe to call on every flag-ready | 599 | // |
| 614 | // notification (and a no-op for users without the flag). | 600 | // The migration itself is idempotent and no longer gated on a flag, |
| 601 | // but the deferral via `on_flags_ready` still matters: the migration | ||
| 602 | // writes to the on-disk `GlobalKeyValueStore`, which dispatches work | ||
| 603 | // on the `sqlezWorker` background thread. In `gpui::test` contexts, | ||
| 604 | // server flags are never received, so this callback never fires — | ||
| 605 | // which keeps that sqlite worker activity from racing with the | ||
| 606 | // `TestScheduler` and tripping its non-determinism panic. | ||
| 615 | { | 607 | { |
| 616 | let fs = fs.clone(); | 608 | let fs = fs.clone(); |
| 617 | cx.on_flags_ready(move |_, cx| { | 609 | cx.on_flags_ready(move |_, cx| { |
| @@ -653,12 +645,6 @@ fn update_command_palette_filter(cx: &mut App) { · 35 unmodified lines | |||
| 653 | .edit_predictions | 645 | .edit_predictions |
| 654 | .provider; | 646 | .provider; |
| 655 | 647 | ||
| 656 | // The Skills feature flag is loaded asynchronously, so this value may | ||
| 657 | // be `false` before flags resolve. `update_command_palette_filter` | ||
| 658 | // gets re-run from `cx.on_flags_ready` (see `init`), which means the | ||
| 659 | // filter is reapplied with the correct value once flags arrive. | ||
| 660 | let skills_enabled = cx.has_flag::<SkillsFeatureFlag>(); | ||
| 661 | |||
| 662 | CommandPaletteFilter::update_global(cx, |filter, _| { | 648 | CommandPaletteFilter::update_global(cx, |filter, _| { |
| 663 | use editor::actions::{ | 649 | use editor::actions::{ |
| 664 | AcceptEditPrediction, AcceptNextLineEditPrediction, AcceptNextWordEditPrediction, | 650 | AcceptEditPrediction, AcceptNextLineEditPrediction, AcceptNextWordEditPrediction, |
| @@ -726,12 +712,12 @@ fn update_command_palette_filter(cx: &mut App) { · 61 unmodified lines | |||
| 726 | filter.show_namespace("multi_workspace"); | 712 | filter.show_namespace("multi_workspace"); |
| 727 | } | 713 | } |
| 728 | 714 | ||
| 729 | // Hide `assistant: open rules library` when Skills are enabled — | 715 | // Hide `assistant: open rules library` — Rules are surfaced |
| 730 | // Rules are surfaced through the Skills UI in that case. Applied | 716 | // through the Skills UI now. Applied after the disable-ai / |
| 731 | // after the disable-ai / agent-enabled branches so it overrides | 717 | // agent-enabled branches so it overrides the |
| 732 | // the `show_namespace("assistant")` call above without affecting | 718 | // `show_namespace("assistant")` call above without affecting the |
| 733 | // the rest of that namespace's actions. | 719 | // rest of that namespace's actions. |
| 734 | if !disable_ai && skills_enabled { | 720 | if !disable_ai { |
| 735 | filter.hide_action_types(&open_rules_library_action); | 721 | filter.hide_action_types(&open_rules_library_action); |
| 736 | } else { | 722 | } else { |
| 737 | filter.show_action_types(open_rules_library_action.iter()); | 723 | filter.show_action_types(open_rules_library_action.iter()); |
| @@ -123,15 +123,3 @@ impl FeatureFlag for AutoWatchFeatureFlag { · 122 unmodified lines | |||
| 123 | type Value = PresenceFlag; | 123 | type Value = PresenceFlag; |
| 124 | } | 124 | } |
| 125 | register_feature_flag!(AutoWatchFeatureFlag); | 125 | register_feature_flag!(AutoWatchFeatureFlag); |
| 126 | |||
| 127 | pub struct SkillsFeatureFlag; | ||
| 128 | |||
| 129 | impl FeatureFlag for SkillsFeatureFlag { | ||
| 130 | const NAME: &'static str = "skills"; | ||
| 131 | type Value = PresenceFlag; | ||
| 132 | |||
| 133 | fn enabled_for_staff() -> bool { | ||
| 134 | true | ||
| 135 | } | ||
| 136 | } | ||
| 137 | register_feature_flag!(SkillsFeatureFlag); | ||
| @@ -18,7 +18,7 @@ assets.workspace = true · 17 unmodified lines | |||
| 18 | chrono.workspace = true | 18 | chrono.workspace = true |
| 19 | collections.workspace = true | 19 | collections.workspace = true |
| 20 | db.workspace = true | 20 | db.workspace = true |
| 21 | feature_flags.workspace = true | 21 | |
| 22 | fs.workspace = true | 22 | fs.workspace = true |
| 23 | futures.workspace = true | 23 | futures.workspace = true |
| 24 | fuzzy.workspace = true | 24 | fuzzy.workspace = true |
| @@ -24,14 +24,10 @@ · 23 unmodified lines | |||
| 24 | //! (still using Zed's shipped default content) are skipped so we don't | 24 | //! (still using Zed's shipped default content) are skipped so we don't |
| 25 | //! pollute AGENTS.md with text the user never wrote. | 25 | //! pollute AGENTS.md with text the user never wrote. |
| 26 | //! | 26 | //! |
| 27 | //! Both migrations are gated by: | 27 | //! Both migrations are gated by a single global "migration already ran" |
| 28 | //! | 28 | //! flag persisted in [`GlobalKeyValueStore`] — keyed by |
| 29 | //! * the `skills` feature flag — users without it never have their Rules | 29 | //! [`MIGRATION_DONE_KEY`], so a shared home directory only gets |
| 30 | //! touched in any way; | 30 | //! populated once per machine even across release channels. |
| 31 | //! * a single global "migration already ran" flag persisted in | ||
| 32 | //! [`GlobalKeyValueStore`] — keyed by [`MIGRATION_DONE_KEY`], so a | ||
| 33 | //! shared home directory only gets populated once per machine even | ||
| 34 | //! across release channels. | ||
| 35 | //! | 31 | //! |
| 36 | //! The migration is intentionally non-destructive: rule rows in the LMDB | 32 | //! The migration is intentionally non-destructive: rule rows in the LMDB |
| 37 | //! database are left in place after the migration. That way users can | 33 | //! database are left in place after the migration. That way users can |
| @@ -45,7 +41,6 @@ use std::sync::atomic::{AtomicBool, Ordering}; · 7 unmodified lines | |||
| 45 | use agent_skills::{SKILL_FILE_NAME, global_skills_dir, slugify_skill_name}; | 41 | use agent_skills::{SKILL_FILE_NAME, global_skills_dir, slugify_skill_name}; |
| 46 | use anyhow::{Context as _, Result}; | 42 | use anyhow::{Context as _, Result}; |
| 47 | use db::kvp::GlobalKeyValueStore; | 43 | use db::kvp::GlobalKeyValueStore; |
| 48 | use feature_flags::{FeatureFlagAppExt as _, SkillsFeatureFlag}; | ||
| 49 | use fs::Fs; | 44 | use fs::Fs; |
| 50 | use gpui::{App, AsyncApp, Entity, TaskExt as _}; | 45 | use gpui::{App, AsyncApp, Entity, TaskExt as _}; |
| 51 | use serde::{Deserialize, Serialize}; | 46 | use serde::{Deserialize, Serialize}; |
| @@ -151,13 +146,9 @@ static MIGRATION_TASK_SPAWNED: AtomicBool = AtomicBool::new(false); · 99 unmodified lines | |||
| 151 | /// Migrate non-Default user rules to global Skills, if not already done. | 146 | /// Migrate non-Default user rules to global Skills, if not already done. |
| 152 | /// | 147 | /// |
| 153 | /// Safe to call on every startup — short-circuits immediately when the | 148 | /// Safe to call on every startup — short-circuits immediately when the |
| 154 | /// migration has already run, when another invocation in this process | 149 | /// migration has already run or when another invocation in this process |
| 155 | /// has already started it, or when the user doesn't have the `skills` | 150 | /// has already started it. |
| 156 | /// feature flag enabled. | ||
| 157 | pub fn migrate_rules_to_skills_if_needed(fs: Arc<dyn Fs>, cx: &mut App) { | 151 | pub fn migrate_rules_to_skills_if_needed(fs: Arc<dyn Fs>, cx: &mut App) { |
| 158 | if !cx.has_flag::<SkillsFeatureFlag>() { | ||
| 159 | return; | ||
| 160 | } | ||
| 161 | if migration_done() { | 152 | if migration_done() { |
| 162 | return; | 153 | return; |
| 163 | } | 154 | } |
| @@ -40,7 +40,7 @@ chrono.workspace = true · 39 unmodified lines | |||
| 40 | client.workspace = true | 40 | client.workspace = true |
| 41 | cloud_api_types.workspace = true | 41 | cloud_api_types.workspace = true |
| 42 | db.workspace = true | 42 | db.workspace = true |
| 43 | feature_flags.workspace = true | 43 | |
| 44 | git_ui.workspace = true | 44 | git_ui.workspace = true |
| 45 | gpui = { workspace = true, features = ["screen-capture"] } | 45 | gpui = { workspace = true, features = ["screen-capture"] } |
| 46 | icons.workspace = true | 46 | icons.workspace = true |
| @@ -25,7 +25,6 @@ use auto_update::AutoUpdateStatus; · 24 unmodified lines | |||
| 25 | use call::ActiveCall; | 25 | use call::ActiveCall; |
| 26 | use client::{Client, UserStore, zed_urls}; | 26 | use client::{Client, UserStore, zed_urls}; |
| 27 | use cloud_api_types::Plan; | 27 | use cloud_api_types::Plan; |
| 28 | use feature_flags::{FeatureFlagAppExt as _, SkillsFeatureFlag}; | ||
| 29 | 28 | ||
| 30 | use gpui::{ | 29 | use gpui::{ |
| 31 | Action, Anchor, Animation, AnimationExt, AnyElement, App, Context, Element, Entity, Focusable, | 30 | Action, Anchor, Animation, AnimationExt, AnyElement, App, Context, Element, Entity, Focusable, |
| @@ -469,7 +468,6 @@ impl TitleBar { · 437 unmodified lines | |||
| 469 | zed_actions::agent::OpenRulesToSkillsMigrationInfo.boxed_clone(), | 468 | zed_actions::agent::OpenRulesToSkillsMigrationInfo.boxed_clone(), |
| 470 | cx, | 469 | cx, |
| 471 | ) | 470 | ) |
| 472 | .visible_when(|cx| cx.has_flag::<SkillsFeatureFlag>()) | ||
| 473 | })); | 471 | })); |
| 474 | 472 | ||
| 475 | let mut this = Self { | 473 | let mut this = Self { |