@xmanrui/dsh-im 4.0.1 → 4.1.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 +321 -0
- package/PROACTIVE_DELIVERY.md +321 -0
- package/README.en.md +11 -5
- package/README.md +11 -5
- package/lib/client.js +1190 -108
- package/lib/index.js +229 -229
- package/package.json +4 -1
- package/plugin-src/client/channel-card-meta.js +44 -0
- package/plugin-src/client/channels/dingtalk/index.js +16 -8
- package/plugin-src/client/channels/feishu/index.js +17 -9
- package/plugin-src/client/channels/qq/index.js +16 -8
- package/plugin-src/client/channels/shared/token-channel.js +16 -8
- package/plugin-src/client/channels/wecom/index.js +16 -8
- package/plugin-src/client/channels/weixin/index.js +16 -8
- package/plugin-src/client/channels/whatsapp/index.js +16 -8
- package/plugin-src/client/delivery-settings.js +749 -0
- package/plugin-src/client/i18n.js +82 -0
- package/plugin-src/client/index.js +46 -20
- package/plugin-src/client/styles.js +72 -0
- package/plugin-src/host/channels/dingtalk/index.mjs +3 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +4 -0
- package/plugin-src/host/channels/discord/index.mjs +6 -1
- package/plugin-src/host/channels/feishu/index.mjs +3 -0
- package/plugin-src/host/channels/feishu/production.mjs +4 -0
- package/plugin-src/host/channels/qq/index.mjs +6 -1
- package/plugin-src/host/channels/qq/production.mjs +2 -0
- package/plugin-src/host/channels/shared/production.mjs +7 -0
- package/plugin-src/host/channels/slack/index.mjs +6 -1
- package/plugin-src/host/channels/slack/production.mjs +4 -0
- package/plugin-src/host/channels/telegram/index.mjs +6 -1
- package/plugin-src/host/channels/wecom/index.mjs +6 -1
- package/plugin-src/host/channels/wecom/production.mjs +4 -0
- package/plugin-src/host/channels/weixin/index.mjs +3 -0
- package/plugin-src/host/channels/weixin/production.mjs +4 -0
- package/plugin-src/host/channels/whatsapp/index.mjs +6 -1
- package/plugin-src/host/channels/whatsapp/production.mjs +4 -0
- package/plugin-src/host/delivery-adapter.mjs +179 -0
- package/plugin-src/host/delivery-http.mjs +132 -0
- package/plugin-src/host/delivery-rpc.mjs +158 -0
- package/plugin-src/host/delivery-service.mjs +224 -0
- package/plugin-src/host/delivery-suggestions.mjs +135 -0
- package/plugin-src/host/index.mjs +32 -5
- package/scripts/verify-package.mjs +3 -0
- package/src/channels/dingtalk/dingtalk-api.mjs +33 -0
- package/src/channels/dingtalk/dingtalk-bridge.mjs +2 -2
- package/src/channels/dingtalk/dingtalk-controller.mjs +14 -0
- package/src/channels/dingtalk/dingtalk-runtime.mjs +39 -0
- package/src/channels/discord/discord-runtime.mjs +16 -0
- package/src/channels/feishu/bridge.mjs +4 -4
- package/src/channels/feishu/feishu-cards.mjs +4 -4
- package/src/channels/feishu/feishu-runtime.mjs +35 -0
- package/src/channels/feishu/multi-bot-controller.mjs +15 -0
- package/src/channels/qq/qq-bridge.mjs +2 -2
- package/src/channels/qq/qq-controller.mjs +14 -0
- package/src/channels/qq/qq-runtime.mjs +23 -0
- package/src/channels/shared/bot-workspace-store.mjs +192 -6
- package/src/channels/shared/i18n-en/feishu.mjs +6 -2
- package/src/channels/shared/i18n-en/shared-a.mjs +4 -0
- package/src/channels/shared/preset-command.mjs +2 -2
- package/src/channels/shared/text-harness-bridge.mjs +7 -2
- package/src/channels/shared/token-bot-controller.mjs +16 -0
- package/src/channels/shared/workspace-command.mjs +1 -1
- package/src/channels/slack/slack-controller.mjs +14 -0
- package/src/channels/slack/slack-runtime.mjs +24 -0
- package/src/channels/telegram/telegram-runtime.mjs +26 -0
- package/src/channels/wecom/state-store.mjs +4 -0
- package/src/channels/wecom/wecom-bridge.mjs +2 -2
- package/src/channels/wecom/wecom-controller.mjs +14 -0
- package/src/channels/wecom/wecom-runtime.mjs +21 -0
- package/src/channels/weixin/weixin-bridge.mjs +2 -2
- package/src/channels/weixin/weixin-controller.mjs +14 -0
- package/src/channels/weixin/weixin-runtime.mjs +24 -0
- package/src/channels/whatsapp/whatsapp-controller.mjs +14 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +19 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# Proactive Delivery Guide
|
|
2
|
+
|
|
3
|
+
[简体中文](PROACTIVE_DELIVERY.md) · **English**
|
|
4
|
+
|
|
5
|
+
Proactive delivery lets an application send a text message through a bot connected to DSH-IM without waiting for a new user message. The caller stores only a stable `botId + targetId` pair—never a Harness `sessionId`, chat reference, message ID, or temporary webhook.
|
|
6
|
+
|
|
7
|
+
All nine built-in channels support proactive delivery: Weixin, Feishu, DingTalk, WeCom, QQ, Slack, Telegram, Discord, and WhatsApp.
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
1. Open **Settings → IM Bot** and find the bot that should send the message.
|
|
12
|
+
2. Select the gear icon in the bot card's upper-right corner.
|
|
13
|
+
3. Copy the **Bot ID** under **Call identifiers**.
|
|
14
|
+
4. Select **New target**, then choose a known conversation or select **Enter manually (advanced)** and enter the platform-native ID.
|
|
15
|
+
5. Review or enter the **Target ID**, target type, and native platform ID.
|
|
16
|
+
6. Select **Test**. After the target receives `DSH-IM 主动投递测试成功。`, select **Save target**.
|
|
17
|
+
7. Select **Copy call parameters** on the saved target and store the resulting `{ botId, targetId }` in the calling application.
|
|
18
|
+
8. Send messages through HTTP POST, same-Host `ctx.dshIm.send()`, or the Connection RPC `message.send` endpoint.
|
|
19
|
+
|
|
20
|
+
## Configure a delivery target
|
|
21
|
+
|
|
22
|
+
### 1. Get the Bot ID
|
|
23
|
+
|
|
24
|
+
`botId` is the real call identifier of the currently connected bot. Copy it from the settings page and treat it as an opaque string. Do not infer the channel from its prefix, and do not substitute the bot name, a platform App ID, or a masked ID from a card.
|
|
25
|
+
|
|
26
|
+
Targets belong to this bot record. Removing a bot also removes its delivery targets. After connecting it again, copy its `botId` again and recreate the required targets.
|
|
27
|
+
|
|
28
|
+
### 2. Create a target
|
|
29
|
+
|
|
30
|
+
After selecting **New target**, the page opens a **Choose from conversations** dropdown:
|
|
31
|
+
|
|
32
|
+
- Suggestions come from conversation mappings already persisted for this bot. They are neither a platform address book nor a complete, strictly time-ordered recent-chat list.
|
|
33
|
+
- A suggestion contains only the target type and native platform ID required for delivery. It does not contain a Harness `sessionId`, message text, message ID, conversation name, or last-active time.
|
|
34
|
+
- A target already configured in the dropdown is marked **Added** and disabled.
|
|
35
|
+
- When the dropdown is empty, send the bot a message on that platform and select **Refresh**. If it still does not appear, use **Enter manually (advanced)**.
|
|
36
|
+
|
|
37
|
+
Choosing a conversation only pre-fills a draft. It does not save anything automatically.
|
|
38
|
+
|
|
39
|
+
### 3. Understand Target ID
|
|
40
|
+
|
|
41
|
+
`targetId` is the stable alias you define for callers. It is not a platform user, group, or channel ID.
|
|
42
|
+
|
|
43
|
+
- It must be unique only within one bot. Different bots may use the same `targetId`.
|
|
44
|
+
- It may contain uppercase and lowercase letters, numbers, dots, underscores, colons, `@`, or hyphens, with a length of 1–128 characters.
|
|
45
|
+
- New targets default to `tgt_` plus 16 random hexadecimal characters, such as `tgt_7f3a91c8d2e64b10`.
|
|
46
|
+
- You may change it before the first save—for example, to `daily-report` or `release-alerts`.
|
|
47
|
+
- After saving, `targetId` is read-only. You may still edit the name, target type, and native route while callers keep using the same `botId + targetId` pair.
|
|
48
|
+
|
|
49
|
+
### 4. Test, save, and copy
|
|
50
|
+
|
|
51
|
+
The **Test** button appears in conversation-filled drafts, manually entered drafts, and edit forms:
|
|
52
|
+
|
|
53
|
+
- It is enabled only while the bot is online and every native ID required by the current target type is present.
|
|
54
|
+
- It tests the target type and native ID currently shown in the form. It does not create, update, or save the target first.
|
|
55
|
+
- Changing a tested native ID clears the old success result; test the new value again.
|
|
56
|
+
- The **Test** button on a saved target row tests that target's currently saved route.
|
|
57
|
+
- A successful test means the platform accepted the send request or its SDK returned success. It does not mean the message was read.
|
|
58
|
+
|
|
59
|
+
After saving, **Copy call parameters** copies JSON in this shape:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"botId": "bot_9577c8572d454122a4ef7fb4d8420a91",
|
|
64
|
+
"targetId": "release-alerts"
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Configuration example: a Feishu alert group
|
|
69
|
+
|
|
70
|
+
Assume the Feishu bot has already received a message in the alert group:
|
|
71
|
+
|
|
72
|
+
1. Open that bot's settings page and copy its **Bot ID**.
|
|
73
|
+
2. Select **New target**, then choose the alert group from the dropdown.
|
|
74
|
+
3. Change the generated **Target ID** to `release-alerts` and set the display name to `Release alerts`.
|
|
75
|
+
4. Confirm that the target type is **Group** and the group Chat ID is filled in.
|
|
76
|
+
5. Select **Test** and confirm the test message in the Feishu group.
|
|
77
|
+
6. Select **Save target**, then **Copy call parameters**.
|
|
78
|
+
|
|
79
|
+
If the group Chat ID is edited later, callers can continue using the same `botId + release-alerts` pair.
|
|
80
|
+
|
|
81
|
+
## Native fields for all nine channels
|
|
82
|
+
|
|
83
|
+
Choose a known conversation whenever possible. Obtain and enter a platform-native ID manually only when the target is missing from the suggestions.
|
|
84
|
+
|
|
85
|
+
| Channel | Target type | Required field | Example or note |
|
|
86
|
+
| --- | --- | --- | --- |
|
|
87
|
+
| Weixin | `user` | Weixin user ID (`toUserId`) | Enter the user ID that should receive messages |
|
|
88
|
+
| Feishu | `user` | Open ID (`openId`) | For example, `ou_xxx` |
|
|
89
|
+
| Feishu | `group` | Group Chat ID (`chatId`) | For example, `oc_xxx` |
|
|
90
|
+
| DingTalk | `user` | User ID (`userId`) | Enter the DingTalk user ID |
|
|
91
|
+
| DingTalk | `group` | Group Open Conversation ID (`openConversationId`) | Proactive delivery never uses a temporary `sessionWebhook` |
|
|
92
|
+
| WeCom | `user` | User ID (route field: `chatId`) | Enter the user ID for a direct message |
|
|
93
|
+
| WeCom | `group` | Group Chat ID (`chatId`) | Enter the group's `chatid` |
|
|
94
|
+
| QQ | `user` | User Open ID (`userOpenId`) | The platform's `user_openid` |
|
|
95
|
+
| QQ | `group` | Group Open ID (`groupOpenId`) | The platform's `group_openid` |
|
|
96
|
+
| Slack | `conversation` | Channel ID (`channelId`) | For example, `C0123456789` |
|
|
97
|
+
| Slack | `thread` | Channel ID + thread timestamp (`channelId`, `threadTs`) | For example, `1712345678.123456` |
|
|
98
|
+
| Telegram | `chat` | Chat ID (`chatId`) | A decimal string, such as `-1001234567890` |
|
|
99
|
+
| Telegram | `topic` | Chat ID + Topic ID (`chatId`, `messageThreadId`) | Topic ID must be a positive integer |
|
|
100
|
+
| Discord | `channel` | Channel ID (`channelId`) | DMs, channels, and Threads all use a messageable Channel ID |
|
|
101
|
+
| WhatsApp | `user` | User JID (`jid`) | For example, `8613800000000@s.whatsapp.net` |
|
|
102
|
+
| WhatsApp | `group` | Group JID (`jid`) | For example, `1234567890-123456@g.us` |
|
|
103
|
+
|
|
104
|
+
Native ID strings must be nonempty and have no leading or trailing whitespace. A target accepts only the fields required by the selected channel and type; extra fields are rejected.
|
|
105
|
+
|
|
106
|
+
## Send through HTTP POST
|
|
107
|
+
|
|
108
|
+
An ordinary external application can call the Host's proactive-delivery endpoint directly:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
curl --request POST \
|
|
112
|
+
http://127.0.0.1:3080/api/dsh-im/delivery/messages \
|
|
113
|
+
--header 'Content-Type: application/json' \
|
|
114
|
+
--data '{
|
|
115
|
+
"botId": "bot_9577c8572d454122a4ef7fb4d8420a91",
|
|
116
|
+
"targetId": "release-alerts",
|
|
117
|
+
"text": "The build has completed."
|
|
118
|
+
}'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
A successful request returns:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{ "sent": true }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The body accepts exactly `botId`, `targetId`, and `text`, with a maximum total JSON size of 1 MiB. Do not add a native platform route, `sessionId`, `chatRef`, temporary webhook, or `idempotencyKey`.
|
|
128
|
+
|
|
129
|
+
The fixed endpoint is `POST /api/dsh-im/delivery/messages`. It reuses the current DSH Host WebServer and does not open another port. Port `3080` is the default for the Web profile; use the address printed by the running Host when it differs.
|
|
130
|
+
|
|
131
|
+
The HTTP endpoint currently has no authentication and does not provide CORS. Use it only on the local machine or a trusted network; never expose it directly to the public internet.
|
|
132
|
+
|
|
133
|
+
## Send from a plugin in the same Host
|
|
134
|
+
|
|
135
|
+
A consumer plugin can declare the `dshIm` injection and call the shared service directly without going through Connection RPC.
|
|
136
|
+
|
|
137
|
+
This minimal example sends one message when the plugin loads:
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
export const inject = ['dshIm'];
|
|
141
|
+
|
|
142
|
+
export async function apply(ctx) {
|
|
143
|
+
const result = await ctx.dshIm.send(
|
|
144
|
+
'bot_9577c8572d454122a4ef7fb4d8420a91',
|
|
145
|
+
'release-alerts',
|
|
146
|
+
'The build has completed.',
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (result.sent !== true) {
|
|
150
|
+
throw new Error('Proactive delivery did not return success');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
In a real plugin, call `ctx.dshIm.send()` from your existing scheduled job, build callback, or business-event handler. Its optional fourth argument currently supports an abort signal:
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
await ctx.dshIm.send(botId, targetId, text, { signal });
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
A same-Host plugin may also list the saved targets for one bot:
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
const targets = await ctx.dshIm.listTargets(botId);
|
|
165
|
+
// [{ targetId, name?, kind, route }, ...]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
On failure, the Promise rejects with an Error whose `code` is one of the public error codes below.
|
|
169
|
+
|
|
170
|
+
## Send through Connection RPC
|
|
171
|
+
|
|
172
|
+
Connection RPC is for a caller that already holds a `connection` client for the current DSH Host. The settings page also uses it to manage targets. Ordinary external applications should prefer the HTTP POST endpoint above.
|
|
173
|
+
|
|
174
|
+
First unwrap the RPC success and error envelopes:
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
const DELIVERY_CHANNEL = '/dsh-im-delivery';
|
|
178
|
+
|
|
179
|
+
async function callDelivery(connection, endpoint, payload, signal) {
|
|
180
|
+
const result = await connection.rpc.call(
|
|
181
|
+
DELIVERY_CHANNEL,
|
|
182
|
+
endpoint,
|
|
183
|
+
payload,
|
|
184
|
+
signal,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
if (result?.ok !== true) {
|
|
188
|
+
const error = new Error(result?.error?.message || 'delivery-failed');
|
|
189
|
+
error.code = result?.error?.code || 'delivery-failed';
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
return result.value;
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Then send text with the copied `botId + targetId` pair:
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
const result = await callDelivery(connection, 'message.send', {
|
|
200
|
+
botId: 'bot_9577c8572d454122a4ef7fb4d8420a91',
|
|
201
|
+
targetId: 'release-alerts',
|
|
202
|
+
text: 'The build has completed.',
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// result: { sent: true }
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`message.send` accepts exactly `{ botId, targetId, text }`. Do not add a native route, `sessionId`, `chatRef`, temporary webhook, or `idempotencyKey`.
|
|
209
|
+
|
|
210
|
+
### Example: deliver a daily report
|
|
211
|
+
|
|
212
|
+
```js
|
|
213
|
+
async function sendDailyReport(connection, summary) {
|
|
214
|
+
try {
|
|
215
|
+
await callDelivery(connection, 'message.send', {
|
|
216
|
+
botId: 'bot_9577c8572d454122a4ef7fb4d8420a91',
|
|
217
|
+
targetId: 'daily-report',
|
|
218
|
+
text: `Daily operations summary\n\n${summary}`,
|
|
219
|
+
});
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (error.code === 'bot-not-connected') {
|
|
222
|
+
// Let the application decide whether to retry after reconnection.
|
|
223
|
+
return { delivered: false, reason: 'offline' };
|
|
224
|
+
}
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
return { delivered: true };
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Management RPC reference
|
|
232
|
+
|
|
233
|
+
The settings page manages targets through the same Connection RPC channel. Most callers need only `message.send`; use the other endpoints only when the caller must manage targets itself.
|
|
234
|
+
|
|
235
|
+
Every response is either `{ ok: true, value }` or `{ ok: false, error: { code, message, details } }`.
|
|
236
|
+
|
|
237
|
+
| Endpoint | Payload | Successful `value` |
|
|
238
|
+
| --- | --- | --- |
|
|
239
|
+
| `message.send` | `{ botId, targetId, text }` | `{ sent: true }` |
|
|
240
|
+
| `target.list` | `{ botId }` | `{ botId, channel, targets }` |
|
|
241
|
+
| `target.suggestion.list` | `{ botId }` | `{ botId, channel, suggestions }` |
|
|
242
|
+
| `target.create` | `{ botId, target: { targetId, name?, kind, route } }` | The complete created target |
|
|
243
|
+
| `target.update` | `{ botId, targetId, target: { name?, kind, route } }` | The complete updated target |
|
|
244
|
+
| `target.delete` | `{ botId, targetId }` | `{ deleted: true }` |
|
|
245
|
+
| `target.test` | `{ botId, targetId }` | `{ sent: true }` |
|
|
246
|
+
| `target.test` | `{ botId, target: { kind, route } }` | `{ sent: true }`; tests a draft without saving it |
|
|
247
|
+
|
|
248
|
+
Payloads are validated with exact fields. The inner `target` in `target.update` must not contain `targetId`; a draft test must not contain `targetId` or `name`.
|
|
249
|
+
|
|
250
|
+
## Error handling
|
|
251
|
+
|
|
252
|
+
An HTTP failure returns `{ "error": { "code", "message", "details" } }`. Same-Host and RPC calls use the same error codes without an HTTP status.
|
|
253
|
+
|
|
254
|
+
| Error code | HTTP status | Meaning and suggested action |
|
|
255
|
+
| --- | --- | --- |
|
|
256
|
+
| `bad-request` | 400 | Invalid request shape, ID format, JSON, or text; check field names and remove extra fields |
|
|
257
|
+
| `unknown-bot` | 404 | The current Host does not own this `botId`; copy it again from bot settings |
|
|
258
|
+
| `unknown-target` | 404 | The bot has no such `targetId`; check the copied pair or whether the target was deleted |
|
|
259
|
+
| `target-conflict` | 409 | The same bot already has this `targetId`; choose another alias |
|
|
260
|
+
| `invalid-target` | 422 | The target type or native ID violates this channel's rules; select the correct type and verify the ID |
|
|
261
|
+
| `bot-not-connected` | 503 | The bot is offline; let the caller decide whether to retry after reconnection |
|
|
262
|
+
| `target-rejected` | 422 | The platform explicitly rejected the target or the bot lacks permission; check platform permissions and the target ID |
|
|
263
|
+
| `delivery-failed` | 502 | A network, platform, or other safely redacted delivery failure; check bot state and Host logs |
|
|
264
|
+
| `cancelled` | 408 | The call was cancelled; stop or start a new call as required by the application |
|
|
265
|
+
|
|
266
|
+
The HTTP protocol layer may also return `method-not-allowed` (405), `unsupported-media-type` (415), or `payload-too-large` (413).
|
|
267
|
+
|
|
268
|
+
## Delivery semantics and limits
|
|
269
|
+
|
|
270
|
+
- Proactive delivery currently accepts nonempty text only. This API does not send images, files, cards, or rich content.
|
|
271
|
+
- The maximum HTTP JSON request body is 1 MiB.
|
|
272
|
+
- `{ sent: true }` means the platform accepted the send request or its SDK returned success. It does not guarantee final delivery or a read receipt.
|
|
273
|
+
- DSH-IM stores no proactive-delivery history, generates no `deliveryHandle` or `idempotencyKey`, and performs no automatic retry.
|
|
274
|
+
- Retrying after a caller timeout can create duplicate messages. When business idempotency matters, the caller must store its own event ID and processing result.
|
|
275
|
+
- Normal delivery uses only a saved `botId + targetId`. Keep the native route in target configuration instead of sending it with every message.
|
|
276
|
+
- One call sends to one target. Notify multiple targets with separate calls and handle each result separately.
|
|
277
|
+
- Targets remain editable while a bot is offline, but testing and delivery require a connected bot.
|
|
278
|
+
|
|
279
|
+
## HTTP and RPC reachability
|
|
280
|
+
|
|
281
|
+
The HTTP endpoint is registered only when the current Host provides a WebServer, and it uses that server's existing listen address and port. A Web profile normally defaults to `127.0.0.1:3080`, which is reachable only from the same machine. To call it from another machine, bind the WebServer to a reachable address in that profile's `cordis.patch.yml`, then restart the Host. For example:
|
|
282
|
+
|
|
283
|
+
```yaml
|
|
284
|
+
- id: webserver
|
|
285
|
+
config:
|
|
286
|
+
host: '0.0.0.0'
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
This also expands network reachability for the other pages and routes on that WebServer. Because the proactive-delivery HTTP endpoint currently has no authentication, use it only with a trusted LAN, firewall, or reverse proxy, and never expose it directly to the public internet.
|
|
290
|
+
|
|
291
|
+
Connection RPC accepts loopback callers by default. If a Web profile is deliberately served on a trusted LAN, it can reuse the existing Host authority in that profile's `cordis.patch.yml`:
|
|
292
|
+
|
|
293
|
+
```yaml
|
|
294
|
+
- id: xmanrui-dsh-im
|
|
295
|
+
config:
|
|
296
|
+
rpcAuthority: trusted-host
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`trusted-host` is only a Host/Origin reachability boundary, not user authentication. Callers that can reach that trusted-network authority can also access bot-management endpoints. Enable it only on a trusted network.
|
|
300
|
+
|
|
301
|
+
## Troubleshooting
|
|
302
|
+
|
|
303
|
+
### The target conversation is missing from the dropdown
|
|
304
|
+
|
|
305
|
+
Send that bot a message on the platform, return to settings, and refresh. Suggestions are not a complete platform conversation directory. Use **Enter manually (advanced)** if the target still does not appear.
|
|
306
|
+
|
|
307
|
+
### The Test button is disabled
|
|
308
|
+
|
|
309
|
+
Make sure the bot is online and every native ID required by the current target type is present. A Slack Thread and a Telegram Topic both require two fields.
|
|
310
|
+
|
|
311
|
+
### Can Target ID be changed after saving?
|
|
312
|
+
|
|
313
|
+
No. You can edit its name, type, and native route without changing call parameters. If the alias itself must change, create a new target, migrate callers, and then delete the old target.
|
|
314
|
+
|
|
315
|
+
### Why not use sessionId?
|
|
316
|
+
|
|
317
|
+
A `sessionId` identifies a Harness Session. It is not a uniform, stable message address across the nine platforms. Proactive delivery uses the stable bot and saved-target pair instead.
|
|
318
|
+
|
|
319
|
+
### The test succeeded, but the recipient cannot see the message
|
|
320
|
+
|
|
321
|
+
A successful test proves only that the platform accepted the send. Check bot permissions, platform restrictions, target accuracy, and client-side filtering or archive settings.
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# 主动投递使用指南
|
|
2
|
+
|
|
3
|
+
**简体中文** · [English](PROACTIVE_DELIVERY.en.md)
|
|
4
|
+
|
|
5
|
+
主动投递让应用在没有用户新消息的情况下,通过已经接入 DSH-IM 的机器人发送文字消息。调用方只需保存一组稳定的 `botId + targetId`,不需要保存 Harness `sessionId`、聊天引用、消息 ID 或临时 Webhook。
|
|
6
|
+
|
|
7
|
+
九个内置渠道均支持主动投递:微信、飞书、钉钉、企业微信、QQ、Slack、Telegram、Discord 和 WhatsApp。
|
|
8
|
+
|
|
9
|
+
## 快速开始
|
|
10
|
+
|
|
11
|
+
1. 打开「设置 → IM机器人」,找到需要发消息的机器人。
|
|
12
|
+
2. 点击机器人卡片右上角的齿轮图标。
|
|
13
|
+
3. 在「调用标识」中复制 `Bot ID`。
|
|
14
|
+
4. 点击「新建目标」,从已聊会话中选择,或点击「手动填写(高级)」填写平台原生 ID。
|
|
15
|
+
5. 填写或确认 `Target ID`、目标类型和平台原生 ID。
|
|
16
|
+
6. 点击「测试」。目标收到 `DSH-IM 主动投递测试成功。` 后,再点击「保存目标」。
|
|
17
|
+
7. 在已保存目标上点击「复制调用参数」,得到可供应用保存的 `{ botId, targetId }`。
|
|
18
|
+
8. 使用 HTTP POST、同 Host 的 `ctx.dshIm.send()` 或 Connection RPC 的 `message.send` 发送消息。
|
|
19
|
+
|
|
20
|
+
## 配置投递目标
|
|
21
|
+
|
|
22
|
+
### 1. 获取 Bot ID
|
|
23
|
+
|
|
24
|
+
`botId` 是当前已接入机器人的真实调用标识。请从设置页复制并将它视为不透明字符串,不要根据前缀推断渠道,也不要使用机器人名称、平台 App ID 或卡片中的脱敏 ID 代替。
|
|
25
|
+
|
|
26
|
+
目标配置跟随当前机器人保存。移除机器人时,它的投递目标也会被清理;重新接入后应重新复制 `botId` 并配置目标。
|
|
27
|
+
|
|
28
|
+
### 2. 新建目标
|
|
29
|
+
|
|
30
|
+
点击「新建目标」后,默认显示「从已聊过的会话选择」下拉框:
|
|
31
|
+
|
|
32
|
+
- 候选来自该机器人已经持久化的会话映射,不是平台通讯录,也不是严格按时间排序的完整最近会话列表。
|
|
33
|
+
- 候选只包含投递所需的目标类型和平台原生 ID,不包含 Harness `sessionId`、消息正文、消息 ID、会话名称或最后活跃时间。
|
|
34
|
+
- 已配置的相同目标会显示「已添加」并禁用。
|
|
35
|
+
- 没有候选时,先在对应平台与机器人聊一条消息,再点击「刷新」。仍未出现时可使用「手动填写(高级)」。
|
|
36
|
+
|
|
37
|
+
选择候选只会预填表单,不会自动保存。
|
|
38
|
+
|
|
39
|
+
### 3. 理解 Target ID
|
|
40
|
+
|
|
41
|
+
`targetId` 是你为调用方定义的稳定别名,不是平台用户 ID、群 ID 或频道 ID。
|
|
42
|
+
|
|
43
|
+
- 它只需在同一个机器人下唯一;不同机器人可以使用相同的 `targetId`。
|
|
44
|
+
- 可使用大小写字母、数字、点、下划线、冒号、`@` 或连字符,长度为 1–128 个字符。
|
|
45
|
+
- 新建时页面默认生成 `tgt_` 加 16 位十六进制随机串,例如 `tgt_7f3a91c8d2e64b10`。
|
|
46
|
+
- 首次保存前可以修改,例如改成 `daily-report` 或 `release-alerts`。
|
|
47
|
+
- 保存后 `targetId` 不可修改,但名称、目标类型和平台原生路由可以修改;调用方仍使用原来的 `botId + targetId`。
|
|
48
|
+
|
|
49
|
+
### 4. 测试、保存和复制
|
|
50
|
+
|
|
51
|
+
从会话新建、手动新建和编辑目标时,表单底部都会显示「测试」按钮:
|
|
52
|
+
|
|
53
|
+
- 机器人在线且当前目标所需的平台 ID 已填写完整时,测试按钮才可用。
|
|
54
|
+
- 测试使用当前表单中的目标类型和平台 ID,不会先创建、更新或保存目标。
|
|
55
|
+
- 修改测试过的平台 ID 后,旧的成功提示会清除,需要重新测试。
|
|
56
|
+
- 已保存目标列表中的「测试」按钮测试该目标当前保存的路由。
|
|
57
|
+
- 测试成功表示平台发送接口已接受请求或 SDK 成功返回,不代表消息已经被阅读。
|
|
58
|
+
|
|
59
|
+
保存后点击「复制调用参数」,页面会复制如下 JSON:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"botId": "bot_9577c8572d454122a4ef7fb4d8420a91",
|
|
64
|
+
"targetId": "release-alerts"
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 配置示例:飞书告警群
|
|
69
|
+
|
|
70
|
+
假设飞书机器人已经在告警群中收到过消息:
|
|
71
|
+
|
|
72
|
+
1. 打开该机器人的设置页并复制 `Bot ID`。
|
|
73
|
+
2. 点击「新建目标」,在下拉框中选择告警群。
|
|
74
|
+
3. 将自动生成的 `Target ID` 改为 `release-alerts`,显示名称填为「发布告警群」。
|
|
75
|
+
4. 确认目标类型为「群聊」,群 Chat ID 已自动填入。
|
|
76
|
+
5. 点击「测试」,到飞书群中确认测试消息。
|
|
77
|
+
6. 点击「保存目标」,再点击「复制调用参数」。
|
|
78
|
+
|
|
79
|
+
以后即使编辑并更换群 Chat ID,调用方仍可继续使用同一组 `botId + release-alerts`。
|
|
80
|
+
|
|
81
|
+
## 九渠道手动填写字段
|
|
82
|
+
|
|
83
|
+
优先从已聊会话中选择。只有目标未出现在候选中时,才需要手动取得以下平台原生 ID。
|
|
84
|
+
|
|
85
|
+
| 渠道 | 目标类型 | 需要填写的字段 | 示例或说明 |
|
|
86
|
+
| --- | --- | --- | --- |
|
|
87
|
+
| 微信 | `user` | 微信用户 ID(`toUserId`) | 填写接收消息的微信用户 ID |
|
|
88
|
+
| 飞书 | `user` | Open ID(`openId`) | 例如 `ou_xxx` |
|
|
89
|
+
| 飞书 | `group` | 群 Chat ID(`chatId`) | 例如 `oc_xxx` |
|
|
90
|
+
| 钉钉 | `user` | 用户 ID(`userId`) | 填写钉钉用户 ID |
|
|
91
|
+
| 钉钉 | `group` | 群 Open Conversation ID(`openConversationId`) | 主动投递不使用临时 `sessionWebhook` |
|
|
92
|
+
| 企业微信 | `user` | 用户 ID(路由字段为 `chatId`) | 私聊填写用户 ID |
|
|
93
|
+
| 企业微信 | `group` | 群 Chat ID(`chatId`) | 群聊填写群 `chatid` |
|
|
94
|
+
| QQ | `user` | 用户 Open ID(`userOpenId`) | 平台提供的 `user_openid` |
|
|
95
|
+
| QQ | `group` | 群 Open ID(`groupOpenId`) | 平台提供的 `group_openid` |
|
|
96
|
+
| Slack | `conversation` | Channel ID(`channelId`) | 例如 `C0123456789` |
|
|
97
|
+
| Slack | `thread` | Channel ID + Thread 时间戳(`channelId`, `threadTs`) | 例如 `1712345678.123456` |
|
|
98
|
+
| Telegram | `chat` | Chat ID(`chatId`) | 十进制字符串,例如 `-1001234567890` |
|
|
99
|
+
| Telegram | `topic` | Chat ID + Topic ID(`chatId`, `messageThreadId`) | Topic ID 必须是正整数 |
|
|
100
|
+
| Discord | `channel` | Channel ID(`channelId`) | 私信、频道和 Thread 都使用可发消息的 Channel ID |
|
|
101
|
+
| WhatsApp | `user` | 用户 JID(`jid`) | 例如 `8613800000000@s.whatsapp.net` |
|
|
102
|
+
| WhatsApp | `group` | 群 JID(`jid`) | 例如 `1234567890-123456@g.us` |
|
|
103
|
+
|
|
104
|
+
平台 ID 字符串不能为空或带首尾空格。一个目标只接受所选渠道和类型要求的字段,额外字段会被拒绝。
|
|
105
|
+
|
|
106
|
+
## 通过 HTTP POST 发送
|
|
107
|
+
|
|
108
|
+
普通外部应用可以直接调用 Host 的主动投递接口:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
curl --request POST \
|
|
112
|
+
http://127.0.0.1:3080/api/dsh-im/delivery/messages \
|
|
113
|
+
--header 'Content-Type: application/json' \
|
|
114
|
+
--data '{
|
|
115
|
+
"botId": "bot_9577c8572d454122a4ef7fb4d8420a91",
|
|
116
|
+
"targetId": "release-alerts",
|
|
117
|
+
"text": "构建已经完成。"
|
|
118
|
+
}'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
成功返回:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{ "sent": true }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
请求体严格只接受 `botId`、`targetId` 和 `text`,JSON 总大小不能超过 1 MiB。不要附加平台原生路由、`sessionId`、`chatRef`、临时 Webhook 或 `idempotencyKey`。
|
|
128
|
+
|
|
129
|
+
接口路径固定为 `POST /api/dsh-im/delivery/messages`,复用当前 DSH Host 的 WebServer,不会另开端口。示例中的 `3080` 是 Web profile 的默认端口;实际地址以 Host 启动时显示的地址为准。
|
|
130
|
+
|
|
131
|
+
当前 HTTP 接口不包含鉴权,也不提供 CORS。只应在本机或可信网络中使用,不要直接暴露到公网。
|
|
132
|
+
|
|
133
|
+
## 在同一 Host 的插件中发送
|
|
134
|
+
|
|
135
|
+
消费插件声明 `dshIm` 注入后,可以直接调用共享服务,不经过 Connection RPC。
|
|
136
|
+
|
|
137
|
+
下面是一个最小示例;插件加载时会发送一次消息:
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
export const inject = ['dshIm'];
|
|
141
|
+
|
|
142
|
+
export async function apply(ctx) {
|
|
143
|
+
const result = await ctx.dshIm.send(
|
|
144
|
+
'bot_9577c8572d454122a4ef7fb4d8420a91',
|
|
145
|
+
'release-alerts',
|
|
146
|
+
'构建已经完成。',
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (result.sent !== true) {
|
|
150
|
+
throw new Error('主动投递没有返回成功结果');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
实际使用时,把 `ctx.dshIm.send()` 放进你的定时任务、构建回调或业务事件处理函数中。可选的第四个参数当前支持取消信号:
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
await ctx.dshIm.send(botId, targetId, text, { signal });
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
同 Host 插件也可以列出某个机器人的已保存目标:
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
const targets = await ctx.dshIm.listTargets(botId);
|
|
165
|
+
// [{ targetId, name?, kind, route }, ...]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
调用失败时 Promise 会拒绝,`error.code` 使用本文后面的公共错误码。
|
|
169
|
+
|
|
170
|
+
## 通过 Connection RPC 发送
|
|
171
|
+
|
|
172
|
+
Connection RPC 适合已经持有当前 DSH Host `connection` 客户端的调用方,也是设置页管理目标所使用的接口。普通外部应用优先使用上面的 HTTP POST。
|
|
173
|
+
|
|
174
|
+
先封装 RPC 成功与错误包络:
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
const DELIVERY_CHANNEL = '/dsh-im-delivery';
|
|
178
|
+
|
|
179
|
+
async function callDelivery(connection, endpoint, payload, signal) {
|
|
180
|
+
const result = await connection.rpc.call(
|
|
181
|
+
DELIVERY_CHANNEL,
|
|
182
|
+
endpoint,
|
|
183
|
+
payload,
|
|
184
|
+
signal,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
if (result?.ok !== true) {
|
|
188
|
+
const error = new Error(result?.error?.message || 'delivery-failed');
|
|
189
|
+
error.code = result?.error?.code || 'delivery-failed';
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
return result.value;
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
然后使用已复制的 `botId + targetId` 发送文字:
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
const result = await callDelivery(connection, 'message.send', {
|
|
200
|
+
botId: 'bot_9577c8572d454122a4ef7fb4d8420a91',
|
|
201
|
+
targetId: 'release-alerts',
|
|
202
|
+
text: '构建已经完成。',
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// result: { sent: true }
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`message.send` 只接受 `{ botId, targetId, text }`。不要附加平台路由、`sessionId`、`chatRef`、临时 Webhook 或 `idempotencyKey`。
|
|
209
|
+
|
|
210
|
+
### 示例:投递每日报告
|
|
211
|
+
|
|
212
|
+
```js
|
|
213
|
+
async function sendDailyReport(connection, summary) {
|
|
214
|
+
try {
|
|
215
|
+
await callDelivery(connection, 'message.send', {
|
|
216
|
+
botId: 'bot_9577c8572d454122a4ef7fb4d8420a91',
|
|
217
|
+
targetId: 'daily-report',
|
|
218
|
+
text: `今日运行摘要\n\n${summary}`,
|
|
219
|
+
});
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (error.code === 'bot-not-connected') {
|
|
222
|
+
// 等待机器人恢复连接后,由业务决定是否重试。
|
|
223
|
+
return { delivered: false, reason: 'offline' };
|
|
224
|
+
}
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
return { delivered: true };
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## 管理 RPC 参考
|
|
232
|
+
|
|
233
|
+
设置页使用同一个 Connection RPC 通道管理目标。普通调用方通常只需要 `message.send`;需要自行管理目标时再使用其他端点。
|
|
234
|
+
|
|
235
|
+
所有响应均为 `{ ok: true, value }` 或 `{ ok: false, error: { code, message, details } }`。
|
|
236
|
+
|
|
237
|
+
| 端点 | Payload | 成功时的 `value` |
|
|
238
|
+
| --- | --- | --- |
|
|
239
|
+
| `message.send` | `{ botId, targetId, text }` | `{ sent: true }` |
|
|
240
|
+
| `target.list` | `{ botId }` | `{ botId, channel, targets }` |
|
|
241
|
+
| `target.suggestion.list` | `{ botId }` | `{ botId, channel, suggestions }` |
|
|
242
|
+
| `target.create` | `{ botId, target: { targetId, name?, kind, route } }` | 已创建的完整目标 |
|
|
243
|
+
| `target.update` | `{ botId, targetId, target: { name?, kind, route } }` | 更新后的完整目标 |
|
|
244
|
+
| `target.delete` | `{ botId, targetId }` | `{ deleted: true }` |
|
|
245
|
+
| `target.test` | `{ botId, targetId }` | `{ sent: true }` |
|
|
246
|
+
| `target.test` | `{ botId, target: { kind, route } }` | `{ sent: true }`;测试草稿,不保存 |
|
|
247
|
+
|
|
248
|
+
接口严格校验字段。`target.update` 的内部 `target` 不能包含 `targetId`;草稿测试不能包含 `targetId` 或 `name`。
|
|
249
|
+
|
|
250
|
+
## 错误处理
|
|
251
|
+
|
|
252
|
+
HTTP 失败响应格式为 `{ "error": { "code", "message", "details" } }`。同 Host 和 RPC 使用相同错误码,但没有 HTTP 状态码。
|
|
253
|
+
|
|
254
|
+
| 错误码 | HTTP 状态 | 含义与处理建议 |
|
|
255
|
+
| --- | --- | --- |
|
|
256
|
+
| `bad-request` | 400 | 请求结构、ID 格式、JSON 或文字无效;检查字段名并移除额外字段 |
|
|
257
|
+
| `unknown-bot` | 404 | `botId` 不属于当前 Host;重新从机器人设置页复制 |
|
|
258
|
+
| `unknown-target` | 404 | 该机器人下不存在 `targetId`;检查是否复制错误或目标已被删除 |
|
|
259
|
+
| `target-conflict` | 409 | 同一机器人下已经存在相同 `targetId`;更换别名 |
|
|
260
|
+
| `invalid-target` | 422 | 目标类型或平台原生 ID 不符合当前渠道规则;重新选择类型并核对 ID |
|
|
261
|
+
| `bot-not-connected` | 503 | 机器人当前离线;恢复连接后由调用方决定是否重试 |
|
|
262
|
+
| `target-rejected` | 422 | 平台明确拒绝目标或机器人缺少发送权限;检查平台权限和目标 ID |
|
|
263
|
+
| `delivery-failed` | 502 | 网络、平台或其他无法安全细分的发送失败;检查连接状态和 Host 日志 |
|
|
264
|
+
| `cancelled` | 408 | 调用被取消;按业务需要结束或重新发起 |
|
|
265
|
+
|
|
266
|
+
HTTP 协议层还可能返回 `method-not-allowed`(405)、`unsupported-media-type`(415)或 `payload-too-large`(413)。
|
|
267
|
+
|
|
268
|
+
## 投递语义与限制
|
|
269
|
+
|
|
270
|
+
- 当前主动投递只发送非空文字,不支持在该接口中发送图片、文件、卡片或富文本。
|
|
271
|
+
- HTTP JSON 请求体上限为 1 MiB。
|
|
272
|
+
- `{ sent: true }` 表示平台发送接口接受请求或 SDK 成功返回,不承诺最终送达或已读。
|
|
273
|
+
- DSH-IM 不保存主动投递历史,不生成 `deliveryHandle` 或 `idempotencyKey`,也不自动重试。
|
|
274
|
+
- 调用方超时后重试可能产生重复消息;需要业务幂等时,由调用方保存自己的业务事件 ID 和处理结果。
|
|
275
|
+
- 正式发送只使用已保存的 `botId + targetId`。平台原生路由保存在目标配置中,不应随每次消息发送。
|
|
276
|
+
- 一个调用只发送到一个目标。需要通知多个目标时,应分别调用并分别处理结果。
|
|
277
|
+
- 机器人离线时仍可编辑目标,但不能测试或主动发送。
|
|
278
|
+
|
|
279
|
+
## HTTP 与 RPC 可达范围
|
|
280
|
+
|
|
281
|
+
HTTP 接口只在当前 Host 提供 WebServer 时注册,并使用同一个监听地址和端口。默认 Web profile 地址通常是 `127.0.0.1:3080`,只能由本机访问。若要从其他机器调用,需要在对应 profile 的 `cordis.patch.yml` 中把 WebServer 绑定到可达地址并重启 Host,例如:
|
|
282
|
+
|
|
283
|
+
```yaml
|
|
284
|
+
- id: webserver
|
|
285
|
+
config:
|
|
286
|
+
host: '0.0.0.0'
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
这会同时扩大该 WebServer 上其他页面和路由的网络可达范围。当前主动投递 HTTP 接口没有鉴权,因此只能配合可信局域网、防火墙或反向代理使用,不能直接暴露到公网。
|
|
290
|
+
|
|
291
|
+
Connection RPC 默认只允许当前 Host 的回环调用。若 Web profile 明确运行在受信任局域网,可在该 profile 的 `cordis.patch.yml` 中复用现有 Host authority:
|
|
292
|
+
|
|
293
|
+
```yaml
|
|
294
|
+
- id: xmanrui-dsh-im
|
|
295
|
+
config:
|
|
296
|
+
rpcAuthority: trusted-host
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`trusted-host` 只是 Host/Origin 可达性边界,不是用户认证。启用后,能访问该受信网络 authority 的调用方也能访问机器人管理接口;只应在可信网络中使用。
|
|
300
|
+
|
|
301
|
+
## 常见问题
|
|
302
|
+
|
|
303
|
+
### 下拉框找不到目标会话
|
|
304
|
+
|
|
305
|
+
先在对应平台向该机器人发送一条消息,再返回设置页刷新。候选不是平台的完整会话目录;仍然找不到时使用「手动填写(高级)」。
|
|
306
|
+
|
|
307
|
+
### 测试按钮不可点击
|
|
308
|
+
|
|
309
|
+
确认机器人在线,并填写当前目标类型要求的全部平台原生 ID。Slack Thread 和 Telegram Topic 都需要两个字段。
|
|
310
|
+
|
|
311
|
+
### 保存后能否修改 Target ID
|
|
312
|
+
|
|
313
|
+
不能。你可以编辑名称、类型和平台路由而保持调用参数不变;若必须更换 `targetId`,请新建目标并让调用方切换后再删除旧目标。
|
|
314
|
+
|
|
315
|
+
### 为什么不用 sessionId
|
|
316
|
+
|
|
317
|
+
`sessionId` 标识 Harness 会话,不是九个平台统一、稳定的消息投递地址。主动投递只使用机器人和已保存目标的稳定组合。
|
|
318
|
+
|
|
319
|
+
### 测试成功但对方没有看到消息
|
|
320
|
+
|
|
321
|
+
测试成功只证明平台接口接受发送。请继续检查机器人权限、平台限制、目标是否正确,以及客户端侧的消息过滤或归档设置。
|