Files
qiming/qimingclaw/crates/agent-electron-client/src/main/bootstrap/startup.ts

154 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import log from "electron-log";
import { app, BrowserWindow } from "electron";
import { getDb, readSetting } from "../db";
import { startComputerServer } from "../services/computerServer";
import {
mcpProxyManager,
DEFAULT_MCP_PROXY_CONFIG,
pruneDisabledDefaultMcpServers,
} from "../services/packages/mcp";
import { getConfiguredPorts } from "../services/startupPorts";
import { DEPS_SYNC_TIMEOUT } from "@shared/constants";
import {
startSandboxService,
stopSandboxService,
} from "../services/sandbox/serviceBootstrap";
/** 依赖同步是否正在进行 */
let _depsSyncInProgress = false;
export function isDepsSyncInProgress(): boolean {
return _depsSyncInProgress;
}
export async function runStartupTasks(): Promise<void> {
// 从 SQLite 恢复镜像配置
const {
setMirrorConfig,
getInitDepsState,
syncInitDependencies,
getSetupRequiredDependencies,
} = await import("../services/system/dependencies");
const mirrorConfig = readSetting("mirror_config");
if (mirrorConfig) {
try {
setMirrorConfig(mirrorConfig);
} catch (e) {
log.warn("[Mirror] Failed to apply mirror config:", e);
}
}
// 尽早启动 Computer HTTP Server对齐 rcoder /computer/* API端口来自聚合配置
{
const { agent: agentPort } = getConfiguredPorts();
startComputerServer(agentPort).then((r) => {
if (r.success)
log.info(`[Init] Computer HTTP server listening on port ${agentPort}`);
else log.warn(`[Init] Computer HTTP server failed: ${r.error}`);
});
}
// 初始化 MCP Proxy 配置(从数据库加载)
try {
const db = getDb();
const savedConfig = db
?.prepare("SELECT value FROM settings WHERE key = ?")
.get("mcp_proxy_config") as { value: string } | undefined;
if (savedConfig) {
try {
const parsed = JSON.parse(savedConfig.value);
// 合并可用的默认服务器;旧版本持久化的 chrome-devtools npx 默认项
// 在本地未安装且未显式启用时会被过滤,避免启动时阻塞 60s。
const merged = {
...parsed,
mcpServers: pruneDisabledDefaultMcpServers({
...DEFAULT_MCP_PROXY_CONFIG.mcpServers,
...(parsed.mcpServers || {}),
}),
};
mcpProxyManager.setConfig(merged);
} catch (e) {
log.warn("[McpProxy] Init config parse failed:", e);
}
}
log.info("[McpProxy] Config loaded");
} catch (e) {
log.warn("[McpProxy] Init config failed:", e);
}
// 初始化沙箱服务(后台启动,不阻塞主流程)
setImmediate(async () => {
try {
await startSandboxService();
log.info("[Sandbox] Sandbox service started");
} catch (e) {
log.warn("[Sandbox] Sandbox service start failed (non-fatal):", e);
// 沙箱服务失败不阻塞应用,只是功能降级
}
});
// 客户端升级后:若 appVersion 或 installVersion 变化,后台同步初始化依赖到写死版本
// 同步检查是否需要 dep sync提前设置标志避免 renderer 在 setImmediate 之前
// 读到 syncInProgress=false 而过早启动服务(竞态条件)
const state = getInitDepsState();
const currentVersion = app.getVersion();
const versionChanged = !state || state.appVersion !== currentVersion;
let packagesChanged = false;
if (state?.packages) {
for (const dep of getSetupRequiredDependencies()) {
if (!dep.installVersion) continue;
if (state.packages[dep.name] !== dep.installVersion) {
packagesChanged = true;
break;
}
}
} else {
packagesChanged = true;
}
const needsSync = versionChanged || packagesChanged;
if (needsSync) {
_depsSyncInProgress = true;
}
setImmediate(async () => {
if (!needsSync) return;
try {
try {
let syncTimer: ReturnType<typeof setTimeout>;
const { updated } = await Promise.race([
syncInitDependencies(),
new Promise<never>((_, reject) => {
syncTimer = setTimeout(
() =>
reject(
new Error(
`Dependency sync timeout after ${DEPS_SYNC_TIMEOUT}ms`,
),
),
DEPS_SYNC_TIMEOUT,
);
}),
]).finally(() => clearTimeout(syncTimer!));
if (updated.length > 0)
log.info("[Init] Init dependencies synced:", updated);
} finally {
_depsSyncInProgress = false;
// 通知所有渲染进程依赖同步完成,重新检测
for (const win of BrowserWindow.getAllWindows()) {
if (!win.isDestroyed()) {
win.webContents.send("deps:syncCompleted");
}
}
}
} catch (e) {
_depsSyncInProgress = false;
// 同步失败也通知渲染进程重新检测实际状态
for (const win of BrowserWindow.getAllWindows()) {
if (!win.isDestroyed()) {
win.webContents.send("deps:syncCompleted");
}
}
log.warn("[Init] Init dependencies sync failed:", e);
}
});
}