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:
+31
-2
@@ -9,6 +9,7 @@ import { GatewayServer } from '../gateway/index.js';
|
||||
import { ChannelRegistry, TelegramAdapter, WebChatAdapter } from '../channels/index.js';
|
||||
import type { InboundMessage, OutboundMessage } from '../channels/index.js';
|
||||
import { McpManager } from '../mcp/index.js';
|
||||
import { SkillRegistry, SkillInstaller, loadAllSkills } from '../skills/index.js';
|
||||
import { resolve } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { mkdirSync, readFileSync, existsSync } from 'fs';
|
||||
@@ -25,6 +26,8 @@ export interface DaemonContext {
|
||||
gateway: GatewayServer;
|
||||
channelRegistry: ChannelRegistry;
|
||||
mcpManager: McpManager;
|
||||
skillRegistry: SkillRegistry;
|
||||
skillInstaller: SkillInstaller;
|
||||
}
|
||||
|
||||
function loadSystemPrompt(): string {
|
||||
@@ -199,11 +202,35 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
|
||||
console.log('MCP servers stopped');
|
||||
});
|
||||
|
||||
// Initialize skills system
|
||||
const defaultManagedDir = resolve(homedir(), '.flynn/workspace/skills');
|
||||
const skillRegistry = new SkillRegistry();
|
||||
const skillInstaller = new SkillInstaller(config.skills.managed_dir ?? defaultManagedDir);
|
||||
|
||||
const skills = loadAllSkills({
|
||||
bundledDir: config.skills.bundled_dir,
|
||||
managedDir: config.skills.managed_dir ?? defaultManagedDir,
|
||||
workspaceDir: config.skills.workspace_dir,
|
||||
});
|
||||
|
||||
for (const skill of skills) {
|
||||
skillRegistry.register(skill);
|
||||
}
|
||||
|
||||
if (skills.length > 0) {
|
||||
const available = skillRegistry.listAvailable().length;
|
||||
console.log(`Loaded ${skills.length} skill(s) (${available} available)`);
|
||||
}
|
||||
|
||||
// Initialize model router
|
||||
const modelRouter = createModelRouter(config);
|
||||
|
||||
// Load system prompt once for reuse
|
||||
const systemPrompt = loadSystemPrompt();
|
||||
// Load system prompt and append skill instructions
|
||||
let systemPrompt = loadSystemPrompt();
|
||||
const skillAdditions = skillRegistry.getSystemPromptAdditions();
|
||||
if (skillAdditions) {
|
||||
systemPrompt = `${systemPrompt}\n\n# Available Skills\n\n${skillAdditions}`;
|
||||
}
|
||||
|
||||
// Initialize gateway WebSocket server
|
||||
const gateway = new GatewayServer({
|
||||
@@ -296,6 +323,8 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
|
||||
gateway,
|
||||
channelRegistry,
|
||||
mcpManager,
|
||||
skillRegistry,
|
||||
skillInstaller,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user