diff --git a/changes/1615-discord-managed-role.md b/changes/1615-discord-managed-role.md new file mode 100644 index 0000000000..a72a4113f4 --- /dev/null +++ b/changes/1615-discord-managed-role.md @@ -0,0 +1,11 @@ +--- +section: Fixed +--- + +## en + +- Discord's checked receiver recognizes mentions of its own native managed role as addressing its bound PersonaBot, while unrelated roles and everyone/here remain non-addressing; role ownership is checked against the current guild and Bot member ([#1615](https://github.com/BotHarness/DeepSeekBot/issues/1615)). + +## zh + +- Discord 受检接收器将自身原生托管角色的提及识别为对已绑定 PersonaBot 的请求;无关角色和 everyone/here 不触发直接提及,角色归属按当前服务器与 Bot 成员核验([#1615](https://github.com/BotHarness/DeepSeekBot/issues/1615))。 diff --git a/docs/adr/0128-discord-checked-replies-preserve-native-child-channel-routing.md b/docs/adr/0128-discord-checked-replies-preserve-native-child-channel-routing.md index bea5b6a433..5226fd2b29 100644 --- a/docs/adr/0128-discord-checked-replies-preserve-native-child-channel-routing.md +++ b/docs/adr/0128-discord-checked-replies-preserve-native-child-channel-routing.md @@ -14,7 +14,7 @@ Keep the DSH-native Plugin/Fiber and checked Service Definition → Provider → The Provider authenticates the current Bot user and associated Application using native API calls and checks the Gateway READY App/user pair. Fingerprints include App and Bot user IDs, never tokens or mutable names. Snowflakes remain strings. A Profile can explicitly set the Discord Provider's `consumerMode: external-consumer` before connecting accounts. This prevents a privileged-content/standalone Session bootstrap before the Human binds the identity. Standalone configurations retain their original mode. Claiming a checked Consumer persists external mode; missing/disposed leases and restart do not restore standalone processing. -The mention path requests `GUILDS` and `GUILD_MESSAGES`, checks the native `mentions` array and accepts bounded Human text from guild text channels or existing public threads. It does not infer direct addressing from role mentions, display names or `@everyone`. Own Bots and webhooks are suppressed. Gateway sequence plus session identifies delivery evidence; it is not a durable replay cursor or per-message acknowledgement protocol. Canonical message identity and per-Bot Admission deduplicate redelivery in Messaging. +The mention path requests `GUILDS` and `GUILD_MESSAGES`, checks native addressing evidence and accepts bounded Human text from guild text channels or existing public threads. Native `mentions` identifies user mentions. The #1615 extension below separately qualifies the authenticated Bot's own managed role; unrelated role mentions, display names, `@everyone` and `@here` do not address the Bot. Own Bots and webhooks are suppressed. Gateway sequence plus session identifies delivery evidence; it is not a durable replay cursor or per-message acknowledgement protocol. Canonical message identity and per-Bot Admission deduplicate redelivery in Messaging. For a text channel, external conversation and native destination are that channel. For an existing public thread, external conversation is its verified parent text channel, while `threadId` is the native child channel ID. Check parent/child guild correspondence. Discord `parent_id` is a parent channel, not a parent message. No root or parent message ID is fabricated from it or from a reply reference. Thread following remains unavailable in this slice because that independent contract has not been qualified. @@ -22,6 +22,14 @@ Before reply, read current guild roles, the Bot member and parent-channel overwr Core platform registration, Inbox/source DTO validation and the existing Client source label admit Discord through these checked contracts. Global defaults editing, ordinary intake, context windows, files, autonomous thread following, shared Channel qualification and proactive canonical receipt are separate real tracers. Their capability ledger remains unqualified. +## Own managed-role addressing — #1615 + +Discord offers both the App's Bot user and its integration-managed role under the same visible name. Treat only the latter's verified native ownership as another way to address the same Bot: the mentioned role's string ID must occur in the original `mention_roles`, the current native guild role must have `managed: true` and `tags.bot_id` equal to the authenticated Bot user ID, and that Bot's current guild member must hold the role. The everyone role is excluded. Reuse the existing checked channel/guild/member query and receiver lifetime; do not add a role cache, message store, listener or Core role-name heuristic. + +The Provider uses the same predicate for early ingress and final normalization. The original text, user mentions and source-bound reply route stay intact; a role is not fabricated as a user mention. Other roles and everyone/here remain non-addressing even when the Bot holds those roles. They may be retained only through separately authorized ordinary collection, without direct-mention attention. Missing or changed native ownership cannot widen intake. A disposed or replaced receiver cannot deliver a result from an earlier lookup. + +This is checked addressing, not authorization to join a guild, acquire Message Content intent or access unavailable message content. Role ownership and application permission do not prove actual Gateway delivery. The controlled Runtime/Consumer regressions and fresh installed-product native/model qualification passed for the #1615 candidate on 2026-10-16 JST: own managed role and Bot-user mentions each received one source-bound reply; an unrelated role held by the Bot, everyone and here created no attention or send. The test App already had Message Content approval. See the [qualification record](../dev/verification/discord-1615-managed-role.md) for exact artifacts, cleanup and review limits. Earlier #855 evidence does not qualify this extension; public release remains separate. + ## Evidence boundary Initial Provider candidate source: `e6f0de2a989c28d20db92c0e7f43b20c6d3028b9`, based on the then product-pinned Provider `48e7a35792af5222cd40cfe1ba2607ac55a59df2`. Those controlled Provider and assembled-Core tests alone did not qualify real Discord behavior. Subsequent agent-operated native/model E2E, original-location receipts, runtime provenance and screenshots completed the narrow **development-source mention/reply qualification** in [#855](https://github.com/BotHarness/BotHarness/issues/855) / [#934](https://github.com/BotHarness/BotHarness/pull/934), under the Human's revised acceptance instruction; no manual Human QA gate remains for that slice. Additional capabilities, installed-product qualification and Provider promotion retain separate evidence and authorization. This ADR's formal status remains Proposed; merge, deployment and publication require their own authorization. diff --git a/docs/architecture/botharness-architecture.md b/docs/architecture/botharness-architecture.md index b09f963048..e1ac6740c7 100644 --- a/docs/architecture/botharness-architecture.md +++ b/docs/architecture/botharness-architecture.md @@ -1057,6 +1057,8 @@ The #1155 ordinary-text extension uses the same exclusive checked QQ inbound con [#855](https://github.com/BotHarness/BotHarness/issues/855) adds a **development-source qualified mention/reply** Discord checked adapter through the same Provider/Consumer and canonical Inbox seams. App/Bot identity is authenticated and checked against Gateway READY. An explicitly external-only Profile avoids standalone bootstrap; consumer loss preserves external ownership. A public thread maps to its verified parent conversation plus exact child-channel `threadId`, without manufactured message ancestry. Replies check current native permissions/source, run the authority fence and validate native author/destination/receipt without fallback or retry. See [ADR-0128](../adr/0128-discord-checked-replies-preserve-native-child-channel-routing.md). The real channel/thread model, native identity/guild boundary and refusal/recovery checks passed under agent-operated E2E acceptance; [verification](../dev/verification/discord-855-mention-reply.md) bounds the qualification to that development runtime. Product Provider promotion and other Discord capabilities remain separate. +[#1615](https://github.com/BotHarness/DeepSeekBot/issues/1615) 将同一受检 Discord 边界扩展到认证 Bot 自身的托管身份组提及。Provider 在归一化前读取当前原生服务器与成员,核对角色归属及准确的提及 ID;其他角色与 everyone/here 不构成直接寻址,不新增接收器、持久化或 Core 身份权威。2026-10-16 JST 的实际安装包已通过自身角色/用户提及、Bot 持有的无关角色及 everyone/here 的原生浏览器与模型验收;该 App 已有 Message Content 权限,公共发布仍是独立步骤。见 [ADR-0128](../adr/0128-discord-checked-replies-preserve-native-child-channel-routing.md) 和[验证记录](../dev/verification/discord-1615-managed-role.md)。 + [#855](https://github.com/BotHarness/BotHarness/issues/855) 通过相同 Provider/Consumer 和 canonical Inbox 边界增加**开发来源提及/回复已取得资格**的 Discord adapter。App/Bot 身份由认证 API 与 Gateway READY 校验,显式 external-only Profile 避免先进入独立 Session,consumer 消失仍保留外部所有权。公开 thread 使用已验证的父会话与准确子频道 `threadId`,不虚构消息祖先。回复复查原生权限/原消息,执行授权门禁并验证原生作者/目的地/回执,不转发、不重试。见 [ADR-0128](../adr/0128-discord-checked-replies-preserve-native-child-channel-routing.md)。真实频道/thread 模型、原生身份/服务器边界与拒绝/恢复检查已按 agent-operated E2E 指令通过;[验证记录](../dev/verification/discord-855-mention-reply.md) 将资格限定于该开发运行时。产品 Provider 晋升与其他 Discord 能力另行处理。 [#937](https://github.com/BotHarness/BotHarness/issues/937) extends that candidate Provider through the existing `bridge_context` / `historyChecked` seam: bounded group and existing-public-thread Human-text pages, native App content visibility plus view/history checks before and after the HTTP page, and runtime-local signed cursors bound to account, original source route, scope and limit. Canonical context retention creates no Admission or placement; a conflicting retained source rolls back the page and reports `source-conflict`. Native edits never replace the earlier source evidence. [The exact candidate checkpoint](../dev/verification/discord-937-context-reads.md) separates real single-page model success, direct native cursor checks and restored-permission/restart refusal from still-pending model continuation/conflict acceptance. Nearby, ordinary subscription, files and product dependency promotion remain outside this slice. diff --git a/docs/client-bridge.md b/docs/client-bridge.md index ad10477d5b..543e06f1ef 100644 --- a/docs/client-bridge.md +++ b/docs/client-bridge.md @@ -144,7 +144,7 @@ resolver cannot substitute workspace-linked Core/Client for the tarballs. node scripts/dev-instance.mjs --home /tmp/bh-im-qa --port 31968 --im-provider --json ``` -仅对已停止的隔离 Profile 执行;重启同一 Profile 时仍传入 `--im-provider`,以重复校验。当前 Provider 来源记录为 Git 完整提交 `7505ce6fcda26c02810781fde9d4da81dab28a68`,开发资格记录为 DSH `0.2.1-rc.2`,这些来源记录不限制实际启动的 DSH 版本;校验 Bundle、入口及运行时代码 digest 后才启动 Host;不依赖另一份 provider 本地源码。安装摘要明确标注 `upstreamReleased: false`。此完整 fork 保留附件、回显与独立回复身份契约;群/话题上下文读取绑定当前独占 Consumer 生命周期,释放或替换后拒绝返回正在读取的结果;上游 PR 是否合并不阻挡此固定提交使用([#789](https://github.com/BotHarness/BotHarness/issues/789))。默认启动不安装它,原 npm `4.32.0` 仍不满足账号校验和条件发送契约。此入口不会发布包,也不是生产启用许可([ADR-0104](adr/0104-isolated-im-profiles-pin-a-qualified-temporary-provider-fork.md))。 +仅对已停止的隔离 Profile 执行;重启同一 Profile 时仍传入 `--im-provider`,以重复校验。当前 Provider 来源记录为 Git 完整提交 `518314f6cf495aea093c0a93b0107de25b66e60d`,开发资格记录为 DSH `0.2.1-rc.2`,这些来源记录不限制实际启动的 DSH 版本;校验 Bundle、入口及运行时代码 digest 后才启动 Host;不依赖另一份 provider 本地源码。安装摘要明确标注 `upstreamReleased: false`。此完整 fork 保留附件、回显与独立回复身份契约;群/话题上下文读取绑定当前独占 Consumer 生命周期,释放或替换后拒绝返回正在读取的结果;上游 PR 是否合并不阻挡此固定提交使用([#789](https://github.com/BotHarness/BotHarness/issues/789))。默认启动不安装它,原 npm `4.32.0` 仍不满足账号校验和条件发送契约。此入口不会发布包,也不是生产启用许可([ADR-0104](adr/0104-isolated-im-profiles-pin-a-qualified-temporary-provider-fork.md))。 在 dsh-im 原设置中配置测试应用,并保存、测试仅含测试者与 Bot 的目标群;凭据交由 DSH credentials service,不复制到 BotHarness。打开 PersonaBot Profile → IM connection,选择已认证账号与已测试目标,显式授权后发送一条唯一测试文本。预期 Recent sends 显示 Platform accepted,并在目标群核对同一文本;这不代表送达或已读。停止本次启动摘要中的确切 PID,再以相同参数启动:绑定与发送历史应保留,不自动重发。 diff --git a/docs/dev/verification/discord-1615-managed-role.md b/docs/dev/verification/discord-1615-managed-role.md new file mode 100644 index 0000000000..e088674854 --- /dev/null +++ b/docs/dev/verification/discord-1615-managed-role.md @@ -0,0 +1,61 @@ +# Discord managed-role mention qualification (#1615) + +## Candidate and scope + +This candidate recognizes only a native mention of the current authenticated Bot user or its own verified integration-managed role. It retains the existing checked Provider → canonical Source/Inbox → Orchestrator → source-bound Outbox/reply path. Role recognition is separate from Message Content permission and actual Gateway delivery. + +Maintained Provider source: `1fc80010ab7cfd10ecc4f63c1d46c098e829c6a6` on DSH `0.2.0-rc.2`. Input runtime: 421 files, SHA-256 `8e9416fc17b44d5af993654794e8a795b1a01784bcda68115e1479b1f6695358`. Product candidate: independently versioned `4.32.0-botharness.22`; no publication or deployment is implied. + +> **2026-11-02 sync:** the integration now pins Provider `518314f6cf495aea093c0a93b0107de25b66e60d` (`4.32.0-botharness.25`, 424 runtime files, SHA-256 `bc54309720ce66906b562d7afaae99669c2177d67717623f6999e906abe473d1`) on top of current main, including explicit mentions from other Bots. The native record below belongs to the earlier candidate; the requalification section covers this one. + +## Automated proof + +The original Runtime/Consumer regression failed with expected one admission, actual zero, before the fix. It now passes with unchanged ordinary collection opt-out. The complete affected Discord suite passes 96/96, including native-channel and existing-thread routes, unrelated roles, everyone/here, role ownership changes, exact IDs, forged message metadata, lifetime disposal, checked replies and existing ordinary intake. Provider build and package verification pass. + +The Core regression additionally exercises a checked event with `mentionedAccount: true`, original role text and an empty user-mention list through canonical Inbox/Orchestrator/Outbox. It must not depend on text search or fabricate a user mention. Controlled tests are not live-model or native Discord proof. + +## Installed-product and native qualification — 2026-10-16 JST + +The Human confirmed machine-registered Discord QA1 was idle, then requested agent-operated testing in the built-in browser. The candidate used one isolated, task-owned packaged Host, with `consumerMode: external-consumer` set before credential binding. No prior receiver was stopped. A 30-minute cutoff guard was armed before connection. Official DeepSeek Flash was configured only for the isolated PersonaBot's Orchestrator and default Assignment; a real Web DM returned `42`, and canonical usage recorded `deepseek-official / deepseek-flash`. + +The six installed product tarballs were verified as `1.3.0-test.1615.1`, with Provider `4.32.0-botharness.22` at the immutable source and digest above. Runtime-bearing Core code was built from `7b8a1da7`; subsequent integration commit `a1d49691` removed an unintended docs-site route/font change without changing runtime code. Local Core build, typecheck, lint, formatting, 80 integrated Core/packaging regressions and docs build passed. The integration CI at `a1d4969199049165615bd9c4554a35474c60297a` passed affected tests, build/type/lint, docs, isolated Host smoke and package preparation. No complete local Windows suite success is claimed. + +The built-in browser signed in as the designated Human account and opened the registered **DeepSeekBot Cloud QA / 常规** destination. Both positive tests selected distinct native entries from Discord's `@` menu, rather than typing a display name as plain text. + +| Native test | Native addressing evidence | Actual result | +| ----------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| `DISCORD_1615_USER_20261016_B` | One Bot-user mention, no role mention | Exactly one rendered `USER_OK`, one accepted Outbox, receipt references the original source | +| `DISCORD_1615_ROLE_20261016_B` | One own managed-role mention, zero user mentions | Exactly one rendered `ROLE_OK`, one accepted Outbox, receipt references the original source | +| `DISCORD_1615_UNRELATED_20261016_A` | One unmanaged unrelated role, also held by the Bot; its permission bitset was zero | No new Source, Admission, model usage or Outbox | +| `DISCORD_1615_EVERYONE_20261016_A` | Native everyone flag, zero user mentions | No new Source, Admission, model usage or Outbox | +| `DISCORD_1615_HERE_20261016_A` | Native everyone flag, zero user mentions | No new Source, Admission, model usage or Outbox | + +The Provider reported six actually received Human messages and a healthy Gateway: an initial setup-only bare mention, the two positive markers and the three negative markers. The setup-only mention came from a browser composer automation attempt that did not preserve the intended text; its normal explanatory reply is retained as separate evidence and excluded from marker acceptance. Only the B markers qualify the positive cases. Native API readback confirmed the exact author, destination, `mentions` / `mention_roles` / everyone fields, and original-message references of both successful receipts. The role Source retained `mentionedAccount: true` with an empty user-mention list. + +The negative observation compared canonical table counts and settled usage across 101 seconds, starting before the three negative sends. All remained unchanged; the visible channel had no negative-case reply. Ordinary collection stayed mentions-only. This does not qualify content delivery without Message Content approval, which the current App already held. + +The temporary unrelated role was deleted, and native readback confirmed the original two-role set and Bot membership were restored. The exact task-owned receiver Host was stopped at 04:46 JST, before the cutoff; QA1 returned to its original idle state. Other App receivers and production model settings were untouched. Private native facts, screenshots and the browser recording remain machine-local. + +## Native requalification on the synced candidate — 2026-11-02 JST + +Provider `518314f6cf495aea093c0a93b0107de25b66e60d` (`4.32.0-botharness.25`) ran from an isolated Host on DSH `0.2.1-rc.3` with QA1 in the **BotHarness QA1 / bot-qa1** channel. A real DM through `deepseek-v4.1-flash` returned `42`. The signed-in Human account sent each message from the built-in browser, selecting native `@` entries. + +| Native test | Native addressing evidence | Actual result | +| --------------------------------- | ------------------------------------- | --------------------------------------------------- | +| `DISCORD_1615_USER_20261102` | One user mention, no role mention | One rendered `USER_OK` reply referencing the source | +| `DISCORD_1615_ROLE_20261102` | One own managed-role mention, no user | One rendered `ROLE_OK` reply referencing the source | +| `DISCORD_1615_EVERYONE_20261102` | Native everyone flag, no user or role | Received by the Provider, no Admission and no reply | +| `DISCORD_1615_HERE_20261102` | Native everyone flag, no user or role | Received by the Provider, no Admission and no reply | +| `DISCORD_1615_UNRELATED_20261102` | One temporary role that nobody held | Received by the Provider, no Admission and no reply | + +`bot-attention` held exactly two handled `group-mention` records plus the setup DM. The negative observation lasted about one minute. The temporary role was deleted, the QA1 App was removed from the Host and the Host was stopped. Other-Bot role mentions have automated coverage only. The model's `ROLE_OK` reply repeated the role text, which rendered without pinging anyone. + +## Remaining review and release gates + +- Core #1927 and maintained Provider #30 remain draft; merge, public publication and deployment have not occurred. +- Current native screenshots/recording exist locally. A matched base/candidate visual pair and GitHub-rendered review evidence remain to be prepared; current evidence does not claim that PR presentation gate is complete. +- Head-bound HTML review aids and any reusable upstream contribution remain independent handoff work. + +Earlier T3 snapshot/open attempts refused with no connected automation host, and a fallback browser reached Discord's login route. Those failures are retained as setup evidence. After the Human requested another built-in-browser attempt, T3 worked with the existing signed-in account and the qualification above completed. The earlier browser-access limit no longer blocks native testing. + +References: [issue #1615](https://github.com/BotHarness/DeepSeekBot/issues/1615), [role tags](https://docs.discord.com/developers/topics/permissions#role-object), [message fields](https://docs.discord.com/developers/resources/message#message-object), [ADR-0128](../../adr/0128-discord-checked-replies-preserve-native-child-channel-routing.md). diff --git a/docs/discord-connection.md b/docs/discord-connection.md new file mode 100644 index 0000000000..280ecdeca3 --- /dev/null +++ b/docs/discord-connection.md @@ -0,0 +1,28 @@ +# Connect a Bot to Discord + +Use a Discord Bot application installed in a designated test server. A Discord user login and an application Client Secret are not Bot Tokens. Keep the Token in the Provider's native credentials flow, never in a chat message or repository. + +## Connect and bind + +1. Prepare the Bot in the [Discord Developer Portal](https://discord.com/developers/applications) and install it in the intended server through Discord's application authorization flow. +2. Give it View Channel, Read Message History and Send Messages in the selected text channel. Existing public-thread replies also need Send Messages in Threads. Do not grant Administrator merely to complete this setup. +3. Connect the Discord account in native **Settings → IM bots**. Open the PersonaBot's **Bind app** control and bind the verified Discord identity to that Bot. +4. Send a fresh mention in the designated channel. Check both the original Discord reply and the corresponding Source/Inbox/Outbox facts. A connected indicator alone is not a successful chat test. + +One App belongs to one PersonaBot. Shared local Channel visibility does not authorize another Bot to send through that App. A newly bound group's default is mentions-only; ordinary collection and Channel synchronization remain separate choices. + +## Which same-name mention to choose + +Discord may offer the Bot user and its integration-managed role with the same name. A direct Bot-user mention is the established baseline. The #1615 candidate also recognizes the App's own role after checking its current native guild/member ownership: `managed: true`, exact `tags.bot_id`, the Bot's current role membership and an exact ID in `mention_roles`. + +Other roles, even ones the Bot holds, and `@everyone`/`@here` do not address the Bot. Typing a display name without a genuine native mention does not address it either. The original text and native reply location are preserved. + +Role recognition does not enable the privileged Message Content intent or bypass unavailable content. Keep the verified Bot-user path usable when the selected App cannot deliver the role-mention message. Any additional App permission needs its own authorization and native qualification. + +## Verify and diagnose + +Use separate unique markers for the Bot-user mention and own managed-role mention. Each admitted request should produce one source-bound reply in the original text channel or existing public thread. Test unrelated role and everyone/here negatives without an independent direct Bot mention. Ordinary collection, if separately enabled, must not reinterpret those negatives as direct requests. + +The #1615 Runtime/Consumer regression is automated; this candidate's fresh native and installed-product acceptance remains pending in its [qualification record](dev/verification/discord-1615-managed-role.md). Earlier Discord evidence does not prove this new case. Do not start a second receiver for the same App; arrange an exclusive test window and restore the original receiver afterwards. + +See [Discord role tags](https://docs.discord.com/developers/topics/permissions#role-object) and [native message mentions](https://docs.discord.com/developers/resources/message#message-object). diff --git a/docs/discord-connection.zh.md b/docs/discord-connection.zh.md new file mode 100644 index 0000000000..524a6826a3 --- /dev/null +++ b/docs/discord-connection.zh.md @@ -0,0 +1,28 @@ +# 将 Bot 接入 Discord + +使用已安装到指定测试服务器的 Discord Bot 应用。Discord 用户登录和应用 Client Secret 都不是 Bot Token。Token 只进入 Provider 的原生凭证流程,不要发到聊天或仓库。 + +## 连接和绑定 + +1. 在 [Discord Developer Portal](https://discord.com/developers/applications) 准备 Bot,通过 Discord 应用授权流程安装到目标服务器。 +2. 在目标文字频道授予查看频道、读取消息历史和发送消息权限。回复已有公开 Thread 还需在线程中发送消息权限。不要仅为完成配置授予 Administrator。 +3. 在原生 **设置 → IM bots** 中连接 Discord 账号。打开 PersonaBot 的 **绑定应用**,绑定已核验的 Discord 身份。 +4. 在指定频道发送新标记和真实提及,同时核对 Discord 原位置回复与 Source、Inbox、Outbox 事实。连接指示灯不代表聊天验收成功。 + +一个 App 只有一个 PersonaBot 所有者。本地共享 Channel 的可见性不会授权其他 Bot 使用该 App 发言。新绑定群默认仅收集提及;普通消息收集和 Channel 同步仍是独立选择。 + +## 同名的两个提及选哪一个 + +Discord 可能同时列出同名的 Bot 用户和应用托管角色。直接提及 Bot 用户是已交付的基础路径。#1615 候选还支持应用自己的托管角色:按当前原生服务器与成员核验 `managed: true`、准确的 `tags.bot_id`、Bot 当前持有的角色,以及 `mention_roles` 中准确的角色 ID。 + +其他角色即使由 Bot 持有,也不构成直接提及;`@everyone`、`@here` 同样不构成直接提及。仅输入显示名称也不会触发请求。原始文本和原生回复位置保持不变。 + +识别角色提及不会开启特权 Message Content intent,也不能读取平台未交付的内容。所选 App 无法交付角色提及消息时,保留已核验的 Bot 用户提及路径。新增应用权限需要单独授权和真实验收。 + +## 验证与排查 + +分别用不同的新标记测试 Bot 用户提及和自身托管角色提及。每个已接纳请求应在原文字频道或已有公开 Thread 中得到一条来源关联回复。测试无关角色、everyone/here 时,不要同时真正提及 Bot 用户。独立开启的普通收集不能将这些反例改成直接请求。 + +#1615 的 Runtime/Consumer 自动回归已通过,候选的真实平台与安装包验收仍记录为待完成,见[资格记录](dev/verification/discord-1615-managed-role.md)。过去的 Discord 证据不能证明这个新场景。不要为同一 App 启动第二个接收器;先安排独占窗口,结束后恢复原接收器。 + +参考 [Discord 角色标签](https://docs.discord.com/developers/topics/permissions#role-object) 与[原生消息提及字段](https://docs.discord.com/developers/resources/message#message-object)。 diff --git a/docs/product-im-installation.md b/docs/product-im-installation.md index 7dd22d7a75..171e0bf19f 100644 --- a/docs/product-im-installation.md +++ b/docs/product-im-installation.md @@ -35,14 +35,14 @@ fails before packing. ```bash git clone https://github.com/DoodleBears/dsh-im.git /tmp/bh-im-source -git -C /tmp/bh-im-source checkout 7505ce6fcda26c02810781fde9d4da81dab28a68 +git -C /tmp/bh-im-source checkout 518314f6cf495aea093c0a93b0107de25b66e60d npm ci --prefix /tmp/bh-im-source --ignore-scripts --no-audit --no-fund pnpm install --frozen-lockfile pnpm build node scripts/product-artifacts.mjs \ --provider-source /tmp/bh-im-source \ --output /tmp/bh-product-artifacts \ - --version 1.3.0-test.1155.1 + --version 1.3.0-test.1615.1 ``` Choose a fresh output directory. `artifacts.json` records the six actual tarballs, diff --git a/packages/core/test/messaging-discord.test.ts b/packages/core/test/messaging-discord.test.ts index 7e56916197..d6927a597c 100644 --- a/packages/core/test/messaging-discord.test.ts +++ b/packages/core/test/messaging-discord.test.ts @@ -40,6 +40,15 @@ it.each([ state: 'provider-accepted', reason: undefined, }, + { + name: 'own managed-role channel reply', + managedRole: true, + nearby: false, + threadId: undefined, + refusal: undefined, + state: 'provider-accepted', + reason: undefined, + }, { name: 'thread reply', nearby: false, @@ -74,7 +83,7 @@ it.each([ }, ])( 'Discord canonical Inbox and own-identity reply preserve $name outcome', - async ({ threadId, refusal, state, reason, nearby }) => { + async ({ threadId, refusal, state, reason, nearby, managedRole }) => { const fingerprint = 'd'.repeat(64); let consumer: Parameters>[1] | undefined; let runs = 0; @@ -219,10 +228,10 @@ it.each([ messageId: '777777777777777777', actor: { kind: 'user', id: '666666666666666666', name: 'QA Human' }, conversation: { kind: 'group', id: '444444444444444444' }, - mentions: [{ id: '111111111111111111', key: '<@111111111111111111>' }], + mentions: managedRole ? [] : [{ id: '111111111111111111', key: '<@111111111111111111>' }], mentionedAccount: true, at: new Date().toISOString(), - text: '<@111111111111111111> Reply here', + text: managedRole ? '<@&888888888888888888> Reply here' : '<@111111111111111111> Reply here', reply: { messageId: '777777777777777777', actorId: '666666666666666666', diff --git a/scripts/dev-im-provider.mjs b/scripts/dev-im-provider.mjs index ee3fa2b6f4..c951ce6b33 100644 --- a/scripts/dev-im-provider.mjs +++ b/scripts/dev-im-provider.mjs @@ -5,11 +5,11 @@ import { join } from 'node:path'; export const qualifiedImProvider = Object.freeze({ package: '@xmanrui/dsh-im', packageVersion: '4.32.0', - source: '7505ce6fcda26c02810781fde9d4da81dab28a68', - spec: 'github:DoodleBears/dsh-im#7505ce6fcda26c02810781fde9d4da81dab28a68', + source: '518314f6cf495aea093c0a93b0107de25b66e60d', + spec: 'github:DoodleBears/dsh-im#518314f6cf495aea093c0a93b0107de25b66e60d', dsh: '0.2.1-rc.2', runtimeFiles: 424, - runtimeSha256: 'a4429580ebf70b08e19394cea97d7d0db9b86b06e1c92f294a28144378537d46', + runtimeSha256: 'bc54309720ce66906b562d7afaae99669c2177d67717623f6999e906abe473d1', }); export function withQualifiedImProvider(manifest) { diff --git a/scripts/product-artifacts.mjs b/scripts/product-artifacts.mjs index 64844fb2ac..a47b0031be 100644 --- a/scripts/product-artifacts.mjs +++ b/scripts/product-artifacts.mjs @@ -21,16 +21,16 @@ export const productDshVersions = Object.freeze(['0.2.0-rc.2', '0.2.1-rc.2', pro export const productImProvider = Object.freeze({ name: '@botharness/im-provider', - version: '4.32.0-botharness.24', + version: '4.32.0-botharness.25', sourceManifestSha256: '3331e8f771a91abf396eb4e7ed5c0acc4c3b504cdccc7d715e37062708384cc2', sourceLockSha256: '3476c34c7d4515000d2617beaf2568b2a794b60de9b350cd8eafb7a2bf21adef', upstream: Object.freeze({ package: '@xmanrui/dsh-im', packageVersion: '4.32.0', - source: '7505ce6fcda26c02810781fde9d4da81dab28a68', + source: '518314f6cf495aea093c0a93b0107de25b66e60d', dsh: '0.2.0-rc.2', runtimeFiles: 424, - runtimeSha256: 'a4429580ebf70b08e19394cea97d7d0db9b86b06e1c92f294a28144378537d46', + runtimeSha256: 'bc54309720ce66906b562d7afaae99669c2177d67717623f6999e906abe473d1', }), }); diff --git a/scripts/test/product-artifacts.test.mjs b/scripts/test/product-artifacts.test.mjs index 6f7c5d246c..1100e86d1e 100644 --- a/scripts/test/product-artifacts.test.mjs +++ b/scripts/test/product-artifacts.test.mjs @@ -324,7 +324,7 @@ describe('release composition', () => { expect(() => verifyProductComposition(entries)).toThrow('conflicts'); }); it('keeps product provenance independent when the development Provider selection changes', () => { - expect(productImProvider.upstream.source).toBe('7505ce6fcda26c02810781fde9d4da81dab28a68'); + expect(productImProvider.upstream.source).toBe('518314f6cf495aea093c0a93b0107de25b66e60d'); expect(productImProvider.upstream.dsh).toBe('0.2.0-rc.2'); }); @@ -339,7 +339,7 @@ describe('release composition', () => { '@botharness/computer': '0.0.0-test.823', '@botharness/core': '0.0.0-test.823', '@botharness/ui': '0.0.0-test.823', - '@botharness/im-provider': '4.32.0-botharness.24', + '@botharness/im-provider': '4.32.0-botharness.25', }); expect(release.optionalDependencies).toEqual( Object.fromEntries(cloudflaredPackageNames.map((name) => [name, '0.0.0-test.823'])),