Files
memind/capabilities.mjs
T
John 6ee6fd64dd Add MindSpace page live edit, chat skills, and H5 deploy tooling.
Introduce page edit sessions with draft preview and patch API, chat skill picker, user memory profile, h5ApiBase resolution, voice WAV transport, and scripts for 105/g2 deployment and Plaza local dev.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 22:09:38 -07:00

323 lines
9.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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/<用户ID>/ 内可使用 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: '会话级项目记忆注入(projectmemoryH5 引导用)',
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: true,
subagent: false,
code_sandbox: false,
chat_recall: true,
context_memory: true,
memory_store: true,
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/<userId>/ — 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: 'auto' };
}
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),
};
}
/**
* Narrow policy for in-preview page edit sub-sessions: patch API via shell when allowed,
* otherwise reply-only patches via mindspace-page-update blocks.
*/
export function buildPageEditAgentPolicy(basePolicy) {
if (basePolicy?.unrestricted) {
return {
...basePolicy,
enableContextMemory: false,
gooseMode: 'auto',
};
}
const baseDeveloper = basePolicy?.extensionOverrides?.find((ext) => ext.name === 'developer');
const canShell = baseDeveloper?.available_tools?.includes('shell') ?? false;
const extensions = canShell ? [makeExtension('platform', 'developer', ['shell'])] : [];
return {
...basePolicy,
extensionOverrides: extensions,
enableContextMemory: false,
gooseMode: 'auto',
};
}
export function normalizeCapabilityPatch(patch) {
const normalized = {};
for (const [key, value] of Object.entries(patch ?? {})) {
if (!isValidCapabilityKey(key)) continue;
normalized[key] = Boolean(value);
}
return normalized;
}