远程连接
Spark Work 远程连接(Telegram / 飞书):连接配置、配对流程、本地 webhook(127.0.0.1:32178)、内置命令(/help /sessions /models 等)、启动项集成。
远程连接让你从手机或另一台设备继续与本地 Spark Work 里的 Agent 对话。当前真实支持 Telegram、飞书、QQ 三个通道:Telegram 走本地长轮询,飞书与 QQ 走官方 WebSocket 长连接,三者都不需要公网地址;同时保留一个本机 HTTP webhook 服务, 用于本地调试与自建网关(微信 Claw 协议类型仍在,但界面没有新建入口)。 所有配置存在 app_settings的remote-connections/data里, 配对、隔离与转发逻辑在 apps/desktop/src/main/services/RemoteConnectionService.ts。
1. 支持的通道
| 通道 | 必填凭据 | 连接方式 | 搭建入口 |
|---|---|---|---|
telegramTelegram | botToken | 本机getUpdates长轮询(不需要公网 webhook) | https://t.me/BotFather |
feishu飞书机器人 | appId、appSecret | @larksuiteoapi/node-sdk官方 WebSocket 长连接 | https://open.feishu.cn/page/openclaw?form=multiAgent |
qqQQ 机器人 | qqBotAppId、qqBotSecret | 官方 WebSocket 网关(api.sgroup.qq.com/bots.qq.com) | https://q.qq.com/#/app/bot |
wechat-claw微信 Claw | clawEndpoint、clawAccessToken | 自建网关协议:本地 webhook 收消息,出站调<endpoint>/send(失败回退 /message) | 协议与解析器仍在,但新建连接的平台卡片里不提供该通道 |
可以同时配置并启用多个连接(每个 Telegram 连接是独立的 botToken 与独立轮询); 界面上的平台入口只有三个:AVAILABLE_REMOTE_CHANNELS = ['telegram', 'feishu', 'qq']。
2. 配置模型:字段、默认值与存储位置
存储位置是app_settings的remote-connections分类、key 为 data,值形如 {global, connections}。
global(全局运行设置),括号内是代码里的默认值:
enabled(true):总开关,关闭时运行时会整体停止。requirePairing(true):未配对用户的消息不会被处理。allowQrPairing(true):是否允许生成二维码配对负载。pairingTtlMinutes(10):配对码有效期。localWebhookPort(32178):本机 HTTP 服务端口。publicBaseUrl(未设置):需要外部回调时使用的基础地址。
connection(每条连接):
| 字段 | 说明 |
|---|---|
id/channel/name/enabled | 身份与开关;id形如remote-xxx |
status | disabled/draft/pending-pairing/ connected/error(界面文案:已停用 / 草稿 / 等待配对 / 已连接 / 错误) |
credentials | 按通道取不同字段(见上一节的必填项) |
commandPrefix | 命令前缀,默认/(Telegram 会同步为 bot command) |
allowedUserIds/allowedChatIds | 允许名单(最多各 200 项) |
routeBindings | 按「外部聊天」各自保存的默认会话/项目/Provider/模型/Agent/权限模式/推理强度 |
defaultSessionId/defaultWorkspaceId/ defaultProviderProfileId/defaultModelId/ defaultAgentId/defaultPermissionMode/ defaultReasoningEffort | 连接级默认值;有 routeBinding 时以聊天级为准 |
allowSharedSession | 是否允许与其他远程连接共享同一会话(默认关闭) |
telegramCommands/qqCommands | 要同步到 Telegram 命令菜单 / QQ 指令面板的命令名清单(默认 14 条,最多 80 条) |
capabilities | 能力开关,见下表 |
pairing/pairedDevices | 当前配对码与已配对设备(remoteUserId、displayName、channelThreadId、pairedAt、lastSeenAt) |
lastConnectedAt/lastError | 最近连接时间与最近错误(「测试配置」会写lastError) |
能力开关(capabilities)的界面标签与默认值:
| 字段 | 界面标签 | 默认 |
|---|---|---|
sendMessages | 发送消息到会话 | true |
switchModel | 切换模型 / Provider | true |
switchSession | 切换会话 | true |
switchAgent | 切换 Agent | true |
manageWorkspace | 查看工作区 | true |
runCommands | 运行内置命令 | true |
approvePermissions | 远程审批权限 | true |
observeDesktop | 观察桌面 | true |
controlDesktop | 控制桌面 | false |
useInternalBrowser | 使用内置浏览器窗口 | false |
transferFiles | 传输文件 | false |
manageRuntime | 管理运行时 | true |
dangerousActions | 高危动作确认 | false |
安全提醒:远程连接的凭据(bot token / AppSecret 等)以明文 JSON存在 app_settings里,不在系统的 Keychain/vault 中;界面用的是普通文本输入框,已保存的值会明文显示,不做掩码。 共享这台机器数据库的人可以看到这些凭据。
3. 本地运行时与两个 HTTP 端点
- 主进程注册远程连接 IPC 时会启动运行时:先跑
startRuntime(要求global.enabled为真),再按每条启用连接启动对应通道的接收端 (Telegram 轮询 / 飞书与 QQ 长连接)。 - 本机 HTTP 服务监听
127.0.0.1:<localWebhookPort>,默认 32178; 如果端口被占用(EADDRINUSE),会退化为让操作系统分配随机端口, 真实端口通过运行态接口回传并在设置页顶部显示。 GET /remote/health:返回{ok: true, ...运行态}。POST /remote/webhook/:channel/:connectionId:找不到连接返回 404; 否则按渠道解析请求体(Telegram 支持callback_query与message; 飞书支持 URL 校验 challenge;QQ 走 dispatch 事件;其余按通用chatId+text结构解析)。- 运行态查询用
remote:runtime-status,返回running、port、localBaseUrl、polling[](Telegram)与longConnections[](飞书/QQ,带lastError)。 - IPC 全景:
remote:list/remote:save/remote:delete/remote:test/remote:create-bot-draft/remote:generate-pairing/remote:confirm-pairing/remote:command-catalog/remote:execute-command/remote:runtime-status;配置变化会通过stream:remote:changed推给界面。
4. 配对:6 位数字码与 QR 负载
- 在设置页填好凭据并保存(保存后状态通常是「草稿」或「等待配对」)。
- 点「生成配对码」或「生成二维码配对」:调用
remote:generate-pairing, 服务端生成 6 位数字码(randomInt(100000, 999999)),有效期由pairingTtlMinutes决定(默认 10 分钟),并把连接状态置为pending-pairing、自动启用该连接。 - QR 负载格式是
spark-agent://remote-pair?connectionId=..&channel=..&code=..&expiresAt=..; 界面上的「复制配对命令」复制的则是/bind <code>。 - 在外部聊天里发送
/bind <code>(正则接受 6~12 位大写字母数字,实际生成为 6 位数字)。 服务端校验码相等且未过期,然后把该聊天写入pairedDevices、清掉pairing、 状态置为connected,并在同通道回复结果。 - 也可以走
remote:confirm-pairing手动确认(设置页的配对区支持手工填入远程用户 ID/名称), 适合自建网关等拿不到入站消息的场景。 - 重新生成配对码会让旧码立即失效;
requirePairing为真时,未配对聊天发来的消息会被直接忽略。
5. 内置命令清单(按能力分组)
命令目录来自服务端的COMMAND_CATALOG,可通过remote:command-catalog 查询;每条命令都绑定一个能力开关,能力关闭时执行会返回被拒绝的说明。 前缀默认是/,可用commandPrefix改。
| 用到的能力 | 命令 |
|---|---|
system | /start、/help、/status |
sendMessages | /send <message> |
switchSession | /sessions [all|idle|running|error] [页码]、/use-session <序号|名称|sessionId>、/new-session [序号|名称|workspaceId] |
switchModel | /models [页码]、/use-model <序号|名称|modelId>、/providers [页码]、/use-provider <序号|名称|providerProfileId>、/channels [页码]、/use-channel <序号|名称|渠道ID> |
switchAgent | /agents [页码]、/use-agent <序号|名称|agentId> |
manageWorkspace | /workspaces [页码]、/projects [页码]、/use-project <序号|名称|项目ID>、/open-workspace <path>、/add-project <path> |
manageRuntime | /reasoning、/use-reasoning <minimal|low|medium|high|xhigh|max>、/progress、/queue、/history、/cancel、/stop |
approvePermissions | /permissions、/use-permission <manual|auto|plan|full> |
observeDesktop | /screen、/windows |
controlDesktop(默认关闭) | /focus <序号|窗口标题>、/click <x> <y>、/type <text>、/hotkey <keys> |
dangerousActions(默认关闭) | /confirm <code> |
新连接默认同步 14 条命令到平台原生菜单:start、help、status、projects、sessions、new-session、channels、models、agents、reasoning、permissions、progress、queue、cancel。Telegram 走setMyCommands(清单为空时改调deleteMyCommands); QQ 注册为「指令面板」,单条面板名超过 14 字符会被跳过。
6. 入站消息:命令、普通消息与会话隔离
已配对聊天的消息按下面的顺序处理:
- 以命令前缀开头的消息→ 走内置命令目录;先检查该命令所需能力是否开启, 再执行会话/项目/模型/Agent/推理/权限等操作。Telegram 与 QQ 支持分页与按钮式选择, 点击按钮会原位更新消息(Telegram 用
editMessageText)而不是刷屏。 - 普通消息→ 包装成一次远程 turn 后提交给
SessionService。 包装内容是【本轮远程渠道:X】+ 渠道约束 + 文件能力说明(开启「传输文件」时要求用mcp__spark_files__present_files提交真实文件,未开启时明确禁止声称已发送文件); 会话与桌面端界面里只显示你的原话,不显示这段内置说明。 - 新聊天没有绑定会话→ 用
defaultWorkspaceId(或该聊天的 routeBinding) 在对应项目里新建会话;没有项目时创建「不使用项目」的会话, 并把这个绑定写回该聊天的 routeBinding。 - 新建会话的权限模式:优先用连接/聊天配置的
defaultPermissionMode, 否则按适配器取默认的自动审批档(Claudeclaude-auto、Codexcodex-auto-review、Sparkspark-auto)。 「完全访问」档仍需在连接里显式配置,并且高危操作仍受权限层保护。 - 配置了默认 Provider / 模型 / Agent / 推理强度时,这些值在发送时应用; 每条远程回复只会发回本轮来源渠道,历史消息里提到的其他渠道不会改变发送目标。
会话隔离的真实规则:
- 同一条机器人下的私聊与群聊是不同聊天(
connectionId+externalId组成唯一路由键),各自保存默认会话、项目、模型与选择状态,互不覆盖。 - 一个会话已被其他连接/聊天绑定时,新连接不能直接复用:
/use-session会返回 「会话已绑定到其他远程聊天」或「会话已被其他远程连接占用」。 - 只有所有相关连接都显式开启「跨连接共享会话」(
allowSharedSession)时, 才允许共享;/status会明示「会话隔离:独立 / 与 X、Y 显式共享」。 - 定时任务的结果只在「该会话恰好由一个已启用聊天拥有」时回传 (
resolveScheduledRemoteRoute:路由数不等于 1 就返回 null,不猜接收者)。
7. 平台运行时要点
Telegram
- 接收:
getUpdates长轮询,timeout=25,allowed_updates=["message","callback_query"];按 offset 递增确认。 - 收到已配对用户消息后优先给原消息加 👀 反应(
setMessageReaction); 群组禁用反应时静默跳过,不影响后续处理。 - 输入状态:长任务期间每 4 秒续期一次
sendChatAction(typing); 正文以「草稿」形式在同一条消息里增量编辑,编辑节流 1 秒,单条最长 3900 字符(超出保留尾部)。 - Markdown 会转换为 Telegram HTML 富文本,解析失败自动回退纯文本; 出站文本按 3900 字符分片发送。
- 图片:入站图片经
getFile下载到{userData}/attachments/remote/telegram/<日期>/后作为附件提交给会话; 出站先尝试sendPhoto(本地文件走 multipart 直传),被拒或失败时改用sendDocument,直传失败还会经临时存储中转。
飞书
- 用 App ID / App Secret 换
tenant_access_token(带缓存与过期时间)。 - 回复走
im/v1/messages,receive_id_type由会话 id 形态推导 (chat_id/open_id);消息体是 interactive 卡片,文本按 10000 字符分片。 - 收到配对消息后给源消息加
Typing表情反应。 - 图片:入站从消息资源接口下载并提交会话;出站先上传到
im/v1/images, 再用image_key发送原生图片消息。
- 鉴权:
POST https://bots.qq.com/app/getAppAccessToken取access_token, 后续请求头是Authorization: QQBot <token>。 - 长连接:先取网关地址再连 WebSocket,支持 Resume;当服务端拒绝订阅(
op:9或关闭码 4013/4014)时会逐级降低 intents 后重连。 - 消息 API:频道用
/channels/<id>/messages,群聊用/v2/groups/<id>/messages,单聊用/v2/users/<id>/messages; 文本msg_type: 0,按字节分片(频道 1900、群聊/单聊 1000),优先被动回复(引用msg_id+ 递增msg_seq), 超窗后退化为主动消息。 - 图片:入站从腾讯图片 CDN 下载;频道出站用 multipart 的
file_image字段, 单聊/群聊先上传拿到file_info再发msg_type: 7富媒体消息。 - 配对的 QQ 机器人会把
qqCommands同步为指令面板(超 14 字符的面板名跳过)。
三个渠道的入站图片都在配对鉴权之后下载,统一限制 20 MB,并按文件头(magic bytes)校验, 只接受 PNG / JPEG / WebP;不合法会报「仅支持 PNG、JPEG 或 WebP 图片」。 出站图片如果给的是网络 URL,会先做公开地址校验再下载。此能力需要连接里开启「传输文件」。
8. 设置 UI 结构
- 入口:设置 → 远程连接(左栏「生态」分组)。页面说明文案是 「通过 Telegram、飞书、QQ 从远程桌面或移动端与 Spark Work 通信」。
- 运行态条:左侧显示
localBaseUrl(未启动时显示「本地 webhook 服务未启动」), 下方提示「N 个渠道已启用,远程消息会进入各聊天独立绑定的会话」或「启用任一渠道后,远程消息才会被接收」; 右上角徽标显示「运行中 / 未运行」与「已连接 / 总数」。右侧有「刷新」。 - 平台入口卡:Telegram / 飞书 / QQ 三个图标卡,点头部按钮会创建草稿并打开对应平台控制台。
- 连接列表卡:平台图标、名称、通道、状态标签、启用状态、已配对设备数、默认会话。
- 编辑模态:宽 980、高度
min(68dvh, 680px),左侧段导航固定五段——基础、凭证、授权、配对、命令;右侧可滚动内容,底部固定操作栏:删除、取消、测试配置、保存连接(未保存过的新连接不能测试/生成配对)。 - 窄窗口下自动切单列布局。
注意:远程连接页不控制开机自启。开机自启动在设置 → 通用页(开关文案「开机自启动 · 登录系统后自动启动 Spark Work」), 底层走 app:get-startup-settings/app:set-startup-settings(openAtLogin+openAsHidden),平台不支持时会提示「当前系统环境不支持读取登录项」。
9. 一键创建 Bot
每个通道都有remote:create-bot-draft:创建一个草稿连接(status: draft), 返回consoleUrl与instructions,需要时同时打开控制台。 真实返回内容:
| 通道 | 默认名称 | 控制台地址 | 要点摘录 |
|---|---|---|---|
telegram | Telegram Bot | https://t.me/BotFather | 创建 bot 复制 token → 填回 Spark Work → 生成配对码发给 bot |
feishu | 飞书机器人 | https://open.feishu.cn/page/openclaw?form=multiAgent | 用 openclaw 快捷入口创建自建应用并预选机器人能力,复制 App ID / App Secret |
qq | QQ 机器人 | https://q.qq.com/#/app/bot | 在 QQ 开放平台申请「群聊」与「单聊」消息能力;单聊加好友即可,群聊需 @机器人;保存后命令自动注册为指令面板 |
wechat-claw | 微信 Claw | 远程连接文档页 | 自建网关协议,没有官方统一搭建入口(界面也不提供新建) |
平台侧授权、审核与复制凭据仍然要你手动完成;「一键」只做本地草稿 + 打开控制台。 保存后务必点「测试配置」:它会校验必填字段是否齐全, 缺少字段会把状态置为error、停用连接,并写入lastError(形如「缺少字段:appId, appSecret」)。
10. 常见坑与排查
- **需要公网 IP 吗?**不需要。Telegram 是本机出站长轮询,飞书/QQ 是官方 WebSocket 长连接;本地 HTTP 服务只监听
127.0.0.1,用于本地调试与自建网关。 - 发消息没反应:先看设置页顶部是否「运行中」,再看该连接状态是不是
connected;如果状态是pending-pairing,说明还没完成/bind。QQ 群聊还需要 @机器人(或走指令面板)。 - 「会话已绑定到其他远程聊天」:这是有意的隔离。选择另一个会话、
/new-session新建,或在所有相关连接里都开启「跨连接共享会话」。 - 配对码过期:默认 10 分钟,重新生成即可(旧码立即失效)。
- 图片发不出去 / 收不到:确认连接里开启了「传输文件」能力; 入站图片限 20 MB 且只支持 PNG/JPEG/WebP;出站图片被平台拒绝时会自动降级(Telegram 图片→文件、 飞书上传失败会报错、QQ 大图可用临时 URL 中转)。
- 改了
localWebhookPort但 URL 还是老的: 端口被占用时会退化成随机端口,以运行态回传的localBaseUrl为准;改端口后要重启运行时 (保存配置会触发运行态同步)。 - 想让远程只能发消息、不能切模型/看桌面:在「授权」段关掉对应能力开关即可—— 命令目录里每条命令都绑定能力,关闭后会直接返回拒绝说明。
- 安全提醒:凭据在 SQLite 里是明文;
requirePairing建议保持开启; 桌面控制类能力(controlDesktop、dangerousActions)默认关闭,开启前请确认你信任这些聊天的使用者。 - 微信 Claw 找不到入口:协议类型与 webhook 解析仍然存在(
clawEndpoint+clawAccessToken),但当前界面白名单只有 Telegram / 飞书 / QQ;它依赖一个外部自建网关, 目前不在新建入口里暴露。
速查表
| 项目 | 内容 |
|---|---|
| 可新建通道 | telegram / feishu / qq(wechat-claw 协议保留但无界面入口) |
| 配置存储 | app_settings → remote-connections/data(凭据为明文 JSON) |
| 本地服务 | 127.0.0.1:32178(占用则随机端口),仅本机可访问 |
| 端点 | GET /remote/health · POST /remote/webhook/:channel/:connectionId |
| 配对 | 6 位数字码,默认 10 分钟;QR 负载 spark-agent://remote-pair?connectionId&channel&code&expiresAt |
| 命令前缀 | 默认 /(commandPrefix 可改) |
| 默认同步命令 | start help status projects sessions new-session channels models agents reasoning permissions progress queue cancel |
| 图片限制 | 20 MB,仅 PNG / JPEG / WebP,需要开启「传输文件」能力 |
| 桌面控制 | observeDesktop 默认开;controlDesktop / useInternalBrowser / transferFiles / dangerousActions 默认关 |
| 运行态接口 | remote:runtime-status(polling[] 与 longConnections[]) |
| 变化推送 | stream:remote:changed |
上手清单:用 Telegram 远程控制 Spark Work
从创建 Bot 到完成配对并发第一条消息
- 在 Telegram 里用 BotFather(https://t.me/BotFather)创建 Bot,复制 bot token
- 打开设置 → 远程连接,点 Telegram 平台卡创建草稿,进入「凭证」段粘贴 Bot Token
- 在「授权」段按需开关能力(默认已开:发送消息、切换会话/模型/Agent、观察桌面;默认关闭:控制桌面、传输文件、高危动作确认)
- 保存连接并点「测试配置」,确认状态不再是「错误」且没有缺少字段的提示
- 在「配对」段点「生成配对码」(或二维码配对),然后在 Telegram 里给 Bot 发 /bind <配对码>
- 状态变为「已连接」后,直接发普通消息即可提交任务;也可以用 /sessions 选会话、/models 换模型、/status 看当前绑定
- 如果列表很长,用消息下方的分页/选择按钮而不是手动输入序号;需要清空当前任务时发 /cancel
常见问题
需要公网 IP 或反向代理吗?
不需要。Telegram 用本机 getUpdates 长轮询,飞书与 QQ 用官方 WebSocket 长连接;本地 HTTP 服务只监听 127.0.0.1(默认 32178,端口占用时退化为随机端口),供本地调试与自建网关使用。
配对码怎么生成、多久过期?
在设置页点「生成配对码」或「生成二维码配对」,服务端生成 6 位数字码,有效期由 global.pairingTtlMinutes 决定(默认 10 分钟)。在外部聊天发送 /bind 加配对码完成配对,也可以用手动确认入口。
一个 Telegram 机器人能同时给多个人用吗?
可以,但每个聊天是独立路由(connectionId + externalId),各自绑定自己的会话、项目、模型与选择状态。只有所有相关连接都开启「跨连接共享会话」时才会共用一个会话。
远程会话默认什么权限?
优先用连接或聊天级的 defaultPermissionMode;未配置时按适配器取自动审批档:Claude claude-auto、Codex codex-auto-review、Spark spark-auto。完全访问档需要显式配置,且高危操作仍受权限层保护。
凭据存在哪里?安全吗?
远程连接的 botToken / AppSecret 以明文 JSON 存在 SQLite 的 app_settings(remote-connections/data)里,不在系统 Keychain 或加密 vault 中,设置页用的是普通文本输入框,已保存的值会明文显示。请确保只有你信任的人能访问这台机器的用户数据目录。
支持微信吗?
协议里保留 wechat-claw(clawEndpoint + clawAccessToken,可经本地 webhook 收发),但当前新建连接的平台卡片只提供 Telegram / 飞书 / QQ,因为微信通道依赖一个尚未内置的自建网关。