我做了一个桌面 AI Agent:Hikari 的功能、架构与工程实践

12 阅读20分钟

Hikari:从对话到可审阅的项目改动转存失败,建议直接上传图片文件

让大模型回答一个代码问题,和让它在真实项目里完成一次修改,中间还有很长一段工程路径。

它需要找到相关文件,理解已有实现,决定调用什么工具;涉及命令和外部服务时,需要处理权限;修改结束后,要留下测试记录和代码差异。任务如果中断,还得知道哪些步骤已经执行,哪些步骤需要先检查结果。

Hikari 就是我围绕这条路径做的一个桌面 AI Agent 工作区。 它把对话、项目工具、任务状态、权限审批和改动审阅放在同一个应用里,也支持不打开项目,直接围绕文档和截图提问。

先放项目地址:

一、先看界面:对话和项目操作放在一起

Hikari 中文界面:任务历史、对话入口和工作区工具转存失败,建议直接上传图片文件

Hikari 使用桌面应用形态,界面主要分为三个区域。

左侧是工作区和任务历史,中间是对话与执行过程,右侧按需要打开文件、改动、终端或浏览器。工具面板可以收起,各区域独立滚动,避免查看代码时挤占整个对话区域。

应用支持中文、英文和日文,也提供亮色、暗色及跟随系统的主题设置。语言与主题会保存在本地,重启后继续使用。

我希望这里保留两种自然的使用方式:

  1. 从问题开始。 不选工作区,直接提问、上传文档或粘贴截图。
  2. 从项目开始。 打开本地仓库,让 Agent 阅读代码、提出计划、执行修改,再审阅结果。

如果只是想看看交互,不需要先申请 API Key。内置离线演示可以体验提问、计划、任务控制和示例 Diff;连接自己的模型后,才进入真实的分析与执行流程。

二、模型可配置,服务商协议由适配层处理

模型设置:服务商、模型与能力选项转存失败,建议直接上传图片文件

Hikari 没有把应用绑定到单个模型服务。模型设置支持保存多份配置,按任务选择使用的服务、地址和模型 ID。

目前的配置预设包括 OpenAI、Anthropic、Google Gemini、DeepSeek、Qwen、Kimi、GLM、豆包等服务,也包括 Ollama、LM Studio 和 OpenAI compatible 接口。

这里需要区分两个概念:能填写某个模型,和这个模型能完成项目任务,是两件事。

比如,普通问答主要依赖文本生成;项目任务需要模型支持工具调用;截图分析需要模型接受图片。服务端是否支持流式输出、返回用量、接收推理强度参数,也会影响具体配置。

在实现上,模型差异集中放在 packages/model-provider:Anthropic、Gemini 使用相应协议,其余不少预设走 Chat Completions 兼容接口。任务运行时面向统一的 Provider 契约,不直接散落各服务商的请求逻辑。

这让桌面端、CLI 和后续执行宿主可以复用同一套模型接入逻辑。新增服务时,也能先判断它是已有协议的地址预设,还是需要独立适配。

另一个行为是:真实服务请求失败时会显示错误,不会悄悄换成示例答案。离线模拟有独立标识,避免让用户误以为模型已经完成真实分析。

三、附件问答:文档提取、图片输入与本地 OCR

很多开发任务的起点并不是代码,可能是一份需求文档、一张报错截图,或者产品给出的 PDF。

Hikari 可以通过文件选择、拖放和粘贴截图添加附件,并在发送前预览处理结果。当前主要支持:

类型处理方式
文本、Markdown、CSV、JSON、常见代码文件读取文本,供模型分析与检索
PDF提取文本,部分扫描页面可进行本地 OCR
DOCX提取正文文本
PNG、JPEG、WebP、GIF处理图片尺寸,同时尝试提取文字;GIF 使用第一帧

单次最多添加 6 个文件,其中最多 3 张图片。文档初始预览有长度限制,较长的提取文本可以继续检索和读取。预览被截断,并不表示保存的全文只剩下预览部分。

图片这条路径也有明确分工:

  • 视觉模型可以接收处理后的图片,结合布局和图像细节分析。
  • 纯文本模型可以利用成功提取的 OCR 文字,但不能因此获得识图能力。
  • 离线演示只展示接收和文本摘录,不会理解图片内容。

例如,可以把错误截图与相关代码一起附上,提出这样的问题:

结合截图中的报错和这段请求代码,解释可能的触发条件。
先列出可以从附件确认的事实,再说明需要到项目中继续检查的位置。

附件处理在本地完成,但连接云端模型后,参与分析的文本和图片仍会发送给配置的模型服务。使用本地模型与关闭外部接口,才可能把这部分推理也留在本机。

四、开发任务:先规划,再执行,最后看证据

打开项目后,Hikari 提供规划和执行两种模式。

规划模式用于阅读项目和整理方案,不修改文件,也不执行命令。适合先了解陌生仓库、分析问题范围,或者确认一项改动会影响哪些模块。

执行模式可以调用项目工具完成修改。工具能力包括文件读取与搜索、补丁修改、命令执行,以及按权限开放的浏览器、MCP 等入口。

比如,一个任务可以这样描述:

定位购物车总价计算异常的原因。
先阅读实现和测试,说明触发条件,再做最小范围修复。
保留现有测试,补充覆盖边界条件的回归用例,并运行相关检查。

相比“帮我优化这个项目”,这样的要求能明确问题、修改边界和交付证据,也更方便人在最后判断结果。

如果任务需要更完整的分工,可以选择“调查 → 规划 → 实施 → 审查”工作流:

阶段主要产物执行边界
调查文件证据、问题范围只读
规划实施步骤、风险、验收方式只读
实施代码改动和检查记录按授权使用工具
审查对结果的独立检查使用受限的只读角色

工作流使用 LangGraph 表达阶段流转,实施与审查之间可以按审查结果返工。当前最多进行三轮返工。

这里的“独立审查”指独立角色和上下文,并不表示结论一定正确。最终还是要核对引用文件、实际 Diff 和测试记录。

五、验收与预算:把“完成”变成可以检查的条件

任务选项中的验收、预算和执行限制转存失败,建议直接上传图片文件

Agent 很容易生成一段“已经完成”的文字,但这句话本身不能证明工作完成。

Hikari 允许在运行前设置两类验收条件:

  • **文件验收:**检查指定路径是否存在,以及可选的必需文本。
  • **测试命令:**指定程序、参数和工作目录,保存实际执行结果。

配置的条件未通过时,执行任务不能标记为完成。默认情况下,修改文件后,测试证据还必须来自最后一次改动之后。

这条约束处理的是一个常见细节:Agent 可能先跑过测试,随后又改了代码。如果继续沿用旧结果,“测试通过”就与最终代码失去了对应关系。

对于不适用测试的文档任务,可以选择只使用配置的验收条件;规划模式不会为了验收而执行命令。

同一面板还可以设置 Token、费用和时长上限。费用根据填写的输入、输出单价与用量计算,是预算参考,实际账单仍以服务商为准。向量检索的费用单独处理,不并入聊天预算。

这些限制让一次任务有了更清楚的结束条件,也减少了长时间尝试而缺少反馈的情况。验收覆盖多大范围,结果就只能证明多大范围。

六、改动审阅与 Git 工作树:保留人的交付环节

离线购物车演示中的改动审阅:对话与 Monaco Diff 同屏展示转存失败,建议直接上传图片文件

上图是离线演示:购物车示例从相加改成相乘,只作用于示例数据,没有修改真实项目,也没有运行真实测试。它用于展示审阅交互。

真实项目里,Hikari 会记录任务改动,并在界面中提供 Diff 查看。文件阅读与差异展示使用 Monaco Editor,终端使用 xterm.js 和 node-pty。

当任务涉及较大范围修改,或者当前工作区已有未提交内容时,可以选择独立 Git 工作树。

工作树起始状态可以使用 HEAD,也可以包含当前未提交的文件。后者会复制已跟踪文件和未被 Git 忽略的未跟踪文件;依赖目录、忽略文件不会一起复制,所以新工作树可能仍需安装依赖。

任务结束后,再通过“预览回写”检查补丁与冲突,将确认过的结果应用到原工作区。如果预览后源文件或工作树发生变化,回写会被阻止,避免继续使用过期的预览。

当前内置回写面向有限规模的 UTF-8 文本改动,一次最多 20 个文件、每个 512 KB。更多文件、二进制或特殊权限变化,需要通过 Git 或外部工具处理。工作树回写后,也应在原工作区重新验证。

这里还需要区分:审阅或接受改动,不等于 Git commit,更不等于 push。 任务完成和代码正式交付仍是不同的动作。

七、任务中断之后,恢复的是执行状态

长任务可能因为退出应用、网络故障或人为中断而停止。如果只保存聊天文字,恢复时很容易丢失工具结果、预算和待回答问题。

Hikari 使用检查点保存执行状态,包括模型消息、工具观察、工作记忆、计划、预算、工作流阶段等。应用重启后,具备有效保存状态的未完成任务会显示为已中断,等待继续。

继续任务沿用原检查点和剩余预算;重试创建新一轮任务;追问则是关联原任务的新对话轮次。这几种操作的含义不同。

恢复过程中最需要谨慎的是副作用。

假设一个工具已经写入文件,但进程在记录成功结果之前退出了。此时“没有成功记录”并不能推导出“操作没有发生”。直接重放可能导致重复写入,外部服务调用还可能产生额外影响。

因此,遇到这种结果未知的中断,系统需要先提示检查实际状态,再继续决定后续步骤。检查点可以提供恢复依据,但无法自动把外部世界回滚到某个时间点。

八、上下文与记忆:让长任务保留目标和来源

随着任务推进,对话、文件内容和工具输出会不断增长。全部原样塞给模型,既昂贵,也可能很快超过上下文窗口。

Hikari 将这部分职责拆开:

层次保存什么主要用途
短期上下文模型消息、工具调用、观察结果当前推理与工具选择
工作记忆目标、计划、摘要、决策、待办维持长任务状态
长期记忆带来源与过期时间的项目事实为后续任务提供参考

长期记忆通过 remember、search_memory 等能力管理,也能在设置里查看、编辑和删除。它不会自动把所有聊天永久变成项目事实,过期记录也不会继续参与召回。

对于很长的工具结果,当前开发版本会保存完整内容,先给模型提供摘要式预览,再通过 read_tool_result 分页读取。这样,模型可以继续检查先前输出,而不必为了重新获得文本再次执行原操作。

检索也有两条路径:

  1. 代码查找: 使用仓库索引、ripgrep 和 search_code 查找文件与相关代码。
  2. 附件和记忆检索: 默认做关键词匹配,可选接入 Embeddings,结合语义相似度排序。

开启向量检索后,系统缓存文本向量,并通过 Reciprocal Rank Fusion 合并关键词与语义排名。缓存与接口、模型、文本内容关联,文本变化后需要重新计算。

它目前不会自动把整个仓库向量化,也没有宣称具备大型向量数据库的索引能力。当前语义集合有规模限制,向量比较仍会线性遍历候选。

对我来说,这里更有价值的是保留来源和失效规则。记忆可能过期,摘要可能遗漏细节,检索结果也可能不相关;Agent 仍需要回到原始文件和工具证据中确认。

九、MCP 与 Skills:把工具连接和工作方法分开

Hikari 支持接入 HTTP、stdio MCP 服务,也支持导入包含 SKILL.md 的技能目录。

可以把两者理解为两个扩展维度:

  • MCP 提供外部工具、资源和提示词的连接入口。
  • Skill 描述任务方法、约束和允许使用的工具。

例如,可以在项目里放入一个只读的前端审查 Skill:

---
name: frontend-review
description: Review frontend code without modifying files.
allowed-tools:
  - read_file
  - list_directory
  - glob
  - grep
  - search_code
  - git_status
  - git_diff
---

阅读相关代码,以文件路径说明可复现的问题。
提出改进建议,不修改文件。

再要求 Agent 加载它,对指定目录进行审查。Skill 的工具限制持续生效,但不会自动授予命令、联网或外部服务权限。

同样,MCP 返回的内容、仓库文件和文档都应作为参考资料处理,不能因为其中出现指令文本,就覆盖用户要求。

十、技术架构:桌面界面、任务宿主和运行时分层

Hikari 当前开发版本的分层架构转存失败,建议直接上传图片文件

架构图依据当前本地开发版本整理,其中共享执行宿主和远程执行相关模块属于后续扩展。

项目采用 TypeScript Monorepo,使用 pnpm workspace 管理包,Turborepo 组织构建任务。

核心目录可以按职责阅读:

apps/
  desktop/          Electron 主进程、preload、React 界面
  cli/              命令行入口
  server/           可选服务端

packages/
  agent-core/       任务、事件、工具等核心契约
  agent-runtime/    执行循环、上下文、审批、检查点、工作流
  agent-host/       CLI 与执行节点的共享宿主(开发版本)
  model-provider/   模型协议与离线演示
  tools/           文件、代码、命令、浏览器等工具
  sandbox/         命令执行边界
  git/             Git 与工作树交付
  database/        持久化
  mcp/             MCP 接入
  skills/          技能加载与限制
  shared/          跨进程类型与输入校验
  ui/              共享界面组件

桌面端的一次请求大致沿着以下路径流转:

React / Zustand
    → window.hikari
    → preload / contextBridge
    → IPC Router 与领域处理器
    → TaskHost
    → AgentRunner
    → 模型适配器与已授权工具
    → 事件、检查点、任务记录
    → 界面更新

renderer 不直接访问数据库和 Node 文件 API,preload 只暴露明确列出的能力。IPC 层负责来源检查和参数校验,再把业务交给对应服务。

当前开发版本将 IPC 按工作区、模型、任务、服务和平台能力拆分,减少入口文件中的职责堆积。共享类型提供编译时约束,运行时输入仍要通过 schema 校验。

窗口启用了 contextIsolation 与 renderer sandbox,并关闭 nodeIntegration。不过,Electron 窗口隔离和 Agent 命令沙箱属于不同边界,不能相互替代。

任务命令默认使用系统沙箱:macOS 使用原生沙箱,Linux 依赖 bubblewrap,Windows 使用 Docker 方案。沙箱不可用时会报错,不会自动降级到主机权限。交互式终端则直接运行在宿主账户下。

本地数据主要保存在 SQLite,桌面基本使用不要求先部署 PostgreSQL 或 Redis。任务、事件、改动、检查点、记忆和模型配置分别保存;模型密钥在系统安全存储可用时加密,不可用时只留在当前会话。整个任务数据库并不是全库加密。

还有一个当前架构限制:AgentRunner 仍运行在 Electron 主进程,附件解析使用 Worker。同步数据库、部分索引和检索计算仍可能占用主进程,后续需要根据实际测量继续拆分。

十一、本地开发进展:从单机任务走向自动化与协作

下面这部分来自本地开发版本,尚未全部进入公开仓库。它们已经有对应实现与文档,但具体环境仍需要独立验收。

本地开发版本中的自动化与集成设置转存失败,建议直接上传图片文件

1. 动态子 Agent

主任务可以委派独立调查,汇总结果。每个子 Agent 有独立上下文,并继承工具限制;默认只读,需要写入时使用独立工作树,改动不会自动回写主工作区。

当前限制同时最多三个子 Agent、每轮运行总共委派十二次,不支持嵌套委派。子任务实际用量计入主任务预算,避免将并发变成无限扩张。

2. Hooks、LSP 与插件包

生命周期 Hooks 支持 SessionStart、PreToolUse、PostToolUse 和 Stop,用于在指定事件执行批准的命令。Hook 失败可以阻止流程,但已经发生的工具副作用不能因此自动撤销。

LSP 提供定义、引用、悬浮信息和诊断入口,需要用户自行安装并配置语言服务器。诊断结果依赖服务器和项目配置,仍需编译器与测试提供验证。

插件包进一步组合 Skills、Hooks、角色、LSP 和 MCP,支持摘要校验和自行分发的插件目录。当前没有项目方托管的在线插件市场。

3. 定时任务与持续目标

定时任务支持重复执行和单次执行,可按变化或失败决定是否通知。桌面调度需要电脑和应用保持运行,命令仍遵循原有授权。

持续目标则把目标、续跑次数和预算保存到检查点。模型答复结束但目标尚未完成时,可以在限制范围内继续处理;暂停、取消、外部阻塞和资源上限仍然会停止执行。

4. VS Code、远程控制与自托管执行

VS Code 扩展可以携带当前选区、路径和诊断创建任务,也能查看结果、处理审批和打开差异。

远程控制提供浏览器入口,控制原执行电脑上的任务。自托管执行则由服务端和 Worker 配合,让仍在运行的服务器或执行节点继续工作。

两者都需要明确的执行环境:项目、依赖、模型和沙箱由使用者准备。它们不会自动提供公共云机器,也不会在电脑关闭后凭空继续运行本地任务。

跨进程执行使用工作区占用记录,避免桌面端、CLI 和 Worker 同时操作同一目录;Worker 的领取与状态更新使用请求标识和租约,处理响应丢失与断线重连。这些机制解决的是具体执行路径,不能扩展解释成任意分布式环境中的强一致保证。

此外,本地版本还加入了 GitHub/CI 只读审查和对话、代码历史恢复。历史恢复有文件范围、大小和冲突限制,外部副作用不会回退,仍需要 Git 提交与备份保存长期历史。

十二、怎样运行起来

1. 从源码启动

准备 Node.js 24 和 pnpm 11,然后执行:

git clone https://github.com/fonghehe/hikari-agent.git
cd hikari-agent

pnpm install
pnpm dev

具体 pnpm 版本以仓库 package.json 中的 packageManager 为准。

真实项目工具需要 Git 和 ripgrep。使用浏览器工具前,还需要安装 Chromium:

pnpm exec playwright install chromium

启动后可以先使用离线演示,确认界面与流程,再连接模型和本地项目。

2. 推荐的第一次真实任务

先选一个熟悉的小项目,使用规划模式提出范围有限的问题:

阅读当前项目的入口、路由和请求封装。
用文件路径说明一次页面请求如何到达接口。
先给分析结果,不修改文件、不执行命令。

确认分析与代码一致后,再切换执行模式,处理一个容易验证的小问题。能解释结果、看懂 Diff、重现检查记录,比第一次就交给 Agent 一个大型重构更容易建立信任。

3. CLI 和文档

也可以在源码目录体验 CLI:

AGENT_PROVIDER=demo pnpm agent run "你好"
AGENT_PROVIDER=demo AGENT_MODE=plan pnpm agent run "分析示例项目"

本地文档启动方式:

pnpm docs:dev --port 5174

GitHub Pages 提供使用文档,桌面应用需要在本地运行。公开仓库的可用命令以对应版本的脚本和文档为准。

十三、怎样验证一个 Agent 项目

Agent 项目的验证至少有两层。

第一层是软件本身:任务状态是否正确、审批是否生效、工具是否真实执行、重启后能否恢复、修改后会不会误用旧测试结果。

第二层是模型完成任务的质量:选用某个服务与模型后,在真实项目里是否找对问题、是否修复成功、用了多少 Token、失败发生在哪里。

Hikari 使用 Vitest 覆盖运行时和服务逻辑,使用 Playwright 驱动 Electron 与文档页面。受控模型响应可以让测试稳定地覆盖审批、中断、恢复和界面连接,但不能据此得出线上模型成功率。

当前开发版本还提供固定提交的仓库评测:在临时副本中执行任务,检查允许修改的路径、必须保留的文件,再运行独立验收,避免 Agent 通过改掉测试获得成功结果。

比较不同模型或工作流时,需要固定仓库版本、任务和验收方式,保留失败记录,再统计成功率、延迟和用量。单个演示成功,或者测试替身全部通过,都不足以证明普遍的开发能力。

仓库提供类型检查、lint、单元测试、桌面构建和 E2E 等验证入口。它们分别覆盖不同层次,真实模型、实际 IDE 宿主、系统安装包和远程部署仍需在对应环境验证。

写在最后

做 Hikari 的过程中,我越来越关注模型回答之外的部分:工具执行前有没有权限边界,修改后有没有有效证据,中断时能不能保留状态,以及用户能不能清楚地审阅最终结果。

这些机制不会让模型永远正确,但可以让一次任务更容易检查,也让出错之后有明确的处理路径。

如果你对 Electron、React、TypeScript 或 AI Agent 工程感兴趣,可以从桌面请求链路、AgentRunner、模型适配器和工作流几个入口开始阅读。也欢迎带着具体使用场景和可复现的问题交流。