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

全球 IM 渠道 monkey-sking/dsh-im-global

DeepSeek Harness
monkey-sking3 天前有提交已提供中文文档扫描:含敏感能力

一个插件接入六种海外聊天平台并保留会话

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

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

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

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么
dsh 原生插件 · other
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 3 天前
档位由哪些信号合成

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

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

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 49 处,展示前 43 条)
  • 读取本地文件
    lib/index.js:772
    const data = JSON.parse(await readFile(localSecretsFile(), 'utf8'));
  • 写入本地文件
    lib/index.js:783
    await mkdir(path.dirname(file), { recursive: true });
  • 读取本地文件
    lib/index.js:786
    data = JSON.parse(await readFile(file, 'utf8')) ?? {};
  • 写入本地文件
    lib/index.js:792
    await writeFile(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
  • 写入本地文件
    lib/index.js:793
    await chmod(tmp, 0o600);
  • 写入本地文件
    lib/index.js:794
    await rename(tmp, file);
  • 发起网络请求
    lib/client.js:97
    const response = await fetch(ROUTE, {
  • 发起网络请求
    lib/discord/api.js:55
    response = await this.fetch(url, {
  • 发起网络请求
    lib/discord/api.js:99
    this.me = await this.request('GET', '/users/@me');
  • 发起网络请求
    lib/discord/api.js:109
    return this.request('POST', `/channels/${channelId}/messages`, { body });
  • 发起网络请求
    lib/discord/api.js:114
    return this.request('PATCH', `/channels/${channelId}/messages/${messageId}`, {
  • 发起网络请求
    lib/discord/api.js:122
    await this.request('POST', `/channels/${channelId}/typing`, { retries: 1 });
  • 发起网络请求
    lib/discord/api.js:130
    const response = await this.fetch(url, { headers: { 'User-Agent': USER_AGENT } });
  • 读取本地文件
    lib/core/bindings.js:49
    const raw = await readFile(this.file, 'utf8');
  • 写入本地文件
    lib/core/bindings.js:130
    await mkdir(path.dirname(this.file), { recursive: true });
  • 写入本地文件
    lib/core/bindings.js:132
    await writeFile(this.file, payload, 'utf8');
  • 读取本地文件
    lib/settings-store.js:110
    const raw = await readFile(this.file, 'utf8');
  • 写入本地文件
    lib/settings-store.js:143
    await mkdir(path.dirname(this.file), { recursive: true });
  • 写入本地文件
    lib/settings-store.js:145
    await writeFile(tmp, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
  • 写入本地文件
    lib/settings-store.js:146
    await rename(tmp, this.file);
  • 发起网络请求
    lib/web-api.js:148
    const response = await fetch(url, init);
  • 发起网络请求
    lib/adapters/matrix.js:38
    const me = await this.request('GET', '/_matrix/client/v3/account/whoami');
  • 发起网络请求
    lib/adapters/matrix.js:56
    const response = await fetch(`${this.baseUrl}${path}`, {
  • 发起网络请求
    lib/adapters/matrix.js:86
    const payload = await this.request(
  • 发起网络请求
    lib/adapters/matrix.js:119
    const payload = await this.request(
  • 发起网络请求
    lib/adapters/matrix.js:191
    const payload = await this.request(
  • 发起网络请求
    lib/adapters/matrix.js:201
    return this.request(
  • 发起网络请求
    lib/adapters/mattermost.js:45
    const me = await this.request('GET', '/users/me');
  • 发起网络请求
    lib/adapters/mattermost.js:66
    const response = await fetch(`${this.apiBase}${path}`, {
  • 发起网络请求
    lib/adapters/mattermost.js:139
    const post = await this.request('GET', `/posts/${postId}`);
  • 发起网络请求
    lib/adapters/mattermost.js:202
    const post = await this.request('POST', '/posts', {
  • 发起网络请求
    lib/adapters/mattermost.js:211
    return this.request('PUT', `/posts/${messageId}`, { id: messageId, message: text });
  • 发起网络请求
    lib/adapters/mattermost.js:216
    await this.request('POST', '/users/me/typing', { channel_id: convId });
  • 发起网络请求
    lib/adapters/slack.js:67
    const response = await fetch(`${SLACK_API}/${method}`, {
  • 发起网络请求
    lib/adapters/slack.js:257
    const response = await fetch(attachment.url, {
  • 发起网络请求
    lib/adapters/zulip.js:47
    const me = await this.request('GET', '/users/me');
  • 发起网络请求
    lib/adapters/zulip.js:65
    const response = await fetch(`${this.apiBase}${path}`, {
  • 发起网络请求
    lib/adapters/zulip.js:87
    const payload = await this.request('POST', '/register', {
  • 发起网络请求
    lib/adapters/zulip.js:133
    const response = await fetch(`${this.apiBase}${path}`, {
  • 发起网络请求
    lib/adapters/zulip.js:186
    const payload = await this.request('GET', `/streams/${id}`);
  • 发起网络请求
    lib/adapters/zulip.js:196
    const payload = await this.request('POST', '/messages', {
  • 发起网络请求
    lib/telegram/api.js:35
    response = await this.fetch(url, {
  • 发起网络请求
    lib/telegram/api.js:136
    const response = await this.fetch(url);
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

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

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

README

由 DeepSeek 最新模型翻译生成
dsh-im-global DeepSeek Harness 的全球 IM 渠道 —— 一个插件同时接入 Telegram、Discord、Slack、Mattermost、Zulip 和 Matrix,每个会话独立,重启后依然保留。 ⚠️ 安全:默认关闭。 渠道没配白名单时,会拒收所有消息(fail-closed),并显示为锁定状态,直到你加上发送者的 user id。接机器人之前,先看 安全:白名单是 fail-closed 的。 它的结构和钉钉 / QQ / 微信 / 飞书 / 企业微信网关(@lijian-ui/dsh-im-gateway)一致:一个插件,一份 channels: [...] 列表,每个平台一个适配器。 为什么做这个 dsh-telegram 验证了这套模型,但它是第三方维护的单平台渠道:语言写死一种,一个包只支持一个平台,想加第二个平台就得再复制一份同样的会话管线。
展开完整文档(README 剩余 1 节)
dsh-im-global

DeepSeek Harness 的全球 IM 渠道 —— 一个插件同时接入 Telegram、Discord、Slack、Mattermost、Zulip 和 Matrix,每个会话独立,重启后依然保留。

⚠️ 安全:默认关闭。 渠道没配白名单时,会拒收所有消息(fail-closed),并显示为锁定状态,直到你加上发送者的 user id。接机器人之前,先看 安全:白名单是 fail-closed 的。

它的结构和钉钉 / QQ / 微信 / 飞书 / 企业微信网关(@lijian-ui/dsh-im-gateway)一致:一个插件,一份 channels: [...] 列表,每个平台一个适配器。

为什么做这个

dsh-telegram 验证了这套模型,但它是第三方维护的单平台渠道:语言写死一种,一个包只支持一个平台,想加第二个平台就得再复制一份同样的会话管线。

这个包把那套管线抽了出来:

lib/index.js plugin: config, channel lifecycle, routing, commands
lib/core/bindings.js conversation -> session (durable, restart-safe)
lib/core/reply.js turn -> message delivery (streaming, platform-agnostic)
lib/core/commands.js /new /status /help parsing
lib/adapters/telegram.js Bot API long polling
lib/adapters/discord.js Gateway WebSocket + REST
lib/adapters/slack.js Socket Mode + Web API
lib/adapters/mattermost.js WebSocket + REST (self-hosted)
lib/adapters/zulip.js event-queue long polling
lib/adapters/matrix.js client-server /sync long polling

加一个平台,只写一个适配器文件。core/ 里什么都不用改。

平台

| type | 入站方式 | 需要公网 URL? | 凭据 |
| telegram | Bot API 长轮询 | 不需要 | bot token(@BotFather) |
| discord | Gateway WebSocket | 不需要 | bot token |
| slack | Socket Mode WebSocket | 不需要 | bot token xoxb-… + app token xapp-… |
| matrix | /sync 长轮询 | 不需要 | access token(+ homeserver URL) |
| mattermost | WebSocket + REST | 不需要 | personal access token + base URL |
| zulip | 事件队列长轮询 | 否 | <bot email>:<api key> + base URL |

WhatsApp / Teams / LINE / Messenger 不在覆盖范围内:它们只把事件投递到公网 HTTPS 回调,这是另一种托管形态,而不是另一个适配器。

安装

从插件市场安装

在 Plugins → Discover → Manage index sources 下添加这个索引:

https://raw.githubusercontent.com/monkey-sking/dsh-im-global/main/index.json

从本地检出安装

plugin_manager({ action: "install_bundle", target: "/Users/you/.local/share/dsh-im-global" })

这个 bundle 会往配置档组合里插入一行:im-global。

配置(两种方式)

设置页(不写 YAML,不重启)

启用一次 bundle,然后打开 Settings → Chat channels。这个页面管理频道列表:添加/删除频道、选择类型、给凭据起名、设置服务器 URL、测试凭据能否连上平台,然后保存。保存会在原地重启受影响的频道——进程不重启。

凭据不在这里编辑。页面只存凭据的名字;密钥留在凭据存储里。

直接保存 token(没有凭据页的构建)

每张频道卡片还有一个 Token(可选,直接保存) 字段。粘贴 token 后按 保存 token——插件会把它写入 ~/.dsh/dsh-im-global/secrets.json(权限 0600),并在 ctx.credentials 和同名环境变量之后作为兜底解析。宿主机没有凭据界面时很有用:频道随后无需触碰宿主机凭据存储就能启动。

cordis.patch.yml

下面的 ## Configure 讲的是基于文件的方式,它始终是底线:在那里钉住的值会一直生效,直到设置页保存了自己的频道列表。

配置

token 放在 ctx.credentials 里,绝不写进配置文件。先创建它们——通过凭据设置页或宿主机的凭据存储——用频道引用的名字:

| platform | default credential | what to paste |
| Telegram | TELEGRAM_BOT_TOKEN | BotFather → /newbot → 它打印的 token |
| Discord | DISCORD_BOT_TOKEN | Bot → Token(Developer Portal → Bot → Reset Token) |
| Slack | SLACK_BOT_TOKEN | OAuth & Permissions → Bot User OAuth Token(xoxb-…) |
| Slack | SLACK_APP_TOKEN | Basic Information → App-Level Tokens(xapp-…,scope connections:write) |
| Matrix | MATRIX_ACCESS_TOKEN | 任意账号的 access token |
| Mattermost | MATTERMOST_TOKEN | Profile → Security → Personal Access Tokens |
| Zulip | ZULIP_BOT_TOKEN | Settings → Bots 里的 <bot email>:<api key> |

然后编辑配置档 cordis.patch.yml 里的 im-global 行:

- id: im-global
config:
channels:
- id: telegram-main
type: telegram
tokenRef: TELEGRAM_BOT_TOKEN
- id: discord-main
type: discord
tokenRef: DISCORD_BOT_TOKEN
cwd: /Users/you/dev/workspace
- id: slack-main
type: slack
tokenRef: SLACK_BOT_TOKEN
appTokenRef: SLACK_APP_TOKEN
- id: matrix-main
type: matrix
tokenRef: MATRIX_ACCESS_TOKEN
homeserverUrl: https://matrix.org

设置存储

设置页会写入 ~/.dsh/dsh-im-global/settings.json(先写临时文件,再原子重命名)。
这个文件存在时,其中的 channels 列表优先于组合配置里的列表;删掉它,就回退到 cordis.patch.yml。

安全:允许名单默认关闭

聊天桥接会把任何能触达机器人的人都变成智能体驱动者,所以这个插件默认关闭。没配允许名单的频道什么都不接受——连主人也不行——在设置页显示为已锁定,所有入站消息一律丢弃并记日志。

要打开某个频道,把发送者的平台用户 id 填进名单,可以在设置页填(允许的用户 id 字段,逗号分隔),也可以在组合配置里填:

- id: im-global
config:
allowedUsers: [123456789] # plugin-level default for every channel
channels:
- id: telegram-main
type: telegram
tokenRef: TELEGRAM_BOT_TOKEN
allowedUsers: [987654321] # per-channel list beats the plugin-level

频道级的 allowedUsers 优先;没配就用插件级的 allowedUsers;两者都没配,频道保持锁定。怎么找用户 id:Telegram = 机器人看到的数字 id,Discord = 数字 snowflake,Slack = 成员 id,Matrix = 完整的 @user:server,Mattermost/Zulip = 数字用户 id。机器人会从每条入站消息记录的 userId 里读到它们。

这是平台和你的智能体之间唯一的闸门。把它当防火墙规则对待:暴露机器人之前先配好,加用户时记得同步维护。

这道闸门覆盖聊天驱动智能体的两种途径:

- 键入的消息,在路由里、任何会话工作开始之前检查;
- 回答待答问题的按钮按下,在适配器里、选项发出之前检查。名单外的发送者按下按钮会被拒绝,提示「你不在允许名单上,无法回答」,永远到不了问题代理——否则群里任何人只要看到键盘就能替主人作答。

Telegram 专属说明

- 用 @BotFather 创建机器人,复制 token。
- 同一个 token 只能有一个进程长轮询。 第二个 getUpdates 会收到 409 Conflict,所以启动这个频道之前,先停用同一个机器人上的其他 Telegram 频道(配置档补丁用 - id: telegram / disabled: true 做到这点)。
- 从别的 Telegram 插件迁过来?把对话带过来:

node scripts/migrate-telegram-bindings.mjs telegram-main

它会复制旧的 chat -> session 映射,同一个聊天就能续上同一个 Harness 会话,不用从零开始。

Mattermost / Zulip 专属说明

- 两者都是自托管,所以 baseUrl 必填(https://chat.example.com)。
- Mattermost 需要 personal access token,socket 用它认证;适配器每 30 秒 ping 一次,否则 socket 会静默。
- Zulip 的事件队列按自己的节奏过期;遇到 BAD_EVENT_QUEUE_ID 时重新注册,而不是抛出错误。
- Zulip 的回复发到同一个 stream 和 topic;私信则回复给发送者。

Discord 注意事项

- 启用 Message Content 特权 intent(Bot → Privileged Gateway Intents)。不启用的话,机器人收到的每条消息 content 都是空的。
- 用 bot scope 邀请机器人,并授予 Send Messages / Read Message History / Attach Files 权限。
- 在聊天里用 !new:开头的 / 被 Discord 自己的斜杠命令 UI 占用,所以 /new 可能被客户端吞掉。!new、!status、!help 在任何地方都能用;私信和其他平台也接受 /…。

Slack 注意事项

- 启用 Socket Mode,创建带 connections:write 的 app-level token。
- 事件订阅:message.channels、message.im(私有频道再加 message.groups)。
- 改完 scope 要重装应用,否则机器人会静默地收不到事件。

Matrix 注意事项

- 任何账号都行;机器人在它能读取的房间里都会回复。
- 适配器会把 since 往前带,所以重启不会重放历史消息。

聊天内命令

| 命令 | 作用 |
| !new / /new | 切换到新会话(旧会话在 Web UI 里仍可读) |
| !status | 会话 id、状态、工作目录 |
| !help | 命令列表 |

普通消息直接发给智能体。平台允许适配器下载图片时,图片会作为附件交给模型。

设计说明

- 会话身份就是平台身份。 会话 id 由 type:conversationId 推导而来(discord:1234…),所以重启后会接着同一个对话,而不是开一个空白会话。
- 创建撞车时回退到恢复。 如果日志已在磁盘上、但存在性探测没发现,创建路径会改走恢复,而不是让每条消息都失败——正是这个 bug 弄坏了 Telegram 频道。
- 每轮只发一条预览消息。 流式输出按 1.6 秒的节奏原地编辑同一条消息,而不是每个 token 发一条。
- 机器人从不回复自己。 适配器在路由前会丢弃 bot 作者、webhook 和自己的用户 id。
- 符合平台习惯的 markdown。 表格转成对齐的等宽块,标题转成加粗,大规模提及会被中和——手机上直接显示原始竖线表格,看起来就像乱码。

引用消息

回复中引用了更早的消息时,会作为短前缀传给模型,这样「这个怎么改」就有指代对象:

[引用 你 的消息] 配置文件在 ~/.dsh/ 下面这个怎么改

| 平台 | 传输中的引用内容 | 状态 |
| Telegram | reply_to_message(完整消息) | ✅ 内联 |
| Discord | referenced_message(完整消息) | ✅ 内联 |
| Slack | 不内联;thread_ts → conversations.replies | ✅ 线程父消息(1 次调用,缓存 2 分钟) |
| Matrix | 只有 event id;m.in_reply_to → /context | ✅ 精确引用的事件(缓存 2 分钟) |
| Mattermost | root_id → GET /posts/{id} | ✅ 线程根消息(缓存 2 分钟) |
| Zulip | 正文里的 markdown 引用块 | ✅ 内联解析,无需额外调用 |

Telegram、Discord 和 Zulip 不需要额外请求。Slack、Matrix 和 Mattermost 会拉取一次被引用的消息,并缓存两分钟,因为它们的协议只携带 id。

交互式提问

智能体提问时(ask_user_question),问题在聊天里作答,而不是只在 Web UI 里:harness 通过 user-questions/request 瀑布流暴露问题,本渠道认领属于自己会话的问题(其余问题落到 Web UI)。

各平台对交互式作答的支持:

| 平台 | 原生提供的控件 | 本插件中的支持 |
| Telegram | 内联键盘(回调按钮、URL 按钮、web_app)。每行 ≤8 个,≤100 个按钮 | ✅ 按钮 —— callback_query + answerCallbackQuery,作答后收起键盘 |
| Discord | 消息组件:按钮(每行 ≤5 个,每条消息 ≤25 个)、选择菜单、模态框。回复需在 3 秒内完成交互 ACK | ⏳ 文本回退(1) … 2) …) |
| Slack | Block Kit actions:按钮(每个 block ≤25 个)、选择菜单、overflow、模态框。经 Socket Mode 到达 —— 无需公网 URL | ⏳ 文本回退 |
| Mattermost | 交互式消息附件(actions),但点击会 POST 到集成 URL | ⏳ 文本回退 |
| Matrix | 无按钮;emoji 回应或纯文本 | ⏳ 文本回退 |
| Zulip | 无按钮;回应和纯文本 | ⏳ 文本回退 |

文本回退到处都能用,且不依赖平台提供任何东西:问题带编号发出,回复 2(或选项标签)即可作答。问题未关闭时,它占用下一条消息 —— 输入 2 是作答,而不是开启新一轮。

审批(user-approval)是独立服务,尚未路由到聊天;需要审批的工具仍会出现在 Web UI 里。

可靠性说明

有四种失败模式做了显式处理,因为从聊天侧看,每一种都像是「机器人坏了」:

- 已归档会话。 内核拒绝已归档会话的每一步(archived-session-gate),表现为 blocked 轮次:消息被接受,却以沉默作答。渠道检测到后,会取消归档该会话;只有取消归档被拒时,才回退到新会话。
- 长时间使用工具的轮次。 本 harness 发布的是已完成的步骤消息,而非原始增量,所以流式预览由两者共同驱动。否则多步轮次在结束前什么都不显示。
- 插件重载。 disposer 同步注册,并在停止适配器前把实例标记为已死。异步副作用把 cordis 的启动完成之后才释放,所以启动期间重载会让旧轮询器继续存活:同一个 bot token 上两个轮询器争抢 getUpdates(409 Conflict),还会悄悄吞掉对方的消息。
- 模型接线缺失。 只配 agentOptions,智能体在组装提示词时没有服务商和模型;installModelSelection 把它同时接到组装和请求路由上。缺了它,一轮对话几毫秒就结束。

测试

node --test test/*.test.js
预览分享卡片长什么样 ↓monkey-sking/dsh-im-global
分享 · DeepSeek Harness Hub
monkey-sking/dsh-im-global

一个插件接入六种海外聊天平台并保留会话

这是你的仓库?认领条目

回到顶部