mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-25 05:46:13 +00:00
fix: refine extension ui and backend errors
This commit is contained in:
@@ -97,12 +97,11 @@ class SkillToolLoader(loader.ToolLoader):
|
||||
if skill_data is None:
|
||||
visible_skills = getattr(skill_mgr, 'skills', {})
|
||||
available_names = ', '.join(sorted(visible_skills.keys())) or 'none'
|
||||
raise ValueError(
|
||||
f'Skill "{skill_name}" not found. Available skills: {available_names}'
|
||||
)
|
||||
raise ValueError(f'Skill "{skill_name}" not found. Available skills: {available_names}')
|
||||
|
||||
# Register activated skill for sandbox mount path resolution
|
||||
from . import skill as skill_loader
|
||||
|
||||
skill_loader.register_activated_skill(query, skill_data)
|
||||
|
||||
# Return SKILL.md content as Tool Result (injects into context)
|
||||
@@ -112,17 +111,17 @@ class SkillToolLoader(loader.ToolLoader):
|
||||
|
||||
# Build Tool Result content
|
||||
result_content = f'<command-message>The "{skill_name}" skill is activated</command-message>\n'
|
||||
result_content += f'<skill-activation>\n'
|
||||
result_content += '<skill-activation>\n'
|
||||
result_content += f'<skill-name>{skill_name}</skill-name>\n'
|
||||
result_content += f'<mount-path>{mount_path}</mount-path>\n'
|
||||
result_content += f'<package-root>{package_root}</package-root>\n'
|
||||
result_content += f'\n## Instructions\n{instructions}\n'
|
||||
result_content += f'\n## Runtime Context\n'
|
||||
result_content += '\n## Runtime Context\n'
|
||||
result_content += f'The skill package is mounted at {mount_path}. Use the standard tools to interact with it:\n'
|
||||
result_content += f'- Use `read` to inspect files under {mount_path}\n'
|
||||
result_content += f'- Use `exec` with workdir set to {mount_path} to run commands in that package\n'
|
||||
result_content += f'- Use `write` and `edit` on that path when the instructions require updating files\n'
|
||||
result_content += f'</skill-activation>\n'
|
||||
result_content += '- Use `write` and `edit` on that path when the instructions require updating files\n'
|
||||
result_content += '</skill-activation>\n'
|
||||
|
||||
return {
|
||||
'activated': True,
|
||||
@@ -190,7 +189,7 @@ class SkillToolLoader(loader.ToolLoader):
|
||||
if not normalized_path.startswith('/workspace'):
|
||||
raise ValueError('path must be under /workspace')
|
||||
|
||||
relative = normalized_path[len('/workspace'):].lstrip('/')
|
||||
relative = normalized_path[len('/workspace') :].lstrip('/')
|
||||
host_root = os.path.realpath(workspace_root)
|
||||
host_path = os.path.realpath(os.path.join(host_root, relative))
|
||||
|
||||
@@ -213,7 +212,7 @@ class SkillToolLoader(loader.ToolLoader):
|
||||
'properties': {
|
||||
'skill_name': {
|
||||
'type': 'string',
|
||||
'description': 'The skill name to activate (no arguments). E.g., "pdf" or "create-skill"',
|
||||
'description': 'The skill name to activate (no arguments). E.g., "pdf" or "data-analysis"',
|
||||
},
|
||||
},
|
||||
'required': ['skill_name'],
|
||||
@@ -227,7 +226,7 @@ class SkillToolLoader(loader.ToolLoader):
|
||||
name=REGISTER_SKILL_TOOL_NAME,
|
||||
human_desc='Register a skill from sandbox',
|
||||
description=(
|
||||
'Register a skill package from a directory under /workspace into LangBot\'s skill store. '
|
||||
"Register a skill package from a directory under /workspace into LangBot's skill store. "
|
||||
'Use this after creating or preparing a skill in the sandbox with exec/read/write/edit. '
|
||||
'The directory must contain a SKILL.md file. '
|
||||
'After registration, the skill can be activated with the activate tool.'
|
||||
@@ -276,15 +275,15 @@ class SkillToolLoader(loader.ToolLoader):
|
||||
available_skills_lines = ['<available_skills>']
|
||||
for skill_name, skill_data in sorted(skills.items()):
|
||||
description = skill_data.get('description', '')
|
||||
available_skills_lines.append(f'<skill>')
|
||||
available_skills_lines.append('<skill>')
|
||||
available_skills_lines.append(f'<name>{skill_name}</name>')
|
||||
available_skills_lines.append(f'<description>{description}</description>')
|
||||
available_skills_lines.append(f'</skill>')
|
||||
available_skills_lines.append('</skill>')
|
||||
available_skills_lines.append('</available_skills>')
|
||||
|
||||
available_skills_block = '\n'.join(available_skills_lines)
|
||||
|
||||
return f'''Activate a skill within the main conversation.
|
||||
return f"""Activate a skill within the main conversation.
|
||||
|
||||
<skills_instructions>
|
||||
When users ask you to perform tasks, check if any of the available skills
|
||||
@@ -299,7 +298,7 @@ The skill is activated
|
||||
- The skill's instructions will be provided in the tool result
|
||||
- Examples:
|
||||
- skill_name: "pdf" - invoke the pdf skill
|
||||
- skill_name: "create-skill" - invoke the create-skill skill for creating new skills
|
||||
- skill_name: "data-analysis" - invoke the data-analysis skill
|
||||
|
||||
Important:
|
||||
- Only use skills listed in <available_skills> below
|
||||
@@ -307,4 +306,4 @@ Important:
|
||||
- To create a new skill: prepare it in /workspace, then use register_skill tool
|
||||
</skills_instructions>
|
||||
|
||||
{available_skills_block}'''
|
||||
{available_skills_block}"""
|
||||
|
||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
import datetime as dt
|
||||
import os
|
||||
import shutil
|
||||
import typing
|
||||
|
||||
from ..core import app
|
||||
@@ -16,9 +15,7 @@ if typing.TYPE_CHECKING:
|
||||
class SkillManager:
|
||||
"""Skill manager backed by filesystem packages.
|
||||
|
||||
Skills are loaded from two sources:
|
||||
1. Builtin skills: templates/skills/ (shipped with LangBot)
|
||||
2. User skills: data/skills/ (created by users)
|
||||
Skills are loaded from data/skills/ and managed by users.
|
||||
|
||||
Skills are activated through the `activate` tool (Tool Call mechanism),
|
||||
aligned with Claude Code's design. This protects KV Cache and follows
|
||||
@@ -38,8 +35,7 @@ class SkillManager:
|
||||
async def reload_skills(self):
|
||||
"""Reload all skills.
|
||||
|
||||
Builtin skills (templates/skills/) are copied to data/skills/ on first run,
|
||||
then all skills are loaded from data/skills/.
|
||||
All skills are loaded from data/skills/.
|
||||
|
||||
NOTE: This performs a full scan. For registering a single new skill,
|
||||
consider adding it directly to self.skills instead of reloading all.
|
||||
@@ -51,25 +47,6 @@ class SkillManager:
|
||||
managed_root = self.get_managed_skills_root()
|
||||
os.makedirs(managed_root, exist_ok=True)
|
||||
|
||||
# Copy builtin skills to data/skills/ if not already present
|
||||
builtin_root = self.get_builtin_skills_root()
|
||||
if os.path.isdir(builtin_root):
|
||||
for package_root, entry_file in self._discover_skill_directories(builtin_root):
|
||||
skill_data = {
|
||||
'package_root': package_root,
|
||||
'entry_file': entry_file,
|
||||
}
|
||||
if not self._load_skill_file(skill_data):
|
||||
continue
|
||||
|
||||
skill_name = skill_data['name']
|
||||
target_path = os.path.join(managed_root, skill_name)
|
||||
|
||||
# Only copy if target doesn't exist (preserve user modifications)
|
||||
if not os.path.exists(target_path):
|
||||
shutil.copytree(package_root, target_path)
|
||||
self.ap.logger.info(f'Copied builtin skill "{skill_name}" to data/skills/')
|
||||
|
||||
# Load all skills from data/skills/
|
||||
if os.path.isdir(managed_root):
|
||||
for package_root, entry_file in self._discover_skill_directories(managed_root):
|
||||
@@ -105,11 +82,6 @@ class SkillManager:
|
||||
"""Get the root directory for managed user skills."""
|
||||
return paths.get_data_path('skills')
|
||||
|
||||
@staticmethod
|
||||
def get_builtin_skills_root() -> str:
|
||||
"""Get the root directory for builtin skills (templates/skills/)."""
|
||||
return paths.get_resource_path('templates/skills')
|
||||
|
||||
def _discover_skill_directories(self, root_path: str, max_depth: int = 6) -> list[tuple[str, str]]:
|
||||
"""Discover all skill directories under root_path."""
|
||||
discovered: list[tuple[str, str]] = []
|
||||
@@ -189,4 +161,4 @@ class SkillManager:
|
||||
|
||||
def get_skill_by_name(self, name: str) -> dict | None:
|
||||
"""Get skill data by name."""
|
||||
return self.skills.get(name)
|
||||
return self.skills.get(name)
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
---
|
||||
name: create-skill
|
||||
description: Create new skills for LangBot. Use when users want to create, modify, or register a skill. Helps draft SKILL.md with proper frontmatter and register skills from sandbox directories.
|
||||
---
|
||||
|
||||
# Create Skill
|
||||
|
||||
A skill for creating new LangBot skills.
|
||||
|
||||
## Skill Structure
|
||||
|
||||
A skill is a directory containing, at minimum, a `SKILL.md` file:
|
||||
|
||||
```
|
||||
skill-name/
|
||||
├── SKILL.md # Required: metadata + instructions
|
||||
├── scripts/ # Optional: helper scripts
|
||||
├── references/ # Optional: documentation
|
||||
├── assets/ # Optional: templates, resources
|
||||
```
|
||||
|
||||
## SKILL.md Format
|
||||
|
||||
SKILL.md must contain YAML frontmatter followed by Markdown content:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: skill-name
|
||||
display_name: Skill Display Name
|
||||
description: What this skill does and when to use it.
|
||||
---
|
||||
|
||||
# Skill Title
|
||||
|
||||
Instructions for how to use this skill...
|
||||
|
||||
## Examples
|
||||
|
||||
- Example 1
|
||||
- Example 2
|
||||
```
|
||||
|
||||
### Frontmatter Fields
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `name` | Yes | Skill identifier. 1-64 chars, lowercase letters, numbers, hyphens only. Must not start or end with hyphen. |
|
||||
| `display_name` | No | Human-readable name shown in UI. |
|
||||
| `description` | Yes | What the skill does and when to use it. Max 1024 chars. Include keywords for triggering. |
|
||||
| `license` | No | License name or reference. |
|
||||
| `metadata` | No | Additional key-value metadata. |
|
||||
|
||||
### Body Content
|
||||
|
||||
The Markdown body contains skill instructions. Recommended sections:
|
||||
- Step-by-step instructions
|
||||
- Examples
|
||||
- Common edge cases
|
||||
- Guidelines
|
||||
|
||||
Keep SKILL.md under 500 lines. Move detailed content to `references/` directory.
|
||||
|
||||
## Creating a Skill
|
||||
|
||||
1. **Understand requirements**: Ask what the skill should do
|
||||
2. **Draft SKILL.md**: Create frontmatter + instructions
|
||||
3. **Create in sandbox**: Write files to `/workspace/{skill-name}/`
|
||||
4. **Register**: Use `register_skill` tool to register to LangBot store
|
||||
|
||||
### Name Rules
|
||||
|
||||
- Lowercase letters, numbers, hyphens only
|
||||
- Cannot start or end with hyphen
|
||||
- No consecutive hyphens (`--`)
|
||||
- 1-64 characters
|
||||
|
||||
Valid: `pdf-processing`, `data-analysis`, `code-review`
|
||||
Invalid: `PDF-Processing`, `-pdf`, `pdf--processing`
|
||||
|
||||
### Description Tips
|
||||
|
||||
Good description describes both what and when:
|
||||
```yaml
|
||||
description: Extracts text from PDF files. Use when working with PDF documents or when user mentions PDFs.
|
||||
```
|
||||
|
||||
Poor description:
|
||||
```yaml
|
||||
description: Helps with PDFs.
|
||||
```
|
||||
|
||||
## Workflow Example
|
||||
|
||||
1. User: "Create a skill for generating reports"
|
||||
2. Ask clarifying questions about report format, templates, etc.
|
||||
3. Create `/workspace/report-generator/SKILL.md`
|
||||
4. Optionally create helper scripts in `/workspace/report-generator/scripts/`
|
||||
5. Call `register_skill(path="/workspace/report-generator", name="report-generator")`
|
||||
6. Skill is now available via `activate(skill_name="report-generator")`
|
||||
|
||||
## After Registration
|
||||
|
||||
The skill package is copied to `data/skills/` and loaded by LangBot.
|
||||
User can activate it immediately with the `activate` tool.
|
||||
Reference in New Issue
Block a user