找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5838

积分

0

好友

752

主题
发表于 7 天前 | 查看: 3| 回复: 0

自从写完 DSH:DeepSeek Harness 架构解析 后,就想亲身体验一番。终于,经过这几天开发和比预想更曲折的跨平台打包,Minke v0.1.0 [1] 发布了。

Minke 是一个面向 DeepSeek Harness [2] 的原生桌面工作台。它把对话、项目文件、终端、Web 工具、本地模型和桌面操作放在同一个工作空间里,希望减少 Agent 工作过程中不断切换应用和上下文的成本。

注意:Minke 并未做老数据迁移,这部分交给 AI 都可以处理,在项目 README.md 中有写数据存储位置。

Minke 桌面应用主界面

Minke 会话与 Hacker News 集成面板

Minke 开发环境与 VS Code 编辑界面

Minke 由来

GitHub:https://github.com/lencx/Minke

Minke 是小须鲸的英文名。根据国际捕鲸委员会的介绍,小须鲸是体型最小、身形最流线的须鲸之一,天生适合快速游动。DeepSeek 的蓝鲸 Logo 为这个名字提供了另一层联系:蓝鲸代表模型与 Harness 向深处探索的力量,Minke 更靠近用户桌面,在本地的对话、文件、终端和浏览器之间灵活穿行。“小”强调贴近个人的产品角色。

注:Minke 是一个独立的社区项目,并非 DeepSeek 官方产品。

这篇文章记录 Minke v0.1.0 开发中的几个关键问题:

  • 为什么最初选择 Tauri,最后又迁移到了 Electron;
  • Minke 如何从桌面壳走向独立产品层;
  • DSH 和 Cordis 给产品开发带来了什么;
  • 一个 Harness 从源码运行到进入安装包,中间还缺多少工程;
  • 这次 macOS、Windows 和 Linux 发布过程中,我踩了哪些坑;
  • 做完 v0.1.0 后,我对 Agent 产品工程有了哪些新理解。

Agent 的行动空间

假设你对 Agent 说:“帮我排查这个打包错误。”

只待在对话框里的 Agent 会请你粘贴日志、猜测原因,再给出几条命令让你代为执行。你跑完命令,把结果贴回来,它再继续猜。这层中转把模型隔在问题现场之外。

获得文件、终端和浏览器访问能力后,Agent 可以查看项目、运行构建、观察输出、查阅资料、修改代码并重新验证。同一个模型开始从“给出可能的答案”走向“把事情做完”。

这就是我理解的 Agent 行动空间。Files 是它观察和修改项目的入口,Terminal 负责执行与验证,Web 把外部世界带进任务现场,Session log 保留一路发生过的事情。这些能力为 Agent 增加了眼睛、双手和记忆。Codex、Claude Code 以及许多 Agent 编码工具都在沿着这个方向发展。

完整的基础设施扩大了这个空间,也让 Agent 有机会处理更长、更复杂的任务。清晰的权限、可见的结果和可恢复的生命周期构成安全边界。Minke 想给 DSH 补足行动空间,继续完善这套基建。

从 Tauri 到 Electron

技术选型应比较承载真实产品能力后的系统边界。空项目的安装包大小只是一项局部指标。

Minke 最早选用 Tauri,技术栈是 Tauri、Vite、React 和 Tailwind CSS(关于技术选型,我已经写过太多,如:Agent 开发指南:技术太多,该怎么学?)。

Tauri 当时的吸引力很直接:它复用操作系统提供的 WebView,桌面壳很轻;Rust Core 负责本机能力,前端通过受控 IPC 调用它。这套结构很适合以 Web UI 为主、原生能力较少的桌面应用。

DSH 还包含一个完整的 Node.js Harness runtime,负责启动 Host、解析插件树、加载 Node package、运行 node-pty 和安装外部插件。WebView 只承载其中的前端。

无感安装要求应用自带 Node runtime、Harness、包管理器和目标平台的 native module。用户无需预先配置正确版本的 Node.js 与 pnpm。

我先尝试保留 Tauri,把 Node 24、DSH 和 pnpm 封装进一个 Node.js Single Executable Application(SEA)sidecar。这个 sidecar 自带完整运行环境,并保留外部插件的安装、加载和 HMR。

方案跑通后,sidecar 经过裁剪仍有 150 MiB 以上,Tauri 的体积优势随之缩小。DSH 的动态包解析、native module 和插件机制还需要逐项适配 SEA。Minke 同时承担 Rust Core、Node sidecar 及其运行时协议的维护。长期演进成本开始主导技术选型。

Browser 能力进一步推动了迁移。Minke 的规划包含独立 Session、请求控制、页面调试和 Chrome DevTools Protocol(CDP)。Tauri 在 Windows、macOS 和 Linux 上分别使用 WebView2、WKWebView 和 WebKitGTK,平台差异会直接进入产品。Electron 提供统一的 Chromium 多进程模型,webContents.debugger 也可以直接使用 CDP。

相关差异可以参考 Tauri 的进程模型Electron 的进程模型Electron Debugger API

Node 分发、动态插件、CDP、Browser 扩展和跨平台一致性评估完成后,我迁移到了 Electron Forge。

Electron 的安装包更大,其中的 Node.js 直接承载 DSH,Chromium 提供 Browser Harness 所需的能力。迁移后,Minke 复用 Electron 自带的 Node runtime 启动 DSH,移除了 SEA 封装层;Session、网络请求、权限、WebContents 和 CDP 也统一到同一套浏览器模型。

DSH:可组合的 Harness

DeepSeek Harness (DSH) 用 “Everything is a Plugin” 概括自身。这句话直接描述了它的工程结构。

DSH 已经提供了一套相当完整的 Agent 基础设施:

  • append-only 的 Session Event log;
  • Agent loop、turn 和 step 生命周期;
  • LLM adapter;
  • Tool registry 与执行管线;
  • 文件、Shell、PTY、Sandbox 和权限策略;
  • MCP、ACP、Background Job;
  • Settings、Credentials、Telemetry;
  • Web Host 与 Client UI。
  • ...

模型适配器、工具、持久化、会话标题和 agent loop 都挂载在同一棵插件树上。profile、bundle 和 cordis.patch.yml 分层组合出一个运行中的 DSH。

把 DSH 当作固定 Web 应用会迫使 Minke 长期维护 fork 或依赖脆弱的 DOM patch。当前架构将 DSH 作为产品引擎,Minke 作为独立产品层。

Minke 的自定义代码集中在 @lencx/minke-harness-overlay,上游 vendor/deepseek-harness 保持独立。公开的 --patch 组合入口将产品能力加入插件树:

- id: llm-pi-ai
  disabled: true

- insert:
    - id: model-runtime
      name: "@lencx/minke-harness-overlay/model-runtime"

    - id: minke-overlay
      name: "@lencx/minke-harness-overlay"

这段配置由 Minke 的 model runtime 接管本地模型准备流程,再挂载 Minke 自己的 Host/Client overlay。

这条边界让上游保持独立,也让 Minke 形成自己的产品体验。它是 v0.1.0 最重要的架构选择。

Cordis:组合与生命周期

DSH 的插件系统建立在 Cordis 之上。Cordis 将自己定义为 “A Meta-Framework of Spatiotemporal Composability”;对应的论文是 A Programming Paradigm for Spatiotemporal Composability

用工程语言概括,Cordis 处理两类组合问题:

  • 空间上的组合:组件声明自己依赖哪些 service,运行时根据 context 中实际存在的能力决定何时激活它;
  • 时间上的组合:组件注册的 service、event listener、timer、DOM adapter 或其他 effect,都应在组件卸载时完整撤销。

Minke 的 Client overlay 提供了一个具体例子。语言包、样式、标签页 renderer、会话订阅、快捷键 runtime 和桌面 surface adapter 都通过 ctx.effect() 注册;主题和语言变化通过 typed event 同步;UI 通过 Harness 暴露的 slot 接入:

ctx.effect(
  () => ctx.locale.register(NAMESPACE, { zh, en }),
  "minke-overlay: shortcut dictionaries",
)

ctx.slots.inject(
  "shell.overlay",
  () => ctx.slots.register(
    {
      name: "shell.overlay",
      id: "minke-tabs-right",
      order: 20,
      locale: TABS_NAMESPACE,
      inject: () => ({
        placement: "right",
        runtime: rightTabs,
        renderers: rightWorkspace.renderers,
      }),
    },
    TabsPanel,
  ),
)

Minke 的扩展以带有明确生命周期的组件挂载。插件卸载时,相关订阅、样式和运行时资源会同步释放。

新功能从三个问题开始设计:

  1. 这是一个 service、event、slot,还是桌面 IPC?
  2. 它属于 Host、Client,还是 Electron?
  3. 它由谁创建、谁消费、谁负责释放?

明确这些边界后,实现通常会简单很多。

三层架构

Minke 的运行结构大致如下:

Minke Electron 桌面应用架构流程图

Electron 负责桌面环境和安全边界:窗口、菜单、快捷键、文件对话框、终端进程、Web tab session、导航策略和持久化配置。

DSH 运行在一个独立的本地子进程中。Minke 使用 Electron 自带的 Node runtime 启动它,监听 127.0.0.1 的随机端口;主进程从输出中读取 ready URL,再让窗口加载 Harness UI。启动超时、输出截断、异常退出后的恢复提示、进程组关闭,都由一个独立的 HarnessRuntime 模块管理。

浏览器侧的 Minke overlay 通过 context-isolated preload 暴露的窄接口访问桌面能力。Node 权限保留在 Electron 主进程,IPC 请求还会校验 sender 和 frame URL。

完整应用内部保留了三条清晰边界:

  • DSH 负责 Agent 与工具运行时;
  • Minke overlay 负责产品组合和 UI 扩展;
  • Electron 负责本机能力、生命周期与安全策略。

打包 Harness Runtime

源码环境通过三条命令即可运行 DSH:

pnpm install
pnpm run build
pnpm dsh web

跨平台安装包还需要一套独立的构建与分发工程。

DSH 是一个大型 monorepo。完整 workspace 会带入开发依赖、源码、类型声明、source map、测试资产和其他平台的 native binary,体积也无法控制。pnpm 的 symlink 布局、Node native module、Electron ABI 和 ASAR 继续增加分发复杂度。

Minke 的独立 Harness staging 流程包含以下步骤:

  1. 用 Git submodule 固定 DSH commit;v0.1.0 对应 @deepseek-ai/dsh0.1.0-rc.7
  2. 校验仓库、commit、包版本、pnpm 版本和 product bundle contract。
  3. 构建完整 DSH workspace 和 Minke overlay。
  4. 从 DSH CLI、Web frontend 及显式 runtime package 出发,计算运行所需的 workspace dependency closure。
  5. 使用 pnpm deploy 生成候选 runtime,并补齐 deploy 忽略的 workspace package。
  6. 将 pnpm symlink 物化为真实文件,移除 .bin 等非运行时布局。
  7. 按平台裁剪 node-pty、reflink 等 native asset。
  8. 删除文档、类型声明、source map、build cache、debug symbol 和发布包中的开发 baggage。
  9. 对重复的 esbuild binary 等大文件做安全去重。
  10. 写入 commit、平台、架构、fingerprint 和体积策略等 metadata。
  11. 在临时目录完成验证后,再原子发布到 runtime/host

可验证的裁剪

这部分是整个开发过程中最有意思、也最容易低估的工程之一。

最初从 monorepo 直接部署出来的 Harness Host,逻辑体积约为 244.5 MiB,共 33,269 个文件。内容分析显示其中混合了多类资源:

  • 生产时必须存在的 JavaScript、package.json 和运行时资源;
  • TypeScript 声明、source map、README 和 CHANGELOG;
  • package 自带的测试、示例、.yarn 与构建缓存;
  • Windows、Linux、macOS 及不同架构的 native binary;
  • 被不同依赖重复带入的 pnpm、esbuild 等工具;
  • Electron 自带的非必要语言包。

每项删除都需要证明生产能力保持完整。裁剪分成多个独立批次,每一批都有反向门禁和打包态验证:

  1. 先让测试在旧 Host 上识别出不应进入生产包的文件,确认规则真的会失败。
  2. 根据 package exports 和真实入口确认生产解析路径,再移除路径外内容。
  3. 按目标平台裁掉不兼容的 node-pty、pnpm 和其他 native asset。
  4. 用 Electron 内置 Node 实际创建 PTY,替代单纯的 .node 文件存在性检查。
  5. 清空系统 Node/pnpm 环境,重新验证外部插件安装、加载与 HMR。
  6. 最后再对 .app 中的 Host 体积、文件数、必需路径和禁止路径设置发布预算。

Electron locale 使用覆盖主要市场的 allowlist。最终保留 24 个语言包,供未来 Browser/WebView 中的 Chromium 原生菜单、权限提示和内置页面使用;其余 196 个长尾语言包被移除。

我用相同环境重建了优化前的提交,单独测量裁剪收益。下表记录裁剪阶段的中间快照;v0.1.0 的最终交付体积见表后数据:

Harness Host 裁剪前后体积对比数据表

主要收益来自 Harness source map、重复工具、类型声明、测试与示例、非目标平台资源,以及 Electron locale。本轮裁剪覆盖 Harness Host 及其依赖中的 source map。v0.1.0 仍包含 Electron 主进程的 source map,后续版本会单独移除。

桌面应用的“体积”至少有三种口径:运行时逻辑大小、安装后的磁盘占用和用户实际下载的压缩包。三个数字各自描述不同阶段,无法直接相加或相互替代。

v0.1.0 的 macOS arm64 最终交付数据如下:

  • staged Harness runtime:141.8 MiB、13,260 个文件
  • 安装后的完整 .app:约 408 MiB
  • 用户下载的压缩发布包:约 143 MiB

141.8 MiB 的 Host 是完整应用的一部分。408 MiB 还包含 Electron、Chromium、主流语言包和原生 Framework;143 MiB 是压缩后的下载体积。最终的 Harness runtime 拥有明确的依赖闭包、平台边界、体积预算和来源校验。

这套工程让我对 Harness 有了一个更具体的认识:

Harness 包含 agent loop 和 tools,也包含构建、裁剪、签名、升级、诊断和分发组成的软件供应链。

DSH 提供能力结构,Minke 负责将它变成桌面产品。

本地模型生命周期

Minke v0.1.0 支持 LM Studio 和 Ollama,并保留通用的 OpenAI-compatible 配置。

model runtime 明确定义了三种生命周期:

  • external:连接用户已经运行的服务;
  • ensure-running:服务不存在时启动;
  • managed(LM Studio):在卸载时关闭由 Minke 启动的服务。

LM Studio 还会检查当前加载实例的 context window。外部服务保持原有配置,Minke 报告当前值和要求值;由 Minke 管理生命周期的实例可以执行受控的 load/reload。

中心控制器会迅速积累平台、模型和生命周期的条件分支。Cordis 插件树让 provider、生命周期策略和产品配置分别演进。

跨平台发布

v0.1.0 的大部分时间花在了打包上。

1. Windows:路径

Electron Forge 清理 .bin 时使用了过宽的 glob,扫描范围超出目标 node_modules,Windows 打包表现为无报错卡住。pnpm patch 将扫描范围收敛到 buildPath/node_modules,打包随即恢复。

Squirrel 的 release extraction 使用独立的临时目录,TEMPTMP 无法完整覆盖。SQUIRREL_TEMP 指向 Windows runner 上的 D:\t 后,路径长度得到控制:

TEMP: D:\t
TMP: D:\t
SQUIRREL_TEMP: D:\t

桌面发布中的每一层工具链都有自己的路径语义。

Ubuntu 上的 pnpm start 卡死源于 esbuild launcher 与平台 binary 共享 hard link。社区贡献的 PR #1 修复了这个问题。

原来的优化逻辑在 launcher 路径上直接写入一个很小的 Node wrapper,同时修改了共享 inode,连 native binary 也被覆盖。修复采用原子目录项替换:先写临时文件,再 rename 到目标路径,保留 canonical binary 的 inode。

这是我很喜欢的一个 bug。它提醒我们:文件路径相同不代表文件身份独立。发布工程里,link、inode、copy-on-write 和 archive layout 都可能成为业务问题。

3. CI:先判断再修复

macOS Intel 的 DMG 曾在 hdiutil detach 阶段报告目标卷不存在,相同代码重新运行后成功。最终发布时,Linux runner 又在通常只需几秒的 apt-get install 上卡了二十分钟。

两次故障都先经过复现与历史耗时对比。macOS 问题未稳定复现;Linux workflow 被取消,随后只重跑 Linux job 及依赖它的 Release job。

CI 失败需要先归类:产品缺陷、工具链缺陷或 runner 瞬态故障。错误归因会制造无效修改。

4. Linux:桌面集成

Linux 安装包会生成 minke.desktop,Electron 的 desktopName 需要与它一致,桌面环境才能正确关联窗口和图标。社区贡献的 PR #2 修复了配置,并补上针对性回归测试。

Linux 桌面集成图标配置

这一行配置连接了包名、desktop entry、Wayland app id 和 X11 WM_CLASS 四层契约。

架构测试

发布前,Minke 的 227 项桌面测试覆盖交互、配置和架构约束:

  • Minke overlay 不得反向修改 vendored Harness;
  • Host 和 Client 的模块边界;
  • Harness contract、runtime fingerprint 和裁剪策略;
  • native module 是否匹配目标平台;
  • ASAR 与最终安装包的内容 allowlist 和 denylist;
  • GitHub Actions 是否真的覆盖四个平台;
  • Release 是否生成稳定资产名和 checksum;
  • Windows 的 fixture 是否不受换行符影响;
  • 单实例、窗口状态和配置目录的启动顺序。

Harness 产品最昂贵的回归集中在“源码—构建—runtime—Electron—安装包—操作系统”这条长链路的接缝上。这些测试直接覆盖接缝。

有结构的复杂性

DSH 仍处于 developer preview,升级会有 breaking change;Cordis 也在快速演进。Minke 固定在 rc.7,下一次升级需要重新检查 settings、LLM adapter、attachment 和 native module 等契约。

DSH 对复杂性的组织方式改善了整体开发体验:

  • 每个能力都有相对清晰的 service 和 event 归属;
  • session log 是模型上下文的权威来源;
  • tool execution、LLM streaming 和 agent lifecycle 都有明确 seam;
  • product bundle 可以覆盖组合,避免 fork core;
  • Cordis effect 让动态 UI 和运行时资源有统一的回收方式;
  • 完整的架构文档和类型契约,让人和 Coding Agent 都更容易定位正确改动面。

“Everything is a Plugin” 为复杂性定义了位置、依赖和生命周期。

这套结构让 Minke 聚焦产品体验:桌面工作区的组织方式、工具与上下文的距离、本地模型的所有权,以及原生能力的桥接范围。底层 Harness 保持独立演进。

结语

v0.1.0 验证了一条路径:开放、可组合的 Agent Harness 可以在不 fork 上游的前提下,长出有明确产品判断的桌面应用。

Minke 定位为 DSH Harness 在桌面场景中的产品化实验。Cordis 的组合模型让这次实验直接从扩展与组合开始,保持上游源码独立。

做 Agent 产品时,可以把关注点从“模型调用”再向外扩一层:

  • Agent 的状态如何成为可重放的事实?
  • Tool、Model、Sandbox 和 UI 如何替换?
  • 插件卸载后,副作用能否完整回收?
  • 开发仓库如何变成可验证、可升级的产品 runtime?
  • CI 失败时,你能否判断问题来自代码、工具链还是环境?

这些问题决定了一个 Agent Demo 能否变成可维护、可分发的软件。

References

[1] Minke v0.1.0: https://github.com/lencx/Minke/releases/tag/v0.1.0

[2] DeepSeek Harness: https://github.com/deepseek-ai/deepseek-harness

[3] 国际捕鲸委员会的介绍: https://iwc.int/about-whales/whale-species/minke-whale

[4] Tauri 的进程模型: https://v2.tauri.app/concept/process-model/

[5] Electron 的进程模型: https://www.electronjs.org/docs/latest/tutorial/process-model

[6] Electron Debugger API: https://www.electronjs.org/docs/latest/api/debugger/

[7] DeepSeek Harness (DSH): https://github.com/deepseek-ai/deepseek-harness

[8] vendor/deepseek-harness: https://github.com/lencx/Minke/tree/main/vendor

[9] Cordis: https://github.com/cordiverse/cordis

[10] A Programming Paradigm for Spatiotemporal Composability: https://github.com/cordiverse/paper

[11] PR #1: https://github.com/lencx/Minke/pull/1

[12] PR #2: https://github.com/lencx/Minke/pull/2




上一篇:腾讯朱雀开源AI安全检测平台:Agent红队实测与提示词泄露防护
下一篇:改内存控制器映射就能读受保护DRAM?AMD 16h地址变换研究
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-8-26 01:41 , Processed in 0.813387 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表