Initial commit: Memind H5 portal with MindSpace, Plaza, and agent jobs.

Track application source and tests; exclude local env, user workspaces, and runtime data via .gitignore.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
John
2026-06-15 15:04:43 -07:00
commit 2e14873f2d
272 changed files with 64133 additions and 0 deletions
+207
View File
@@ -0,0 +1,207 @@
/** @typedef {'auto' | 'approve' | 'smart_approve' | 'chat'} GooseMode */
/** @typedef {'readwrite' | 'readonly'} WorkspaceAccess */
/** @typedef {'allow' | 'deny'} NetworkEgress */
export const POLICY_CATALOG = [
{
key: 'goose_mode',
label: 'TKMind 模式',
description:
'H5 终端用户不在聊天里点「允许/拒绝」;可用工具由「能力权限」决定。chat 不调用工具,auto 在授权范围内自动执行。',
type: 'select',
options: [
{ value: 'chat', label: '仅聊天 (chat)' },
{ value: 'auto', label: '自动执行 (auto)' },
],
defaultValue: 'chat',
category: 'goose',
risk: 'high',
},
{
key: 'workspace_access',
label: '工作区访问',
description: 'readonly 时禁止 shell 与文件写入,仅允许浏览与分析',
type: 'select',
options: [
{ value: 'readwrite', label: '读写' },
{ value: 'readonly', label: '只读' },
],
defaultValue: 'readwrite',
category: 'workspace',
risk: 'medium',
},
{
key: 'network_egress',
label: '网络出站',
description: 'deny 时禁用 shell、电脑控制、沙箱脚本等可能访问网络的扩展',
type: 'select',
options: [
{ value: 'deny', label: '禁止(推荐)' },
{ value: 'allow', label: '允许(需同时开启对应能力)' },
],
defaultValue: 'deny',
category: 'network',
risk: 'high',
},
{
key: 'api_lockdown',
label: 'API 代理锁定',
description: '开启后仅允许聊天相关 API,拦截改模式、加扩展、写配置等危险请求',
type: 'boolean',
defaultValue: true,
category: 'proxy',
risk: 'medium',
},
];
export const DEFAULT_USER_POLICIES = Object.fromEntries(
POLICY_CATALOG.map((item) => [item.key, item.defaultValue]),
);
const POLICY_KEYS = new Set(POLICY_CATALOG.map((item) => item.key));
const GOOSE_MODES = new Set(['auto', 'approve', 'smart_approve', 'chat']);
const WORKSPACE_ACCESS = new Set(['readwrite', 'readonly']);
const NETWORK_EGRESS = new Set(['allow', 'deny']);
export function policyKeys() {
return [...POLICY_KEYS];
}
export function isValidPolicyKey(key) {
return POLICY_KEYS.has(key);
}
export function normalizePolicyPatch(patch) {
const normalized = {};
for (const [key, raw] of Object.entries(patch ?? {})) {
if (!isValidPolicyKey(key)) continue;
const def = POLICY_CATALOG.find((item) => item.key === key);
if (!def) continue;
if (def.type === 'boolean') {
normalized[key] = raw === true || raw === 'true' || raw === 1 || raw === '1';
continue;
}
const value = String(raw ?? '').trim();
if (def.key === 'goose_mode' && GOOSE_MODES.has(value)) normalized[key] = value;
if (def.key === 'workspace_access' && WORKSPACE_ACCESS.has(value)) normalized[key] = value;
if (def.key === 'network_egress' && NETWORK_EGRESS.has(value)) normalized[key] = value;
}
return normalized;
}
export function resolvePolicies(rolePolicies, userOverrides) {
const resolved = { ...DEFAULT_USER_POLICIES };
for (const [key, value] of Object.entries(rolePolicies ?? {})) {
if (isValidPolicyKey(key)) resolved[key] = value;
}
for (const [key, value] of Object.entries(userOverrides ?? {})) {
if (isValidPolicyKey(key)) resolved[key] = value;
}
if (resolved.goose_mode === 'approve' || resolved.goose_mode === 'smart_approve') {
resolved.goose_mode = 'auto';
}
return resolved;
}
function hasExecutableTools(capabilities) {
return Boolean(
capabilities?.static_publish
|| capabilities?.shell
|| capabilities?.filesystem
|| capabilities?.code_browse
|| capabilities?.subagent
|| capabilities?.code_sandbox
|| capabilities?.computer
|| capabilities?.charts
|| capabilities?.aider
|| capabilities?.apps
|| capabilities?.todo
|| capabilities?.skills
|| capabilities?.chat_recall,
);
}
/**
* Goose mode for H5 agent sessions. Tool allow/deny is enforced via capability grants,
* not end-user confirmation dialogs.
*/
export function resolveAgentGooseMode(capabilities, policies) {
const requested = policies?.goose_mode ?? DEFAULT_USER_POLICIES.goose_mode;
if (!hasExecutableTools(capabilities)) {
return 'chat';
}
if (requested === 'chat' || requested === 'approve' || requested === 'smart_approve') {
return 'auto';
}
return 'auto';
}
/**
* Apply security policies on top of capability flags before building extension overrides.
*/
export function applyPoliciesToCapabilities(capabilities, policies) {
const effective = { ...capabilities };
if (policies.workspace_access === 'readonly') {
effective.shell = false;
effective.filesystem = false;
effective.static_publish = false;
effective.aider = false;
effective.code_sandbox = false;
effective.image_read = false;
}
if (policies.network_egress === 'deny') {
// static_publish 用户的 shell 仅能在本工作区内执行本地命令,不依赖外网出站
if (!effective.static_publish) {
effective.shell = false;
}
effective.computer = false;
effective.code_sandbox = false;
}
return effective;
}
const USER_API_ALLOWLIST = [
{ method: 'GET', pattern: /^\/status$/ },
{ method: 'POST', pattern: /^\/agent\/start$/ },
{ method: 'POST', pattern: /^\/agent\/resume$/ },
{ method: 'GET', pattern: /^\/sessions$/ },
{ method: 'GET', pattern: /^\/sessions\/[^/]+$/ },
{ method: 'GET', pattern: /^\/sessions\/[^/]+\/events$/ },
{ method: 'POST', pattern: /^\/sessions\/[^/]+\/reply$/ },
{ method: 'POST', pattern: /^\/sessions\/[^/]+\/cancel$/ },
{ method: 'POST', pattern: /^\/action-required\/tool-confirmation$/ },
{ method: 'POST', pattern: /^\/agent\/update_provider$/ },
{ method: 'POST', pattern: /^\/config\/read$/ },
{ method: 'POST', pattern: /^\/agent\/harness_bootstrap$/ },
{ method: 'POST', pattern: /^\/agent\/harness_remember$/ },
];
export function isNativeH5ApiPath(pathname) {
const path = String(pathname ?? '');
return path.startsWith('/mindspace/') || path.startsWith('/plaza/');
}
/**
* @returns {{ allowed: boolean, reason?: string }}
*/
export function evaluateProxyRequest(method, pathname, policies, { unrestricted = false } = {}) {
if (unrestricted) return { allowed: true };
if (!policies.api_lockdown) return { allowed: true };
if (isNativeH5ApiPath(pathname)) {
return {
allowed: false,
reason: `H5 本地接口不应走 Agent 代理:${method.toUpperCase()} ${pathname}`,
};
}
const upper = method.toUpperCase();
const allowed = USER_API_ALLOWLIST.some(
(rule) => rule.method === upper && rule.pattern.test(pathname),
);
if (allowed) return { allowed: true };
return {
allowed: false,
reason: `策略已锁定 API${upper} ${pathname}`,
};
}