diff --git a/.env.example b/.env.example index 6540c58..6ea2837 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,10 @@ LETTA_API_KEY=your_letta_api_key # Allowed tools (comma-separated) # ALLOWED_TOOLS=Read,Glob,Grep,Task,web_search,conversation_search +# Disallowed tools (comma-separated) +# Default blocks plan-mode interactive tools that can stall headless agents +# DISALLOWED_TOOLS=EnterPlanMode,ExitPlanMode + # ============================================ # Telegram (required: at least one channel) # ============================================ diff --git a/src/core/bot.ts b/src/core/bot.ts index b8ed142..d272db1 100644 --- a/src/core/bot.ts +++ b/src/core/bot.ts @@ -151,11 +151,20 @@ export class LettaBot implements AgentSession { // ========================================================================= private get baseSessionOptions() { + const disallowedTools = this.config.disallowedTools || []; + return { permissionMode: 'bypassPermissions' as const, allowedTools: this.config.allowedTools, + disallowedTools, cwd: this.config.workingDir, canUseTool: (toolName: string, _toolInput: Record) => { + if (disallowedTools.includes(toolName)) { + return { + behavior: 'deny' as const, + message: `Tool '${toolName}' is blocked by bot configuration`, + }; + } console.log(`[Bot] Tool approval requested: ${toolName} (should be auto-approved by bypassPermissions)`); return { behavior: 'allow' as const }; }, diff --git a/src/core/types.ts b/src/core/types.ts index 5616ce7..74157f4 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -124,6 +124,7 @@ export interface BotConfig { workingDir: string; agentName?: string; // Name for the agent (set via API after creation) allowedTools: string[]; + disallowedTools?: string[]; // Display displayName?: string; // Prefix outbound messages (e.g. "💜 Signo") diff --git a/src/main.ts b/src/main.ts index d1c0677..7f98e8b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -397,10 +397,22 @@ function createGroupBatcher( // Skills are installed to agent-scoped directory when agent is created (see core/bot.ts) +function parseCsvList(raw: string): string[] { + return raw + .split(',') + .map((item) => item.trim()) + .filter((item) => item.length > 0); +} + // Global config (shared across all agents) const globalConfig = { workingDir: getWorkingDir(), - allowedTools: (process.env.ALLOWED_TOOLS || 'Bash,Read,Edit,Write,Glob,Grep,Task,web_search,conversation_search').split(','), + allowedTools: parseCsvList( + process.env.ALLOWED_TOOLS || 'Bash,Read,Edit,Write,Glob,Grep,Task,web_search,conversation_search', + ), + disallowedTools: parseCsvList( + process.env.DISALLOWED_TOOLS || 'EnterPlanMode,ExitPlanMode', + ), attachmentsMaxBytes: resolveAttachmentsMaxBytes(), attachmentsMaxAgeDays: resolveAttachmentsMaxAgeDays(), cronEnabled: process.env.CRON_ENABLED === 'true', // Legacy env var fallback @@ -472,6 +484,7 @@ async function main() { workingDir: globalConfig.workingDir, agentName: agentConfig.name, allowedTools: globalConfig.allowedTools, + disallowedTools: globalConfig.disallowedTools, displayName: agentConfig.displayName, maxToolCalls: agentConfig.features?.maxToolCalls, skills: {