openvibe 0.63.1 → 0.63.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +92 -2
- package/README.md +39 -0
- package/README_CN.md +1 -1
- package/dist/cli/args.d.ts +3 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +22 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +5 -3
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/companion/executor/bash-executor.d.ts +30 -0
- package/dist/core/companion/executor/bash-executor.d.ts.map +1 -0
- package/dist/core/companion/executor/bash-executor.js +105 -0
- package/dist/core/companion/executor/bash-executor.js.map +1 -0
- package/dist/core/companion/executor/code-executor.d.ts +33 -0
- package/dist/core/companion/executor/code-executor.d.ts.map +1 -0
- package/dist/core/companion/executor/code-executor.js +144 -0
- package/dist/core/companion/executor/code-executor.js.map +1 -0
- package/dist/core/companion/executor/resource-monitor.d.ts +55 -0
- package/dist/core/companion/executor/resource-monitor.d.ts.map +1 -0
- package/dist/core/companion/executor/resource-monitor.js +141 -0
- package/dist/core/companion/executor/resource-monitor.js.map +1 -0
- package/dist/core/companion/http-server.d.ts +92 -0
- package/dist/core/companion/http-server.d.ts.map +1 -0
- package/dist/core/companion/http-server.js +229 -0
- package/dist/core/companion/http-server.js.map +1 -0
- package/dist/core/companion/index.d.ts +29 -0
- package/dist/core/companion/index.d.ts.map +1 -0
- package/dist/core/companion/index.js +48 -0
- package/dist/core/companion/index.js.map +1 -0
- package/dist/core/companion/scheduler/resource-scheduler.d.ts +82 -0
- package/dist/core/companion/scheduler/resource-scheduler.d.ts.map +1 -0
- package/dist/core/companion/scheduler/resource-scheduler.js +333 -0
- package/dist/core/companion/scheduler/resource-scheduler.js.map +1 -0
- package/dist/core/companion/scheduler/workers/base-worker.d.ts +87 -0
- package/dist/core/companion/scheduler/workers/base-worker.d.ts.map +1 -0
- package/dist/core/companion/scheduler/workers/base-worker.js +70 -0
- package/dist/core/companion/scheduler/workers/base-worker.js.map +1 -0
- package/dist/core/companion/scheduler/workers/cpu-worker.d.ts +12 -0
- package/dist/core/companion/scheduler/workers/cpu-worker.d.ts.map +1 -0
- package/dist/core/companion/scheduler/workers/cpu-worker.js +134 -0
- package/dist/core/companion/scheduler/workers/cpu-worker.js.map +1 -0
- package/dist/core/companion/scheduler/workers/gpu-worker.d.ts +16 -0
- package/dist/core/companion/scheduler/workers/gpu-worker.d.ts.map +1 -0
- package/dist/core/companion/scheduler/workers/gpu-worker.js +167 -0
- package/dist/core/companion/scheduler/workers/gpu-worker.js.map +1 -0
- package/dist/core/companion/scheduler/workers/index.d.ts +6 -0
- package/dist/core/companion/scheduler/workers/index.d.ts.map +1 -0
- package/dist/core/companion/scheduler/workers/index.js +6 -0
- package/dist/core/companion/scheduler/workers/index.js.map +1 -0
- package/dist/core/companion/scheduler/workers/io-worker.d.ts +17 -0
- package/dist/core/companion/scheduler/workers/io-worker.d.ts.map +1 -0
- package/dist/core/companion/scheduler/workers/io-worker.js +197 -0
- package/dist/core/companion/scheduler/workers/io-worker.js.map +1 -0
- package/dist/core/companion/scheduler/workers/network-worker.d.ts +13 -0
- package/dist/core/companion/scheduler/workers/network-worker.d.ts.map +1 -0
- package/dist/core/companion/scheduler/workers/network-worker.js +167 -0
- package/dist/core/companion/scheduler/workers/network-worker.js.map +1 -0
- package/dist/core/companion/security/security-manager.d.ts +50 -0
- package/dist/core/companion/security/security-manager.d.ts.map +1 -0
- package/dist/core/companion/security/security-manager.js +102 -0
- package/dist/core/companion/security/security-manager.js.map +1 -0
- package/dist/core/hybrid-cloud/extension.d.ts +8 -0
- package/dist/core/hybrid-cloud/extension.d.ts.map +1 -0
- package/dist/core/hybrid-cloud/extension.js +64 -0
- package/dist/core/hybrid-cloud/extension.js.map +1 -0
- package/dist/core/hybrid-cloud/index.d.ts +98 -0
- package/dist/core/hybrid-cloud/index.d.ts.map +1 -0
- package/dist/core/hybrid-cloud/index.js +211 -0
- package/dist/core/hybrid-cloud/index.js.map +1 -0
- package/dist/core/keybindings.d.ts +1 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +3 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +6 -1
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +13 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +35 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +7 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +46 -0
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/todo-manager.d.ts +32 -0
- package/dist/core/todo-manager.d.ts.map +1 -0
- package/dist/core/todo-manager.js +117 -0
- package/dist/core/todo-manager.js.map +1 -0
- package/dist/core/tools/batch-write.d.ts +42 -0
- package/dist/core/tools/batch-write.d.ts.map +1 -0
- package/dist/core/tools/batch-write.js +267 -0
- package/dist/core/tools/batch-write.js.map +1 -0
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +10 -2
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/fast-executor.d.ts +6 -1
- package/dist/core/tools/fast-executor.d.ts.map +1 -1
- package/dist/core/tools/fast-executor.js +83 -4
- package/dist/core/tools/fast-executor.js.map +1 -1
- package/dist/core/tools/index.d.ts +141 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +30 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/todo.d.ts +47 -0
- package/dist/core/tools/todo.d.ts.map +1 -0
- package/dist/core/tools/todo.js +212 -0
- package/dist/core/tools/todo.js.map +1 -0
- package/dist/core/tools/unified.d.ts +121 -0
- package/dist/core/tools/unified.d.ts.map +1 -0
- package/dist/core/tools/unified.js +481 -0
- package/dist/core/tools/unified.js.map +1 -0
- package/dist/core/tools/write.d.ts +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +20 -5
- package/dist/core/tools/write.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +63 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/index.d.ts +2 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +2 -0
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/todo-display.d.ts +9 -0
- package/dist/modes/interactive/components/todo-display.d.ts.map +1 -0
- package/dist/modes/interactive/components/todo-display.js +60 -0
- package/dist/modes/interactive/components/todo-display.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +8 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +151 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/plan-mode.d.ts +14 -0
- package/dist/modes/plan-mode.d.ts.map +1 -0
- package/dist/modes/plan-mode.js +107 -0
- package/dist/modes/plan-mode.js.map +1 -0
- package/dist/modes/spec-mode.d.ts +16 -0
- package/dist/modes/spec-mode.d.ts.map +1 -0
- package/dist/modes/spec-mode.js +186 -0
- package/dist/modes/spec-mode.js.map +1 -0
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +7 -17
- package/dist/utils/version-check.js.map +1 -1
- package/docs/HYBRID_CLOUD_README.md +188 -0
- package/docs/hybrid-architecture-design.md +317 -0
- package/docs/todo.md +71 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP API Server for OpenVibe Companion
|
|
3
|
+
*
|
|
4
|
+
* 提供 RESTful API 供内部调用
|
|
5
|
+
*/
|
|
6
|
+
export interface CompanionHttpConfig {
|
|
7
|
+
port?: number;
|
|
8
|
+
host?: string;
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
workingDir?: string;
|
|
11
|
+
allowedCommands?: string[];
|
|
12
|
+
blockedPaths?: string[];
|
|
13
|
+
}
|
|
14
|
+
export declare class CompanionHttpServer {
|
|
15
|
+
private server;
|
|
16
|
+
private bashExecutor;
|
|
17
|
+
private codeExecutor;
|
|
18
|
+
private security;
|
|
19
|
+
private config;
|
|
20
|
+
private isRunning;
|
|
21
|
+
constructor(config?: CompanionHttpConfig);
|
|
22
|
+
/**
|
|
23
|
+
* 启动服务器
|
|
24
|
+
*/
|
|
25
|
+
start(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* 停止服务器
|
|
28
|
+
*/
|
|
29
|
+
stop(): Promise<void>;
|
|
30
|
+
private handleRequest;
|
|
31
|
+
private handleHealth;
|
|
32
|
+
private handleStatus;
|
|
33
|
+
private handleExecuteBash;
|
|
34
|
+
private handleExecuteCode;
|
|
35
|
+
/**
|
|
36
|
+
* 读取请求体
|
|
37
|
+
*/
|
|
38
|
+
private readBody;
|
|
39
|
+
/**
|
|
40
|
+
* 发送 JSON 响应
|
|
41
|
+
*/
|
|
42
|
+
private sendJson;
|
|
43
|
+
/**
|
|
44
|
+
* 发送错误响应
|
|
45
|
+
*/
|
|
46
|
+
private sendError;
|
|
47
|
+
/**
|
|
48
|
+
* 获取服务地址
|
|
49
|
+
*/
|
|
50
|
+
getAddress(): string;
|
|
51
|
+
/**
|
|
52
|
+
* 检查是否正在运行
|
|
53
|
+
*/
|
|
54
|
+
isActive(): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* 获取资源状态
|
|
57
|
+
*/
|
|
58
|
+
getResourceStatus(): {
|
|
59
|
+
status: string;
|
|
60
|
+
host: string;
|
|
61
|
+
port: number;
|
|
62
|
+
cpu: {
|
|
63
|
+
coreCount: number;
|
|
64
|
+
usagePercent: number;
|
|
65
|
+
availableCores: number;
|
|
66
|
+
};
|
|
67
|
+
memory: {
|
|
68
|
+
totalMb: number;
|
|
69
|
+
usedMb: number;
|
|
70
|
+
freeMb: number;
|
|
71
|
+
usagePercent: number;
|
|
72
|
+
};
|
|
73
|
+
gpu: {
|
|
74
|
+
deviceCount: number;
|
|
75
|
+
devices: {
|
|
76
|
+
name: string;
|
|
77
|
+
memoryUsedMb: number;
|
|
78
|
+
memoryTotalMb: number;
|
|
79
|
+
memoryUsagePercent: number;
|
|
80
|
+
}[];
|
|
81
|
+
};
|
|
82
|
+
disk: {
|
|
83
|
+
disks: {
|
|
84
|
+
mountPoint: string;
|
|
85
|
+
usedBytes: number;
|
|
86
|
+
totalBytes: number;
|
|
87
|
+
usagePercent: number;
|
|
88
|
+
}[];
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=http-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../../../src/core/companion/http-server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,MAAM,WAAW,mBAAmB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,qBAAa,mBAAmB;IAC/B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,SAAS,CAAS;IAE1B,YAAY,MAAM,GAAE,mBAAwB,EAgB3C;IAED;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAmB3B;IAED;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAiB1B;YAKa,aAAa;YA6Cb,YAAY;YAUZ,YAAY;YAQZ,iBAAiB;YAuBjB,iBAAiB;IAkB/B;;OAEG;IACH,OAAO,CAAC,QAAQ;IAWhB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAMhB;;OAEG;IACH,OAAO,CAAC,SAAS;IAIjB;;OAEG;IACH,UAAU,IAAI,MAAM,CAEnB;IAED;;OAEG;IACH,QAAQ,IAAI,OAAO,CAElB;IAED;;OAEG;IACH,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAoBhB;CACD","sourcesContent":["/**\n * HTTP API Server for OpenVibe Companion\n *\n * 提供 RESTful API 供内部调用\n */\n\nimport chalk from \"chalk\";\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"http\";\nimport { BashExecutor } from \"./executor/bash-executor.js\";\nimport { CodeExecutor } from \"./executor/code-executor.js\";\nimport { SecurityManager } from \"./security/security-manager.js\";\n\nexport interface CompanionHttpConfig {\n\tport?: number;\n\thost?: string;\n\tapiKey?: string;\n\tworkingDir?: string;\n\tallowedCommands?: string[];\n\tblockedPaths?: string[];\n}\n\nexport class CompanionHttpServer {\n\tprivate server: Server | null = null;\n\tprivate bashExecutor: BashExecutor;\n\tprivate codeExecutor: CodeExecutor;\n\tprivate security: SecurityManager;\n\tprivate config: Required<CompanionHttpConfig>;\n\tprivate isRunning = false;\n\n\tconstructor(config: CompanionHttpConfig = {}) {\n\t\tthis.config = {\n\t\t\tport: config.port ?? 50051,\n\t\t\thost: config.host ?? \"127.0.0.1\",\n\t\t\tapiKey: config.apiKey ?? \"\",\n\t\t\tworkingDir: config.workingDir ?? process.cwd(),\n\t\t\tallowedCommands: config.allowedCommands ?? [\"python\", \"node\", \"npm\", \"cargo\", \"rustc\"],\n\t\t\tblockedPaths: config.blockedPaths ?? [],\n\t\t};\n\n\t\tthis.security = new SecurityManager({\n\t\t\tallowedCommands: this.config.allowedCommands,\n\t\t\tblockedPaths: this.config.blockedPaths,\n\t\t});\n\t\tthis.bashExecutor = new BashExecutor(this.security);\n\t\tthis.codeExecutor = new CodeExecutor(this.security);\n\t}\n\n\t/**\n\t * 启动服务器\n\t */\n\tasync start(): Promise<void> {\n\t\tif (this.isRunning) {\n\t\t\tconsole.log(chalk.yellow(\"[Companion] Already running\"));\n\t\t\treturn;\n\t\t}\n\n\t\tthis.server = createServer(this.handleRequest.bind(this));\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.server!.listen(this.config.port, this.config.host, () => {\n\t\t\t\tthis.isRunning = true;\n\t\t\t\tconsole.log(chalk.green(`[Companion] HTTP server started on ${this.config.host}:${this.config.port}`));\n\t\t\t\tresolve();\n\t\t\t});\n\n\t\t\tthis.server!.on(\"error\", (err) => {\n\t\t\t\treject(err);\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * 停止服务器\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (!this.isRunning || !this.server) {\n\t\t\treturn;\n\t\t}\n\n\t\tconsole.log(chalk.blue(\"[Companion] Stopping server...\"));\n\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.bashExecutor.shutdown();\n\t\t\tthis.codeExecutor.shutdown();\n\t\t\tthis.server!.close(() => {\n\t\t\t\tthis.isRunning = false;\n\t\t\t\tthis.server = null;\n\t\t\t\tconsole.log(chalk.gray(\"[Companion] Server stopped\"));\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * 处理请求\n\t */\n\tprivate async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\t// 设置 CORS 头\n\t\tres.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n\t\tres.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n\t\tres.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\");\n\n\t\tif (req.method === \"OPTIONS\") {\n\t\t\tres.writeHead(200);\n\t\t\tres.end();\n\t\t\treturn;\n\t\t}\n\n\t\t// API 密钥验证\n\t\tif (this.config.apiKey) {\n\t\t\tconst authHeader = req.headers.authorization;\n\t\t\tif (!authHeader || authHeader !== `Bearer ${this.config.apiKey}`) {\n\t\t\t\tthis.sendError(res, 401, \"Unauthorized\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tconst url = new URL(req.url || \"/\", `http://${req.headers.host}`);\n\t\tconst path = url.pathname;\n\n\t\ttry {\n\t\t\tif (path === \"/health\") {\n\t\t\t\tawait this.handleHealth(req, res);\n\t\t\t} else if (path === \"/status\") {\n\t\t\t\tawait this.handleStatus(req, res);\n\t\t\t} else if (path === \"/execute/bash\" && req.method === \"POST\") {\n\t\t\t\tawait this.handleExecuteBash(req, res);\n\t\t\t} else if (path === \"/execute/code\" && req.method === \"POST\") {\n\t\t\t\tawait this.handleExecuteCode(req, res);\n\t\t\t} else {\n\t\t\t\tthis.sendError(res, 404, \"Not found\");\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[Companion] Request error:\", error);\n\t\t\tthis.sendError(res, 500, \"Internal server error\");\n\t\t}\n\t}\n\n\t/**\n\t * 健康检查\n\t */\n\tprivate async handleHealth(_req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tthis.sendJson(res, 200, {\n\t\t\thealthy: true,\n\t\t\tversion: \"0.2.0\",\n\t\t});\n\t}\n\n\t/**\n\t * 获取资源状态\n\t */\n\tprivate async handleStatus(_req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\t// TODO: Implement proper status endpoint\n\t\tthis.sendJson(res, 200, { status: \"ok\" });\n\t}\n\n\t/**\n\t * 执行 Bash 命令\n\t */\n\tprivate async handleExecuteBash(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tconst body = await this.readBody(req);\n\t\tconst { command, args, workingDir, env, timeout } = JSON.parse(body);\n\n\t\tif (!command) {\n\t\t\tthis.sendError(res, 400, \"Missing command\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = await this.bashExecutor.execute({\n\t\t\tcommand,\n\t\t\targs: args || [],\n\t\t\tworkingDir,\n\t\t\tenv,\n\t\t\ttimeout: timeout || 300,\n\t\t});\n\n\t\tthis.sendJson(res, result.success ? 200 : 500, result);\n\t}\n\n\t/**\n\t * 执行代码\n\t */\n\tprivate async handleExecuteCode(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tconst body = await this.readBody(req);\n\t\tconst { code, language, timeout } = JSON.parse(body);\n\n\t\tif (!code || !language) {\n\t\t\tthis.sendError(res, 400, \"Missing code or language\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = await this.codeExecutor.execute({\n\t\t\tcode,\n\t\t\tlanguage,\n\t\t\ttimeout: timeout || 300,\n\t\t});\n\n\t\tthis.sendJson(res, result.success ? 200 : 500, result);\n\t}\n\n\t/**\n\t * 读取请求体\n\t */\n\tprivate readBody(req: IncomingMessage): Promise<string> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tlet body = \"\";\n\t\t\treq.on(\"data\", (chunk) => {\n\t\t\t\tbody += chunk.toString();\n\t\t\t});\n\t\t\treq.on(\"end\", () => resolve(body));\n\t\t\treq.on(\"error\", reject);\n\t\t});\n\t}\n\n\t/**\n\t * 发送 JSON 响应\n\t */\n\tprivate sendJson(res: ServerResponse, status: number, data: unknown): void {\n\t\tres.setHeader(\"Content-Type\", \"application/json\");\n\t\tres.writeHead(status);\n\t\tres.end(JSON.stringify(data));\n\t}\n\n\t/**\n\t * 发送错误响应\n\t */\n\tprivate sendError(res: ServerResponse, status: number, message: string): void {\n\t\tthis.sendJson(res, status, { error: message });\n\t}\n\n\t/**\n\t * 获取服务地址\n\t */\n\tgetAddress(): string {\n\t\treturn `http://${this.config.host}:${this.config.port}`;\n\t}\n\n\t/**\n\t * 检查是否正在运行\n\t */\n\tisActive(): boolean {\n\t\treturn this.isRunning;\n\t}\n\n\t/**\n\t * 获取资源状态\n\t */\n\tgetResourceStatus() {\n\t\treturn {\n\t\t\tstatus: \"running\",\n\t\t\thost: this.config.host,\n\t\t\tport: this.config.port,\n\t\t\tcpu: { coreCount: 0, usagePercent: 0, availableCores: 0 },\n\t\t\tmemory: { totalMb: 0, usedMb: 0, freeMb: 0, usagePercent: 0 },\n\t\t\tgpu: {\n\t\t\t\tdeviceCount: 0,\n\t\t\t\tdevices: [] as Array<{\n\t\t\t\t\tname: string;\n\t\t\t\t\tmemoryUsedMb: number;\n\t\t\t\t\tmemoryTotalMb: number;\n\t\t\t\t\tmemoryUsagePercent: number;\n\t\t\t\t}>,\n\t\t\t},\n\t\t\tdisk: {\n\t\t\t\tdisks: [] as Array<{ mountPoint: string; usedBytes: number; totalBytes: number; usagePercent: number }>,\n\t\t\t},\n\t\t};\n\t}\n}\n"]}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP API Server for OpenVibe Companion
|
|
3
|
+
*
|
|
4
|
+
* 提供 RESTful API 供内部调用
|
|
5
|
+
*/
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { createServer } from "http";
|
|
8
|
+
import { BashExecutor } from "./executor/bash-executor.js";
|
|
9
|
+
import { CodeExecutor } from "./executor/code-executor.js";
|
|
10
|
+
import { SecurityManager } from "./security/security-manager.js";
|
|
11
|
+
export class CompanionHttpServer {
|
|
12
|
+
server = null;
|
|
13
|
+
bashExecutor;
|
|
14
|
+
codeExecutor;
|
|
15
|
+
security;
|
|
16
|
+
config;
|
|
17
|
+
isRunning = false;
|
|
18
|
+
constructor(config = {}) {
|
|
19
|
+
this.config = {
|
|
20
|
+
port: config.port ?? 50051,
|
|
21
|
+
host: config.host ?? "127.0.0.1",
|
|
22
|
+
apiKey: config.apiKey ?? "",
|
|
23
|
+
workingDir: config.workingDir ?? process.cwd(),
|
|
24
|
+
allowedCommands: config.allowedCommands ?? ["python", "node", "npm", "cargo", "rustc"],
|
|
25
|
+
blockedPaths: config.blockedPaths ?? [],
|
|
26
|
+
};
|
|
27
|
+
this.security = new SecurityManager({
|
|
28
|
+
allowedCommands: this.config.allowedCommands,
|
|
29
|
+
blockedPaths: this.config.blockedPaths,
|
|
30
|
+
});
|
|
31
|
+
this.bashExecutor = new BashExecutor(this.security);
|
|
32
|
+
this.codeExecutor = new CodeExecutor(this.security);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 启动服务器
|
|
36
|
+
*/
|
|
37
|
+
async start() {
|
|
38
|
+
if (this.isRunning) {
|
|
39
|
+
console.log(chalk.yellow("[Companion] Already running"));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
this.server = createServer(this.handleRequest.bind(this));
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
this.server.listen(this.config.port, this.config.host, () => {
|
|
45
|
+
this.isRunning = true;
|
|
46
|
+
console.log(chalk.green(`[Companion] HTTP server started on ${this.config.host}:${this.config.port}`));
|
|
47
|
+
resolve();
|
|
48
|
+
});
|
|
49
|
+
this.server.on("error", (err) => {
|
|
50
|
+
reject(err);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 停止服务器
|
|
56
|
+
*/
|
|
57
|
+
async stop() {
|
|
58
|
+
if (!this.isRunning || !this.server) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
console.log(chalk.blue("[Companion] Stopping server..."));
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
this.bashExecutor.shutdown();
|
|
64
|
+
this.codeExecutor.shutdown();
|
|
65
|
+
this.server.close(() => {
|
|
66
|
+
this.isRunning = false;
|
|
67
|
+
this.server = null;
|
|
68
|
+
console.log(chalk.gray("[Companion] Server stopped"));
|
|
69
|
+
resolve();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 处理请求
|
|
75
|
+
*/
|
|
76
|
+
async handleRequest(req, res) {
|
|
77
|
+
// 设置 CORS 头
|
|
78
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
79
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
80
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
81
|
+
if (req.method === "OPTIONS") {
|
|
82
|
+
res.writeHead(200);
|
|
83
|
+
res.end();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// API 密钥验证
|
|
87
|
+
if (this.config.apiKey) {
|
|
88
|
+
const authHeader = req.headers.authorization;
|
|
89
|
+
if (!authHeader || authHeader !== `Bearer ${this.config.apiKey}`) {
|
|
90
|
+
this.sendError(res, 401, "Unauthorized");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
95
|
+
const path = url.pathname;
|
|
96
|
+
try {
|
|
97
|
+
if (path === "/health") {
|
|
98
|
+
await this.handleHealth(req, res);
|
|
99
|
+
}
|
|
100
|
+
else if (path === "/status") {
|
|
101
|
+
await this.handleStatus(req, res);
|
|
102
|
+
}
|
|
103
|
+
else if (path === "/execute/bash" && req.method === "POST") {
|
|
104
|
+
await this.handleExecuteBash(req, res);
|
|
105
|
+
}
|
|
106
|
+
else if (path === "/execute/code" && req.method === "POST") {
|
|
107
|
+
await this.handleExecuteCode(req, res);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.sendError(res, 404, "Not found");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error("[Companion] Request error:", error);
|
|
115
|
+
this.sendError(res, 500, "Internal server error");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 健康检查
|
|
120
|
+
*/
|
|
121
|
+
async handleHealth(_req, res) {
|
|
122
|
+
this.sendJson(res, 200, {
|
|
123
|
+
healthy: true,
|
|
124
|
+
version: "0.2.0",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 获取资源状态
|
|
129
|
+
*/
|
|
130
|
+
async handleStatus(_req, res) {
|
|
131
|
+
// TODO: Implement proper status endpoint
|
|
132
|
+
this.sendJson(res, 200, { status: "ok" });
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 执行 Bash 命令
|
|
136
|
+
*/
|
|
137
|
+
async handleExecuteBash(req, res) {
|
|
138
|
+
const body = await this.readBody(req);
|
|
139
|
+
const { command, args, workingDir, env, timeout } = JSON.parse(body);
|
|
140
|
+
if (!command) {
|
|
141
|
+
this.sendError(res, 400, "Missing command");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const result = await this.bashExecutor.execute({
|
|
145
|
+
command,
|
|
146
|
+
args: args || [],
|
|
147
|
+
workingDir,
|
|
148
|
+
env,
|
|
149
|
+
timeout: timeout || 300,
|
|
150
|
+
});
|
|
151
|
+
this.sendJson(res, result.success ? 200 : 500, result);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 执行代码
|
|
155
|
+
*/
|
|
156
|
+
async handleExecuteCode(req, res) {
|
|
157
|
+
const body = await this.readBody(req);
|
|
158
|
+
const { code, language, timeout } = JSON.parse(body);
|
|
159
|
+
if (!code || !language) {
|
|
160
|
+
this.sendError(res, 400, "Missing code or language");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const result = await this.codeExecutor.execute({
|
|
164
|
+
code,
|
|
165
|
+
language,
|
|
166
|
+
timeout: timeout || 300,
|
|
167
|
+
});
|
|
168
|
+
this.sendJson(res, result.success ? 200 : 500, result);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* 读取请求体
|
|
172
|
+
*/
|
|
173
|
+
readBody(req) {
|
|
174
|
+
return new Promise((resolve, reject) => {
|
|
175
|
+
let body = "";
|
|
176
|
+
req.on("data", (chunk) => {
|
|
177
|
+
body += chunk.toString();
|
|
178
|
+
});
|
|
179
|
+
req.on("end", () => resolve(body));
|
|
180
|
+
req.on("error", reject);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* 发送 JSON 响应
|
|
185
|
+
*/
|
|
186
|
+
sendJson(res, status, data) {
|
|
187
|
+
res.setHeader("Content-Type", "application/json");
|
|
188
|
+
res.writeHead(status);
|
|
189
|
+
res.end(JSON.stringify(data));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 发送错误响应
|
|
193
|
+
*/
|
|
194
|
+
sendError(res, status, message) {
|
|
195
|
+
this.sendJson(res, status, { error: message });
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* 获取服务地址
|
|
199
|
+
*/
|
|
200
|
+
getAddress() {
|
|
201
|
+
return `http://${this.config.host}:${this.config.port}`;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* 检查是否正在运行
|
|
205
|
+
*/
|
|
206
|
+
isActive() {
|
|
207
|
+
return this.isRunning;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* 获取资源状态
|
|
211
|
+
*/
|
|
212
|
+
getResourceStatus() {
|
|
213
|
+
return {
|
|
214
|
+
status: "running",
|
|
215
|
+
host: this.config.host,
|
|
216
|
+
port: this.config.port,
|
|
217
|
+
cpu: { coreCount: 0, usagePercent: 0, availableCores: 0 },
|
|
218
|
+
memory: { totalMb: 0, usedMb: 0, freeMb: 0, usagePercent: 0 },
|
|
219
|
+
gpu: {
|
|
220
|
+
deviceCount: 0,
|
|
221
|
+
devices: [],
|
|
222
|
+
},
|
|
223
|
+
disk: {
|
|
224
|
+
disks: [],
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=http-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-server.js","sourceRoot":"","sources":["../../../src/core/companion/http-server.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * HTTP API Server for OpenVibe Companion\n *\n * 提供 RESTful API 供内部调用\n */\n\nimport chalk from \"chalk\";\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"http\";\nimport { BashExecutor } from \"./executor/bash-executor.js\";\nimport { CodeExecutor } from \"./executor/code-executor.js\";\nimport { SecurityManager } from \"./security/security-manager.js\";\n\nexport interface CompanionHttpConfig {\n\tport?: number;\n\thost?: string;\n\tapiKey?: string;\n\tworkingDir?: string;\n\tallowedCommands?: string[];\n\tblockedPaths?: string[];\n}\n\nexport class CompanionHttpServer {\n\tprivate server: Server | null = null;\n\tprivate bashExecutor: BashExecutor;\n\tprivate codeExecutor: CodeExecutor;\n\tprivate security: SecurityManager;\n\tprivate config: Required<CompanionHttpConfig>;\n\tprivate isRunning = false;\n\n\tconstructor(config: CompanionHttpConfig = {}) {\n\t\tthis.config = {\n\t\t\tport: config.port ?? 50051,\n\t\t\thost: config.host ?? \"127.0.0.1\",\n\t\t\tapiKey: config.apiKey ?? \"\",\n\t\t\tworkingDir: config.workingDir ?? process.cwd(),\n\t\t\tallowedCommands: config.allowedCommands ?? [\"python\", \"node\", \"npm\", \"cargo\", \"rustc\"],\n\t\t\tblockedPaths: config.blockedPaths ?? [],\n\t\t};\n\n\t\tthis.security = new SecurityManager({\n\t\t\tallowedCommands: this.config.allowedCommands,\n\t\t\tblockedPaths: this.config.blockedPaths,\n\t\t});\n\t\tthis.bashExecutor = new BashExecutor(this.security);\n\t\tthis.codeExecutor = new CodeExecutor(this.security);\n\t}\n\n\t/**\n\t * 启动服务器\n\t */\n\tasync start(): Promise<void> {\n\t\tif (this.isRunning) {\n\t\t\tconsole.log(chalk.yellow(\"[Companion] Already running\"));\n\t\t\treturn;\n\t\t}\n\n\t\tthis.server = createServer(this.handleRequest.bind(this));\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.server!.listen(this.config.port, this.config.host, () => {\n\t\t\t\tthis.isRunning = true;\n\t\t\t\tconsole.log(chalk.green(`[Companion] HTTP server started on ${this.config.host}:${this.config.port}`));\n\t\t\t\tresolve();\n\t\t\t});\n\n\t\t\tthis.server!.on(\"error\", (err) => {\n\t\t\t\treject(err);\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * 停止服务器\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (!this.isRunning || !this.server) {\n\t\t\treturn;\n\t\t}\n\n\t\tconsole.log(chalk.blue(\"[Companion] Stopping server...\"));\n\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.bashExecutor.shutdown();\n\t\t\tthis.codeExecutor.shutdown();\n\t\t\tthis.server!.close(() => {\n\t\t\t\tthis.isRunning = false;\n\t\t\t\tthis.server = null;\n\t\t\t\tconsole.log(chalk.gray(\"[Companion] Server stopped\"));\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * 处理请求\n\t */\n\tprivate async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\t// 设置 CORS 头\n\t\tres.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n\t\tres.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n\t\tres.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\");\n\n\t\tif (req.method === \"OPTIONS\") {\n\t\t\tres.writeHead(200);\n\t\t\tres.end();\n\t\t\treturn;\n\t\t}\n\n\t\t// API 密钥验证\n\t\tif (this.config.apiKey) {\n\t\t\tconst authHeader = req.headers.authorization;\n\t\t\tif (!authHeader || authHeader !== `Bearer ${this.config.apiKey}`) {\n\t\t\t\tthis.sendError(res, 401, \"Unauthorized\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tconst url = new URL(req.url || \"/\", `http://${req.headers.host}`);\n\t\tconst path = url.pathname;\n\n\t\ttry {\n\t\t\tif (path === \"/health\") {\n\t\t\t\tawait this.handleHealth(req, res);\n\t\t\t} else if (path === \"/status\") {\n\t\t\t\tawait this.handleStatus(req, res);\n\t\t\t} else if (path === \"/execute/bash\" && req.method === \"POST\") {\n\t\t\t\tawait this.handleExecuteBash(req, res);\n\t\t\t} else if (path === \"/execute/code\" && req.method === \"POST\") {\n\t\t\t\tawait this.handleExecuteCode(req, res);\n\t\t\t} else {\n\t\t\t\tthis.sendError(res, 404, \"Not found\");\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[Companion] Request error:\", error);\n\t\t\tthis.sendError(res, 500, \"Internal server error\");\n\t\t}\n\t}\n\n\t/**\n\t * 健康检查\n\t */\n\tprivate async handleHealth(_req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tthis.sendJson(res, 200, {\n\t\t\thealthy: true,\n\t\t\tversion: \"0.2.0\",\n\t\t});\n\t}\n\n\t/**\n\t * 获取资源状态\n\t */\n\tprivate async handleStatus(_req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\t// TODO: Implement proper status endpoint\n\t\tthis.sendJson(res, 200, { status: \"ok\" });\n\t}\n\n\t/**\n\t * 执行 Bash 命令\n\t */\n\tprivate async handleExecuteBash(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tconst body = await this.readBody(req);\n\t\tconst { command, args, workingDir, env, timeout } = JSON.parse(body);\n\n\t\tif (!command) {\n\t\t\tthis.sendError(res, 400, \"Missing command\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = await this.bashExecutor.execute({\n\t\t\tcommand,\n\t\t\targs: args || [],\n\t\t\tworkingDir,\n\t\t\tenv,\n\t\t\ttimeout: timeout || 300,\n\t\t});\n\n\t\tthis.sendJson(res, result.success ? 200 : 500, result);\n\t}\n\n\t/**\n\t * 执行代码\n\t */\n\tprivate async handleExecuteCode(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tconst body = await this.readBody(req);\n\t\tconst { code, language, timeout } = JSON.parse(body);\n\n\t\tif (!code || !language) {\n\t\t\tthis.sendError(res, 400, \"Missing code or language\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = await this.codeExecutor.execute({\n\t\t\tcode,\n\t\t\tlanguage,\n\t\t\ttimeout: timeout || 300,\n\t\t});\n\n\t\tthis.sendJson(res, result.success ? 200 : 500, result);\n\t}\n\n\t/**\n\t * 读取请求体\n\t */\n\tprivate readBody(req: IncomingMessage): Promise<string> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tlet body = \"\";\n\t\t\treq.on(\"data\", (chunk) => {\n\t\t\t\tbody += chunk.toString();\n\t\t\t});\n\t\t\treq.on(\"end\", () => resolve(body));\n\t\t\treq.on(\"error\", reject);\n\t\t});\n\t}\n\n\t/**\n\t * 发送 JSON 响应\n\t */\n\tprivate sendJson(res: ServerResponse, status: number, data: unknown): void {\n\t\tres.setHeader(\"Content-Type\", \"application/json\");\n\t\tres.writeHead(status);\n\t\tres.end(JSON.stringify(data));\n\t}\n\n\t/**\n\t * 发送错误响应\n\t */\n\tprivate sendError(res: ServerResponse, status: number, message: string): void {\n\t\tthis.sendJson(res, status, { error: message });\n\t}\n\n\t/**\n\t * 获取服务地址\n\t */\n\tgetAddress(): string {\n\t\treturn `http://${this.config.host}:${this.config.port}`;\n\t}\n\n\t/**\n\t * 检查是否正在运行\n\t */\n\tisActive(): boolean {\n\t\treturn this.isRunning;\n\t}\n\n\t/**\n\t * 获取资源状态\n\t */\n\tgetResourceStatus() {\n\t\treturn {\n\t\t\tstatus: \"running\",\n\t\t\thost: this.config.host,\n\t\t\tport: this.config.port,\n\t\t\tcpu: { coreCount: 0, usagePercent: 0, availableCores: 0 },\n\t\t\tmemory: { totalMb: 0, usedMb: 0, freeMb: 0, usagePercent: 0 },\n\t\t\tgpu: {\n\t\t\t\tdeviceCount: 0,\n\t\t\t\tdevices: [] as Array<{\n\t\t\t\t\tname: string;\n\t\t\t\t\tmemoryUsedMb: number;\n\t\t\t\t\tmemoryTotalMb: number;\n\t\t\t\t\tmemoryUsagePercent: number;\n\t\t\t\t}>,\n\t\t\t},\n\t\t\tdisk: {\n\t\t\t\tdisks: [] as Array<{ mountPoint: string; usedBytes: number; totalBytes: number; usagePercent: number }>,\n\t\t\t},\n\t\t};\n\t}\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenVibe Companion - Integrated Local Agent
|
|
3
|
+
*
|
|
4
|
+
* 直接在 coding-agent 中运行的本地代理服务
|
|
5
|
+
*/
|
|
6
|
+
export { BashExecutor, type BashResult, type BashTask } from "./executor/bash-executor.js";
|
|
7
|
+
export { CodeExecutor, type CodeResult, type CodeTask } from "./executor/code-executor.js";
|
|
8
|
+
export { ResourceMonitor, type ResourceStatus as ResourceMonitorStatus } from "./executor/resource-monitor.js";
|
|
9
|
+
export { type CompanionHttpConfig, CompanionHttpServer } from "./http-server.js";
|
|
10
|
+
export { BaseWorker, CPUWorker, GPUWorker, IOWorker, NetworkWorker, type ScheduledTask, type TaskPayload, type TaskPriority, type TaskRequirements, type TaskStatus, type TaskType, type WorkerCapabilities, type WorkerInfo, type WorkerStats, } from "./scheduler/workers/index.js";
|
|
11
|
+
export { type SecurityConfig, SecurityManager } from "./security/security-manager.js";
|
|
12
|
+
import { type CompanionHttpConfig, CompanionHttpServer } from "./http-server.js";
|
|
13
|
+
/**
|
|
14
|
+
* 获取或创建全局 Companion 实例
|
|
15
|
+
*/
|
|
16
|
+
export declare function getCompanion(config?: CompanionHttpConfig): CompanionHttpServer;
|
|
17
|
+
/**
|
|
18
|
+
* 启动 Companion
|
|
19
|
+
*/
|
|
20
|
+
export declare function startCompanion(config?: CompanionHttpConfig): Promise<CompanionHttpServer>;
|
|
21
|
+
/**
|
|
22
|
+
* 停止 Companion
|
|
23
|
+
*/
|
|
24
|
+
export declare function stopCompanion(): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* 检查 Companion 是否运行中
|
|
27
|
+
*/
|
|
28
|
+
export declare function isCompanionRunning(): boolean;
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/companion/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAC3F,OAAO,EAAE,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAC3F,OAAO,EAAE,eAAe,EAAE,KAAK,cAAc,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAC/G,OAAO,EAAE,KAAK,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EACN,UAAU,EACV,SAAS,EACT,SAAS,EACT,QAAQ,EACR,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACvB,KAAK,UAAU,EACf,KAAK,WAAW,GAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,KAAK,cAAc,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAEtF,OAAO,EAAE,KAAK,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAIjF;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,mBAAmB,GAAG,mBAAmB,CAK9E;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,MAAM,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAM/F;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAKnD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C","sourcesContent":["/**\n * OpenVibe Companion - Integrated Local Agent\n *\n * 直接在 coding-agent 中运行的本地代理服务\n */\n\nexport { BashExecutor, type BashResult, type BashTask } from \"./executor/bash-executor.js\";\nexport { CodeExecutor, type CodeResult, type CodeTask } from \"./executor/code-executor.js\";\nexport { ResourceMonitor, type ResourceStatus as ResourceMonitorStatus } from \"./executor/resource-monitor.js\";\nexport { type CompanionHttpConfig, CompanionHttpServer } from \"./http-server.js\";\nexport {\n\tBaseWorker,\n\tCPUWorker,\n\tGPUWorker,\n\tIOWorker,\n\tNetworkWorker,\n\ttype ScheduledTask,\n\ttype TaskPayload,\n\ttype TaskPriority,\n\ttype TaskRequirements,\n\ttype TaskStatus,\n\ttype TaskType,\n\ttype WorkerCapabilities,\n\ttype WorkerInfo,\n\ttype WorkerStats,\n} from \"./scheduler/workers/index.js\";\nexport { type SecurityConfig, SecurityManager } from \"./security/security-manager.js\";\n\nimport { type CompanionHttpConfig, CompanionHttpServer } from \"./http-server.js\";\n\nlet globalCompanion: CompanionHttpServer | null = null;\n\n/**\n * 获取或创建全局 Companion 实例\n */\nexport function getCompanion(config?: CompanionHttpConfig): CompanionHttpServer {\n\tif (!globalCompanion) {\n\t\tglobalCompanion = new CompanionHttpServer(config);\n\t}\n\treturn globalCompanion;\n}\n\n/**\n * 启动 Companion\n */\nexport async function startCompanion(config?: CompanionHttpConfig): Promise<CompanionHttpServer> {\n\tconst companion = getCompanion(config);\n\tif (!companion.isActive()) {\n\t\tawait companion.start();\n\t}\n\treturn companion;\n}\n\n/**\n * 停止 Companion\n */\nexport async function stopCompanion(): Promise<void> {\n\tif (globalCompanion) {\n\t\tawait globalCompanion.stop();\n\t\tglobalCompanion = null;\n\t}\n}\n\n/**\n * 检查 Companion 是否运行中\n */\nexport function isCompanionRunning(): boolean {\n\treturn globalCompanion?.isActive() ?? false;\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenVibe Companion - Integrated Local Agent
|
|
3
|
+
*
|
|
4
|
+
* 直接在 coding-agent 中运行的本地代理服务
|
|
5
|
+
*/
|
|
6
|
+
export { BashExecutor } from "./executor/bash-executor.js";
|
|
7
|
+
export { CodeExecutor } from "./executor/code-executor.js";
|
|
8
|
+
export { ResourceMonitor } from "./executor/resource-monitor.js";
|
|
9
|
+
export { CompanionHttpServer } from "./http-server.js";
|
|
10
|
+
export { BaseWorker, CPUWorker, GPUWorker, IOWorker, NetworkWorker, } from "./scheduler/workers/index.js";
|
|
11
|
+
export { SecurityManager } from "./security/security-manager.js";
|
|
12
|
+
import { CompanionHttpServer } from "./http-server.js";
|
|
13
|
+
let globalCompanion = null;
|
|
14
|
+
/**
|
|
15
|
+
* 获取或创建全局 Companion 实例
|
|
16
|
+
*/
|
|
17
|
+
export function getCompanion(config) {
|
|
18
|
+
if (!globalCompanion) {
|
|
19
|
+
globalCompanion = new CompanionHttpServer(config);
|
|
20
|
+
}
|
|
21
|
+
return globalCompanion;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 启动 Companion
|
|
25
|
+
*/
|
|
26
|
+
export async function startCompanion(config) {
|
|
27
|
+
const companion = getCompanion(config);
|
|
28
|
+
if (!companion.isActive()) {
|
|
29
|
+
await companion.start();
|
|
30
|
+
}
|
|
31
|
+
return companion;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 停止 Companion
|
|
35
|
+
*/
|
|
36
|
+
export async function stopCompanion() {
|
|
37
|
+
if (globalCompanion) {
|
|
38
|
+
await globalCompanion.stop();
|
|
39
|
+
globalCompanion = null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 检查 Companion 是否运行中
|
|
44
|
+
*/
|
|
45
|
+
export function isCompanionRunning() {
|
|
46
|
+
return globalCompanion?.isActive() ?? false;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/companion/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAkC,MAAM,6BAA6B,CAAC;AAC3F,OAAO,EAAE,YAAY,EAAkC,MAAM,6BAA6B,CAAC;AAC3F,OAAO,EAAE,eAAe,EAAgD,MAAM,gCAAgC,CAAC;AAC/G,OAAO,EAA4B,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EACN,UAAU,EACV,SAAS,EACT,SAAS,EACT,QAAQ,EACR,aAAa,GAUb,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAuB,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAEtF,OAAO,EAA4B,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEjF,IAAI,eAAe,GAA+B,IAAI,CAAC;AAEvD;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,MAA4B,EAAuB;IAC/E,IAAI,CAAC,eAAe,EAAE,CAAC;QACtB,eAAe,GAAG,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,eAAe,CAAC;AAAA,CACvB;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAA4B,EAAgC;IAChG,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,CAAC;QAC3B,MAAM,SAAS,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,GAAkB;IACpD,IAAI,eAAe,EAAE,CAAC;QACrB,MAAM,eAAe,CAAC,IAAI,EAAE,CAAC;QAC7B,eAAe,GAAG,IAAI,CAAC;IACxB,CAAC;AAAA,CACD;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,GAAY;IAC7C,OAAO,eAAe,EAAE,QAAQ,EAAE,IAAI,KAAK,CAAC;AAAA,CAC5C","sourcesContent":["/**\n * OpenVibe Companion - Integrated Local Agent\n *\n * 直接在 coding-agent 中运行的本地代理服务\n */\n\nexport { BashExecutor, type BashResult, type BashTask } from \"./executor/bash-executor.js\";\nexport { CodeExecutor, type CodeResult, type CodeTask } from \"./executor/code-executor.js\";\nexport { ResourceMonitor, type ResourceStatus as ResourceMonitorStatus } from \"./executor/resource-monitor.js\";\nexport { type CompanionHttpConfig, CompanionHttpServer } from \"./http-server.js\";\nexport {\n\tBaseWorker,\n\tCPUWorker,\n\tGPUWorker,\n\tIOWorker,\n\tNetworkWorker,\n\ttype ScheduledTask,\n\ttype TaskPayload,\n\ttype TaskPriority,\n\ttype TaskRequirements,\n\ttype TaskStatus,\n\ttype TaskType,\n\ttype WorkerCapabilities,\n\ttype WorkerInfo,\n\ttype WorkerStats,\n} from \"./scheduler/workers/index.js\";\nexport { type SecurityConfig, SecurityManager } from \"./security/security-manager.js\";\n\nimport { type CompanionHttpConfig, CompanionHttpServer } from \"./http-server.js\";\n\nlet globalCompanion: CompanionHttpServer | null = null;\n\n/**\n * 获取或创建全局 Companion 实例\n */\nexport function getCompanion(config?: CompanionHttpConfig): CompanionHttpServer {\n\tif (!globalCompanion) {\n\t\tglobalCompanion = new CompanionHttpServer(config);\n\t}\n\treturn globalCompanion;\n}\n\n/**\n * 启动 Companion\n */\nexport async function startCompanion(config?: CompanionHttpConfig): Promise<CompanionHttpServer> {\n\tconst companion = getCompanion(config);\n\tif (!companion.isActive()) {\n\t\tawait companion.start();\n\t}\n\treturn companion;\n}\n\n/**\n * 停止 Companion\n */\nexport async function stopCompanion(): Promise<void> {\n\tif (globalCompanion) {\n\t\tawait globalCompanion.stop();\n\t\tglobalCompanion = null;\n\t}\n}\n\n/**\n * 检查 Companion 是否运行中\n */\nexport function isCompanionRunning(): boolean {\n\treturn globalCompanion?.isActive() ?? false;\n}\n"]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
import { type BaseWorker, type ScheduledTask, type TaskPayload, type TaskRequirements, type TaskResult, type TaskType, type WorkerInfo } from "./workers/index.js";
|
|
3
|
+
export interface ResourceSchedulerConfig {
|
|
4
|
+
maxConcurrentTasks?: number;
|
|
5
|
+
enableGPU?: boolean;
|
|
6
|
+
maxCPUWorkers?: number;
|
|
7
|
+
maxGPUWorkers?: number;
|
|
8
|
+
maxIOWorkers?: number;
|
|
9
|
+
maxNetworkWorkers?: number;
|
|
10
|
+
taskTimeout?: number;
|
|
11
|
+
priorityBoost?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface SchedulerStats {
|
|
14
|
+
totalTasks: number;
|
|
15
|
+
completedTasks: number;
|
|
16
|
+
failedTasks: number;
|
|
17
|
+
queuedTasks: number;
|
|
18
|
+
runningTasks: number;
|
|
19
|
+
averageWaitTime: number;
|
|
20
|
+
averageExecutionTime: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ResourceStatus {
|
|
23
|
+
cpu: {
|
|
24
|
+
coreCount: number;
|
|
25
|
+
usagePercent: number;
|
|
26
|
+
availableCores: number;
|
|
27
|
+
};
|
|
28
|
+
memory: {
|
|
29
|
+
totalMb: number;
|
|
30
|
+
usedMb: number;
|
|
31
|
+
freeMb: number;
|
|
32
|
+
usagePercent: number;
|
|
33
|
+
};
|
|
34
|
+
gpu?: {
|
|
35
|
+
available: boolean;
|
|
36
|
+
deviceCount: number;
|
|
37
|
+
devices: Array<{
|
|
38
|
+
name: string;
|
|
39
|
+
memoryMb: number;
|
|
40
|
+
utilization: number;
|
|
41
|
+
}>;
|
|
42
|
+
};
|
|
43
|
+
workers: WorkerInfo[];
|
|
44
|
+
queueLength: number;
|
|
45
|
+
runningTasks: number;
|
|
46
|
+
}
|
|
47
|
+
export declare class ResourceScheduler extends EventEmitter {
|
|
48
|
+
private config;
|
|
49
|
+
private resourceMonitor;
|
|
50
|
+
private workers;
|
|
51
|
+
private taskQueue;
|
|
52
|
+
private runningTasks;
|
|
53
|
+
private taskCounter;
|
|
54
|
+
private stats;
|
|
55
|
+
private isProcessing;
|
|
56
|
+
private processInterval?;
|
|
57
|
+
constructor(config?: ResourceSchedulerConfig);
|
|
58
|
+
private initializeWorkers;
|
|
59
|
+
dispatch(taskType: TaskType, payload: TaskPayload, requirements?: TaskRequirements): Promise<TaskResult>;
|
|
60
|
+
submit(task: Omit<ScheduledTask, "id" | "createdAt" | "status">): Promise<TaskResult>;
|
|
61
|
+
private createTask;
|
|
62
|
+
private submitTask;
|
|
63
|
+
private sortQueue;
|
|
64
|
+
private processQueue;
|
|
65
|
+
private doProcessQueue;
|
|
66
|
+
private selectNextTask;
|
|
67
|
+
private selectWorker;
|
|
68
|
+
private executeTask;
|
|
69
|
+
private updateAverageWaitTime;
|
|
70
|
+
private updateAverageExecutionTime;
|
|
71
|
+
getResourceStatus(): ResourceStatus;
|
|
72
|
+
getStats(): SchedulerStats;
|
|
73
|
+
getQueue(): ScheduledTask[];
|
|
74
|
+
cancelTask(taskId: string): boolean;
|
|
75
|
+
getWorkerInfo(): WorkerInfo[];
|
|
76
|
+
addWorker(worker: BaseWorker): void;
|
|
77
|
+
removeWorker(workerId: string): boolean;
|
|
78
|
+
start(): void;
|
|
79
|
+
stop(): void;
|
|
80
|
+
shutdown(): void;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=resource-scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-scheduler.d.ts","sourceRoot":"","sources":["../../../../src/core/companion/scheduler/resource-scheduler.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from \"events\";\nimport { ResourceMonitor } from \"../executor/resource-monitor.js\";\nimport {\n\ttype BaseWorker,\n\tCPUWorker,\n\tGPUWorker,\n\tIOWorker,\n\tNetworkWorker,\n\ttype ScheduledTask,\n\ttype TaskPayload,\n\ttype TaskPriority,\n\ttype TaskRequirements,\n\ttype TaskResult,\n\ttype TaskType,\n\ttype WorkerInfo,\n} from \"./workers/index.js\";\n\nexport interface ResourceSchedulerConfig {\n\tmaxConcurrentTasks?: number;\n\tenableGPU?: boolean;\n\tmaxCPUWorkers?: number;\n\tmaxGPUWorkers?: number;\n\tmaxIOWorkers?: number;\n\tmaxNetworkWorkers?: number;\n\ttaskTimeout?: number;\n\tpriorityBoost?: boolean;\n}\n\nexport interface SchedulerStats {\n\ttotalTasks: number;\n\tcompletedTasks: number;\n\tfailedTasks: number;\n\tqueuedTasks: number;\n\trunningTasks: number;\n\taverageWaitTime: number;\n\taverageExecutionTime: number;\n}\n\nexport interface ResourceStatus {\n\tcpu: {\n\t\tcoreCount: number;\n\t\tusagePercent: number;\n\t\tavailableCores: number;\n\t};\n\tmemory: {\n\t\ttotalMb: number;\n\t\tusedMb: number;\n\t\tfreeMb: number;\n\t\tusagePercent: number;\n\t};\n\tgpu?: {\n\t\tavailable: boolean;\n\t\tdeviceCount: number;\n\t\tdevices: Array<{\n\t\t\tname: string;\n\t\t\tmemoryMb: number;\n\t\t\tutilization: number;\n\t\t}>;\n\t};\n\tworkers: WorkerInfo[];\n\tqueueLength: number;\n\trunningTasks: number;\n}\n\ninterface QueuedTask {\n\ttask: ScheduledTask;\n\tresolve: (result: TaskResult) => void;\n\treject: (error: Error) => void;\n\taddedAt: Date;\n}\n\nconst PRIORITY_WEIGHTS: Record<TaskPriority, number> = {\n\tcritical: 100,\n\thigh: 75,\n\tmedium: 50,\n\tlow: 25,\n};\n\nconst TASK_TYPE_WORKER_MAP: Record<TaskType, string[]> = {\n\trender: [\"gpu\", \"cpu\"],\n\tio_heavy: [\"io\", \"cpu\"],\n\tcpu_intensive: [\"cpu\"],\n\tnetwork: [\"network\"],\n\tscript: [\"cpu\", \"io\"],\n\tbash: [\"cpu\"],\n\tcode: [\"cpu\", \"gpu\"],\n};\n\nexport class ResourceScheduler extends EventEmitter {\n\tprivate config: Required<ResourceSchedulerConfig>;\n\tprivate resourceMonitor: ResourceMonitor;\n\tprivate workers: Map<string, BaseWorker> = new Map();\n\tprivate taskQueue: QueuedTask[] = [];\n\tprivate runningTasks: Map<string, QueuedTask> = new Map();\n\tprivate taskCounter = 0;\n\tprivate stats: SchedulerStats = {\n\t\ttotalTasks: 0,\n\t\tcompletedTasks: 0,\n\t\tfailedTasks: 0,\n\t\tqueuedTasks: 0,\n\t\trunningTasks: 0,\n\t\taverageWaitTime: 0,\n\t\taverageExecutionTime: 0,\n\t};\n\tprivate isProcessing = false;\n\tprivate processInterval?: NodeJS.Timeout;\n\n\tconstructor(config: ResourceSchedulerConfig = {}) {\n\t\tsuper();\n\t\tthis.config = {\n\t\t\tmaxConcurrentTasks: config.maxConcurrentTasks ?? 8,\n\t\t\tenableGPU: config.enableGPU ?? true,\n\t\t\tmaxCPUWorkers: config.maxCPUWorkers ?? 4,\n\t\t\tmaxGPUWorkers: config.maxGPUWorkers ?? 2,\n\t\t\tmaxIOWorkers: config.maxIOWorkers ?? 2,\n\t\t\tmaxNetworkWorkers: config.maxNetworkWorkers ?? 2,\n\t\t\ttaskTimeout: config.taskTimeout ?? 300000,\n\t\t\tpriorityBoost: config.priorityBoost ?? true,\n\t\t};\n\t\tthis.resourceMonitor = new ResourceMonitor();\n\t\tthis.initializeWorkers();\n\t}\n\n\tprivate initializeWorkers(): void {\n\t\tfor (let i = 0; i < this.config.maxCPUWorkers; i++) {\n\t\t\tconst worker = new CPUWorker(`cpu_${i}`);\n\t\t\tthis.workers.set(worker.id, worker);\n\t\t}\n\n\t\tif (this.config.enableGPU) {\n\t\t\tfor (let i = 0; i < this.config.maxGPUWorkers; i++) {\n\t\t\t\tconst worker = new GPUWorker(`gpu_${i}`);\n\t\t\t\tif (worker.capabilities.hasGpu) {\n\t\t\t\t\tthis.workers.set(worker.id, worker);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (let i = 0; i < this.config.maxIOWorkers; i++) {\n\t\t\tconst worker = new IOWorker(`io_${i}`);\n\t\t\tthis.workers.set(worker.id, worker);\n\t\t}\n\n\t\tfor (let i = 0; i < this.config.maxNetworkWorkers; i++) {\n\t\t\tconst worker = new NetworkWorker(`net_${i}`);\n\t\t\tthis.workers.set(worker.id, worker);\n\t\t}\n\t}\n\n\tdispatch(taskType: TaskType, payload: TaskPayload, requirements?: TaskRequirements): Promise<TaskResult> {\n\t\tconst task = this.createTask(taskType, payload, requirements);\n\t\treturn this.submitTask(task);\n\t}\n\n\tsubmit(task: Omit<ScheduledTask, \"id\" | \"createdAt\" | \"status\">): Promise<TaskResult> {\n\t\tconst fullTask = this.createTask(task.type, task.payload, task.requirements);\n\t\tfullTask.priority = task.priority;\n\t\tfullTask.metadata = task.metadata;\n\t\treturn this.submitTask(fullTask);\n\t}\n\n\tprivate createTask(type: TaskType, payload: TaskPayload, requirements?: TaskRequirements): ScheduledTask {\n\t\tthis.taskCounter++;\n\t\treturn {\n\t\t\tid: `task_${Date.now()}_${this.taskCounter}`,\n\t\t\ttype,\n\t\t\tpriority: \"medium\",\n\t\t\tstatus: \"pending\",\n\t\t\tpayload,\n\t\t\trequirements,\n\t\t\tcreatedAt: new Date(),\n\t\t};\n\t}\n\n\tprivate submitTask(task: ScheduledTask): Promise<TaskResult> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst queuedTask: QueuedTask = {\n\t\t\t\ttask,\n\t\t\t\tresolve,\n\t\t\t\treject,\n\t\t\t\taddedAt: new Date(),\n\t\t\t};\n\n\t\t\tthis.taskQueue.push(queuedTask);\n\t\t\tthis.stats.totalTasks++;\n\t\t\tthis.stats.queuedTasks = this.taskQueue.length;\n\n\t\t\tthis.sortQueue();\n\t\t\tthis.emit(\"taskQueued\", task);\n\t\t\tthis.processQueue();\n\t\t});\n\t}\n\n\tprivate sortQueue(): void {\n\t\tif (!this.config.priorityBoost) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.taskQueue.sort((a, b) => {\n\t\t\tconst priorityDiff = PRIORITY_WEIGHTS[b.task.priority] - PRIORITY_WEIGHTS[a.task.priority];\n\t\t\tif (priorityDiff !== 0) {\n\t\t\t\treturn priorityDiff;\n\t\t\t}\n\t\t\treturn a.addedAt.getTime() - b.addedAt.getTime();\n\t\t});\n\t}\n\n\tprivate processQueue(): void {\n\t\tif (this.isProcessing) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.isProcessing = true;\n\t\tthis.doProcessQueue();\n\t}\n\n\tprivate async doProcessQueue(): Promise<void> {\n\t\twhile (this.taskQueue.length > 0 && this.runningTasks.size < this.config.maxConcurrentTasks) {\n\t\t\tconst queuedTask = this.selectNextTask();\n\t\t\tif (!queuedTask) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tthis.executeTask(queuedTask);\n\t\t}\n\n\t\tthis.isProcessing = false;\n\t\tthis.stats.queuedTasks = this.taskQueue.length;\n\t\tthis.stats.runningTasks = this.runningTasks.size;\n\t}\n\n\tprivate selectNextTask(): QueuedTask | null {\n\t\tfor (const queuedTask of this.taskQueue) {\n\t\t\tconst worker = this.selectWorker(queuedTask.task);\n\t\t\tif (worker) {\n\t\t\t\tconst index = this.taskQueue.indexOf(queuedTask);\n\t\t\t\tthis.taskQueue.splice(index, 1);\n\t\t\t\treturn queuedTask;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate selectWorker(task: ScheduledTask): BaseWorker | null {\n\t\tconst preferredTypes = TASK_TYPE_WORKER_MAP[task.type] || [\"cpu\"];\n\t\tconst candidates: Array<{ worker: BaseWorker; score: number }> = [];\n\n\t\tfor (const [, worker] of this.workers) {\n\t\t\tif (!worker.canHandle(task)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst typeIndex = preferredTypes.indexOf(worker.type);\n\t\t\tif (typeIndex === -1 && !preferredTypes.includes(worker.type)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst info = worker.getInfo();\n\t\t\tlet score = 100;\n\n\t\t\tif (typeIndex === 0) {\n\t\t\t\tscore += 50;\n\t\t\t}\n\n\t\t\tscore += info.stats.tasksCompleted;\n\n\t\t\tcandidates.push({ worker, score });\n\t\t}\n\n\t\tif (candidates.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tcandidates.sort((a, b) => b.score - a.score);\n\t\treturn candidates[0].worker;\n\t}\n\n\tprivate async executeTask(queuedTask: QueuedTask): Promise<void> {\n\t\tconst { task } = queuedTask;\n\t\tconst worker = this.selectWorker(task);\n\n\t\tif (!worker) {\n\t\t\tqueuedTask.reject(new Error(\"No available worker for task\"));\n\t\t\tthis.stats.failedTasks++;\n\t\t\tthis.emit(\"taskFailed\", task);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.runningTasks.set(task.id, queuedTask);\n\t\ttask.status = \"running\";\n\t\ttask.startedAt = new Date();\n\n\t\tconst waitTime = Date.now() - queuedTask.addedAt.getTime();\n\t\tthis.updateAverageWaitTime(waitTime);\n\n\t\tthis.emit(\"taskStarted\", task);\n\n\t\tconst controller = new AbortController();\n\t\tconst timeoutId = setTimeout(\n\t\t\t() => controller.abort(),\n\t\t\ttask.requirements?.maxExecutionTimeMs || this.config.taskTimeout,\n\t\t);\n\n\t\ttry {\n\t\t\tconst startTime = Date.now();\n\t\t\tconst result = await worker.execute(task, controller.signal);\n\t\t\tconst executionTime = Date.now() - startTime;\n\n\t\t\ttask.status = result.success ? \"completed\" : \"failed\";\n\t\t\ttask.completedAt = new Date();\n\t\t\ttask.result = result;\n\n\t\t\tthis.updateAverageExecutionTime(executionTime);\n\n\t\t\tif (result.success) {\n\t\t\t\tthis.stats.completedTasks++;\n\t\t\t\tthis.emit(\"taskCompleted\", task);\n\t\t\t} else {\n\t\t\t\tthis.stats.failedTasks++;\n\t\t\t\tthis.emit(\"taskFailed\", task);\n\t\t\t}\n\n\t\t\tqueuedTask.resolve(result);\n\t\t} catch (error) {\n\t\t\ttask.status = \"failed\";\n\t\t\ttask.completedAt = new Date();\n\t\t\ttask.result = {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\terrorCode: \"EXECUTION_ERROR\",\n\t\t\t};\n\n\t\t\tthis.stats.failedTasks++;\n\t\t\tthis.emit(\"taskFailed\", task);\n\t\t\tqueuedTask.reject(error instanceof Error ? error : new Error(String(error)));\n\t\t} finally {\n\t\t\tclearTimeout(timeoutId);\n\t\t\tthis.runningTasks.delete(task.id);\n\t\t\tthis.stats.runningTasks = this.runningTasks.size;\n\t\t\tthis.processQueue();\n\t\t}\n\t}\n\n\tprivate updateAverageWaitTime(waitTime: number): void {\n\t\tconst total = this.stats.completedTasks + this.stats.failedTasks;\n\t\tthis.stats.averageWaitTime = (this.stats.averageWaitTime * (total - 1) + waitTime) / total;\n\t}\n\n\tprivate updateAverageExecutionTime(executionTime: number): void {\n\t\tconst total = this.stats.completedTasks + this.stats.failedTasks;\n\t\tthis.stats.averageExecutionTime = (this.stats.averageExecutionTime * (total - 1) + executionTime) / total;\n\t}\n\n\tgetResourceStatus(): ResourceStatus {\n\t\tconst baseStatus = this.resourceMonitor.getStatus();\n\t\tconst workers = Array.from(this.workers.values()).map((w) => w.getInfo());\n\n\t\tconst gpuWorkers = workers.filter((w) => w.type === \"gpu\" && w.capabilities.hasGpu);\n\t\tconst gpuStatus =\n\t\t\tgpuWorkers.length > 0\n\t\t\t\t? {\n\t\t\t\t\t\tavailable: true,\n\t\t\t\t\t\tdeviceCount: gpuWorkers.length,\n\t\t\t\t\t\tdevices: gpuWorkers.map((w) => ({\n\t\t\t\t\t\t\tname: w.capabilities.gpuName || \"Unknown\",\n\t\t\t\t\t\t\tmemoryMb: w.capabilities.gpuMemoryMb || 0,\n\t\t\t\t\t\t\tutilization: w.status === \"busy\" ? 100 : 0,\n\t\t\t\t\t\t})),\n\t\t\t\t\t}\n\t\t\t\t: undefined;\n\n\t\treturn {\n\t\t\tcpu: baseStatus.cpu,\n\t\t\tmemory: baseStatus.memory,\n\t\t\tgpu: gpuStatus,\n\t\t\tworkers,\n\t\t\tqueueLength: this.taskQueue.length,\n\t\t\trunningTasks: this.runningTasks.size,\n\t\t};\n\t}\n\n\tgetStats(): SchedulerStats {\n\t\treturn { ...this.stats };\n\t}\n\n\tgetQueue(): ScheduledTask[] {\n\t\treturn this.taskQueue.map((qt) => qt.task);\n\t}\n\n\tcancelTask(taskId: string): boolean {\n\t\tconst index = this.taskQueue.findIndex((qt) => qt.task.id === taskId);\n\t\tif (index !== -1) {\n\t\t\tconst [removed] = this.taskQueue.splice(index, 1);\n\t\t\tremoved.task.status = \"cancelled\";\n\t\t\tremoved.reject(new Error(\"Task cancelled\"));\n\t\t\tthis.stats.queuedTasks = this.taskQueue.length;\n\t\t\tthis.emit(\"taskCancelled\", removed.task);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tgetWorkerInfo(): WorkerInfo[] {\n\t\treturn Array.from(this.workers.values()).map((w) => w.getInfo());\n\t}\n\n\taddWorker(worker: BaseWorker): void {\n\t\tthis.workers.set(worker.id, worker);\n\t\tthis.emit(\"workerAdded\", worker.getInfo());\n\t}\n\n\tremoveWorker(workerId: string): boolean {\n\t\tconst worker = this.workers.get(workerId);\n\t\tif (worker) {\n\t\t\tworker.setOffline();\n\t\t\tthis.workers.delete(workerId);\n\t\t\tthis.emit(\"workerRemoved\", workerId);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tstart(): void {\n\t\tif (this.processInterval) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.processInterval = setInterval(() => {\n\t\t\tthis.processQueue();\n\t\t}, 100);\n\n\t\tthis.emit(\"started\");\n\t}\n\n\tstop(): void {\n\t\tif (this.processInterval) {\n\t\t\tclearInterval(this.processInterval);\n\t\t\tthis.processInterval = undefined;\n\t\t}\n\n\t\tfor (const queuedTask of this.taskQueue) {\n\t\t\tqueuedTask.reject(new Error(\"Scheduler stopped\"));\n\t\t}\n\t\tthis.taskQueue = [];\n\n\t\tthis.emit(\"stopped\");\n\t}\n\n\tshutdown(): void {\n\t\tthis.stop();\n\n\t\tfor (const [_taskId, queuedTask] of this.runningTasks) {\n\t\t\tqueuedTask.reject(new Error(\"Scheduler shutdown\"));\n\t\t}\n\t\tthis.runningTasks.clear();\n\n\t\tfor (const worker of this.workers.values()) {\n\t\t\tworker.setOffline();\n\t\t}\n\t\tthis.workers.clear();\n\n\t\tthis.emit(\"shutdown\");\n\t}\n}\n"]}
|