diff --git a/package.json b/package.json index 1e3856ff..d8094ac8 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "socket.io-client": "^4.8.3", "turndown": "^7.2.2", "undici": "^7.19.2", - "uuid": "^13.0.0" + "uuid": "^13.0.0", + "yaml": "^2.8.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02d9e9ec..fbfe07a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,6 +80,9 @@ importers: uuid: specifier: ^13.0.0 version: 13.0.0 + yaml: + specifier: ^2.8.2 + version: 2.8.2 devDependencies: '@types/node': specifier: ^25.0.10 diff --git a/skills/code-review/SKILL.md b/skills/code-review/SKILL.md new file mode 100644 index 00000000..fe0f3256 --- /dev/null +++ b/skills/code-review/SKILL.md @@ -0,0 +1,81 @@ +--- +name: Code Review +description: Review code for bugs, security issues, and best practices +version: 1.0.0 +metadata: + emoji: "🔍" + tags: + - code-quality + - security + - review +--- + +## Instructions + +When the user asks you to review code, follow these guidelines: + +### Review Checklist + +1. **Correctness** + - Does the code do what it's supposed to do? + - Are there any logic errors? + - Are edge cases handled? + +2. **Security** + - Input validation and sanitization + - SQL injection vulnerabilities + - XSS vulnerabilities + - Command injection + - Path traversal + - Sensitive data exposure + - Authentication/authorization issues + +3. **Code Quality** + - Is the code readable and maintainable? + - Are variable/function names descriptive? + - Is there unnecessary complexity? + - Are there code duplications? + +4. **Performance** + - Are there obvious performance issues? + - N+1 queries + - Unnecessary loops or computations + - Memory leaks + +5. **Error Handling** + - Are errors properly caught and handled? + - Are error messages helpful? + - Is there proper logging? + +6. **Testing** + - Are there tests for the new code? + - Do the tests cover edge cases? + +### Review Format + +Structure your review as follows: + +``` +## Summary +[Brief overview of what the code does and overall assessment] + +## Critical Issues +[Must-fix issues: bugs, security vulnerabilities] + +## Suggestions +[Improvements and best practices recommendations] + +## Questions +[Clarifications needed about intent or design decisions] + +## Positive Aspects +[Good practices observed in the code] +``` + +### Guidelines + +- Be constructive, not critical +- Explain the "why" behind suggestions +- Provide concrete examples for improvements +- Prioritize issues by severity +- Acknowledge good practices diff --git a/skills/commit/SKILL.md b/skills/commit/SKILL.md new file mode 100644 index 00000000..d9d38eac --- /dev/null +++ b/skills/commit/SKILL.md @@ -0,0 +1,75 @@ +--- +name: Git Commit Helper +description: Create well-formatted git commits following conventional commit standards +version: 1.0.0 +metadata: + emoji: "📝" + requiresBinaries: + - git + tags: + - git + - developer-tools +--- + +## Instructions + +When the user asks you to create a commit or commit their changes, follow these guidelines: + +### Step 1: Review Changes + +1. Run `git status` to see what files have changed +2. Run `git diff` to see the actual changes +3. If there are staged changes, also run `git diff --staged` + +### Step 2: Analyze and Group Changes + +Group related changes into logical commits: +- Feature additions +- Bug fixes +- Refactoring (no functional change) +- Documentation +- Tests +- Configuration/dependencies + +### Step 3: Create Atomic Commits + +For each logical group of changes: + +1. Stage only the relevant files: `git add ` +2. Create a commit with conventional message format + +### Commit Message Format + +Use conventional commits: +- `feat`: New feature +- `fix`: Bug fix +- `refactor`: Code refactoring (no functional change) +- `docs`: Documentation changes +- `test`: Adding or updating tests +- `chore`: Build, config, dependencies + +Format: `(): ` + +Example: `feat(auth): add user login endpoint` + +### Rules + +- Each commit should be independently meaningful and buildable +- Related test files should be committed with their implementation +- Never create empty commits +- Never combine unrelated changes in one commit +- Keep commit messages concise but descriptive +- If all changes are related to one logical unit, a single commit is fine + +### Example + +If the user modified: +- `src/api/user.ts` (added new endpoint) +- `src/api/user.test.ts` (tests for new endpoint) +- `src/utils/format.ts` (refactored helper) +- `README.md` (updated docs) + +Create three commits: +1. `git add src/api/user.ts src/api/user.test.ts && git commit -m "feat(api): add user profile endpoint"` +2. `git add src/utils/format.ts && git commit -m "refactor(utils): simplify date formatting logic"` +3. `git add README.md && git commit -m "docs: update API documentation"` diff --git a/src/agent/index.ts b/src/agent/index.ts index 6f569f3f..c3384160 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -2,3 +2,4 @@ export * from "./runner.js"; export * from "./types.js"; export * from "./profile/index.js"; export * from "./context-window/index.js"; +export * from "./skills/index.js"; diff --git a/src/agent/runner.ts b/src/agent/runner.ts index 53c558a8..34cb90de 100644 --- a/src/agent/runner.ts +++ b/src/agent/runner.ts @@ -5,6 +5,7 @@ import { createAgentOutput } from "./output.js"; import { resolveModel, resolveTools } from "./tools.js"; import { SessionManager } from "./session/session-manager.js"; import { ProfileManager } from "./profile/index.js"; +import { SkillManager } from "./skills/index.js"; import { checkContextWindow, DEFAULT_CONTEXT_TOKENS, @@ -43,6 +44,7 @@ export class Agent { private readonly output; private readonly session: SessionManager; private readonly profile?: ProfileManager; + private readonly skillManager?: SkillManager; private readonly contextWindowGuard: ContextWindowGuardResult; private readonly debug: boolean; @@ -57,7 +59,7 @@ export class Agent { this.agent = new PiAgentCore(); - // 加载 Agent Profile(如果指定了 profileId) + // Load Agent Profile (if profileId is specified) let systemPrompt: string | undefined; if (options.profileId) { this.profile = new ProfileManager({ @@ -65,13 +67,29 @@ export class Agent { baseDir: options.profileBaseDir, }); systemPrompt = this.profile.buildSystemPrompt(); - if (systemPrompt) { - this.agent.setSystemPrompt(systemPrompt); - } } else if (options.systemPrompt) { - // 直接使用传入的 systemPrompt + // Use provided systemPrompt directly systemPrompt = options.systemPrompt; - this.agent.setSystemPrompt(options.systemPrompt); + } + + // Initialize SkillManager (enabled by default) + if (options.enableSkills !== false) { + this.skillManager = new SkillManager({ + profileId: options.profileId, + profileBaseDir: options.profileBaseDir, + extraDirs: options.extraSkillDirs, + }); + + // Append skills prompt to system prompt + const skillsPrompt = this.skillManager.buildSkillsPrompt(); + if (skillsPrompt) { + systemPrompt = systemPrompt ? `${systemPrompt}\n\n${skillsPrompt}` : skillsPrompt; + } + } + + // Set the combined system prompt + if (systemPrompt) { + this.agent.setSystemPrompt(systemPrompt); } this.sessionId = options.sessionId ?? uuidv7(); diff --git a/src/agent/skills/eligibility.ts b/src/agent/skills/eligibility.ts new file mode 100644 index 00000000..dab295dd --- /dev/null +++ b/src/agent/skills/eligibility.ts @@ -0,0 +1,110 @@ +/** + * Skill Eligibility Checker + * + * Filter skills based on platform, binaries, and environment requirements + */ + +import { execSync } from "node:child_process"; +import type { Skill, EligibilityResult } from "./types.js"; + +/** + * Check if a binary exists in PATH + * + * @param binary - Binary name to check + * @returns True if binary exists + */ +function binaryExists(binary: string): boolean { + try { + // Use 'which' on Unix, 'where' on Windows + const cmd = process.platform === "win32" ? `where ${binary}` : `which ${binary}`; + execSync(cmd, { stdio: "ignore" }); + return true; + } catch { + return false; + } +} + +/** + * Check if an environment variable is set + * + * @param envVar - Environment variable name + * @returns True if set (even if empty string) + */ +function envExists(envVar: string): boolean { + return envVar in process.env; +} + +/** + * Check if a skill is eligible based on its requirements + * + * @param skill - Skill to check + * @param platform - Platform to check against (defaults to current) + * @returns Eligibility result with reasons if ineligible + */ +export function checkEligibility( + skill: Skill, + platform: NodeJS.Platform = process.platform, +): EligibilityResult { + const reasons: string[] = []; + const metadata = skill.frontmatter.metadata; + + // No metadata means no requirements + if (!metadata) { + return { eligible: true }; + } + + // Platform check + if (metadata.platforms && metadata.platforms.length > 0) { + if (!metadata.platforms.includes(platform)) { + reasons.push( + `Platform '${platform}' not supported (requires: ${metadata.platforms.join(", ")})`, + ); + } + } + + // Binary requirements check + if (metadata.requiresBinaries && metadata.requiresBinaries.length > 0) { + for (const binary of metadata.requiresBinaries) { + if (!binaryExists(binary)) { + reasons.push(`Required binary not found: ${binary}`); + } + } + } + + // Environment variable check + if (metadata.requiresEnv && metadata.requiresEnv.length > 0) { + for (const envVar of metadata.requiresEnv) { + if (!envExists(envVar)) { + reasons.push(`Required environment variable not set: ${envVar}`); + } + } + } + + return { + eligible: reasons.length === 0, + reasons: reasons.length > 0 ? reasons : undefined, + }; +} + +/** + * Filter skills by eligibility + * + * @param skills - Map of skills to filter + * @param platform - Platform to check against + * @returns Map containing only eligible skills + */ +export function filterEligibleSkills( + skills: Map, + platform?: NodeJS.Platform, +): Map { + const eligible = new Map(); + + for (const [id, skill] of skills) { + const result = checkEligibility(skill, platform); + if (result.eligible) { + eligible.set(id, skill); + } + } + + return eligible; +} diff --git a/src/agent/skills/index.ts b/src/agent/skills/index.ts new file mode 100644 index 00000000..776d983c --- /dev/null +++ b/src/agent/skills/index.ts @@ -0,0 +1,166 @@ +/** + * Skills Module + * + * Manages skill loading, eligibility filtering, and system prompt generation + */ + +import type { Skill, SkillManagerOptions } from "./types.js"; +import { loadAllSkills, getBundledSkillsDir, getProfileSkillsDir } from "./loader.js"; +import { filterEligibleSkills, checkEligibility } from "./eligibility.js"; + +// Re-export types and utilities +export type { + Skill, + SkillFrontmatter, + SkillMetadata, + SkillSource, + SkillManagerOptions, + EligibilityResult, +} from "./types.js"; + +export { SKILL_FILE, SKILL_SOURCE_PRECEDENCE } from "./types.js"; +export { checkEligibility, filterEligibleSkills } from "./eligibility.js"; +export { parseFrontmatter, parseSkillFile } from "./parser.js"; +export { loadAllSkills, getBundledSkillsDir, getProfileSkillsDir } from "./loader.js"; + +/** + * SkillManager - Loads and manages skills + * + * Provides access to skills from multiple sources with precedence handling + * and eligibility filtering. + */ +export class SkillManager { + private readonly options: SkillManagerOptions; + private skills: Map | undefined; + private eligibleSkills: Map | undefined; + + constructor(options: SkillManagerOptions = {}) { + this.options = options; + } + + /** + * Ensure skills are loaded (lazy loading) + */ + private ensureLoaded(): void { + if (this.skills) return; + this.skills = loadAllSkills(this.options); + this.eligibleSkills = filterEligibleSkills(this.skills, this.options.platform); + } + + /** + * Get all loaded skills (including ineligible) + */ + getAllSkills(): Map { + this.ensureLoaded(); + return this.skills!; + } + + /** + * Get only eligible skills + */ + getEligibleSkills(): Map { + this.ensureLoaded(); + return this.eligibleSkills!; + } + + /** + * Get a specific skill by ID (only from eligible skills) + * + * @param skillId - Skill identifier + * @returns Skill or undefined if not found or ineligible + */ + getSkill(skillId: string): Skill | undefined { + this.ensureLoaded(); + return this.eligibleSkills!.get(skillId); + } + + /** + * Get skill by ID from all skills (including ineligible) + * + * @param skillId - Skill identifier + * @returns Skill or undefined if not found + */ + getSkillFromAll(skillId: string): Skill | undefined { + this.ensureLoaded(); + return this.skills!.get(skillId); + } + + /** + * Reload skills from disk + * Clears cache and reloads on next access + */ + reload(): void { + this.skills = undefined; + this.eligibleSkills = undefined; + } + + /** + * Build skills section for system prompt + * + * Generates formatted documentation of all eligible skills + * for inclusion in the agent's system prompt. + * + * @returns Formatted skill documentation or empty string if no skills + */ + buildSkillsPrompt(): string { + this.ensureLoaded(); + + if (this.eligibleSkills!.size === 0) { + return ""; + } + + const parts: string[] = []; + parts.push("# Available Skills\n"); + parts.push("You have access to the following skills:\n"); + + for (const [id, skill] of this.eligibleSkills!) { + const emoji = skill.frontmatter.metadata?.emoji ?? "🔧"; + const name = skill.frontmatter.name; + const desc = skill.frontmatter.description ?? "No description provided"; + + parts.push(`## ${emoji} ${name} (${id})`); + parts.push(`${desc}\n`); + + // Include full instructions + if (skill.instructions) { + parts.push(skill.instructions); + parts.push(""); + } + } + + return parts.join("\n"); + } + + /** + * Get skill instructions for a specific skill + * + * @param skillId - Skill identifier + * @returns Instructions markdown or undefined if not found + */ + getSkillInstructions(skillId: string): string | undefined { + const skill = this.getSkill(skillId); + return skill?.instructions; + } + + /** + * List skill IDs with their display info + * + * @returns Array of skill info for display + */ + listSkills(): Array<{ id: string; name: string; emoji: string; description: string }> { + this.ensureLoaded(); + + const result: Array<{ id: string; name: string; emoji: string; description: string }> = []; + + for (const [id, skill] of this.eligibleSkills!) { + result.push({ + id, + name: skill.frontmatter.name, + emoji: skill.frontmatter.metadata?.emoji ?? "🔧", + description: skill.frontmatter.description ?? "No description", + }); + } + + return result; + } +} diff --git a/src/agent/skills/loader.ts b/src/agent/skills/loader.ts new file mode 100644 index 00000000..a6f11c66 --- /dev/null +++ b/src/agent/skills/loader.ts @@ -0,0 +1,142 @@ +/** + * Skills Loader + * + * Multi-source loading with precedence handling + */ + +import { existsSync, readdirSync, statSync } from "node:fs"; +import { homedir } from "node:os"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import type { Skill, SkillSource, SkillManagerOptions } from "./types.js"; +import { SKILL_FILE, SKILL_SOURCE_PRECEDENCE } from "./types.js"; +import { parseSkillFile } from "./parser.js"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +/** Default profile base directory */ +const DEFAULT_PROFILE_BASE_DIR = join(homedir(), ".super-multica", "agent-profiles"); + +/** Bundled skills directory (relative to package) */ +const BUNDLED_DIR = join(__dirname, "../../../skills"); + +/** + * Discover skill directories in a given base path + * A valid skill directory contains a SKILL.md file + * + * @param baseDir - Base directory to search + * @returns Array of absolute paths to skill directories + */ +function discoverSkillDirs(baseDir: string): string[] { + if (!existsSync(baseDir)) { + return []; + } + + try { + const entries = readdirSync(baseDir); + return entries + .map((name) => join(baseDir, name)) + .filter((path) => { + try { + if (!statSync(path).isDirectory()) { + return false; + } + return existsSync(join(path, SKILL_FILE)); + } catch { + return false; + } + }); + } catch { + return []; + } +} + +/** + * Load all skills from a source directory + * + * @param baseDir - Base directory containing skill subdirectories + * @param source - Source type for loaded skills + * @returns Array of loaded skills + */ +function loadSkillsFromSource(baseDir: string, source: SkillSource): Skill[] { + const skillDirs = discoverSkillDirs(baseDir); + const skills: Skill[] = []; + + for (const dir of skillDirs) { + const skillId = dir.split("/").pop(); + if (!skillId) continue; + + const filePath = join(dir, SKILL_FILE); + const skill = parseSkillFile(filePath, skillId, source); + if (skill) { + skills.push(skill); + } + } + + return skills; +} + +/** + * Get profile skills directory path + * + * @param profileId - Agent profile ID + * @param profileBaseDir - Profile base directory + * @returns Path to profile skills directory + */ +export function getProfileSkillsDir(profileId: string, profileBaseDir?: string): string { + const baseDir = profileBaseDir ?? DEFAULT_PROFILE_BASE_DIR; + return join(baseDir, profileId, "skills"); +} + +/** + * Load all skills from all sources, applying precedence + * Higher precedence sources override skills with the same ID + * + * Loading order (lowest to highest precedence): + * 1. bundled - Package bundled skills + * 2. extra - User-configured extra directories + * 3. profile - ~/.super-multica/agent-profiles//skills/ + * + * @param options - Loader options + * @returns Map of skill ID to Skill + */ +export function loadAllSkills(options: SkillManagerOptions = {}): Map { + const skillMap = new Map(); + + // Define sources in order of precedence (lowest first) + const sources: Array<[string, SkillSource]> = [ + // Bundled skills (lowest precedence) + [BUNDLED_DIR, "bundled"], + // Extra directories (treated as bundled) + ...(options.extraDirs ?? []).map((d): [string, SkillSource] => [d, "bundled"]), + ]; + + // Add profile skills if profileId is provided (highest precedence) + if (options.profileId) { + const profileSkillsDir = getProfileSkillsDir(options.profileId, options.profileBaseDir); + sources.push([profileSkillsDir, "profile"]); + } + + for (const [dir, source] of sources) { + const skills = loadSkillsFromSource(dir, source); + for (const skill of skills) { + const existing = skillMap.get(skill.id); + // Higher precedence overwrites lower + if ( + !existing || + SKILL_SOURCE_PRECEDENCE[source] > SKILL_SOURCE_PRECEDENCE[existing.source] + ) { + skillMap.set(skill.id, skill); + } + } + } + + return skillMap; +} + +/** + * Get path to bundled skills directory + */ +export function getBundledSkillsDir(): string { + return BUNDLED_DIR; +} diff --git a/src/agent/skills/parser.ts b/src/agent/skills/parser.ts new file mode 100644 index 00000000..3205882e --- /dev/null +++ b/src/agent/skills/parser.ts @@ -0,0 +1,132 @@ +/** + * SKILL.md Parser + * + * Parse skill files with YAML frontmatter and markdown body + */ + +import { readFileSync } from "node:fs"; +import { parse as parseYaml } from "yaml"; +import type { Skill, SkillFrontmatter, SkillSource } from "./types.js"; + +/** + * Parse YAML frontmatter from markdown content + * + * @param content - Raw markdown content + * @returns Tuple of [frontmatter object or null, body content] + */ +export function parseFrontmatter(content: string): [Record | null, string] { + // Match frontmatter between --- delimiters at the start + const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/); + if (!match) { + return [null, content.trim()]; + } + + const frontmatterRaw = match[1] ?? ""; + const body = match[2] ?? ""; + + if (!frontmatterRaw) { + return [null, content.trim()]; + } + + try { + const frontmatter = parseYaml(frontmatterRaw) as Record; + return [frontmatter, body.trim()]; + } catch { + // Invalid YAML, return null frontmatter + return [null, content.trim()]; + } +} + +/** + * Validate and coerce frontmatter to SkillFrontmatter type + * + * @param raw - Raw parsed frontmatter + * @returns Validated SkillFrontmatter or null if invalid + */ +function validateFrontmatter(raw: Record): SkillFrontmatter | null { + // Name is required + if (typeof raw.name !== "string" || raw.name.trim() === "") { + return null; + } + + const frontmatter: SkillFrontmatter = { + name: raw.name.trim(), + }; + + if (typeof raw.description === "string") { + frontmatter.description = raw.description; + } + + if (typeof raw.version === "string") { + frontmatter.version = raw.version; + } + + if (typeof raw.author === "string") { + frontmatter.author = raw.author; + } + + if (typeof raw.homepage === "string") { + frontmatter.homepage = raw.homepage; + } + + // Parse metadata if present + if (typeof raw.metadata === "object" && raw.metadata !== null) { + const meta = raw.metadata as Record; + frontmatter.metadata = { + emoji: typeof meta.emoji === "string" ? meta.emoji : undefined, + requiresEnv: Array.isArray(meta.requiresEnv) + ? meta.requiresEnv.filter((v): v is string => typeof v === "string") + : undefined, + requiresBinaries: Array.isArray(meta.requiresBinaries) + ? meta.requiresBinaries.filter((v): v is string => typeof v === "string") + : undefined, + platforms: Array.isArray(meta.platforms) + ? meta.platforms.filter((v): v is string => typeof v === "string") + : undefined, + tags: Array.isArray(meta.tags) + ? meta.tags.filter((v): v is string => typeof v === "string") + : undefined, + }; + } + + return frontmatter; +} + +/** + * Parse a SKILL.md file into a Skill object + * + * @param filePath - Full path to SKILL.md file + * @param skillId - Unique identifier for the skill + * @param source - Source type of the skill + * @returns Parsed Skill or null if invalid + */ +export function parseSkillFile( + filePath: string, + skillId: string, + source: SkillSource, +): Skill | null { + try { + const content = readFileSync(filePath, "utf-8"); + const [rawFrontmatter, instructions] = parseFrontmatter(content); + + if (!rawFrontmatter) { + return null; + } + + const frontmatter = validateFrontmatter(rawFrontmatter); + if (!frontmatter) { + return null; + } + + return { + id: skillId, + frontmatter, + instructions, + source, + filePath, + }; + } catch { + // File read error or other issues + return null; + } +} diff --git a/src/agent/skills/types.ts b/src/agent/skills/types.ts new file mode 100644 index 00000000..787e9eaf --- /dev/null +++ b/src/agent/skills/types.ts @@ -0,0 +1,97 @@ +/** + * Skills Module Types + * + * Type definitions for the skills system + */ + +/** + * Skill metadata for eligibility and display + */ +export interface SkillMetadata { + /** Emoji for display (e.g., "📝") */ + emoji?: string | undefined; + /** Required environment variables */ + requiresEnv?: string[] | undefined; + /** Required binaries in PATH */ + requiresBinaries?: string[] | undefined; + /** Supported platforms (darwin, linux, win32) */ + platforms?: string[] | undefined; + /** Skill tags for categorization */ + tags?: string[] | undefined; +} + +/** + * SKILL.md frontmatter schema + */ +export interface SkillFrontmatter { + /** Skill name (required) */ + name: string; + /** Human-readable description */ + description?: string | undefined; + /** Skill version */ + version?: string | undefined; + /** Author information */ + author?: string | undefined; + /** Homepage/documentation URL */ + homepage?: string | undefined; + /** Skill-specific metadata */ + metadata?: SkillMetadata | undefined; +} + +/** + * Skill source type with precedence (lower value = lower priority) + */ +export type SkillSource = "bundled" | "profile"; + +/** + * Skill source precedence values + */ +export const SKILL_SOURCE_PRECEDENCE: Record = { + bundled: 0, + profile: 1, +}; + +/** + * Parsed skill entry + */ +export interface Skill { + /** Unique skill identifier (directory name) */ + id: string; + /** Parsed frontmatter */ + frontmatter: SkillFrontmatter; + /** Skill instructions (markdown body after frontmatter) */ + instructions: string; + /** Source type */ + source: SkillSource; + /** Full path to SKILL.md */ + filePath: string; +} + +/** + * Skill Manager options + */ +export interface SkillManagerOptions { + /** Agent profile ID (for profile-specific skills) */ + profileId?: string | undefined; + /** Profile base directory, defaults to ~/.super-multica/agent-profiles */ + profileBaseDir?: string | undefined; + /** Additional directories to search for skills */ + extraDirs?: string[] | undefined; + /** Platform override (for testing) */ + platform?: NodeJS.Platform | undefined; +} + +/** + * Skill eligibility check result + */ +export interface EligibilityResult { + /** Whether the skill is eligible */ + eligible: boolean; + /** Reasons for ineligibility */ + reasons?: string[] | undefined; +} + +/** + * Filename constant for skill definition file + */ +export const SKILL_FILE = "SKILL.md"; diff --git a/src/agent/types.ts b/src/agent/types.ts index a31dfe6e..60494a07 100644 --- a/src/agent/types.ts +++ b/src/agent/types.ts @@ -48,4 +48,10 @@ export type AgentOptions = { /** Enable debug logging */ debug?: boolean | undefined; + + // === Skills Configuration === + /** Enable skills system (default: true) */ + enableSkills?: boolean | undefined; + /** Additional directories to search for skills */ + extraSkillDirs?: string[] | undefined; };