universal-tool-access.ts
1 import { dedup } from '@/lib/shared-utils' 2 import { getExtensionManager } from './extensions' 3 4 const UNIVERSAL_CORE_EXTENSION_IDS = [ 5 'shell', 6 'files', 7 'edit_file', 8 'delegate', 9 'web', 10 'browser', 11 'memory', 12 'manage_platform', 13 'manage_agents', 14 'manage_projects', 15 'manage_tasks', 16 'manage_schedules', 17 'manage_skills', 18 'manage_documents', 19 'manage_webhooks', 20 'manage_connectors', 21 'manage_sessions', 22 'manage_secrets', 23 'manage_chatrooms', 24 'spawn_subagent', 25 'http_request', 26 'wallet', 27 'monitor', 28 'openclaw_workspace', 29 'openclaw_nodes', 30 'schedule_wake', 31 'context_mgmt', 32 'discovery', 33 'extension_creator', 34 'image_gen', 35 'email', 36 'replicate', 37 'mailbox', 38 'ask_human', 39 ] as const 40 41 function normalizeExtensionList(value: string[] | undefined | null): string[] { 42 if (!Array.isArray(value)) return [] 43 return value 44 .map((entry) => (typeof entry === 'string' ? entry.trim() : '')) 45 .filter(Boolean) 46 } 47 48 export function listUniversalToolAccessExtensionIds(extraExtensions?: string[] | null): string[] { 49 const installedExtensionIds = getExtensionManager() 50 .listExtensions() 51 .filter((meta) => meta.enabled !== false) 52 .map((meta) => meta.filename) 53 54 return dedup([ 55 ...UNIVERSAL_CORE_EXTENSION_IDS, 56 ...installedExtensionIds, 57 ...normalizeExtensionList(extraExtensions), 58 ]) 59 } 60 61 // Minimum extensions that a 'scoped' agent always gets regardless of its 62 // declared tool list. Memory + context management are required for the agent 63 // to function (remembering things, noticing when it's out of context), and 64 // ask_human lets it escalate to the user when stuck. Everything else is 65 // filterable through agent.tools. 66 const SCOPED_TOOL_BASELINE = ['memory', 'context_mgmt', 'ask_human'] as const 67 68 /** 69 * Returns the set of enabled extension IDs for a scoped-access agent: the 70 * intersection of `listUniversalToolAccessExtensionIds()` with the agent's 71 * declared tools, plus the non-negotiable baseline. Use this when an agent 72 * has opted into `toolAccessMode: 'scoped'` to shrink per-turn context. 73 */ 74 export function listScopedToolAccessExtensionIds( 75 declaredTools: string[] | null | undefined, 76 extraExtensions?: string[] | null, 77 ): string[] { 78 const universe = new Set(listUniversalToolAccessExtensionIds(extraExtensions)) 79 const declared = normalizeExtensionList(declaredTools) 80 const scoped = declared.filter((tool) => universe.has(tool)) 81 return dedup([...SCOPED_TOOL_BASELINE, ...scoped]) 82 }