@xmanrui/dsh-im 4.35.1 → 4.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROACTIVE_DELIVERY.en.md +50 -0
- package/PROACTIVE_DELIVERY.md +22 -0
- package/README.en.md +3 -1
- package/README.md +3 -1
- package/lib/client.js +4 -3
- package/lib/index.js +309 -309
- package/package.json +3 -3
- package/plugin-src/host/channels/dingtalk/production.mjs +1 -1
- package/plugin-src/host/channels/feishu/production.mjs +4 -2
- package/plugin-src/host/channels/matrix/production.mjs +1 -1
- package/plugin-src/host/channels/qq/production.mjs +1 -1
- package/plugin-src/host/channels/shared/production.mjs +1 -1
- package/plugin-src/host/channels/slack/production.mjs +1 -1
- package/plugin-src/host/channels/wecom/production.mjs +1 -1
- package/plugin-src/host/channels/wecom-app/production.mjs +1 -1
- package/plugin-src/host/channels/weixin/production.mjs +1 -1
- package/plugin-src/host/channels/whatsapp/production.mjs +1 -1
- package/plugin-src/host/delivery-adapter.mjs +60 -12
- package/plugin-src/host/delivery-service.mjs +127 -1
- package/plugin-src/host/delivery-suggestions.mjs +2 -1
- package/plugin-src/host/harness-connection.mjs +3 -7
- package/plugin-src/host/index.mjs +5 -2
- package/plugin-src/host/modern-harness-api.mjs +139 -4
- package/src/channels/dingtalk/dingtalk-bridge.mjs +6 -0
- package/src/channels/dingtalk/dingtalk-controller.mjs +14 -0
- package/src/channels/dingtalk/dingtalk-runtime.mjs +10 -0
- package/src/channels/discord/discord-runtime.mjs +13 -0
- package/src/channels/feishu/bridge.mjs +30 -13
- package/src/channels/feishu/external-consumer.mjs +118 -0
- package/src/channels/feishu/feishu-runtime.mjs +72 -3
- package/src/channels/feishu/history-reader.mjs +92 -0
- package/src/channels/feishu/multi-bot-controller.mjs +130 -2
- package/src/channels/feishu/nearby-history.mjs +72 -0
- package/src/channels/feishu/plugin-config-store.mjs +1 -0
- package/src/channels/matrix/matrix-controller.mjs +9 -0
- package/src/channels/matrix/matrix-runtime.mjs +14 -0
- package/src/channels/qq/qq-bridge.mjs +6 -0
- package/src/channels/qq/qq-controller.mjs +14 -0
- package/src/channels/qq/qq-runtime.mjs +10 -0
- package/src/channels/shared/harness-approval.mjs +84 -7
- package/src/channels/shared/harness-client.mjs +36 -32
- package/src/channels/shared/i18n-en/shared-c.mjs +3 -0
- package/src/channels/shared/text-harness-bridge.mjs +6 -0
- package/src/channels/shared/token-bot-controller.mjs +16 -0
- package/src/channels/slack/manifest.mjs +1 -0
- package/src/channels/slack/slack-api.mjs +7 -0
- package/src/channels/slack/slack-controller.mjs +14 -0
- package/src/channels/slack/slack-runtime.mjs +12 -0
- package/src/channels/telegram/telegram-runtime.mjs +10 -0
- package/src/channels/wecom/wecom-bridge.mjs +6 -0
- package/src/channels/wecom/wecom-controller.mjs +14 -0
- package/src/channels/wecom/wecom-runtime.mjs +10 -0
- package/src/channels/wecom-app/wecom-app-bridge.mjs +6 -0
- package/src/channels/wecom-app/wecom-app-controller.mjs +14 -0
- package/src/channels/wecom-app/wecom-app-runtime.mjs +10 -0
- package/src/channels/weixin/weixin-bridge.mjs +6 -0
- package/src/channels/weixin/weixin-controller.mjs +14 -0
- package/src/channels/weixin/weixin-runtime.mjs +10 -0
- package/src/channels/whatsapp/whatsapp-controller.mjs +14 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +10 -0
package/PROACTIVE_DELIVERY.en.md
CHANGED
|
@@ -364,3 +364,53 @@ The same-Host `dshIm` Service exposes `contractVersion: 1`, `describeBot(botId)`
|
|
|
364
364
|
Options require `expectedFingerprint` and `expectedTargetDigest` and optionally accept `signal` and `format`. Derive the target digest from lowercase SHA-256 of UTF-8 `JSON.stringify({kind, route})`, with route keys sorted by ascending JavaScript string code-unit order. Names and aliases do not affect this digest. The service checks the currently saved target, freezes its normalized route, revalidates the authenticated account inside the account transition and sends that frozen route. Editing an alias during verification cannot redirect the request. Removing or changing a target before lookup rejects the request; after a request starts, changes cannot undo its external effect.
|
|
365
365
|
|
|
366
366
|
`account-unverified`, `account-changed`, `target-changed` and `capability-unavailable` are pre-send refusals; credential lookup and platform authentication failures also return `account-unverified`. `{sent:true}` still means platform acceptance, not delivery/read. SDK cancellation after start, timeout and other ambiguous outcomes are not proof that nothing was sent. The caller owns durable authorization, intent/attempt records and reconciliation and must not blindly retry. Provider Registration disposal or controller closure rejects checked sends whose SDK request has not started, including disposal during the final account verification; it cannot unsend an already started SDK request.
|
|
367
|
+
|
|
368
|
+
## Exclusive Feishu/Lark text intake (same Host)
|
|
369
|
+
|
|
370
|
+
Applications with their own durable message store can use `ctx.dshIm` (`inboundVersion: 1`) instead of the standalone DSH Session bridge. This is an opt-in Feishu/Lark capability; other channels and accounts that have never opted in retain their existing behavior.
|
|
371
|
+
|
|
372
|
+
```js
|
|
373
|
+
const account = await ctx.dshIm.describeBot(botId);
|
|
374
|
+
const dispose = await ctx.dshIm.consumeInbound(botId, {
|
|
375
|
+
expectedFingerprint: account.account.fingerprint,
|
|
376
|
+
signal: lifecycleSignal,
|
|
377
|
+
async onEvent(event, { signal }) {
|
|
378
|
+
// Authorize the conversation, deduplicate by provider event/message ID,
|
|
379
|
+
// and commit into the application's canonical store before acknowledging.
|
|
380
|
+
await application.acceptDurably(event, { signal });
|
|
381
|
+
return { accepted: true };
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
// Later, use the same verified account and the exact event.reply route:
|
|
385
|
+
await ctx.dshIm.replyChecked(botId, event.reply, 'Acknowledged', {
|
|
386
|
+
expectedFingerprint: account.account.fingerprint,
|
|
387
|
+
signal: lifecycleSignal,
|
|
388
|
+
});
|
|
389
|
+
// Release with the application's Registration/Fiber lifecycle:
|
|
390
|
+
dispose();
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Check the account's `exclusive-text-consumer` and `reply-text-checked` capabilities before enabling reception. Only one consumer may own an account; conflicts fail with `consumer-conflict`. The Host-only callback is not exposed through browser RPC or HTTP.
|
|
394
|
+
|
|
395
|
+
A version-1 event contains `channel`, `botId`, authenticated `fingerprint`, `eventId`, `messageId`, `actor: {kind: 'user', id}`, `conversation: {kind: 'group' | 'dm', id}`, `mentions: [{id, key}]`, `mentionedAccount`, ISO `at`, original `text`, and `reply: {messageId, conversationId, actorId, threadId?, rootId?, parentId?}`. These are platform-native IDs; optional server names are display-only; avatars and attachments are outside this slice. Bounded history is described below. Applications decide which conversations/messages to accept and when to wake an agent.
|
|
396
|
+
|
|
397
|
+
The mode is saved per account as `consumerMode: 'external-consumer'`. In that mode the native Session bridge, card callbacks and slash commands do not run. Releasing the consumer, losing its Registration, or restarting the Host does not restore standalone processing; intake fails closed until a consumer registers again. Returning to standalone requires an explicit configuration change and reconnect. A consumer must cooperate with cancellation while committing; releasing a consumer cannot undo an application commit already completed.
|
|
398
|
+
|
|
399
|
+
Checked replies re-read the original message and compare sender, conversation, thread, root and parent IDs before sending. A topic reply uses Lark's original-topic reply operation; it never falls back to the group mainline. Missing/changed sources fail with `stale-route`. An SDK failure after attempting the reply returns `reply-result-unknown`: inspect the external conversation before retrying. Provider redelivery has no resume cursor and may have gaps; this contract does not promise exactly-once transport, message-read receipts, or cross-account replies. Keep deduplication and durable delivery intents in the application.
|
|
400
|
+
|
|
401
|
+
## Bounded Feishu/Lark context reads (same Host)
|
|
402
|
+
|
|
403
|
+
An active exclusive consumer may call the optional `dshIm.historyChecked(botId, source.reply, query, {expectedFingerprint, signal})`. Check `history-text-checked`, and also `thread-history-text-checked` for a thread. Query is `{scope: 'group' | 'nearby' | 'thread', limit: 1..20, cursor?: string}`. There is no browser/HTTP history endpoint. The verified account and live consumer lease are required; standalone accounts cannot read through this contract.
|
|
404
|
+
|
|
405
|
+
Each call re-fetches the source and compares its author, chat, thread, root and parent IDs before listing. `group` lists the source chat; `nearby` uses a bounded five-minute-before/after chat time window, not a native around-message API; `thread` lists the native thread. The response is `{version:1, scope, events, omitted, hasMore, nextCursor?, window?, coverage:'provider-visible-human-text'}`. At most `limit` provider records are inspected; unsupported, deleted, application-sent or invalid text is counted as omitted. Pagination is explicit, one page per call. The caller binds its continuation to the same source/query. History-derived events use `history:<messageId>` as event ID; deduplicate with the native message ID alongside received events.
|
|
406
|
+
|
|
407
|
+
Only visible Human text is returned, with the same normalized event shape. Optional platform `sender_name` and mention `name` are display labels, never identity or authorization; missing names remain absent. No directory lookup or Human token fallback is performed. A read does not admit ordinary messages to an Inbox, wake an agent, reply, mark a message read, or synchronize withdrawal. The companion application owns those decisions and canonical persistence.
|
|
408
|
+
|
|
409
|
+
Missing Bot permissions return `history-permission-denied`; missing/changed source IDs return `stale-route`; an anchor without a thread returns `thread-unavailable`; provider failures or malformed pagination return `history-unavailable`. Unrelated chat/thread records fail with `untrusted-source`. Caller cancellation, consumer release, Host close and Provider replacement discard in-flight results. The consumer must retain its lifetime until the read completes. This capability is neither a complete transcript guarantee nor provider-wide search.
|
|
410
|
+
|
|
411
|
+
```js
|
|
412
|
+
const page = await ctx.dshIm.historyChecked(botId, event.reply,
|
|
413
|
+
{ scope: 'thread', limit: 10 },
|
|
414
|
+
{ expectedFingerprint: account.account.fingerprint, signal: lifecycleSignal });
|
|
415
|
+
// Persist/reconcile only after application authorization. Do not turn reads into intake.
|
|
416
|
+
```
|
package/PROACTIVE_DELIVERY.md
CHANGED
|
@@ -364,3 +364,25 @@ same-Host `dshIm` Service 新增 `contractVersion:1`、`describeBot(botId)` 和
|
|
|
364
364
|
options 必须包含 `expectedFingerprint`、`expectedTargetDigest`,可选 `signal`、`format`。目标 digest 为 `JSON.stringify({kind,route})` 的 UTF-8 小写 SHA-256,route keys 按 JavaScript 字符串 code-unit 升序排列;名称和 alias 不参与。发送检查当前 saved target,冻结规范化 route,再在账号 transition 内重新验证认证身份后使用此 route;验证期间编辑 alias 不会改投。lookup 前删除或改址会拒绝;请求已经开始后,修改不能撤销外部效果。
|
|
365
365
|
|
|
366
366
|
`account-unverified`、`account-changed`、`target-changed`、`capability-unavailable` 是发送前拒绝;凭据读取与平台认证失败也返回 `account-unverified`。`{sent:true}` 仍只代表平台接受,不代表送达/已读。SDK 开始后的取消、超时和含糊失败不能证明没有发送;调用者持有 durable authorization/intent/attempt 与 reconciliation,不得盲重试。Provider Registration 撤销或控制器关闭会拒绝尚未开始 SDK 请求的条件投递,包括在最后一次账号核验期间发生的撤销;不能撤回已开始的 SDK 请求。
|
|
367
|
+
|
|
368
|
+
## 飞书/Lark 独占文本收件(同 Host)
|
|
369
|
+
|
|
370
|
+
拥有独立持久消息存储的应用,可通过 `ctx.dshIm`(`inboundVersion: 1`)独占接收文本消息,并使用 `replyChecked` 沿校验过的原消息/话题回复。此能力仅适用于明确选择外部消费模式的飞书/Lark 账号;其他平台及未启用此模式的账号沿用现有行为。
|
|
371
|
+
|
|
372
|
+
调用 `describeBot(botId)` 校验身份和 `exclusive-text-consumer`、`reply-text-checked` 能力,然后调用 `consumeInbound(botId, {expectedFingerprint, signal, onEvent})`。`onEvent(event, {signal})` 应先进行群授权、去重和应用持久提交,再返回 `{accepted: true}`。返回的释放函数应随应用 Registration/Fiber 释放。回调仅用于同 Host Service,不暴露至浏览器 RPC 或 HTTP。完整示例见[英文契约](PROACTIVE_DELIVERY.en.md#exclusive-feishulark-text-intake-same-host)。
|
|
373
|
+
|
|
374
|
+
事件 v1 携带平台、账号和可信 fingerprint、eventId/messageId、发送人原始 ID、群/私聊 ID、mentions、mentionedAccount、ISO 时间、原始文本和原消息回复路由(messageId/conversationId/actorId/threadId/rootId/parentId)。平台提供的可选姓名仅用于展示;此切片不提供头像或附件,有界历史读取见下文。应用自行决定收件范围和唤醒策略。
|
|
375
|
+
|
|
376
|
+
每个账号只能有一个 Consumer;冲突返回 `consumer-conflict`。`consumerMode: external-consumer` 持久保存后,不创建独立 Session,不执行原生卡片或 slash command。释放 Consumer、Registration 替换、Host 重启均不自动回退独立处理;需重新注册 Consumer,或者明确修改账号模式并重连。应用持久提交需响应取消;已完成的提交不能被释放操作撤销。
|
|
377
|
+
|
|
378
|
+
`replyChecked` 在发送前读取原消息,核对发送人、群以及 thread/root/parent 路由;同话题回复不回退群主线。来源消失或变化返回 `stale-route`。发送请求已尝试后的 SDK 失败返回 `reply-result-unknown`,应先核对外部会话再决定是否重试。平台重投递可能有缺口、无恢复游标;本契约不保证传输恰好一次,不提供消息已读回执或跨账号回复。去重和持久发送意图归应用管理。
|
|
379
|
+
|
|
380
|
+
## 有界飞书/Lark 上下文读取(同一 Host)
|
|
381
|
+
|
|
382
|
+
活跃的独占接收方可调用可选的 `dshIm.historyChecked(botId, source.reply, query, {expectedFingerprint, signal})`。先检查 `history-text-checked`;读取话题另需 `thread-history-text-checked`。query 为 `{scope: 'group' | 'nearby' | 'thread', limit: 1..20, cursor?: string}`。不提供浏览器/HTTP 历史端点;必须使用已验证的 Bot 身份及当前接收权,standalone 账号无法通过此契约读取。
|
|
383
|
+
|
|
384
|
+
每次读取先重新获取来源,核对发送人、群、thread、root、parent ID。`group` 列出来源群;`nearby` 用消息前后各五分钟的群时间窗口,不声称是原生“附近消息”接口;`thread` 按原生话题读取。返回 `{version:1, scope, events, omitted, hasMore, nextCursor?, window?, coverage:'provider-visible-human-text'}`。每次最多检查 limit 条平台记录;不支持的消息、撤回、应用发言及无效文本计入 omitted。一次只读一页,后续页显式调用;调用方将 continuation 绑定原来源和查询。历史事件 ID 为 `history:<messageId>`,应用应以平台 messageId 和实时事件一起去重。
|
|
385
|
+
|
|
386
|
+
只返回该 Bot 可见的 Human 文本,沿用标准事件格式。平台返回的可选 `sender_name` 和 mention `name` 仅用于展示,不能用于身份或授权;缺失时不猜测,不查通讯录、不使用 Human token。读取不自动投递普通消息到 Inbox、不唤醒、不回复、不标记已读、不同步撤回;这些决定及 canonical 持久化由消费应用负责。
|
|
387
|
+
|
|
388
|
+
Bot 权限不足返回 `history-permission-denied`;来源消失或改变返回 `stale-route`;来源没有话题返回 `thread-unavailable`;平台故障及无效分页返回 `history-unavailable`;混入其他群/话题返回 `untrusted-source`。调用取消、接收权释放、Host 关闭、Provider 替换都会丢弃未返回结果。消费方需保留接收生命周期直到读取结束。本能力不保证完整历史,也不提供全平台搜索。
|
package/README.en.md
CHANGED
|
@@ -227,6 +227,8 @@ Shells can embed the complete IM management panel through the optional `dshImCli
|
|
|
227
227
|
| Interactive question | Reply with an option number, option label, or custom text; separate multiple choices with commas. In Feishu, an image or image post from the person answering ends the pending question turn and is processed in the same Session, without finishing the remaining questions or resending the image. |
|
|
228
228
|
| Remote approval | Reply with `批准` / `拒绝` / `同意` / `不同意` / `yes` / `no`. |
|
|
229
229
|
|
|
230
|
+
Web and bot approvals share one decision: the first allow-once or reject wins and retires the other prompt. Supported channels are WeChat, Feishu, DingTalk, WeCom bot/app, QQ, Telegram, Slack, Discord, WhatsApp, and Matrix; iMessage, email, and AI Office are excluded. Bot-origin approvals use the original chat. Web/Host-origin approvals require exactly one bound private chat with bidirectional sync enabled; aliases of that chat count once, while multiple chats receive a Web-only notice. Binding and access checks apply again when submitting, and permanent approval is not offered. Slack delivery from Web additionally requires `im:read`; update the app Manifest and reinstall the app if needed. Web remains usable when the bot cannot present an approval.
|
|
231
|
+
|
|
230
232
|
### Command details
|
|
231
233
|
|
|
232
234
|
[Read the command details](docs/bot-commands.md)
|
|
@@ -260,7 +262,7 @@ Startup configuration validation failures also include `file`, `field`, and `iss
|
|
|
260
262
|
|
|
261
263
|
## Local development
|
|
262
264
|
|
|
263
|
-
The latest dsh-im follows the latest DSH, with DSH `0.
|
|
265
|
+
The latest dsh-im follows the latest DSH, with DSH `0.2.0-rc.2` (Session format V4) as this update's supported baseline. New features and fixes do not add compatibility branches for older DSH versions; existing unrelated compatibility code remains in place. Older hosts should use the corresponding historical plugin release. `package.json` declares only the host versions actually verified for this build, without promising support for untested future releases. After upgrading the plugin, restart the Host and refresh the settings page so both sides use the same plugin build.
|
|
264
266
|
|
|
265
267
|
Source details, guidance, and quoted replies now use the V4 `plugin:dsh-im` source kind, fixing `SessionFormatError: format v4 message requires a producer-owned source kind`. Message ordering, user text, and session-level guidance deduplication retain their existing behavior. The host owns historical Session migration.
|
|
266
268
|
|
package/README.md
CHANGED
|
@@ -229,6 +229,8 @@ Logo 由 dsh-im 的浏览器适配显示,无需修改 DSH。适配保留原始
|
|
|
229
229
|
| 交互式提问 | 回复选项序号、选项文字或自定义文字;多选时用逗号分隔。飞书中,提问发起人发送图片或图文消息会结束本轮追问,并在同一会话继续处理图片,无需答完剩余问题或重新发图。 |
|
|
230
230
|
| 远程审批 | 回复 `批准` / `拒绝` / `同意` / `不同意` / `yes` / `no`。 |
|
|
231
231
|
|
|
232
|
+
Web 与机器人可同时处理同一审批,先提交的「批准一次」或「拒绝」生效,另一端随即失效。支持微信、飞书、钉钉、企业微信机器人/应用、QQ、Telegram、Slack、Discord、WhatsApp、Matrix;iMessage、邮箱和 AI Office 暂不参与。机器人发起时使用原聊天;Web 或其他 Host 任务发起时,需要该 Session 恰好绑定一个已开启双向同步的私聊。同一私聊的目标别名按一个入口计算,多个私聊只发 Web 处理提醒。审批复用当前绑定与权限,绑定变更后不能继续提交;不提供永久批准。Slack 从 Web 投递审批还需 `im:read` 权限,可更新应用 Manifest 后重新安装应用;缺少权限时保留 Web 处理入口。
|
|
233
|
+
|
|
232
234
|
### 命令说明
|
|
233
235
|
|
|
234
236
|
钉钉菜单使用插件内置的共享卡片模板,无需逐个机器人创建或配置模板。卡片打开后可操作 30 分钟;超时或 Host 重启后重新发送 `/m`。模板源文件保存在 `assets/dingtalk-menu-template.json`,供维护者导入卡片平台更新。
|
|
@@ -263,7 +265,7 @@ Logo 由 dsh-im 的浏览器适配显示,无需修改 DSH。适配保留原始
|
|
|
263
265
|
|
|
264
266
|
## 本地开发
|
|
265
267
|
|
|
266
|
-
最新版 dsh-im 跟随最新版 DSH,本次支持基线为 DSH `0.
|
|
268
|
+
最新版 dsh-im 跟随最新版 DSH,本次支持基线为 DSH `0.2.0-rc.2`(Session 格式 v4)。后续功能和修复不再增加旧版 DSH 的兼容分支,已有其他兼容逻辑暂时保留;旧宿主请使用对应的历史插件版本。`package.json` 只声明当前实际验证的宿主版本,不承诺未经验证的未来版本。升级插件后重启 Host 并刷新设置页,使 Host 和客户端使用同一版插件。
|
|
267
269
|
|
|
268
270
|
上下文增强中的来源信息、引导词和引用回复使用 v4 的 `plugin:dsh-im` 来源字段,修复了它们触发的 `SessionFormatError: format v4 message requires a producer-owned source kind`。消息顺序、用户正文和会话级引导词去重沿用原有机制,历史会话由宿主负责迁移。
|
|
269
271
|
|
package/lib/client.js
CHANGED
|
@@ -595,7 +595,7 @@ var React39 = __toESM(require("react"), 1);
|
|
|
595
595
|
// package.json
|
|
596
596
|
var package_default = {
|
|
597
597
|
name: "@xmanrui/dsh-im",
|
|
598
|
-
version: "4.
|
|
598
|
+
version: "4.36.0",
|
|
599
599
|
icon: "./assets/logo-plugin-message-link-gradient.webp",
|
|
600
600
|
description: "\u628A\u5FAE\u4FE1\u3001\u98DE\u4E66\u3001\u4F01\u5FAE\u3001\u9489\u9489\u3001QQ\u3001Telegram\u3001WhatsApp\u3001Slack\u3001Discord\u3001Matrix\u63A5\u5165DSH\u3002 Connect Telegram, WhatsApp, Slack, Discord, Matrix, WeChat, Feishu, WeCom, DingTalk, and QQ to DSH.",
|
|
601
601
|
keywords: [
|
|
@@ -682,9 +682,9 @@ var package_default = {
|
|
|
682
682
|
platform: "web"
|
|
683
683
|
},
|
|
684
684
|
compatibility: {
|
|
685
|
-
dsh: "0.
|
|
685
|
+
dsh: "0.2.0-rc.2",
|
|
686
686
|
dshReleases: {
|
|
687
|
-
"0.
|
|
687
|
+
"0.2.0-rc.2": "compatible"
|
|
688
688
|
},
|
|
689
689
|
profiles: [
|
|
690
690
|
"web"
|
|
@@ -10944,6 +10944,7 @@ oauth_config:
|
|
|
10944
10944
|
- files:read
|
|
10945
10945
|
- files:write
|
|
10946
10946
|
- groups:history
|
|
10947
|
+
- im:read
|
|
10947
10948
|
- im:history
|
|
10948
10949
|
- mpim:history
|
|
10949
10950
|
- reactions:write
|