LanChat —— 局域网消息 / 文件互传(含 DSH 原生插件)
同一个局域网内的几台 Windows 电脑之间,互发消息和文件。 不用服务器、不用注册、不用联网。
配一个 DSH(DeepSeek Harness)插件后,AI 之间也能互相通讯:AI 能收发消息、传文件,
收到消息还会自动唤醒本机会话里的 AI 来处理。
- 只用 Windows 自带端口:TCP 80(网页与传输)、UDP 1900(自动发现,与系统 SSDP 共存)
- 单个 exe,零依赖(.NET Framework 4.x 自带),拷到哪都能跑
- 设备之间自动发现,不需要手填 IP
- 消息与文件在链路上加密传输(详见「注意事项」里的强度说明)
装它(第一次来的人看这里)
第 1 步:拿到两样东西
从本仓库下载或克隆,你会看到:
LanChat.exe ← 应用本体(双击即用)
package.json ← DSH 插件清单
index.js ← DSH 插件(宿主侧:工具 + 联网 + 唤醒)
client.js ← DSH 插件(界面侧:设置面板)
cordis.patch.yml ← 插件声明
LanChat.exe 与插件文件放在同一个目录是刻意的:插件会自动在自己旁边找 exe,装上就能用。
第 2 步:先单独跑一次 LanChat(推荐)
双击 LanChat.exe。右下角托盘会出现图标,并弹出气泡提示。
第一次运行,Windows 防火墙会问「是否允许访问网络」——必须勾选"专用网络"并允许,
否则别的机器发现不了你。
第 3 步:装 DSH 插件
在 DSH 里(或让 DSH 的 AI 执行):
plugin_manager install_bundle target="<你放这些文件的目录>"
例如把文件放在 D:\DSH-LanChat,就填 D:\DSH-LanChat。
装完重启一次 DSH(客户端半身与插件代码需要从磁盘重新加载)。
第 4 步:确认装好了
重启后:
- 设置 → 局域网通讯:能看到开关与参数(9 项)
- 让 AI 调用
lanchat_status:能看到本机名称与在线的其它机器
- 托盘里 LanChat 图标还在(插件不会把它藏起来)
到此结束。 之后每次开 DSH,若 LanChat 没在跑,插件会帮你启动;已经在跑就直接接入。
0. 给 AI 的 60 秒速览
这一节写给 DSH 里的 AI。读完即可开始通讯。
你有 5 个工具:
| 工具 | 一句话 |
|---|
lanchat_status | 我是谁 + 局域网里有谁(通讯前先调它) |
lanchat_recv | 收消息。peer 省略 = 群聊;waitSeconds 可长轮询等回复 |
lanchat_send | 发消息/文件。默认攒批,同一目标连发会合并成一批 |
lanchat_flush | 一轮内容发完后调用它,立即把这一批发出去 |
lanchat_identity | 看/改本机名称与头像颜色 |
三条铁律:
- 要发文字 + 文件、或连续说几句时:连续调
lanchat_send,最后调一次 lanchat_flush。
这样对面收到的是一条消息、一个批次号,能一次看全 —— 避免"文字先到把对面 AI 唤醒、
文件后到被忽略"。
mine: true 的消息是自己发的,不要回复自己。
recv 返回的 next 就是游标,下次调用把它当 since,天然去重。
最短对话示例:
lanchat_status() # 看谁在线
lanchat_send(text="帮我查一下磁盘剩余空间", peer="对方的机器名")
lanchat_flush() # 立刻送出
lanchat_recv(peer="对方的机器名", since=0, waitSeconds=20) # 等回复
收到消息时,系统会自动往你会话里投一条「【LanChat 收到消息…】」的提示 ——
看到就调 lanchat_recv 取全文(文件路径在 messages[].files[].path,可直接用 shell 读写)。
想用 HTTP 直接调(不装插件也能用,任何语言/工具都行):见 DSH-API.md。
1. 人怎么用
1.1 启动与托盘
| 方式 | 效果 |
|---|
双击 LanChat.exe | 托盘图标 + 自动打开浏览器界面 |
LanChat.exe --no-browser | 托盘图标,不自动开浏览器 |
LanChat.exe --port=8080 | 换端口(默认 80) |
LanChat.exe --name=我的电脑 | 指定在局域网里显示的名字 |
LanChat.exe --data=D:\LanChatData | 指定数据目录 |
托盘右键菜单:
| 菜单项 | 作用 |
|---|
| 打开界面 | 打开 WebUI |
| 打开接收文件夹 | 直接进文件目录 |
| 我的地址: http://<你的局域网IP>:80/ | 点一下复制,方便发给别人 |
| 其它网卡地址 ▸ | 多网卡时列出其它可用地址 |
| 退出 | 完全退出 LanChat |
双击托盘图标 = 打开 WebUI。
LanChat 完全独立:关掉 DSH 它照样运行,直到你从托盘点「退出」。
1.2 群聊与私聊
- 顶部一排是本机发现的其它机器(各自带头像字母与颜色)
- 双击任意头像或机器名 → 在左侧会话列表里新开一个私聊
- 私聊只有你们两台能看见,第三台机器拿不到(不是"不显示",是接口层面拒绝)
- 私聊会话若一条消息都没发就切走,会自动清除,不留空壳
1.3 发文件 / 收文件
- 拖拽文件到输入框,或点「文件」按钮
- 收到的文件自动落在
数据目录\received\<发送方名字>\,按机器分文件夹
- 单文件几十 MB 没问题(走原始字节流,不做 base64 膨胀)
1.4 名片(头像与颜色)
每台机器在会话里都有一张名片:名称 + 头像字母 + 头像颜色。
- 头像字母取名称首字符;撞车会自动顺延到下一个可用字符
- 颜色从内置 256 色里随机分配,首次使用随机生成并存盘;撞色会自动换一个
- 名称若与别人重复,会自动改成可用名称,并把新名片广播给全网
- 每条消息的气泡底色 = 发送方头像色,文字颜色按背景亮度自动选深/浅,任何底色都看得清
想改自己的名片:点界面右上角 设置。
1.5 DSH 插件设置面板(设置 → 局域网通讯)
| 设置项 | 默认 | 说明 |
|---|
| 启用插件 | 开 | 总开关;关掉后不收发、也不唤醒 AI |
| LanChat.exe 路径 | 空 | 留空自动查找(插件同目录 → 工作目录 → 安装目录 → 局域网邻居) |
| LanChat 端口 | 80 | 改了自动重连 |
| 数据目录 | 空 | 留空用 LanChat 默认 |
| 自动拉起 LanChat | 开 | 没在跑就帮你启动(脱离式,与手动双击一样有托盘);已在跑则直接接入 |
| 允许从局域网获取程序 | 开 | 本机没有 exe 时,从局域网里其它机器拉一份 |
| 合并窗口(毫秒) | 30000 | 同一批内容攒这么久没新增就自动发出 |
| 唤醒防抖(毫秒) | 4000 | 收到消息后安静这么久才唤醒本机 AI |
| AI 忙时不打断 | 开 | AI 正在跑就只入队,等它这一轮结束 |
改完立即生效,不用重启。 值保存在你的 DSH profile 配置里。
2. AI 怎么用(DSH 插件)
2.1 五个工具
lanchat_status({ includeFiles? })
返回本机名片、在线机器列表;(可选)最近的文件清单。
lanchat_recv({ peer?, since?, waitSeconds? })
peer 省略 = 群聊,填机器名/节点号 = 与它的私聊。since 用上次返回的 next。
waitSeconds>0 时若无新消息会挂起等待(最长 25 秒)——等对方回复就用它,比反复空轮询省资源。
{ "ok": true, "key": "public", "next": 7, "count": 1,
"messages": [ { "seq": 7, "from": "对方的机器名", "mine": false, "text": "在么" } ] }
文件消息会多一个 files 数组,每个元素带 name / size / path(磁盘绝对路径,直接用)。
lanchat_send({ text?, file?, peer?, mode?, kind? })
text 与 file 至少给一个,可反复调用累加。mode:stage(默认,攒批)/ flush(攒批并立即发)/
now(不合并、立刻单发,只适合一句话)。kind: "image" 表示图片,图片不参与合并(免得对方干等)。
lanchat_flush({ peer? })
把攒着的一批发出去。一轮内容发完请调用一次。
lanchat_identity({ name?, color? })
不带参数 = 查看;带了 = 修改(会自动同步给全网,重名/撞色自动避让)。
2.2 为什么默认要"合并发送"
这是本插件存在的核心理由。
真实的坑:AI 先发一条文字、再发一个文件。文字一到,对面的 AI 立刻被唤醒开始处理;
随后到达的文件就成了"另一件事",被延迟处理甚至直接忽略 —— 可它们明明是同一批。
做法:在发送侧暂存。同一目标的内容攒够 30 秒没有新增,才作为一整批发出去;
对面收到的是一条消息 + 一个批次号 + files 数组,一次就能看全。
lanchat_send(text="这是需求说明") → 攒入本批(1 件)
lanchat_send(file="D:/需求.docx") → 攒入本批(2 件)
lanchat_send(file="D:/数据.csv") → 攒入本批(3 件)
lanchat_flush() → 3 件作为一整批立刻发出 ✅
即使忘了 flush 也不会丢:30 秒空闲后自动发出,依然是一批。
2.3 收到消息会自动唤醒本机 AI
插件在后台收消息,然后:
- 等 4 秒内没有新消息;
- 且本会话 AI 处于空闲;
- 才把合并后的一条用户消息投进你的会话。
AI 正在处理别的事务 → 只入队不打断,等它这一轮结束再处理。
配合上面的合并发送,同一批消息既不会被拆开唤醒,也不会被漏看。
2.4 完整对话示例
A 机(发任务):
lanchat_status()
→ peers: [{ name: "B机", online: true }]
lanchat_send(text="把 D:/报告.docx 和 D:/数据.csv 一起看,给我一份摘要", peer="B机")
lanchat_send(file="D:/报告.docx")
lanchat_send(file="D:/数据.csv")
lanchat_flush() # 三件作为一整批发给 B 机
lanchat_recv(peer="B机", since=0, waitSeconds=20) # 等 B 机回复
B 机(被唤醒后):
# 收到提示:【LanChat 收到 1 条消息(同一批)|来自 A机】
lanchat_recv(peer="A机", since=0)
→ messages[0].files = [{ name:"报告.docx", path:"...\received\A机\报告.docx" }, …]
# 用 shell 读取 path 指向的文件,处理完回信
lanchat_send(text="摘要:……", peer="A机")
lanchat_flush()
3. 不用插件也能用(HTTP 接口)
LanChat 暴露了一套极简 JSON 接口,任何语言、任何工具都能直接调:
GET /dsh/whoami 我是谁
GET /dsh/peers 局域网里有谁
GET /dsh/recv?peer=&since=&timeout= 收消息(timeout = 长轮询秒数)
GET /dsh/send?text=&to= 发消息
POST /dsh/stage 合并发送(推荐)
GET /dsh/stage/flush 立即送出一批
GET /dsh/files 文件清单(含磁盘路径)
GET /dsh/doc 完整接口文档(Markdown)
完整字段说明、示例与注意事项见 DSH-API.md(也可以直接 GET /dsh/doc 取回原文)。
4. 注意事项(务必看)
4.1 网络范围
- 设计目标是同一个局域网。跨网段、跨公网、异地组网不保证可用(自动发现依赖 UDP 广播/组播)。
- 手动填 IP 可以跨网段直连(
lanchat_send 的 peer 支持 ip:端口),但需要路由可达。
4.2 端口与防火墙
| 端口 | 协议 | 用途 |
|---|
| 80 | TCP | 网页界面、消息、文件传输 |
| 1900 | UDP | 自动发现(与 Windows 自带的 SSDP 共用,靠 SO_REUSEADDR 共存) |
- 端口 80 被占用时(例如 IIS、其它 Web 服务),用
--port= 换端口;
换端口后同一局域网内的机器仍能互相发现,但要用新端口访问界面。
- 防火墙必须放行,否则别人发现不了你。
4.3 加密强度(请如实理解)
- 消息与文件在传输链路上是加密的:嗅探抓包看不到明文。
- 但密钥是编译进 exe 的固定密钥 —— 属于「防明文嗅探」级别,不是端到端加密,
也不能防中间人。
- 反编译 exe 可以取出密钥。不要在不可信网络里传敏感数据(公司内网、家庭局域网这类可信环境
才是设计场景)。
4.4 LanChat 与 DSH 的关系
- LanChat 是独立软件,不依赖 DSH。DSH 插件只是"接口适配层"。
- 关掉 DSH,LanChat 继续运行(托盘常驻),直到你手动退出。
- 插件只会在 LanChat 没在跑的时候帮你启动;已经在跑就直接接入,不会有两个托盘图标。
4.5 数据位置与备份
- 默认在 exe 同目录下的
LanChatData\:history.jsonl(聊天历史)、identity.json(名片)、
received\(收到的文件)。
- 想换位置用
--data=,或插件的「数据目录」设置项。
- 备份 = 直接复制这个目录。
4.6 隐私
- 私聊只有参与的两台机器能拿到内容,第三台机器在接口层面被拒绝。
- 别人之间的私聊,即使因为网络广播被你收到,也只落库、不进任何界面与接口。
5. 排错
| 现象 | 处理 |
|---|
| 别人看不到我 | 防火墙没放行;或不在同一网段(UDP 1900 被路由器拦了) |
| 界面打不开 | 端口 80 被别的程序占了 → --port=8080 |
AI 的工具里没有 lanchat_* | 插件没装或没启用;装完重启 DSH |
| 设置里没有「局域网通讯」 | 客户端半身需重启 DSH 后才加载;确认 client.js 与 package.json 在同一目录 |
| 收到消息但 AI 没被唤醒 | 本会话需先调用过任意 lanchat_* 工具(用于绑定会话);看 DSH 日志里的 [lanchat] 前缀 |
| 消息攒着不发 | 调 lanchat_flush;或等「合并窗口」自动发出 |
对面收到 LCE1:… 乱码 | 对面是旧版本 LanChat(不支持加密),双方都升级即可 |
| 插件找不到 LanChat.exe | 把 LanChat.exe 与插件放同目录,或在设置里填「LanChat.exe 路径」 |
| 托盘图标没了 | 确认不是用 --nogui 启动的(那会隐藏托盘,仅用于无界面场景) |
6. 卸载
- DSH 里:
plugin_manager 停用或移除 @local/dsh-plugin-lanchat
- 托盘右键「退出」关掉 LanChat
- 删掉程序目录(想留聊天记录就保留
LanChatData\)
7. 版本与兼容性
| 组件 | 版本 |
|---|
| LanChat | 1.6.1 |
| DSH 插件 | 1.0.x(宿主 index.js + 客户端 client.js) |
DSH 版本兼容性(已按两版类型声明逐条核对):
| 插件依赖的契约 | 0.1.7-rc.2 | 0.2.0-rc.2 |
|---|
tools.register(ToolDefinition) | 一致 | 一致 |
sessionController.prompt({requestId, sessionId, mode, content}) | 一致 | 一致 |
subprocess / SubprocessSpawnSpec | 存在 | 存在 |
agent/status 事件(idle ⇄ running) | 没有 | 有 |
- 0.2.0-rc.2:全部功能可用(含"AI 忙就不打断"的精确判忙)。
- 0.1.7-rc.2:也能用。唯一差别是判忙 —— 收不到
agent/status 时插件不再自己判忙,
直接交给 sessionController.prompt(mode:'queue')(它本身就是"正在跑就排队、空闲才起跑"的语义),
所以队列行为仍然正确,只是唤醒时机不如有事件时精确。启动 5 秒后日志会提示这一点。
- 插件只 import Node 内置模块,不会因为 DSH 内部包升级而崩。
升级 DSH 前后建议各跑一次 lanchat_status;若工具消失或唤醒失效,就是上表发生了变化。
1.6 一台电脑只有一个身份(v1.5.3+)
有虚拟机(VMware/VirtualBox)、VPN 或多张网卡的机器,以前会被 LanChat 识别成好几个身份:
每张网卡一个地址、各聊各的。现在改为:
- 只认「有默认网关」的那张网卡(= 由路由器派发的那个网段)作为本机身份;
没有网关的虚拟网卡、169.254 自动地址、VPN 隧道统统不参与
- 广播里带上自己的主 IP,别人看到副网卡发来的广播会直接忽略
- 邻居列表里同一台机器只会有一条,自己也不会出现在自己的邻居里
换网后会每 20 秒重新判定一次,插拔网线/切 Wi-Fi 都能自动跟上。
托盘菜单的「其它网卡地址 ▸」仍会列出全部地址,需要时手动复制。
1.7 改名后历史消息一起变(v1.5.3+)
以前改名只影响新消息,旧消息还挂着老名字老颜色。现在:对方改名/换色后,
他发过的历史消息会一起更新(界面按"当前身份"渲染,只在对方从未同步过身份时才退回消息里的快照)。
1.8 设置界面改成页面内模态框(v1.5.3+)
点右上角「设置」不再是弹出桌面窗口,而是页面内的模态框:
- 更大(760px)、圆角更圆(卡片 20px、按钮胶囊形)
- 默认弹到顶部,背后整页压暗
- 不关闭就不能操作其它内容(遮罩拦截点击、锁住滚动、Tab 焦点锁在框内)
- 256 色盘 + 实时预览 + 随机换色;保存即同步给全网
直接访问 http://<你的地址>/#settings 可以打开界面时同时弹出设置框。
8. 许可
MIT,见 LICENSE。