IPC 协议字典
1. Channel 拓扑
flowchart TB R[Renderer] --> P[preload.ts] P -->|ipcRenderer.send/invoke| M[main/index.ts ipcMain] M -->|HTTP + internal token| B[backend routes]
2. Channel 字典
| Channel | IPC Type | Params | Return Schema | Main Handler Behavior |
|---|---|---|---|---|
app:close-window | send | none | none | 请求关闭当前聚焦窗口或主窗口;主窗口关闭守卫会先检查 backend SSH/SFTP 活动状态再允许销毁 |
app:close-confirmation-request | Main-to-renderer event | { requestId: string } | none | 请求使用共享 renderer 对话框显示关闭警告;preload 校验不透明 ID,并在 React listener 挂载前暂存一个提前到达的请求 |
app:close-confirmation-response | send | { requestId: string, confirmed: boolean } | none | 仅解析由当前发送 webContents 持有且不透明 request ID 匹配的进行中关闭决策 |
i18n:get-locale | invoke | none | Promise<string> | Returns current resolved locale |
i18n:set-locale | invoke | locale: string | Promise<string> | Resolves/persists in-memory locale and updates title |
app:get-runtime-user-name | invoke | none | Promise<string> | Returns OS username fallback chain |
app:get-version-info | invoke | none | Promise<{ appName: string; version: string; buildVersion: string; buildTime: string; commit: string; electron: string; chromium: string; node: string; v8: string; os: string }> | 为关于页返回应用名称、版本号、构建时间与运行时技术信息 |
app:get-pending-launch-working-directory | invoke | none | Promise<string | null> | 返回当前待消费的上下文启动工作目录(来自 CLI 参数) |
app:get-downloads-path | invoke | none | Promise<string> | 返回系统下载目录,供本地保存默认路径使用 |
app:create-sftp-temporary-file | invoke | fileName: string | Promise<string> | 在 Cosmosh SFTP 临时根目录下创建唯一的本地目标路径,供 backend 下载与打开流程使用 |
app:create-sftp-downloads-file | invoke | fileName: string | Promise<string> | 在系统 Downloads 目录下为发起请求的 renderer 授权一个精确的单次下载目标 |
app:select-sftp-upload-files | invoke | none | Promise<{ canceled: boolean; files: Array<{ name: string; localPath: string; size: number; modifiedAt: string }> }> | 打开原生多文件选择器,并将所选普通文件复制到受控 SFTP 临时根目录下的隔离目录 |
app:stage-sftp-dropped-upload-files | invoke | entries: Array<{ name: string; localPath?: string }> | Promise<{ canceled: false; files: Array<{ name: string; localPath: string; size: number; modifiedAt: string }>; rejectedEntries?: Array<{ name: string; reason: 'directory-unsupported' | 'not-file' | 'path-unavailable' | 'unreadable' }> }> | 暂存 preload 已解析的、拖放到 SFTP 目录目标上的本地文件,并在不向 renderer 暴露任意路径暂存能力的前提下报告不支持的文件夹/非文件项 |
app:cleanup-sftp-temporary-files | invoke | localPaths: string[] | Promise<boolean> | 尽力删除经过校验的上传暂存文件及其已清空的隔离临时目录 |
app:open-sftp-temporary-file | invoke | localPath: string | Promise<boolean> | 使用系统默认应用打开 Cosmosh SFTP 临时根目录下的既有文件 |
app:read-sftp-temporary-image-preview | invoke | localPath: string | Promise<string> | 校验 Cosmosh SFTP 临时根目录下的既有图片文件,并返回有大小上限的 data URL 供 renderer 图片预览使用 |
app:start-sftp-temporary-file-watch | invoke | localPath: string | Promise<string> | 为 Cosmosh SFTP 临时根目录下的既有文件启动防抖监听,并返回 watch id |
app:stop-sftp-temporary-file-watch | invoke | watchId: string | Promise<boolean> | 停止此前创建的 SFTP 临时文件监听 |
app:show-sftp-open-with-dialog | invoke | localPath: string | Promise<boolean> | 仅 Windows:校验临时文件路径,并通过 shell openas verb 打开系统“打开方式”选择器 |
app:list-sftp-open-with-applications | invoke | localPath: string | Promise<Array<{ id: string; name: string; path: string; bundleIdentifier?: string; iconDataUrl?: string }>> | 仅 macOS:校验临时文件路径,并返回 NSWorkspace 判定可打开该文件的应用列表 |
app:open-sftp-file-with-application | invoke | localPath: string, applicationPath: string | Promise<boolean> | 仅 macOS:校验临时文件与所选应用属于可用应用列表后,使用该应用打开文件 |
app:sftp-temporary-file-changed | event (main -> renderer) | { watchId: string; localPath: string; size: number; modifiedAt: string } | none | 向拥有该监听的 renderer webContents 推送一次防抖后的 SFTP 临时文件变更事件 |
app:get-database-security-info | invoke | none | Promise<{ runtimeMode: 'development' | 'production'; resolverMode: 'development-fixed-key' | 'safe-storage' | 'master-password-fallback'; safeStorageAvailable: boolean; databasePath: string; securityConfigPath: string; hasEncryptedDbMasterKey: boolean; hasMasterPasswordHash: boolean; hasMasterPasswordSalt: boolean; hasMasterPasswordEnv: boolean; fallbackReady: boolean }> | 为设置 → 高级页返回非敏感的数据库加密引导诊断信息 |
app:resolve-system-proxy | invoke | { host: string; port: number } | Promise<{ proxyRules: string }> | 校验一个 SSH 服务器目标,并为 https://host:port/ 解析 Chromium 系统/PAC 代理规则 |
app:launch-working-directory | event (main -> renderer) | cwd: string | none | 当第二实例触发时,向渲染层推送上下文启动工作目录 |
app:menu-action | event (main -> renderer) | action: 'open-about' | 'open-settings' | 'new-tab' | 'close-current-tab' | 'close-right-tabs' | 'show-tab-switcher' | none | 将 macOS 系统菜单触发的应用菜单命令以受控枚举形式分发到渲染层标签页/状态处理器 |
app:open-devtools | invoke | none | Promise<boolean> | 为当前主窗口打开 DevTools(窗口可用时) |
app:toggle-devtools | invoke | none | Promise<boolean> | 切换当前主窗口的分离式 DevTools(已打开则关闭,已关闭则打开) |
app:reload-webview | invoke | none | Promise<boolean> | 重新加载当前活动 renderer webContents,并忽略缓存以保证调试刷新可预测 |
app:restart-backend-runtime | invoke | none | Promise<boolean> | 在开发环境中原位重启 backend 运行时,无需重启整个应用 |
app:show-in-file-manager | invoke | targetPath?: string | Promise<boolean> | Opens file/folder in OS file manager |
app:open-external-url | invoke | targetUrl: string | Promise<boolean> | 使用系统默认浏览器打开受信任的 HTTP(S) 链接 |
app:set-windows-system-menu-symbol-color | invoke | symbolColor: string | Promise<boolean> | 将 token 驱动的 Windows 标题栏系统菜单符号色应用到当前主窗口 overlay |
app:show-save-file-dialog | invoke | defaultPath?: string | Promise<{ canceled: boolean; filePath?: string }> | 打开原生保存对话框,并为发起请求的 renderer 授权所选路径执行一次 SFTP 下载 |
app:import-private-key | invoke | none | Promise<{ canceled: boolean; fileName?: string; content?: string }> | 调起系统文件选择器并在选择后返回文件名与 UTF-8 私钥内容 |
app:get-process-performance-stats | invoke | none | Promise<{ sampledAt: number; cpuPercent: number | null; mainProcessMemory: { rssBytes: number; heapTotalBytes: number; heapUsedBytes: number; externalBytes: number; arrayBuffersBytes: number }; rendererProcessMemory: { residentSetBytes: number; privateBytes: number; sharedBytes: number } | null; backendProcess: { pid: number; cpuPercent: number | null; memoryRssBytes: number | null } | null }> | 为调试浮层采样主进程 CPU/内存,基于当前活动窗口获取渲染进程内存,并补充 backend 子进程 CPU/RSS 内存数据 |
app:export-main-heap-snapshot | invoke | none | Promise<{ ok: boolean; filePath?: string; message?: string }> | 将主进程 V8 堆快照写入应用 user-data 下的 debug 快照目录 |
debug:backend-request-trace-list | invoke | none | Promise<BackendRequestTrace[]> | 返回已保留的脱敏 backend 请求镜像列表,并让该 renderer webContents 订阅后续 trace event;未启用请求追踪时返回空列表 |
debug:backend-request-trace-clear | invoke | none | Promise<boolean> | 清空开发态请求镜像 ring buffer |
debug:backend-request-trace-event | event (main -> renderer) | BackendRequestTrace | none | 向已订阅的 renderer webContents 推送一条已完成且脱敏的 backend proxy 请求镜像 |
backend:test-ping | invoke | none | Promise<ApiTestPingResponse | ApiErrorResponse> | Calls backend health test endpoint |
backend:settings-get | invoke | none | Promise<ApiSettingsGetResponse | ApiErrorResponse> | GET 已持久化设置 |
backend:settings-update | invoke | payload: ApiSettingsUpdateRequest | Promise<ApiSettingsUpdateResponse | ApiErrorResponse> | PUT 设置快照 |
backend:audit-list-events | invoke | query?: ApiAuditEventListQuery | Promise<ApiAuditEventListResponse | ApiErrorResponse> | GET 审计事件列表(支持过滤与分页) |
backend:audit-get-event-by-id | invoke | eventId: string | Promise<ApiAuditEventDetailResponse | ApiErrorResponse> | GET 单条审计事件详情 |
backend:ssh-list-servers | invoke | none | Promise<ApiSshListServersResponse | ApiErrorResponse> | GET SSH server list |
backend:ssh-create-server | invoke | payload: ApiSshCreateServerRequest | Promise<ApiSshCreateServerResponse | ApiErrorResponse> | POST create SSH server |
backend:ssh-update-server | invoke | serverId: string, payload: ApiSshUpdateServerRequest | Promise<ApiSshUpdateServerResponse | ApiErrorResponse> | PUT update SSH server |
backend:ssh-get-server-credentials | invoke | serverId: string | Promise<ApiSshGetServerCredentialsResponse | ApiErrorResponse> | GET decrypted credentials |
backend:ssh-list-folders | invoke | none | Promise<ApiSshListFoldersResponse | ApiErrorResponse> | GET folder list |
backend:ssh-create-folder | invoke | payload: ApiSshCreateFolderRequest | Promise<ApiSshCreateFolderResponse | ApiErrorResponse> | POST create folder |
backend:ssh-update-folder | invoke | folderId: string, payload: ApiSshUpdateFolderRequest | Promise<ApiSshUpdateFolderResponse | ApiErrorResponse> | PUT update folder |
backend:ssh-list-tags | invoke | none | Promise<ApiSshListTagsResponse | ApiErrorResponse> | GET tag list |
backend:ssh-create-tag | invoke | payload: ApiSshCreateTagRequest | Promise<ApiSshCreateTagResponse | ApiErrorResponse> | POST create tag |
backend:ssh-list-keychains | invoke | none | Promise<ApiSshListKeychainsResponse | ApiErrorResponse> | GET 钥匙链列表 |
backend:ssh-create-keychain | invoke | payload: ApiSshCreateKeychainRequest | Promise<ApiSshCreateKeychainResponse | ApiErrorResponse> | POST 创建钥匙链 |
backend:ssh-update-keychain | invoke | keychainId: string, payload: ApiSshUpdateKeychainRequest | Promise<ApiSshUpdateKeychainResponse | ApiErrorResponse> | PUT 更新钥匙链 |
backend:ssh-get-keychain-credentials | invoke | keychainId: string | Promise<ApiSshGetKeychainCredentialsResponse | ApiErrorResponse> | GET 解密后的钥匙链凭据 |
backend:ssh-create-session | invoke | payload: ApiSshCreateSessionRequest | Promise<ApiSshCreateSessionResponse | ApiSshCreateSessionHostVerificationRequiredResponse | ApiErrorResponse> | POST create SSH shell session |
backend:ssh-trust-fingerprint | invoke | payload: ApiSshTrustFingerprintRequest | Promise<ApiSshTrustFingerprintResponse | ApiErrorResponse> | POST trust host fingerprint |
backend:ssh-close-session | invoke | sessionId: string | Promise<{ success: boolean }> | DELETE SSH session |
backend:ssh-delete-server | invoke | serverId: string | Promise<{ success: boolean }> | DELETE SSH server |
backend:ssh-delete-folder | invoke | folderId: string | Promise<{ success: boolean }> | DELETE SSH folder |
backend:ssh-delete-keychain | invoke | keychainId: string | Promise<{ success: boolean }> | DELETE SSH 钥匙链 |
backend:port-forward-list-rules | invoke | none | Promise<ApiPortForwardListRulesResponse | ApiErrorResponse> | GET 已持久化的 SSH 端口转发规则,并合并内存运行状态 |
backend:port-forward-create-rule | invoke | payload: ApiPortForwardCreateRuleRequest | Promise<ApiPortForwardCreateRuleResponse | ApiErrorResponse> | POST 创建一条 stopped 端口转发规则 |
backend:port-forward-update-rule | invoke | ruleId: string, payload: ApiPortForwardUpdateRuleRequest | Promise<ApiPortForwardUpdateRuleResponse | ApiErrorResponse> | PUT 更新一条 stopped 端口转发规则 |
backend:port-forward-start-rule | invoke | ruleId: string, payload: ApiPortForwardStartRuleRequest | Promise<ApiPortForwardStartRuleResponse | ApiErrorResponse> | POST 启动一条规则,并可携带临时系统代理规则;可能返回共享 SSH_HOST_UNTRUSTED payload 供指纹信任后重试 |
backend:port-forward-stop-rule | invoke | ruleId: string | Promise<ApiPortForwardStopRuleResponse | ApiErrorResponse> | POST 停止一条活动规则;已停止规则由 backend 幂等处理 |
backend:port-forward-delete-rule | invoke | ruleId: string | Promise<{ success: boolean }> | DELETE 一条 stopped 端口转发规则 |
backend:sftp-create-session | invoke | payload: ApiSftpCreateSessionRequest | Promise<ApiSftpCreateSessionResponse | ApiSftpCreateSessionHostVerificationRequiredResponse | ApiErrorResponse> | POST 创建 SFTP 文件系统会话 |
backend:sftp-list-directory | invoke | sessionId: string, query?: ApiSftpListDirectoryQuery | Promise<ApiSftpListDirectoryResponse | ApiErrorResponse> | GET 单个 SFTP 目录列表 |
backend:sftp-get-entry-details | invoke | sessionId: string, payload: ApiSftpEntryDetailsRequest | Promise<ApiSftpEntryDetailsResponse | ApiErrorResponse> | POST 获取已选 SFTP 条目的非递归元数据 |
backend:sftp-read-file | invoke | sessionId: string, query: ApiSftpReadFileQuery | Promise<ApiSftpReadFileResponse | ApiErrorResponse> | GET 当前 SFTP 会话内有上限的 UTF-8 文件预览 |
backend:sftp-write-file | invoke | sessionId: string, payload: ApiSftpWriteFileRequest | Promise<ApiSftpWriteFileResponse | ApiErrorResponse> | POST 经过远程 size/mtime 冲突检查后,将可编辑 UTF-8 SFTP 预览内容保存回一个远程普通文件;远程冲突返回 SFTP_UPLOAD_CONFLICT |
backend:sftp-download-file | invoke | sessionId: string, payload: ApiSftpDownloadFileRequest | Promise<ApiSftpDownloadFileResponse | ApiErrorResponse> | POST 仅将一个远程普通 SFTP 文件流式写入 app utility IPC 为该 renderer 所有者授权的精确路径 |
backend:sftp-upload-file | invoke | sessionId: string, payload: ApiSftpUploadFileRequest | Promise<ApiSftpUploadFileResponse | ApiErrorResponse> | POST 将一个受控本地临时文件流式写入新的远程路径,或在快照/显式覆盖确认后替换既有普通文件;冲突返回 SFTP_UPLOAD_CONFLICT |
backend:sftp-get-transfer-progress | invoke | transferId: string | Promise<ApiSftpTransferProgressResponse | ApiErrorResponse> | GET 一个活动或近期完成的 SFTP 传输的字节进度、滚动速度、状态与可选失败原因 |
backend:sftp-create-directory | invoke | sessionId: string, payload: ApiSftpCreateDirectoryRequest | Promise<ApiSftpCreateDirectoryResponse | ApiErrorResponse> | POST 创建远程 SFTP 目录 |
backend:sftp-create-file | invoke | sessionId: string, payload: ApiSftpCreateFileRequest | Promise<ApiSftpCreateFileResponse | ApiErrorResponse> | POST 创建远程 SFTP 空文件 |
backend:sftp-rename-entry | invoke | sessionId: string, payload: ApiSftpRenameRequest | Promise<ApiSftpRenameResponse | ApiErrorResponse> | POST 重命名或移动远程 SFTP 条目 |
backend:sftp-copy-entry | invoke | sessionId: string, payload: ApiSftpCopyRequest | Promise<ApiSftpCopyResponse | ApiErrorResponse> | POST 复制远程 SFTP 文件或目录树 |
backend:sftp-delete-entry | invoke | sessionId: string, payload: ApiSftpDeleteRequest | Promise<ApiSftpDeleteResponse | ApiErrorResponse> | POST 删除远程 SFTP 文件、符号链接或目录树 |
backend:sftp-batch-operation | invoke | sessionId: string, payload: ApiSftpBatchOperationRequest | Promise<ApiSftpBatchOperationResponse | ApiErrorResponse> | POST 对多个 SFTP 条目执行有序批量复制、移动、创建链接或删除 |
backend:sftp-start-task | invoke | sessionId: string, payload: ApiSftpStartTaskRequest | Promise<ApiSftpStartTaskResponse | ApiErrorResponse> | POST 启动一个异步 SFTP 任务;下载任务转发前必须消费精确匹配 owner/path/transferId 的授权 |
backend:sftp-list-tasks | invoke | sessionId: string | Promise<ApiSftpListTasksResponse | ApiErrorResponse> | GET 保留的任务快照,并释放属于调用 renderer 的终态下载授权租约 |
backend:sftp-get-task | invoke | sessionId: string, taskId: string | Promise<ApiSftpGetTaskResponse | ApiErrorResponse> | 使用任务接纳时的 session id 获取一个保留快照;观察到下载终态时释放绑定 owner 的授权租约 |
backend:sftp-get-archive-capabilities | invoke | sessionId: string | Promise<ApiSftpArchiveCapabilitiesResponse | ApiErrorResponse> | GET 获取并缓存一个 SFTP 会话的固定远端归档工具能力矩阵 |
backend:sftp-start-archive-operation | invoke | sessionId: string, payload: ApiSftpArchiveOperationRequest | Promise<ApiSftpArchiveOperationAcceptedResponse | ApiErrorResponse> | POST 提交结构化压缩或解压请求;Main 不接受也不构造命令文本 |
backend:sftp-get-archive-operation | invoke | sessionId: string, operationId: string | Promise<ApiSftpArchiveOperationStatusResponse | ApiErrorResponse> | GET 获取归档任务的保留状态、具名阶段(包括解压后的verifying)、冲突摘要、取消状态和稳定终态结果 |
backend:sftp-resolve-archive-conflict | invoke | sessionId: string, operationId: string, payload: ApiSftpArchiveConflictResolutionRequest | Promise<ApiSftpArchiveConflictResolutionResponse | ApiErrorResponse> | POST 为暂存解压冲突提交任务级overwrite、keep-both或cancel决定 |
backend:sftp-cancel-archive-operation | invoke | sessionId: string, operationId: string | Promise<ApiSftpArchiveCancelResponse | ApiErrorResponse> | DELETE 请求有界取消;通过归档任务轮询观察最终状态 |
backend:sftp-close-session | invoke | sessionId: string | Promise<{ success: boolean }> | DELETE SFTP 会话 |
backend:local-terminal-list-profiles | invoke | none | Promise<ApiLocalTerminalListProfilesResponse | ApiErrorResponse> | GET local terminal profile list |
backend:local-terminal-create-session | invoke | payload: ApiLocalTerminalCreateSessionRequest | Promise<ApiLocalTerminalCreateSessionResponse | ApiErrorResponse> | POST 本地终端会话(Main 可能注入一次性 cwd 上下文) |
backend:local-terminal-close-session | invoke | sessionId: string | Promise<{ success: boolean }> | DELETE local terminal session |
3. Schema 来源
- API payload 类型来自
@cosmosh/api-contract,并由packages/api-contract/openapi/cosmosh.openapi.yaml生成。 - Backend、Main IPC 代理与 renderer HTTP 调用端必须通过
@cosmosh/api-contract中生成的API_PATHS及相关合同导出访问 API,不允许硬编码路由字符串。 - 归档 IPC 只接受生成的结构化路径、枚举和冲突决定。远端命令文本、flag、工具输出与暂存路径绝不跨越 preload 边界。
- 未由 OpenAPI 生成的 IPC-only payload(包括
AppMenuAction、SftpOpenWithApplication、SftpTemporaryFileWatchChange与BackendRequestTrace)定义在packages/api-contract/src/ipc.ts,供 main、preload 与 renderer 类型声明共同消费。 - 终端 WebSocket payload 与远端增强协议常量定义在
packages/api-contract/src/terminal-protocol.ts;backend 与 renderer 直接导入其中的 discriminated union。 BackendRequestTrace仅用于开发诊断。它会在未打包开发运行中由 main-process backend proxy 填充;生产包不会采集 trace,也不会加载 DevTools extension。
3.1 SSH 视觉元数据字段
以下 SSH 实体相关载荷已包含用于持久化图标/配色自定义的视觉字段:
ApiSshCreateServerRequest/ApiSshUpdateServerRequest:可选iconKey、可选colorKey。ApiSshCreateFolderRequest/ApiSshUpdateFolderRequest:可选iconKey、可选colorKey。ApiSshListServersResponse:每个服务器条目包含iconKey与colorKey。ApiSshListFoldersResponse:每个文件夹条目包含iconKey与colorKey。
colorKey 受 API 契约中的预设配色枚举约束。
当前 SSH 安全策略相关字段:
ApiSshCreateServerRequest/ApiSshUpdateServerRequest:主机/transport 与 renderer 策略字段包括strictHostKey、enableSshCompression、remoteEnhancementsEnabled、disableCharacterWidthCompatibilityMode、terminalClipboardAccess与 proxy policy。ApiSshListServersResponse:每个 server 条目都必须包含持久化的strictHostKey、enableSshCompression、remoteEnhancementsEnabled、disableCharacterWidthCompatibilityMode、terminalClipboardAccess与proxyMode;响应缺少策略时,消费端不得通过本地默认值 fail open。ApiSshCreateSessionRequest:可选strictHostKey、enableSshCompression与remoteEnhancementsEnabled会把单次尝试绑定到已解析快照。远端增强请求字段只能进一步关闭;最终权限为当前全局设置、持久化服务器字段与request !== false。- 字符宽度兼容模式不会传入 SSH session create 或终端 WS 消息;renderer 会在创建 xterm 实例时应用该规则。
3.2 SSH 端口转发契约
端口转发 payload 由 OpenAPI 源生成,并被 backend、main、preload 与 renderer wrapper 共同消费:
ApiPortForwardListRulesResponseApiPortForwardCreateRuleRequest/ApiPortForwardCreateRuleResponseApiPortForwardUpdateRuleRequest/ApiPortForwardUpdateRuleResponseApiPortForwardStartRuleResponseApiPortForwardStopRuleResponse
规则类型为 local、remote 或 dynamic。
类型专属字段:
- Local:
localBindHost、localBindPort、targetHost、targetPort - Remote:
remoteBindHost、remoteBindPort、targetHost、targetPort - Dynamic:
localBindHost、localBindPort
运行状态通过 runtime.status 返回,但不会持久化。Start 可能返回 SSH_HOST_UNTRUSTED;renderer 必须先通过 backend:ssh-trust-fingerprint 信任指纹,然后再重试。
3.3 SFTP 批量操作契约
SFTP batch payload 由 OpenAPI 生成,并由 renderer、main IPC proxy 与 backend routes 原样共用。
ApiSftpBatchOperationRequest.operation可取copy、move、link或delete。targetDirectoryPath对copy、move与link必填;对delete会被忽略。link会在目标目录中创建指向源远程绝对路径的绝对符号链接。目标名称使用源 basename,并沿用复制操作的冲突后缀策略。- 响应形状保持为
ApiSftpBatchOperationResponse:按顺序返回每个条目的结果、completed/failed/skipped 计数、fail-fast 执行,并且不回滚已经完成的条目。
3.4 终端 WebSocket 契约(Renderer ↔ Backend)
终端流式消息虽然不属于 Electron IPC channel,但同样属于跨进程契约面。terminal-protocol.ts 是唯一来源,当前远端 helper 协议版本为 2。
- 客户端到服务端(
/ws/ssh/{sessionId}与/ws/local-terminal/{sessionId}):input、resize、ping、close、history-deletecompletion-request,包含requestId、linePrefix、cursorIndex、可选workingDirectoryHint、可选limit、可选fuzzyMatch、可选来源过滤字段(includeHistory、includeBuiltInCommands、includePathSuggestions、includePasswordSuggestions)、trigger(typing或manual)
- 服务端到客户端:
ready、output、telemetry、history、pong、error、exitcompletion-response,包含requestId、replacePrefixLength与排序后的候选itemsbootstrap-status,用于侧通道 Remote Bootstrap 安装/探测状态remote-enhancement-runtime-status,包含 backend 持有的状态(pending、active或disabled),以及可选helperVersion、protocolVersion、capabilities、code与messageremote-shell-event,用于已安装 helper 通过 OSC 777 发出的运行期 shell 状态;每条事件都必须包含helperVersion、整数protocolVersion、capabilities、shell、event与timestamp
远端 shell event union 规则:
cwd必须包含解码后的绝对cwd,并声明cwdcapability。command-start与foreground-command必须包含清洗后的command及commandId;command-end还必须包含整数exitCode与durationMs。line-state必须包含lineLength、cursorIndex与promptGeneration,不携带输入文本,目前仅由 Zsh 声明。- Sh/Ash 只声明
cwd与prompt-ready。事件名与必填字段不符合交互 shell 打开前的精确 capability 契约时一律拒绝。
补全候选契约说明:
items[].source目前包含history、inshellisense与运行时计算来源runtime。items[].kind在原有命令规范/历史分类之外,新增运行时分类(path、secret)。- 运行时分类用于路径候选与交互式密钥填充动作,但仍复用相同的
completion-response外层结构。
当前实现说明:
- 补全消息在
SshSessionService与LocalTerminalSessionService中处理,输入规范化由terminal/shared.ts统一,排序引擎由terminal/completion/engine.ts共享。 remote-enhancement-runtime-status与remote-shell-event仅用于 SSH session,不会出现在 local-terminal session。交互 shell 打开前的 Bootstrap ensure 成功后,运行时以pending开始;10 秒内到达且匹配的integration-ready会切换为active。Ensure 失败、BOOTSTRAP_ENSURE_TIMEOUT、HELPER_HANDSHAKE_TIMEOUT或 live 契约不匹配都会切换为disabled。Renderer 会把最新运行状态与有界事件历史分别保存,并且只能将其用于诊断,不得据此重建 backend helper 状态。remote-shell-event不得承载密码、secret、完整终端输出、完整命令行、line-buffer 内容或任意大数据。Helper 的动态 cwd/command 字段在 OSC JSON envelope 内使用规范 Base64,解码并校验后才会转发。Backend 将解码后的 OSC payload 限制在 8 KiB,剥离 Cosmosh OSC,并原样流式透传非 Cosmosh OSC。- Renderer 会把完整 server message union 路由到来源 pane 的 runtime/reducer。补全响应、密码提示、状态、telemetry、错误、退出、调试事件、重连与命令 marker 都不得隐式回退到 primary/active pane 状态。
3.5 Main 持有的活动连接契约
关闭守卫将活动状态权威性保留在带鉴权的 Main 到 Backend HTTP 调用中,只向 renderer 暴露窄化的确认握手:
GET /api/v1/runtime/active-connections从 backend 会话注册表返回sshCount、sftpCount及二者之和totalCount。DELETE /api/v1/runtime/active-connections关闭当前所有已注册 SSH/SFTP 会话,并返回本次关闭的数量。- Electron Main 模式下两个 HTTP 操作都要求内部 token。它们不会通过
preload.ts暴露,因此 renderer 既不能批量断开会话,也不能向守卫提供活动计数。 app:close-window保持现有 fire-and-forget 签名。守卫挂在主BrowserWindow的关闭生命周期上,因此标题栏关闭、最后标签页关闭、macOS close role 与应用退出共享同一权威判断。- 仅当权威探测要求确认时,Main 才发送
app:close-confirmation-request。Renderer 使用共享Dialog展示警告,再返回app:close-confirmation-response;Main 只接受来自所属webContents且不透明 request ID 匹配的响应。
4. 变更规则
当新增/修改 channel 时,必须在一个变更中同步更新:
packages/main/src/preload.tspackages/main/src/index.tspackages/renderer/src/vite-env.d.ts- 相关 renderer transport/service 封装
- 本文件(
docs/zh-CN/developer/core/ipc-protocol.md)
5. Channel 新增模板
新增 channel 时建议按以下清单执行:
- Channel 命名:
domain:action-name - IPC 类型:
invoke或send - 参数 schema:在 bridge 与 renderer 声明中显式类型化
- 返回 schema:成功与错误结构
- Main 行为:后端代理或本地特权动作
- 安全说明:token/header 处理、权限边界、暴露范围
- 文档同步:同一变更集更新中英文协议文档
6. 服务器代理契约
ApiSshCreateServerRequest/ApiSshUpdateServerRequest可携带proxyMode = default | off | custom与可选proxyUrl。ApiSshListServersResponse返回持久化代理模式和 URL,用于编辑与连接规划。ApiSshCreateSessionRequest、ApiSftpCreateSessionRequest与ApiPortForwardStartRuleRequest可携带临时systemProxyRules;该字段永不持久化。SystemProxyResolveRequest与SystemProxyResolveResult是packages/api-contract/src/ipc.ts中的 IPC-only 类型。- Main 根据已校验 host/port 构造解析 URL,Renderer 不能向
Session.resolveProxy提交任意 URL。