<p align="center">
  <img src="assets/branding/dsh-banner.png" alt="DSH IM Connect" width="100%">
</p>

<div align="center">

# DSH IM Connect

  **把飞书、Lark、钉钉、企业微信、微信、QQ、Telegram、Discord、Slack 接到本机 DeepSeek Harness**

  [English](README.md) · [更新日志](CHANGELOG.zh-CN.md) · [QQ 引用交接](docs/01-当前工作/I022-QQ直接引用接入/00-迭代总览.md) · [侧栏协作交接](docs/07-迭代归档/2026/I024-侧栏页签协作与渲染修复/00-迭代总览.md) · [Apache-2.0](LICENSE)

  [![许可证：Apache-2.0](https://img.shields.io/badge/许可证-Apache--2.0-blue.svg)](LICENSE)
  [![npm package](https://img.shields.io/npm/v/%40michengai%2Fdsh-im-connect.svg?label=npm%20package)](https://www.npmjs.com/package/@michengai/dsh-im-connect)
  [![npm 下载量](https://img.shields.io/npm/dt/%40michengai%2Fdsh-im-connect.svg?label=npm%20%E4%B8%8B%E8%BD%BD%E9%87%8F)](https://www.npmjs.com/package/@michengai/dsh-im-connect)
  [![DSH Web Plugin](https://img.shields.io/badge/DSH%20Web-Plugin-0f766e.svg)](https://github.com/MichengAI/dsh-im-connect)
  [![Node.js 22 or later](https://img.shields.io/badge/Node.js-22%20or%20later-339933.svg?logo=node.js&logoColor=white)](https://nodejs.org/)
  [![Channels](https://img.shields.io/badge/channels-9-238636.svg)](#-支持的渠道)
</div>

> DSH IM Connect 是社区维护的 DeepSeek Harness（DSH）插件，并非 DeepSeek AI 官方产品。

## 功能概览

不在电脑前，也能通过常用聊天软件把任务交给本机 DSH，并在同一聊天里接收回复、回答问题和处理工具审批。

- **连接常用消息平台**：支持钉钉、飞书、Lark、微信、企业微信、QQ、Telegram、Discord 和 Slack。
- **多个账号分别配置**：同一平台可添加多个账号，各自选择工作区、模型、推理强度、权限、私聊准入和发送方式。
- **手机上完成任务交互**：下任务、读回复、回答单选或多选问题；已批准用户可在私聊中批准或拒绝工具执行。
- **聊天记录互不混淆**：每个 IM 聊天对应独立会话，在网页工作区「频道」中回看。
- **按平台扫码或填写凭据**：## 界面预览

在「设置 → IM助理」连接账号，管理消息接收开关。
- **控制谁能使用**：群聊通过 @ 触发，私聊按账号的准入设置处理，详见下表。

## 谁可以驱动助手

入站消息先看发送者，再处理命令、工具审批和注入。

| 场景 | 行为 |
| --- | --- |
| 群聊未 @ 当前机器人 | 忽略，不回复、不进待批准；只 @ 其他成员也不触发 |
| 群聊已准确 @ 当前机器人 | 不用绑定，任何人都可以下任务 |
| 私聊 · 扫码用户 | 微信 / 飞书 / Lark / QQ 扫码者自动进白名单，可直接对话 |
| 私聊 · 其他人 | 进入设置页待批准；不批准就不能驱动助手 |
| 私聊 · 扫码不返回身份 | 钉钉 / 企微的快捷绑定只返回机器人凭据，扫码者仍需在设置页批准 |
| 私聊 · 手动凭据 | Telegram、Discord、Slack，以及手动填写凭据的钉钉 / 企微 / QQ，所有私聊都要先批准 |
| 私聊缺少 userId | 拒绝 |
| 工具审批 | 在绑定该会话的聊天里回复「批准 / 拒绝」；群聊和私聊都可以，不用去网页。5 分钟内未回复按取消结束，期间其他文字不会进入下一轮 |
| 交互选择 | 在原 IM 会话回复选项序号或文字；多选用逗号分隔，也可补充自定义答案；群聊只接受任务发起者回答 |

微信是扫码渠道且只支持私聊，所以连上后用**同一个微信号**即可直接用。换一个微信号私聊，会出现在设置页待批准。

## 📡 支持的渠道

<p align="center">
  <code>🔔 钉钉</code>&nbsp;
  <code>🐦 飞书</code>&nbsp;
  <code>🌐 Lark</code>&nbsp;
  <code>💬 微信</code>&nbsp;
  <code>🏢 企业微信</code>&nbsp;
  <code>🐧 QQ</code>&nbsp;
  <code>✈️ Telegram</code>&nbsp;
  <code>🎮 Discord</code>&nbsp;
  <code>🧩 Slack</code>
</p>

| 渠道 | 状态 | 接入方式 | 需要 |
| --- | --- | --- | --- |
| 🔔 **钉钉** | ✅ 可用 | 扫码，或 Client ID / Secret | 钉钉开放平台机器人；回复优先走 AI Card |
| 🐦 **飞书** | ✅ 可用 | 扫码（自动创建机器人），或 App ID / App Secret | 飞书账号，或自建机器人凭据 |
| 🌐 **Lark** | ✅ 可用 | 扫码，或 App ID / App Secret | Lark 国际版账号，或自建机器人凭据 |
| 💬 **微信** | ✅ 可用* | 官方 iLink 扫码 | 建议专用小号；仅私聊 |
| 🏢 **企业微信** | ✅ 可用 | 扫码（推荐），或 Bot ID / Secret | 企业微信智能机器人 |
| 🐧 **QQ** | ✅ 可用 | 扫码，或 AppID / AppSecret | QQ 开放平台机器人，不是个人号 |
| ✈️ **Telegram** | ✅ 可用 | 仅填 Bot Token | `@BotFather`；同一 Bot 不要同时开 Webhook |
| 🎮 **Discord** | ✅ 可用 | 仅填 Bot Token | Developer Portal 的官方 Bot。打开 Message Content Intent；文字频道和公告频道第一次 @ 后在 Thread 中继续，不必重复 @ |
| 🧩 **Slack** | ✅ 可用 | 填 Bot Token 与 App Token | api.slack.com 建应用并开启 Socket Mode。私聊直接回复，频道被 @ 后在该消息的线程里回复，线程内后续消息不必重复 @ |

✅ 可用 = 文字收发可用 ｜ *微信 = 只走腾讯官方 iLink，不做逆向个人号 ｜ 群聊都需要 @ 机器人才回复。Discord 仅文字频道和公告频道在 Bot 自己创建的 Thread 中免重复 @；Slack 在机器人已参与的消息线程中免重复 @

## 图片输入

图片输入使用与 DSH Chat 一致的模型能力和附件规则，不按模型名称猜测视觉能力：

覆盖微信、企业微信、钉钉、飞书、Lark、QQ、Telegram、Discord 和 Slack；具体平台消息形式和联调检查见[图片输入验收](docs/image-input.md)。

- 以当前 IM 会话实际使用的模型为准，而不是只看全局默认模型。
- 模型声明支持 `image` 时，将图片保存为 DSH 标准图片附件并提交给模型；会话记录保留图片引用，而不是只有本地文件路径文字。
- 模型明确不支持图片时，在 IM 提示切换模型，不静默丢图。模型未提供能力元数据时，遵循 Chat 的兼容规则，不仅因元数据缺失而拒绝。
- 图片仍受渠道下载限制、宿主附件大小与格式限制，以及原有私聊准入和群聊 @ 规则约束。
- 此功能是用户向机器人发送图片进行分析，不代表所有渠道都支持机器人发送图片或生成图片。

## 文件输入

微信、企业微信、钉钉、飞书、Lark、QQ、Telegram、Discord、Slack 可接收普通文件，并通过 Chat 的上传服务交给当前会话。可以发送 PDF、文档、表格等文件，再让助手读取或处理；能否解析具体格式与网页 Chat 的模型、工具和文件能力一致，不承诺每种格式都能直接理解。

- 需要支持的宿主（`>=0.2.0-rc.1 <0.3.0-0`）所提供的文件上传服务。早期版本与 `0.3.0` 代际破坏性更新由版本门拦截。仍在使用 `0.1.x` 的，请安装插件 `0.1.78`，不要装 `@latest`。
- 单条消息最多 4 个普通文件，累计最多 20 MiB，还受渠道下载限制约束。
- 文件先保存为 Chat 标准附件，通过当前会话专属的凭据提交；不把本地路径文字当作文件输入。
- 保留私聊准入与群聊提及规则；文件说明中的 `/new` 或“批准”属于普通内容，不执行命令或审批。
- 上传失败不提交部分正文，停用或重载账号后不继续向旧会话提交。支持回复后的文件产物回传。

## 文件回传

微信、企业微信、钉钉、飞书、Lark、QQ、Telegram、Discord 和 Slack 均可回传助手产出的文件。例如：“整理成 PDF 报告，并把文件发给我。”

- 遵循 DSH 宿主交付契约：当助手通过 `tool-present`（`deliverables/presented`）显式声明交付时，对应文件将作为附件发到聊天中；底层的普通工具写操作（如修改代码、记录状态文件）不会被自动当作产物发送，防止群聊噪音与隐私泄露。
- 文件访问范围与 Chat 一致，允许宿主可读取的工作区外文件。不从回复正文中猜测文件路径；通过脚本等方式生成的文件，由助手明确 `present` 交付。
- 已支持的宿主使用 Chat 完整文件下载服务及其配置的大小上限。目录和符号链接不发送，渠道自身的文件限制仍然适用。
- 单个文件发送失败会明确提示文件名，并继续处理其他文件；切走会话或停止账号后，不会把尚未投递的文件转发到其他聊天。
- 单独发送 `/export`，可将当前聊天绑定会话的 Chat ZIP 日志发回当前聊天（不含子会话）。ZIP 上限为 32 MiB，渠道自身限制仍适用；超限或发送失败时可在网页 Chat 使用 `/export` 下载。不接受路径或其他会话 ID 参数。

## 操作菜单

有效点击后，支持更新的原卡片移除按钮，提示已选择；实际执行结果见后续回复。企业微信仅能在按钮回调窗口更新，过期卡片仍由服务端拒绝。`/status` 根据实际状态提供停止任务、暂停或恢复目标、查看队列等操作。

支持原生按钮的渠道发送 `/sessions`、`/workspaces`、`/models` 即可直接选择，无需再进入菜单。分页后，回复序号或 `/session 序号`、`/workspace 序号`、`/model 序号` 对应当前选择页；文字渠道仍使用原列表序号。

发送 `/menu` 或 `/m`，可选择会话、工作区、模型和 Agent 预设，以及新建、停止、导出和查看状态。列表支持分页；点击按钮或回复当前菜单序号即可操作，普通文字退出菜单。

菜单提供上一页、下一页和返回入口；企业微信按卡片容量分页，主菜单也可通过 `/menu 2` 翻页。会话、工作区和模型选择分别可直接发送 `/menu sessions`、`/menu workspaces`、`/menu models`；预设与推理可用 `/menu presets`、`/menu reasoning`。

常用命令回复会提供相关下一步，例如 `/model` → 选择模型 / 调整推理 → 返回菜单。支持原生按钮时可点击跳转，其他情况显示可直接发送的命令。普通结果回复的导航按钮不支持数字选择，避免把聊天中的数字误当操作；只有选择菜单支持回复序号。超长卡片保留完整文字回复。

钉钉、Telegram、Discord、Slack、飞书/Lark 使用原生按钮；企业微信在按钮数量和正文符合平台限制时使用卡片，其他情况及 QQ、微信使用文字序号。平台明确拒绝或超出卡片容量时退回完整文字；网络异常导致发送结果未知时仅提示确认，避免重复发送。菜单按账号、聊天、操作者和会话隔离，15 分钟或重启后失效；执行时重新检查权限，旧按钮不能重复执行。

审批与问答复用上述原生按钮渠道：审批提供批准一次/拒绝，单选题直接点选，多选题勾选后提交；开放问题保留文字回答。审批始终留在发起任务的聊天里，不会回退到网页端；提示发送失败时按拒绝处理。5 分钟内没有批准或拒绝就按取消结束，并说明这次未执行；这段时间里的其他文字只提示先完成审批，不会送进下一轮。图片和文件仍按原消息提交，不当成审批答复。群聊里只接受任务发起者批准，私聊没有额外限制，关闭命令不影响已有审批及问答。问题不设这个超时，下一句文字仍作为答案。


Agent 预设：`/presets`（别名 `/presetlist`）查看目录，`/preset` 查看当前预设，`/preset 序号或ID` 使用预设在当前工作区新建并切换会话，旧会话保留。纯数字 ID 使用 `/preset id:ID`，`/preset --default` 使用宿主默认预设。账号默认设置不变，后续 `/new` 仍按账号设置创建。原生按钮渠道可直接选择，文字渠道使用列表序号（15 分钟有效）。

推理等级：`/reasoning`（别名 `/reasonings`、`/reasoninglist`）提供当前模型的等级选择，`/reasoning 序号` 或 `/reasoning id:等级ID` 设置等级，`/reasoning --default` 恢复默认。原生渠道支持分页按钮；旧列表序号和按钮绑定会话与模型，变化后需重新获取列表。模型与推理修改沿用 Chat 的默认选择保存规则。

IM 消息发起的处理结束后，会在正文和文件投递完成后给出一次结果提示，并提供最近记录、状态、会话导出和菜单入口。执行失败、停止和投递失败会分别说明；不会自动重跑任务或重发文件。关闭命令时只显示文字提示，普通数字仍作为聊天内容。审批和问答沿用原交互提示。切换会话、停用账号或后续回合已开始时，不再发送旧回合的完成导航。

## 消息处理状态

普通聊天消息会随实际任务更新状态；命令仍使用文字回复。

- 钉钉：在原消息上显示排队、思考、等待确认、已完成、失败或取消标签。
- 飞书 / Lark：使用处理中、完成和失败表情；等待确认沿用处理中表情，取消时撤下表情。
- Telegram：使用 👀 处理中、🤔 等待确认、👍 完成、👎 失败；取消时撤下表情。处理期间持续显示输入状态。
- Discord：使用与 Telegram 相同的表情，并编辑同一条消息显示逐步生成的回答。处理期间持续显示输入状态。取消时撤下表情。
- Slack：用 ✅ 完成、❌ 失败、🤔 等待确认、👀 处理中作为消息表情回应（需要 `reactions:write`）；回答通过编辑同一条消息逐步生成，Slack 没有机器人输入状态。
- 微信：处理期间刷新输入状态，结束或等待确认时停止。企业微信、QQ 保持现有回复方式。

微信默认不投递同一条助手消息中带 `tool-call` 的过渡文字，减少回复窗口消耗；纯文本回复、审批、提问和成果文件不受此过滤影响。过滤不算成功投递，补发读取历史时使用相同规则。该措施不会解除平台限额，长回复分片和必要交互仍可能占满窗口；送达未知或部分送达不会盲目整条重发。实现、验证与回滚见[微信投递交接](docs/01-当前工作/I023-微信中间叙述投递过滤/00-迭代总览.md)。

“已完成”表示该回合正常结束且正文、文件均已投递；停止、失败和下一条排队消息不会误标完成。状态接口受渠道权限与网络影响，更新失败不阻塞聊天；文字标签跟随网页语言设置。

## 连接诊断

账号设置中的“诊断连接”会实际调用渠道接口：微信查询配置，企微检测现有连接的 ping 回执，钉钉验证应用凭据，飞书/Lark 查询鉴权与机器人身份，QQ 查询鉴权与网关，Telegram 查询机器人身份及 Webhook 冲突，Discord 查询机器人身份及 Gateway 地址，Slack 查询机器人身份，并在已建立 Socket Mode 连接时回报连接状态（不新建连接）。

每项显示通过、失败或未验证、耗时和处理建议。诊断不发送测试消息、不额外拉取聊天消息；单项通过不代表完整收发权限已验证。旧后端不支持诊断时会提示重启 DSH 并刷新页面。接口、各渠道能力边界与验证记录见[账号连接诊断](docs/01-当前工作/I015-命令权限与Chat命令/06-账号状态检查.md)。

在「设置 → IM助理」按渠道添加账号。展开渠道后，点击账号名称可填写备注，用来区分是谁在使用；留空则恢复自动名称。设置按钮配置工作区、模型、权限、私聊准入和发送方式，那里也可以改备注；接收消息开关位于账号行：

![IM 助理设置页](assets/screenshots/settings-channels.png)

工作区左侧「任务 / 频道」分列。IM 会话只出现在「频道」：

![工作区频道侧栏](assets/screenshots/workspace-channels.png)

企业微信等渠道支持扫码快捷绑定：

![企业微信扫码绑定](assets/screenshots/wecom-qr.png)

连上后，可在各 IM 里直接驱动本机助手：

<p align="center">
  <img src="assets/screenshots/wecom-chat.jpg" width="220" alt="企业微信对话">
  <img src="assets/screenshots/weixin-chat.jpg" width="220" alt="微信对话">
  <img src="assets/screenshots/dingtalk-chat.jpg" width="220" alt="钉钉对话">
</p>
<p align="center">
  <img src="assets/screenshots/feishu-chat.jpg" width="220" alt="飞书对话">
  <img src="assets/screenshots/qq-chat.jpg" width="220" alt="QQ 对话">
  <img src="assets/screenshots/telegram-chat.jpg" width="220" alt="Telegram 对话">
</p>

## DSH 产品生态

想使用桌面工作台，可下载 [DSH Codex Desktop](https://github.com/MichengAI/dsh-codex-desktop/releases)；已有 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 环境，可按各项目 README 按需安装。以下列出 11 个自研插件；桌面端实际随附范围以对应版本的发行说明和内置清单为准。

| 插件 | 你可以用它做什么 |
| --- | --- |
| [Codex UI](https://github.com/MichengAI/dsh-codex-ui) | 整理项目与会话、搜索任务、跳转对话轮次 |
| [Agency Agents](https://github.com/MichengAI/dsh-agency-agents) | 按任务选择并召唤专业角色 |
| [Skills Manager](https://github.com/MichengAI/dsh-skills-manager) | 统一查找、启停、创建和导入本机技能 |
| [Archive Manager](https://github.com/MichengAI/dsh-archive-manager) | 搜索、恢复或清理已归档会话 |
| [IM Connect](https://github.com/MichengAI/dsh-im-connect) | 从消息平台下任务、收回复 |
| [Automation](https://github.com/MichengAI/dsh-automation) | 按计划执行任务，查看每次运行的结果 |
| [BTW](https://github.com/MichengAI/dsh-btw) | 在当前上下文中临时旁问，不打断主任务 |
| [Simplify](https://github.com/MichengAI/dsh-simplify) | 用 `/simplify` 整理 Git 改动范围内的代码 |
| [PUA](https://github.com/MichengAI/dsh-pua) | 引导 Agent 在失败时换方法、查原因，并在完成前验证结果 |
| [Code Review](https://github.com/MichengAI/dsh-code-review) | 用 `/review` 发起独立 Agent 代码审查，在当前会话接收报告 |
| [Codex Pet](https://github.com/MichengAI/dsh-codex-pet) | 通过桌面宠物查看会话提醒、处理工具审批和问题回答 |

## 前置条件

- 已可正常运行 DeepSeek Harness Web，且可在 PowerShell 中使用 `dsh`。
- 宿主支持范围：DeepSeek Harness `0.2.x` 全系列（`>=0.2.0-rc.1 <0.3.0-0`），已验证兼容 `0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-rc.1`、`0.2.1-rc.2`、`0.2.1-rc.3`，并自动支持后续日常小版本、alpha 与 RC，同代宿主升级无需重复等待发版。早期版本与 `0.3.0` 代际破坏性更新由版本门拦截。仍在使用 `0.1.x` 的，请安装插件 `0.1.78`，不要装 `@latest`。
- 以下示例使用 `web` profile；请替换为实际目标 profile。
- 从源码安装或二次开发需要 Node.js 22+；仅从 npm 安装无需在任意目录执行 `npm install`。
- 安装后必须重启 `dsh web`，并在浏览器硬刷新，才能看到「设置 → IM助理」。
- **反向代理部署**：支持将 DSH 部署在带路径前缀的反向代理后面（如 `/app/dsh/`），插件 API 与更新检查端点统一自动按页面基址（`document.baseURI`）相对解析，避免丢失代理路径前缀导致 404。

## 安装

以下安装命令使用官方 npm 源。

### 让 Agent 帮你安装（推荐）

把下面这段话发给任意能够执行本机终端命令的 Agent。将 `web` 替换为实际使用的 profile；安装完成后，在 DSH 中使用本插件。

```text
请将 DSH 插件 @michengai/dsh-im-connect 安装到本机 web profile，执行：dsh plugin --profile web add @michengai/dsh-im-connect@latest --registry=https://registry.npmjs.org/。安装后执行 dsh --profile web --dump-config，确认配置包含 im-connect，并告诉我如何重新加载 DSH 和开始使用。
```

### 从官方 npm 安装最新版

在任意 PowerShell 目录执行：

```powershell
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
dsh plugin --profile web add @michengai/dsh-im-connect@latest --registry=https://registry.npmjs.org/
dsh --profile web --dump-config
```

需要钉死某一版时，把 `@latest` 换成具体版本，例如 `@0.1.28`。

配置输出中应包含 `im-connect`。安装后重启 DSH Web 并在浏览器硬刷新。不要手工复制客户端文件，`dsh plugin add` 会同时应用 `cordis.patch.yml`。

## 插件配置与可选开关

插件遵循 DSH 官方扩展配置规范，配置项位于：
> **主界面侧边栏「插件」→ 点击 `@michengai/dsh-im-connect` 详情页**（对应宿主 `plugins.bundle.config` 槽位）。

此处提供原生配置项。界面配置值保存在插件 `stateDir` 下的 `channels.json` 中，重启后保留；它们不是等价的 Cordis YAML 配置项。`location` 和 `taskTreeSessions` 只通过此界面管理，`onlineUpdate` 另受下述宿主锁定规则约束：

### 1. 入口位置 (`location`)
- **默认状态**：仅设置（`settings`）
- **功能说明**：控制 IM 管理面板在宿主中的导航入口展示位置，支持三态切换（对齐官方扩展入口规范）：
  - **全部展示 (`both`)**：同时在左侧侧边栏常驻图标并在「设置 → IM助理」保留入口；
  - **仅侧边栏 (`sidebar`)**：仅在左侧主导航栏显示图标入口，不在全局设置侧栏中显示；
  - **仅设置 (`settings`)**：注销侧边栏图标（若当前正停留在 IM 面板，会自动安全切回默认视图，防止悬空和白屏），仅在「设置 → IM助理」保留入口。
- **生效效果**：在侧边栏点击即可直达 IM 助理中心，并在主工作区以 **960px 居中版心**（对齐官方插件管理与市场规范）展开完整大面板，彻底告别在设置弹窗狭窄视口内滚动的局限；两处入口数据与状态 100% 实时同步。

### 2. 在线更新 (`onlineUpdate` / `updater`)
- **默认状态**：开启（`true`）
- **功能说明**：控制是否允许在线检查并安装 IM 连接器的新版本。
- **生效效果**：
  - **界面联动**：关闭开关后，IM 管理面板标题旁的「检查更新」按钮与版本提示将即时隐藏，无需刷新页面；开启后自动恢复展示；
  - **接口安全拦截**：关闭后，服务端的更新路由对任何版本检查和安装请求均返回 `HTTP 403` 禁用状态，彻底杜绝在插件内安装未审核版本；
  - **宿主/平台级锁定**：面向精选插件索引（如 VibeDev）或企业安全合规环境，支持直接在 Cordis 配置文件（bundle patch）中声明，无需进入界面手动配置：
    ```yaml
    updater: false # 或 onlineUpdate: false
    ```
    配置生效后，更新器在启动时即被禁用，更新入口隐藏，详情页在线更新开关置灰。宿主锁定优先于 `channels.json` 中的用户选择，界面不能解除锁定；宿主移除锁定后，重新按已保存的用户选择生效。
  - **更新模式**：发现新版后，只有检测到 DSH CLI 或 Desktop 更新服务时才可使用“自动更新”；其他环境会在弹窗中提供可复制、与当前 Profile 对应的手工更新命令。

### 3. 在任务树显示 IM 会话 (`taskTreeSessions`)
- **默认状态**：关闭（`false`，默认在任务树隐藏即时通讯会话，保持日常工作区清爽）
- **功能说明**：控制是否在官方工作区任务树中显示各渠道产生的即时通讯会话。
- **生效效果**：
  - **降噪隔离**：关闭后，各渠道产生的即时通讯会话（包括新格式 `dsh-im-session-*`、存量 `im:*` 前缀以及活跃频道绑定会话）自动从左侧官方工作区树中过滤，避免高频沟通的即时会话刷满日常开发工作区；
  - **频道面板不受影响**：关闭仅作用于主任务树，在侧栏「频道」页签中依然完整保留并可随时点开、回复和复盘全部渠道会话；
  - **轻量管道协同**：基于 `NativeTabRegistry.sessionFilters` 标准协作协议实现，无需深度劫持宿主组件，切换即时生效，与定时插件及归档管理等第三方插件完全兼容。未支持该协议的外壳会自动安全降级并提示。

## 使用

打开「设置 → IM助理」，在目标渠道点击「添加账号」，并为该账号选择工作区、模型、权限、私聊准入和发送方式。若需要新工作区，可展开工作区下拉列表点击底部的「添加工作区…」直接调起系统文件选择器（禁止手输路径）；不支持原生目录选择器的宿主会提示「当前 Host 无法新增工作区」，请升级到「环境要求」中列出的受支持宿主版本，或直接在宿主侧栏添加工作区。

| 目标 | 操作 | 说明 |
| --- | --- | --- |
| 添加账号 | 在对应渠道点击「添加账号」，选择账号配置后扫码或填写凭据 | 同一渠道可添加多个账号；微信仅扫码，飞书 / Lark 支持扫码或填写 App ID / App Secret，Telegram 和 Discord 仅填 Bot Token，Slack 填 Bot Token 与 App Token |
| 修改账号配置 | 展开渠道并选择账号，在设置弹窗中修改工作区、模型、推理强度、权限、私聊准入或发送方式 | 配置只影响当前账号；保存后该账号的后续会话立即使用新配置 |
| 暂停接收 | 关闭账号行上的「接收消息」开关 | 凭据和账号配置保留，只暂停该账号接收新消息 |
| 在 IM 里下任务 | 微信 / 飞书 / Lark / QQ 扫码用户可直接私聊；钉钉 / 企微扫码者和其他用户需先批准。群聊只需 @ | 每个聊天对应一条独立频道会话 |
| 分段输入 | 结尾加 `..` 表示还有后续，`!!` 表示立即提交 | 默认约 5 秒合并窗口 |
| 新开会话 | 发送 `/new` 或 `/clear` | 新建并切换当前 IM 会话；旧会话保留在频道列表，不影响网页任务 |
| 查看状态 / 帮助 | 发送 `/status` 或 `/help` | 查看当前会话及动态 Chat 命令 |
| Agent 预设 | 在新增或编辑账号弹窗选择「Agent 预设」 | 使用 Chat 的预设名册；更改后新会话使用新预设，旧会话保持原预设 |
| 命令权限 | 点击账号右侧「设置」，调整私聊、群聊命令开关 | 先准入，再判断命令权限；关闭后仍可正常对话和回答审批、问题 |
| 会话 / 工作区 | `/sessions`、`/session <序号或ID>`；`/workspaces`、`/workspace <序号或ID>` | 可接续普通 Chat 会话；选择工作区会新建会话，不修改账号默认配置 |
| 任务控制 | `/stop`、`/steer <内容>`、`/queue` | 停止、补充指令、查看队列；停止保留 Host 排队消息 |
| 发送方式 | 点击账号右侧「设置」，在「发送方式」选择排队发送或插话发送 | 默认排队发送，行为不变；插话发送把普通消息作为补充指令立即交给运行中的任务（等同 `/steer`），任务空闲时与排队发送相同 |
| 分段回复 | 点击账号右侧「设置」，在「分段回复」选择「分段发送」并填写发送间隔区间 | 默认完整发送；选择分段发送后助手用分隔标记把一次回复拆成多条短消息，第一条也等随机间隔，规则见[分段回复](#分段回复) |
| 预设新会话 | `/presets`、`/preset <序号或ID>`、`/preset --default` | 使用指定预设在当前工作区新建并接续，不改变账号默认 |
| 模型 / 推理 | `/models`、`/model <序号或provider/model>`、`/reasoning [等级或 --default]` | 修改当前选择，并更新宿主后续 Chat 新会话的默认选择 |
| 结果补发 | `/delivery`、`/delivery retry <记录ID>` | 查看近期交付状态，按记录补发原任务文字结果 |
| 会话管理 | `/history`、`/rename <名称>`、`/fork` | 最近文字对话、改名、分叉并切换 |
| 批准陌生人私聊 | 打开「设置 → IM助理」，在待批准列表点「批准」或「拒绝」 | 只影响私聊准入，不影响群聊 |
| 回答交互问题 | 直接回复选项序号或文字；多选用逗号分隔，也可以输入自定义答案 | 多个问题会按顺序发送；群聊只接受任务发起者回答 |
| 批准工具 | 在私聊回复「批准」或「拒绝」 | 也接受 `yes` / `no` / `allow` / `reject`；群聊无效 |
| 在网页里回看 | 打开工作区「频道」页签 | IM 会话不会出现在「任务」里 |

通过 `/workspace` 新建后，`/new` 和当前会话归档后的续聊会保留所选工作区，并使用账号配置的模型、Agent 与权限预设。`/session` 接续已有会话、`/fork` 分叉则保留原会话配置。

### Telegram 话题命名

Telegram 话题默认使用第一条非兜底会话标题自动命名一次，之后不会随对话频繁变化。`/rename <名称>` 会显式同步话题名；普通聊天不受影响。机器人需要拥有该话题的改名权限。

- `/topicname` 查看当前话题的策略和锁定状态；`/topicname off` 锁定自动命名，`/topicname on` 解除锁定。默认策略下，解除锁定后允许下一个有效标题重新命名。
- 插件配置 `topicNaming` 支持 `once`（默认）、`follow`（跟随有效标题）和 `off`（关闭自动命名）；`/topicname on` 不会覆盖全局 `off`。显式 `/rename` 不受自动命名开关限制。
- 收到群组论坛的人工改名事件后自动锁定；私聊话题无法依赖该事件检测人工改名，请使用 `/topicname off`。开关与命名状态保存在状态目录的 `topic-titles.json`，重启后保留。

### 分段回复

在账号「设置」里将「分段回复」选为「分段发送」后，助手会把一次回复按分隔标记拆成多条短消息逐条发送，读起来更像连着说几句。默认为「完整发送」，关闭时行为与以前一致。目前只在 QQ 渠道开放（已按逐条投递验证），其他渠道不显示该选项，也不会切分。

- **发送间隔**：填写最小与最大间隔（秒，0–60）。每条消息（包括第一条）的发送间隔都从这个区间里随机取；上限低于下限时不会保存，改动保存后立即生效。
- **分隔标记**：由插件配置 `segmentMarker` 指定，默认 `<<<MSG>>>`，必须独占一行；围栏代码块里的同名标记不会触发分段，代码示例不会被拆散。
- **配置位置**：开关和间隔按账号保存在 `channels.json`；插件配置 `segmentReply` 可作为所有账号的默认值，账号设置优先。分段发送中途失败就停止，不整条重发，避免重复消息。

### 空闲新开会话

群聊和私聊都按「聊天」绑定会话，隔了很久再说话容易接着上一个话题。在账号设置里的「发送方式」下方配置「空闲新开会话」，距上一条用户消息超过该时间后，下一条消息会新开一个会话，并在聊天里提示一次；旧会话照常保留，用 `/sessions` 可以切回。缺省为关闭（保持原会话），行为与以前一致。

- 旧会话仍在运行时不切换，避免把长任务的补充指令切进新上下文；切换发生在下一条消息提交之前。
- 计时按聊天持久化在 `sessions.json` 的 `lastInboundAt`，重启后继续累计；`/new`、`/session`、`/workspace` 等手动命令不受影响。
- 支持在账号设置弹窗中直接选择：30 分钟、1 小时、2 小时、6 小时、12 小时、24 小时或关闭。

### Slack 接入步骤

Slack 走 Socket Mode 长连接，不需要公网地址，也不用配置请求网址：

1. 打开 <https://api.slack.com/apps>，点 **Create New App → From scratch**，选择工作区。
2. 左侧 **OAuth & Permissions → Bot Token Scopes**，至少勾选：`chat:write`（回复）、`reactions:write`（状态表情）、`files:read`（接收文件）、`files:write`（回传文件）、`im:history`（接收私聊）、`channels:history`（接收公开频道）、`groups:history`（接收私有频道）、`app_mentions:read`（接收 @）。不要填用户令牌（`xoxp-`）。多人私聊（mpim）这次不支持。
3. 回到页面顶部点 **Install to Workspace**，复制 **Bot User OAuth Token**（`xoxb-` 开头）。
4. 左侧 **Socket Mode** 打开开关，再点 **Generate Token and Scopes**，勾选 `connections:write`，复制 **App-Level Token**（`xapp-` 开头，只显示一次）。
5. 左侧 **Event Subscriptions** 打开订阅，在 **Subscribe to bot events** 添加：`message.im`、`message.channels`、`message.groups`、`app_mention`。
6. 在「设置 → IM助理 → Slack → 添加账号」里填入上面两个令牌。
7. 把机器人拉进要用的频道：在频道里 `/invite @你的机器人`。频道里只有 @ 机器人才会响应，私聊可以直接说话。

配置要点：

- **回复位置**：私聊直接回复；公开频道和私有频道里，机器人在你那条消息的线程内回复。最近参与的 512 个线程按账号保存在本机，重启 DSH 后仍可继续免 @ 追问。更换机器人身份、清理账号状态目录或线程记录被淘汰后，需要重新 @。多人私聊这次不响应。
- **格式**：Slack 不支持 Markdown 表格和 `#` 标题，正文会转成 Slack 的 mrkdwn：标题变粗体整行，表格竖排成「粗体标题 + 表头：值」，`**粗体**` 变 `*粗体*`，链接变 `<地址|文字>`。
- **命令**：Slack 客户端会把半角 `/` 开头的文本当斜杠命令。要用 `/menu` 这类命令，需要在应用里登记同名的 Slash Command。不想登记时，可以像 Discord 一样发送 `!menu`、`!help`，或用全角 `／help`；未知的 `!hello` 仍是普通消息。私聊里的命令仍走私聊白名单。频道里的 `!` 命令不必再 @，回复落在线程里。也可以直接用原生按钮。
- **诊断**：账号里的「诊断连接」会调用 `auth.test` 校验机器人身份；Socket Mode 的连接状态只在已连接时一并回报，未连接过则只报身份，不会为此新开连接。
- **权限范围变更后**要重新 Install App 并重新连接机器人，旧的 Bot Token 不会自动获得新权限。

切换会话后，旧会话不再向本聊天推送输出，`/preset`、`/fork` 切换也遵循此规则。网页历史仍保留，重启 DSH 后也不会恢复旧推送。`/unbind` 查看本聊天登记的会话；`/unbind 序号或ID` 解除旧登记，`/unbind all` 仅保留当前会话。`/mute 序号或ID` 保留登记但暂停助手输出，`/unmute` 使用同一目标恢复后续输出。序号以 `/unbind` 列表为准。

通过 `/session` 显式绑定会话会恢复输出，重新选择当前会话也会解除静音。静音暂停助手正文和文件输出，审批、提问和任务状态通知仍会送达。切换或静音会丢弃排队中的流式更新和缓存文本；已经发送的半截卡片可能仍可见，但后续输出会开启新流。

钉钉回复优先走官方 AI Card 流式卡片；创建失败则回退纯文本，保留换行；代码与列表标记按原文显示。QQ 回复走官方 markdown，保留标题、加粗、链接和表格；只有平台拒绝 markdown 时才退回纯文本（那时表格降级成列表）。Telegram 回复用 Rich Message 发送 Markdown，标题、加粗和表格可渲染；该方法不可用时自动回退纯文本，同一 Bot 不要同时开 Webhook。飞书普通回复也用卡片 Markdown，标题和表格会被渲染；按表格数量拆卡，单卡最多 5 个表格，卡片被拒时回退纯文本。长回复分片会保护结构：表格分片重复表头，代码块补齐围栏，序号只加在纯文字片。`/help` 按「入口 / 会话与工作区 / 模型与推理 / 任务与交付」分组，每个功能只列一条入口，并标出可直接使用的别名；分组标题会加粗，其余命令文案保持纯文本，便于在手机上扫读。Discord 客户端会自己渲染标题、加粗、代码和列表，但不支持 Markdown 表格，手机端又无法横向滚动，所以正文里的表格会逐行竖排成「粗体标题 + 表头：值」，其余正文原样发送；不要配置 Interactions Endpoint URL，否则按钮不会从 Gateway 进来。Slack 同样不渲染 Markdown 表格与 `#` 标题，正文会转成 mrkdwn：标题变整行粗体、表格竖排、`**粗体**` 变 `*粗体*`、链接变 `<地址|文字>`，代码块内的内容保持原样。

命令单独发送为文字，图片说明按普通消息处理。首次使用可发 `/help`，各命令回复也会给出相关操作。

- 会话与工作区：列表序号 15 分钟内有效；归档项在列表中标记，需要先在网页恢复。切换前完成运行任务和待处理交互。
- 模型：`/model`、`/reasoning` 与 Chat 一致，也会尝试保存后续新会话的默认选择；已有其他会话不主动修改。
- 停止与队列：`/stop` 先提交停止请求，保留队列；活跃目标另用 `/goal pause` 暂停。发 `/queue` 查看和管理排队消息。
- 分叉：`/fork` 需要至少一个完整回合。已创建但未切换时，按回复中的会话 ID 和恢复指引接续。
- 权限与导出：`/permission` 设置在恢复会话时保留；`/export` 遵循私聊准入和私聊/群聊命令权限，只有 ZIP 文件发送成功才回复完成。

命令回复、工具审批、交互问答，以及直接发给用户的渠道提示，跟随宿主已保存的全局语言。未保存语言或语言服务不可用时使用中文。动态名称、路径、用户内容与宿主扩展结果保持原文。设置页里的连接状态仍走界面词典。

## 权限与安全边界

命令权限不授予私聊准入，也不替代工具审批。开启命令的用户可查看、接续普通 Chat 会话并执行宿主注册命令。每账号独立设置私聊、群聊开关，不需要用户 ID；旧配置缺少该字段时默认开启以保持兼容。

| 项 | 当前行为 |
| --- | --- |
| 用户准入 | 群聊不用绑定，只需 @。每个账号可选择「仅已批准用户」或「允许所有私聊用户」；默认仅批准用户可用，微信 / 飞书 / Lark / QQ 扫码者会自动加入该账号白名单 |
| 管理接口 | `/api/dsh-im-connect`；通过宿主 `connection.requestRejection` 验证 Host、Origin 和 Cookie，本机也须登录；认证不可用返回 503，写接口保留 JSON、客户端请求头和 1 MiB 限制 |
| 敏感字段 | 包括微信 token 在内均优先写入 DSH `ctx.credentials`；没有该服务时落到 `%DSH_HOME%\dsh-im-connect\secrets.json`（明文，仅限当前用户，禁止同步或分享） |
| 账号状态 | `channels.json` 按账号保存工作区、模型、权限、私聊准入、发送方式、分段回复开关与发送间隔、启用状态和凭据引用，不保存明文 Secret |
| 浏览器回包 | 不返回 token、secret、App Secret 或内部异常详情 |
| 微信协议 | 只走腾讯官方 iLink，不使用逆向个人微信协议 |
| 工具批准 | 在绑定该会话的聊天里由任务发起者回复「批准 / 拒绝」；私聊准入只决定谁能对话，不再单独卡审批，也不会交回网页。5 分钟无回复按取消结束，不把期间的其他文字送进下一轮 |
| 交互问题 | 单选、多选和自定义问题回到发起任务的 IM 会话；同一会话按顺序处理，群聊只接受任务发起者回答 |

DSH 后端保持本机监听；远程访问使用受控 HTTPS 反向代理，并在宿主配置实际访问地址的 `trustedHosts`、通过该地址登录。不要伪装 localhost 或删除认证检查来绕过 403；图片下载的 `additionalImageHosts` 不用于管理接口。详见 [管理面认证](SECURITY.md#管理面)。权限预设与 Chat 使用相同的 Host sandbox-policy；`danger-full-access` 不套沙箱。

## 二次开发

### 从源码安装

适用于调试或使用未发布改动。克隆后的本地路径就是插件安装路径：

```powershell
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
# 替换为本机已存在的项目目录。
Set-Location "$HOME\Projects"
git clone https://github.com/MichengAI/dsh-im-connect.git
Set-Location .\dsh-im-connect
npm install
npm test
dsh plugin --profile web add .
dsh --profile web --dump-config
```

完成后重启 DSH Web 并硬刷新浏览器。`dsh plugin ... add .` 会读取当前目录的包信息和 `cordis.patch.yml`；不要改为直接复制 `lib` 目录。

本仓库用 `src` 开发，构建到 `lib`：

- [src\index.ts](src/index.ts)：Host 入口、配置和生命周期。
- [src\manager.ts](src/manager.ts)：渠道启停、已认证管理 API、凭据落盘。
- [src\engine](src/engine)：会话路由、斜杠命令、审批、分片和回推。
- [src\channels](src/channels)：钉钉、飞书、Lark、微信、企业微信、QQ、Telegram、Discord、Slack 适配器。
- `client.js`：设置页和工作区频道侧栏。
- `tests\*.test.mjs`：路由、扫码、凭据、QQ、投递和侧栏测试。

修改后运行测试并以本地目录安装验证：

```powershell
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
npm test
dsh plugin --profile web add .
```

修改渠道或会话逻辑时，必须保持：引擎不写死平台名、渠道不创建 agent、网页任务与 IM 频道分列。

### 原生侧栏协作协议

IM 与定时插件通过 `sidebar.workspaces` 当前显示项及条目／组件上的 `__dshNativeTabs` 注册表共享页签。这是插件间约定，不是 DSH 官方稳定 API。直接替换或恢复侧栏组件、交接注册表且宿主不会发送槽位通知时，变更方应在状态更新完成后通过微任务派发 `window` 事件 `dsh-native-sidebar-change`（普通 `Event`，无负载）。接收方重新读取当前显示项；仅插入页签不转发该事件，避免通知循环。

清理时只恢复自己仍持有的组件、移除自己拥有的注册表和页签，并解除事件、槽位及页签订阅。事件名、注册表结构或通知语义变更需要协作插件同步兼容。IM 每 250ms、最多 20 次的启动重试在成功插入页签后提前结束；它只兼容启动加载顺序，不能保证旧插件在重试结束后无声替换组件时重新接入。

## 验证

真实管理认证测试需要把 `DSH_CONNECTION_CONTRACT_ROOT` 指向隔离安装的 `@deepseek-ai/dsh-client-connection` 包根目录；与 Gateway 共存的用例同时使用图片契约的 `DSH_CHAT_CONTRACT_ROOT`。未配置时本地会跳过对应契约测试，CI 已配置执行。测试使用临时 HTTP 服务和临时凭据，不代表真实 Cloudflare 部署联调。

```powershell
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
npm test
```

`prepublishOnly` 会在发布前自动执行测试。

侧栏测试读取构建后的 `lib/client.js`；直接运行单项测试前先执行 `npm run build`。生命周期 Harness 验证模拟插槽、通知与计时器下的接入和卸载，不执行 React 渲染，也不代替真实消息、归档、定时任务或 Desktop 验收。


## 断线与重启后的结果补发

从本功能启用后收到的普通 IM 消息开始记录交付状态。原任务的结果已保存、但尚未开始发送时，可在恢复连接或重启后补发最终文字和结束状态；不会重新执行问题、工具或审批，也不会补发文件。旧版本处理过的消息不回溯补发。

- Telegram、Discord、Slack、飞书/Lark、企业微信可通过稳定聊天目标后台尝试补发；钉钉、QQ、微信等待原聊天发来新消息刷新回复通道。发送仍受渠道连接、时效和配额限制。
- 已尝试发送但无法确认送达时，暂停自动补发。发送 `/delivery` 查看记录；确认需要补发后发送 `/delivery retry <记录ID>`。这可能重复已收到的文字，文件请在网页原会话查看。
- 查询与补发仅限当前聊天的原发起人，并遵循准入和命令权限。账号停用、原会话换绑或访问权限变化会暂停补发；恢复原绑定与权限后可手动尝试。
- 每条记录的补发窗口为 7 天，最多保存 1000 条；满额时优先清理已送达或明确未提交的终态记录，过期记录在新消息进入时清理。后台每 30 秒分批检查，历史暂不可读或任务尚未结束时继续等待。补发不是任务自动续跑。

## 许可证

安全说明见 [SECURITY.md](SECURITY.md)。

本项目采用 [Apache License 2.0](LICENSE)。
