
Reasonix Desktop Electron 壳层深度解析Go 服务监督、NDJSON RPC 协议与多安全边界设计【免费下载链接】DeepSeek-ReasonixDeepSeek-native AI coding agent for your terminal. Engineered around prefix-cache stability — leave it running.项目地址: https://gitcode.com/GitHub_Trending/de/DeepSeek-ReasonixReasonix Desktop 的桌面应用采用Electron 壳层 Go 桌面服务的双进程架构Electron 进程只负责承载 React 界面并监督 Go 服务进程的生命周期全部业务逻辑都在 Go 侧实现。本文以 desktop/electron/README.md 为骨架结合 desktop/electron/src 下的源码实现完整讲解壳层的模块划分、构建与运行方式、环境变量、浏览器表面Browser surface、硬件加速恢复以及九条安全边界帮助你理解并上手开发、调试与扩展这一壳层。一、整体架构壳层只做承载与监督壳层进程Electron与 Go 服务进程之间的数据流可以用下面这条链路概括renderer (reasonix://app) ──preload (window.reasonixDesktop)──▶ main process ──NDJSON JSON-RPC over stdio──▶ reasonix-desktop --host-rpcrenderer加载reasonix://app上的 React 前端即 desktop/frontend 构建出的产物preload通过contextBridge暴露唯一一个桥接对象window.reasonixDesktop见 desktop/electron/src/preload/index.tsmain processElectron 主进程负责窗口管理、协议注册、IPC 白名单与 Go 服务子进程的监督Go service以--host-rpc参数启动的reasonix-desktop二进制通过标准输入输出上的NDJSON JSON-RPC 2.0与壳层通信。壳层与 Go 服务之间的完整线上协议wire contract定义在 docs/DESKTOP_HOST_PROTOCOL.md。该文档是双方唯一的契约壳层只实现其中的 shell 侧业务逻辑一律留在 GoUI 一律留在desktop/frontend这是本包设计上最核心的约束。二、模块布局主进程的每一块职责壳层主进程desktop/electron/src/main/按单一职责拆分成多个模块下表继承自原文档并补充了各模块对应的源码路径路径职责src/main/index.ts引导数据主目录解析、单实例锁、特权 scheme 注册、全部模块装配src/main/service.tsGo 服务监督器spawn、stderr 落盘、重启预算、优雅关停src/main/rpc.tsNDJSON JSON-RPC 2.0 客户端64 MiB 帧上限、超时、反向请求src/main/handshake.tsdesktop/hello参数构造、结果校验、失败描述src/main/window.ts主BrowserWindow、host/window.*、关闭与崩溃处理src/main/protocol.tsreasonix://app文件服务与资源源resource origin转发src/main/ipc.ts渲染进程 IPC发送者校验、契约白名单、原生调用src/main/hostCalls.tshost/*分发表src/main/lifecycle.ts退出序列beforeClose→shutdown→ stdin 关闭 → 退出src/main/menu.ts、tray.ts、dialogs.ts、remoteWindows.ts原生界面面src/main/browser/应用内浏览器网站视图、快照、动作、下载、授权src/preload/index.ts唯一的window.reasonixDesktop对象src/shared/ipc.ts主进程与 preload 共享的 IPC 通道名与类型2.1 引导流程index.tssrc/main/index.ts 是整条引导链的入口关键步骤包括app.setName(Reasonix)并解析REASONIX_DEV环境变量判定开发模式通过reasonixHome()解析数据主目录解析失败直接app.exit(1)调用claimShellInstance()抢占单实例锁第二个实例会触发second-instance事件聚焦已有窗口读取graphics.json决定是否app.disableHardwareAcceleration()详见硬件加速一节protocol.registerSchemesAsPrivileged注册reasonix:scheme启用standard、secure、supportFetchAPI、stream特权corsEnabled: false加载desktopContract.json契约加载失败时退化为空契约——之后所有desktop/invoke都会被拒绝组装MainWindow、ServiceSupervisor、BrowserSurfaceManager、GrantRegistry、QuitSequencer等核心对象app.whenReady()后注册reasonix://app协议处理器、权限处理器、渲染进程 IPC 与菜单最后service.start()。值得注意的细节主窗口在dom-ready时会向服务发送desktop/domReady与desktop/rendererAttached服务重启后如果渲染进程仍然存活会走reattachApp()而不是整页 reload——注释明确说明 reload 会丢失未发送的编辑器草稿desktop:resync只修复读取侧状态。2.2 Go 服务监督器service.tssrc/main/service.ts 中的ServiceSupervisor是壳层最核心的运维组件负责spawn以[--host-rpc]启动 Go 服务stdio三管道全开windowsHide: true握手启动后先请求desktop/hello校验通过后发送desktop/start完成就绪phase状态机为starting → readystdout 解析child.stdout的每个 chunk 喂给RpcClient.feed()stderr写入service.log非打包环境同时回显到终端重启预算意外退出后由 restartBudget.ts 决定是否自动重启预算为5 分钟内最多 3 次RESTART_BUDGET_MAX 3窗口5 * 60 * 1000ms耗尽后进入failed状态并展示失败页优雅关停shutdown()先请求desktop/shutdown10 秒超时然后关闭 stdin等待退出最多 5 秒EXIT_GRACE_MS仍不退则SIGKILL事件序列desktop/event通知按seq递增去重、丢弃过期 generation 的事件并记录事件缺口见onNotification()。2.3 NDJSON JSON-RPC 2.0 客户端rpc.tssrc/main/rpc.ts 实现了精简的 RPC 客户端关键行为每行一个 JSON 帧LineDecoder以0x0a切行单帧上限MAX_FRAME_BYTES 64 * 1024 * 102464 MiB超限抛OversizeFrameError并整体关闭连接request()支持可选超时超时以RpcError(-32000, ...)拒绝请求 ID 自增notify()用于无响应通知如desktop/event双向能力serve()处理服务端发来的请求method id即壳层可以响应 Go 发起的反向请求例如host/*分发见 src/main/hostCalls.ts协议错误分类统计ignoredLines非 JSON / 非 2.0 / 非法 id与orphanResponses无匹配 pending 的响应。2.4 握手与失败描述handshake.tssrc/main/handshake.ts 定义了PROTOCOL_VERSION 1desktop/hello参数包含四组身份信息protocolVersion协议版本contractDigest命令契约摘要build{ version, channel, commit }取自resources/build.json开发环境默认devhost{ name: electron, version, chrome, platform, arch }instance{ home, dev }其中home与 Go 侧internal/config.ReasonixHomeDir的解析规则一致。握手的失败码具有明确语义HANDSHAKE_CODES失败页会据此给出对应标题错误码名称含义-32001protocol_mismatch服务端协议版本不同-32002not_ready服务未就绪-32003contract_mismatch壳层与服务命令契约不一致混合安装-32004build_mismatch壳层与服务是不同构建-32005instance_mismatch双方使用了不同的数据主目录validateHelloResult()对结果做严格字段校验非空字符串、有限数值、几何尺寸必须为正任何不合法都会以HandshakeError形式转化为失败页描述。三、浏览器表面网站视图、授权与任务接管壳层除了承载应用 UI还能在应用旁托管真实网站其契约见 docs/DESKTOP_BROWSER.md。每个标签页都是一个沙箱化的WebContentsView由 src/main/browser/surfaceManager.ts 统一管理。两条驱动路径用户面板React 面板通过reasonixDesktop.browser.*见 src/preload/index.ts 的browser对象驱动无需授权因为操作者就是用户本人AgentGo 服务通过host/browser.*主机调用src/main/browser/hostCalls.ts驱动必须持有随任务下发的授权grant且授权会随服务 generation 失效。3.1 模块职责表模块职责guestView.ts、electronGuestViews.ts注入接口背后的WebContentsView测试使用内存 fakesurfaceManager.ts标签页、布局/浮层可见性、接管take-over与崩溃恢复grants.ts、errors.ts每任务授权与-32010/-32011/-32012契约错误码snapshotScript.ts、snapshot.ts、pageScripts.ts序列化页面遍历器aria 风格快照、ref 解析/定位/选择documents.ts、refResolver.ts文档令牌一次导航或接管会使所有更早的 ref 失效actions.ts、keys.ts、upload.ts可信输入派发点击、键入、按键、滚动、选择、上传screenshot.ts元素/整页截图到任务 scratch 目录downloads.tswill-download路由、进度事件、按标签页等待guestPreload.ts网站视图 preload仅上报用户输入用于接管判定fakeGuestViews.ts内存视图使以上全部逻辑可在纯node --test下运行3.2 授权与错误码授权模型在 grants.ts 与 errors.ts 中定义错误码与语义错误码常量语义-32010BROWSER_ERR_STALE_REFERENCEref 已过期导航或接管后旧 ref 全部失效-32011BROWSER_ERR_TAKEN_OVER标签页处于人类模式被用户接管-32012BROWSER_ERR_NO_GRANT缺少授权或授权不匹配任务授权流程Go 先调用host/browser.grant为某个taskId安装授权携带grantId、sessionId后续所有host/browser.*调用都必须带grantId且每次调用都会重新校验授权与标签页归属grants.verifyTab。读取与写入类操作在标签页处于人类模式时一律拒绝-32011。host/browser.revoke会吊销授权并使该任务所有标签页的文档令牌失效。hostCalls.test.tssrc/main/browser/hostCalls.test.ts验证了这些边界无授权调用tabs.list报-32012、跨任务访问报-32012、接管状态下 snapshot/navigate/screenshot 报-32011、无效 documentToken 与 revoke 后调用act均被拒绝。3.3 接管take-over与下载用户在网站视图中的输入mousedown、keydown、wheel、touchstart、pointerdown五类事件见 index.ts 的TAKEOVER_KINDS会把标签页翻转为人类模式提升其 epoch并向 Go 上报browser.takeover事件host/browser.resume或用户面板的browser.resume交还控制权。Agent 派发的输入带有标记其回显不会触发接管。BrowserTabView中的modeagent | human与epoch字段完整暴露了这一状态见 src/shared/ipc.ts。下载行为当browser.act/browser.screenshot调用注册了任务 scratch 目录时下载落入该目录否则落入userData/downloads/taskId。渲染进程通过reasonixDesktop.browser.onDownload接收BrowserDownloadView进度事件状态包括progressing / completed / cancelled / interrupted。四、硬件加速恢复桌面 UI 暴露Settings → General → System → Hardware acceleration开关。该偏好存储在 Electron 壳层 profile 的graphics.json即userData/graphics.json见 graphics.ts只在完全重启应用后生效。如果在设置页打开之前渲染就已失败请完全退出 Reasonix 并用环境变量启动一次REASONIX_DISABLE_GPU1 reasonix这是临时覆盖不会改动已保存的偏好Windows、macOS、Linux 均支持。实现上loadGraphicsBootstrap()同时识别两种覆盖源环境变量REASONIX_DISABLE_GPU 1与命令行参数--disable-gpu并记录override为environment或command-line。startupEnabled在有覆盖时为false禁用 GPU无覆盖时取已保存的hardwareAcceleration值。状态字段还包括restartRequired保存值与本次启动值不一致与warning配置损坏提示invalid-config、unreadable-config、unsupported-version。保存时使用临时文件 rename的原子写并串行化写队列损坏的旧配置会被备份为graphics.json.invalid。五、构建从工作区根目录到可分发产物5.1 前置依赖Node 24、pnpm 10、Go首次在desktop下执行一次安装cd desktop pnpm installpnpm install会一并下载 Electron 二进制allowBuilds: electron配置在 desktop/pnpm-workspace.yaml。若之后发现node_modules/electron/dist缺失可在desktop/electron目录执行node node_modules/electron/install.js5.2 四步构建cd desktop go build -o build/bin/reasonix-desktop-service . # 接受 --host-rpc 的 Go 服务 go run . -emit-contract frontend/src/generated # 生成 desktopContract.generated.{ts,json} pnpm --filter reasonix-desktop-frontend build # 构建 frontend/dist pnpm --filter reasonix-desktop-shell build # 构建 electron/dist/{main,preload}.cjs desktopContract.json壳层构建desktop/electron/scripts/build.mjs会读取frontend/src/generated/desktopContract.generated.json按hostrpc.Contract.Canonical定义的方式重算摘要键排序、紧凑序列化、不做 HTML 转义与生成器发出的DESKTOP_CONTRACT_DIGEST比对并把契约与digest写入dist/desktopContract.json。契约缺失会直接构建失败如需强行构建可设REASONIX_ELECTRON_ALLOW_MISSING_CONTRACT1——此时所有desktop/invoke都会被拒绝hello 摘要为空运行时loadContract()contract.ts要求契约必须有非空digest且至少列出一个命令否则按空契约处理。5.3 打包身份与版本语义打包后的壳层从resources/build.json读取完整版本标签、channel 与 commit用于desktop/hello。app.getVersion()与package.json.version是数值型原生元数据不能用于标识 RPC 构建。打包环境的启动冒烟测试不带任何开发覆盖运行并要求渲染进程的Version命令与该 manifest 一致CI 使用的服务也必须以相同的非开发版本链接。六、运行与开发模式6.1 常规启动cd desktop/electron pnpm start # electron . 使用 ../build/bin/reasonix-desktop-service REASONIX_DESKTOP_SERVICE/path/to/binary pnpm start6.2 对接 Vite 开发服务器cd desktop/frontend pnpm dev # http://127.0.0.1:5173 cd desktop/electron pnpm dev # REASONIX_DEV1加载 REASONIX_ELECTRON_DEV_URL开发模式下REASONIX_DEV1会跳过单实例锁并将实例标记为dev壳层改从REASONIX_ELECTRON_DEV_URL加载 UI。6.3 环境变量一览变量作用REASONIX_DESKTOP_SERVICEGo 服务二进制路径打包默认resources/service/reasonix-desktop[.exe]REASONIX_HOME数据主目录解析规则与internal/config.ReasonixHomeDir完全一致并写入hello.instance.homeREASONIX_DEV跳过单实例锁并把实例标记为devREASONIX_ELECTRON_DEV_URL加载该 URL 替代reasonix://app/index.htmlREASONIX_FRONTEND_DIST覆盖reasonix://app/服务的目录REASONIX_CHANNEL、REASONIX_COMMIThello.build中的构建身份默认devREASONIX_DISABLE_GPU置为1临时禁用 GPU不影响已保存偏好6.4 日志日志位于home/desktop-shell/logs/shell.log主进程与service.logGo 服务 stderr各自5 MB 轮转RotatingFile见 src/main/log.ts。开发模式下两者同时回显到终端。七、验证pnpm typecheck # main preload 两个 tsconfig 类型检查 pnpm test # node --test仅纯模块Electron 通过接口注入测试策略非常明确src/main/browser/fakeGuestViews.ts提供内存视图src/main/browser/*.test.ts在纯node --test环境验证快照、动作、授权、下载、布局等全部逻辑package.json中test脚本为node --import tsx --test src/**/*.test.ts。另有smoke脚本node scripts/smoke.mjs用于打包冒烟。八、安全边界九条硬约束以下约束全部可在源码中得到印证是壳层对抗注入与越权的核心设计主窗口沙箱sandbox: true、contextIsolation: true、nodeIntegration: false、spellcheck: false只加载reasonix://app见 window.ts 的webPreferences。所有离开应用源的导航被will-navigate阻止并记日志setWindowOpenHandler一律deny弹窗will-attach-webview一律preventDefault拒绝webview。preload 只暴露一个对象window.reasonixDesktop形状严格对应协议文档中的ReasonixDesktopHost。所有 IPC 应答都是{ ok, value }信封src/shared/ipc.ts 的IpcResult因此 Go 的错误会以Error(Go message)到达渲染进程不带 Electron 前缀。IPC 发送者校验ipcMain处理器只接受主窗口顶层 frame 的调用event.sender与event.senderFrame双重校验isTrustedSender其他发送者一律拒绝并告警。命令契约白名单desktop/invoke的方法名先经 contract.ts 的isAllowedCommand()校验未知方法以-32601失败不会触达 Go。reasonix://app文件服务只严格服务 frontend dist 下的文件——拒绝..、拒绝绝对路径逃逸、除/外无目录索引回退见 protocol.ts 的routeAppRequest对路径遍历、NUL/反斜杠、越界路径分别给出明确 404 原因。仅三个资源前缀被转发到回环源/__reasonix_workspace_media/、/__reasonix_theme_asset/、/__reasonix_remote_markdown_image且 Bearer token 只在主进程附加永不进入任何渲染进程。Remote Serve 窗口使用独立的persist:remote-hostKey会话无 preload、sandbox 开启、弹窗拒绝、导航锁定在页面源。网站视图沙箱位于persist:browser分区的沙箱化WebContentsView临时标签页用temp:id分区preload 只上报用户输入。host/browser.*需要限定单任务、单服务 generation 的授权读写均拒绝人类模式下的标签页。外链白名单渲染进程触发的shell.openExternal只接受http:、https:、mailto:ipc.ts 的isOpenableExternalURL。服务崩溃恢复意外退出后自动重启至多每 5 分钟 3 次restartBudget.ts之后失败页提供手动重启、打开日志目录与退出三个动作failurePage.ts 与 index.ts 的onShellAction没有任何 mock 兜底。九、退出序列与进程生命周期lifecycle.ts 的QuitSequencer负责把 Electron 的before-quit事件驱动成严格一次的两段式关停beforeClose先向 Go 请求desktop/beforeCloseGo可以否决返回prevent: true窗口隐藏而不是退出请求失败时照常退出shutdown调用desktop/shutdown→ 关闭服务 stdin → 等待进程退出超时后SIGKILL对应ServiceSupervisor.shutdown()的完整路径关停顺序上有明确注释网站视图先销毁因为窗口消失后再关闭其 WebContents正是原型期遗留孤立渲染进程的根因随后主窗口放行关闭、Remote 窗口关闭、托盘销毁会话结束与脚本化关停会投递SIGTERM壳层统一走菜单同一条退出序列确保 Go 在进程结束前完成会话快照。十、当前状态与演进边界按 desktop/electron/README.md 的说明electron-builder 打包尚未纳入本包Packaging is intentionally not part of this package yet当前打包相关逻辑散见于仓库的desktop/packaging/与desktop/cmd的打包工具中。这意味着本包现阶段聚焦于壳层运行时与协议实现分发链路仍在独立演进。如果你准备为壳层贡献代码最稳妥的切入点是从 src/main/browser/ 的纯模块与对应测试开始——它们不依赖真实 Electron 环境可在pnpm typecheck pnpm test下快速闭环涉及主进程与 Go 服务交互的改动则务必同步更新 docs/DESKTOP_HOST_PROTOCOL.md 与 docs/DESKTOP_BROWSER.md 两份契约文档。【免费下载链接】DeepSeek-ReasonixDeepSeek-native AI coding agent for your terminal. Engineered around prefix-cache stability — leave it running.项目地址: https://gitcode.com/GitHub_Trending/de/DeepSeek-Reasonix创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考