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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/i18n/en/warp.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,8 @@ code-replace-all = Replace all
code-goto-line-placeholder = Line number:Column
code-open-file-unavailable-remote-tooltip = Opening files is unavailable for remote sessions
code-view-markdown-preview = View Markdown preview
markdown-display-mode-rendered = Rendered
markdown-display-mode-raw = Raw
code-review-commit-and-create-pr = Commit and create PR
notebook-link-text-placeholder = Text
notebook-link-url-placeholder = Link (web or file)
Expand Down Expand Up @@ -1798,6 +1800,7 @@ keybinding-desc-workspace-left-panel-project-explorer = Left Panel: Project expl
keybinding-desc-workspace-left-panel-global-search = Left Panel: Global search
keybinding-desc-workspace-left-panel-warp-drive = Left Panel: Warp Drive
keybinding-desc-workspace-left-panel-ssh-manager = Left Panel: SSH Manager
keybinding-desc-workspace-left-panel-skill-manager = Left Panel: Skill Manager
keybinding-desc-workspace-open-global-search = Open global search
keybinding-desc-workspace-open-global-search-menu = Global Search
keybinding-desc-workspace-toggle-warp-drive = Toggle Warp Drive
Expand Down Expand Up @@ -2843,6 +2846,14 @@ workspace-left-panel-global-search = Global search
workspace-left-panel-warp-drive = Warp Drive
workspace-left-panel-agent-conversations = Agent conversations
workspace-left-panel-ssh-manager = SSH Manager
workspace-left-panel-skill-manager = Skill Manager
skill-manager-search-placeholder = Search skills
skill-manager-filter-all = All
skill-manager-filter-provider = Source
skill-manager-meta-default = Default
skill-manager-meta-duplicate = Duplicate
skill-manager-empty = No skills match the current filters.
skill-manager-preview-empty = Select a skill to preview SKILL.md.
workspace-left-panel-ssh-manager-placeholder = SSH Manager — coming soon
workspace-left-panel-ssh-manager-detail-empty = Select a server to see its details.
workspace-left-panel-ssh-manager-detail-host = Host
Expand Down
11 changes: 11 additions & 0 deletions app/i18n/zh-CN/warp.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ code-replace-all = 全部替换
code-goto-line-placeholder = 行号:列号
code-open-file-unavailable-remote-tooltip = 远程会话无法打开文件
code-view-markdown-preview = 查看 Markdown 预览
markdown-display-mode-rendered = 预览
markdown-display-mode-raw = 源码
code-review-commit-and-create-pr = 提交并创建 PR
notebook-link-text-placeholder = 文本
notebook-link-url-placeholder = 链接(网页或文件)
Expand Down Expand Up @@ -1718,6 +1720,7 @@ keybinding-desc-workspace-left-panel-project-explorer = 左侧面板:项目浏
keybinding-desc-workspace-left-panel-global-search = 左侧面板:全局搜索
keybinding-desc-workspace-left-panel-warp-drive = 左侧面板:Warp Drive
keybinding-desc-workspace-left-panel-ssh-manager = 左侧面板:SSH 管理器
keybinding-desc-workspace-left-panel-skill-manager = 左侧面板:Skill 管理器
keybinding-desc-workspace-open-global-search = 打开全局搜索
keybinding-desc-workspace-open-global-search-menu = 全局搜索
keybinding-desc-workspace-toggle-warp-drive = 切换 Warp Drive
Expand Down Expand Up @@ -2749,6 +2752,14 @@ workspace-left-panel-global-search = 全局搜索
workspace-left-panel-warp-drive = Warp Drive
workspace-left-panel-agent-conversations = 智能体对话
workspace-left-panel-ssh-manager = SSH 管理器
workspace-left-panel-skill-manager = Skill 管理器
skill-manager-search-placeholder = 搜索 skill
skill-manager-filter-all = 全部
skill-manager-filter-provider = 来源
skill-manager-meta-default = 默认
skill-manager-meta-duplicate = 重复
skill-manager-empty = 当前过滤条件下没有 skill。
skill-manager-preview-empty = 选择一个 skill 预览 SKILL.md。
workspace-left-panel-ssh-manager-placeholder = SSH 管理器 — 功能开发中
workspace-left-panel-ssh-manager-detail-empty = 选择左侧的服务器以查看详情。
workspace-left-panel-ssh-manager-detail-host = 主机
Expand Down
4 changes: 2 additions & 2 deletions app/src/ai/agent_sdk/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1365,11 +1365,11 @@ impl AgentDriver {
skills.len()
);
}
SkillManager::handle(ctx).update(ctx, |manager, _| {
SkillManager::handle(ctx).update(ctx, |manager, ctx| {
// All repo skills should be in scope regardless of cwd when
// a cloud environment is configured.
manager.set_cloud_environment(true);
manager.handle_skills_added(skills);
manager.handle_skills_added(skills, ctx);
});
})
.await;
Expand Down
39 changes: 36 additions & 3 deletions app/src/ai/skills/dummy_skill_manager.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,38 @@
use std::path::Path;
use std::path::{Path, PathBuf};

use ai::skills::{ParsedSkill, SkillProvider, SkillReference};
use ai::skills::{ParsedSkill, SkillProvider, SkillReference, SkillScope};
use warpui::{AppContext, Entity, ModelContext, SingletonEntity};

use crate::ai::skills::SkillDescriptor;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SkillManagerEvent {
InventoryChanged,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SkillInventoryDuplicate {
pub path: PathBuf,
pub name: String,
pub description: String,
pub content: String,
pub provider: SkillProvider,
pub scope: SkillScope,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SkillInventoryItem {
pub name: String,
pub default_skill: SkillInventoryDuplicate,
pub duplicates: Vec<SkillInventoryDuplicate>,
}

impl SkillInventoryItem {
pub fn has_duplicates(&self) -> bool {
self.duplicates.len() > 1
}
}

pub struct SkillManager {}

impl SkillManager {
Expand All @@ -24,6 +52,11 @@ impl SkillManager {
None
}

pub fn list_skill_inventory(&self, ctx: &AppContext) -> Vec<SkillInventoryItem> {
let _ = ctx;
vec![]
}

pub fn reference_for_skill_path(&self, skill_path: &Path) -> SkillReference {
SkillReference::Path(skill_path.to_path_buf())
}
Expand Down Expand Up @@ -54,7 +87,7 @@ impl SkillManager {
}

impl Entity for SkillManager {
type Event = ();
type Event = SkillManagerEvent;
}

impl SingletonEntity for SkillManager {}
2 changes: 1 addition & 1 deletion app/src/ai/skills/listed_skill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub struct SkillDescriptor {
pub reference: SkillReference,
pub name: String,
pub description: String,
/// The scope of the skill (home directory vs project directory).
/// The scope of the skill.
pub scope: SkillScope,
/// The provider/origin of the skill (Claude, Codex, or Warp).
/// None if the skill path didn't match a known provider directory.
Expand Down
9 changes: 7 additions & 2 deletions app/src/ai/skills/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ pub use telemetry::{SkillOpenOrigin, SkillTelemetryEvent};
cfg_if::cfg_if! {
if #[cfg(not(feature = "local_fs"))] {
mod dummy_skill_manager;
pub use dummy_skill_manager::SkillManager;
pub use dummy_skill_manager::{
SkillInventoryDuplicate, SkillInventoryItem, SkillManager, SkillManagerEvent,
};
}
}

Expand All @@ -28,6 +30,9 @@ pub use resolve_skill_spec::{
cfg_if::cfg_if! {
if #[cfg(feature = "local_fs")] {
mod skill_manager;
pub use skill_manager::{SkillManager, SkillWatcher};
pub use skill_manager::{
SkillInventoryDuplicate, SkillInventoryItem, SkillManager, SkillManagerEvent,
SkillWatcher,
};
}
}
100 changes: 92 additions & 8 deletions app/src/ai/skills/skill_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,34 @@ use warp_core::{
};
use warpui::{AppContext, Entity, ModelContext, ModelHandle, SingletonEntity};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SkillManagerEvent {
InventoryChanged,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SkillInventoryDuplicate {
pub path: PathBuf,
pub name: String,
pub description: String,
pub content: String,
pub provider: SkillProvider,
pub scope: ai::skills::SkillScope,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SkillInventoryItem {
pub name: String,
pub default_skill: SkillInventoryDuplicate,
pub duplicates: Vec<SkillInventoryDuplicate>,
}

impl SkillInventoryItem {
pub fn has_duplicates(&self) -> bool {
self.duplicates.len() > 1
}
}

/// Activation condition for a bundled skill.
#[derive(Debug, Clone)]
pub enum BundledSkillActivation {
Expand Down Expand Up @@ -86,8 +114,8 @@ impl SkillManager {

ctx.spawn_stream_local(
skill_watcher_rx,
|me, message, _ctx| {
me.handle_skill_watcher_event(message);
|me, message, ctx| {
me.handle_skill_watcher_event(message, ctx);
},
|_, _| {}, // No cleanup needed when stream ends
);
Expand Down Expand Up @@ -342,18 +370,66 @@ impl SkillManager {
bundled.activation.is_enabled(ctx).then_some(&bundled.skill)
}

fn handle_skill_watcher_event(&mut self, event: SkillWatcherEvent) {
pub fn list_skill_inventory(&self, ctx: &AppContext) -> Vec<SkillInventoryItem> {
let _ = ctx;
let mut by_name: HashMap<String, Vec<SkillInventoryDuplicate>> = HashMap::new();

for skill in self.skills_by_path.values() {
by_name
.entry(skill.name.clone())
.or_default()
.push(SkillInventoryDuplicate {
path: skill.path.clone(),
name: skill.name.clone(),
description: skill.description.clone(),
content: skill.content.clone(),
provider: skill.provider,
scope: skill.scope,
});
}

let mut items = by_name
.into_iter()
.filter_map(|(name, mut duplicates)| {
duplicates.sort_by(|a, b| {
provider_rank(a.provider)
.cmp(&provider_rank(b.provider))
.then_with(|| format!("{:?}", a.scope).cmp(&format!("{:?}", b.scope)))
.then_with(|| a.path.cmp(&b.path))
});
let default_skill = duplicates.first()?.clone();
Some(SkillInventoryItem {
name,
default_skill,
duplicates,
})
})
.collect::<Vec<_>>();

items.sort_by(|a, b| a.name.cmp(&b.name));
items
}

fn handle_skill_watcher_event(
&mut self,
event: SkillWatcherEvent,
ctx: &mut ModelContext<Self>,
) {
match event {
SkillWatcherEvent::SkillsAdded { skills } => {
self.handle_skills_added(skills);
self.handle_skills_added(skills, ctx);
}
SkillWatcherEvent::SkillsDeleted { paths } => {
self.handle_skills_deleted(paths);
self.handle_skills_deleted(paths, ctx);
}
}
}

pub fn handle_skills_added(&mut self, skills: Vec<ParsedSkill>) {
pub fn handle_skills_added(&mut self, skills: Vec<ParsedSkill>, ctx: &mut ModelContext<Self>) {
if skills.is_empty() {
return;
}

for skill in skills {
if let Ok(parent_dir) = extract_skill_parent_directory(&skill.path) {
self.directory_skills
Expand All @@ -373,12 +449,20 @@ impl SkillManager {
);
}
}

ctx.emit(SkillManagerEvent::InventoryChanged);
}

fn handle_skills_deleted(&mut self, paths: Vec<PathBuf>) {
fn handle_skills_deleted(&mut self, paths: Vec<PathBuf>, ctx: &mut ModelContext<Self>) {
if paths.is_empty() {
return;
}

for path in paths {
self.handle_path_deleted(&path);
}

ctx.emit(SkillManagerEvent::InventoryChanged);
}

fn handle_path_deleted(&mut self, path: &Path) {
Expand Down Expand Up @@ -593,7 +677,7 @@ fn is_home_directory(path: &Path) -> bool {
}

impl Entity for SkillManager {
type Event = ();
type Event = SkillManagerEvent;
}

impl SingletonEntity for SkillManager {}
Expand Down
Loading
Loading