feat: add skills system for extensible capability packages
Implement a three-tier skill system (bundled/managed/workspace) that extends Flynn's abilities via SKILL.md instructions injected into the system prompt. - SkillManifest/Skill types with requirements gating (OS, binaries, env) - Loader: discovers skills from directories, validates manifests, checks system requirements, infers manifest from SKILL.md if missing - SkillRegistry: holds skills, generates system prompt additions, supports override by name (workspace > managed > bundled) - SkillInstaller: copies/removes skills in managed directory with upgrade support - Config: add skills.workspace_dir, managed_dir, bundled_dir options - Daemon: loads all skills at startup, injects available skill instructions into the system prompt - Tests: 45 new tests (loader 22, registry 11, installer 12)
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import type { Skill } from './types.js';
|
||||
|
||||
/**
|
||||
* SkillRegistry holds loaded skills and generates system prompt additions.
|
||||
*
|
||||
* Skills are keyed by name. Managed/workspace skills may override bundled
|
||||
* skills by registering with the same name.
|
||||
*/
|
||||
export class SkillRegistry {
|
||||
private skills: Map<string, Skill> = new Map();
|
||||
|
||||
/** Register a skill. Replaces any existing skill with the same name. */
|
||||
register(skill: Skill): void {
|
||||
this.skills.set(skill.manifest.name, skill);
|
||||
console.log(
|
||||
`Skill '${skill.manifest.name}' registered (${skill.manifest.tier}, ${skill.available ? 'available' : 'unavailable'})`
|
||||
);
|
||||
}
|
||||
|
||||
/** Unregister a skill by name. Returns true if the skill existed. */
|
||||
unregister(name: string): boolean {
|
||||
return this.skills.delete(name);
|
||||
}
|
||||
|
||||
/** Look up a skill by name. */
|
||||
get(name: string): Skill | undefined {
|
||||
return this.skills.get(name);
|
||||
}
|
||||
|
||||
/** Return all registered skills. */
|
||||
list(): Skill[] {
|
||||
return Array.from(this.skills.values());
|
||||
}
|
||||
|
||||
/** Return only skills whose requirements are met (available === true). */
|
||||
listAvailable(): Skill[] {
|
||||
return this.list().filter(skill => skill.available);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate system prompt additions from all available skills.
|
||||
*
|
||||
* Each skill's SKILL.md content is formatted as a markdown section:
|
||||
* ```
|
||||
* ## Skill: <name>
|
||||
* <instructions>
|
||||
* ```
|
||||
*
|
||||
* Returns an empty string if no skills are available.
|
||||
*/
|
||||
getSystemPromptAdditions(): string {
|
||||
const available = this.listAvailable();
|
||||
if (available.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return available
|
||||
.map(skill => `## Skill: ${skill.manifest.name}\n${skill.instructions}`)
|
||||
.join('\n\n');
|
||||
}
|
||||
|
||||
/** Return the names of all available skills. */
|
||||
getSkillNames(): string[] {
|
||||
return this.listAvailable().map(skill => skill.manifest.name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user