本页目录(10 节)

桌面端架构

Spark Work 桌面端架构总览:Electron 主进程 / 渲染进程 / Preload / 类型化 IPC 桥、Agent Runtime 真实服务清单、本地 SQLite 与文件目录、凭据存储(keystore / 加密 vault)与三种执行引擎的载具差异。

阅读约 16 分钟最后核对 2026-09-18

本文按真实目录与真实模块名讲清 SparkWork 桌面端的结构:5 类进程、3 个执行内核、 主进程本地服务清单、SQLite/文件/凭据三处存储、459 个类型化 IPC 通道与 41 个 stream 推送通道, 以及一次画布文生图的完整链路。文中出现的服务名与文件名都可以在仓库里直接搜到; 旧版本页面里写过的 agent.service、workflow.service、audit.service、rules-engine、SQLCipher 在当前代码里不存在,已按实际实现改写。

1. 进程模型与代码位置

进程/角色代码位置职责
Main 进程apps/desktop/src/main/(入口 index.ts)窗口管理、IPC handler、数据库、本地服务、更新、远程连接、媒体任务路由;构建产物还有两个 worker 入口(workers/background-maintenance.worker.ts、workers/depth-inference.worker.ts)
Preloadapps/desktop/src/preload/index.ts用 contextBridge 暴露受限的 window.spark:invoke、on、平台信息、文件拖放路径等;不暴露任何原始 Node/Electron API
Rendererapps/desktop/src/renderer/(单入口 index.html)React 19 + electron-vite 构建;主窗口、画布窗口(CanvasWindowApp.tsx)与内置浏览器窗口(BrowserWindowApp.tsx)共用同一份 bundle,靠 URL 参数区分,例如画布窗口是 ?window=canvas&projectId=...
Agent 子进程/运行时见第 6 节Claude Code CLI 子进程(每次 SDK query 一个)、Codex 载具进程(可选常驻 App Server,空闲 2 分钟回收)、spark-engine 进程内 SDK
受管 MCP 子进程apps/desktop/src/main/services/PlaywrightMcpRegistration.ts、BrowserAutomationMcpRuntime.ts例如 @playwright/mcp 通过独立 standalone Node 执行(打包时复制到真实文件系统,因为外部 Node 读不到 app.asar)

窗口安全基线写死在主窗口创建处:contextIsolation: true、nodeIntegration: false、sandbox: true,preload 指向 out/preload/index.js。

仓库是 pnpm monorepo(nodeLinker: hoisted,为了 electron-builder 能完整收集生产依赖闭包):

  • apps/desktop:桌面端本体;apps/website:官网(含本文所在文档站)。
  • packages/:protocol(IPC 契约与枚举的唯一来源)、agent-runtime(服务层与执行器)、storage(SQLite 与 migration SQL)、shared(keystore、 日志、工具函数)、tool-sdk、plugin-sdk、ui-kit。
  • spark-engine:自研引擎(进程内 @spark/agent SDK),独立于 packages/* 命名空间,作为第三个执行器被主进程 bundle 打包进去。

2. Agent Runtime 服务层(真实模块)

核心服务在 packages/agent-runtime/src/services/。会话相关的大文件已拆成 services/session/ 子目录(executor 选择、用量增量、检查点、事件定序等)。 下表是最常被问到的模块,路径均为真实文件:

模块真实文件职责
SessionServicesession.service.ts + session/*会话生命周期、turn 调度、系统提示词组装(项目上下文 + Runtime Rules + Skill + MCP)
引擎注册表session/engine-registry.ts按 adapter 选择执行器并声明能力(原生 resume / 权限热切换 / checkpoint 回滚 / 子代理工具)
ProviderServiceprovider.service.tsProvider CRUD、API Key 经 keystore 解析、健康检查
ModelServicemodel.service.ts、model-router.service.ts模型清单与路由
PermissionServicepermission.service.ts动作类目映射、Profile 规则判定、审批等待(超时 30 分钟)
RulesService / RuleCompositionEnginerules.service.ts、rule-composition.engine.ts规则 CRUD(system 规则只读)与 compose(仅经 rules:compose IPC 暴露)
Hook 服务hook.service.ts、services/hooks/*Hooks V2:事件发射、绑定解析、派发、worker、补偿、旧配置迁移
Skill 服务skill.service.ts、skill-registry/*、local-skill-importer.ts技能元信息、安装、加载与本地导入
MCP 服务mcp-server.service.tsMCP Server 注册表(managed / user)
项目上下文project-context.service.ts读取工作区 AGENTS.md/CLAUDE.md 与上下文预算
运行时上下文组装runtime-composition.service.tsSkill / 环境变量 / 工具等运行时上下文合成
用量账本usage-ledger.service.ts、session/session-usage-ledger.tsper-turn token 增量入账与查询
Team 分派team-dispatch.service.ts、team-*.tsTeam Mode 分派、成员执行生命周期、ledger / task graph / replay playbook 适配器
定时任务scheduled-task.service.ts任务调度与执行器对接
媒体media/media-router.service.ts、media/media-task-runtime.service.ts按 manifest 选适配器、异步媒体任务生命周期与恢复
记忆memory/*长期记忆抽取、写入与检索
工具与包unified-tools.ts、tool-packages/*、custom-tools/*、plugins/*、plugin-runtime/*统一工具目录、工具包与插件运行时
Git / 工作区git-command.service.ts、git-worktree.service.ts、workspace.service.ts、checkpoint-git.service.tsGit 状态、worktree、checkpoint 回滚
SettingsServicesettings.service.tsapp_settings 分类键值读写

没有独立的 agent.service / workflow.service / mcp-registry /usage.service / audit.service / hooks-engine 这些文件;Agent、Workflow、Team 定义由 packages/storage 的仓库类直接读写, 审计类数据分散在各模块自己的表里。

3. 主进程本地服务清单

apps/desktop/src/main/services/ 下是「只有桌面端才需要」的能力(约 120 个文件,含测试)。 常用的真实文件名:

  • 窗口与浏览器:CanvasWindowService.ts、BrowserPanelWindowService.ts、BrowserPanelDevtoolsService.ts、InternalBrowserService.ts、SystemBrowserService.ts、HtmlViewerService.ts、BrowserBridgeServer.ts、PlaywrightMcpRegistration.ts、PlaywrightEnvironment.ts。
  • 协议与资源:SafeFileProtocol.ts(safe-file://)、CapabilityAssetProtocol.ts、PrivilegedProtocolSchemes.ts、ExternalUrlPolicy.ts、FontAssetService.ts。
  • 文件与工作区:FilePatchService.ts、FileWatcherService.ts、WorkspaceSearchService.ts、WorkspaceSearchGitignore.ts、GitRuntimeService.ts、TerminalService.ts(node-pty)。
  • 凭据与完整性:CredentialVaultPersistence.ts、SdkIntegrityService.ts、CodexRuntimeIntegrityService.ts、PlaywrightIntegrityService.ts、FfmpegIntegrityService.ts、VoiceIntegrityService.ts、DepthModelIntegrityService.ts。
  • 远程连接:RemoteConnectionService.ts(桥接运行时与命令目录)、QqBotGateway.ts(QQ 官方 WebSocket 网关)、qqProtocol.ts、telegramInboundMedia.ts / telegramOutboundMedia.ts /telegramTextFormatting.ts / telegramTurnFeedback.ts、feishuImageMedia.ts、qqImageMedia.ts、remoteImageMedia.ts、remoteSessionIsolation.ts、remoteTurnReply.ts。
  • 数据与维护:DatabaseBackupService.ts(迁移前恢复点)、ProductionDbInheritService.ts(开发实例继承安装版数据)、TempMediaFilesMaintenance.ts、background-maintenance-worker.ts、AccountSync/(账号同步适配器与本地仓库)。
  • 更新:UpdateService.ts + updaterCache.ts。
  • 可选能力与子应用:optional-capabilities/、SubApp*.ts(SubAppServiceManager、SubAppJobManager、SubAppNetworkGateway、SubAppFileStore、SubAppPackageRuntime、SubAppShareService、SubAppBrowserService)、PlatformModel/(平台托管模型与凭据)、computer-use/、depth-video/、media/。

4. 本地存储:SQLite、文件、凭据

① SQLite(业务数据)

  • 文件路径:{userData}/spark.db,开发与安装版共用同一路径策略;打开时启用 journal_mode = WAL、synchronous = NORMAL、foreign_keys = ON、temp_store = MEMORY、mmap_size = 256MB。
  • schema 由 packages/storage/migrations/*.sql 顺序执行(当前 101 个文件, 由 schema_migrations 表跟踪),迁移前会自动做恢复点备份。
  • 表覆盖 78 个(CREATE TABLE IF NOT EXISTS 去重计数),代表性的有:sessions、agent_events(会话时间线)、app_settings、agents、agent_teams、workflows、workflow_runs、skills、skill_registries、mcp_servers、provider_profiles、usage_ledger、rules、permission_profiles/permission_rules/permission_decisions、hook_definitions/hook_bindings/hook_events/hook_runs、media_generation_tasks、canvas_projects/canvas_snapshots、scheduled_tasks、plugin_runtime_audit。

② 文件系统

路径内容
{userData}/projects/项目工作目录(存储用量页单列)
{userData}/attachments/会话附件;attachments/remote/telegram/ 存 Telegram 入站图片
{userData}/.spark-artifacts/media/媒体产物默认根目录,按类型落 images/、audio/、videos/、text/
{userData}/logs/运行时日志(设置 → 本地日志可读、导出、清空)
{userData}/spark-agent-updater/<version>/更新包缓存,启动时只保留最近 2 个版本目录
~/.spark-agent/board-attachments/看板任务附件(safe-file:// 白名单内,其余 ~/.spark-agent 不放行)

③ 凭据

  • Provider API Key 走 packages/shared/src/keystore:唯一合法的 keytar 入口, service 名固定为 spark-agent,引用形如 <provider>-<profileId>。
  • macOS 上不再是「一个 Provider 一条 Keychain 项」:敏感凭据集中进一个 vault, 由 Electron safeStorage 加密后存到 {userData}/credential-vault-v1.enc(权限 0600);macOS Keychain 只作为首次导入来源。 Windows/Linux 仍走 keytar 的对应后端。
  • 注意:远程连接(Telegram/飞书/QQ)的 bot token 等凭据不在 vault 里, 而是以明文 JSON 存在 app_settings 的 remote-connections 分类下。

5. IPC 桥:类型化通道与事件流

  • 契约只有一处真源:packages/protocol/src/ipc/index.ts 的 IpcChannelMap(459 个请求/响应通道)与 IpcStreamChannelMap(41 个 main → renderer 推送通道)。IpcChannel 就是前者的 key 联合类型, preload 只能按这些名字调用。
  • 主进程一律用 typedIpcHandle(apps/desktop/src/main/ipc/typed-ipc.ts)注册, 返回值统一包成 IpcResult(ok/data 或 ok/error); preload 解包后成功返回 data、失败抛 SparkIpcError,所以渲染端可以直接 try/catch。
  • 命名约定:session:*、agent:*、workflow:*、provider:*、mcp:*、skill:*、canvas:*、remote:*、update:*、permission:*、rules:*、hook:*(经典)与 hookV2:*、usage:*、log:*、app:*、env:*。
  • 主进程的 IPC 注册被拆成 apps/desktop/src/main/ipc/register*.ts 一系列文件 (如 registerHooksV2Ipc.ts、registerSubAppIpc.ts、registerToolPackagesIpc.ts、registerComputerUseIpc.ts), 剩下的集中注册在 ipc/index.ts。
  • 常用 stream 通道:stream:session:agent-event(会话事件流,含 assistant 增量与工具事件)、stream:permission:approval-request / stream:permission:approval-resolved、stream:update:status(及 available/progress/downloaded)、stream:canvas:media-task、stream:skill:install-progress、stream:remote:changed、stream:sdk:integrity。

6. 执行内核与载具

适配器(SessionAgentAdapter)有四个取值:claude、claude-sdk、codex、spark;统一抽象为三个引擎 EngineKind:claude-sdk(claude 与 claude-sdk 都归它)、codex、spark。契约是 EngineExecutor: 每个 turn 新建实例、终态只经事件流表达、实例身份即闸门。

引擎真实实现进程形态能力声明
claude-sdksdk/claude-sdk-executor.ts每个 turn 一次 query(),底层是 Claude Code CLI 子进程(可指定 pathToClaudeCodeExecutable);取消时 close query + abort原生 resume、权限热切换、checkpoint 回滚、子代理工具
codexsdk/codex-app-server/*、codex-cli-executor.ts、codex-openai-executor.ts、codex-sdk-executor.ts三种载具:本地 CLI(useLocalConfig)、OpenAI 兼容 chat 载具、App Server 常驻进程(可在执行器间复用,空闲 120 秒回收;握手失败或图片附件场景回退 SDK 载具)有 supervisor 时原生 resume;不支持权限热切换/回滚/子代理
sparksdk/spark-engine/* + spark-engine/(@spark/agent SDK)进程内 SDK,无子进程原生 resume(事件重放续跑)、权限热切换

流式事件统一为 AgentEvent,主进程通过 stream:session:agent-event 推给渲染端(assistant 增量/完成、tool_use、tool_result、agent_status、agent_error、presented_files 等)。

原生模块(better-sqlite3、keytar、node-pty)必须编译到 Electron ABI: 打包脚本会先跑 pnpm run rebuild:native -- <arch>;该脚本内部在宿主架构与目标架构一致时,会用 Electron 真实加载一次(native:verify),不一致时跳过校验并给出警告。

7. 内置 MCP 与媒体运行时

内置 MCP server 的实现在 packages/agent-runtime/src/tools/*.mjs,注册名(也就是工具前缀)如下:

注册名实现文件用途
spark_imageimage-generation-mcp-server.mjs旧图片生成链路(兼容保留)
spark_mediamedia-generation-mcp-server.mjs统一多媒体:模型清单/描述、图片生成与编辑、语音合成、转写、视频生成与编辑、文件上传与任务查询
spark_searchweb-search-mcp-server.mjs联网搜索
spark_browserbrowser-automation-mcp-server.mjs内置浏览器自动化
spark_filespresent-files-mcp-server.mjs把生成的文件作为卡片呈现给用户
spark_uiquick-replies-mcp-server.mjs快捷回复按钮
spark_memory / spark_sessionspark-memory-mcp-server.mjs / spark-session-mcp-server.mjs长期记忆与会话自省
spark_canvasspark-canvas-mcp-server.mjs画布节点读写
spark_platform / spark_team / spark_plugins / spark_appplatform-management-mcp-server.mjs 等平台管理、团队分派、插件运行时、子应用
spark_debug / spark_computer / spark_verify / spark_tool_resultsdebug-mode-mcp-server.mjs 等调试、电脑操作、校验、超长工具结果回读

多媒体走 MediaRouterService:按 manifest 选择适配器,适配器 id 取自 MediaProviderKind(apimart、agnes、xai、openai-compatible、openai-images、google-generative-ai、bailian、volcengine-ark、volcengine-speech、kling、pixverse、minimax-hailuo、wan、happyhorse、omni),实现在 packages/agent-runtime/src/services/media/adapters/。 异步任务由 MediaTaskRuntimeService 持久化到 media_generation_tasks 表 (migration 029_media_generation_tasks.sql,另有 066_media_task_recovery.sql 支持恢复),产物写入 .spark-artifacts/media/<kind>。

渲染端要显示本地媒体/文档时走自定义协议 safe-file://(SafeFileProtocol.ts):绝对路径经 base64 编码进 URL, 主进程只放行白名单根目录——userData、系统临时目录、~/.spark-agent/board-attachments、已登记的 workspace 根目录、画布项目目录; 白名单外一律 403。

8. 一次画布文生图的真实链路

  1. 渲染端调用 canvas:task:create-media,带 operation、prompt、waitForCompletion: false、可选的 providerProfileId / manifestId / modelId / modelParams / outputDir。
  2. 主进程写入 media_generation_tasks 一行并立即返回 running 响应(runtimeTaskId、providerProfileId、model)。
  3. 渲染端在画布上先画出「任务节点」,用户可继续平移/缩放/拖拽。
  4. 主进程经 MediaRouterService 按 capability 选适配器与 manifest,走 MediaRequestCompiler 组装请求并调用 Provider API;长任务进入轮询。
  5. 产物下载/解码后落盘到 .spark-artifacts/media/images(或对应 kind 子目录), 并按需生成预览 data URL 或 safe-file:// 链接。
  6. 完成、失败或取消时,主进程通过 stream:canvas:media-task 推送任务状态, 载荷里的 projectId/clientTaskId 用于路由回对应画布。
  7. 渲染端写回画布(新增 image 节点与 lineage 边)。

9. 安全边界(可验证的部分)

  • 渲染进程沙箱:主窗口 contextIsolation: true、nodeIntegration: false、sandbox: true;能力只经 window.spark 暴露。
  • CSP 写在 renderer 的 index.html meta 里(default-src 'self'、script-src 'self' capability-asset:、img-src ... safe-file: spark-snapshot: https: http: blob:、media-src ...、frame-src 'self' safe-file: capability-asset:), 不是由主进程动态下发。
  • 内置浏览器窗口与主 UI 隔离:创建浏览器面板窗口时会删掉 webPreferences.preload/preloadURL,并强制 nodeIntegration: false(含 subframe/worker)、contextIsolation: true, 远程页面拿不到 IPC bridge。
  • 本地文件读取白名单:safe-file:// 只放行上文列出的根目录, 越界返回 403;工作区路径边界另有 isWithinWorkspace 检查。
  • 凭据:Provider Key 走 keystore(macOS 为 safeStorage 加密的 vault 文件); 远程连接凭据是 app_settings 里的明文 JSON,界面用的是普通文本输入框,已保存的值会明文显示,不做掩码。
  • 高风险操作走审批:由 PermissionService 按动作类目与 Profile 规则判定, 详见「权限与治理」主题。
  • 数据库不做透明加密:代码里没有 SQLCipher 或等价实现; 需要长期保密请依赖操作系统磁盘加密,或使用设置 → 存储与备份的迁移前恢复点做归档。

10. 排查与常见坑

  • 想知道某个功能走哪条 IPC:先在 packages/protocol/src/ipc/index.ts 搜通道名,再到 apps/desktop/src/main/ipc/ 搜 typedIpcHandle('通道名', 最后看渲染端的 window.spark.invoke。这条路径比全局搜字符串快得多。
  • 「设置页改了没用」:很多设置分两层持久化——localStorage(即时渲染)+app_settings(权威值)。只看 localStorage 会误判;例如更新相关的值在 updates/data 与 updates/lastChecked。
  • 媒体任务卡住:先看 media_generation_tasks 的行状态, 再看设置 → 本地日志里 canvas 范围的日志(画布任务日志有独立的 scope)。
  • 原生模块报 Cannot find module 或加载失败:几乎都是 ABI 不匹配, 跑 pnpm --filter @spark/desktop rebuild:native 后重试;打包相关请确认走的是build-mac-release.sh / build-win-release.sh(它们会在打包前重建原生模块)。
  • 换机器后要重新下模型/运行时:Codex native runtime、Playwright、 ffmpeg、depth 推理与 voice pack 都是可选能力包,基础安装包不带,入口在设置 → 完整性 / 浏览器自动化 / 电脑操作等相关页面。
  • 多窗口共用一个 bundle:画布窗口与浏览器窗口不是独立前端工程, 改 CanvasWindowApp.tsx 就等于改主 bundle,注意别把主窗口逻辑耦合进去。

速查表

进程
Main / Preload / Renderer(单 bundle 多窗口)/ 受管 MCP 子进程 / 执行引擎进程
执行内核
claude-sdk / codex(cli、openai-chat、app-server 三种载具)/ spark(进程内)
应用配置
app_settings(分类 + key 的 JSON 值)
数据库
{userData}/spark.db(WAL,101 个 migration SQL)
凭据
keystore(keytar service=spark-agent);macOS 为 safeStorage 加密的 credential-vault-v1.enc
IPC 规模
459 个请求/响应通道 + 41 个 stream 推送通道(packages/protocol/src/ipc/index.ts)
媒体产物
{userData}/.spark-artifacts/media/{images,audio,videos,text}
本地媒体协议
safe-file://(白名单根目录,越界 403)
更新缓存
{userData}/spark-agent-updater/<version>/,保留最近 2 个版本

常见问题

展开 6 条常见问题
  • Renderer 能直接访问 Node 吗?

    不能。主窗口以 contextIsolation: true、nodeIntegration: false、sandbox: true 创建,只有 preload 用 contextBridge 暴露的 window.spark(invoke / on / 平台信息)可用。

  • 每个 turn 都会新起一个子进程吗?

    Claude 路径是——每个 turn 一次 SDK query,底层就是 Claude Code CLI 子进程。Codex 不一定:App Server 载具是常驻进程,可在执行器之间复用,空闲 120 秒回收;本地 CLI / OpenAI chat 载具则是单次调用。Spark 引擎完全进程内。

  • 状态放在哪里跨 turn 共享?

    SQLite({userData}/spark.db,WAL 模式)存会话、事件、设置、用量、Hook 等业务数据;文件系统存项目、附件、媒体产物与日志;两者才是跨 turn 的共享状态,执行器实例本身不共享。

  • 数据库加密了吗?

    没有做透明加密,代码里不存在 SQLCipher 集成。{userData}/spark.db 是普通 SQLite 文件,依赖操作系统磁盘加密;应用只保证迁移前自动创建恢复点。

  • 画布里的产物是怎么显示出来的?

    media 产物默认落在 {userData}/.spark-artifacts/media/<kind>,渲染端用 safe-file://(绝对路径 base64 编码)加载,只放行白名单根目录(userData、临时目录、board-attachments、已登记 workspace、画布项目目录),白名单外返回 403。

  • 内置 MCP 服务有哪些?

    spark_image、spark_media、spark_search、spark_browser、spark_files、spark_ui、spark_memory、spark_session、spark_canvas、spark_platform、spark_team、spark_plugins、spark_app、spark_debug、spark_computer、spark_verify、spark_tool_results,实现都在 packages/agent-runtime/src/tools/*.mjs。