feat: MetonaAI Desktop 初始项目

- Electron + React + TypeScript 架构
- 三栏布局: Sidebar | ChatPanel | DetailPanel
- 9 个内置工具 (文件系统/网络/记忆/命令)
- SQLite 持久化 (better-sqlite3)
- MUI 暗色/亮色主题系统
- Agent Loop ReAct 状态机引擎
- DeepSeek / Agnes AI / Ollama Provider 适配器
- MCP 协议集成
- 系统托盘 + 全局快捷键
- Tailwind CSS v4 + Tailwind Merge
- 修复: Sidebar 缺失 TextField 导入导致黑屏
This commit is contained in:
thzxx
2026-06-27 21:33:27 +08:00
commit 1d185db6b3
109 changed files with 39155 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
/**
* Policy Engine — 权限策略引擎
*
* 三级权限模型:Read / Write / External Action
*
* @see docs/生产级通用 AI Agent 智能体桌面应用:完整设计与构建指南.html — 第十章
*/
export enum PermissionLevel {
READ = 'read',
WRITE = 'write',
EXTERNAL_ACTION = 'external',
}
export interface PermissionPolicy {
toolName: string;
requiredLevel: PermissionLevel;
allowedPatterns?: RegExp[];
deniedPatterns?: RegExp[];
maxFrequency?: number;
requireConfirmation?: boolean;
}
export const DEFAULT_POLICIES: PermissionPolicy[] = [
{ toolName: 'read_file', requiredLevel: PermissionLevel.READ, deniedPatterns: [/\/etc\//, /\/proc\//] },
{ toolName: 'web_search', requiredLevel: PermissionLevel.READ, maxFrequency: 10 },
{ toolName: 'list_directory', requiredLevel: PermissionLevel.READ },
{ toolName: 'search_files', requiredLevel: PermissionLevel.READ },
{ toolName: 'memory_search', requiredLevel: PermissionLevel.READ },
{ toolName: 'write_file', requiredLevel: PermissionLevel.WRITE, deniedPatterns: [/\/etc\//, /\/proc\//, /\/System\//], requireConfirmation: true, maxFrequency: 5 },
{ toolName: 'memory_store', requiredLevel: PermissionLevel.WRITE },
{ toolName: 'run_command', requiredLevel: PermissionLevel.EXTERNAL_ACTION, requireConfirmation: true, maxFrequency: 3 },
{ toolName: 'web_extract', requiredLevel: PermissionLevel.READ },
];
export class PolicyEngine {
private policies: Map<string, PermissionPolicy> = new Map();
constructor(customPolicies: PermissionPolicy[] = []) {
for (const policy of DEFAULT_POLICIES) {
this.policies.set(policy.toolName, policy);
}
for (const policy of customPolicies) {
this.policies.set(policy.toolName, policy);
}
}
checkAuthorization(toolName: string, args: Record<string, unknown>): {
authorized: boolean;
reason?: string;
level: PermissionLevel;
requiresConfirmation: boolean;
} {
const policy = this.policies.get(toolName);
if (!policy) {
return {
authorized: false,
reason: `No policy configured for tool: ${toolName}`,
level: PermissionLevel.EXTERNAL_ACTION,
requiresConfirmation: true,
};
}
if (policy.deniedPatterns) {
const argsStr = JSON.stringify(args);
for (const pattern of policy.deniedPatterns) {
if (pattern.test(argsStr)) {
return {
authorized: false,
reason: `Arguments match denied pattern: ${pattern.source}`,
level: policy.requiredLevel,
requiresConfirmation: false,
};
}
}
}
return {
authorized: true,
level: policy.requiredLevel,
requiresConfirmation: policy.requireConfirmation ?? false,
};
}
}
+91
View File
@@ -0,0 +1,91 @@
/**
* Sandbox Manager — 沙箱执行环境
*
* 四层纵深防御:进程隔离、路径白名单、网络策略、资源限制。
*
* @see docs/生产级通用 AI Agent 智能体桌面应用:完整设计与构建指南.html — 第五章
*/
export interface SandboxConfig {
allowedPaths?: string[];
networkPolicy?: 'allowall' | 'deny-all' | 'allowlist';
resourceLimits?: Partial<ResourceLimits>;
}
export interface ResourceLimits {
maxMemoryMB: number;
maxCpuSeconds: number;
maxExecutionMs: number;
maxOutputSizeKB: number;
}
export interface SandboxExecutionResult {
success: boolean;
output?: string;
stderr?: string;
exitCode?: number;
error?: string;
durationMs: number;
}
export class SandboxManager {
private allowedPaths: Set<string> = new Set();
private networkPolicy: 'allowall' | 'deny-all' | 'allowlist' = 'deny-all';
private resourceLimits: ResourceLimits = {
maxMemoryMB: 512,
maxCpuSeconds: 30,
maxExecutionMs: 60_000,
maxOutputSizeKB: 1024,
};
constructor(private config: SandboxConfig) {
this.allowedPaths = new Set(config.allowedPaths ?? []);
this.networkPolicy = config.networkPolicy ?? 'allowlist';
}
/**
* 校验文件路径是否在白名单内
*/
validatePath(requestedPath: string): { allowed: boolean; resolvedPath: string; reason?: string } {
const { resolve } = require('path');
const resolved = resolve(requestedPath);
if (requestedPath.includes('..')) {
return { allowed: false, resolvedPath: resolved, reason: 'Path traversal detected' };
}
if (this.allowedPaths.size > 0) {
const isAllowed = Array.from(this.allowedPaths).some((allowed) => resolved.startsWith(allowed));
if (!isAllowed) {
return { allowed: false, resolvedPath: resolved, reason: 'Path not in allowed list' };
}
}
return { allowed: true, resolvedPath: resolved };
}
/**
* 静态代码安全扫描
*/
scanCode(code: string): { safe: boolean; reason?: string } {
const dangerousPatterns = [
/require\s*\(\s*['"]child_process['"]\s*\)/,
/eval\s*\(/,
/process\.exit/,
/import\s+.*from\s+['"]fs['"]/,
/\.\.\//,
/rm\s+-rf/,
/>\s*\/dev\/null/,
/curl.*\|\s*bash/,
/wget.*\|\s*sh/,
];
for (const pattern of dangerousPatterns) {
if (pattern.test(code)) {
return { safe: false, reason: `Dangerous pattern detected: ${pattern.source}` };
}
}
return { safe: true };
}
}