kovey/dsh-chat-interaction ↗★ 0
dsh-chat-interaction
Abstract interaction layer between DeepSeek Harness (DSH) and IM platforms (Feishu, WeCom, ...). Channel-agnostic hub with proven interaction patterns (dedupe, instant ack, wait-reply, followup wakeup, scoring-based model routing, approval gate) plus pluggable adapters for Feishu and WeCom.
安装
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:kovey/dsh-chat-interaction说明文档
阅读完整 README ↗使用
1. 安装(挂进 DSH)
前置:Node ≥ 18、pnpm、DSH 宿主 v0.1.5-rc.1;飞书/企业微信应用凭证(按需)。
依赖对齐:本包 peerDependencies 是 @deepseek-ai/{dsh-agent,dsh-llm,dsh-tools}@^0.1.5-rc.1
cordis@^4.0.2—— 与 dsh 0.1.5-rc.1 官方宿主提供的版本一致(dsh --version可确认), pnpm 会直接复用、不会装第二份。
### 2. 配置
**a) 插件配置**(profile 覆盖,注意是 **id 覆盖**而非 insert —— bundle 层已经 insert 过,
重复 insert 同 id 会抛 `duplicate loader entry id`):
```yaml
### 3. 首次使用
1. 重启 TUI 会话(插件加载,但**默认不建立任何连接**)。
2. 在会话里对 agent 说 **「连接飞书」**(agent 调 `feishu_listener` 的 `action=start`);
企业微信同理(启动回调服务)。也可让 agent 用 `feishu_auth_state` 查看当前状态。
3. 在飞书里给机器人发一条消息:
- 会先收到**即时回执**("收到,正在处理~");
- 然后 agent 用 `feishu_send_message` 回复结果;
- 需要确认时 agent 会发 `feishu_send_card` 卡片,你点按钮后它会收到
`[飞书卡片点击]` 继续处理。
4. 企业微信还需在**管理后台 → 应用 → 接收消息**里把回调 URL 填成
`http://:8787/wecom`(URL 校验、签名、AES 解密都由适配器完成);
也可以用你自己的中间件解密后调 `WeComChannel.feed(xml)`。
### 4. 日常使用
**工具**(每个渠道自动生成一套,前缀 = 渠道名):
| 工具 | 用途 |
|---|---|
| `feishu_send_message` / `wecom_send_message` | 回文本;带 `title` 时发富文本 |
| `feishu_send_card` / `wecom_send_card` | 交互卡片(按钮值 A/B/yes/…),点击以 `[渠道卡片点击]` 回来 |
| `feishu_wait_reply` / `wecom_wait_reply` | 阻塞等该 chat 的下一条消息(**被本次调用消费**,不产生新回合) |
| `feishu_listener` / `wecom_listener` | `start` / `stop` / `status`,连接控制 |
| `feishu_auth_state` / `wecom_auth_state` | 权限模式、allowlist、active chat、待确认问题 |
**你可以直接对它说的话**:「连接飞书」「断开飞书」「git status」(插件直接执行并回结果)、
「最近提交」「新增一个活动功能」(转交 agent 开发)、「今天天气不错」(闲聊直答)、
卡片上的 A/B/C/D 点击(确认类)。
**权限与审批**(`manual` 模式):
```sh
echo manual > /.dsh/feishu-permission-mode.txt # 或让 agent 发权限模式卡切换
之后来自渠道的任务执行 bash 时,会先给飞书/企微推「同意 / 拒绝 / 始终同意」卡片;
超时按 fail-closed 拒绝;「始终同意」把该命令追加到 /.dsh/-permission-allowlist.txt。
任务连续性 + 收尾卡:需求/修复类任务开始时,agent 会写
/.dsh/-task-active/.json(8h TTL)——这期间该 chat 的消息一律转交
agent(不被命令/闲聊路由截走);任务收尾时按提示词策略发「收尾提交方式」四按钮卡
(A 提交+推送+部署 / B 提交并推送 / C 仅本地提交 / D 暂不提交)。
打分与模型路由:每条转交 agent 的消息会先打分,回合里能看到
消息评分: 0.90 (high) → 执行模型: deepseek-reasoner;覆盖只作用于该回合,
回合结束自动恢复会话默认模型。关闭:scoring: { enabled: false }。
6. 独立使用核心(不依赖 cordis / 不触 agent runtime)
import { InteractionHub, createFeishuChannel, createWeComChannel } from 'dsh-chat-interaction'
const hub = new InteractionHub({
spoolFile: '~/.dsh/chat-spool.jsonl',
onFollowup: (msg, ctx) => myAgent.followup(msg), // 你自己接 agent
})
hub.addChannel(createFeishuChannel({ appId: 'cli_...', appSecret: '...' }))
hub.addChannel(createWeComChannel({ corpId: 'ww...', corpSecret: '...' }))
await hub.connect('feishu') // 开始收飞书消息
await hub.sendText('feishu', chatId, '你好')
const answer = await hub.waitReply('feishu', chatId, 120_000)
完整配置(apply / cordis.patch.yml 的 config)
{
enabled: true, // 总开关
logFile: '~/.dsh/chat-interaction.log',
spoolFile: '~/.dsh/chat-interaction-spool.jsonl', // 每条入站消息 JSONL tee
pendingDir: '~/.dsh/chat-pending', // 插件待确认问题存储(按渠道分目录)
retry: { // 模型失败重投守卫; false 关闭
maxAttempts: 10, baseDelayMs: 30_000, capDelayMs: 600_000, pollMs: 5_000,
},
scoring: { /* 见「消息打分与模型路由」一节 */ },
lease: { // 渠道租约(24×7 服务 ⇄ TUI 心跳接管)
enabled: true,
role: 'interactive', // interactive | service (默认按 channels.*.role 推断)
ttlMs: 90_000, // 心跳过期阈值
heartbeatMs: 30_000, // 续约间隔(检查间隔上限 2s)
dir: '~/.dsh', // 租约文件目录
},
router: { // 插件自治(命令/确认/闲聊/任务连续性)
enabled: true,
evaluator: 'auto', // auto | rule | model
autoPermissionCard: true, // 任务类消息自动发权限模式卡
autoDisambiguation: true, // 无法分类且无模型时自动发消歧卡
model: 'deepseek-v4-flash', // 分类与闲聊用的模型
autoReply: true, // 闲聊是否插件内直答
chatHistoryTurns: 10,
chatDir: '~/.dsh/chat-history',
commandTimeoutMs: 30_000,
commandMaxOutputChars: 2000,
taskActiveTtlMs: 28_800_000, // 任务标记 8h
},
permission: {
mode: 'auto', // 权限模式文件缺省值 (auto/manual)
activeWindowMs: 600_000, // followup 后 bash 视为渠道来源的窗口
answerTimeoutMs: 300_000, // 审批卡超时 → fail closed
bridgeHarnessApproval: false, // L3: 把所有 harness 审批转渠道卡片 (headless)
},
channels: {
feishu: {
enabled: true,
role: 'off', // 'listener' = 启动即连 (部署决策)
appId: '', appSecret: '', // 留空走 feishu-app.json 凭证链 / FEISHU_* 环境变量
credsFile: '', // 可选: 显式凭证文件路径
mediaRetentionDays: 7,
},
wecom: {
enabled: true,
role: 'off',
corpId: '', corpSecret: '', // 留空走 wecom-app.json 链 / WECOM_* 环境变量
agentId: '', // 可选
token: '', aesKey: '', // 只用内置回调服务器时需要; 否则可留空 (feed() 模式)
credsFile: '', // 可选: 显式凭证文件路径
callback: { host: '0.0.0.0', port: 8787, path: '/wecom' },
mediaRetentionDays: 7,
},
// 智能机器人(WS 长连接,与上面的自建应用二选一或并存)
wecom_bot: {
enabled: true,
role: 'off',
botId: '', secret: '', // 留空走 wecom-bot.json 链 / WECOM_BOT_* 环境变量
wsUrl: '', // 私有部署的长连接地址(默认官方 wss://openws.work.weixin.qq.com)
credsFile: '',
mediaRetentionDays: 7,
},
},
channelFactories: { /* 自定义渠道: { ding: (cfg) => new DingChannel(cfg) } */ },
}