/** * Preload:contextBridge 暴露受控 API。 * 渲染进程只能看到这里列出的方法,拿不到 ipcRenderer 本体。 */ import { contextBridge, ipcRenderer } from 'electron' import type { ChatEvent, WindowState } from '@shared/types' import type { ConfigPatchLike, EditAction, SendPayload, WorkspaceAttachResult, YCodeApi } from '@shared/ipc' import type { DownloadPayload, ResourceRange } from '@shared/resources' import type { InteractionResponse } from '@shared/conversation' const api: YCodeApi = { experts: { list: () => ipcRenderer.invoke('experts:list'), read: id => ipcRenderer.invoke('experts:read', id), activate: id => ipcRenderer.invoke('experts:activate', id), remove: id => ipcRenderer.invoke('experts:remove', id), openPath: id => ipcRenderer.invoke('experts:open-path', id) }, skills: { list: (cwd) => ipcRenderer.invoke('skills:list', cwd), importPath: (path, overwrite, kind) => ipcRenderer.invoke('skills:import', path, overwrite, kind), read: (id, cwd) => ipcRenderer.invoke('skills:read', id, cwd), setEnabled: (id, enabled, cwd) => ipcRenderer.invoke('skills:enable', id, enabled, cwd), remove: (id) => ipcRenderer.invoke('skills:remove', id), export: (id, destination) => ipcRenderer.invoke('skills:export', id, destination) }, mcp: { list: () => ipcRenderer.invoke('mcp:list'), save: (input) => ipcRenderer.invoke('mcp:save', input), trust: (id, hash) => ipcRenderer.invoke('mcp:trust', id, hash), connect: (id) => ipcRenderer.invoke('mcp:connect', id), disconnect: (id) => ipcRenderer.invoke('mcp:disconnect', id), setEnabled: (id, enabled) => ipcRenderer.invoke('mcp:enable', id, enabled), setToolEnabled: (id, tool, enabled) => ipcRenderer.invoke('mcp:tool-enable', id, tool, enabled), remove: (id) => ipcRenderer.invoke('mcp:remove', id) }, terminal: { rebind: (id, previousSessionId, nextSessionId) => ipcRenderer.invoke('terminal:rebind', id, previousSessionId, nextSessionId), create: (sessionId, cols, rows) => ipcRenderer.invoke('terminal:create', sessionId, cols, rows), get: (id, sessionId) => ipcRenderer.invoke('terminal:get', id, sessionId), write: (id, data, sessionId) => ipcRenderer.invoke('terminal:write', id, data, sessionId), resize: (id, cols, rows, sessionId) => ipcRenderer.invoke('terminal:resize', id, cols, rows, sessionId), clear: (id, sessionId) => ipcRenderer.invoke('terminal:clear', id, sessionId), close: (id, sessionId) => ipcRenderer.invoke('terminal:close', id, sessionId), onEvent: (listener) => { const handler = (_event: unknown, payload: Parameters[0]): void => listener(payload) ipcRenderer.on('terminal:event', handler) return () => ipcRenderer.removeListener('terminal:event', handler) } }, browser: { rebind: (id, previousSessionId, nextSessionId) => ipcRenderer.invoke('browser:rebind', id, previousSessionId, nextSessionId), create: (sessionId, url) => ipcRenderer.invoke('browser:create', sessionId, url), get: (id, sessionId) => ipcRenderer.invoke('browser:get', id, sessionId), navigate: (id, url, sessionId) => ipcRenderer.invoke('browser:navigate', id, url, sessionId), back: (id, sessionId) => ipcRenderer.invoke('browser:back', id, sessionId), forward: (id, sessionId) => ipcRenderer.invoke('browser:forward', id, sessionId), reload: (id, sessionId) => ipcRenderer.invoke('browser:reload', id, sessionId), bounds: (id, bounds, sessionId) => ipcRenderer.invoke('browser:bounds', id, bounds, sessionId), close: (id, sessionId) => ipcRenderer.invoke('browser:close', id, sessionId) }, window: { minimize: () => ipcRenderer.invoke('window:minimize'), toggleMaximize: () => ipcRenderer.invoke('window:toggleMaximize'), close: () => ipcRenderer.invoke('window:close'), getState: () => ipcRenderer.invoke('window:state'), onState: (listener: (state: WindowState) => void) => { const handler = (_event: unknown, payload: WindowState): void => listener(payload) ipcRenderer.on('window:state', handler) return () => { ipcRenderer.removeListener('window:state', handler) } }, onFocus: (listener: (focused: boolean) => void) => { const handler = (_event: unknown, payload: { focused: boolean }): void => listener(payload.focused) ipcRenderer.on('window:focus', handler) return () => { ipcRenderer.removeListener('window:focus', handler) } } }, edit: { action: (action: EditAction) => ipcRenderer.invoke('edit:action', action) }, app: { info: () => ipcRenderer.invoke('app:info'), openConfigDir: () => ipcRenderer.invoke('app:openConfigDir'), openSettings: (section, action) => ipcRenderer.invoke('app:openSettings', section, action), onSettingsNavigate: (listener) => { const handler = (_event: Electron.IpcRendererEvent, section: string, action?: 'new'): void => listener(section, action) ipcRenderer.on('settings:navigate', handler) return () => { ipcRenderer.removeListener('settings:navigate', handler) } }, onSettingsChanged: (listener) => { const handler = (): void => listener() ipcRenderer.on('settings:changed', handler) return () => ipcRenderer.removeListener('settings:changed', handler) } }, git: { branch: (sessionId) => ipcRenderer.invoke('git:branch', sessionId) }, config: { get: () => ipcRenderer.invoke('config:get'), update: (patch: ConfigPatchLike) => ipcRenderer.invoke('config:update', patch) }, general: { get: (options) => ipcRenderer.invoke('general:get', options), update: (patch) => ipcRenderer.invoke('general:update', patch), openFolder: (kind) => ipcRenderer.invoke('general:openFolder', kind), changeWorkspaceRoot: (path) => ipcRenderer.invoke('general:changeWorkspaceRoot', path), openSystemNotificationSettings: () => ipcRenderer.invoke('general:open-system-notification-settings') }, workspace: { get: () => ipcRenderer.invoke('workspace:get'), choose: () => ipcRenderer.invoke('workspace:choose'), list: () => ipcRenderer.invoke('workspace:list'), generate: (name) => ipcRenderer.invoke('workspace:generate', name), attach: (path): Promise => ipcRenderer.invoke('workspace:attach', path), select: (id) => ipcRenderer.invoke('workspace:select', id), rename: (id, name) => ipcRenderer.invoke('workspace:rename', id, name), remove: (id) => ipcRenderer.invoke('workspace:remove', id), open: (id) => ipcRenderer.invoke('workspace:open', id) }, models: { list: () => ipcRenderer.invoke('models:list'), save: (input, overwrite) => ipcRenderer.invoke('models:save', input, overwrite), remove: (id) => ipcRenderer.invoke('models:remove', id), select: (id) => ipcRenderer.invoke('models:select', id), test: (input) => ipcRenderer.invoke('models:test', input) }, resources: { list: (sessionId?: string, workspaceId?: string) => ipcRenderer.invoke('resources:list', sessionId, workspaceId), openExternal: (url) => ipcRenderer.invoke('resources:openExternal', url), read: (path: string, range?: ResourceRange, sessionId?: string) => ipcRenderer.invoke('resources:read', path, range, sessionId), readText: (path: string, sessionId?: string) => ipcRenderer.invoke('resources:read-text', path, sessionId), classify: (path: string, sessionId?: string) => ipcRenderer.invoke('resources:classify', path, sessionId), resolveImage: (source: string, sessionId?: string) => ipcRenderer.invoke('resources:resolveImage', source, sessionId), download: (payload: DownloadPayload) => ipcRenderer.invoke('resources:download', payload), downloadImage: (source: string, filename?: string, sessionId?: string) => ipcRenderer.invoke('resources:downloadImage', source, filename, sessionId) }, attachments: { pick: () => ipcRenderer.invoke('attachments:pick'), stage: (payload) => ipcRenderer.invoke('attachments:stage', payload), remove: (id) => ipcRenderer.invoke('attachments:remove', id) }, speech: { transcribe: (payload) => ipcRenderer.invoke('speech:transcribe', payload) }, session: { list: () => ipcRenderer.invoke('session:list'), listArchived: () => ipcRenderer.invoke('session:list-archived'), create: (title, context) => ipcRenderer.invoke('session:create', title, context), updateContext: (id, context) => ipcRenderer.invoke('session:update-context', id, context), messages: (sessionId: string) => ipcRenderer.invoke('session:messages', sessionId), rename: (sessionId: string, title: string) => ipcRenderer.invoke('session:rename', sessionId, title), remove: (sessionId: string) => ipcRenderer.invoke('session:delete', sessionId), setPinned: (sessionId: string, pinned: boolean) => ipcRenderer.invoke('session:set-pinned', sessionId, pinned), setArchived: (sessionId: string, archived: boolean) => ipcRenderer.invoke('session:set-archived', sessionId, archived), openFolder: (sessionId: string) => ipcRenderer.invoke('session:open-folder', sessionId), openWindow: (sessionId: string) => ipcRenderer.invoke('session:open-window', sessionId) }, chat: { revert: (sessionId, userMessageId) => ipcRenderer.invoke('chat:revert', sessionId, userMessageId), feedback: (sessionId, messageId, value) => ipcRenderer.invoke('chat:feedback', sessionId, messageId, value), send: (payload: SendPayload) => ipcRenderer.invoke('chat:send', payload), cancel: (runId: string) => ipcRenderer.invoke('chat:cancel', runId), respond: (runId: string, interactionId: string, response: InteractionResponse) => ipcRenderer.invoke('chat:respond', runId, interactionId, response), onEvent: (listener: (event: ChatEvent) => void) => { const handler = (_event: unknown, payload: ChatEvent): void => listener(payload) ipcRenderer.on('chat:event', handler) return () => { ipcRenderer.removeListener('chat:event', handler) } } } } contextBridge.exposeInMainWorld('ycode', api)