dsh-telegram
Đã xác minh@ashafizullah/dsh-telegram · v0.5.1 · MIT · Giao diện web
Telegram channel for DeepSeek Harness — real Telegram markdown, and answer the agent's questions and approvals straight from chat
Cài đặt
dsh plugin add @ashafizullah/dsh-telegram Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.
Mã nguồn
Thẻ
Tác giả
Readme
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