chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,371 @@
/**
* 沙箱管理器抽象基类
*
* 定义所有沙箱实现必须遵循的接口规范。
* 支持的沙箱类型Docker、macOS Seatbelt、Linux bwrap、Windows Sandbox
*
* @version 1.1.0
* @updated 2026-04-03
*/
import { EventEmitter } from "events";
import type {
SandboxConfig,
SandboxType,
Workspace,
ExecuteOptions,
ExecuteResult,
FileInfo,
SandboxStatus,
CleanupResult,
RetentionPolicy,
} from "@shared/types/sandbox";
import {
SandboxError,
SandboxErrorCode,
WorkspaceError,
isSandboxError,
} from "@shared/errors/sandbox";
import {
createPlatformAdapter,
isSupportedPlatform,
} from "../system/platformAdapter";
/**
* 沙箱管理器抽象基类
*/
export abstract class SandboxManager extends EventEmitter {
protected config: SandboxConfig;
protected workspaces: Map<string, Workspace> = new Map();
protected initialized: boolean = false;
constructor(config: SandboxConfig) {
super();
this.config = config;
}
// ============================================================================
// 抽象方法 - 子类必须实现
// ============================================================================
/**
* 初始化沙箱
* - 检查沙箱环境是否可用
* - 准备必要的资源
*/
abstract init(): Promise<void>;
/**
* 检查沙箱是否可用
*/
abstract isAvailable(): Promise<boolean>;
/**
* 创建工作区
* @param sessionId 会话 ID
*/
abstract createWorkspace(sessionId: string): Promise<Workspace>;
/**
* 销毁工作区
* @param sessionId 会话 ID
*/
abstract destroyWorkspace(sessionId: string): Promise<void>;
/**
* 在沙箱中执行命令
* @param sessionId 会话 ID
* @param command 命令
* @param args 参数
* @param options 执行选项
*/
abstract execute(
sessionId: string,
command: string,
args: string[],
options?: ExecuteOptions,
): Promise<ExecuteResult>;
/**
* 读取文件
* @param sessionId 会话 ID
* @param path 文件路径
*/
abstract readFile(sessionId: string, path: string): Promise<string>;
/**
* 写入文件
* @param sessionId 会话 ID
* @param path 文件路径
* @param content 文件内容
*/
abstract writeFile(
sessionId: string,
path: string,
content: string,
): Promise<void>;
/**
* 读取目录
* @param sessionId 会话 ID
* @param path 目录路径
*/
abstract readDir(sessionId: string, path: string): Promise<FileInfo[]>;
/**
* 删除文件
* @param sessionId 会话 ID
* @param path 文件路径
*/
abstract deleteFile(sessionId: string, path: string): Promise<void>;
/**
* 清理所有资源
*/
abstract cleanup(): Promise<CleanupResult>;
// ============================================================================
// 具体方法 - 通用实现
// ============================================================================
/**
* 获取工作区
* @param sessionId 会话 ID
*/
getWorkspace(sessionId: string): Workspace | undefined {
return this.workspaces.get(sessionId);
}
/**
* 列出所有工作区
*/
listWorkspaces(): Workspace[] {
return Array.from(this.workspaces.values());
}
/**
* 检查工作区是否存在
* @param sessionId 会话 ID
*/
hasWorkspace(sessionId: string): boolean {
return this.workspaces.has(sessionId);
}
/**
* 获取活跃工作区数量
*/
getActiveWorkspaceCount(): number {
return this.workspaces.size;
}
/**
* 获取沙箱状态
*/
async getStatus(): Promise<SandboxStatus> {
const available = await this.isAvailable();
return {
available,
type: this.config.type,
platform: this.config.platform,
activeWorkspaces: this.workspaces.size,
};
}
/**
* 获取沙箱配置
*/
getConfig(): SandboxConfig {
return { ...this.config };
}
/**
* 检查是否已初始化
*/
isInitialized(): boolean {
return this.initialized;
}
/**
* 更新工作区最后访问时间
* @param sessionId 会话 ID
*/
protected updateLastAccessed(sessionId: string): void {
const workspace = this.workspaces.get(sessionId);
if (workspace) {
workspace.lastAccessedAt = new Date();
}
}
/**
* 验证工作区存在
* @param sessionId 会话 ID
* @throws WorkspaceError 如果工作区不存在
*/
protected validateWorkspaceExists(sessionId: string): Workspace {
const workspace = this.workspaces.get(sessionId);
if (!workspace) {
throw new WorkspaceError(
`Workspace not found: ${sessionId}`,
SandboxErrorCode.WORKSPACE_NOT_FOUND,
{ sessionId },
);
}
return workspace;
}
/**
* 验证路径在工作区内
* @param workspace 工作区
* @param path 要验证的路径
* @throws SandboxError 如果路径在工作区外
*/
protected validatePathInWorkspace(workspace: Workspace, path: string): void {
const normalizedPath = this.normalizePath(path);
const normalizedRoot = this.normalizePath(workspace.rootPath);
if (!normalizedPath.startsWith(normalizedRoot)) {
throw new SandboxError(
`Path outside workspace: ${path}`,
SandboxErrorCode.PERMISSION_DENIED,
{
sessionId: workspace.sessionId,
details: { path, rootPath: workspace.rootPath },
},
);
}
}
/**
* 规范化路径
* @param path 路径
*/
protected normalizePath(path: string): string {
// 子类可以覆盖此方法以处理平台特定的路径规范化
return path.replace(/\\/g, "/").toLowerCase();
}
/**
* 生成工作区 ID
* @param sessionId 会话 ID
*/
protected generateWorkspaceId(sessionId: string): string {
return `workspace-${sessionId}-${Date.now()}`;
}
/**
* 创建默认保留策略
*/
protected createDefaultRetentionPolicy(): RetentionPolicy {
return {
mode: "timeout",
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 天
maxWorkspaces: 10,
preserveOnError: false,
};
}
/**
* 发出事件
*/
protected emitEvent<T extends string>(event: T, data?: unknown): void {
this.emit(event, data);
}
/**
* 销毁(清理所有资源)
*/
async destroy(): Promise<void> {
try {
await this.cleanup();
this.workspaces.clear();
this.initialized = false;
this.removeAllListeners();
} catch (error) {
// 记录错误但不抛出,确保清理继续
console.error("[SandboxManager] Destroy error:", error);
}
}
// ============================================================================
// 静态工具方法
// ============================================================================
/**
* 检测当前平台
*/
static detectPlatform(): NodeJS.Platform {
if (isSupportedPlatform(process.platform)) {
return createPlatformAdapter(process.platform).platform;
}
return process.platform;
}
/**
* 获取推荐的沙箱类型(按平台)
*/
static getRecommendedSandboxType(): SandboxType {
if (!isSupportedPlatform(process.platform)) {
return "none";
}
return createPlatformAdapter(process.platform).getRecommendedSandboxType();
}
/**
* 解析内存限制字符串
* @param memoryLimit 内存限制(如 "2g", "512m"
* @returns 字节数
*/
static parseMemoryLimit(memoryLimit: string): number {
const match = memoryLimit.match(/^(\d+(?:\.\d+)?)([kmg]?)$/i);
if (!match) {
return 0;
}
const value = parseFloat(match[1]);
const unit = match[2].toLowerCase();
switch (unit) {
case "k":
return Math.floor(value * 1024);
case "m":
return Math.floor(value * 1024 * 1024);
case "g":
return Math.floor(value * 1024 * 1024 * 1024);
default:
return Math.floor(value);
}
}
/**
* 格式化内存大小
* @param bytes 字节数
* @returns 格式化字符串(如 "2 GB"
*/
static formatMemory(bytes: number): string {
const units = ["B", "KB", "MB", "GB", "TB"];
let value = bytes;
let unitIndex = 0;
while (value >= 1024 && unitIndex < units.length - 1) {
value /= 1024;
unitIndex++;
}
return `${value.toFixed(1)} ${units[unitIndex]}`;
}
/**
* 判断是否为沙箱错误
*/
static isSandboxError(error: unknown): error is SandboxError {
return isSandboxError(error);
}
}
/**
* 沙箱管理器类型
*/
export type SandboxManagerType = typeof SandboxManager;
export default SandboxManager;