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,388 @@
import { ipcMain } from "electron";
import * as fs from "fs";
import log from "electron-log";
import { z } from "zod";
import type { HandlerContext } from "@shared/types/ipc";
import { createServiceManager } from "../window/serviceManager";
import { checkLanproxyHealth } from "../services/packages/lanproxyHealth";
export const lanproxyConfigSchema = z.object({
serverIp: z.string().min(1),
serverPort: z.number().int().positive(),
clientKey: z.string().min(1),
ssl: z.boolean().optional(),
});
export const agentRunnerConfigSchema = z.object({
binPath: z.string().min(1),
backendPort: z.number().int().positive(),
proxyPort: z.number().int().positive(),
apiKey: z.string().min(1),
apiBaseUrl: z.string().min(1),
defaultModel: z.string().min(1),
});
export const portSchema = z.number().int().positive();
function invalidArgs(channel: string, issues: unknown) {
log.warn(`[IPC] ${channel} invalid args:`, issues);
return { success: false, error: `Invalid arguments for ${channel}` };
}
/** 模块级 _serviceManager供其他模块如 computerServer访问 */
let _serviceManager: ReturnType<typeof createServiceManager> | null = null;
/** 获取 _serviceManager 实例(需先调用 registerProcessHandlers */
export function getServiceManager(): ReturnType<
typeof createServiceManager
> | null {
return _serviceManager;
}
export function registerProcessHandlers(ctx: HandlerContext): void {
_serviceManager = createServiceManager({
lanproxy: ctx.lanproxy,
fileServer: ctx.fileServer,
agentRunner: ctx.agentRunner,
});
// 本地别名,确保 TypeScript 知道它已被赋值
const sm = _serviceManager;
// ==================== Helper: Start File Server ====================
const startFileServerProcess = async (
port: number,
): Promise<{ success: boolean; error?: string }> => {
return sm.startFileServer(port);
};
// ==================== Helper: Start Lanproxy ====================
const startLanproxyProcess = async (config: {
serverIp: string;
serverPort: number;
clientKey: string;
ssl?: boolean;
}): Promise<{
success: boolean;
error?: string;
healthCheck?: { healthy: boolean; error?: string };
}> => {
if (ctx.lanproxy.running) {
// 切换账号后 clientKey 会变化,必须用新配置重启,不能跳过。
// 否则旧进程继续使用旧 clientKey 导致「本地显示已联通、会话显示离线」。
log.info(
"[Lanproxy] Already running, stopping first before restarting with new config",
);
await ctx.lanproxy.stopAsync();
}
const useSsl = config.ssl !== false;
const maskedKey =
config.clientKey.length > 8
? `${config.clientKey.slice(0, 4)}****${config.clientKey.slice(-4)}`
: "****";
log.info("[Lanproxy] Starting", {
server: config.serverIp,
port: config.serverPort,
keyMasked: maskedKey,
ssl: useSsl,
});
const result = await sm.startLanproxy(config);
if (result.success) {
log.info("[Lanproxy] Started", {
server: config.serverIp,
port: config.serverPort,
});
// 远端 health 接口可选(私有化部署可能未提供);异步探测仅打日志,不阻塞 IPC、不影响启动结果
void checkLanproxyHealth(config.clientKey)
.then((health) => {
result.healthCheck = health;
if (!health.healthy) {
log.warn(
"[Lanproxy] Post-start health probe failed (non-fatal; private backends may omit /api/sandbox/config/health):",
health.error,
);
} else {
log.info("[Lanproxy] Post-start health probe OK");
}
})
.catch((e) => {
log.warn("[Lanproxy] Post-start health probe error (non-fatal):", e);
});
} else {
log.error("[Lanproxy] Start failed", {
error: result.error,
server: config.serverIp,
port: config.serverPort,
});
}
return result;
};
// Lanproxy handlers
ipcMain.handle(
"lanproxy:start",
async (
_,
config: {
serverIp: string;
serverPort: number;
clientKey: string;
ssl?: boolean;
},
) => {
const parsed = lanproxyConfigSchema.safeParse(config);
if (!parsed.success) {
return invalidArgs("lanproxy:start", parsed.error.issues);
}
return startLanproxyProcess(parsed.data);
},
);
ipcMain.handle("lanproxy:stop", async () => {
return ctx.lanproxy.stop();
});
ipcMain.handle("lanproxy:status", () => {
return ctx.lanproxy.status();
});
/** 供设置页判断是否可显示「启动」并提示不可用原因 */
ipcMain.handle("lanproxy:isAvailable", async () => {
const { getLanproxyBinPath } =
await import("../services/system/dependencies");
const binPath = getLanproxyBinPath();
return { available: fs.existsSync(binPath) };
});
// Agent Runner handlers
ipcMain.handle(
"agentRunner:start",
async (
_,
config: {
binPath: string;
backendPort: number;
proxyPort: number;
apiKey: string;
apiBaseUrl: string;
defaultModel: string;
},
) => {
const parsed = agentRunnerConfigSchema.safeParse(config);
if (!parsed.success) {
return invalidArgs("agentRunner:start", parsed.error.issues);
}
const cfg = parsed.data;
const { getAppEnv } = await import("../services/system/dependencies");
if (ctx.agentRunner.running) {
return { success: true, message: "Already running" };
}
const args = [
"--backend-port",
String(cfg.backendPort),
"--proxy-port",
String(cfg.proxyPort),
"--api-key",
cfg.apiKey,
"--api-base-url",
cfg.apiBaseUrl,
"--default-model",
cfg.defaultModel,
];
// 仅记录端口与 URL不记录 apiKey避免敏感信息写入日志
log.info(
"Starting agent runner:",
cfg.binPath,
"--backend-port",
cfg.backendPort,
"--proxy-port",
cfg.proxyPort,
"--api-base-url",
cfg.apiBaseUrl,
);
const result = await ctx.agentRunner.start({
command: cfg.binPath,
args,
shell: true,
env: getAppEnv(),
startupDelayMs: 2000,
});
if (result.success) {
ctx.setAgentRunnerPorts({
backendPort: cfg.backendPort,
proxyPort: cfg.proxyPort,
});
}
return result;
},
);
ipcMain.handle("agentRunner:stop", async () => {
const result = ctx.agentRunner.stop();
ctx.setAgentRunnerPorts(null);
return result;
});
ipcMain.handle("agentRunner:status", () => {
const st = ctx.agentRunner.status();
return {
...st,
backendUrl: ctx.agentRunnerPorts
? `http://127.0.0.1:${ctx.agentRunnerPorts.backendPort}`
: undefined,
proxyUrl: ctx.agentRunnerPorts
? `http://127.0.0.1:${ctx.agentRunnerPorts.proxyPort}`
: undefined,
};
});
// File Server handlers
ipcMain.handle("fileServer:start", async (_, port: number = 60000) => {
const parsed = portSchema.safeParse(port);
if (!parsed.success) {
return invalidArgs("fileServer:start", parsed.error.issues);
}
return startFileServerProcess(parsed.data);
});
ipcMain.handle("fileServer:stop", async () => {
return ctx.fileServer.stop();
});
ipcMain.handle("fileServer:status", () => {
return ctx.fileServer.status();
});
// Computer Server handlers (Agent HTTP 接口服务,对齐 rcoder /computer/* API)
ipcMain.handle("computerServer:status", async () => {
const { getComputerServerStatus } =
await import("../services/computerServer");
return getComputerServerStatus();
});
ipcMain.handle("computerServer:start", async (_, port?: number) => {
const { startComputerServer } = await import("../services/computerServer");
const { getConfiguredPorts } = await import("../services/startupPorts");
const resolvedPortRaw = port ?? getConfiguredPorts().agent;
const parsed = portSchema.safeParse(resolvedPortRaw);
if (!parsed.success) {
return invalidArgs("computerServer:start", parsed.error.issues);
}
const resolvedPort = parsed.data;
return startComputerServer(resolvedPort);
});
ipcMain.handle("computerServer:stop", async () => {
const { stopComputerServer } = await import("../services/computerServer");
await stopComputerServer();
return { success: true };
});
// Admin Server handlers (管理接口服务)
// Admin Server 已合并到 Computer Server (60006),不再有独立的 60007 端口
// start/stop 为空操作Computer Server 通过 services:restartAll 管理)
ipcMain.handle("adminServer:start", async () => {
return { success: true };
});
ipcMain.handle("adminServer:stop", async () => {
return { success: true };
});
// adminServer:status 现在返回 Computer Server 状态
ipcMain.handle("adminServer:status", async () => {
const { getComputerServerStatus } =
await import("../services/computerServer");
return getComputerServerStatus();
});
// ==================== services:restartAll ====================
ipcMain.handle("services:restartAll", async () => {
log.info("[Services] Restarting all services...");
try {
const { stopComputerServer } = await import("../services/computerServer");
await stopComputerServer();
} catch (e) {
log.warn("[Services] ComputerServer stop error (ignored):", e);
}
const base = await sm.restartAllServices();
const results: Record<string, { success: boolean; error?: string }> = {
...base.results,
};
// 补充 processHandlers 特有步骤Computer Server
try {
const { startComputerServer } =
await import("../services/computerServer");
const { getConfiguredPorts } = await import("../services/startupPorts");
const { agent: agentPort } = getConfiguredPorts();
results.computerServer = await startComputerServer(agentPort);
log.info("[Services] ComputerServer started:", results.computerServer);
} catch (e) {
results.computerServer = { success: false, error: String(e) };
log.error("[Services] ComputerServer start failed:", e);
}
log.info("[Services] All services restart complete:", results);
return { success: true, results };
});
// ==================== services:stopAll ====================
ipcMain.handle("services:stopAll", async () => {
log.info("[Services] Stopping all services...");
const base = await sm.stopAllServices();
const results: Record<string, { success: boolean; error?: string }> = {
...base.results,
};
// 补充 processHandlers 特有步骤Computer Server
try {
const { stopComputerServer } = await import("../services/computerServer");
await stopComputerServer();
results.computerServer = { success: true };
log.info("[Services] ComputerServer stopped");
} catch (e) {
results.computerServer = { success: false, error: String(e) };
log.error("[Services] ComputerServer stop failed:", e);
}
log.info("[Services] All services stopped:", results);
return { success: true, results };
});
// ==================== services:restartAllExceptLanproxy ====================
ipcMain.handle("services:restartAllExceptLanproxy", async () => {
log.info("[Services] Restarting all services except lanproxy...");
const base = await sm.restartAllServicesExceptLanproxy();
const results: Record<string, { success: boolean; error?: string }> = {
...base.results,
};
// 补充 processHandlers 特有步骤Computer Server
try {
const { startComputerServer } =
await import("../services/computerServer");
const { getConfiguredPorts } = await import("../services/startupPorts");
const { agent: agentPort } = getConfiguredPorts();
results.computerServer = await startComputerServer(agentPort);
log.info("[Services] ComputerServer started:", results.computerServer);
} catch (e) {
results.computerServer = { success: false, error: String(e) };
log.error("[Services] ComputerServer start failed:", e);
}
log.info(
"[Services] All services (except lanproxy) restart complete:",
results,
);
return { success: true, results };
});
}