Files
dnd-claw/agent-console/out/main/index.js
T

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
}));