161 lines
4.9 KiB
JavaScript
161 lines
4.9 KiB
JavaScript
import { app, BrowserWindow, ipcMain, shell } from "electron";
|
|
import { join } from "path";
|
|
import { existsSync, mkdirSync } from "fs";
|
|
import { spawn } from "child_process";
|
|
import WebSocket from "ws";
|
|
import __cjs_mod__ from "node:module";
|
|
const __filename = import.meta.filename;
|
|
const __dirname = import.meta.dirname;
|
|
const require2 = __cjs_mod__.createRequire(import.meta.url);
|
|
const USER_DATA = join(__dirname, "../../.electron-userdata");
|
|
app.setPath("userData", USER_DATA);
|
|
const PATHS = {
|
|
userData: USER_DATA,
|
|
data: join(USER_DATA, "data"),
|
|
// SQLite 数据库
|
|
workspaces: join(USER_DATA, "workspaces"),
|
|
// worktree 根目录
|
|
backups: join(USER_DATA, "backups"),
|
|
// 数据库备份
|
|
logs: join(USER_DATA, "logs")
|
|
// 日志
|
|
};
|
|
const AGENT_CORE_PORT = 8787;
|
|
const AGENT_CORE_PATH = join(__dirname, "../../../agent-core");
|
|
const AGENT_CORE_ENTRY = "src/index.ts";
|
|
let agentCoreProcess = null;
|
|
let agentCoreWs = null;
|
|
let mainWindow = null;
|
|
function initUserDataDirs() {
|
|
for (const dir of Object.values(PATHS)) {
|
|
if (!existsSync(dir)) {
|
|
mkdirSync(dir, { recursive: true });
|
|
}
|
|
}
|
|
console.log("[main] userData:", USER_DATA);
|
|
}
|
|
function startAgentCore() {
|
|
return new Promise((resolve, reject) => {
|
|
const isDev = !!process.env.ELECTRON_RENDERER_URL;
|
|
const cmd = isDev ? "npx" : "node";
|
|
const args = isDev ? ["tsx", AGENT_CORE_ENTRY] : ["dist/index.js"];
|
|
agentCoreProcess = spawn(cmd, args, {
|
|
cwd: AGENT_CORE_PATH,
|
|
env: {
|
|
...process.env,
|
|
AGENT_CORE_PORT: String(AGENT_CORE_PORT),
|
|
AGENT_CORE_DB_DIR: PATHS.data
|
|
},
|
|
stdio: ["pipe", "pipe", "pipe"],
|
|
shell: process.platform === "win32"
|
|
// Windows 上需要 shell 才能找到 npx
|
|
});
|
|
agentCoreProcess.stdout?.on("data", (data) => {
|
|
console.log("[agent-core]", data.toString().trim());
|
|
});
|
|
agentCoreProcess.stderr?.on("data", (data) => {
|
|
console.error("[agent-core]", data.toString().trim());
|
|
});
|
|
agentCoreProcess.on("exit", (code) => {
|
|
console.log(`[agent-core] 进程退出,code=${code}`);
|
|
agentCoreProcess = null;
|
|
});
|
|
let retries = 0;
|
|
const tryConnect = () => {
|
|
agentCoreWs = new WebSocket(`ws://localhost:${AGENT_CORE_PORT}`);
|
|
agentCoreWs.on("open", () => {
|
|
console.log("[main] 已连接 agent-core WebSocket");
|
|
resolve();
|
|
});
|
|
agentCoreWs.on("error", () => {
|
|
retries++;
|
|
if (retries > 20) {
|
|
reject(new Error("无法连接 agent-core WebSocket"));
|
|
return;
|
|
}
|
|
setTimeout(tryConnect, 500);
|
|
});
|
|
};
|
|
setTimeout(tryConnect, 1e3);
|
|
});
|
|
}
|
|
function callAgentCore(type, data) {
|
|
return new Promise((resolve, reject) => {
|
|
if (!agentCoreWs || agentCoreWs.readyState !== WebSocket.OPEN) {
|
|
reject(new Error("agent-core 未连接"));
|
|
return;
|
|
}
|
|
const id = Date.now().toString();
|
|
const onMessage = (raw) => {
|
|
const msg = JSON.parse(raw.toString());
|
|
if (msg.id === id) {
|
|
agentCoreWs?.off("message", onMessage);
|
|
if (msg.error) reject(new Error(msg.error));
|
|
else resolve(msg.data);
|
|
}
|
|
};
|
|
agentCoreWs.on("message", onMessage);
|
|
agentCoreWs.send(JSON.stringify({ type, data, id }));
|
|
setTimeout(() => {
|
|
agentCoreWs?.off("message", onMessage);
|
|
reject(new Error("agent-core 响应超时"));
|
|
}, 1e4);
|
|
});
|
|
}
|
|
function createWindow() {
|
|
mainWindow = new BrowserWindow({
|
|
width: 1440,
|
|
height: 900,
|
|
minWidth: 1024,
|
|
minHeight: 640,
|
|
titleBarStyle: "hiddenInset",
|
|
backgroundColor: "#1f1f1f",
|
|
webPreferences: {
|
|
preload: join(__dirname, "../preload/index.mjs"),
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
sandbox: false
|
|
}
|
|
});
|
|
mainWindow.on("ready-to-show", () => {
|
|
mainWindow?.show();
|
|
});
|
|
mainWindow.webContents.setWindowOpenHandler((details) => {
|
|
shell.openExternal(details.url);
|
|
return { action: "deny" };
|
|
});
|
|
const rendererUrl = process.env.ELECTRON_RENDERER_URL;
|
|
if (rendererUrl) {
|
|
mainWindow.loadURL(rendererUrl);
|
|
} else {
|
|
mainWindow.loadFile(join(__dirname, "../renderer/index.html"));
|
|
}
|
|
}
|
|
app.whenReady().then(async () => {
|
|
initUserDataDirs();
|
|
try {
|
|
await startAgentCore();
|
|
} catch (err) {
|
|
console.error("[main] agent-core 启动失败:", err);
|
|
}
|
|
createWindow();
|
|
app.on("activate", () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
});
|
|
});
|
|
app.on("window-all-closed", () => {
|
|
if (agentCoreWs) agentCoreWs.close();
|
|
if (agentCoreProcess) agentCoreProcess.kill();
|
|
if (process.platform !== "darwin") app.quit();
|
|
});
|
|
ipcMain.handle("ping", () => "pong");
|
|
ipcMain.handle("get-paths", () => PATHS);
|
|
ipcMain.handle("get-db-path", () => join(PATHS.data, "agent-console.db"));
|
|
ipcMain.handle("agent:call", async (_event, { type, data }) => {
|
|
return callAgentCore(type, data);
|
|
});
|
|
ipcMain.handle("agent:status", () => ({
|
|
connected: agentCoreWs?.readyState === WebSocket.OPEN,
|
|
port: AGENT_CORE_PORT
|
|
}));
|