DisPatch:面向本地 AI 智能体的自托管聊天应用
- 发布日期
- 2026年7月11日
- 更新日期
- 2026年7月11日
- 作者
- Jacob Lloyd —— 项目完成后,在 AI 协助下撰写
- 阅读时长
- 约 12 分钟阅读
简单来说: DisPatch 三合一,完全跑在你自己的电脑上:一个用来和同样住在这台电脑里的 AI 助手聊天的应用、一个文件服务器,还有一个终端——你所有的设备随时都能用,没有任何数据流向云端。它的文件服务器能把任意设备上的文件搬到运行助手的那台电脑上,让助手能直接处理你上传的内容。在共用设备上,不输入 PIN 也能用一部分安全的助手;输入 PIN 之后就能解锁全部功能强大的助手。
DisPatch 这个应用是我做出来的,为的是不用再仅仅为了给自己的 AI 智能体发消息,就得维护一套 30 容器的 Rocket.Chat 部署。它最后变成了三合一——一个本地托管的 LLM 聊天应用、一个文件服务器,还有一个终端——我拥有的每一台设备随时都能用。它是一个约 720KB 的压缩包,免费,而且是我现在唯一在用的聊天前端。
TL;DR
- 这是什么:三合一的自托管应用——为你自己那一批本地 AI 智能体(一个 OpenClaw 网关)准备的网页聊天、一个拖拽式文件服务器,以及一个真正的终端——在你局域网或 Tailscale 网络上的每一台设备上随时可用。
- 费用:0 元。不用付 SaaS 席位费,不用注册账号。
- 需要什么:Linux、Python 3.12+,以及
uv。如果想要智能体打字回复就需要一个 OpenClaw 网关——其他一切没有它也能用。 - 最终得到什么:按机器人分组的会话线程、流式 Markdown、拖拽式文件服务器、应用内的编程智能体终端、可用 PIN 锁定的"安全模式"、供 cron 任务使用的推送 API,以及解压即可直接通过的 187 项测试。
最终会得到什么
在讲配置之前,先看看它实际工作时是什么样子。截图来自一份带假数据的沙盒副本——没必要让大家看到我真实的机器人日志。



它实际能做什么
- 按机器人分组的会话线程,到哪儿都一样。聊到一半用手机打开,还是同一个线程、同一份历史记录,不用重新登录折腾。
- 一个文件服务器——在任意设备和这台主机之间拖拽传文件。这是我用得最多的功能,下面会单独讲。
- 一个终端(Terminex)——浏览器里的真实 shell,跑着一个编程智能体。同样在下面单独讲。
- 流式 Markdown 回复,代码带语法高亮(自带复制按钮),图片/视频支持缩放灯箱,还能作为 PWA 安装。
- 实时的"工作中"面板。点一下打字动画的小圆点,就能实时看到模型的工具调用和思考过程——这是在实时追踪智能体的记录文件,不是假装转个圈骗你。
- 两级锁定。一个 PIN 把全功能智能体和一小部分安全、只提供常规信息的智能体区分开来——细节见下面的两级锁定小节。
- 一个推送 API——
POST /api/inject——让 cron 任务或另一个智能体能往某个线程里丢一条消息(或一张图片)。每日简报能不请自来地出现在你手机上,靠的就是这个。 - 直接在线程里生成本地图片。让智能体帮你画张图,它就能调用本地的 ComfyUI 实例;结果会像其他媒体一样出现在聊天里,灯箱效果一应俱全。不走任何图像 API,也不上传给任何人。
- 可选功能,默认关闭:一个 ComfyUI 面板,可以直接在聊天里启动或停止那个图像生成服务。
文件服务器
在 DisPatch 的所有功能里,文件服务器是我用得最多的一个。它是同一台主机提供的一个拖拽式文件区域:在任意设备上打开 DisPatch,把文件拖进去,文件就到服务器上了——按天分组,图片和视频都有缩略图。

让它不只是"方便"的关键在于文件落在哪里:和智能体住在同一台机器上。从手机上传一张照片,几秒钟后任何智能体就能从磁盘上读到它——描述它、归档它、放到网站上、喂给图像工具。它是"我手上这台设备里的文件"和"我的智能体能处理的文件"之间的桥梁,中间既没有云盘,也不用接线。
那些不起眼但重要的细节也都处理好了:上传的文件在被缓冲之前会先检查大小(手机没法把主机内存撑爆),有一个总的存储上限,写入是原子操作,所以一次被打断的上传绝不会留下半截文件,而且文件存储和聊天记录用的是同一套滚动备份方案。还有一个配置开关,可以把整个文件服务器变成单向的、只能上传的投递箱——设备只能加文件,不能浏览——适合这种场景:能打开这个应用的人,比该看到它归档内容的人要多。
Terminex:聊天里的终端
另一个每天都在发挥作用的功能:DisPatch 能把一个真正的终端放进浏览器。它像其他机器人一样出现在机器人栏里,但打开它会把你接到服务器端的一个 PTY 上,通过 WebSocket 流式传输到 xterm.js——这是主机上真实的 shell 会话,不是模拟出来的。

我自己的配置会直接启动 Reasonix,也就是我跑在 DeepSeek 上、Claude Code 风格的编程智能体——助手中心页面把这套配置叫做 Terminex。这样一来,一个聊天标签页就变成了一整个编程智能体控制台:把任务交给它,看它干活,任何能打开这个应用的设备都行。和文件服务器搭配使用,整台主机在手机上就能完全操控——不管你人在哪儿,先把文件丢进去,再打开终端,告诉智能体拿它做什么。
- 仅限解锁模式。安全模式下终端根本不存在,而且除非你手动启用(
LOCAL_CHAT_TERMINAL),否则它一直是关闭的。 - 会话保存在内存里。可以在应用里启动 / 重启 / 停止;服务器重启就会终止它。不存在藏在后台的持久 shell。
为什么要做这个
它取代的是一整套 Rocket.Chat 部署——一个数据库加一堆容器,就为了让一个人能和几个本地模型对话。它是能用,但为了"发文字、收文字、到处同步"这么点事,这套基础设施未免太重了。
而"为什么连聊天应用都要自己搭"这个问题更深层的答案是:一旦你的模型都在本地跑了,聊天界面就是最后一个还在引诱你投向云服务的部件。自己托管它,就意味着你的提示词、智能体的输出、家人的消息永远不会离开家里,而且 UI、网关、模型、图像生成这一整套流程,就算拔了网线也照样能用。既然你已经决定用 本地 LLM 了,一个托管在云上的聊天前端反而是那个格格不入的存在。
有一个设计选择值得单独提一下:DisPatch 刻意不设构建步骤。前端是原生 JS(ES 模块)加普通的 CSS/HTML。没有打包工具,没有 npm run build。我当初真正的需求是"以后 AI 智能体要能轻松改动它"。改一个 JS 文件,刷新一下,就完事了。如果重新来一次,我还是会这么选。
技术栈
| 层 | 内容 |
|---|---|
| 后端 | Python 3.12+、FastAPI + uvicorn、aiosqlite(单个 SQLite 文件,WAL 模式)、Pillow,用 uv 管理 |
| 后端规模 | backend/app/ 中约 7,200 行(仅 main.py 就约 3,600 行),另有约 2,700 行测试代码 |
| 前端 | 无需构建的原生 JS + HTML/CSS——JS 约 4,900 行,CSS/HTML/service-worker 约 2,100 行 |
| 内置库 | marked、highlight.js、DOMPurify、xterm.js——不调用任何 CDN |
| 数据 | 代码之外,存放在 ~/.local/share/local-chat/:数据库、config.yaml、security.yaml、media/、files/,以及滚动的数据库备份 |
部署方法
以下内容浓缩自压缩包内的 README-SETUP.md(下载框见下方)。
unzip dispatch-2026-07.zip
cd dispatch
./install.sh
./run.sh
这样它就会在 http://0.0.0.0:8765 上启动,并打印出局域网和 Tailscale 的地址,方便你直接在手机上点开。接下来:
- 编辑
~/.local/share/local-chat/config.yaml(自动生成,说明文档在config.yaml.example),让机器人的id和你的 OpenClaw 智能体 id 对上。它会热重载。 - 可选:
./install.sh --systemd会生成一个用户级 systemd 单元。 - 可选:从齿轮图标 → Security 设置 PIN。
security.yaml.example里说明了这个文件的内容,包括从主机外访问/api/inject需要的api_token。 - 环境变量旋钮:
LOCAL_CHAT_HOST/PORT/DATA_DIR/AGENT_TIMEOUT/MAX_CONCURRENCY,以及用来关闭可选面板的LOCAL_CHAT_COMFY=0/LOCAL_CHAT_TERMINAL=0。
openclaw CLI 本身没有包含在内——DisPatch 假定某个它能连到的地方已经有一个 OpenClaw 网关在运行。还没有网关?应用照样能启动,只是会在日志里提醒你,除了打字回复以外的一切都能用:会话线程、inject API、文件服务器、PIN 和安全模式。我当初就是这么冒烟测试这个压缩包的——全新解压、uv sync、随便找个空闲端口、没有网关——打包好的测试套件跑出来是187 passed in 8.4s。
输入的一条消息背后到底发生了什么
你打了点字,按下发送。它走的路径是这样的:
这是理想情况。真正花心思的工程都在处理不理想的情况:CLI 超时了、崩溃了,或者子智能体十分钟后才姗姗来迟地回答。四个独立的来源——实时监视器、CLI 的 JSON 返回内容、一个在每轮结束后重新扫描记录文件的核对器、以及一个再多追踪约 30 分钟的"追随者"——都汇入同一个投递漏斗,并按轮次去重(基于规范化文本加媒体指纹)。任何一条路径失败,回复依然恰好只会送达一次。就算 CLI 报错了,核对器也会先跑一遍,因为模型往往在挂掉之前已经把答案写进了记录文件。
两级锁定:普通模式与安全模式
这个区分很重要,因为解锁那一侧不是玩具。那些智能体是真能干活的:编辑源代码、把改动推送到我正在运营的网站上、运行各种工具、管理它们所在的那台机器。锁定那一侧只提供常规信息——依然是真正能用的,你可以提问、聊天、获得实际的帮助——但被刻意隔绝在那些权限之外。原因很简单:我家有个三岁小孩,家里那台手机也不是随时都在大人手上。想象一下,一个三岁小孩拿着我的手机、拥有完整权限能干出什么事——网站大概会被清一色的《汪汪队立大功》视频占领。所以安全模式其实是服务器强制执行的"防熊孩子"机制。还有一个小小的体验改进:锁定状态下的主屏幕现在显示的机器人头像和解锁那一侧一样,而不是过去那种字母方块占位图。下面是当一个请求到达、但还没输入 PIN 时会发生的事:
万一把自己锁在外面,有一份恢复码文件加一个可编辑的 YAML 兜底,不用去开工单求助。而且如果 security.yaml 在写入过程中损坏了,失败模式是"保持锁定状态",而不是"把一切都开放给局域网上的任何人"。这是刻意做出的设计决定。
踩过的坑
- API 字段是
content,不是text。POST /api/inject要的是{"bot_id": "...", "content": "..."}。这是集成时最常见的一个坑,连我自己都踩过好几次。 - 不要把长消息当作 CLI 参数传递。内核把 argv 限制在 128KB(
MAX_ARG_STRLEN);一条很长的回复会让子进程启动时因 E2BIG 崩溃。代码用的是--message-file <tmpfile>——不要把它"简化"回参数形式。 - 机器人 id 只在一个特定位置区分大小写。OpenClaw 的网关会在自己的索引里把会话键统一转成小写,但你的 id 可能是大小写混合的(
Doxy、DS_Brain)。区分大小写的查找会悄无声息地弄坏工作面板和延迟回答追随者,而其他一切照常运行——非常难发现。做法是先精确匹配,匹配不上再回退到小写。 - CLI 只返回最后一块内容。智能体在工具调用之间说的任何话,只存在于它的记录文件里,不会出现在 CLI 的返回值中——这就是为什么需要监视器和核对器,也是为什么要特别处理智能体在等待子智能体时"这是我目前找到的内容"这类中间反馈。
- 跨站 WebSocket 劫持通过 Origin 校验来阻止。两条 WS 路由都会拒绝那些
Origin主机和Host不一致的握手请求;非浏览器客户端不发送 Origin,所以能正常通过。 - SVG 被刻意设计为不能作为媒体提供。浏览器会把内联 SVG 当作同源脚本处理——把它当图片返回等于开了一个存储型 XSS 的洞。媒体响应改用带沙箱效果的 CSP 加 nosniff。
- Service worker 更新后,旧标签页会卡在旧版 JS 上。表面看起来完全就是个回归 bug,直到你意识到那个标签页压根没接收到新的 worker。解决办法是监听
controllerchange自动刷新。
如果你想复刻这个思路
就算你永远不会跑我这个压缩包,这套架构也值得借鉴。整体上是四个相互独立、都可以替换的部分:
- 一个掌管历史记录的聊天后端:一个小型 web 服务器(这里是 FastAPI 加 WAL 模式下的单个 SQLite 文件),配一个用于实时更新的 WebSocket,和一个用来推送消息进来的普通 HTTP 端点。这就是"数据库加一堆容器"的完整替代品。
- 一个智能体网关,后端通过 shell 调用或 HTTP 请求它——这里用的是 OpenClaw,但任何"输入会话 id + 消息,输出文本"的东西都行。让它保持独立进程,意味着即使智能体栈崩溃、升级,或者被整个换掉,聊天应用都能照常存活。
- 网关背后的本地模型服务器(LM Studio、vLLM、Ollama——这是网关要操心的事,不是聊天应用的事)。
- 可选的媒体服务,比如用于图像生成的本地 ComfyUI,智能体把它当作工具调用,后端只需把输出结果当作媒体存起来。
不管用什么技术栈,可以带走的经验是:安全/锁定模式要在服务器端强制执行,绝不能靠 CSS;把"模型的回复恰好送达用户一次"当作一个真实的分布式系统问题来对待,而不是想当然地假设理想路径;如果你打算让 AI 智能体来维护前端,那就让它保持无需构建。
下载
- 下载 DisPatch 源码(zip) 716 KB
仅限个人使用免费。如果它替你省下了一下午的时间,欢迎点旁边的咖啡按钮支持一下。