6ee6fd64dd
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>
323 lines
9.1 KiB
JavaScript
323 lines
9.1 KiB
JavaScript
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: '会话级项目记忆注入(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: 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;
|
||
}
|