feat(platform): add dingtalk eba adapter

This commit is contained in:
Junyan Qin
2026-05-10 19:52:36 +08:00
parent 3ed35593e9
commit 950da65797
18 changed files with 1256 additions and 151 deletions

View File

@@ -15,9 +15,10 @@ Current acceptance report: [EBA Adapter Acceptance Report](./acceptance-report.m
| Adapter | Status | Document |
|---------|--------|----------|
| Telegram | Migrated and live-tested | [Telegram](./telegram.md) |
| Discord | Migrated and live-tested | [Discord](./discord.md) |
| OneBot v11 / aiocqhttp | Migrated; Matcha-tested where supported | [OneBot v11 / aiocqhttp](./aiocqhttp.md) |
| Telegram | Migrated; partial plugin E2E, media-inbound gaps remain | [Telegram](./telegram.md) |
| Discord | Migrated; partial plugin E2E, media-inbound gaps remain | [Discord](./discord.md) |
| OneBot v11 / aiocqhttp | Migrated; Matcha UI plus protocol-level multi-component coverage | [OneBot v11 / aiocqhttp](./aiocqhttp.md) |
| DingTalk | Migrated; partial plugin E2E, group/media-inbound gaps remain | [DingTalk](./dingtalk.md) |
## Documentation Checklist
@@ -29,3 +30,4 @@ When migrating a new adapter, add one document here with:
- Supported `call_platform_api` action list.
- Known unsupported APIs and the reason.
- Live test notes, including platform, channel type, destructive operations, and residual risks.
- A clear distinction between real UI inbound media, protocol-level injected inbound media, and bot outbound media.

View File

@@ -8,13 +8,15 @@ Use these evidence levels consistently in adapter records:
| Level | Meaning | Can Mark Complete |
|-------|---------|-------------------|
| `plugin-e2e` | Real SDK plugin running through standalone runtime, LangBot core, the migrated adapter, and a real or simulator platform endpoint. | Yes |
| `plugin-e2e-ui` | Real SDK plugin running through standalone runtime, LangBot core, the migrated adapter, and a real platform/simulator UI action. | Yes |
| `plugin-e2e-protocol` | Real SDK plugin running through standalone runtime, LangBot core, and the migrated adapter from a protocol-boundary event injection, such as a OneBot reverse WebSocket event. | Partial; must not be claimed as UI coverage |
| `plugin-e2e-outbound` | Real SDK plugin calls an API and the bot output is visible in the real platform/simulator UI. | Yes for send/API coverage only |
| `adapter-live` | Direct adapter probe connected to a real or simulator platform endpoint, bypassing plugin runtime. | No, auxiliary only |
| `unit` | Unit/API-shape tests with mocked platform SDK objects or mocked APIs. | No, auxiliary only |
| `not-supported` | Platform protocol or SDK has no equivalent capability. Must include reason and source. | Yes, as explicitly unsupported |
| `blocked` | Intended capability could not be verified because of credentials, permissions, endpoint gaps, or simulator gaps. | No |
The primary acceptance path must be `plugin-e2e`. `adapter-live` and `unit` tests are useful, but they do not prove the EBA architecture path.
The primary acceptance path must be `plugin-e2e-ui` for inbound UI-triggered behavior and `plugin-e2e-outbound` for bot send/API behavior. `adapter-live`, `plugin-e2e-protocol`, and `unit` tests are useful, but they must be labelled precisely.
## Required Architecture Path
@@ -47,7 +49,7 @@ The test plugin must record JSONL evidence containing:
## Required Message Receive Tests
For every adapter, inbound message conversion must be tested through `plugin-e2e` for each component the platform can receive. If the platform cannot create a component from the UI/simulator, record it as `blocked` with the endpoint limitation.
For every adapter, inbound message conversion must be tested through `plugin-e2e-ui` for each component the platform can receive. If a protocol-level injection is used, label it `plugin-e2e-protocol`; it proves the adapter/core/plugin path, but it does not prove that the user-facing platform UI can send that component. If the platform UI/simulator cannot create a component, record it as `blocked` with the endpoint limitation.
| Component | Required Receive Assertion |
|-----------|----------------------------|
@@ -68,7 +70,7 @@ The plugin must subscribe to `MessageReceivedEvent` and assert that `message_cha
## Required Message Send Tests
For every adapter, outbound message conversion must be tested through `plugin-e2e` by having the plugin call SDK platform APIs and verifying the platform UI/simulator receives the expected message.
For every adapter, outbound message conversion must be tested through `plugin-e2e-outbound` by having the plugin call SDK platform APIs and verifying the platform UI/simulator receives the expected message.
| Component | Required Send Assertion |
|-----------|-------------------------|
@@ -87,7 +89,7 @@ If a platform supports a component only in one direction, the adapter record mus
## Required Event Tests
The plugin must subscribe to every event declared in `manifest.yaml -> spec.supported_events` and record one of `plugin-e2e`, `not-supported`, or `blocked`.
The plugin must subscribe to every event declared in `manifest.yaml -> spec.supported_events` and record one of `plugin-e2e-ui`, `plugin-e2e-protocol`, `not-supported`, or `blocked`.
| Event | Required Assertion |
|-------|--------------------|
@@ -154,7 +156,8 @@ The result must be serialized into JSON-safe values before it is returned to the
Every action listed in `manifest.yaml -> spec.platform_specific_apis` must have one acceptance entry:
- `plugin-e2e`: called by the plugin against the live/simulator endpoint.
- `plugin-e2e-ui` or `plugin-e2e-outbound`: called by the plugin against the live/simulator endpoint.
- `plugin-e2e-protocol`: called by the plugin after a protocol-boundary injected event; useful for endpoint-specific simulators but must be labelled.
- `not-supported`: removed from manifest or explained if the platform SDK exposes it but this adapter intentionally does not.
- `blocked`: endpoint did not implement it, permissions missing, or safe fixture unavailable.
@@ -195,10 +198,11 @@ Each adapter document must include:
An adapter can be marked migrated only when:
1. All declared events have `plugin-e2e` or `not-supported` evidence.
2. All declared APIs have `plugin-e2e` or `not-supported` evidence.
3. All platform-supported receive/send message components have `plugin-e2e` evidence.
4. Unit tests cover conversion and API-shape boundaries.
5. The adapter document lists every blocked or skipped item honestly.
1. All declared events have `plugin-e2e-ui`, justified `plugin-e2e-protocol`, or `not-supported` evidence.
2. All declared APIs have `plugin-e2e-outbound` or `not-supported` evidence.
3. All platform-supported receive components have `plugin-e2e-ui` evidence; protocol-only receive coverage keeps the status partial.
4. All platform-supported send components have `plugin-e2e-outbound` evidence.
5. Unit tests cover conversion and API-shape boundaries.
6. The adapter document lists every blocked or skipped item honestly.
If any declared capability is only covered by `adapter-live` or `unit`, the adapter status must remain partial.

View File

@@ -7,177 +7,141 @@ Scope:
- `telegram-eba`
- `discord-eba`
- `aiocqhttp-eba`
- `dingtalk-eba`
This report follows `acceptance-checklist.md`. The primary evidence is a real SDK plugin, `EBAEventProbe`, running through standalone runtime, LangBot core, the migrated adapter, and a real platform or simulator endpoint.
This report follows `acceptance-checklist.md`. Evidence levels are intentionally strict:
- `plugin-e2e-ui`: real platform or simulator UI event reached LangBot, standalone runtime, and `EBAEventProbe`.
- `plugin-e2e-protocol`: real adapter endpoint event reached LangBot, standalone runtime, and `EBAEventProbe`, but the event was injected at the platform protocol boundary rather than sent through the UI.
- `plugin-e2e-outbound`: the plugin called SDK APIs and the resulting bot message was visible on the platform.
- `unit`: mocked converter/API coverage only.
- `blocked`: not completed, either because the platform/simulator/client could not trigger it or because a safe disposable fixture was unavailable.
- `not-supported`: the platform has no equivalent capability.
## Summary
| Adapter | Status | Acceptance summary |
|---------|--------|--------------------|
| Telegram | Accepted with documented platform limits | Private and group `MessageReceived` paths, bot invite event, outbound component sweep, SDK APIs, storage APIs, and Telegram platform APIs were verified through standalone-runtime plugin E2E. Bot API limitations and unsupported common APIs are listed below. |
| Discord | Accepted with documented platform limits | Real Discord server/channel E2E verified `MessageReceived`, common entity conversion, outbound components, SDK APIs, Discord guild/member APIs, and Discord platform APIs. Destructive moderation was not run against the shared server. |
| OneBot v11 / aiocqhttp | Accepted for Matcha-supported capabilities; partial for endpoint-gapped capabilities | Matcha E2E verified message receive, common fields, send, reply, supported outbound components, safe common APIs, safe OneBot platform APIs, and SDK storage/list APIs. Matcha lacks merged-forward, file send, and several destructive/admin fixtures; those remain blocked for that endpoint. |
| Adapter | Status | Honest acceptance summary |
|---------|--------|---------------------------|
| Telegram | Partial EBA acceptance | Real Telegram UI covered private text, group mention text, bot invite, outbound component sweep, safe SDK APIs, and safe Telegram platform APIs. Real UI inbound image/file/voice/quote was not completed in the latest plugin run. |
| Discord | Partial EBA acceptance | Real Discord UI covered group text, outbound image/file/quote/mention components, safe SDK APIs, and safe Discord platform APIs. Real UI inbound attachment/image/file/reply/mention was not completed. A later UI retry was blocked because the Discord client kept the send button disabled. |
| OneBot v11 / aiocqhttp | Partial EBA acceptance | Matcha UI covered real group text and outbound supported components/APIs. Multi-component inbound `Source/Plain/At/Face/Image/Voice/File/Quote` was verified through the real OneBot reverse WebSocket adapter endpoint, but not through Matcha UI upload/send. Matcha blocks file-send and merged-forward APIs. |
| DingTalk | Partial EBA acceptance | Real DingTalk UI covered private text and emoji-as-text inbound, outbound image/file/quote/mention fallback components, safe SDK APIs, and safe DingTalk platform APIs. Real UI inbound image/file/voice/quote and group trigger were not completed. |
No adapter in this report is marked as fully accepted for production-grade media inbound until real user-side UI image/file upload evidence exists in the plugin JSONL.
## Evidence Files
| Adapter | Real endpoint | Evidence |
|---------|---------------|----------|
| Adapter | Endpoint | Evidence |
|---------|----------|----------|
| Telegram private | Telegram Lite, `@rockchinq_bot` private chat | `data/temp/telegram-plugin-e2e-rerun.jsonl` |
| Telegram group | Telegram Lite, `Rock'sBotGroup` | `data/temp/telegram-plugin-e2e-group.jsonl` |
| Discord | Discord web client, LangBot server, `#🐞-debugging` | `data/temp/discord-plugin-e2e-20260510-final.jsonl` |
| aiocqhttp | local Matcha, group `测试群` | `data/temp/aiocqhttp-plugin-e2e-rerun.jsonl` |
| Discord | Discord client, LangBot server, `#debugging` | `data/temp/discord-plugin-e2e-20260510-final.jsonl` |
| aiocqhttp UI | local Matcha, group `test group` | `data/temp/aiocqhttp-plugin-e2e-20260510-multiformat.jsonl` |
| aiocqhttp protocol | OneBot reverse WebSocket endpoint `127.0.0.1:2280/ws` | `data/temp/aiocqhttp-plugin-e2e-20260510-multiformat.jsonl` |
| DingTalk | DingTalk Mac, `LangBot Team` org private chat | `data/temp/dingtalk-plugin-e2e-20260510-rerun.jsonl` |
All runs used standalone runtime ports `5400/5401`, LangBot `--standalone-runtime`, and the plugin at `langbot-plugin-demo/EBAEventProbe`.
All plugin runs used SDK standalone runtime ports `5400/5401`, LangBot `--standalone-runtime`, and the real plugin at `langbot-plugin-demo/EBAEventProbe`.
## Unified Shape Verification
All three adapters deliver common SDK entities to plugins before LangBot core/plugin logic handles the event:
All four adapters deliver common SDK entities to plugins before LangBot core/plugin logic handles the event.
| Requirement | Telegram | Discord | aiocqhttp |
|-------------|----------|---------|-----------|
| `bot_uuid` filled | plugin-e2e: `eba-telegram-live` | plugin-e2e: `eba-discord-live` | plugin-e2e: `eba-aiocqhttp-matcha` |
| `adapter_name` filled | plugin-e2e: `telegram` | plugin-e2e: `discord` | plugin-e2e: `aiocqhttp` |
| common `MessageChain` | plugin-e2e: `At`, `Plain` in group, `Plain` in private | plugin-e2e: `Source`, `Plain` | plugin-e2e: `Source`, `Plain` |
| common user/group entities | plugin-e2e: Telegram user/group IDs and group name | plugin-e2e: Discord user, guild, channel, member count | plugin-e2e: OneBot user, group ID, group name |
| raw native object isolation | plugin-visible behavior used common fields only | plugin-visible behavior used common fields only | plugin-visible behavior used common fields only |
| Requirement | Telegram | Discord | aiocqhttp | DingTalk |
|-------------|----------|---------|-----------|----------|
| `bot_uuid` filled | plugin-e2e | plugin-e2e | plugin-e2e | plugin-e2e |
| `adapter_name` filled | `telegram` | `discord` | `aiocqhttp` | `dingtalk` |
| common `MessageChain` delivered | `Plain`, group `At + Plain` | `Source + Plain` | UI `Source + Plain`; protocol `Source + Plain + At + Face + Image + Voice + File + Quote + Plain` | `Source + Plain` |
| common user/group entities | plugin-e2e | plugin-e2e | plugin-e2e | plugin-e2e private user; group not completed |
| raw native object isolation | raw data stays in `source_platform_object` | raw data stays in `source_platform_object` | raw data stays in `source_platform_object` | raw data stays in `source_platform_object` |
## Message Receive Components
| Component | Telegram | Discord | aiocqhttp |
|-----------|----------|---------|-----------|
| `Source` | supported by event `message_id`; converter does not currently append `Source` to chain, documented design gap | plugin-e2e | plugin-e2e |
| `Plain` | plugin-e2e private/group | plugin-e2e | plugin-e2e |
| `At` | plugin-e2e group mention | unit + supported converter path | unit + supported converter path |
| `AtAll` | not-supported: Telegram has no common broadcast mention object in Bot API messages | unit + supported converter path | unit + supported converter path |
| `Image` | supported by converter; not reproduced in plugin run | supported by converter; outbound plugin rendering verified | supported by converter; outbound plugin rendering verified |
| `Voice` | supported by converter; not reproduced in plugin run | not-supported as native voice message; Discord audio is a file attachment | unit + supported converter path |
| `File` | supported by converter; outbound plugin rendering verified | supported by converter; outbound plugin rendering verified | supported by converter; Matcha file send is blocked |
| `Quote` | supported for replies/outbound quoted send; inbound quote not reproduced | outbound quote verified; inbound structured quote not emitted by Discord | unit + supported converter path |
| `Face` | not-supported as common `Face` in current Telegram adapter | not-supported as common message component | unit + supported converter path |
| `Forward` | not-supported for inbound structured forward | not-supported for inbound structured forward | implemented where endpoint supports forward payloads; Matcha forward action blocked |
| `Unknown` | not reproduced | not reproduced | unit coverage |
| Mixed chain | group `At` + `Plain` plugin-e2e | outbound mixed chain plugin-e2e | outbound mixed chain plugin-e2e |
| Component | Telegram | Discord | aiocqhttp | DingTalk |
|-----------|----------|---------|-----------|----------|
| `Source` | design gap: event has message id but chain omits `Source` | plugin-e2e-ui | plugin-e2e-ui/protocol | plugin-e2e-ui |
| `Plain` | plugin-e2e-ui private/group | plugin-e2e-ui | plugin-e2e-ui/protocol | plugin-e2e-ui |
| `At` | plugin-e2e-ui group mention | unit; real UI mention not completed in latest run | plugin-e2e-protocol; unit | unit; group trigger not completed |
| `AtAll` | not-supported | unit only | unit only | unit/send fallback only |
| `Image` | converter/unit; real UI inbound not completed | converter/unit; real UI attachment not completed | plugin-e2e-protocol, not Matcha UI | converter/unit; real UI inbound not completed |
| `Voice` | converter/unit; real UI inbound not completed | not-supported as native voice; audio is attachment/file | plugin-e2e-protocol, not Matcha UI | converter/unit; real UI inbound not completed |
| `File` | converter/unit; real UI inbound not completed | converter/unit; real UI attachment not completed | plugin-e2e-protocol, not Matcha UI | converter/unit; real UI inbound not completed |
| `Quote` | converter/unit; real UI reply not completed | unit; real UI reply not completed | plugin-e2e-protocol | converter/unit; real UI quote not completed |
| `Face` | not-supported as common `Face` | not-supported as common `Face` | plugin-e2e-protocol | UI emoji becomes `Plain` (`[smile]` text), not `Face` |
| `Forward` | not-supported inbound | not-supported inbound | unit; Matcha forward UI/action blocked | not-supported inbound |
| Mixed chain | group `At + Plain` only | not completed inbound | plugin-e2e-protocol | not completed inbound |
## Message Send Components
| Component | Telegram | Discord | aiocqhttp |
|-----------|----------|---------|-----------|
| `Plain` | plugin-e2e | plugin-e2e | plugin-e2e |
| `At` | plugin-e2e: group mention text equivalent | plugin-e2e: user mention rendered | plugin-e2e: `@Rock` rendered |
| `AtAll` | plugin-e2e fallback text/equivalent; no native common broadcast object | plugin-e2e: `@everyone` rendered | plugin-e2e: `@全体成员` rendered |
| `Image` | plugin-e2e base64 image | plugin-e2e base64 image | plugin-e2e base64 image |
| `Voice` | not-supported in current send converter | not-supported as native voice; use `File` attachment | supported by converter; not exercised against Matcha |
| `File` | plugin-e2e document send | plugin-e2e attachment send | blocked: Matcha errors on file segment despite official segment shape |
| `Quote` | plugin-e2e quoted reply | plugin-e2e quoted reply | plugin-e2e quoted reply |
| `Face` | not-supported | not-supported | plugin-e2e converter path attempted in `plain_at_face`; Matcha accepts face-like payload path |
| `Forward` | plugin-e2e flattened forward fallback | plugin-e2e flattened forward fallback | blocked: Matcha does not support merged-forward action |
| Mixed chain | plugin-e2e | plugin-e2e | plugin-e2e except Matcha-blocked file/forward |
| Component | Telegram | Discord | aiocqhttp | DingTalk |
|-----------|----------|---------|-----------|----------|
| `Plain` | plugin-e2e-outbound | plugin-e2e-outbound | plugin-e2e-outbound | plugin-e2e-outbound |
| `At` | plugin-e2e-outbound equivalent | plugin-e2e-outbound | plugin-e2e-outbound | plugin-e2e-outbound fallback/equivalent |
| `AtAll` | plugin-e2e-outbound fallback | plugin-e2e-outbound | plugin-e2e-outbound | plugin-e2e-outbound fallback |
| `Image` | plugin-e2e-outbound | plugin-e2e-outbound | plugin-e2e-outbound | plugin-e2e-outbound |
| `Voice` | not-supported in current send converter | not-supported as native voice | converter path; not completed against Matcha UI | fallback as file/text depending DingTalk media support |
| `File` | plugin-e2e-outbound | plugin-e2e-outbound | blocked by Matcha endpoint error | plugin-e2e-outbound |
| `Quote` | plugin-e2e-outbound | plugin-e2e-outbound | plugin-e2e-outbound | plugin-e2e-outbound fallback |
| `Face` | not-supported | not-supported | plugin-e2e-outbound attempted in mixed chain | fallback text |
| `Forward` | flattened fallback | flattened fallback | blocked by Matcha unsupported action | flattened fallback |
| Mixed chain | plugin-e2e-outbound | plugin-e2e-outbound | plugin-e2e-outbound except blocked file/forward | plugin-e2e-outbound |
## Event Acceptance
### Telegram
| Event | Evidence | Notes |
|-------|----------|-------|
| `message.received` | plugin-e2e | Private and group messages reached `EBAEventProbe`. |
| `message.edited` | implemented; not reproduced in current plugin run | Requires user edit fixture. |
| `message.reaction` | implemented; not reproduced in current plugin run | Requires Telegram reaction update fixture. |
| `group.member_joined` | implemented; not reproduced in current plugin run | |
| `group.member_left` | adapter-live historical; not reproduced in current plugin run | |
| `group.member_banned` | adapter-live historical; not reproduced in current plugin run | |
| `bot.invited_to_group` | plugin-e2e | Adding the bot to `Rock'sBotGroup` emitted the event. |
| `bot.removed_from_group` | adapter-live historical; destructive not repeated | |
| `bot.muted` | implemented; blocked without disposable moderation target | |
| `bot.unmuted` | implemented; blocked without disposable moderation target | |
| `platform.specific` | implemented; not reproduced in current plugin run | |
### Discord
| Event | Evidence | Notes |
|-------|----------|-------|
| `message.received` | plugin-e2e | Real web-client message in `#🐞-debugging`. |
| `message.edited` | adapter-live historical; not repeated in final plugin run | |
| `message.deleted` | adapter-live historical; not repeated in final plugin run | |
| `message.reaction` | adapter-live historical; not repeated in final plugin run | |
| `group.member_joined` | blocked | No disposable user/bot join fixture in shared server. |
| `group.member_left` | blocked | No disposable user/bot leave fixture in shared server. |
| `group.member_banned` | blocked | No disposable moderation target. |
| `bot.invited_to_group` | plugin-e2e during OAuth invite | Verified by runtime event in the same run series. |
| `bot.removed_from_group` | blocked/destructive | Not repeated after final invite. |
| `platform.specific` | not reproduced | No unmapped gateway payload triggered in final run. |
### OneBot v11 / aiocqhttp
| Event | Evidence | Notes |
|-------|----------|-------|
| `message.received` | plugin-e2e | Real Matcha group message. |
| `message.deleted` | unit | Matcha recall fixture not available. |
| `group.member_joined` | unit | Matcha fixture not available. |
| `group.member_left` | unit | Matcha fixture not available. |
| `group.member_banned` | unit | Matcha fixture not available. |
| `friend.request_received` | unit | Matcha request fixture not available. |
| `friend.added` | unit | Matcha request fixture not available. |
| `bot.invited_to_group` | unit | Matcha invite fixture not available. |
| `bot.removed_from_group` | unit | destructive fixture skipped. |
| `bot.muted` | unit | Matcha moderation fixture not available. |
| `bot.unmuted` | unit | Matcha moderation fixture not available. |
| `platform.specific` | adapter-live | Lifecycle/meta events observed; plugin run focused on message path. |
| Event category | Telegram | Discord | aiocqhttp | DingTalk |
|----------------|----------|---------|-----------|----------|
| `message.received` | plugin-e2e-ui | plugin-e2e-ui | plugin-e2e-ui and plugin-e2e-protocol | plugin-e2e-ui private |
| `message.edited` | implemented/unit, not plugin-e2e-ui | historical/direct only, not latest plugin-e2e | unit | not declared |
| `message.deleted` | implemented/unit, not plugin-e2e-ui | historical/direct only, not latest plugin-e2e | unit | not declared |
| `message.reaction` | implemented/unit, not plugin-e2e-ui | historical/direct only, not latest plugin-e2e | not-supported in standard OneBot message path | not declared |
| member join/left/ban | implemented/unit or blocked without disposable users | blocked without disposable users | unit; Matcha fixture unavailable | not declared |
| bot invited/removed | invite plugin-e2e-ui for Telegram; removal blocked | invite historical/plugin-series; removal blocked | unit; Matcha fixture unavailable | not declared |
| requests/friend events | not applicable | not applicable | unit; Matcha fixture unavailable | not declared |
| `platform.specific` | implemented; not latest plugin-e2e | not latest plugin-e2e | adapter lifecycle observed; plugin focus was message path | declared for fallback; not reproduced in UI run |
## Common API Acceptance
| API | Telegram | Discord | aiocqhttp |
|-----|----------|---------|-----------|
| `send_message` | plugin-e2e | plugin-e2e | plugin-e2e |
| `reply_message` | plugin-e2e via `Quote` send | plugin-e2e via `Quote` send | plugin-e2e via `Quote` send |
| `edit_message` | adapter-live historical | adapter-live historical | not-supported: OneBot v11 has no standard edit |
| `delete_message` | adapter-live historical | adapter-live historical | unit; Matcha destructive skipped |
| `forward_message` | plugin-e2e flattened forward | plugin-e2e flattened forward | blocked: Matcha lacks merged-forward action |
| `get_message` | not-supported in Telegram adapter | not-supported in Discord adapter | plugin-e2e |
| `get_group_info` | plugin-e2e | plugin-e2e | plugin-e2e |
| `get_group_list` | not-supported in Telegram adapter | not-supported in Discord adapter | plugin-e2e |
| `get_group_member_list` | plugin-e2e, administrators/member subset | plugin-e2e | plugin-e2e returned Matcha-supported shape |
| `get_group_member_info` | plugin-e2e | plugin-e2e | plugin-e2e |
| `set_group_name` | platform-specific only | not declared common | blocked: Matcha/admin fixture not used |
| `get_user_info` | plugin-e2e | plugin-e2e | plugin-e2e |
| `get_friend_list` | not-supported | not-supported | plugin-e2e returned `[]` |
| `upload_file` | not-supported | not-supported | not-supported |
| `get_file_url` | implemented; not reproduced in final plugin run | supported URL passthrough; covered by attachment send | not-supported portable common API |
| `mute_member` | blocked without disposable target | blocked without disposable target | blocked without disposable target |
| `unmute_member` | blocked without disposable target | blocked without disposable target | blocked without disposable target |
| `kick_member` | blocked/destructive | blocked/destructive | blocked/destructive |
| `leave_group` | blocked/destructive | blocked/destructive | blocked/destructive |
| `call_platform_api` | plugin-e2e safe Telegram actions | plugin-e2e safe Discord actions | plugin-e2e safe OneBot actions |
| API area | Telegram | Discord | aiocqhttp | DingTalk |
|----------|----------|---------|-----------|----------|
| send/reply | plugin-e2e-outbound | plugin-e2e-outbound | plugin-e2e-outbound, with Matcha file/forward gaps | plugin-e2e-outbound |
| edit/delete | historical/direct or unit; destructive/current UI not repeated | historical/direct; destructive/current UI not repeated | unit/destructive blocked | not declared or blocked |
| message lookup | not-supported | not-supported | plugin-e2e | inbound cache-backed where available; limited live coverage |
| group info/member info | plugin-e2e safe subset | plugin-e2e safe subset | plugin-e2e safe subset | private path only; group not completed |
| user/friend info | plugin-e2e where platform allows | plugin-e2e where platform allows | plugin-e2e | plugin-e2e private user |
| moderation/leave | blocked without disposable safe targets | blocked without disposable safe targets | blocked without disposable safe targets | blocked/not declared |
| `get_file_url` | implemented; not latest inbound-file verified | URL passthrough for attachments; inbound attachment not completed | not portable/endpoint-dependent | implemented through DingTalk media API; inbound file not completed |
| `call_platform_api` | plugin-e2e safe actions | plugin-e2e safe actions | plugin-e2e safe actions, Matcha gaps documented | plugin-e2e safe `check_access_token` |
## Platform-Specific API Acceptance
| Adapter | plugin-e2e verified | Blocked or not reproduced |
|---------|---------------------|---------------------------|
| Telegram | `get_chat_administrators`, `get_chat_member_count`, `send_chat_action` | `pin_message`, `unpin_message`, `unpin_all_messages`, `set_chat_title`, `set_chat_description`, `create_chat_invite_link`, `answer_callback_query` were not repeated in final plugin run because they are mutating or require callback fixtures. |
| Discord | `get_channel`, `typing`, `get_guild`, `get_guild_channels`, `get_guild_roles` | `create_invite`, `pin_message`, `unpin_message`, `add_reaction`, `remove_reaction` were verified by prior direct live run; final plugin run avoided extra mutation/reaction side effects. |
| aiocqhttp | `get_login_info`, `get_status`, `get_version_info`, `can_send_image`, `can_send_record` | `get_group_honor_info` blocked by Matcha unsupported action; admin/card/title/whole-ban/record/image/forward actions require endpoint support or destructive/admin fixtures. |
| Telegram | safe chat/admin/member count/chat-action actions | mutating actions and callback-only actions were not repeated; inbound file URL path lacks latest UI file evidence |
| Discord | safe channel/guild/role/typing actions | mutating pin/reaction/invite actions were not repeated in the latest plugin run; inbound attachment paths not completed |
| aiocqhttp | safe OneBot actions such as status/version/can-send checks | `get_group_honor_info` unsupported by Matcha; admin/card/title/ban/record/file/forward require better endpoint fixtures |
| DingTalk | `check_access_token` | media download/get-file-url actions need real inbound media IDs; group actions need a working group trigger |
## SDK API Acceptance
The EBA probe verified these SDK APIs through standalone runtime on all three platform runs:
`EBAEventProbe` exercised the standalone runtime path for:
- `get_langbot_version`
- `get_bots`
- `get_bot_info`
- `send_message`
- `call_platform_api`
- plugin storage set/get/list/delete
- workspace storage set/get/list/delete
- `list_plugins_manifest`
- `list_commands`
- `list_tools`
- `list_knowledge_bases`
- bot discovery and bot info lookup
- send message
- component sweep where enabled
- platform API sweep where enabled
- plugin storage
- workspace storage
- plugin/command/tool/knowledge-base list APIs
## Residual Risks
The probe logs set `ok=true` when the sweep completed with only expected unsupported/blocked items. Individual call details are stored in the JSONL evidence files.
- Full event-matrix coverage still needs disposable Telegram/Discord accounts and richer OneBot simulator fixtures for member join/leave/ban, reactions, edit/delete, and request flows.
- Destructive moderation APIs are implemented but intentionally not re-run against shared real groups/servers.
- Matcha is not a complete OneBot v11 endpoint; file and merged-forward failures are endpoint limitations, not accepted as adapter failures.
## Residual Risks And Required Follow-Up
- Telegram, Discord, and DingTalk still require real UI inbound image and file upload evidence before they can be called media-complete.
- aiocqhttp has rich inbound component evidence only at the OneBot reverse WebSocket boundary; Matcha UI did not provide image/file upload coverage.
- DingTalk group trigger remains unclosed; current evidence is private chat only.
- Discord UI retry on May 10, 2026 was blocked by the client keeping the send button disabled even after text was entered.
- Destructive moderation and leave APIs are intentionally blocked until disposable users/groups are available.
## Conclusion
Telegram, Discord, and aiocqhttp now have real standalone-runtime plugin E2E evidence for their core EBA migration path and safe API/component surfaces. The adapters are acceptable for the supported capabilities documented here. Items marked `blocked` require disposable users/groups or a more complete simulator before they can be claimed as fully verified.
The EBA conversion path is implemented and partially proven for all four adapters, but the current evidence does not support a claim that all platforms are fully end-to-end tested for real user-side image/file inbound. This report therefore treats the adapters as partial acceptance with explicit gaps, not production-complete media acceptance.

View File

@@ -142,11 +142,12 @@ Verified on May 10, 2026 with `EBAEventProbe`, SDK standalone runtime, LangBot `
Evidence:
- Plugin JSONL: `data/temp/aiocqhttp-plugin-e2e-rerun.jsonl`
- Plugin JSONL: `data/temp/aiocqhttp-plugin-e2e-20260510-multiformat.jsonl`
Observed and verified:
- A real Matcha group message reached the plugin as `MessageReceived` with `bot_uuid=eba-aiocqhttp-matcha`, `adapter_name=aiocqhttp`, common `Source`/`Plain` message components, common sender, and common group identifiers.
- A protocol-level OneBot reverse WebSocket event reached the plugin as `MessageReceived` with a mixed common chain: `Source`, `Plain`, `At`, `Face`, `Image`, `Voice`, `File`, `Quote`, and trailing `Plain`. This proves the real adapter + LangBot + standalone runtime + plugin path for mixed inbound OneBot payloads, but it was not sent through Matcha UI.
- SDK API calls succeeded: `get_langbot_version`, `get_bots`, `get_bot_info`, `send_message`, plugin storage, workspace storage, `list_plugins_manifest`, `list_commands`, `list_tools`, and `list_knowledge_bases`.
- Outbound component sweep succeeded for plain text plus `At`/`Face`, `AtAll`, base64 `Image`, and quoted reply.
- Common APIs succeeded through the plugin path: `get_message`, `get_user_info`, `get_friend_list`, `get_group_info`, `get_group_list`, `get_group_member_list`, and `get_group_member_info`.
@@ -154,6 +155,7 @@ Observed and verified:
Documented Matcha limits in this E2E run:
- Matcha UI did not provide a completed image/file upload/send path for inbound media. The rich inbound media evidence is `plugin-e2e-protocol`, not UI-level media upload evidence.
- Outbound `File` failed in Matcha even after the adapter emitted an official `file` segment shape.
- Outbound `Forward` failed because Matcha returned unsupported action for merged-forward.
- `get_group_honor_info` failed because Matcha returned unsupported action.

View File

@@ -0,0 +1,111 @@
# DingTalk EBA Adapter Migration Record
Status: migrated with partial plugin E2E evidence.
Adapter directory: `src/langbot/pkg/platform/adapters/dingtalk/`
## What Changed
The DingTalk adapter now has an Event-Based Agents adapter package with:
- `manifest.yaml` for adapter metadata, configuration, events, common APIs, and platform-specific APIs.
- `adapter.py` for DingTalk client startup, native callback handling, legacy compatibility, and EBA dispatch.
- `event_converter.py` for native DingTalk events to common EBA events.
- `message_converter.py` for DingTalk message payloads to/from common `MessageChain` components.
- `api_impl.py` for common EBA API implementations.
- `platform_api.py` for DingTalk-specific `call_platform_api` actions.
The legacy DingTalk HTTP client now returns successful JSON response bodies from proactive send methods and raises with response details on non-200 responses.
## Configuration
| Field | Required | Notes |
|-------|----------|-------|
| `client-id` | yes | DingTalk robot/client identifier. |
| `client-secret` | yes | DingTalk client secret. |
| `robot-code` | yes | Robot code used for send APIs. |
| `robot-name` | no | Used for bot mention/self filtering and display. |
| `encrypt-key` | no | DingTalk callback encryption key when configured. |
| `verification-token` | no | DingTalk callback verification token when configured. |
## Supported Events
| Event | Support | Evidence |
|-------|---------|----------|
| `message.received` | implemented | `plugin-e2e-ui` private text and emoji-as-text. |
| `platform.specific` | implemented | Not reproduced in the latest UI run. |
## Receive Components
| Component | Support | Evidence |
|-----------|---------|----------|
| `Source` | supported | `plugin-e2e-ui` private message. |
| `Plain` | supported | `plugin-e2e-ui` private text. DingTalk emoji currently arrives as plain text such as `[smile]`. |
| `At` | converter path | Group trigger was not completed in the latest run. |
| `AtAll` | fallback/send-side only | Not completed inbound. |
| `Image` | converter path | Real UI inbound image was not completed. |
| `Voice` | converter path | Real UI inbound voice was not completed. |
| `File` | converter path | Real UI inbound file was not completed. |
| `Quote` | converter path | Real UI inbound quote was not completed. |
| `Face` | not native common mapping | DingTalk emoji was observed as `Plain`, not `Face`. |
| `Forward` | not-supported inbound | DingTalk does not expose a portable structured forward event in this adapter. |
## Send Components
| Component | Support | Evidence |
|-----------|---------|----------|
| `Plain` | supported | `plugin-e2e-outbound`. |
| `At` | supported or text fallback | `plugin-e2e-outbound`. |
| `AtAll` | fallback | `plugin-e2e-outbound`. |
| `Image` | supported | `plugin-e2e-outbound`. |
| `File` | supported | `plugin-e2e-outbound`. |
| `Quote` | fallback | `plugin-e2e-outbound`. |
| `Face` | fallback | `plugin-e2e-outbound` as text fallback. |
| `Forward` | flattened fallback | `plugin-e2e-outbound`. |
| `Voice` | fallback/endpoint-dependent | Not separately verified as a native DingTalk voice send. |
## Common APIs
| API | Support | Notes |
|-----|---------|-------|
| `send_message` | supported | Verified through `EBAEventProbe`. |
| `reply_message` | supported | Verified through quoted/fallback send path. |
| `get_message` | cache-backed | Requires the message to have been observed by this adapter process. |
| `get_group_info` | cache-backed/API-backed where available | Group path not completed in latest UI run. |
| `get_group_list` | supported where DingTalk API allows | Limited live coverage. |
| `get_group_member_info` | supported where DingTalk API allows | Limited live coverage. |
| `get_user_info` | supported | Private sender path verified. |
| `get_friend_list` | limited | DingTalk does not expose a portable friend-list equivalent. |
| `get_file_url` | supported with media/file identifiers | Needs real inbound media evidence. |
| `call_platform_api` | supported | Safe action `check_access_token` verified. |
## Platform-Specific APIs
| Action | Support | Evidence |
|--------|---------|----------|
| `check_access_token` | supported | `plugin-e2e`. |
| `refresh_access_token` | supported | Implemented; not separately reproduced in the latest plugin run. |
| `get_file_url` | supported | Needs real inbound file/media ID. |
| `get_audio_base64` | supported | Needs real inbound audio/media ID. |
| `download_image_base64` | supported | Needs real inbound image/media ID. |
## End-to-End Evidence
Evidence file: `data/temp/dingtalk-plugin-e2e-20260510-rerun.jsonl`
Verified:
- DingTalk Mac private chat in the `LangBot Team` organization produced `MessageReceived` through LangBot standalone runtime and `EBAEventProbe`.
- The common chain was `Source + Plain` for normal text.
- DingTalk emoji was received as `Source + Plain`, not common `Face`.
- The plugin sent outbound text, mention/fallback, image, quote/fallback, file, and forward/fallback messages visible in DingTalk.
- The plugin called safe SDK and DingTalk platform APIs.
Not completed:
- Real UI inbound image.
- Real UI inbound file.
- Real UI inbound voice.
- Real UI inbound quote.
- Group trigger with a real robot mention.
- Destructive or organization-mutating APIs.

View File

@@ -139,6 +139,8 @@ Observed and verified:
Documented limits in this E2E run:
- Real Discord UI inbound attachment/image/file, reply/quote, and fresh mention-chain messages were not completed in the plugin E2E evidence. Outbound image/file attachments from the bot do not prove inbound attachment conversion.
- A later May 10 UI retry could write text into the Discord message box, but the client kept the send button disabled and did not send the message, so it produced no new plugin evidence.
- `get_message`, `get_friend_list`, and `get_group_list` are not supported by this Discord adapter.
- Destructive moderation and guild-leave APIs were not repeated against the shared LangBot server.
- Native Discord voice is not represented as common `Voice`; audio-like payloads are treated as file attachments.

View File

@@ -122,6 +122,7 @@ Observed and verified:
Documented limits in this E2E run:
- Real Telegram UI inbound image, file, voice, sticker/emoji-as-common-component, and reply/quote messages were not completed in the plugin E2E evidence. Outbound image/file messages from the bot do not prove inbound media conversion.
- `get_message`, `get_friend_list`, and `get_group_list` are not supported by this Telegram adapter.
- Mutating/destructive Telegram-specific actions such as pin/unpin, title/description changes, invite-link creation, moderation, kick, and leave were not repeated in the plugin run. They remain opt-in live-probe cases.
- Telegram does not expose a portable common `Face` component for native sticker/emoji semantics in the current adapter.