在 Telegram 里收发消息并接入智能体对话
30.2/100
综合分
2
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/12 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add lovedheart/dsh-plugin-telegram该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:需留意
是什么
dsh 原生插件 · platform
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 33 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
⚠︎ 实装验证未通过(unknown · 2026/10/12) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 27 处,展示前 27 条)
- 读取本地文件lib/index.js:997
form.append('file', new File([new Uint8Array(readFileSync(localPath))], 'voice.ogg', { type: 'audio/ogg' })); - 发起网络请求lib/index.js:1006
res = await fetch(`${sttEndpoint}/v1/audio/transcriptions`, { method: 'POST', body: form, signal: controller.signal }); - 执行外部命令固定命令lib/index.js:1069
const ff = spawn('ffmpeg', [ - 读取本地文件lib/index.js:1086
const buf = readFileSync(wavPath); - 发起网络请求lib/index.js:2193
const resp = await fetch(`${ttsEndpoint}/tts`, { - 写入本地文件lib/index.js:2207
writeFileSync(wavPath, buf); - 删除本地文件递归lib/index.js:2219
rmSync(workDir, { recursive: true, force: true }); - 读取本地文件lib/index.js:3990
const st = statSync(p); - 执行外部命令固定命令lib/index.js:3992
const out = spawnSync('zstd', ['-q', '-dc', p], { encoding: 'utf8', timeout: 15000 }); - 读取本地文件lib/index.js:4019
const arch = wsSvc?.archiveSession && !existsSync(marker); - 写入本地文件lib/index.js:4031
writeFileSync(marker, new Date().toISOString() + '\n', 'utf8'); - 发起网络请求lib/client.js:101
const resp = await fetch(url, { - 读取本地文件lib/client.js:242
const data = await readFile(filePath); - 发起网络请求lib/client.js:261
const resp = await fetch(url, { method: 'POST', body: form, signal: withTimeout(signal, timeoutMs) }); - 读取本地文件lib/client.js:415
const st = statSync(abs, { throwIfNoEntry: false }); - 写入本地文件lib/client.js:623
mkdirSync(dirname(localPath), { recursive: true }); - 发起网络请求lib/client.js:626
const resp = await fetch(url, { signal: withTimeout(opts.signal, DEFAULT_DOWNLOAD_TIMEOUT_MS) }); - 写入本地文件lib/client.js:632
await writeFile(localPath, buf); - 读取本地文件lib/poller.js:69
const raw = readFileSync(this._offsetPath, 'utf8'); - 写入本地文件lib/poller.js:86
mkdirSync(dir, { recursive: true }); - 写入本地文件lib/poller.js:88
writeFileSync(tmp, JSON.stringify({ offset: this.lastOffset, at: Date.now() })); - 写入本地文件lib/poller.js:89
renameSync(tmp, this._offsetPath); - 读取本地文件lib/approval.js:181
const raw = readFileSync(storePath, 'utf8'); - 写入本地文件lib/approval.js:208
mkdirSync(dir, { recursive: true }); - 写入本地文件lib/approval.js:215
writeFileSync(tmp, JSON.stringify(payload, null, 2)); - 写入本地文件lib/approval.js:216
renameSync(tmp, storePath); - 读取本地文件lib/inbound-media.js:159
const bytes = new Uint8Array(await readFile(dl.localPath));
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-tools给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 收发Telegram消息
- 接入智能体对话
DSH 插件,用来接入 Telegram 机器人
README
由 DeepSeek 最新模型翻译生成dsh-plugin-telegram
DeepSeek Harness (DSH) 插件,用于接入 Telegram Bot。提供收发 Telegram 消息的工具,可选开启长轮询接收消息。
基于 QwenPaw 的 Telegram channel 实现改写,适配 DSH Cordis 插件框架。
功能
- 发送消息,支持 Markdown/HTML 格式
- 发送图片、文档、音频、视频和语音,可用 file_id、URL 或本地路径(语音文本由本地 Qwen3-TTS 服务合成)
- 入站语音转写(🎧)—— 语音由本地 Whisper 代理转写,无需往返即可回复
- 编辑和删除已有消息
- 长轮询接收消息(可选)
- 智能体集成:把 Telegram 消息注入 DSH 智能体循环,实现 AI 对话
- 多 bot:一个插件实例跑多个 Telegram bot(bots: 配置,每个 bot 独立 token/路由、状态隔离)
- 实时子代理面板:每个聊天置顶显示子代理实时状态(🧩)
- /autopilot:按聊天开启全自动模式(全局写权限 + 自动放行 + 自动采纳)
- 访问控制:通过允许的聊天/用户列表限制
- 自动重连,指数退避
- 限流处理,符合 Telegram API 规范
- 消息分片,内容超过 Telegram 4096 字符上限时自动切分
展开完整文档(README 剩余 1 节)
dsh-plugin-telegram
DeepSeek Harness (DSH) 插件,用于接入 Telegram Bot。提供收发 Telegram 消息的工具,可选开启长轮询接收消息。
基于 QwenPaw 的 Telegram channel 实现改写,适配 DSH Cordis 插件框架。
功能
- 发送消息,支持 Markdown/HTML 格式
- 发送图片、文档、音频、视频和语音,可用 file_id、URL 或本地路径(语音文本由本地 Qwen3-TTS 服务合成)
- 入站语音转写(🎧)—— 语音由本地 Whisper 代理转写,无需往返即可回复
- 编辑和删除已有消息
- 长轮询接收消息(可选)
- 智能体集成:把 Telegram 消息注入 DSH 智能体循环,实现 AI 对话
- 多 bot:一个插件实例跑多个 Telegram bot(bots: 配置,每个 bot 独立 token/路由、状态隔离)
- 实时子代理面板:每个聊天置顶显示子代理实时状态(🧩)
- /autopilot:按聊天开启全自动模式(全局写权限 + 自动放行 + 自动采纳)
- 访问控制:通过允许的聊天/用户列表限制
- 自动重连,指数退避
- 限流处理,符合 Telegram API 规范
- 消息分片,内容超过 Telegram 4096 字符上限时自动切分
提供的工具
| 工具 | 说明 |
| telegram_send_message | 向聊天发送文本消息 |
| telegram_send_photo | 向聊天发送图片 |
| telegram_send_document | 向聊天发送文档 |
| telegram_send_audio | 发送音频文件(file_id、URL 或本地路径) |
| telegram_send_video | 向聊天发送视频 |
| telegram_send_voice | 用本地 Qwen3-TTS 把文本合成语音,以 OGG Opus 发送 |
| telegram_edit_message | 编辑已有消息 |
| telegram_delete_message | 删除消息 |
| telegram_get_info | 获取所有已配置 bot 的信息 —— 返回数组,每个 bot 一项 |
| telegram_get_updates | 手动拉取新更新 |
| telegram_get_last_assistant_message | 读取当前会话最新一条助手消息(调试/测试用) |
Telegram 会话管理命令(直接发给 bot)
参考 QwenPaw 的命令风格实现:
| 命令 | 作用 |
| /new 或 /clear | 新建会话并路由过去(清空上下文;若当前会话在运行会先停止) |
| /sessions | 列出活动会话(👉=当前,🏠=默认),s-xxxx 短 id + 状态 + 会话标题(长标题折两行) |
| /use | 切换到指定会话(支持 s-xxxx 短 id、hex 前缀、完整 id) |
| /stop | 停止当前会话正在执行的任务 |
| /compact | 压缩当前会话历史为摘要(走 DSH compaction 服务) |
| /history [n] | 查看最近 n 条对话(默认 12) |
| /model | 查看当前会话的模型 |
| /model list | 列出可用 provider/model |
| /model <provider>:<model> | 为当前会话切换模型(下一轮请求生效) |
| /approval | 查看已记住的「一直允许」授权(/approval clear 清空全部,/approval <ruleKey> 清单条) |
| /autopilot | 全自动模式(on/off/status):全局写权限 + 自动放行授权 + 自动采纳推荐方案(⚠️ 有安全隐患) |
| /start 或 /help | 显示帮助 |
普通消息路由到当前聊天的 active 会话;无显式路由时落到默认(第一个)会话。
/new 创建的会话继承默认会话的工作目录(cwd)、模型(provider/model,来自 agent.options)和 agent preset(meta.agentPreset,setup 时 agentPresets.mount 挂载)——preset 决定工具目录(Read/Write/Edit/Bash 等)、提示段和 skill 清单,随插件卸载一起销毁。
命令菜单在 poller 启动时通过 Bot API setMyCommands 注册,Telegram 客户端输入 / 即可看到全部命令的自动补全。
Installation
1. 设置 Token(三种方式,优先级从高到低)
方式一:DSH Credentials 系统(推荐,最安全)
编辑 $DSH_HOME/.credentials.yaml(权限 0600,只有所有者可读)。
该文件是顶层 YAML mapping:key 为凭据引用名,value 为字符串(建议加引号): TELEGRAM_BOT_TOKEN: "你的token" 也可在 DSH Web UI 的 Credentials 设置页写入。插件启动时通过
ctx.credentials.resolve('TELEGRAM_BOT_TOKEN') 读取。
注:DSH 没有 dsh credential set 子命令。
方式二:环境变量
一次性传入
TELEGRAM_BOT_TOKEN='你的token' dsh web --patch ./cordis.yml
或写入 ~/.bashrc / ~/.zshrc
export TELEGRAM_BOT_TOKEN='你的token'
方式三:写在 cordis.yml 中(不推荐,会明文存储)
config:
botToken: '你的token' # 不推荐
多 bot:设置多个 token
配置 bots: 数组跑多个 bot 时,每个 bot 一个 token,互相隔离。隔离的关键是让每个 bot 指向不同的来源键——否则两个 bot 都会默认读同一个 TELEGRAM_BOT_TOKEN,第二个 bot 会错误地拿到第一个 bot 的 token。
三种方式的多 bot 写法(优先级从高到低,与单 bot 一致:
bots[].token(明文)→ 环境变量 process.env[envKey] → DSH credentials):
方式一:DSH Credentials 系统(推荐) — 在 $DSH_HOME/.credentials.yaml 里为每个 bot 写一条独立的凭据(key 名自取),再让每个 bot 的 envKey 指向对应的 key:
$DSH_HOME/.credentials.yaml
TELEGRAM_BOT_TOKEN: "alice的token" TELEGRAM_BOT_TOKEN_BOB: "bob的token"
cordis.yml
config:
pollingEnabled: true
bots:
- id: alice
envKey: 'TELEGRAM_BOT_TOKEN' # 默认值,可省略
- id: bob
envKey: 'TELEGRAM_BOT_TOKEN_BOB' # 指向自己的凭据 key
方式二:环境变量 — 同样每个 bot 一个变量,envKey 指向它:
export TELEGRAM_BOT_TOKEN='alice的token'
export TELEGRAM_BOT_TOKEN_BOB='bob的token'
config:
bots:
- id: alice # envKey 默认 TELEGRAM_BOT_TOKEN
- id: bob
envKey: 'TELEGRAM_BOT_TOKEN_BOB'
方式三:明文写在 cordis.yml 的 bots[].token(不推荐)
config:
bots:
- id: alice
token: '123456...:AA...'
- id: bob
token: '789012...:BB...'
要点:
- 不填 envKey 的 bot 一律读 TELEGRAM_BOT_TOKEN——多 bot 时除了第一个 bot,其余每个都必须给 envKey(或 credentialKey)指定不同的键。
- 某个 bot 的 token 三个来源(明文 / 环境变量 / credentials)都取不到时,该 bot 跳过并打 warn 日志(不报错);全部 bot 都取不到时插件降级为 tools-only(工具仍注册,调用时返回提示)。
- 每个 bot 的完整字段与回退规则见下方 "Multi-Bot Configuration"。
2. Configure in cordis.yml
- insert:
- id: telegram
name: '/path/to/dsh-plugin-telegram/lib/index.js'
config:
botToken 可以不填,插件按以下优先级自动查找:
1. config.botToken(明文,不推荐)
2. 环境变量 TELEGRAM_BOT_TOKEN
3. DSH Credentials 系统中的 TELEGRAM_BOT_TOKEN defaultChatId: '123456789' pollingEnabled: false
3. Start DSH with the plugin
dsh web --patch ./cordis.yml
Configuration
| Option | Type | Default | Description |
| botToken | string | "" | Bot Token。可留空,插件会按优先级查找:config → 环境变量 → DSH Credentials |
| baseUrl | string | "" | 自定义 Telegram API base URL |
| allowedChats | string[] | [] | 允许的 chat ID(留空 = 全部) |
| allowedUsers | string[] | [] | 允许的 user ID(留空 = 全部) |
| requireMention | boolean | false | 群组中需要 @mention |
| pollingEnabled | boolean | false | 启用长轮询 |
| longPollTimeout | number | 30 | 轮询超时,单位秒 |
| defaultChatId | string | "" | 消息的默认 chat ID |
| maxMessageLength | number | 4000 | 拆分前的最大字符数 |
| parseMode | string | "HTML" | 解析模式(HTML 或 Markdown) |
| injectToAgent | boolean | true | 把消息注入智能体循环 |
| agentResponseMode | string | "tool" | 响应模式:'tool' 或 'direct' |
| replyPrefix | string | "" | 智能体响应的可选前缀 |
| directReplyTimeoutSec | number | 3600 | (direct 模式)回复转发 watcher 的绝对安全上限(秒)。watcher 会感知忙碌状态——智能体运行期间它一直跟随(长工具调用轮次也没问题),一旦智能体空闲且出现新消息,就立刻转发回复;这个上限只用来兜住病态卡死。短回复仍会在几秒内转发。 |
| progressEnabled | boolean | true | 智能体工作期间,在 Telegram 上显示实时轨迹(工具调用 + 思考)。direct 和 tool 两种响应模式都可用。 |
| progressDelaySec | number | 5 | 只有轮次运行超过这么多秒,才发送轨迹(短轮次什么都不显示)。 |
| progressIntervalMs | number | 5000 | 原地编辑之间的最小间隔(Telegram 对编辑限流约 1 次/秒/条消息;降低频率可减轻 API 负载)。 |
| progressTrailLines | number | 3 | 流式页脚:模型工作期间,在原地回复下方显示多少条最近活动行(💭/🔧)(0 = 关闭)。让消息在长时间工具调用/推理期间保持可见的变动。 |
| progressPerBlockChars | number | 240 | 每条轨迹行的最大字符数(一个推理块或一次工具调用)。 |
| progressMaxChars | number | 1500 | 整条轨迹消息的最大字符数(从尾部截断,保留最新内容)。 |
| progressTimeoutSec | number | 3600 | 轨迹自动清理前的绝对上限(仅针对病态卡死)。 |
| approvalEnabled | boolean | true | 当智能体的权限策略为 ask,且某次工具调用需要决策(例如沙箱升级)时,向所属 chat 发送一张带内联键盘的批准卡片(✅ 批准 / 🔁 一直允许 / ❌ 拒绝),而不是直接失败关闭。见下文「工具守卫批准」。 |
| approvalTimeoutSec | number | 1800 | 批准卡片等待点击多久后过期(cancelled)。0 = 不过期。 |
| approvalForDefaultAgent | boolean | true | 同时把部署的共享默认智能体发起的请求推送到手机。执行 /new 之前,普通 Telegram 消息会路由到该智能体,所以正是这个选项让卡片出现在你通常测试的状态下。设为 false 则只推送本插件显式创建的智能体(telegram-)的卡片。需要 defaultChatId。 |
| approvalAlwaysPath | string | '' | 「🔁 一直允许」记住的内容持久化到哪个文件(默认 $DSH_HOME/telegram-approval-always.json)。设为绝对路径可换个位置。 |
| questionsEnabled | boolean | true | 智能体调用 ask_user_question(选一个选项 / 自己输入)时,向所属聊天发一张带内联键盘的问题卡片,并就地作答(进程内 waterfall 应答器),这样只用手机的用户不必干等浏览器。见下文「问题卡片」。 |
| questionsForDefaultAgent | boolean | true | 同时把部署的共享默认智能体提出的问题推送到手机(对应 approvalForDefaultAgent)。设为 false 则只推送本插件显式创建的智能体(telegram-)的卡片。需要 defaultChatId。 |
| autopilotEnabled | boolean | true | /autopilot 命令是否可用。设为 false 则完全禁用全自动模式。见下文「Autopilot(全自动模式)」。 |
| autopilotSandboxMode | string | danger-full-access | 聊天处于 autopilot 期间追加到会话的沙箱模式(「全局写入」那一半)。默认给完整磁盘访问权限。 |
| autopilotWindowMs | number | 10000 | autopilot 收到 ask_user_question 通知后,等多久自动采纳推荐选项(0 = 立即采纳)。这段时间留给你点 ✋ 接管 接手。 |
| questionsTimeoutSec | number | 1800 | 问题卡片等多久没答案就自动取消(智能体回合解除阻塞)。0 = 不过期。对应 approvalTimeoutSec。 |
| sttEndpoint | string | http://127.0.0.1:18068 | 用来转写收到的语音消息的 OpenAI 兼容 Whisper 基础 URL(与 dsh-tool-audio 的 transcribe_audio 访问的是同一个服务)。 |
| voiceTranscribe | boolean | true | 用户发来语音消息时,转写它,并把文字回复在语音气泡下方(🎧)。需要 forwardInboundMedia。 |
| voiceTranscribeLanguage | string | auto | 强制指定转写语言代码(如 zh/en),或设为 auto 让 Whisper 自行检测。 |
| voiceTranscriptToAgent | boolean | true | 同时把转写文本放进注入给智能体的消息里,这样它已经有文字,不会再跑一遍 transcribe_audio。 |
| subagentBoardEnabled | boolean | true | 会话派生子代理期间,每个聊天保留一条置顶消息,实时显示每个子代理(任务 + 状态,以及它正在做什么)。见下文「实况子代理面板」。 |
| subagentBoardPin | boolean | true | 置顶面板消息,让它一直待在聊天顶部(「固定」的那部分)。 |
| subagentBoardRefreshMs | number | 2000 | 面板多久重新读取一次活跃子会话并重新渲染(无论怎样,编辑都会被限流到约 1.5 秒)。 |
| subagentBoardIncludeDescendants | boolean | false | 设为 true 时,也显示嵌套子代理(子代理又派生出子代理)。默认只显示直接子级。 |
| subagentBoardMaxRows | number | 10 | 显示的子代理数量上限,超出部分折叠成一行 … 另有 K 个未显示(让消息保持在 Telegram 的 4096 字符限制内)。 |
| verbose | boolean | false | 启用 debug 和 info 日志(默认:只记错误) |
多机器人配置
在一个插件实例里运行多个 Telegram 机器人。添加顶层 bots: 数组,每一项就是一个机器人。这是 v0.6.0 的功能——旧的单机器人配置(顶层字段,没有 bots)照常可用,无需改动(见下文「向后兼容」)。
每个 bots[] 项支持的字段与顶层配置里的单机器人字段相同。某项字段未设置时,回退到同名字段的顶层值,所以你只需写出每个机器人不同的部分。
| 字段 | 类型 | 默认值(未设置时) | 说明 |
| id | string | 自动 | 用于路由的机器人 id(见「id 自动生成」)。在 bots 中必须唯一(重复会在启动时抛错)。 |
| token | string | 顶层 botToken | 机器人 token。botToken 是可接受的别名(项上两个名字都能用)。 |
| envKey | string | "TELEGRAM_BOT_TOKEN" | 从哪个环境变量读取 token(按机器人区分,所以两个机器人读两个不同的变量——不会串)。 |
| credentialKey | string | 与 envKey 相同 | 该机器人 token 回退使用的 DSH credentials key。 |
| baseUrl | string | 顶层 baseUrl | 按机器人自定义 API base URL。 |
| defaultChatId | string | 顶层 defaultChatId | 按机器人设置的默认聊天(用于卡片/审批路由和 /new 后续消息)。 |
| allowedChats | string[] | 顶层 allowedChats | 按机器人设置的允许聊天 id(空 = 全部)。 |
| allowedUsers | string[] | 顶层 allowedUsers | 按机器人设置的允许用户 id(空 = 全部)。 |
| requireMention | boolean | 顶层 requireMention | 按机器人设置的群组 @提及 要求(对照该机器人自己的 getMe 过滤)。 |
| injectToAgent | boolean | 顶层 injectToAgent | 按机器人设置是否把消息注入智能体循环。 |
| agentResponseMode | string | 顶层 agentResponseMode | 按机器人设置的 'tool' / 'direct' 回复模式。 |
其他顶层字段(longPollTimeout、maxMessageLength、parseMode、pollingEnabled、replyPrefix 等)在项上设置时同样可读。
id 自动生成 —— 当某项没有 id 时:
- 它有可解析的 token → id = "bot-" + token.slice(0, 8);
- 否则(没有 token)→ id = "default"。
- 旧的单机器人回退路径(没有 bots 字段)始终生成 id "default"。
向后兼容 —— 省略 bots(或设为 bots: [] / YAML 强制转换出的 {}):插件只构建 一个 bot,id 为 "default",完全来自顶层字段。现有的单 bot cordis.yml 文件无需改动,行为逐位一致。
缺少 token 时的降级 —— 某个 bot 的 token 解析结果为空(配置 + envKey + credentialKey 全空)时,跳过该 bot 并打一条 warn 日志;绝不抛错。它仍会被注册(所以 clientFor/meFor 会报出精确的「已跳过」错误),但不会获得 client/poller/命令菜单。如果所有 bot 都被跳过,插件以 tools-only 模式运行(工具仍会注册;调用时它们的 !client 守卫会返回一条有用的错误)—— 与旧路径相同的「缺少 token → tools-only」语义。
每个 bot 的 token 解析优先级(逐项,按顺序): token(明文)→ process.env[envKey] → DSH credentials 的 envKey → DSH credentials 的 credentialKey(最后一项仅当它与 envKey 不同时才用)。推荐给每个 bot 用不同的 envKey,避免第二个 bot 误拿到第一个 bot 的 token。
多 bot 下的发送工具与 telegram_get_info
- 每个发送/编辑/删除/媒体工具(telegram_send_message、telegram_send_photo、telegram_send_document、telegram_edit_message、telegram_delete_message 等)现在都接受可选的 bot 参数(bot id)。解析顺序: (1) 显式 bot —— 必须是已知且已连接的 bot,否则返回清晰的工具错误; (2) 当恰好只有一个 bot 路由该 chatId 时,用该目标 chat 的归属 bot(复合键反查,即 poller 最近为该 chat 路由过消息的那个 bot); (3) 当两个 bot 共用一个 chatId 时(例如两个 bot 与同一 owner 的私聊),由发起调用的智能体决定(v0.6.5):发送走拥有该智能体所在工具调用的那个 bot —— 而不是注册表里的第一个 bot,过去正是这一点让 bot B 的照片从 bot A 发出来; (4) 第一个/旧版 bot。单 bot 配置不受影响。
- telegram_get_info 现在返回数组 —— 每个已配置的 bot 一项:{ id, username, botId, name, connected, defaultChat }。旧版单 bot 配置只返回一项(id: "default"),所以单 bot 调用方看到的结果和以前一样,只有一项。
- telegram_get_updates 接受 bot 参数;每个 bot 保留自己的手动轮询 offset(各 bot 独立,也独立于该 bot 的后台 poller),所以对一个 bot 手动轮询永远不会推进另一个 bot 的流。
双 bot 示例
- insert:
- id: telegram
name: '/path/to/dsh-plugin-telegram/lib/index.js'
config:
pollingEnabled: true
longPollTimeout: 30
Top-level values act as the per-bot defaults (inherited when an item omits a field).
requireMention: true
agentResponseMode: 'direct'
bots:
- id: alice
token: '123456...:AA...' # or leave empty + envKey below
defaultChatId: '100000000'
allowedUsers: ['100000000']
- id: bob
envKey: 'TELEGRAM_BOT_TOKEN_BOB' # reads its own env var (no token on the item)
defaultChatId: '200000000'
allowedUsers: ['200000000']
多机器人注意事项
- chatId 在不同机器人之间并不全局唯一 —— 同一个数字 chatId 可能同时存在于两个机器人下。因此所有按会话维护的状态(去重、看板、指示器、智能体路由)都以复合键 k(botId, chatId)("botId::chatId")为键,绝不用裸 chatId。所以消息不会跨机器人去重:同一条 (chatId, messageId) 经由两个不同机器人送达时,每个机器人各处理一次。
- 用 envKey 隔离 token,避免第二个机器人继承第一个机器人的 TELEGRAM_BOT_TOKEN(默认的 envKey/credentialKey 都是同一个 TELEGRAM_BOT_TOKEN;给每个额外机器人指定各自的键)。
- getUpdates 的 offset 按机器人独立(Telegram 为每个机器人维护一条更新流)—— 插件为 telegram_get_updates 工具按机器人分别记录手动 offset,每个后台轮询器也各自跟踪游标(offset 文件 telegram-poller-offset-<botId>.json)。
入站语音转写(🎧)
用户发来语音消息时,插件通过本地 Whisper 服务(sttEndpoint,默认与 transcribe_audio 使用同一个代理)转写,并在同一步:
1. 把识别出的文本直接回复在语音气泡下方 —— 回复语音消息是让它显示在「下一行」的唯一办法(Telegram 机器人无法编辑其他用户的消息)。这是一条安静、纯文本的消息(🎧 …),这样任意识别文本都不会触发实体解析器。
2. 在注入给智能体的提示中复用这份转写文本,智能体因此已经拿到说的话,无需再调用 transcribe_audio(省一次往返,智能体可以立即作答)。
需要 forwardInboundMedia: true(必须下载文件才能转写)。转写文本原样展示 —— 不经 LLM 改写 —— 所以展示快且零成本。整个功能是尽力而为:服务宕机或音频无声时,语音消息仍会照常注入并作答(只是没有转写行)。设 voiceTranscribe: false 可关闭。
实时轨迹(工具调用 + 思考)
智能体处理 Telegram 消息期间,一条可编辑消息会滚动显示它最近的活动轨迹(direct 和 tool 两种模式下都有),外加持续的「正在输入…」聊天动作。参考 QwenPaw 的 Telegram 频道原地编辑流式方案。每条近期活动占一行:
- 💭 <reasoning 片段> —— 模型思考的一段内容(边产生边流式输出);
- 🔧 <tool>:<参数预览> —— 一次工具调用(名称 + 紧凑的参数预览)。
整条消息按尾部截断到 progressMaxChars,所以最新的内容始终可见,最旧的滚出视野(每行另有单独上限, progressPerBlockChars)。最终回复不在这里显示——回合结束时,它会作为单独一条消息发出。
回合结束(turn/end)的瞬间它就被删除,超过 progressTimeoutSec 后自行清理,在 progressDelaySec 之前就结束的回合则从不显示。设置 progressEnabled: false 可关闭它。它纯粹是尽力而为——Telegram 出故障也绝不影响真正的回复。
实况子代理看板(🧩)
当会话生成子代理时(通过 subagent / subagent_fork 工具),插件会在每个聊天保留一条置顶消息,实时显示当前正在工作的每个子代理——以及每个已完成的子代理,锁定在原位。每个子代理最多占两行:
🧩 子代理看板 · 2 工作中 / 1 完成 🟢 重构认证模块 · 工作中 🔧 read src/auth/session.js 🟢 跑集成测试 · 工作中正在启动…
✅ 整理依赖 · 已完成已完成 · 用时 42s
- 第 1 行——状态 emoji + 简短任务名 + 状态词(工作中 / 已完成 / …)。
任务名来自子会话的 subagent/descriptor 标签(或 subagent 工具调用的 description),截断到合适长度。
- 第 2 行——它此刻在做什么:子会话最近一次工具调用(🔧 名称 + 参数),否则是最新的推理内容,否则在出现活动前显示 正在启动…。
- 实时——一个定时器每隔 subagentBoardRefreshMs(默认 2 秒)重新读取活跃的子会话,并就地编辑同一条消息(编辑节流到约 1.5 秒,绝不触发 Telegram 的编辑频率限制)。
- 置顶——看板消息在第一个子代理出现时置顶(「固定」的部分),这样它始终留在聊天顶部;subagentBoardPin: false 可关闭置顶。
- 结束时锁定——子代理完成时(subagent/end),它那一行冻结,带上终态和耗时,之后不再重新读取。重新启动(可继续的子会话为新纪元唤醒)会把该行重新打开为工作中。
生命周期边界来自 DSH 的 subagent/start / subagent/end 事件(全局监听器,因此绕过父作用域过滤)。同一个定时器上还会跑一次对活跃 agents.list() 的存在性扫描作为兜底:启动事件总线漏掉的任何子会话,并锁定任何消失的(经过短暂宽限后),这样即使事件从未到达插件的主机,看板也能保持正确。默认只显示直接子会话;设置 subagentBoardIncludeDescendants: true 可包含嵌套子代理。超出 subagentBoardMaxRows 的部分折叠成一行 … 另有 K 个未显示,让消息保持在 Telegram 的 4096 字符限制以内。
/new(或 /clear)会拆除聊天的看板(取消置顶 + 删除);插件卸载时看板也会拆除。设置 subagentBoardEnabled: false 可关闭该功能。和进度指示器一样,它纯粹是尽力而为——Telegram 出点小毛病绝不影响真正的回复或子代理本身。
工具守卫审批(手机上的权限提示)
当智能体的权限策略为 ask(默认值),且某次工具调用需要决策——比如沙箱提权,像往工作区外写文件,或者受保护的 pre-execute 检查——DSH 会通过一条 approval/request 瀑布流交给若干应答者处理。如果没有应答者认领这个请求,就会失败关闭:用户什么都看不到,操作被拒绝。
在这个功能之前,Telegram 插件没有注册任何应答者,所以 Telegram 智能体的请求会被 web 宿主认领(也就是浏览器 UI,在手机上根本看不见),或者失败关闭——这就是那个被报告的「手机上收不到权限提示」的 bug。本次发布新增了一个应答者,参照 QwenPaw 的 tool_guard 卡片实现:
- 插件注册一个 approval/request 应答者(在 web 应答者之前运行),认领它自己负责的请求——它自己的 telegram- 智能体,以及默认情况下的共享默认智能体——其余的则委托出去,让 web UI 继续正常工作。
- 它向所属聊天发送一张内联键盘卡片:🛡️ 需要授权批准 + 工具名 + DSH 给出的原因,带 ✅ 批准 / ❌ 拒绝 按钮。
- 点击即可解决请求:批准 → allowed-once,拒绝 → rejected。超时(approvalTimeoutSec)、回合中止或卸载都会将其解决为 cancelled。卡片会原地编辑以显示结果,按钮点击也会被确认。
一直允许(🔁 一直允许)
DSH 的审批服务没有原生的「一直允许」——它唯一认识的授权是一次性的 allowed-once。这个插件在它之上加了一层:
- 卡片带第三个按钮 🔁 一直允许(批准并记住)。点击它会批准当前请求,并为这类请求记住一个稳定的规则键。之后匹配的请求就会自动批准,不再发送卡片。
- 规则键经过归一化,所以重复的请求会映射到同一个键,即使自由文本的原因变了:沙箱提权(escalate sandbox to <mode>: …)以 <tool>:<mode> 为键 → sandbox:<tool>:<mode>;其他受保护的请求以整个工具为键 → tool:<name>。
- 记住的规则会持久化到 $DSH_HOME/telegram-approval-always.json(原子写入,插件重载后依然存在)。用 /approval 命令管理它们:/approval 列出已记住的规则,/approval clear 清空全部,/approval <ruleKey> 按精确键清除一条。
⚠️「一直允许」范围很广——一条 sandbox:<tool>:<mode> 规则会为所属聊天自动批准未来所有到该模式的提权,直到你清除它。只对你确实不想再被问到的规则使用。
设置 approvalEnabled: false 可以完全关闭它。如果卡片无法送达,请求会被委托出去(交给 web UI,或者失败关闭)——绝不会被悄悄丢弃。
问题卡片(手机上的 ask_user_question)
有时智能体会停下来向用户提问——从几个选项里选一个,或者自己输入答案(ask_user_question 工具)。在 DSH 中,web 宿主拥有这些提问的唯一 UI 服务商,所以只有浏览器能看到提示;只用手机的用户会一直等下去,屏幕上什么都没有。这个插件新增 Telegram 应答器:
1. 它在 user-questions/request Cordis 瀑布上注册前置应答器(插件跑在同一个 dsh web 进程里,所以没有回环 HTTP —— 从 DSH 0.1.3-alpha.2 起,问题传输移到带认证的 /api/remote.mux 网关后面,旧的 /api/events.mux + /api/respond 通道已废弃)。
2. 当请求到达本插件拥有的智能体(或者开启 questionsForDefaultAgent 时,共享的默认智能体),它会把内联键盘卡片发到所属聊天;其余请求通过 next() 委派出去,让 web UI 继续正常工作。
3. 你的回答直接从按钮点击中解决待处理的提问。
如何回答:
- 单选题 —— 点选项立即作答,或者直接以纯文本回复卡片:你的文本会成为该问题的自定义答案(这就是「输入我自己的提示词」路径)。
- 多选题 —— 点选项切换选中状态,然后点 ✅ 提交 提交。包含多个子问题的问题只能用按钮;你未作答的子问题会被跳过。
- ❌ 取消 取消该提问。
- 如果 web UI 先作答,委派路径会解决该提问,卡片重新渲染为锁定状态(选项禁用),并显示一条中性状态行 —— 先作答者胜出,所以手机上迟到的点击会被丢弃,而不是重复提交。
- 超过 questionsTimeoutSec 后,未作答的卡片自动取消,智能体回合永远不会一直挂起。
来自其他(仅 web)智能体的问题留给浏览器 —— 你不会看到重复卡片。设置 questionsEnabled: false 可关闭此功能。
自动驾驶(全自动模式)
/autopilot 开启按聊天生效的「无需手把手即可达成目标」模式 —— 相当于全局 /goal 在 Telegram 上的对应物。它按聊天选择开启,开启时会明确警告,因为它授予了真实权限:
1. 全局写权限。 开启自动驾驶会向会话追加一条 sandbox/mode = danger-full-access 事件,因此 bash/fs 调用会以完整磁盘访问权限运行,直到你关闭它。执行 /autopilot off 时,会重新应用先前捕获的模式(默认 workspace-write)。
2. 无权限提示。 聊天处于自动驾驶时,插件的 approval/request 应答器会自动批准每个请求,而不是发送卡片。沙箱升级走同一个应答器,所以智能体无需在提示上阻塞就能获得完整写权限。会发一条简短的静默通知,让你看到自动批准了什么。
3. 自动采纳推荐答案。 自动驾驶开启时,ask_user_question 卡片会自动选中智能体的推荐选项,并在 autopilotWindowMs 接管窗口(默认 10s)后提交。通知卡片会列出所有选项 + 被锁定的那个,并带有 ⏩ 立即采纳(立即提交)和 ✋ 接管(停止该聊天的自动驾驶 + 手动作答)。
命令: /autopilot 或 /autopilot on 启用;/autopilot off 禁用(并恢复之前的沙箱模式);/autopilot status 报告状态。
安全警告。 Autopilot 会授予全局写入权限,自动批准工具和沙箱升级,并自动回答问题。确实存在意外执行、且难以撤销的操作风险。只在你信任的聊天/会话中启用,并留意静默通知。设置 autopilotEnabled: false 可完全移除该命令。
自动允许的工作方式(不是 danger-full-access 预设)。 该插件
does not switch to the danger-full-access permission preset or set
approval/policy: 'never' — never rejects* asks before dispatch rather than auto-allowing, which would break approvals. The auto-allow lives in this plugin's own approval/request answerer; the approval policy stays ask.
Recommended-option convention. For auto-adoption to pick the right answer,
the agent should put its recommended option first and tag its label with
(推荐) / recommended. While a chat is in autopilot the plugin injects this instruction into every forwarded message (see AGENT_INTEGRATION). pickRecommended locks on the 推荐/recommended marker, else falls back to the first option.
Creating a Telegram Bot
1. Open a chat with @BotFather on Telegram
2. Send /newbot and follow the instructions
3. Copy the bot token
4. Add the bot to your target chat/group
5. Grant necessary permissions (admin rights for deleting messages)
Usage Examples
Send a message
Use telegram_send_message with:
- chat_id: "123456789"
- text: "Hello from DSH!"
Send a photo
Use telegram_send_photo with:
- chat_id: "123456789"
- photo: "https://example.com/image.jpg"
- caption: "Check this out!"
Get bot info
Use telegram_get_info to see the bot's username, ID, and current configuration.
Manually check for updates
Use telegram_get_updates with limit: 10 to fetch the latest 10 updates.
Agent Integration
The plugin can inject Telegram messages directly into the DSH agent loop, enabling AI-powered conversations through Telegram.
Quick Start
1. Enable polling and agent injection in cordis.yml:
config:
pollingEnabled: true
injectToAgent: true
agentResponseMode: 'tool'
2. Restart DSH:
dsh web --patch ./cordis.yml
3. Send a message to your Telegram bot — the agent will process it and respond automatically!
How It Works
1. Message Reception: Poller receives Telegram messages via getUpdates
2. Session Injection: Messages are appended to the DSH session as user/message events
3. Agent Processing: The agent loop picks up the message and generates a response
4. Tool-based Reply: The agent uses telegram_send_message to send the reply
Message Format
Injected messages include metadata:
[Telegram from @username in chat 123456789]
Your message content here
The agent can use this metadata to personalize responses and know which chat to reply to.
Configuration Options
| Option | Description |
| injectToAgent: true | Enable message injection to agent loop |
| agentResponseMode: 'tool' | Agent uses telegram_send_message tool (recommended) |
| agentResponseMode: 'direct' | Agent responds directly without tool |
For detailed documentation, see AGENT_INTEGRATION.md.
Architecture
┌─────────────────────────────────────────────────────┐
│ DSH Agent Loop │
│ │
│ ┌────────────────────────────────────────────────┐ │
│ │ Cordis Plugin (index.js) │ │
│ │ │ │
│ │ ┌──────────────┐ ┌────────────────────────┐ │ │
│ │ │ Tools │ │ Background Poller │ │ │
│ │ │ · send_msg │ │ · getUpdates loop │ │ │ │ │ │ · send_photo│ │ · dispatch messages │ │ │ │ │ │ · edit_msg │ │ · reconnect on error │ │ │ │ │ │ · delete_msg│ │ · rate limit backoff │ │ │ │ │ └──────┬───────┘ └───────────┬────────────┘ │ │ │ │ │ │ │ │ │ └─────────┼──────────────────────┼────────────────┘ │ │ │ │ │ └────────────┼──────────────────────┼───────────────────┘ │ │ ▼ ▼ ┌──────────────────────────────────────┐ │ Telegram Bot HTTP API │
│ (api.telegram.org) │
└──────────────────────────────────────┘
File Structure
dsh-plugin-telegram/
├── CHANGELOG.md # Version history
├── LICENSE # MIT license
├── cordis.yml # Sample cordis.yml patch for loading the plugin
├── package.json # Package metadata
├── README.md # This file
├── src/
│ ├── index.js # Main plugin entry (tools + bot registry + polling + agent injection)
│ ├── client.js # Telegram Bot API HTTP client (incl. pin/unpin, multipart upload)
│ ├── poller.js # Long-polling background service (per-bot)
│ ├── text.js # Pure text helpers (Markdown→HTML, fence-aware chunking)
│ ├── progress.js # Live-trajectory indicator (streaming footer, activity trail)
│ ├── inbound-media.js# Inbound media download + image sniffing (voice/photo handling)
│ ├── approval.js # Tool-guard approval cards (incl. "allow always" remember-rules)
│ ├── questions.js # ask_user_question cards (in-process user-questions/request waterfall)
│ └── subagents.js # Live subagent board (state, render, throttled flush)
├── test/
│ ├── text.test.mjs # Pure text helpers (chunking, Markdown→HTML)
│ ├── client.test.mjs # Transient-error classification, local-file upload
│ ├── poller.test.mjs # Poller loop / offset / dedup
│ ├── progress.test.mjs # Live-trajectory indicator
│ ├── approval.test.mjs # Approval cards + allow-always rules
│ ├── questions.test.mjs # Question cards (waterfall answerer)
│ ├── questions-wiring.test.mjs # Question answerer wiring
│ ├── subagents.test.mjs # Subagent board
│ ├── command-media.test.mjs # Command media helpers
│ ├── ownership.test.mjs # Agent-ownership policy (web vs telegram routing)
│ └── multi-bot.test.mjs # Multi-bot config contract + routing (T1–T32)
└── lib/ # Built output (copy of src/; npm run prepare)
├── index.js
├── client.js
├── poller.js
├── text.js
├── progress.js
├── inbound-media.js
├── approval.js
├── questions.js
└── subagents.js
References
- QwenPaw Telegram Channel - Original implementation
- DSH Plugin Tutorial - Cordis plugin framework
- Telegram Bot API - Official API documentation
License
MIT预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
lovedheart/dsh-plugin-telegram
在 Telegram 里收发消息并接入智能体对话
相关插件 · 平台集成
智能体工程课walkinglabs/learn-harness-engineering
一门项目制课程,教你搭建环境、管理状态、做验证、加控制机制,让 AI 编程智能体稳定干活。
★ 21,155已汉化DSH
71.7
视频翻译配音liuzhao1225/YouDub-webui
开源视频本地化工作台:导入视频,识别、翻译并逐句克隆原声,生成字幕和配音成片。
★ 5,778已汉化Cordis
71.4
失败经验库Ikalus1988/MisakaNet
📚 零依赖、由 git 支撑的微课程库,让 AI 智能体异步分享并检索经过验证的调试经验。| https://misakanet.org
★ 549已汉化DSH
71.2
IM 机器人接入xmanrui/dsh-im
让 DeepSeek Harness 触手可及 Connecting DeepSeek Harness
★ 3,011已汉化DeepSeek 客户端
71
桌面智能体工作区freestylefly/wesight
WeSight 是开源桌面控制台,面向本地 AI 智能体。它帮你安装或复用 Claude…
★ 933已汉化MCP
71
中国平台研究引擎Jesseovo/last30days-skill-cn
给 AI Agent 的中文互联网研究 Skill:检索 8 大平台最近 30…
★ 1,969已汉化DSH
70.7