🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

Telegram 前端 ashafizullah/dsh-telegram

DeepSeek Harness
ashafizullah74 天前有提交已提供中文文档扫描:未发现敏感能力

在 Telegram 里与 Agent 对话并直接回答它的提问

25.8/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/12 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add ashafizullah/dsh-telegram
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
dsh 原生插件 · vision
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 74 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🟢 扫描:未发现敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描未发现敏感能力:最多只发网络请求、读取自身元数据或只读文件,未发现执行命令、文件写入或敏感信息读取。

扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 聊天内对话Agent
  • 直接回答提问与审批

DeepSeek Harness 的 Telegram 前端——原生富 Markdown 回复,在聊天里直接回答提问和审批,图片交给能看图的模型来读

README

由 DeepSeek 最新模型翻译生成
dsh-telegram 在 Telegram 上与你的 Agent 对话——并且在它提问时,真的能够回答。 English · Bahasa Indonesia · 中文 DeepSeek Harness 的 Telegram 前端。 在手机上与你的 Agent 对话——并且在它提问时,真的能够回答。 为什么需要它 用聊天软件驱动 Agent,会在两个具体的地方卡住,这个插件就是为了解决它们。 Agent 写的是 markdown,而 Telegram 收到的是原文。 模型的回答里有 粗体、标题、表格、任务清单和代码块。以纯文本发送时,这些全都变成了字面上的星号和竖线。 从 Bot API 10.1 起,Telegram 自己会解析 markdown,因此本插件通过 sendRichMessage 几乎原样转发 Agent 的回复——表格显示为表格,清单显示为清单——同时消息上限也从 4096 提升到 32768 个字符。 Agent 会提问,却没有地方回答。 当 Agent 调用 ask_user_question,或某个工具需要你的许可时,harness 会阻塞并等待某个 UI 作答。而这件事以前只有浏览器能做。完全发生在 Telegram 里的对话,会在第一个提问处停住,且无从解开。本插件把自己注册为那个 UI,于是提问与授权都以按钮的形式出现在聊天里。
展开完整文档(README 剩余 1 节)
dsh-telegram

在 Telegram 上与你的 Agent 对话——并且在它提问时,真的能够回答。

English · Bahasa Indonesia · 中文

DeepSeek Harness 的 Telegram 前端。

在手机上与你的 Agent 对话——并且在它提问时,真的能够回答。

为什么需要它

用聊天软件驱动 Agent,会在两个具体的地方卡住,这个插件就是为了解决它们。

Agent 写的是 markdown,而 Telegram 收到的是原文。 模型的回答里有 粗体、标题、表格、任务清单和代码块。以纯文本发送时,这些全都变成了字面上的星号和竖线。

从 Bot API 10.1 起,Telegram 自己会解析 markdown,因此本插件通过 sendRichMessage 几乎原样转发 Agent 的回复——表格显示为表格,清单显示为清单——同时消息上限也从 4096 提升到 32768 个字符。

Agent 会提问,却没有地方回答。 当 Agent 调用 ask_user_question,或某个工具需要你的许可时,harness 会阻塞并等待某个 UI 作答。而这件事以前只有浏览器能做。完全发生在 Telegram 里的对话,会在第一个提问处停住,且无从解开。本插件把自己注册为那个 UI,于是提问与授权都以按钮的形式出现在聊天里。

前置要求

- 一个可以添加插件的 DeepSeek Harness profile
- Bot API 10.1 或更高版本,用于 sendRichMessage 与 sendRichMessageDraft
- Node 22 或更高版本

没有 HTML 回退路径。Telegram 的 rich markdown 解析器是宽容的——未闭合的代码围栏或一行散乱的标记都会被接受而非拒绝——所以流式过程中的中间帧并不需要回退。

安装

npx @deepseek-ai/dsh plugin --profile web add -w @ashafizullah/dsh-telegram

或者从源码检出,以便在其之上开发:

git clone https://github.com/ashafizullah/dsh-telegram.git
cd dsh-telegram
pnpm install && pnpm build

npx @deepseek-ai/dsh plugin --profile web add -w "$(pwd)"

然后给它一个机器人 Token。用 @BotFather 创建机器人,并把 Token 存放在凭据引用之下——永远不要写进配置文件:

npx @deepseek-ai/dsh credentials set TELEGRAM_BOT_TOKEN

启动 profile,控制台会打印一个认领码:

[dsh-telegram] this bot has no owner yet. Message @your_bot with:

/claim 3f9a2b1c

把它发给你的机器人,它就归你了。在此之前,它不回应任何人。

该认领码同时会以仅属主可读的权限写入 $DSH_HOME/dsh-telegram/claim-code.txt,因为有些 profile 根本没有组合任何控制台输出,而一个没人能读到的认领码会让机器人永远无法使用。

访问控制

任何知道句柄的人都能找到一个 Telegram 机器人,而它背后的 Agent 能在你的机器上执行 shell 命令。因此默认是关闭的。

- 认领流程(默认):第一个发送控制台认领码的人成为属主。所有权是持久且一次性的——即使拿着正确的码,后来的认领也会被拒绝,所以事后泄露的码毫无用处。
- 允许名单:把 allowFrom 设为一组 Telegram 用户 ID,即可完全跳过认领。用 /whoami 查看自己的 ID。

认领码每次重启都会更换,且从不经由 Telegram 发送。

访问检查先于其它一切进行,因此未经授权的文本不会到达 Agent——连命令也不会。

命令

| 命令 | 作用 |
| /start | 这个机器人是什么,以及你是否可以使用它 |
| /help | 列出所有命令 |
| /claim <码> | 认领一个尚未被认领的机器人 |
| /new | 开始新对话,忘掉当前这一段 |
| /cd [路径] | 查看或切换工作目录 |
| /model [名称] | 查看模型、/model list,或切换到某一个 |
| /effort [强度] | 查看或调整模型思考的深度 |
| /vision [名称] | 查看、更换或关闭负责读图的模型 |
| /permission [名称] | 查看或调整 Agent 在这里被允许做什么 |
| /diag | 插件对自身的观察,以及最近的失败 |
| /screenshot | 发送 harness 所在机器的屏幕截图 |
| /sessions | 继续这个聊天里较早的一段对话 |
| /status | 会话 ID、工作目录,以及是否已加载 |
| /stop | 取消 Agent 当前正在做的事 |
| /whoami | 你的 Telegram 用户 ID |

在群里

一个每句话都要接的机器人,没人愿意留在群里。所以在群里它只在被 @提及或被回复时才作答——这也正是大家已经在用的惯例。它自己的 @提及会在进入提示词前被去掉,因为那是称呼而不是内容;而回复它说过的话可以继续这段交流,无需每行都 @一次。私聊不受影响。想要旧行为,把 requireMentionInGroups 设为 false。

@提及是跟 Telegram 自己解析出的区间比对的,而不是在文本里搜索: @mybot_staging 里含有 @mybot,用子串匹配会让这个机器人抢答另一个机器人的 @提及。

Agent 被允许做什么

部署会为它运行的一切选定一个权限默认值,而这个选择通常是对着网页界面做的:仅回环访问,有人在旁边看着。Telegram 机器人不是这样——它从任何地方都能被联系到,只靠一份用户 ID 名单把关。所以同样的 danger-full-access,在那里意味着完全不同的东西。permissionPreset 从部署自己的表里挑一个,只作用于 Telegram 对话。

它同时决定审批按钮能否工作:在审批策略为 never 的 preset 下,永远不会有人来请求许可,按钮也就永远不会出现。选一个会询问的 preset,才是把它们打开。

屏幕截图

/screenshot 会把 harness 所在机器正在显示的画面发过来。这正是这个机器人存在的理由,只不过用在了屏幕本身上:机器在桌上,而你不在——否则想看看那个跑了很久的构建现在显示到哪了,就得走回键盘前。

它默认关闭,而且这个开关刻意放在部署设置里,而不是做成聊天命令。屏幕上有什么就会拍到什么——打开着的密码管理器、别人的消息、毫不相干的客户数据——而这是这里唯一一件不经过 Agent 就把本机内容发往外部的事。打开它,理应需要与配置这个机器人相同的权限。

macOS 还需要给运行 harness 的进程「屏幕录制」权限。没有它,screencapture 仍会成功,只是返回一张没有任何窗口的桌面图——看起来像功能坏了,其实只是缺权限。所以这种情况会被明确说出来,而不是含糊带过。请在 系统设置 → 隐私与安全性 → 屏幕录制中授权,然后重启 harness。

超过 Telegram 10 MB 照片上限的截图会改以文件形式发送,那条通道可到 50 MB——大尺寸显示器的 PNG 经常需要。

思考强度,以及被允许做什么

/effort 显示模型思考得多深,并列出这个模型提供的档位——档位是从模型自己读来的,因为 low/medium/high 只是某一家供应方的说法,而不是所有人的;提供一个模型没有的档位,失败的会是这个回合,而不只是这条命令。/effort default 可以还原。

/permission 显示 Agent 在这里被允许做什么,并可切换:read-only、 workspace-write、danger-full-access,或者你的部署定义的任何其他名字——名字读自它自己的表,而不是写死在这里。拼写很宽松,full access、full-access 和 readonly 都能命中;而同时匹配两个 preset 的简写会被拒绝,而不是靠猜。改动对正在进行的对话同样生效,因为人们收紧权限的理由,通常正是马上要跑的那个回合。

这几项都按对话生效,并且叠在设置页面所配置的东西之上。这意味着有两个界面在展示相关状态,所以命令会说明是哪一层在回答:一旦某个对话自己做了选择,它的回复就会一并说出底下的部署默认值。没有这句,设置页面读起来就像在撒谎——它显示一个值,而聊天里在按另一个值行事,两者之间毫无关联。/… default 会把对话交还给部署默认值。

/status 用一条消息回答全部——会话、目录、模型、思考强度、权限——因为为了搞清楚自己在跟什么说话而要敲四条命令,是四条太多了。

用哪个模型,以及哪一段对话

/model 告诉你当前对话用的是哪个模型,/model list 列出已配置的, /model provider/model 则切换。当只有一个供应方提供某个模型 id 时,直接写它就够了;有多个时,它会反问是哪一个。与 /cd 不同,这不会重启任何东西——harness 在组装每一步时都会读取一份可变的选择,所以改动会落在下一条消息上,历史完好无损。
/model default 把对话交还给部署默认值。

/sessions 把这个聊天里较早的对话做成按钮供你挑选。在此之前 /new 是一扇单向门:harness 保留了每一份日志,但指向当前对话的绑定被替换掉了,从手机上再没有别的路回去。这份列表属于本插件自己,因此装的是这个聊天里的对话——而不是网页界面开过的每一个会话。

Agent 有哪些工具

工具由 preset 提供。注册表本身属于 host 平面,但几乎每一个面向模型的行——bash、编辑器、grep、skills、子代理、todo、计划模式——都注册在某个 preset 的 scope 层里。因此没有加入任何 preset 的 agent,到达模型时只带着 host 组合中全局注册的那些。Telegram 会话按部署的默认 preset 组合,或在指定了 agentPreset 时按它组合,并把该选择记入会话头,好让之后的读取者解析到同一套组合。

Agent 在哪里工作

/cd 不带参数会告诉你当前对话在哪;/cd ~/projects/app 则把它移过去。绝对路径、 ~、以及相对当前位置的路径都可以用,粘贴进来的路径会自动去掉引号。

移动目录会开启一段新对话,机器人也会明说。这不是偷懒:沙箱的可写根目录来自会话的工作目录,而这个根在会话打开时就已固定——所以切换目录在构造上就等于换一个会话。
你的选择按聊天记住,/new 和重启都不会丢。这正是它与 /new 会丢弃的会话绑定分开存放的原因。

目录不存在、目标其实是个文件、以及读不到,是三种不同的错误,会得到三种不同的说明。
三种情况都让对话原地不动。

每次连接时这份列表都会注册到 Telegram,所以在聊天里输入 / 就会看到命令提示和各自的说明。机器人一旦有了主人,/claim 就会从列表里消失——它是唯一一个成功之后便不再有用的命令。

除此之外你输入的任何内容,都会作为提示词交给 Agent。

你可以发送什么

| 你发送 | Agent 收到 |
| 文本 | 提示词本身 |
| 照片,或以文件形式发送的图片 | 视觉模型读出的内容,以及你的说明文字 |
| 一次发多张照片 | 全部合成一条消息,附在你的说明文字下 |
| 文本文件——日志、堆栈、源码 | 其内容进入提示词,过长时会被截断 |
| 语音、音频或视频 | 一句说明:无法读取 |

图片经由 harness 的附件接缝,它接受 PNG、JPEG、WebP 和 GIF。其余类型被官方明确搁置,因此本插件会直言相告,而不是收下消息再悄悄丢掉其中的内容。

该接缝还会拒绝最长边超过 maxImageDimension(默认 2000 像素)的图片——而每一张 满屏的手机截图都超过它:iPhone 是 1179×2556,多数 Android 是 1080×2400。Telegram 会为一张照片渲染多个尺寸,因此这里选的是放得下的最大尺寸,而不是现有的最大尺寸;限制值直接从 store 本身读取,不再另存一份会走样的数字。若接缝仍然拒绝,就退到下一个更小的尺寸。至于以文件形式发送的图片——只有一个尺寸,无处可退——拒绝信息会说明限制是多少,并提示改用照片方式发送,让 Telegram 提供较小的副本。

文件过大或下载失败时,会变成提示词里的一句说明——无论如何,你的说明文字仍会到达 Agent。

一次发好几张

Telegram 没有「一条消息里放多张照片」这回事。相册会作为 N 条独立更新到达,彼此之间只靠一个共享 id 连着,而说明文字只挂在其中一条上——所以三张截图以前会变成三个回合,其中两个是 Agent 无从下手的裸图片。

现在属于相册的消息会先被暂存而不是立即作答,等相册不再增长,整组作为一条提示词送出去:你的说明文字,然后是全部图片。这点等待只由相册承担,且每个相册只付一次——比把同一个问题回答三遍划算得多。

模型必须看得见

不声明图片输入的模型会拒绝整个请求,因此图片在发送前会对照 inputModalities 做检查。没有任何 DeepSeek 模型接受图片—— deepseek-v4-flash 与 deepseek-v4-pro 都是纯文本——所以开箱即用的情况下,截图会被婉拒,并附上一句说明什么才可行,而你的说明文字仍会到达 Agent。

设置 → Telegram → 附件 提供一个下拉框,列出你已在 设置 → Models 中配置好的模型。选一个,图片就能被读取了。

/vision 用来选择由哪个模型在这里读图,或者用 /vision off 把读图整个关掉。
「关掉」是一个真正的答案,而不是答案的缺席:对话本身能看见的时候,它不需要任何读图者,而这个表态必须压过部署层面的任何配置。和其他几项一样,它按对话生效,并且能挺过 /new。

如果对话本身用的模型就能读图,下面这一切都不会发生。 图片会直接送进去,由模型自己去看。自 DeepSeek 发布 deepseek-v4-flash-vision-exp 起,这已是一个现实的选项——而且当截图不只是文字时,它是更好的那个:转写会丢掉图表、曲线、错位的布局,也就是你真正在问的东西。

下面这层间接之所以存在,是因为供应方会检查整个请求历史,图片会把对话绑定到一个看得见的模型上。而当那个模型正是你选的那个,就没有什么需要挣脱,也没有什么需要绕开——于是读取、谢绝、以及粘住的路由会一起退场。

图片本身从不进入你的对话。它会被发到该模型上的一个一次性会话,被要求转写其中的每一处文字,并简要描述这是什么;回答以普通文本返回,那才是你的对话所收到的内容,就放在你自己的说明文字下面。那个会话随后即被销毁——它只活一个回合。

这一层间接正是关键。供应方会检查整个请求历史中的图片,所以留在对话里的一张图片会把这段对话终身绑定到一个看得见图片的模型上:一张截图之后,后续每一个回合——无论其文字多么普通——都得跟着跑到那里,远离你选定的模型和围绕它配置的工具。把图片放到别处去读,历史中就始终没有图片,对话因而留在原处、保有工具,也永远不会卡住。

如果根本没有配置视觉模型,这条路径压根不会被走到:图片在下载之前就被谢绝,并附上一句说明哪些模型本可胜任,而你的说明文字仍会到达 Agent。

如果读取已经尝试但失败了——模型无法连接,或该回合在两分钟后超时——图片就按原样发出,改为让对话迁移到视觉模型上,并持久生效,直到 /new。那是退路而非设计,提示词里会说明发生了哪一种情况。

浏览器能读到的模型目录不携带模态信息,所以下拉框无法标出哪些模型接受图片。这项检查交由 host 在图片真正发送时进行,那是唯一能给出确定答案的地方。视觉模型通过承载它们的供应方进入 harness,例如在 设置 → Models 中添加的 OpenAI-compatible 路由,其模型条目声明了 input: [text, image]。

当没有模型能看时

在没有配置视觉模型时,图片过去会被直接谢绝,你得到的是一句关于模型配置的说明,而不是关于这张图的任何信息。现在,只要装了 tesseract,就改为读取其中的文字。

它是退路,而且它自己会这么说。OCR 读的是文字,它并不「看见」。报错、日志或收据的截图会读得很干净——文字清晰、对比度高、没有透视,正是它最擅长的情形;而白板、架构图或图表则只会变成一堆散落的词,没有任何东西能说明这张图是什么。因此读出的内容无论去到哪里都会被标注为 OCR:把未加标注的 OCR 交给 Agent,它会把读错的数字当成事实,而收据上的金额恰恰是最容易读错的。

tesseract 从不被假定存在。没有任何一个运行本插件的操作系统自带它,因此它的缺席才是常态:只探测一次,缺失时旧的谢绝依然生效——只是现在会同时说明两条出路。
/diag 会告诉你这台机器有哪一条。

同一条退路也覆盖「配置了视觉模型但连不上」的情形,理由相同:读出文字总好过什么都不返回。

拉丁字母仅用 eng 就读得不错——印尼语、数字、日期和金额都能穿过——所以 media.ocr.languages 只有在换一种书写系统时才需要改。tesseract --list-langs 会列出已安装的语言。

当对话卡住时

有一类失败重试永远无法解决——最常见的正是上面那种:早先的某条消息携带了当前模型不接受的内容,而你接下来输入什么都无济于事。机器人会识别这类失败,说明失败原因,并给出一个开启新对话的按钮。让用户去记住 /new,等于让他们替插件做诊断。

可能自行恢复的失败则不带按钮上报,因为对那些失败来说,重试确实是正确的做法。

配置

在 harness 的网页界面中打开 设置 → Telegram。该页面直接写入设置文档——没有保存按钮,因为 host 通过重新连接来应用已提交的更改,而一个暂存改动的表单会让页面和正在运行的机器人对"当前配置是什么"产生分歧。

机器人 Token 是例外。它是机密,因此从不经由设置通道来回传输:页面只知道是否已存有 Token,通过 credentials 域写入它,并且对于环境变量已经提供的引用拒绝提供编辑——在那里写入会看似成功,而解析仍旧返回环境变量中的值。

页面上的每一项,同样可以在 profile patch 中设置,供以文件方式配置的部署使用。

配置项

每个字段都有可用的默认值;配置为空也能运行。

| 键 | 默认值 | 含义 |
| enabled | true | 连接是否随 harness 一同启动 |
| tokenRef | TELEGRAM_BOT_TOKEN | 存放 Token 的凭据引用名 |
| baseUrl | https://api.telegram.org | Bot API 源站;仅在使用代理时修改 |
| allowFrom | [] | 允许的用户 ID;留空则启用认领流程 |
| cwd | harness 的 cwd | 对话的起始目录,直到用 /cd 切换 |
| agentPreset | "" | Telegram 对话所用的 preset;留空则取部署默认值。工具正是由 preset 提供 |
| permissionPreset | "" | Telegram 使用的权限 preset,取自部署自己的表;留空则跟随部署默认值 |
| requireMentionInGroups | true | 在群里,只有被 @提及或被回复时才作答 |
| screenshot.enabled | false | 允许 /screenshot。默认关闭;macOS 还需要「屏幕录制」权限 |
| streaming.enabled | true | 边生成边显示回答 |
| streaming.throttleMs | 1200 | 两帧之间的最小间隔 |
| timeoutMs | 30000 | 单次 Bot API 请求的超时时间 |
| longPollSeconds | 25 | Telegram 保持空轮询打开的时长 |
| media.enabled | true | 读取用户发送的图片和文本文件 |
| media.maxBytes | 20 MB | 超过则拒绝;Telegram 的机器人下载上限即在此 |
| media.maxTextChars | 60000 | 内联文本文件截断到此字符数 |
| media.ocr.enabled | true | 没有视觉模型时,用 tesseract 读取图片中的文字。未安装 tesseract 则不起作用 |
| media.ocr.languages | eng | tesseract 读取的语言;多个用 + 连接。只有已安装的才可用 |
| media.visionModel | "" | 在独立会话中读取图片的 provider/model;留空则把图片直接发给对话本身 |
| reconnect.baseDelayMs | 1000 | 第一次重连前的延迟 |
| reconnect.maxDelayMs | 30000 | 重连之间的最长延迟 |

诊断

/diag 报告插件对自身的观察:连接状况、这个部署究竟组合了哪些 harness 接缝,以及最近二十件出错的事。

它还会报告正在运行的版本,以及 npm 上是否有更新——只读,并缓存一小时,所以问第二遍不花任何代价。这里刻意没有配套的 /update:更新 harness 只有重启后才生效,而从运行在其中的插件里重启,等于杀掉正在回答你的那个进程——在没有守护进程的机器上,没有任何东西会把它拉起来。知道自己落后了是有用的那一半;动手则该在你能盯着的地方做。

接缝列表是其中最有用的部分。缺席的接缝能一眼解释一整类「它为什么不会做那个」,不需要任何人去猜——缺少 agentPresets 正是 Telegram agent 曾经到达模型时几乎没有工具的原因,而当时没有任何地方说出这件事。

ctx.logger 写往部署所组合的任何输出端,而有些 profile 一个都没有组合——因此一个只把失败写进日志的插件,实际上是沉默的。本插件还会在每次状态变化时把自身状态写入 $DSH_HOME/dsh-telegram/status.json:

{ "state": "connected", "bot": "your_bot", "updatedAt": "..." }

connecting、connected、带原因的 idle、带原因的 failed。机器人 Token 绝不会出现在其中。

与网页界面共存

harness 只允许一个 user-questions provider,而在同时运行网页应用的 profile 中,浏览器已经占用了它。本插件接管该位置,并把浏览器的 provider 保留为回退:属于浏览器会话的提问会被原样转交回去,属于 Telegram 对话的提问则变成聊天里的按钮。卸载本插件会把先前的安排原样恢复。

授权本身是可组合的——harness 以 waterfall 方式运行它们——因此本插件只为自己的会话作答,其余一律向后传递。

各部分如何衔接

Telegram Bot API
│ 长轮询:message + callback_query ▼ UpdatePoller ──► UpdateRouter ──┬──► SessionRunner ──► ctx.agents │ │ │ └──► VisionExtractor ──► 一次性会话 ├──► MediaCollector ──► ctx.attachments ├──► TelegramQuestionProvider ──► ctx.userQuestions └──► TelegramApprovalAnswerer ──► approval/request

ctx.on('session/event') ──┬──► VisionExtractor (它自己的读取会话)
└──► TurnBridge ──► RichReplyStream ──► sendRichMessage

TypingIndicator (路由器与桥接持有,直到回复出现)

回复是如何流式呈现的

Telegram 提供了两种机制,二者不可互换:

- 私聊使用 sendRichMessageDraft——一个临时预览,共享同一 draft id 的各帧之间会有动画过渡。它在最后一帧之后 30 秒过期,因此在漫长的工具调用期间,会有一个心跳重发当前文本;否则预览会消失,机器人看上去就像死了。草稿从不持久化,所以一个回合以真正的 sendRichMessage 结束。
- 群组没有草稿 API。 那里就在回复写完时直接发送。

两者最终都归于一条持久的 rich message。

在有话可说之前,什么都不发

等待期交给 Telegram 自己的 “正在输入…” 指示,回复只在真正有内容时才出现——第一批文字,或者 Agent 调用的工具名。回合一开就发出去的省略号,只是在告诉用户他们已经知道的事;在群里,它还是一条永久留存的消息。

指示是被持有的,而不是发一次。sendChatAction 五秒即失效,比这里几乎所有值得等待的事都短——下载文件、在视觉模型上读取图片、排在上一个回合后面、或在某个工具调用里待上一分钟——所以单次调用读起来就像机器人启动后立刻死了。持有按会话计数,并在其自身有效期内重发,因此路由器读取附件时的持有与桥接随后那个回合的持有能干净地重叠,只有最后一个释放时才停止输入。另有十分钟的兜底,以防某次释放永远不来。

任何一次重绘都不会比上一帧显示得更少。当一个工具跑完而正文还没出现时,那行工具名会一直留着,直到有真正的文字来替换它——Telegram 拒绝空草稿,所以另一种做法等于用一帧什么也没说的内容,换掉刚刚发生过的事。

在 Agent 工作期间,正在运行的工具会以 <tg-thinking> 块显示在回复上方:

▸ bash: npm test

目前我发现的是……

Telegram 只在草稿中接受该块,别处一概不接受,这与它的生命周期恰好吻合——回合被持久化时它就消失,于是最终回复承载的是答案,而不是产生答案的脚手架。它只有被截断的一行:一次工具调用的参数可能长达整个文件,而这里的目的是知道 Agent 还活着,不是阅读一份记录。

开发

pnpm install
pnpm test # 598 个测试
pnpm test -- --coverage
pnpm typecheck # host 与 browser 两半
pnpm build # host 用 tsc,浏览器包用 esbuild

发布由 .github/workflows/release.yml 在推送 v* 标签时完成,走 npm 的可信发布: GitHub 通过 OIDC 证明该工作流的身份,npm 据此换发一份只在这一次发布期间有效的凭据。
没有任何地方存放 token,也没有需要抢时间输入的一次性密码——当账号的第二因素是通行密钥而不是验证码时,这一点尤其重要。工作流会拒绝与 package.json 不一致的标签,因为那是它唯一可能悄无声息发布出去的错误。

每个模块都能脱离 harness 运行,这正是测试套件跑得快的原因:插件入口是对着 Bot API 的真实 HTTP 桩来执行的,而浏览器包的物化方式与 shell 完全一致。

浏览器那一半

build.client.mjs 把 esbuild 产出的 CJS 包裹进 shell 的惰性 CJS 工厂信封
(window.__ModuleLoader__.load({ id, factory }))。该信封是复现出来的而非引入
的:harness 的 clientBundle 预设并未发布,其自身文档也把这一点列为对仓库之外插件的已知限制。

因此这里是本插件唯一与内部格式耦合的地方,而 test/client-bundle.test.ts 将其钉住——该测试会执行构建、用桩 require 物化工厂,并检查 apply 是否占据了它的设置席位。若某个 harness 版本改变了该格式,失败会在那里以明确的名字出现,而不是表现为一个空白的设置页。

React 与 shell 自身的包被标记为 external;打包第二份 React 会在页面挂载的一瞬间破坏所有 hook。

已知限制

- 每个对话一个目录。 /cd 可以移动对话,但会话本身无法移动——切换目录会开启一段新会话。
- 暂不支持语音、音频或视频。 harness 的附件接缝只接受图片。

许可证

MIT
预览分享卡片长什么样 ↓ashafizullah/dsh-telegram
分享 · DeepSeek Harness Hub
ashafizullah/dsh-telegram

在 Telegram 里与 Agent 对话并直接回答它的提问

这是你的仓库?认领条目

回到顶部