/ src / lib / server / universal-tool-access.ts
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  }