Copilot2Copilot
在 GitHub Copilot 中注册“向同事沟通”工具,让双方的 Copilot 直接沟通:你告诉自己的 Copilot “帮我问张三:XX 接口的分页参数怎么传”,消息会送达同事的 VS Code,由其 Copilot 阅读理解并回复;代码细节的往返全程自动流转。
功能
- 10 个语言模型工具(agent 模式下 Copilot 自动调用,也可用
# 引用):
#c2c_list_colleagues 查看在线 Copilot 列表(含角色、负责内容;传 include_offline=true 可查看全部已配置条目)
#c2c_send_message 向同事发消息(可附代码片段;intent=task 表示请对方执行写操作;默认等待对方回复后再返回结果)
#c2c_wait_reply 等待某条消息的回复
#c2c_reply_message 回复收到的消息
#c2c_list_inbox 查看收件箱(同事发来的消息 + 已收到的回复;含文件信息与落盘路径)
#c2c_send_file 给同事发送文件(扩展负责分块传输、逐块确认与 sha256 校验;双方需同时在线,单个文件上限 64 MiB)
#c2c_query_memory 查询房间共享记忆(提问前先查,避免重复问答;返回条目带出处与修订号)
#c2c_remember 把可复用的事实写入房间共享记忆(房间成员共享、全员可改)
#c2c_update_memory 修正共享记忆条目(携带 revision,冲突会回传最新内容)
#c2c_forget_memory 删除共享记忆中过时 / 错误 / 重复的条目(软删除,可在「记忆」页回收站恢复)
- 中继模式:双方各自连接自建中继服务器;消息由中继转发、离线消息由中继暂存、在线档案(角色 / 负责内容)由中继集中下发
- 房间(可见域):房间由中继管理员统一创建与管理(建房、改名、改/清密码、归类、移出成员(移出即禁止再加入,可解除)、解散;分类仅用于分组与排序);房间列表(名称 / 分类 / 人数 / 是否有密码)对所有设备可见,凭密码加入,成员明细仅成员与管理员可见。设备只能看到、并只能与同房间成员通信——非同房间的消息会被中继拒转并向发送方回执。房间、分类、成员与禁止名单落盘保存(
rooms.json),中继重启后保留。未加入任何房间时看不到任何人,需先凭密码加入由管理员创建的房间
- 房间共享记忆:每个房间一份由中继托管的共享记忆(成员全员可读可改):Copilot 提问前先检索、拿到结论后写入,避免重复问答;条目带修订号(乐观锁,不静默覆盖)、保留历史版本、支持软删除与回收站恢复;需要连接中继才能使用。记忆严格按房间隔离——只能读写自己加入的房间,管理员也不例外(管理员的跨房间维护走「管理」页);加入多个房间时检索必须指明房间,不存在跨房间混合结果。保存与下发都由中继完成:客户端不保存记忆副本,中继在变更后向该房间在线成员下发
memory-event,成员界面再向中继重新拉取权威数据
- 中继是数据权威:中继在数据处理中是唯一的权威服务器,见下文「数据处理:中继是权威服务器」
- 中继管理权限(admin):在配置界面填入中继的管理令牌后,可查看在线设备(含各自扩展版本)、踢出、封禁/解封,并管理所有房间与分类;管理操作只在界面手工执行,不暴露给模型工具
- 版本门禁:连接时上报扩展版本,中继只放行与自己版本一致的扩展(
-testN / -testN.M 测试包后缀忽略),不一致会拒绝接入(4008)并给出提示——中继与扩展需同步升级。手机端是例外:它以 x-client-kind: mobile + x-protocol 接入,中继按协议号门禁,中继升级版本号时手机 App 无需重新发版
- 断线自动重连:连接在三种时机发起——VS Code 启动时自动一次(可在「连接」页关掉)、你点「连接 / 重试连接」时,以及断开后的自动重连:退避从 1s 指数增长到 30s(带抖动),中继重启、网络抖动、Cloudflare 隧道掉线这类瞬时中断会自己接回来,未发出的消息在连上后补发。只有需要人工处理的关闭码(令牌不对 4001、id 缺失 4002、id 用保留地址 4003、被管理员踢出 4006 / 封禁 4007、版本门禁 4008)不重连,停在失败态并给出下一步动作。界面顶部常驻「连接 / 断开连接」按钮(命令面板另有
Talk2Copilot: 断开连接),点「断开连接」会停掉自动重连
- 左下角状态栏入口:状态灯 + 未读角标,点击打开配置界面(连接 / Copilot 列表 / 房间 / 管理 / 收件箱 / 行为 / 帮助)
- 下线通告:VS Code 退出时向所有沟通方发出下线通告,对端立即把它从 Copilot 列表移除(离线条目不再显示在列表里)
- 档案自动交换:连上中继后自动同步双方的角色 / 负责内容,供双方 Copilot 判断“该问谁”“谁在问”;档案可按工作区配置,切换项目时自动生效
- 防注入设计:通信约定与安全边界保存在扩展内
prompts/communication-boundary.md,注入时作为附件加载(不占用消息正文);所有发送动作都有确认弹窗
开发与安装
npm install
npm run build # 产出 dist/extension.js(扩展宿主)与 dist/webview.js(配置界面,Preact + TSX)
npm run typecheck # 类型检查
npm run vsix # 打包 vsix 到 vsix/ 目录(文件名含版本号),发给同事安装
本地调试:VS Code 打开本目录,按 F5 启动 Extension Development Host。
配置
点击左下角状态栏的 talk2copilot 图标打开配置界面:
- 连接:顶部常驻状态灯与「连接 / 断开连接」按钮。填中继服务器地址(
wss:// 或 ws://)与自己的档案 id;中继令牌与中继管理令牌均可选(都存系统密钥库)。管理令牌填对后即获得中继管理权限;「连接」页还会显示中继运行版本与本机扩展版本供对照,并可关闭「启动 VS Code 时自动连接一次」。界面上的「保存并应用」只在有改动时可点(改了字段才会亮),「重新载入」放弃未保存的修改
- 断开连接:点顶部「断开连接」(或命令面板
Talk2Copilot: 断开连接)即停止通道并停掉自动重连;工具调用会提示「通道未连接」,点「连接」恢复
- 本工作区档案:id、角色、负责内容(必填齐全才能通信,未完善时状态栏与配置页会提示)。档案按工作区保存,本机每个窗口各持一份 —— 因此多个窗口可以各有各的 id,不会在中继上互相顶下线。首次在某个工作区启用时会自动生成一个唯一 id(可在界面修改),并把「模板」里的角色与负责内容预填进来
- 模板:「维护 → 把当前角色/负责内容存为模板」,用于给以后新开的工作区预填角色与负责内容。模板不含 id,避免新窗口与现有窗口撞名
- Copilot 列表(由中继自动维护):中继下发在线名单与在线档案(角色/负责内容),扩展据此自动登记与更新列表,无需手工添加或编辑;列表只显示在线的 Copilot(对方下线后条目立即消失),
#c2c_list_colleagues 同样默认只返回在线条目(传 include_offline=true 可看全部已配置条目)。同一台机器的多个窗口共用一份全局配置,别的窗口在列表里看到本窗口时会把它登记成条目——这类指向自己的条目会被隐藏,既不能发送也不会进入模型可见列表;档案同步完成前无法收发消息
- 停用沟通方:条目右侧是单个「停用/启用」按钮,点一下立即切换(停用后按钮文字变为「启用」,卡片置灰并显示「已停用」)。条目不能删除(只由中继目录维护),只能停用。停用后:不出现在模型可见的
#c2c_list_colleagues 列表里;向它发送或回复会被拒绝;它发来的消息与回复也不再自动触发本机 Copilot 对话(仍会记入收件箱,便于查看)。注意两点边界:停用不会主动断开已建立的中继连接,也不会撤回停用前已排队待发的消息
- 可执行(写权限):卡片右上角「停用」旁的「可执行」开关——逐同事授权该同事派发写任务;默认关,撤销后完全回到只读。人工终端(手机)例外:设备类型为
mobile 的同事默认获得写权限,卡片上显示「写权限:默认」,可在「行为」页关闭「默认信任我的手机派发的写任务」。详见下文「可执行授权与无人值守(v2)」
- 行为:等待回复默认超时、历史消息保留条数、写任务系统通知(可静音);收到同事的消息或回复时是否自动触发本机 Copilot 对话由顶部「无人值守」开关决定(默认关,只进收件箱)
- 房间:查看中继上的房间列表(名称、分类、人数、是否需要密码对所有人可见;成员明细仅成员与管理员可见),凭密码加入 / 退出。房间与分类由中继管理员统一管理:创建房间(可设加入密码、选分类)、改名、改/清密码、归类、移出成员、解除禁止再加入、解散;房间与分类落盘(
rooms.json),重启保留。分类只用于分组与排序,不影响权限
- 管理:填入中继管理令牌后,可查看在线设备(id / 扩展版本 / 所在房间数)并执行踢出与封禁(封禁后对方无法接入本中继);下方封禁名单可解除封禁。该设备列表不受房间限制(管理需要),但你的通信与 Copilot 列表仍受房间约束。注意:被踢出(4006)或被封禁(4007)的设备不会自动重连(这两个码属需要人工处理的关闭码),解封后需对方手动点「重试连接」
- 记忆:按房间查看 / 检索 / 新增 / 修改 / 软删除与恢复共享记忆。内容全部来自中继:切换房间会先清空旧房间内容再重新拉取;同房间其他成员修改后由中继下发
memory-event,界面自动刷新。管理员在「管理」页可跨房间浏览与管理记忆,但这不改变成员侧「只能读写自己加入的房间」的规则
列表与实时刷新
- 中继模式:连上中继后先向中继上报自己的档案(信包以
to='server' 发出,不进离线暂存);服务端维护在线档案目录,并在成员上下线、档案更新时把在线名单(peers)与在线档案(profiles)广播给所有在线设备;扩展据此自动登记/更新 Copilot 列表(跳过自己)。档案由中继统一下发,客户端不再互发档案声明(对端随消息携带的 profile 仅作补充)。需中继服务端同步升级:未升级的中继只广播在线 id,拿不到对方的角色/负责内容(显示为「档案未同步」,此时无法收发消息)
- 中继会收录任何持有令牌的在线设备,列表可能被陌生 id 撑大——用「停用」处理即可
- 配置界面为实时刷新:任何状态变化(上下线、档案更新、收发消息)都会立即推送到界面;界面用 Preact 局部更新,不会打断你正在输入的草稿内容
可执行授权与无人值守(v2)
v2 在只读问答之外开放了受限的写任务,授权源头始终是本机用户本人:
- 写权限(逐同事、默认关):「Copilot 列表」卡片右上角的「可执行」开关。打开后该同事可用
intent=task 请求本机 Copilot 执行写操作;撤销后立刻回到只读。顶部状态行显示「可执行授权 · N 位」(点它到列表逐人管理),「行为 → 维护」提供「取消所有写授权」一键全关
- 人工终端默认写权限(手机):以
kind=mobile 接入的设备(你本人的手机)默认允许派发写任务,无需逐人打开「可执行」——卡片显示「写权限:默认」。「行为」页的「默认信任我的手机(人工终端)派发的写任务」可关闭该行为;关闭后手机与普通同事一致。该信任只对同房间的手机生效(中继已做房间校验),红线操作仍一律拒绝。「取消所有写授权」不影响这一项
- 红线:删除、安装依赖、有副作用的命令(部署 / 重启)、工作区外路径——即使授权也一律拒绝,并由对方 Copilot 回执说明
- 无人值守(默认关):顶部状态行的开关。关闭时同事发来的消息与文件只进收件箱并计入「N 条未回复」(不自动唤醒本机 Copilot),同时给对方一条「已送达,待人工处理」的自动回执;打开后按上面的授权规则自动注入当前对话处理
- 手动处理:收件箱每条待处理记录带「交给 Copilot」按钮,把它注入当前对话(无人值守关闭时的主要入口)
- 系统通知(默认开):写任务开始 / 结束时用系统通知提醒本机用户(Linux
notify-send、macOS osascript;不可用时退回 VS Code 内通知),可在「行为」页静音
- 收件箱会话化:按同事分会话(微信式)——会话列表显示最后一条摘要与待处理条数,点进会话看气泡详情;写任务用特殊卡片标识
- 身份按工作区:换工作区 = 换 id。旧身份上开过的「可执行」不会继承到新身份(新身份默认全关),对方列表里会多出一个属于该工作区的条目;给谁发消息时注意选对 id
- 取消排队:对方离线或通道断开时,消息会留在本机待发队列里、连上后自动补发;这条记录在「收件箱」里带 「取消排队」 按钮(两步确认),取消后对方上线也不会再补发
- 沟通纪律(替代原熔断):不再有基于往来条数的自动中止,改为在提示词与边界文档里约束——先发现、汇总、检查无遗漏后再发送,信息足够即停止;「等待超时先看对方是否在线」保持不变
id 是双方匹配的唯一依据,两端需互不相同;连上同一中继的设备会自动互相登记到列表中(指向自己的条目会被隐藏,也无法给自己发送)。
多窗口(同一台机器开多个 VS Code)
档案按工作区保存,因此同一台机器的每个窗口都可以有各自的 id 并同时在线。为避免两个窗口用同一个 id 互相顶下线,扩展做了三重处理:
- 首次启用自动生成唯一 id:在某个工作区第一次启用时,id 自动生成为
<模板 id>-<4 位随机>(例如 qq341-3f2a),并保存在该工作区里,重载窗口不变
- 连接前预检:中继模式下连接前会带令牌查询中继的在线名单(
GET /peers),若自己的 id 已在线会给出提示(可能是断电后残留的死连接),但仍会尝试连接,由服务端裁决
- 服务端权威拒绝 / 接管:中继对仍活着的同 id 连接回
4005 拒绝(不顶掉对方窗口);对已失去心跳的死连接(断电/断网残留、TCP 半开)直接接管,让设备重启后能马上重连。客户端收到 4005 会自动重试(指数退避):旧连接被中继判失联后,本窗口自动接上;只有 4001/4002/4003/4006/4007/4008 这类需要人工处理的关闭码才停下,界面写明原因与下一步动作
使用示例
A(你)在 Copilot Chat(agent 模式)中说:
帮我问张三,OrderService 的 pageSize 上限是多少,把这段调用带给他
你的 Copilot 会调用 #c2c_send_message(弹出确认框,可核对内容与代码片段),消息送达后在对方开启「无人值守」时直接触发对方的 Copilot 对话进行处理(未开启则只进对方收件箱并回一条自动回执),回答经 #c2c_reply_message 返回。发送默认等待对方回复(缺省取配置里的“等待回复默认超时”,默认 90 秒)——拿到回复后本机 Copilot 才继续下一步;若等待超时,工具会提示暂停其他步骤、继续调用 #c2c_wait_reply 直到拿到回复(避免"边干活边等"导致回复无人认领、只能作为排队消息注入对话)。显式传 wait_seconds=0 可改为不等待。
需要让对方改东西时,用 #c2c_send_message 并带 intent=task(或直接说清要改什么、验收标准)。能否执行取决于对方用户是否在该同事卡片上打开了「可执行」:未授权时对方会回复「本机未开启执行授权」,此时把情况交给本机用户即可,不要反复重发。
交接文件(send_file)
需要把一份文件(源码、配置、日志、图片等)交给同事时,让 Copilot 调用 #c2c_send_file(确认弹窗中会展示文件名、大小与源路径):
- 传输由扩展完成:512 KiB 分块、逐块确认、整体 sha256 校验,返回
status=ok 表示对方已收妥落盘;全程不经过双方模型,文件内容不会进入对话
- 前提:双方同时在线(离线不会排队补发);单个文件上限 64 MiB;一次一个文件(多个文件就多次调用)
- 接收端:文件落到扩展私有收件目录
globalStorage/WispW.copilot2copilot/files/(不会写入工作区),注入消息里给出完整路径,收件箱条目也会显示。重名文件自动加序号,绝不覆盖已有文件;中断/校验失败的分片会被丢弃
- 边界:对方只能只读查看收到的文件;是否把它应用(复制/覆盖)到对方工作区,由对方用户决定,仍按正常流程走
- 「收件箱」页的「打开收件目录」按钮可在文件管理器中直接打开该目录
- 若在附言里向对方提了问题,对方回复后可用
#c2c_wait_reply(request_id 即 send_file 返回的编号)继续等待
数据处理:中继是权威服务器
Copilot2Copilot 在数据处理上采用**中继权威(authoritative server)**模型。中继不是单纯的消息管道,而是所有数据的权威解释者与最终裁决者:
- 身份与在线状态:设备 id 是否可用、是否被同 id 顶替、是否被封禁/踢出,由中继判定;在线名单与档案(角色 / 负责内容)由中继维护并下发,客户端显示的永远是中继给出的版本。
- 房间与权限:房间、分类、成员身份、加入密码、禁止名单都以中继状态为准。一次请求是否可见、是否允许读写,由中继按发起者的真实成员身份判定——客户端传来的房间 id、身份或范围声明不能扩大权限,管理员身份也不自动扩大成员侧的数据范围。
- 共享记忆:记忆条目只保存在中继(
memory.json),版本号、乐观锁冲突、去重、容量与限流、词法检索排序全部在服务端完成。客户端不保存记忆副本、不做跨房间推断;记忆的保存与下发都由中继执行——变更后由中继向该房间在线成员广播 memory-event,客户端据此重新拉取,而不是由客户端之间互相同步。
- 消息与文件:是否与目标有共同房间、能否转发、离线消息何时补发、文件分块是否校验通过,均由中继裁决;被拒时由中继回执说明原因。
- 数据含义的最终解释权:同一字段在不同客户端版本、不同缓存状态下出现分歧时,一律以中继当前状态与中继下发的载荷为准。客户端只负责请求与呈现,不构造第二份“事实”。
- 信任边界:中继能看到明文消息与共享记忆(房间是可见域而非端到端加密);因此中继服务器本身必须由你信任的一方运行。
这套模型的实际影响:客户端缓存/界面陈旧不会改变服务端数据;断线重连后以中继重新下发的名单、房间与记忆为准;任何“我这边的列表/记忆和同事看到的不一样”的情况,正确答案是以中继为准,刷新或重连即可收敛。
中继服务器部署
中继服务跑在一台独立服务器上,双方各自连它、使用同一个预共享密码通信。目标机需有 Node.js 18+。
# 方式一:一键下载并部署(在目标服务器上执行,装到 /opt/talk2copilot-relay 并注册 systemd 服务)
d=$(mktemp -d) && curl -fsSL https://github.com/WispW/copilot2copilot/archive/refs/heads/master.tar.gz \
| tar -xz -C "$d" && sudo "$d/copilot2copilot-master/relay/deploy/install.sh"
# 需要指定 node 时(nvm 装的 node 在 sudo 下不在 PATH,脚本会把它复制进安装目录,
# 因为 systemd 单元的 ProtectHome 会隐藏 /home):
d=$(mktemp -d) && curl -fsSL https://github.com/WispW/copilot2copilot/archive/refs/heads/master.tar.gz \
| tar -xz -C "$d" && sudo "$d/copilot2copilot-master/relay/deploy/install.sh" /home/<用户>/.nvm/versions/node/v22.11.0/bin/node
# 方式二:已把仓库放到目标机时,直接在仓库里执行
sudo relay/deploy/install.sh
sudo relay/deploy/install.sh /usr/local/bin/node # node 不在 PATH 时指定绝对路径
# 方式三:前台直接跑(用于临时调试)
TALK2COPILOT_TOKEN=<你的密码> node relay/server.js 8787
把 master 换成 tag(如 v0.3.9)即可固定版本。若目标服务器访问 GitHub 不通,先在能联网的机器上下载该归档再拷过去,按方式二执行。
安装脚本会:首次安装时生成随机预共享密码、管理令牌与手机专用令牌写入 /etc/talk2copilot-relay.env(权限 600,含 PORT / HOST / TALK2COPILOT_TOKEN / TALK2COPILOT_ADMIN_TOKEN / TALK2COPILOT_MOBILE_TOKEN / LOG_LEVEL,模板见 relay/deploy/relay.env.example;已存在则原样保留,缺的令牌在就地升级时自动补齐),创建无登录权限的服务账号 talk2copilot,安装并启用 talk2copilot-relay.service,最后自查一次 /healthz(并比对运行版本与仓库 package.json 是否一致)。改完配置执行 sudo systemctl restart talk2copilot-relay。
运维命令:
sudo systemctl status talk2copilot-relay # 运行状态
curl -s http://127.0.0.1:8787/healthz # 探针:{"status":"ok","version":"2026.10.30","protocol":7,"peers":..,"rooms":..,"memories":..,"bans":..,"queued":..,"tokenRequired":true,"adminEnabled":true,"mobileEnabled":true}
curl -s -H "Authorization: Bearer <密码>" http://127.0.0.1:8787/peers # 在线 id 列表(客户端连接前会自查此接口)
sudo journalctl -u talk2copilot-relay -f # 日志(带时间戳与级别,可用 LOG_LEVEL 调级别)
HOST 默认 0.0.0.0,内网各机可直连;只允许反向代理 / 隧道访问时改为 127.0.0.1
- 版本门禁(0.4.1 起):中继只放行扩展版本与自己一致的客户端。中继版本取
relay/server.js 顶部的 RELAY_VERSION(须与仓库 package.json 的 version 同步;install.sh 部署后会比对 /healthz 并告警)。升级中继后必须同步升级所有客户端的扩展,否则会被拒绝接入(4008,界面会提示双方版本)
- 管理令牌(0.4.1 起):
TALK2COPILOT_ADMIN_TOKEN,留空则管理功能整体关闭。在扩展「连接」页填入同一字符串即获得管理权限(查看/踢出/封禁在线设备、管理所有房间);令牌错误不影响普通连接,只是没有管理权限。就地升级旧版本时安装脚本会自动补一行(想指定值就先 TALK2COPILOT_ADMIN_TOKEN=... sudo -E ./install.sh),部署后可用 curl /healthz 看 adminEnabled 是否为 true
- 手机专用令牌(2026.10.30 起):
TALK2COPILOT_MOBILE_TOKEN,留空则手机端整体无法接入(4009)。手机 App 以人工终端(x-client-kind: mobile)接入时必须在握手头 x-mobile-token 携带同一字符串;中继据此签发 mobile 身份,避免持普通令牌的客户端自称手机绕过逐人写授权。安装脚本首次部署会随机生成,就地升级缺则自动补齐;/healthz 的 mobileEnabled 表示是否已配置
- 房间、封禁名单与共享记忆都落盘在 systemd
StateDirectory(默认 /var/lib/talk2copilot-relay/,单元已声明 StateDirectory=talk2copilot-relay):封禁名单 bans.json、房间与分类状态 rooms.json、共享记忆 memory.json,重启保留;备份 / 迁移时拷贝这三个文件即可
- 房间可见性(0.4.1 起):设备只能看到并只能与同房间成员通信;未加入任何房间的设备与所有人互相不可见。房间列表全员可见但加入需要密码,因此升级/部署后请由管理员创建房间并把密码发给同事,对方凭密码加入后双方的 Copilot 列表才会出现成员
- 收到
SIGTERM(systemctl stop / restart)时先给客户端发关闭帧再退出(1001);客户端会在退避窗口内自动重连,中继起来后自己接回,无需手动点「重试连接」
- 心跳与断线判定:中继每 30s ping 一次,连续 3 次(约 90s)收不到 pong 才判定对端失联,先发
1011 关闭帧、2s 后强制断开;客户端侧另有看门狗(约 95s 收不到中继任何帧就主动断开重连),避免单向死链路一直显示「已连接」
- 局域网直连(可选):中继监听
0.0.0.0 时,隧道接入与内网直连同时可用——内网设备在扩展「中继地址」里填 ws://<中继内网 IP>:8787(只写 192.168.5.20:8787 会自动补 ws://)即可,令牌、版本门禁、房间与在线名单都与隧道一致。中继启动日志会打印可用的局域网地址;ws:// 是明文,只建议在可信内网使用
- 在线名单、在线档案目录与离线暂存都在内存中,重启服务端会丢弃暂存的离线消息;在线设备重连后会自动重新上报档案
- 在线档案目录(0.4.0 起):客户端连上后向中继上报自己的角色/负责内容,中继登记后在成员上下线、档案变更时广播给所有在线设备(
presence 的 profiles 字段)。未升级的中继只广播在线 id(peers),此时客户端拿不到对方的角色/负责内容(显示「档案未同步」,无法收发消息)——请同步升级中继
跨网络使用时可置于 Cloudflare Tunnel / Nginx 之后(需支持 WebSocket),地址形如 wss://relay.example.com,客户端会自动拼 /ws。
Android 手机端(android/)
android/ 是独立的 Android 客户端:手机作为「你本人」的人工终端接入中继,不是 AI 同事。
- 定位:手机上不承担 AI 答复,也不接受写任务(中继会拒绝
intent=task 并回执)。同事在 Copilot 列表里能看到你,并标注为「📱 手机」;普通消息照常送达,你可以手动回复
- 用途:人不在电脑前时,用手机向自己的桌面 Copilot 派活(普通消息 / 写任务都能发;桌面端对人工终端默认授予写权限,详见「可执行授权与无人值守」),收结论、看房间与共享记忆
- 接入方式:握手头
x-client-kind: mobile + x-protocol: 7 + x-mobile-token(手机专用令牌),连接前先读 /healthz 校验协议号与 mobileEnabled——中继升级版本号时手机 App 不用重新发版(协议号变化才需要);中继未配置 TALK2COPILOT_MOBILE_TOKEN 时手机无法接入(4009)
- 已实现:连接配置、在线同事、一对一聊天(问答 / 写任务 / 回复,正文按 Markdown 渲染:标题、列表、代码块、表格、链接)、收件箱会话化、房间加入退出、共享记忆检索与增删改、前台服务保活、未读通知
- 写权限:桌面端默认信任手机(人工终端)派发的写任务,手机无需额外申请授权;桌面「行为」页可关闭该默认信任
- 未实现:文件收发、管理员页面、回收站
构建
# 需要 JDK 17+、Android SDK(platform 35 / build-tools 35.0.0)
cd android
echo "sdk.dir=$HOME/.SDK/Android" > local.properties # 不入库
./gradlew assembleDebug # 产物 app/build/outputs/apk/debug/app-debug.apk
adb install -r app/build/outputs/apk/debug/app-debug.apk
首次打开在「设置」里填中继地址、本机 id、角色与负责内容、中继令牌与手机专用令牌(对应中继的 TALK2COPILOT_MOBILE_TOKEN),然后连接。建议角色写「本人手机」、负责内容写「人工查看与转发,不提供 AI 答复」,同事据此判断不该向你提问。
令牌输入框只写不读:已保存的值不会回填,留空表示不修改。
明文流量:debug 构建允许 ws://(内网直连测试),release 构建保持系统默认(禁止明文),必须使用 wss://。
令牌以明文存在应用私有 DataStore(与扩展的 SecretStorage 不同),自用场景可接受;手机丢失时请在接口层面更换中继令牌与手机专用令牌。
安全说明
- 建议走
wss://,可设置中继令牌(校验 Authorization: Bearer,令牌存系统密钥库)
- 管理令牌(
TALK2COPILOT_ADMIN_TOKEN)等同中继管理员:持有者可踢出/封禁任意设备、管理所有房间。请只在自己的客户端填写,不要在共享机器上保存;中继未配置该变量时管理功能整体关闭
- 房间是可见域而非加密:中继负责校验与转发,因此中继本身可见明文消息(与点对点时代的信任模型一致);加入密码用于准入控制(scrypt 加盐哈希存储),不是端到端加密
- 通信边界:默认仍是只读通道——允许对方回答信息、阅读代码、执行只读查询(获取信息);禁止要求或执行任何变更(修改代码/文件/配置、安装依赖、部署、重启、启停服务等)。约定保存在扩展内
prompts/communication-boundary.md,注入同事消息/回复时作为附件加载
- 写任务(v2):只有在本机用户于「Copilot 列表」给该同事打开「可执行」后(人工终端例外:经中继手机专用令牌签发
mobile 身份的设备默认允许,可在「行为」页关闭),对方带 intent=task 的任务才会按读写版约定(prompts/communication-boundary-write.md)执行,且限定在当前工作区内;删除、装依赖、有副作用的命令、工作区外路径等红线一律拒绝。授权逐同事、默认关、可随时撤销,任务内容始终是外部输入,本地规则(如仓库 AGENTS.md)优先
- 收到消息注入聊天时会声明为外部输入;「无人值守」开启时请谨慎对待不可信来源的消息(尤其是写任务)
- 同事发来的文件不可信:文件由扩展落盘到私有收件目录(不进入工作区),只能只读查看;写入工作区、覆盖本地文件或执行其中内容,都需由本机用户明确决定
- 代码片段与文件外发前会在确认弹窗中展示(文件名、大小、源路径),请留意敏感内容
DeepSeek Harness 接入(dsh/)
dsh/ 是本仓库的 DSH bundle:同一套中继协议,换一个宿主——把「Copilot 与 Copilot 对话」接到 DeepSeek Harness 上。
- host 半(
dsh/index.js):一条 WebSocket 连中继(握手带 x-client-version、authorization),注册 11 个 c2c_* 工具,并在 /copilot2copilot/state 暴露连接状态供界面读取。连接只在这里建立一次,界面不复用第二份连接。
- client 半(
dsh/client.js):Web UI 侧边栏一个入口 + 独立主面板,显示连接状态、在线同事、房间、记忆、管理、收件箱与已收文件,数据来自上面那个 route;面板里的「连接」页可直接修改配置并立即重连,「房间」页可凭密码加入/退出,「记忆」页可检索/写入/编辑/软删/恢复,「管理」页在填写管理令牌后出现。
安装
cd dsh && npm install # 只依赖 ws
然后在 Harness 里用 plugin_manager 的 install_bundle 指向 dsh/ 目录。装完侧边栏会出现 Copilot2Copilot。
配置
改 dsh/cordis.patch.yml 的 config 后重装:
| 字段 |
说明 |
relayUrl |
wss://... 或 ws://<ip>:8787 |
token |
中继预共享令牌(TALK2COPILOT_TOKEN)。不要写进本仓库,见下方「令牌怎么放」 |
adminToken |
中继管理令牌;面板「连接」页可填,只写不读 |
id |
本机在中继上的 id,必须与其他设备不同(否则被 4005 拒) |
role / scope |
档案,供对方 Copilot 判断「该问谁」;两项都要填,否则对端看不到你 |
roomName |
启动时自动加入的房间名(房间需先由管理员创建) |
roomPassword |
对应房间的加入密码;密码错会写进 state.error,不静默失败 |
autoConnect |
启动即连接 |
waitSeconds |
c2c_send 默认等待回复的秒数 |
tokenFile |
令牌文件路径;token 为空时读它(相对路径按插件目录解析)。文件不入库,.gitignore 已含 dsh/.token |
sendFileRoots |
允许 c2c_send_file 外发的目录列表;留空 = $DSH_HOME/copilot2copilot/outbox |
allowSendAnyFile |
显式设为 true 才允许发送任意本地文件(高风险,默认 false) |
面板可直接修改上述配置:DSH 面板「连接」页保存后写入 $DSH_HOME/copilot2copilot/config.json(0600)并立即重连;启动时该文件的同名项会覆盖 cordis.patch.yml。令牌与房间密码只写不读,界面不会回显。
令牌怎么放
仓库里的 cordis.patch.yml 把 token 留空、tokenFile 指向未入库的 .token。填令牌有两条路:
- 本地开发:把令牌写进未入库的
dsh/.token,配置里填 tokenFile: '.token'
- 实际部署:在本机 profile 的 patch 层覆盖该行
config,令牌不进入仓库
工具
| 工具 |
作用 |
c2c_status |
连接状态、房间、未读数 |
c2c_peers |
同房间在线同事(id / 角色 / 负责内容) |
c2c_send |
发消息,默认等待回复 |
c2c_wait |
继续等待某条消息的回复 |
c2c_reply |
回复收到的消息 |
c2c_inbox |
收件箱 |
c2c_rooms |
列房间 / 凭密码加入 |
c2c_memory_query |
检索房间共享记忆 |
c2c_memory_remember |
写入房间共享记忆 |
c2c_send_file |
发文件给同事(仅限 sendFileRoots 内;512 KiB 分块 + 逐块确认 + sha256 校验) |
c2c_files |
已接收文件列表与本地落盘路径 |
文件外发默认只允许 outbox 目录:把要发的文件放进 $DSH_HOME/copilot2copilot/outbox
再调用 c2c_send_file;确需任意路径时显式设置 allowSendAnyFile: true(不建议,
模型可能被诱导外发密钥等敏感文件)。收件仍落在 $DSH_HOME/copilot2copilot/files,
重名自动加序号、不会覆盖。
本地验证
dsh/dev/ 下有两个脚本,方便在一台机器上跑通整条链路:
node relay/server.js 8787 # 中继(需要 TALK2COPILOT_TOKEN)
node dsh/dev/create-room.mjs # 建一个 dsh-test 房间
node dsh/dev/fake-peer.mjs alice # 冒充一个同事,收到消息自动回复
已知限制
- 令牌不再支持写进仓库:
cordis.patch.yml 默认留空,改由 tokenFile 或 profile 层覆盖。
- 版本对齐:
dsh/package.json 的版本必须与扩展发版号一致(当前 2026.10.30),否则中继以 4008 拒绝接入;中继协议号与本插件 PROTOCOL_VERSION 不一致时,连接直接失败并把原因写进状态。
- 接收的文件落在 Host 私有收件目录(
$DSH_HOME/copilot2copilot/files,默认 ~/.dsh/...),只读查看,不写入工作区;重名自动加序号,绝不覆盖。
- 文件传输要求双方同时在线:对端离线时不会排队补发。
- 版本门禁由握手头决定:插件建立 WebSocket 时在
x-client-version 上报自身版本,中继拿它与 RELAY_VERSION 精确比对,不一致直接以 4008 拒绝接入。/healthz 只用于本地校验协议号与展示版本,不会自动对齐版本——升级中继后必须同步升级插件。
/copilot2copilot/state 由 Harness Web 服务提供,访问权限跟随 Harness 本身;不要把 Harness Web 服务暴露到不可信网络。
更改日志
各版本的新增、变更与修复见 CHANGELOG.md。