Skip to content

dsh-telegram

Verified

@ashafizullah/dsh-telegram · v0.5.1 · MIT · Web UI

Telegram channel for DeepSeek Harness — real Telegram markdown, and answer the agent's questions and approvals straight from chat

Install

dsh plugin add @ashafizullah/dsh-telegram

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Tags

Creators

Readme

dsh-telegram

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

CI npm license node Bot API DeepSeek Harness

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 或更高版本,用于 sendRichMessagesendRichMessageDraft
  • 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-onlyworkspace-writedanger-full-access,或者你的部署定义的任何其他名字——名字读 自它自己的表,而不是写死在这里。拼写很宽松,full accessfull-accessreadonly 都能命中;而同时匹配两个 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-flashdeepseek-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": "..." }

connectingconnected、带原因的 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