559 lines
20 KiB
TypeScript
559 lines
20 KiB
TypeScript
import { contextBridge, ipcRenderer } from "electron";
|
||
import type { SandboxPolicy } from "@shared/types/sandbox";
|
||
|
||
// Expose protected methods to the renderer process
|
||
contextBridge.exposeInMainWorld("electronAPI", {
|
||
// Process info (available in preload but not in renderer)
|
||
versions: {
|
||
node: process.versions.node,
|
||
electron: process.versions.electron,
|
||
chrome: process.versions.chrome,
|
||
},
|
||
|
||
// Settings
|
||
settings: {
|
||
get: (key: string) => ipcRenderer.invoke("settings:get", key),
|
||
set: (key: string, value: unknown) =>
|
||
ipcRenderer.invoke("settings:set", key, value),
|
||
},
|
||
|
||
// Window controls
|
||
window: {
|
||
minimize: () => ipcRenderer.invoke("window:minimize"),
|
||
maximize: () => ipcRenderer.invoke("window:maximize"),
|
||
close: () => ipcRenderer.invoke("window:close"),
|
||
},
|
||
|
||
// MCP Proxy management (qiming-mcp-stdio-proxy 聚合代理)
|
||
mcp: {
|
||
start: () => ipcRenderer.invoke("mcp:start"),
|
||
stop: () => ipcRenderer.invoke("mcp:stop"),
|
||
restart: () => ipcRenderer.invoke("mcp:restart"),
|
||
status: () => ipcRenderer.invoke("mcp:status"),
|
||
getConfig: () => ipcRenderer.invoke("mcp:getConfig"),
|
||
setConfig: (config: {
|
||
mcpServers: Record<
|
||
string,
|
||
{ command: string; args: string[]; env?: Record<string, string> }
|
||
>;
|
||
}) => ipcRenderer.invoke("mcp:setConfig", config),
|
||
discoverTools: (serverId: string) =>
|
||
ipcRenderer.invoke("mcp:discoverTools", serverId),
|
||
exportConfig: () => ipcRenderer.invoke("mcp:exportConfig"),
|
||
getPort: () => Promise.resolve(0),
|
||
setPort: (_port: number) => Promise.resolve({ success: true }),
|
||
},
|
||
|
||
// Lanproxy management (via IPC to main process)
|
||
lanproxy: {
|
||
start: (config: {
|
||
serverIp: string;
|
||
serverPort: number;
|
||
clientKey: string;
|
||
ssl?: boolean;
|
||
}) => ipcRenderer.invoke("lanproxy:start", config),
|
||
stop: () => ipcRenderer.invoke("lanproxy:stop"),
|
||
status: () => ipcRenderer.invoke("lanproxy:status"),
|
||
isAvailable: () =>
|
||
ipcRenderer.invoke("lanproxy:isAvailable") as Promise<{
|
||
available: boolean;
|
||
}>,
|
||
},
|
||
|
||
// Agent Runner management (via IPC to main process)
|
||
agentRunner: {
|
||
start: (config: {
|
||
binPath: string;
|
||
backendPort: number;
|
||
proxyPort: number;
|
||
apiKey: string;
|
||
apiBaseUrl: string;
|
||
defaultModel: string;
|
||
}) => ipcRenderer.invoke("agentRunner:start", config),
|
||
stop: () => ipcRenderer.invoke("agentRunner:stop"),
|
||
status: () => ipcRenderer.invoke("agentRunner:status"),
|
||
},
|
||
|
||
// Sandbox policy and diagnostics
|
||
sandbox: {
|
||
status: () => ipcRenderer.invoke("sandbox:status"),
|
||
getPolicy: () => ipcRenderer.invoke("sandbox:policy:get"),
|
||
setPolicy: (patch: Partial<SandboxPolicy>) =>
|
||
ipcRenderer.invoke("sandbox:policy:set", patch),
|
||
capabilities: () => ipcRenderer.invoke("sandbox:capabilities"),
|
||
setup: () => ipcRenderer.invoke("sandbox:setup"),
|
||
},
|
||
|
||
// Agent - unified ACP service (claude-code/qimingcode)
|
||
agent: {
|
||
// Unified Agent ACP
|
||
init: (config: any) => ipcRenderer.invoke("agent:init", config),
|
||
destroy: () => ipcRenderer.invoke("agent:destroy"),
|
||
getEngineType: () => ipcRenderer.invoke("agent:getEngineType"),
|
||
isReady: () => ipcRenderer.invoke("agent:isReady"),
|
||
serviceStatus: () => ipcRenderer.invoke("agent:serviceStatus"),
|
||
|
||
// Session management (ACP)
|
||
listSessions: () => ipcRenderer.invoke("agent:listSessions"),
|
||
createSession: (opts?: { parentID?: string; title?: string }) =>
|
||
ipcRenderer.invoke("agent:createSession", opts),
|
||
getSession: (id: string) => ipcRenderer.invoke("agent:getSession", id),
|
||
deleteSession: (id: string) =>
|
||
ipcRenderer.invoke("agent:deleteSession", id),
|
||
updateSession: (id: string, title?: string) =>
|
||
ipcRenderer.invoke("agent:updateSession", id, title),
|
||
getSessionStatus: () => ipcRenderer.invoke("agent:getSessionStatus"),
|
||
forkSession: (id: string, messageId?: string) =>
|
||
ipcRenderer.invoke("agent:forkSession", id, messageId),
|
||
|
||
// Messages
|
||
getMessages: (sessionId: string, limit?: number) =>
|
||
ipcRenderer.invoke("agent:getMessages", sessionId, limit),
|
||
getMessage: (sessionId: string, messageId: string) =>
|
||
ipcRenderer.invoke("agent:getMessage", sessionId, messageId),
|
||
|
||
// Prompt / Command / Shell
|
||
prompt: (sessionId: string, parts: any[], opts?: any) =>
|
||
ipcRenderer.invoke("agent:prompt", sessionId, parts, opts),
|
||
promptAsync: (sessionId: string, parts: any[], opts?: any) =>
|
||
ipcRenderer.invoke("agent:promptAsync", sessionId, parts, opts),
|
||
command: (sessionId: string, cmd: string, args?: string, opts?: any) =>
|
||
ipcRenderer.invoke("agent:command", sessionId, cmd, args, opts),
|
||
shell: (sessionId: string, cmd: string, agent?: string, model?: any) =>
|
||
ipcRenderer.invoke("agent:shell", sessionId, cmd, agent, model),
|
||
|
||
// Abort
|
||
abort: (sessionId: string) => ipcRenderer.invoke("agent:abort", sessionId),
|
||
|
||
// Permission
|
||
respondPermission: (
|
||
sessionId: string,
|
||
permissionId: string,
|
||
response: "once" | "always" | "reject",
|
||
) =>
|
||
ipcRenderer.invoke(
|
||
"agent:respondPermission",
|
||
sessionId,
|
||
permissionId,
|
||
response,
|
||
),
|
||
|
||
// Session operations
|
||
getSessionDiff: (sessionId: string, messageId?: string) =>
|
||
ipcRenderer.invoke("agent:getSessionDiff", sessionId, messageId),
|
||
revert: (sessionId: string, messageId: string, partId?: string) =>
|
||
ipcRenderer.invoke("agent:revert", sessionId, messageId, partId),
|
||
unrevert: (sessionId: string) =>
|
||
ipcRenderer.invoke("agent:unrevert", sessionId),
|
||
shareSession: (sessionId: string) =>
|
||
ipcRenderer.invoke("agent:shareSession", sessionId),
|
||
|
||
// Tools
|
||
listTools: (provider?: string, model?: string) =>
|
||
ipcRenderer.invoke("agent:listTools", provider, model),
|
||
|
||
// Providers
|
||
listProviders: () => ipcRenderer.invoke("agent:listProviders"),
|
||
|
||
// Config
|
||
getConfig: () => ipcRenderer.invoke("agent:getConfig"),
|
||
|
||
// File operations
|
||
findText: (pattern: string) =>
|
||
ipcRenderer.invoke("agent:findText", pattern),
|
||
findFiles: (query: string, dirs?: boolean) =>
|
||
ipcRenderer.invoke("agent:findFiles", query, dirs),
|
||
listFiles: (dirPath: string) =>
|
||
ipcRenderer.invoke("agent:listFiles", dirPath),
|
||
readFile: (filePath: string) =>
|
||
ipcRenderer.invoke("agent:readFile", filePath),
|
||
|
||
// MCP via ACP
|
||
mcpStatus: () => ipcRenderer.invoke("agent:mcpStatus"),
|
||
|
||
// Agents & Commands
|
||
listAgents: () => ipcRenderer.invoke("agent:listAgents"),
|
||
listCommands: () => ipcRenderer.invoke("agent:listCommands"),
|
||
|
||
// Claude Code specific
|
||
claudePrompt: (message: string) =>
|
||
ipcRenderer.invoke("agent:claudePrompt", message),
|
||
|
||
// Sessions tab (detailed view)
|
||
listSessionsDetailed: () =>
|
||
ipcRenderer.invoke("agent:listSessionsDetailed"),
|
||
stopSession: (sessionId: string) =>
|
||
ipcRenderer.invoke("agent:stopSession", sessionId),
|
||
|
||
// SSE Event listening
|
||
onEvent: (
|
||
callback: (event: any, data: { type: string; data: any }) => void,
|
||
) => {
|
||
ipcRenderer.on("agent:event", callback);
|
||
},
|
||
offEvent: (
|
||
callback: (event: any, data: { type: string; data: any }) => void,
|
||
) => {
|
||
ipcRenderer.removeListener("agent:event", callback);
|
||
},
|
||
},
|
||
|
||
// File Server management
|
||
fileServer: {
|
||
start: (port?: number) => ipcRenderer.invoke("fileServer:start", port),
|
||
stop: () => ipcRenderer.invoke("fileServer:stop"),
|
||
status: () => ipcRenderer.invoke("fileServer:status"),
|
||
},
|
||
|
||
// Computer Server lifecycle (Agent HTTP 接口服务)
|
||
computerServer: {
|
||
start: (port?: number) => ipcRenderer.invoke("computerServer:start", port),
|
||
stop: () => ipcRenderer.invoke("computerServer:stop"),
|
||
status: () => ipcRenderer.invoke("computerServer:status"),
|
||
},
|
||
|
||
// GUI Agent Server lifecycle (桌面自动化视觉操作服务)
|
||
guiServer: {
|
||
start: () => ipcRenderer.invoke("guiServer:start"),
|
||
stop: () => ipcRenderer.invoke("guiServer:stop"),
|
||
status: () => ipcRenderer.invoke("guiServer:status"),
|
||
isEnabled: () => ipcRenderer.invoke("guiServer:isEnabled"),
|
||
setEnabled: (enabled: boolean) =>
|
||
ipcRenderer.invoke("guiServer:setEnabled", enabled),
|
||
},
|
||
|
||
// Admin Server lifecycle (管理接口服务)
|
||
adminServer: {
|
||
start: (port?: number) => ipcRenderer.invoke("adminServer:start", port),
|
||
stop: () => ipcRenderer.invoke("adminServer:stop"),
|
||
status: () => ipcRenderer.invoke("adminServer:status"),
|
||
},
|
||
|
||
// Computer API (对齐 rcoder /computer/* API)
|
||
computer: {
|
||
chat: (request: any) => ipcRenderer.invoke("computer:chat", request),
|
||
agentStatus: (request: any) =>
|
||
ipcRenderer.invoke("computer:agentStatus", request),
|
||
agentStop: (request: any) =>
|
||
ipcRenderer.invoke("computer:agentStop", request),
|
||
cancelSession: (request: any) =>
|
||
ipcRenderer.invoke("computer:cancelSession", request),
|
||
health: () => ipcRenderer.invoke("computer:health"),
|
||
onProgress: (callback: any) => {
|
||
ipcRenderer.on("computer:progress", callback);
|
||
},
|
||
offProgress: (callback: any) => {
|
||
ipcRenderer.removeListener("computer:progress", callback);
|
||
},
|
||
},
|
||
|
||
// Services (对齐 Tauri services_restart_all)
|
||
services: {
|
||
restartAll: () => ipcRenderer.invoke("services:restartAll"),
|
||
stopAll: () => ipcRenderer.invoke("services:stopAll"),
|
||
},
|
||
|
||
// Tray status sync
|
||
tray: {
|
||
updateStatus: (status: "running" | "stopped" | "error" | "starting") =>
|
||
ipcRenderer.invoke("tray:updateStatus", status),
|
||
updateServicesStatus: (running: boolean) =>
|
||
ipcRenderer.invoke("tray:updateServicesStatus", running),
|
||
},
|
||
|
||
// Dependency management
|
||
dependencies: {
|
||
checkAll: (options?: { checkLatest?: boolean }) =>
|
||
ipcRenderer.invoke("dependencies:checkAll", options),
|
||
checkNode: () => ipcRenderer.invoke("dependencies:checkNode"),
|
||
checkUv: () => ipcRenderer.invoke("dependencies:checkUv"),
|
||
checkMcpProxyBundled: () =>
|
||
ipcRenderer.invoke("dependencies:checkMcpProxyBundled"),
|
||
checkQimingCodeBundled: () =>
|
||
ipcRenderer.invoke("dependencies:checkQimingCodeBundled"),
|
||
checkClaudeCodeAcpBundled: () =>
|
||
ipcRenderer.invoke("dependencies:checkClaudeCodeAcpBundled"),
|
||
checkQimingFileServerBundled: () =>
|
||
ipcRenderer.invoke("dependencies:checkQimingFileServerBundled"),
|
||
detectPackage: (packageName: string, binName?: string) =>
|
||
ipcRenderer.invoke("dependencies:detectPackage", packageName, binName),
|
||
installPackage: (
|
||
packageName: string,
|
||
options?: { registry?: string; version?: string },
|
||
) =>
|
||
ipcRenderer.invoke("dependencies:installPackage", packageName, options),
|
||
installMissing: () => ipcRenderer.invoke("dependencies:installMissing"),
|
||
getAppDataDir: () => ipcRenderer.invoke("dependencies:getAppDataDir"),
|
||
getRequiredList: () => ipcRenderer.invoke("dependencies:getRequiredList"),
|
||
},
|
||
|
||
// Engine Manager (claude-code / qimingcode)
|
||
engine: {
|
||
checkLocal: (engine: string) =>
|
||
ipcRenderer.invoke("engine:checkLocal", engine),
|
||
checkGlobal: (engine: string) =>
|
||
ipcRenderer.invoke("engine:checkGlobal", engine),
|
||
getVersion: (engine: string) =>
|
||
ipcRenderer.invoke("engine:getVersion", engine),
|
||
findBinary: (engine: string) =>
|
||
ipcRenderer.invoke("engine:findBinary", engine),
|
||
install: (engine: string, options?: { registry?: string }) =>
|
||
ipcRenderer.invoke("engine:install", engine, options),
|
||
start: (config: {
|
||
engine: string;
|
||
apiKey?: string;
|
||
baseUrl?: string;
|
||
model?: string;
|
||
workspaceDir?: string;
|
||
}) => ipcRenderer.invoke("engine:start", config),
|
||
stop: (engineId: string) => ipcRenderer.invoke("engine:stop", engineId),
|
||
status: (engineId?: string) =>
|
||
ipcRenderer.invoke("engine:status", engineId),
|
||
send: (engineId: string, message: string) =>
|
||
ipcRenderer.invoke("engine:send", engineId, message),
|
||
stopAll: () => ipcRenderer.invoke("engine:stopAll"),
|
||
},
|
||
|
||
// Shell utilities
|
||
shell: {
|
||
openExternal: (url: string) =>
|
||
ipcRenderer.invoke("shell:openExternal", url),
|
||
// 打开本地目录/文件到系统文件管理器,供设置页“打开工作空间目录”等功能复用。
|
||
openPath: (targetPath: string) =>
|
||
ipcRenderer.invoke("shell:openPath", targetPath),
|
||
},
|
||
|
||
// Session / Cookie management (for embedded webview)
|
||
session: {
|
||
setCookie: (params: {
|
||
url: string;
|
||
name: string;
|
||
value: string;
|
||
domain?: string;
|
||
httpOnly?: boolean;
|
||
secure?: boolean;
|
||
}) => ipcRenderer.invoke("session:setCookie", params),
|
||
getCookie: (params: { url: string; name: string }) =>
|
||
ipcRenderer.invoke("session:getCookie", params),
|
||
removeCookie: (params: { url: string; name: string }) =>
|
||
ipcRenderer.invoke("session:removeCookie", params),
|
||
flushStore: () => ipcRenderer.invoke("session:flushStore"),
|
||
},
|
||
|
||
// WebView Window - 独立窗口打开会话浏览器
|
||
webview: {
|
||
openWindow: (params: { url: string; title?: string }) =>
|
||
ipcRenderer.invoke("webview:openWindow", params) as Promise<{
|
||
success: boolean;
|
||
reused?: boolean;
|
||
error?: string;
|
||
}>,
|
||
closeWindow: () =>
|
||
ipcRenderer.invoke("webview:closeWindow") as Promise<{
|
||
success: boolean;
|
||
error?: string;
|
||
}>,
|
||
isWindowOpen: () =>
|
||
ipcRenderer.invoke("webview:isWindowOpen") as Promise<boolean>,
|
||
},
|
||
|
||
// Mirror / Registry
|
||
mirror: {
|
||
get: () => ipcRenderer.invoke("mirror:get"),
|
||
set: (config: { npmRegistry?: string; uvIndexUrl?: string }) =>
|
||
ipcRenderer.invoke("mirror:set", config),
|
||
},
|
||
|
||
// i18n - 语言同步
|
||
i18n: {
|
||
getLang: () => ipcRenderer.invoke("i18n:getLang"),
|
||
setLang: (lang: string) => {
|
||
console.debug(`[preload] i18n.setLang("${lang}")`);
|
||
return ipcRenderer.invoke("i18n:setLang", lang);
|
||
},
|
||
},
|
||
|
||
// Dialog utilities
|
||
dialog: {
|
||
openDirectory: (title?: string) =>
|
||
ipcRenderer.invoke("dialog:openDirectory", title),
|
||
},
|
||
|
||
// Autolaunch
|
||
autolaunch: {
|
||
get: () => ipcRenderer.invoke("autolaunch:get"),
|
||
set: (enabled: boolean) => ipcRenderer.invoke("autolaunch:set", enabled),
|
||
},
|
||
|
||
// Long-term Memory
|
||
memory: {
|
||
// Lifecycle
|
||
init: (workspaceDir: string, config?: any) =>
|
||
ipcRenderer.invoke("memory:init", workspaceDir, config),
|
||
destroy: () => ipcRenderer.invoke("memory:destroy"),
|
||
status: () => ipcRenderer.invoke("memory:status"),
|
||
ensureReady: () =>
|
||
ipcRenderer.invoke("memory:ensureReady") as Promise<{
|
||
ready: boolean;
|
||
synced: boolean;
|
||
}>,
|
||
|
||
// Configuration
|
||
getConfig: () => ipcRenderer.invoke("memory:getConfig"),
|
||
updateConfig: (config: any) =>
|
||
ipcRenderer.invoke("memory:updateConfig", config),
|
||
|
||
// Extraction
|
||
extract: (
|
||
sessionId: string,
|
||
messageId: string,
|
||
messages: any[],
|
||
modelConfig: any,
|
||
) =>
|
||
ipcRenderer.invoke(
|
||
"memory:extract",
|
||
sessionId,
|
||
messageId,
|
||
messages,
|
||
modelConfig,
|
||
),
|
||
append: (content: string, title?: string) =>
|
||
ipcRenderer.invoke("memory:append", content, title),
|
||
handleMessage: (
|
||
message: { role: "user" | "assistant"; content: string },
|
||
sessionId: string,
|
||
modelConfig: any,
|
||
) =>
|
||
ipcRenderer.invoke(
|
||
"memory:handleMessage",
|
||
message,
|
||
sessionId,
|
||
modelConfig,
|
||
) as Promise<{ success: boolean; error?: string }>,
|
||
onSessionEnd: (sessionId: string, modelConfig: any) =>
|
||
ipcRenderer.invoke(
|
||
"memory:onSessionEnd",
|
||
sessionId,
|
||
modelConfig,
|
||
) as Promise<{ success: boolean; taskId?: string; error?: string }>,
|
||
getExtractionProgress: (sessionId: string) =>
|
||
ipcRenderer.invoke("memory:getExtractionProgress", sessionId) as Promise<{
|
||
success: boolean;
|
||
progress?: any[];
|
||
error?: string;
|
||
}>,
|
||
|
||
// Retrieval
|
||
search: (query: string, options?: any) =>
|
||
ipcRenderer.invoke("memory:search", query, options),
|
||
getContext: (query: string, options?: any) =>
|
||
ipcRenderer.invoke("memory:getContext", query, options),
|
||
|
||
// File operations
|
||
sync: () => ipcRenderer.invoke("memory:sync"),
|
||
rebuildIndex: () => ipcRenderer.invoke("memory:rebuildIndex"),
|
||
getFiles: () => ipcRenderer.invoke("memory:getFiles"),
|
||
|
||
// Management
|
||
add: (entry: any) => ipcRenderer.invoke("memory:add", entry),
|
||
update: (id: string, updates: any) =>
|
||
ipcRenderer.invoke("memory:update", id, updates),
|
||
delete: (id: string) => ipcRenderer.invoke("memory:delete", id),
|
||
list: (options?: any) => ipcRenderer.invoke("memory:list", options),
|
||
|
||
// Scheduled tasks
|
||
runConsolidation: (modelConfig?: {
|
||
provider: string;
|
||
model: string;
|
||
apiKey: string;
|
||
baseUrl?: string;
|
||
}) => ipcRenderer.invoke("memory:runConsolidation", modelConfig),
|
||
runCleanup: () => ipcRenderer.invoke("memory:runCleanup"),
|
||
|
||
// Vector
|
||
checkVectorSupport: () => ipcRenderer.invoke("memory:checkVectorSupport"),
|
||
setEmbeddingConfig: (config: any) =>
|
||
ipcRenderer.invoke("memory:setEmbeddingConfig", config),
|
||
|
||
// Queue status
|
||
getQueueStatus: () => ipcRenderer.invoke("memory:getQueueStatus"),
|
||
getSchedulerStatus: () => ipcRenderer.invoke("memory:getSchedulerStatus"),
|
||
},
|
||
|
||
// Log
|
||
log: {
|
||
getDir: () => ipcRenderer.invoke("log:getDir"),
|
||
openDir: () => ipcRenderer.invoke("log:openDir"),
|
||
list: (count?: number, offset?: number) =>
|
||
ipcRenderer.invoke("log:list", count, offset),
|
||
write: (
|
||
level: "info" | "warn" | "error",
|
||
message: string,
|
||
...args: unknown[]
|
||
) => ipcRenderer.invoke("log:write", level, message, ...args),
|
||
},
|
||
|
||
// App
|
||
app: {
|
||
checkUpdate: () => ipcRenderer.invoke("app:checkUpdate"),
|
||
getVersion: () => ipcRenderer.invoke("app:getVersion"),
|
||
downloadUpdate: () => ipcRenderer.invoke("app:downloadUpdate"),
|
||
installUpdate: () => ipcRenderer.invoke("app:installUpdate"),
|
||
getUpdateState: () => ipcRenderer.invoke("app:getUpdateState"),
|
||
openReleasesPage: () => ipcRenderer.invoke("app:openReleasesPage"),
|
||
getUpdateDebugInfo: () => ipcRenderer.invoke("app:getUpdateDebugInfo"),
|
||
getDeviceId: () => ipcRenderer.invoke("app:getDeviceId"),
|
||
getPrimaryLocalIp: () => ipcRenderer.invoke("app:getPrimaryLocalIp"),
|
||
},
|
||
|
||
// Permissions (macOS)
|
||
permissions: {
|
||
check: () => ipcRenderer.invoke("permissions:check"),
|
||
openSettings: (permissionKey: string) =>
|
||
ipcRenderer.invoke("permissions:openSettings", permissionKey),
|
||
},
|
||
|
||
// Quick Init — 读取快捷初始化配置
|
||
quickInit: {
|
||
getConfig: () => ipcRenderer.invoke("quickInit:getConfig"),
|
||
},
|
||
|
||
// Event listeners
|
||
// 保存 callback → wrapper 映射,使 off() 能正确移除 on() 注册的 listener
|
||
on: (channel: string, callback: (...args: unknown[]) => void) => {
|
||
const validChannels = [
|
||
"menu:new-session",
|
||
"menu:settings",
|
||
"menu:mcp-settings",
|
||
"menu:dependencies",
|
||
"cowork:message",
|
||
"cowork:permission",
|
||
"agent:event",
|
||
"computer:progress",
|
||
"update:status",
|
||
"deps:syncCompleted",
|
||
"autolaunch:changed",
|
||
"memory:sync",
|
||
"memory:consolidation",
|
||
"memory:cleanup",
|
||
"admin:servicesRestarting",
|
||
"admin:servicesRestarted",
|
||
];
|
||
if (validChannels.includes(channel)) {
|
||
const wrapper = (_: unknown, ...args: unknown[]) => callback(...args);
|
||
(callback as any).__ipcWrapper = wrapper;
|
||
ipcRenderer.on(channel, wrapper as any);
|
||
}
|
||
},
|
||
|
||
off: (channel: string, callback: (...args: unknown[]) => void) => {
|
||
const wrapper = (callback as any).__ipcWrapper || callback;
|
||
ipcRenderer.removeListener(channel, wrapper);
|
||
},
|
||
|
||
// PERF 专用日志(fire-and-forget,写入主进程 perf.YYYY-MM-DD.log)
|
||
perf: {
|
||
log: (msg: string) => ipcRenderer.send("perf:log", msg),
|
||
},
|
||
});
|