Compare commits

..

No commits in common. "c36650c9aa66c67f4ee0dd5580f49b752dae8671" and "cc3e890ccd9153e3d0028c729d3566c98dc0ed75" have entirely different histories.

4 changed files with 56 additions and 34 deletions

View File

@ -7,7 +7,7 @@ use crate::storage::{MemoryRepository, SchedulerJobRepository, SkillEventReposit
use crate::tools::{ use crate::tools::{
BashTool, CalculatorTool, FileEditTool, FileReadTool, FileWriteTool, BashTool, CalculatorTool, FileEditTool, FileReadTool, FileWriteTool,
HttpRequestTool, MemoryManageTool, MemorySearchTool, HttpRequestTool, MemoryManageTool, MemorySearchTool,
SchedulerManageTool, SessionMessageSender, SessionSendTool, SkillActivateTool, SchedulerManageTool, SessionMessageSender, SessionSendTool, SkillActivateTool, SkillListTool,
SkillManageTool, SubAgentRuntime, TaskTool, TimeTool, SkillManageTool, SubAgentRuntime, TaskTool, TimeTool,
ToolRegistry, WebFetchTool, ToolRegistry, WebFetchTool,
}; };
@ -102,6 +102,9 @@ impl ToolRegistryFactory {
self.skill_events.clone(), self.skill_events.clone(),
)); ));
} }
if self.is_enabled("skill_list") {
registry.register(SkillListTool::new(self.skills.clone()));
}
if self.is_enabled("skill_manage") { if self.is_enabled("skill_manage") {
registry.register(SkillManageTool::new(self.skills.clone())); registry.register(SkillManageTool::new(self.skills.clone()));
} }
@ -177,6 +180,9 @@ impl ToolRegistryFactory {
self.skill_events.clone(), self.skill_events.clone(),
)); ));
} }
if self.is_enabled("skill_list") {
registry.register(SkillListTool::new(self.skills.clone()));
}
// 进度通知工具 // 进度通知工具
if self.is_enabled("session_send") { if self.is_enabled("session_send") {

View File

@ -434,23 +434,7 @@ impl SkillCatalog {
} }
let mut prompt = String::from( let mut prompt = String::from(
"# 技能系统Skills\n\n\ "# 技能说明\n技能为特定任务提供专用说明和工作流。\n当任务匹配其描述时,使用 skill_activate 工具加载技能。\n技能不是工具名,即使技能名看起来像工具,也不能直接调用技能名。\n如果需要某个技能,必须先调用 tool skill_activate并传入 {\"name\": \"<skill-name>\"},再根据返回的技能说明执行。\n\n<available_skills>\n",
使\n\n\
## 使\n\n\
使\n\
- \n\
- \n\
- \n\n\
## 使\n\n\
1. ****: <available_skills> \n\
2. ****: \n\
3. ****: `skill_activate` name \n\
4. ****: skill_activate \n\n\
## \n\n\
- \n\
- skill_activate \n\
- \n\n\
<available_skills>\n",
); );
for skill in &self.skills { for skill in &self.skills {

View File

@ -33,7 +33,7 @@ pub use session_send::{
}; };
pub use schema::{CleaningStrategy, SchemaCleanr}; pub use schema::{CleaningStrategy, SchemaCleanr};
pub use skill_activate::SkillActivateTool; pub use skill_activate::SkillActivateTool;
pub use skill_manage::SkillManageTool; pub use skill_manage::{SkillListTool, SkillManageTool};
pub use task::{ pub use task::{
DefaultSubAgentRuntime, InMemoryTaskRepository, SubAgentRuntime, SubAgentRuntimeConfig, DefaultSubAgentRuntime, InMemoryTaskRepository, SubAgentRuntime, SubAgentRuntimeConfig,
TaskError, TaskRepository, TaskTool, TaskError, TaskRepository, TaskTool,

View File

@ -10,12 +10,22 @@ pub struct SkillManageTool {
skills: Arc<SkillRuntime>, skills: Arc<SkillRuntime>,
} }
pub struct SkillListTool {
skills: Arc<SkillRuntime>,
}
impl SkillManageTool { impl SkillManageTool {
pub fn new(skills: Arc<SkillRuntime>) -> Self { pub fn new(skills: Arc<SkillRuntime>) -> Self {
Self { skills } Self { skills }
} }
} }
impl SkillListTool {
pub fn new(skills: Arc<SkillRuntime>) -> Self {
Self { skills }
}
}
#[async_trait] #[async_trait]
impl Tool for SkillManageTool { impl Tool for SkillManageTool {
fn name(&self) -> &str { fn name(&self) -> &str {
@ -23,16 +33,7 @@ impl Tool for SkillManageTool {
} }
fn description(&self) -> &str { fn description(&self) -> &str {
"Manage PicoBot skills. Actions: list, get, create, update, delete, disable, reload.\n\n\ "Manage PicoBot skills stored under .picobot/skills or ~/.picobot/skills, while discovery also reads .agents/skills, ~/.agents/skills, .openclaw/skills, and ~/.openclaw/skills. Supports actions: list, get, create, update, delete, disable, reload."
Skill Structure:\n\
- Folder name: kebab-case (lowercase with hyphens, e.g., 'my-cool-skill')\n\
- Required: SKILL.md with YAML frontmatter + Markdown body\n\
- Optional folders: scripts/, references/, assets/\n\
- Storage: .picobot/skills/{name}/SKILL.md or ~/.picobot/skills/{name}/SKILL.md\n\n\
Installing from Zip:\n\
- Extract skill folders to skills/ directory\n\
- If zip contains multiple skills, extract each subfolder separately\n\
- Final structure: skills/{skill-name}/SKILL.md"
} }
fn parameters_schema(&self) -> serde_json::Value { fn parameters_schema(&self) -> serde_json::Value {
@ -51,7 +52,7 @@ impl Tool for SkillManageTool {
}, },
"name": { "name": {
"type": "string", "type": "string",
"description": "Skill folder name in kebab-case (e.g., 'my-cool-skill', 'code-review'). Must match the folder name under .picobot/skills/ or ~/.picobot/skills/" "description": "Skill name"
}, },
"names": { "names": {
"type": "array", "type": "array",
@ -100,6 +101,10 @@ impl Tool for SkillManageTool {
}; };
let name = args.get("name").and_then(|v| v.as_str()); let name = args.get("name").and_then(|v| v.as_str());
let names = match parse_disable_names(&args) {
Ok(names) => names,
Err(err) => return Ok(error_result(&err)),
};
let result = match action { let result = match action {
"list" => list_skills_payload(&self.skills), "list" => list_skills_payload(&self.skills),
@ -202,10 +207,6 @@ impl Tool for SkillManageTool {
Err(err) => return Ok(error_result(&err)), Err(err) => return Ok(error_result(&err)),
}, },
"disable" => { "disable" => {
let names = match parse_disable_names(&args) {
Ok(names) => names,
Err(err) => return Ok(error_result(&err)),
};
let targets = &names; let targets = &names;
let mut changes = Vec::new(); let mut changes = Vec::new();
@ -240,6 +241,37 @@ impl Tool for SkillManageTool {
} }
} }
#[async_trait]
impl Tool for SkillListTool {
fn name(&self) -> &str {
"skill_list"
}
fn description(&self) -> &str {
"List currently discovered PicoBot skills as a read-only operation. Use this when you only need to inspect available skills without modifying them."
}
fn parameters_schema(&self) -> serde_json::Value {
json!({
"type": "object",
"properties": {},
"additionalProperties": false
})
}
fn read_only(&self) -> bool {
true
}
async fn execute(&self, _args: serde_json::Value) -> anyhow::Result<ToolResult> {
Ok(ToolResult {
success: true,
output: serde_json::to_string_pretty(&list_skills_payload(&self.skills))?,
error: None,
})
}
}
fn error_result(message: &str) -> ToolResult { fn error_result(message: &str) -> ToolResult {
ToolResult { ToolResult {
success: false, success: false,