feat(skills): install to agent-scoped location instead of .skills/ (#148)
Skills now install to ~/.letta/agents/{agentId}/skills/ after agent
creation, aligning with Letta Code CLI behavior. This removes the
duplicate installation that was happening at both startup and after
agent creation.
Changes:
- Add SkillsConfig type and pass through BotConfig
- Update installSkillsToAgent() to actually install skills
- Remove installSkillsToWorkingDir() call from main.ts startup
- Closes #108 (reimplemented from PR #114 due to conflicts)
"The best way to predict the future is to invent it." - Alan Kay
Written by Cameron ◯ Letta Code
This commit is contained in:
@@ -424,7 +424,7 @@ export class LettaBot {
|
||||
updateAgentName(session.agentId, this.config.agentName).catch(() => {});
|
||||
}
|
||||
if (session.agentId) {
|
||||
installSkillsToAgent(session.agentId);
|
||||
installSkillsToAgent(session.agentId, this.config.skills);
|
||||
}
|
||||
}
|
||||
} else if (session.conversationId && session.conversationId !== this.store.conversationId) {
|
||||
|
||||
@@ -103,6 +103,15 @@ export interface OutboundFile {
|
||||
kind?: 'image' | 'file';
|
||||
}
|
||||
|
||||
/**
|
||||
* Skills installation config
|
||||
*/
|
||||
export interface SkillsConfig {
|
||||
cronEnabled?: boolean;
|
||||
googleEnabled?: boolean;
|
||||
additionalSkills?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Bot configuration
|
||||
*/
|
||||
@@ -113,6 +122,9 @@ export interface BotConfig {
|
||||
agentName?: string; // Name for the agent (set via API after creation)
|
||||
allowedTools: string[];
|
||||
|
||||
// Skills
|
||||
skills?: SkillsConfig;
|
||||
|
||||
// Security
|
||||
allowedUsers?: string[]; // Empty = allow all
|
||||
}
|
||||
|
||||
25
src/main.ts
25
src/main.ts
@@ -5,7 +5,7 @@
|
||||
* Chat continues seamlessly between Telegram, Slack, and WhatsApp.
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, readdirSync, promises as fs } from 'node:fs';
|
||||
import { existsSync, mkdirSync, readFileSync, promises as fs } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { spawn } from 'node:child_process';
|
||||
|
||||
@@ -123,7 +123,7 @@ import { CronService } from './cron/service.js';
|
||||
import { HeartbeatService } from './cron/heartbeat.js';
|
||||
import { PollingService } from './polling/service.js';
|
||||
import { agentExists, findAgentByName } from './tools/letta-api.js';
|
||||
import { installSkillsToWorkingDir } from './skills/loader.js';
|
||||
// Skills are now installed to agent-scoped location after agent creation (see bot.ts)
|
||||
|
||||
// Check if config exists (skip in Railway/Docker where env vars are used directly)
|
||||
const configPath = resolveConfigPath();
|
||||
@@ -313,27 +313,16 @@ async function main() {
|
||||
console.log(`[Storage] Data directory: ${dataDir}`);
|
||||
console.log(`[Storage] Working directory: ${config.workingDir}`);
|
||||
|
||||
// Install feature-gated skills based on enabled features
|
||||
// Skills are NOT installed by default - only when their feature is enabled
|
||||
const skillsDir = resolve(config.workingDir, '.skills');
|
||||
mkdirSync(skillsDir, { recursive: true });
|
||||
|
||||
installSkillsToWorkingDir(config.workingDir, {
|
||||
cronEnabled: config.cronEnabled,
|
||||
googleEnabled: config.polling.gmail.enabled, // Gmail polling uses gog skill
|
||||
});
|
||||
|
||||
const existingSkills = readdirSync(skillsDir).filter(f => !f.startsWith('.'));
|
||||
if (existingSkills.length > 0) {
|
||||
console.log(`[Skills] ${existingSkills.length} skill(s) available: ${existingSkills.join(', ')}`);
|
||||
}
|
||||
|
||||
// Create bot
|
||||
// Create bot with skills config (skills installed to agent-scoped location after agent creation)
|
||||
const bot = new LettaBot({
|
||||
workingDir: config.workingDir,
|
||||
model: config.model,
|
||||
agentName: process.env.AGENT_NAME || 'LettaBot',
|
||||
allowedTools: config.allowedTools,
|
||||
skills: {
|
||||
cronEnabled: config.cronEnabled,
|
||||
googleEnabled: config.polling.gmail.enabled,
|
||||
},
|
||||
});
|
||||
|
||||
const attachmentsDir = resolve(config.workingDir, 'attachments');
|
||||
|
||||
@@ -294,8 +294,47 @@ export function installSkillsToWorkingDir(workingDir: string, config: SkillsInst
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated Use installSkillsToWorkingDir instead
|
||||
* Install feature-gated skills to the agent-scoped skills directory
|
||||
* (~/.letta/agents/{agentId}/skills/)
|
||||
*
|
||||
* This aligns with Letta Code CLI which uses agent-scoped skills.
|
||||
* Called after agent creation in bot.ts.
|
||||
*/
|
||||
export function installSkillsToAgent(agentId: string): void {
|
||||
// No-op - skills are now installed to working dir on startup
|
||||
export function installSkillsToAgent(agentId: string, config: SkillsInstallConfig = {}): void {
|
||||
const targetDir = getAgentSkillsDir(agentId);
|
||||
|
||||
// Ensure target directory exists
|
||||
mkdirSync(targetDir, { recursive: true });
|
||||
|
||||
// Collect skills to install based on enabled features
|
||||
const skillsToInstall: string[] = [];
|
||||
|
||||
// Cron skills (always if cron is enabled)
|
||||
if (config.cronEnabled) {
|
||||
skillsToInstall.push(...FEATURE_SKILLS.cron);
|
||||
}
|
||||
|
||||
// Google skills (if Gmail polling or Google is configured)
|
||||
if (config.googleEnabled) {
|
||||
skillsToInstall.push(...FEATURE_SKILLS.google);
|
||||
}
|
||||
|
||||
// Additional explicitly enabled skills
|
||||
if (config.additionalSkills?.length) {
|
||||
skillsToInstall.push(...config.additionalSkills);
|
||||
}
|
||||
|
||||
if (skillsToInstall.length === 0) {
|
||||
return; // No skills to install - silent return
|
||||
}
|
||||
|
||||
// Source directories (later has priority)
|
||||
const sourceDirs = [SKILLS_SH_DIR, BUNDLED_SKILLS_DIR, PROJECT_SKILLS_DIR];
|
||||
|
||||
// Install the specific skills
|
||||
const installed = installSpecificSkills(skillsToInstall, sourceDirs, targetDir);
|
||||
|
||||
if (installed.length > 0) {
|
||||
console.log(`[Skills] Installed ${installed.length} skill(s) to agent: ${installed.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user