dsh-im-bridge
Đã xác minh@mhfire/dsh-im-bridge · v0.4.2 · MIT · Giao diện web
企业微信智能机器人 ⇄ DeepSeek Harness Agent 桥接插件:进程内按企微窗口创建 Agent(单聊一人一条、同一群共用一条),会话在 GUI 实时可见;含 Settings 插件配置卡片
Cài đặt
dsh plugin add @mhfire/dsh-im-bridge Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.
Mã nguồn
Tác giả
Readme
中文 | English
@mhfire/dsh-im-bridge
企业微信智能机器人 ⇄ DeepSeek Harness Agent 桥接 DSH 插件。
在 dsh profile 内进程内创建 Agent(不再 spawn 子进程):按企微窗口拆分持久会话(单聊 = 该用户一条;同一群里所有人共用一条;同一个人的私聊和群聊互不串上下文),会话与 Web GUI 同进程注册(实时可见、可续聊),并在 Settings → 插件配置页提供配置卡片(botId / secret、白名单、超时、提示语、模型覆盖,写入 settings.yaml)。
Host 通过 installSettingsSection 注册 im-bridge 命名空间;浏览器半包以 key: im-bridge 挂进 settings.plugin.item。卡片可填 botId / secret,与 profile patch 写入同一用户层;改 startHint 等热字段后下一轮消息即生效,改凭证仍需重启进程才会连 WebSocket。
会话粒度
一条 DSH 会话对应一个企微聊天窗口,而不是「同一个 userid 的所有窗口」:
- 单聊:
single:<userid>,该用户一条 Agent - 群聊:
group:<chatid>,群内所有人共用一条 Agent 和同一条串行队列(两人同时发也不会并发followup) allowFrom按发送者 userid 拦截谁能聊天;空 = 所有人可进队。办公命令另用wecomCli.allowFrom- 群里 @机器人 的消息,开头的
@昵称在入站就去掉:模型看到的和标题用的都是「测试一下」而不是「@MediaAgent 测试一下」;正文中间的 @某人 保留,整条只有 @ 时按原文交给模型 - 进程重启后用稳定 id
wecom-+ key 的短 hash 续上同一条会话:进程里已有活 Agent 就直接采用(例如浏览器已打开该行),存档里有就resume,都没有才create。resume 的 cwd / preset 跟存档,不跟当前配置;cwd 不一致时打警告,不改写。 - GUI 标题为「企微·私聊/群」+ 第一句用户话(与其它会话一样由 DSH 生成),不再露出 userid / chatid;同类型窗口若第一句话相近,侧栏仍是两行
- 在 GUI 里归档某条企微会话 = 结束那段上下文:下一条消息用
wecom-<hash>-2(再归档就-3)静默开一条新会话,企微侧不额外提示。DSH 没有取消归档的接口,所以旧会话只是不再被本插件写入,既不会恢复可见也不会被删除 - 所有窗口仍共用同一个
workspace(文件 / rag 环境),与聊天上下文分窗是两件事
兼容的 DeepSeek Harness 版本
DeepSeek Harness 仍是 developer preview,对外置插件没有 semver 兼容承诺。本包 0.4.2 按实际调用的 API 对齐已发布 tag:
| DSH | 本包 |
|---|---|
| 0.1.0-rc.8、0.1.1-rc.1、0.1.1-rc.2 | 可适配(对照开发的是 0.1.1-rc.2 一线) |
| 0.1.0-rc.7 及更早 | 不可适配。rc.7 已有插件配置卡槽位和 dsh plugin add,但浏览器没有 settingsScope.describe(),配置卡加载和凭证「已配置」徽章会失败 |
| 更新的 RC / 未打 tag 的 HEAD | 未保证。升级 dsh 后请再验 Settings 卡和企微连线 |
建议把 dsh 钉在 0.1.0-rc.8 及以上,例如 npx @deepseek-ai/[email protected] web,不要只跑浮动的 latest。
安装
推荐:从 npm 安装
dsh plugin --profile web add @mhfire/dsh-im-bridge
# 或钉版本:
# dsh plugin --profile web add @mhfire/[email protected]
在 $DSH_HOME/profiles/web/cordis.patch.yml(或对应 profile)中补密钥即可(其余项已有 bundle 默认,可按需覆盖):
- id: im-bridge
config:
botId: "<你的 BotID>"
secret: "<你的 Secret>"
# 可选:workspace / personaFile 等,见下方配置项
重启 dsh 进程即可使用(例如 dsh web / pnpm dsh web)。
备选:本地开发
从本仓库 plugin/ 目录或 file: 路径安装:
dsh plugin --profile web add <本包路径>
未配置 botId / secret 时插件仍会加载(不阻塞 dsh web),日志会提示跳过企微连线;也可在 Settings 插件配置卡填写(见下节)。
Settings 插件配置卡
安装插件并启动 dsh web 后,打开 设置 → 插件 → 插件配置,展开 企业微信桥接(与「终端 / Agent 循环 / 网页搜索」同一组卡片)。改完点右下角 保存 写入 settings.yaml 用户层,与 profile cordis.patch.yml 同一层;放弃 丢掉未保存草稿。带「已覆盖」的字段可 重置 回 bundle 默认。
卡片字段自上而下:
| 卡片项 | 对应配置 | 保存后 |
|---|---|---|
| Bot ID / Secret | botId / secret |
徽章变为「已配置」;须重启进程 才会连 WebSocket。输入框为密码框,线上看不到已存字面值;留空再保存不会清空已存凭证 |
| 允许的发送者 userid | allowFrom |
下一轮消息生效;逗号分隔,空 = 允许所有人 |
| 单任务超时(秒) | agentTimeoutSec |
下一轮消息生效 |
| 开始处理时的占位提示 | startHint |
下一轮消息生效 |
| 非白名单拒绝文案 | deniedMessage |
下一轮消息生效 |
| 进入会话欢迎语 | welcomeMessage |
下一轮消息生效 |
| 企微专用 provider / model | provider / model |
只影响之后新建的企微窗口会话;须两项都填才覆盖,否则跟随 GUI 默认模型 |
workspace、agentPreset、persona / personaFile、thinking、maxReplyBytes、reasoningEffort、wecomCli 不在卡片上,仍在 profile patch 或下表中配置。本版本不做凭证热重连;改 wecomCli 也须重启进程。
配置项
bundle 的 cordis.patch.yml 已为除 botId / secret 外的字段提供默认值;下表为完整说明。
| 字段 | 说明 |
|---|---|
botId / secret |
企业微信智能机器人凭证(role('secret'),UI 自动脱敏);缺省时跳过企微侧,不阻塞主进程 |
workspace |
Agent 工作目录(会话 cwd) |
allowFrom |
聊天白名单;空 = 允许所有人问诊断。不控制 wecom-cli |
agentTimeoutSec |
单任务最长执行时间(秒),动画进度条/剩余估算的基准 |
startHint |
开始处理时的占位提示语 |
agentPreset |
Agent 加入的 preset(默认 standard) |
provider / model |
企微专用模型;两者都非空才覆盖,否则跟随 GUI 的 agent-default-model;只填一项会告警并回退。Settings 可编,只影响之后新建的企微窗口会话 |
reasoningEffort |
覆盖生效时可选的推理强度;未覆盖模型时忽略 |
persona / personaFile |
机器人「人设」;优先级:personaFile → persona → 包内默认(按 Host locale.preference 选中/英);覆盖不跟语言切换;含敏感信息请勿入库 |
maxReplyBytes |
回复上限(字节,默认 20000) |
deniedMessage |
非白名单用户的拒绝文案(Settings 可编) |
welcomeMessage |
进入会话欢迎语(Settings 可编) |
wecomCli |
可选的企业微信办公能力(默认关闭)。见下一节 |
thinking |
流式动画。优先级:工具活动(toolLabels)> 模型流式阶段(reasoningStatus / outputStatus,来自 assistant/chunk)> 时间轴 phases 兜底;另有 spin / reasoningSpin / outputSpin / eggs 等 |
企微与 GUI 使用不同模型时,在 profile cordis.patch.yml 同时填写:
- id: im-bridge
config:
provider: deepseek-official
model: deepseek-reasoner
thinking 行为:
- 收到
reasoning-delta→ 「模型思考中」类文案轮换 +reasoningSpin - 收到
text-delta→ 「正在输出回复」类文案轮换 +outputSpin tool/call→activityPrefix+ 友好名;tool/result短暂完成/失败后清空- 尚无 chunk 时 → 按秒数走
phases(与模型是否在推理无关)
thinking:
intervalMs: 1500
reasoningStatus:
- '💭 模型思考中…'
outputStatus:
- '✍️ 正在输出回复…'
toolLabels:
pwsh: PowerShell
企业微信办公能力(wecom-cli)
插件依赖官方 @wecom/cli 二进制。wecomcli-* 装在 $DSH_HOME/wecom-cli-skills(不要装进工作区 .dsh/skills / .agents/skills,也不要装进 $DSH_HOME/skills)。插件只在办公 userid 的单聊 Agent 上注入:skills.register() 装 catalog,tools.register() 装门控工具 wecom_cli。两者都走该 Agent 自己的 ctx,群聊与 GUI 看不到。工作区里其它 skill 仍由 skill-filesystem 发现,不受影响。
办公命令只经 wecom_cli 工具执行:模型传 argv(wecom-cli 之后的参数数组),插件直接 spawn 官方二进制,并拒绝任何 auth init。PATH 上的 wecom-cli 是一个只打印拒绝信息并 exit 1 的 shim,所以群聊、GUI 以及任何 pwsh wecom-cli 都跑不通;shim 的文案会指回 wecom_cli。凭证目录不进程级导出,只在插件自己 spawn 时注入。
wecomCli.enabled 默认关闭。开启须同时配置非空 wecomCli.allowFrom(办公 userid);根级 allowFrom 只管谁能聊天,空名单表示所有人可问诊断。办公名单为空时插件会告警并跳过 shim / 授权 / wecom_cli。
一次性准备:
根级
allowFrom留空(所有人可问诊断),把 办公 userid 写进wecomCli.allowFrom(不要留空)在 Settings → 插件配置 → 企业微信桥接 点「安装官方 skills」(Host 下载官方仓库 zip,解到
$DSH_HOME/wecom-cli-skills)。不要用npx skills add -g(会泄漏给 GUI);skills CLI 没有--dir,加了也不会写到程序目录。若工作区里已有wecomcli-*,先挪到该目录再删工作区副本。也可手动把官方仓库skills/wecomcli-*拷进$DSH_HOME/wecom-cli-skills。在 profile
cordis.patch.yml打开(启用后插件会用已有botId/secret走auth init --bot-id/--secret写入 wecom-cli 凭据,不必扫码、不必npm install -g @wecom/cli):
- id: im-bridge
config:
allowFrom: []
wecomCli:
enabled: true
allowFrom: ["<办公 userid>"]
# skillsDir: '' # 空 = $DSH_HOME/wecom-cli-skills
# configDir: '' # 空 = <workspace>/.dsh/wecom-cli;请 gitignore
| 字段 | 说明 |
|---|---|
wecomCli.enabled |
装 PATH 拒绝 shim、跑授权检查、给企微 Agent 接上 prompt 与 wecom_cli 工具;默认 false |
wecomCli.allowFrom |
能拿到 wecom_cli 工具的 userid;空则跳过 shim / 授权。与根级聊天名单独立 |
wecomCli.skillsDir |
覆盖 skills 根目录;空 = $DSH_HOME/wecom-cli-skills |
wecomCli.configDir |
覆盖凭证目录;空 = <workspace>/.dsh/wecom-cli。请把该目录加入 gitignore。WECOM_CLI_CONFIG_DIR 只在插件 spawn CLI 时注入,不写进程环境,因此不会使用 ~/.config/wecom。 |
改 wecomCli 后须重启进程。未授权时插件仍收消息;启动时用隐藏的 --bot-id/--secret(stderr 非 TTY)写入凭据。若自动写入失败,日志会打印一条带 WECOM_CLI_CONFIG_DIR 的手动命令——必须带上它,否则 npx --yes @wecom/cli auth init --manual 会把凭证写到 ~/.config/wecom,插件读不到。禁止在 Agent 里扫码 auth init(会新建机器人)。
人设(persona)
优先级:personaFile → persona 字符串 → 包内默认人设。
- 包内默认:
persona.default.md(中文)/persona.default.en.md(英文)。按 Host settingslocale.preference(zh|en)选择;未显式选择时回退中文(Host 看不到仅浏览器决定的语言)。每次 assemble 重新读取,Settings 改语言后下一轮请求生效。 - 覆盖不跟语言切换:配置了
personaFile/ 非空persona时始终用该内容。
推荐覆盖方式:在 $DSH_HOME/profiles/<name>/ 与 cordis.patch.yml 同目录放置 persona.md,并在 profile patch 里用绝对路径指向它(相对路径相对进程 cwd,不宜依赖):
- id: im-bridge
config:
personaFile: 'C:\\Users\\you\\.dsh\\profiles\\web\\persona.md'
也可复制 persona.example.md 为模板后按环境填写。支持 {{model}} / {{cwd}} 占位符。含环境凭据的人设文件请勿提交。欢迎语 / 拒绝文案 / 思考动画文案目前仍为中文配置项,不随语言切换。
把 PNG 发到企业微信
Agent 的最终回复若包含指向工作区内 PNG 的 Markdown,桥会在文字流结束之后,把图作为独立图片消息发出(先 uploadMedia,再用 media_id 调用 sendMediaMessage)。
触发写法(相对 workspace):

[屏幕截图](out/frame.png)
- 只认
.png;跳过http(s):/data:;路径必须落在workspace内 - 文件须为真实 PNG(文件头),单张不超过 10MB,最多 10 张(按出现顺序去重)
- 只把 PNG 写到磁盘、回复里没有上述 Markdown,不会发图
- 图在文字气泡之后另发,不会嵌进同一条流式消息
- 入站图片/语音/文件消息仍忽略
安全
config.json/persona.md/.dsh/wecom-cli/等含密钥文件不入库;- 会话与工具输出可能含对抗性文本,插件内置安全提示词约束 agent 不把工具输出当指令。
Known Limitations and Deferred Work
- wecom-cli 凭证在工作区
<workspace>/.dsh/wecom-cli(请 gitignore)。wecomCli.allowFrom里的人借用这份凭据的办公权限;聊天名单(根级allowFrom)不授予办公。办公单聊仍可向任意--chat-id发信,插件不锁定收件人。 - 门控不是沙箱。
wecom_cli工具与wecomcli-*只注册到办公单聊 Agent,PATH 上的wecom-cli一律拒绝,凭证目录也只在插件自己 spawn 时注入;但同进程的 shell 仍可绕过:直接node <@wecom/cli 的 wecom.js 绝对路径>,或npx --yes @wecom/cli并自行设置WECOM_CLI_CONFIG_DIR。不设该变量时这类旁路会落到未授权的~/.config/wecom。真正的隔离需要进程级沙箱。 - 企微通道没有 GUI 审批框:发信、取消会议、删待办、覆盖文档等不可逆操作只靠 prompt 约束(先
--dry-run,等用户下一条确认)。所有企微会话都禁止ask_user_question(会挂到超时)。 - 工作区
.dsh/skills/.agents/skills里残留的wecomcli-*仍会被同 cwd 的 GUI 和群聊发现。其它 skill 不受影响。enabled: false只关拒绝 shim、授权检查、wecom_cli与 wecomcli-* 注册;通道上的ask_user_question禁令仍会注入。
License
MIT