import { resolveAgentGooseMode } from './policies.mjs'; export const CAPABILITY_CATALOG = [ { key: 'shell', label: 'Shell 命令', description: '执行 bash/shell 命令(developer.shell)', risk: 'high', category: 'developer', }, { key: 'static_publish', label: '用户沙箱目录', description: '在 MindSpace/<用户名>/ 内可使用 write、edit、shell、tree(不可越出该目录;由 static-page-publish 技能开通)', risk: 'medium', category: 'publisher', }, { key: 'filesystem', label: '文件读写(全目录)', description: '在工作区内任意读写文件(developer.write / edit),仅高级用户', risk: 'high', category: 'developer', }, { key: 'code_browse', label: '代码浏览', description: '目录树、代码结构分析(developer.tree、analyze)', risk: 'low', category: 'developer', }, { key: 'image_read', label: '图片读取', description: '读取本地或网络图片(developer.read_image)', risk: 'low', category: 'developer', }, { key: 'skills', label: '技能加载', description: '加载技能与知识(skills / summon.load)', risk: 'low', category: 'knowledge', }, { key: 'subagent', label: '子任务派发', description: 'delegate 子 Agent 执行复杂任务(summon)', risk: 'high', category: 'agent', }, { key: 'code_sandbox', label: '沙箱脚本', description: 'execute_typescript 在沙箱中批量调用工具(code_execution)', risk: 'high', category: 'agent', }, { key: 'chat_recall', label: '对话回溯', description: '检索历史会话(chatrecall)', risk: 'low', category: 'memory', }, { key: 'context_memory', label: '项目记忆', description: '会话级项目记忆注入(projectmemory,H5 引导用)', risk: 'low', category: 'memory', }, { key: 'memory_store', label: '长期记忆', description: '记住/检索用户偏好(memory 扩展)', risk: 'medium', category: 'memory', }, { key: 'extension_admin', label: '扩展管理', description: '搜索、启用、禁用扩展(extensionmanager)', risk: 'high', category: 'admin', }, { key: 'apps', label: '应用管理', description: '创建与管理 TKMind 应用(apps)', risk: 'medium', category: 'agent', }, { key: 'todo', label: '待办事项', description: '任务列表跟踪(todo)', risk: 'low', category: 'productivity', }, { key: 'computer', label: '电脑控制', description: '自动化脚本、网页抓取、文档处理(computercontroller)', risk: 'high', category: 'automation', }, { key: 'charts', label: '数据可视化', description: '图表与可视化(autovisualiser)', risk: 'low', category: 'automation', }, { key: 'aider', label: 'Aider 编码', description: '多文件编码委托(aider)', risk: 'high', category: 'developer', }, ]; /** Capabilities that must never be enabled for regular users, even via DB overrides. */ export const USER_NON_GRANTABLE_CAPABILITIES = new Set(['extension_admin']); export function clampUserCapabilities(capabilities) { const clamped = { ...capabilities }; for (const key of USER_NON_GRANTABLE_CAPABILITIES) { clamped[key] = false; } return clamped; } export const DEFAULT_USER_CAPABILITIES = Object.fromEntries( CAPABILITY_CATALOG.map(({ key }) => { const defaults = { shell: false, static_publish: false, filesystem: false, code_browse: false, image_read: true, skills: false, subagent: false, code_sandbox: false, chat_recall: false, context_memory: true, memory_store: false, extension_admin: false, apps: false, todo: false, computer: false, charts: false, aider: false, }; return [key, defaults[key] ?? false]; }), ); const CATALOG_KEYS = new Set(CAPABILITY_CATALOG.map((item) => item.key)); export function catalogKeys() { return [...CATALOG_KEYS]; } export function isValidCapabilityKey(key) { return CATALOG_KEYS.has(key); } function makeExtension(type, name, tools = []) { return { type, name, description: '', display_name: name, bundled: true, available_tools: tools, }; } export const SANDBOX_DEVELOPER_TOOLS = ['write', 'edit', 'shell', 'tree', 'read_image']; /** static_publish sandbox: only file writes inside MindSpace// — no tree/shell by default */ export function sandboxDeveloperTools(capabilities) { const tools = ['write', 'edit']; if (capabilities.shell) { tools.push('shell', 'tree'); } else if (capabilities.code_browse) { tools.push('tree'); } if (capabilities.image_read) tools.push('read_image'); return tools; } export function developerToolsFromPolicy(sessionPolicy) { const developer = sessionPolicy?.extensionOverrides?.find((ext) => ext.name === 'developer'); return developer?.available_tools ?? []; } function mergeDeveloperTools(capabilities) { const tools = []; if (capabilities.shell) tools.push('shell'); if (capabilities.filesystem) tools.push('write', 'edit'); if (capabilities.code_browse) tools.push('tree'); if (capabilities.image_read) tools.push('read_image'); return tools; } /** * Build goose agent/start extension_overrides from resolved capability flags. * Returns null when the caller should use server defaults (admin / unrestricted). */ export function buildAgentExtensionPolicy( capabilities, { unrestricted = false, policies = null } = {}, ) { if (unrestricted) { return { extensionOverrides: null, enableContextMemory: true, gooseMode: null }; } const extensions = []; if (capabilities.static_publish) { const sandboxTools = sandboxDeveloperTools(capabilities); if (sandboxTools.length > 0) { extensions.push(makeExtension('platform', 'developer', sandboxTools)); } extensions.push(makeExtension('platform', 'skills', [])); extensions.push(makeExtension('platform', 'summon', ['load_skill'])); extensions.push(makeExtension('platform', 'projectmemory', [])); } else { const developerTools = mergeDeveloperTools(capabilities); if (developerTools.length > 0) { extensions.push(makeExtension('platform', 'developer', developerTools)); } if (capabilities.code_browse) { extensions.push(makeExtension('platform', 'analyze', [])); } if (capabilities.skills) { extensions.push(makeExtension('platform', 'skills', [])); } if (capabilities.skills || capabilities.subagent) { const summonTools = []; if (capabilities.skills) summonTools.push('load', 'load_skill'); if (capabilities.subagent) summonTools.push('delegate'); extensions.push(makeExtension('platform', 'summon', summonTools)); } } if (capabilities.code_sandbox) { extensions.push(makeExtension('platform', 'code_execution', [])); } if (capabilities.chat_recall) { extensions.push(makeExtension('platform', 'chatrecall', [])); } if (capabilities.context_memory) { extensions.push(makeExtension('platform', 'projectmemory', [])); } if (capabilities.memory_store) { extensions.push(makeExtension('builtin', 'memory', [])); } if (capabilities.extension_admin) { extensions.push(makeExtension('platform', 'extensionmanager', [])); } if (capabilities.apps) { extensions.push(makeExtension('platform', 'apps', [])); } if (capabilities.todo) { extensions.push(makeExtension('platform', 'todo', [])); } if (capabilities.computer) { extensions.push(makeExtension('builtin', 'computercontroller', [])); } if (capabilities.charts) { extensions.push(makeExtension('builtin', 'autovisualiser', [])); } if (capabilities.aider) { extensions.push(makeExtension('platform', 'aider', [])); } return { extensionOverrides: extensions, enableContextMemory: Boolean( capabilities.context_memory || capabilities.chat_recall || capabilities.static_publish, ), gooseMode: resolveAgentGooseMode(capabilities, policies), }; } export function normalizeCapabilityPatch(patch) { const normalized = {}; for (const [key, value] of Object.entries(patch ?? {})) { if (!isValidCapabilityKey(key)) continue; normalized[key] = Boolean(value); } return normalized; }