@openclaw/discord 2026.5.2 → 2026.5.3-beta.2
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/dist/access-B9ujuUtS.js +89 -0
- package/dist/account-inspect-C2UEUhbG.js +81 -0
- package/dist/account-inspect-api.js +10 -0
- package/dist/accounts-BKnkNaoA.js +128 -0
- package/dist/action-runtime-api.js +2 -0
- package/dist/agent-components.runtime-DUhLr9hy.js +4 -0
- package/dist/allow-list-ek-1hMKN.js +336 -0
- package/dist/api-DzNBVTto.js +130 -0
- package/dist/api.js +24 -0
- package/dist/approval-handler.runtime-v8nzQHlT.js +426 -0
- package/dist/approval-native-DqWGp0bM.js +153 -0
- package/dist/approval-shared-DKnwwjZM.js +93 -0
- package/dist/audit-CJ92YD6J.js +102 -0
- package/dist/channel-B3aTtBj1.js +745 -0
- package/dist/channel-access-ewDxhd9q.js +62 -0
- package/dist/channel-actions-TNih7k3w.js +140 -0
- package/dist/channel-actions.runtime-CaPytiY4.js +236 -0
- package/dist/channel-api-CTSWMrnD.js +21 -0
- package/dist/channel-config-api.js +2 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.setup-Dt4tIDrl.js +336 -0
- package/dist/components-BapWDmDM.js +760 -0
- package/dist/config-api-CFZtoMaS.js +2 -0
- package/dist/config-schema-DwFkL904.js +252 -0
- package/dist/configured-state.js +6 -0
- package/dist/contract-api.js +8 -0
- package/dist/conversation-identity-BN9wSmxJ.js +31 -0
- package/dist/directory-cache-D93eSrpB.js +62 -0
- package/dist/directory-config-LyMP0sdv.js +49 -0
- package/dist/directory-contract-api.js +2 -0
- package/dist/directory-live-BQapdpkZ.js +101 -0
- package/dist/discord-D1kDh0X_.js +2751 -0
- package/dist/doctor-B2G7WqO0.js +244 -0
- package/dist/doctor-contract-D3pSutkb.js +383 -0
- package/dist/doctor-contract-api.js +2 -0
- package/dist/doctor-shared-DU8RcnF5.js +4 -0
- package/dist/format-D8TsaXxW.js +24 -0
- package/dist/gateway-registry-BKG4KIVC.js +74 -0
- package/dist/handle-action.guild-admin-BuqsSVXu.js +283 -0
- package/dist/inbound-context-e_oBBJtF.js +51 -0
- package/dist/index.js +26 -0
- package/dist/manager.runtime-M2aAa7qA.js +1019 -0
- package/dist/mentions-BPZUaFk7.js +88 -0
- package/dist/message-handler-D6JfFV3P.js +381 -0
- package/dist/message-handler.preflight-DqaF3vHm.js +1022 -0
- package/dist/message-handler.process-tl3Nwnhr.js +1124 -0
- package/dist/message-utils-Dmgu-7fC.js +512 -0
- package/dist/normalize-B-ktw-T_.js +275 -0
- package/dist/outbound-adapter-DJf9_sfH.js +451 -0
- package/dist/outbound-session-route-uHGLDP-Y.js +43 -0
- package/dist/pluralkit-voQvSN3g.js +22 -0
- package/dist/preflight-audio-BpYtUAT6.js +72 -0
- package/dist/preflight-audio.runtime-BAGmU6uO.js +7 -0
- package/dist/preview-streaming-C0O92Qqz.js +14 -0
- package/dist/probe-DcNEodPI.js +139 -0
- package/dist/probe.runtime-P-e4r1Hl.js +2 -0
- package/dist/provider-CMvXOp-3.js +8440 -0
- package/dist/provider-session.runtime-JFemrDZT.js +6 -0
- package/dist/provider.runtime-BO007oR2.js +2 -0
- package/dist/reply-delivery-D9So77a6.js +131 -0
- package/dist/resolve-allowlist-common-DqqFY_qa.js +34 -0
- package/dist/resolve-channels-CGPntufJ.js +265 -0
- package/dist/resolve-users-CDvSlW0V.js +120 -0
- package/dist/rolldown-runtime-C3SqQTfK.js +28 -0
- package/dist/route-resolution-BYiC-6Cc.js +236 -0
- package/dist/runtime-K9RT6Egn.js +8 -0
- package/dist/runtime-api.actions.js +3 -0
- package/dist/runtime-api.js +31 -0
- package/dist/runtime-api.lookup.js +7 -0
- package/dist/runtime-api.monitor-BC-XN0tY.js +6 -0
- package/dist/runtime-api.monitor.js +9 -0
- package/dist/runtime-api.send.js +6 -0
- package/dist/runtime-api.threads.js +6 -0
- package/dist/runtime-n5xZHW55.js +1001 -0
- package/dist/runtime-setter-api.js +2 -0
- package/dist/secret-config-contract-CoGryS5c.js +115 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/security-audit-Cdz2iq3m.js +120 -0
- package/dist/security-audit-contract-api.js +2 -0
- package/dist/security-audit.runtime-DBV1T1_N.js +2 -0
- package/dist/security-contract-api.js +2 -0
- package/dist/security-contract-ei3Mz8Sa.js +26 -0
- package/dist/security-doctor-CzTzpXV8.js +18 -0
- package/dist/send-B_frVn_Q.js +845 -0
- package/dist/send.components-B1EgHAds.js +468 -0
- package/dist/send.outbound-DlBAuW7y.js +211 -0
- package/dist/send.shared-Db0opnak.js +708 -0
- package/dist/sender-identity-BiSDAk2P.js +43 -0
- package/dist/session-contract-goJZckp2.js +6 -0
- package/dist/session-key-api.js +2 -0
- package/dist/session-key-normalization-Daag9II6.js +23 -0
- package/dist/setup-entry.js +11 -0
- package/dist/setup-plugin-api.js +2 -0
- package/dist/shared-CqlrJmSs.js +166 -0
- package/dist/shared-interactive-KgJjCqnB.js +79 -0
- package/dist/subagent-hooks-api.js +22 -0
- package/dist/subagent-hooks-mEK5ARfP.js +113 -0
- package/dist/system-events-Bu9jmO4W.js +34 -0
- package/dist/targets-kKlbZ4ai.js +3 -0
- package/dist/test-api.js +45 -0
- package/dist/thread-binding-api.js +4 -0
- package/dist/thread-bindings-Bj1R-6QH.js +256 -0
- package/dist/thread-bindings.discord-api-ClPMuIr8.js +184 -0
- package/dist/thread-bindings.manager-BaN0l4y8.js +535 -0
- package/dist/thread-bindings.session-updates-TTP020qQ.js +54 -0
- package/dist/thread-bindings.state-Dzu1gCE7.js +318 -0
- package/dist/threading-CWhdYHVx.js +475 -0
- package/dist/timeouts-C7jeTtGs.js +52 -0
- package/dist/timeouts.js +2 -0
- package/dist/token-D-w3Rigl.js +42 -0
- package/dist/typing-CJiowRTZ.js +15 -0
- package/package.json +14 -6
- package/account-inspect-api.ts +0 -6
- package/action-runtime-api.ts +0 -1
- package/api.ts +0 -132
- package/channel-config-api.ts +0 -1
- package/channel-plugin-api.ts +0 -3
- package/config-api.ts +0 -4
- package/configured-state.ts +0 -6
- package/contract-api.ts +0 -21
- package/directory-contract-api.ts +0 -4
- package/doctor-contract-api.ts +0 -1
- package/index.test.ts +0 -13
- package/index.ts +0 -24
- package/runtime-api.actions.ts +0 -15
- package/runtime-api.lookup.ts +0 -22
- package/runtime-api.monitor.ts +0 -50
- package/runtime-api.send.ts +0 -79
- package/runtime-api.threads.ts +0 -30
- package/runtime-api.ts +0 -180
- package/runtime-setter-api.ts +0 -3
- package/secret-contract-api.ts +0 -4
- package/security-audit-contract-api.ts +0 -1
- package/security-contract-api.ts +0 -4
- package/session-key-api.ts +0 -1
- package/setup-entry.ts +0 -9
- package/setup-plugin-api.ts +0 -3
- package/src/account-inspect.test.ts +0 -126
- package/src/account-inspect.ts +0 -132
- package/src/accounts.test.ts +0 -247
- package/src/accounts.ts +0 -196
- package/src/actions/handle-action.guild-admin.ts +0 -411
- package/src/actions/handle-action.test.ts +0 -306
- package/src/actions/handle-action.ts +0 -372
- package/src/actions/runtime.guild.ts +0 -446
- package/src/actions/runtime.messaging.messages.ts +0 -205
- package/src/actions/runtime.messaging.reactions.ts +0 -67
- package/src/actions/runtime.messaging.runtime.ts +0 -69
- package/src/actions/runtime.messaging.send.ts +0 -248
- package/src/actions/runtime.messaging.shared.ts +0 -97
- package/src/actions/runtime.messaging.ts +0 -37
- package/src/actions/runtime.moderation-shared.ts +0 -48
- package/src/actions/runtime.moderation.authz.test.ts +0 -151
- package/src/actions/runtime.moderation.ts +0 -116
- package/src/actions/runtime.presence.test.ts +0 -160
- package/src/actions/runtime.presence.ts +0 -117
- package/src/actions/runtime.shared.ts +0 -83
- package/src/actions/runtime.test.ts +0 -1087
- package/src/actions/runtime.ts +0 -87
- package/src/api-barrel.test.ts +0 -80
- package/src/api.test.ts +0 -130
- package/src/api.ts +0 -169
- package/src/approval-handler.runtime.test.ts +0 -41
- package/src/approval-handler.runtime.ts +0 -632
- package/src/approval-native.test.ts +0 -330
- package/src/approval-native.ts +0 -219
- package/src/approval-runtime.ts +0 -14
- package/src/approval-shared.ts +0 -53
- package/src/audit-core.ts +0 -141
- package/src/audit.test.ts +0 -145
- package/src/audit.ts +0 -32
- package/src/channel-actions.contract.test.ts +0 -45
- package/src/channel-actions.runtime.ts +0 -1
- package/src/channel-actions.test.ts +0 -275
- package/src/channel-actions.ts +0 -203
- package/src/channel-api.ts +0 -29
- package/src/channel.conversation.ts +0 -159
- package/src/channel.loaders.ts +0 -47
- package/src/channel.runtime.ts +0 -1
- package/src/channel.setup.ts +0 -12
- package/src/channel.test.ts +0 -571
- package/src/channel.ts +0 -629
- package/src/chunk.test.ts +0 -157
- package/src/chunk.ts +0 -321
- package/src/client.proxy.test.ts +0 -176
- package/src/client.test.ts +0 -76
- package/src/client.ts +0 -132
- package/src/component-custom-id.ts +0 -72
- package/src/components-registry.ts +0 -356
- package/src/components.builders.ts +0 -409
- package/src/components.modal.ts +0 -124
- package/src/components.parse.ts +0 -407
- package/src/components.test.ts +0 -312
- package/src/components.ts +0 -54
- package/src/components.types.ts +0 -187
- package/src/config-schema.test.ts +0 -325
- package/src/config-schema.ts +0 -6
- package/src/config-ui-hints.ts +0 -249
- package/src/conversation-identity.ts +0 -58
- package/src/delivery-retry.ts +0 -56
- package/src/directory-cache.ts +0 -116
- package/src/directory-config.ts +0 -58
- package/src/directory-contract.test.ts +0 -129
- package/src/directory-live.test.ts +0 -126
- package/src/directory-live.ts +0 -135
- package/src/doctor-contract.ts +0 -477
- package/src/doctor-shared.ts +0 -5
- package/src/doctor.test.ts +0 -405
- package/src/doctor.ts +0 -340
- package/src/draft-chunking.test.ts +0 -64
- package/src/draft-chunking.ts +0 -43
- package/src/draft-stream.test.ts +0 -159
- package/src/draft-stream.ts +0 -154
- package/src/error-body.ts +0 -38
- package/src/exec-approvals.test.ts +0 -88
- package/src/exec-approvals.ts +0 -110
- package/src/gateway-logging.test.ts +0 -98
- package/src/gateway-logging.ts +0 -67
- package/src/group-policy.ts +0 -113
- package/src/guilds.ts +0 -29
- package/src/inbound-context.contract.test.ts +0 -11
- package/src/interactive-dispatch.ts +0 -104
- package/src/internal/api.commands.ts +0 -51
- package/src/internal/api.guild.ts +0 -164
- package/src/internal/api.interactions.ts +0 -53
- package/src/internal/api.messages.ts +0 -113
- package/src/internal/api.reactions.ts +0 -38
- package/src/internal/api.test.ts +0 -262
- package/src/internal/api.ts +0 -61
- package/src/internal/api.users.ts +0 -19
- package/src/internal/api.webhooks.ts +0 -13
- package/src/internal/client.test.ts +0 -440
- package/src/internal/client.ts +0 -310
- package/src/internal/command-deploy.ts +0 -297
- package/src/internal/commands.ts +0 -188
- package/src/internal/components.base.ts +0 -65
- package/src/internal/components.message.ts +0 -279
- package/src/internal/components.modal.ts +0 -95
- package/src/internal/components.ts +0 -31
- package/src/internal/discord.ts +0 -11
- package/src/internal/embeds.ts +0 -35
- package/src/internal/entity-cache.ts +0 -98
- package/src/internal/event-queue.ts +0 -162
- package/src/internal/gateway-close-codes.ts +0 -25
- package/src/internal/gateway-dispatch.ts +0 -96
- package/src/internal/gateway-identify-limiter.ts +0 -26
- package/src/internal/gateway-lifecycle.ts +0 -61
- package/src/internal/gateway-rate-limit.ts +0 -104
- package/src/internal/gateway.test.ts +0 -603
- package/src/internal/gateway.ts +0 -476
- package/src/internal/interaction-dispatch.test.ts +0 -148
- package/src/internal/interaction-dispatch.ts +0 -162
- package/src/internal/interaction-options.ts +0 -98
- package/src/internal/interaction-response.ts +0 -53
- package/src/internal/interactions.test.ts +0 -325
- package/src/internal/interactions.ts +0 -378
- package/src/internal/listeners.ts +0 -85
- package/src/internal/live-smoke.live.test.ts +0 -26
- package/src/internal/modal-fields.ts +0 -95
- package/src/internal/payload.ts +0 -69
- package/src/internal/rest-body.ts +0 -115
- package/src/internal/rest-errors.ts +0 -88
- package/src/internal/rest-routes.ts +0 -50
- package/src/internal/rest-scheduler.ts +0 -557
- package/src/internal/rest.test.ts +0 -673
- package/src/internal/rest.ts +0 -322
- package/src/internal/schemas.ts +0 -36
- package/src/internal/structures.test.ts +0 -43
- package/src/internal/structures.ts +0 -280
- package/src/internal/test-builders.test-support.ts +0 -167
- package/src/internal/voice.ts +0 -49
- package/src/media-detection.ts +0 -28
- package/src/mentions.test.ts +0 -111
- package/src/mentions.ts +0 -147
- package/src/monitor/access-groups.ts +0 -55
- package/src/monitor/ack-reactions.ts +0 -70
- package/src/monitor/acp-bind-here.integration.test.ts +0 -211
- package/src/monitor/agent-components-auth.ts +0 -7
- package/src/monitor/agent-components-context.ts +0 -154
- package/src/monitor/agent-components-data.ts +0 -224
- package/src/monitor/agent-components-dm-auth.ts +0 -221
- package/src/monitor/agent-components-guild-auth.ts +0 -322
- package/src/monitor/agent-components-helpers.runtime.ts +0 -5
- package/src/monitor/agent-components-helpers.ts +0 -34
- package/src/monitor/agent-components-reply.ts +0 -10
- package/src/monitor/agent-components.deps.runtime.ts +0 -2
- package/src/monitor/agent-components.dispatch.ts +0 -366
- package/src/monitor/agent-components.handlers.ts +0 -303
- package/src/monitor/agent-components.modal.ts +0 -160
- package/src/monitor/agent-components.plugin-interactive.ts +0 -187
- package/src/monitor/agent-components.runtime.ts +0 -14
- package/src/monitor/agent-components.system-controls.ts +0 -211
- package/src/monitor/agent-components.ts +0 -70
- package/src/monitor/agent-components.types.ts +0 -58
- package/src/monitor/agent-components.wildcard-controls.ts +0 -168
- package/src/monitor/agent-components.wildcard.test.ts +0 -71
- package/src/monitor/allow-list.test.ts +0 -14
- package/src/monitor/allow-list.ts +0 -633
- package/src/monitor/auto-presence.test.ts +0 -156
- package/src/monitor/auto-presence.ts +0 -356
- package/src/monitor/channel-access.test.ts +0 -99
- package/src/monitor/channel-access.ts +0 -102
- package/src/monitor/commands.test.ts +0 -24
- package/src/monitor/commands.ts +0 -9
- package/src/monitor/dm-command-auth.test.ts +0 -197
- package/src/monitor/dm-command-auth.ts +0 -158
- package/src/monitor/dm-command-decision.test.ts +0 -113
- package/src/monitor/dm-command-decision.ts +0 -49
- package/src/monitor/exec-approvals.test.ts +0 -226
- package/src/monitor/exec-approvals.ts +0 -158
- package/src/monitor/format.ts +0 -45
- package/src/monitor/gateway-handle.ts +0 -34
- package/src/monitor/gateway-metadata.test.ts +0 -29
- package/src/monitor/gateway-metadata.ts +0 -298
- package/src/monitor/gateway-plugin.test.ts +0 -297
- package/src/monitor/gateway-plugin.ts +0 -294
- package/src/monitor/gateway-registry.ts +0 -37
- package/src/monitor/gateway-supervisor.test.ts +0 -150
- package/src/monitor/gateway-supervisor.ts +0 -206
- package/src/monitor/inbound-context.test-helpers.ts +0 -37
- package/src/monitor/inbound-context.test.ts +0 -106
- package/src/monitor/inbound-context.ts +0 -103
- package/src/monitor/inbound-dedupe.ts +0 -79
- package/src/monitor/inbound-job.test.ts +0 -203
- package/src/monitor/inbound-job.ts +0 -118
- package/src/monitor/listeners.queue.ts +0 -91
- package/src/monitor/listeners.reactions.ts +0 -610
- package/src/monitor/listeners.test.ts +0 -200
- package/src/monitor/listeners.ts +0 -150
- package/src/monitor/message-channel-info.ts +0 -96
- package/src/monitor/message-forwarded.ts +0 -107
- package/src/monitor/message-handler.batch-gate.test.ts +0 -22
- package/src/monitor/message-handler.batch-gate.ts +0 -19
- package/src/monitor/message-handler.bot-self-filter.test.ts +0 -68
- package/src/monitor/message-handler.context.ts +0 -406
- package/src/monitor/message-handler.dm-preflight.ts +0 -123
- package/src/monitor/message-handler.draft-preview.ts +0 -246
- package/src/monitor/message-handler.hydration.test.ts +0 -80
- package/src/monitor/message-handler.hydration.ts +0 -198
- package/src/monitor/message-handler.inbound-context.test.ts +0 -59
- package/src/monitor/message-handler.module-test-helpers.ts +0 -31
- package/src/monitor/message-handler.preflight-channel-access.ts +0 -86
- package/src/monitor/message-handler.preflight-channel-context.test.ts +0 -18
- package/src/monitor/message-handler.preflight-channel-context.ts +0 -58
- package/src/monitor/message-handler.preflight-context.ts +0 -54
- package/src/monitor/message-handler.preflight-helpers.ts +0 -164
- package/src/monitor/message-handler.preflight-history.ts +0 -23
- package/src/monitor/message-handler.preflight-logging.ts +0 -36
- package/src/monitor/message-handler.preflight-pluralkit.ts +0 -26
- package/src/monitor/message-handler.preflight-runtime.ts +0 -28
- package/src/monitor/message-handler.preflight-thread.ts +0 -49
- package/src/monitor/message-handler.preflight.acp-bindings.test.ts +0 -369
- package/src/monitor/message-handler.preflight.test-helpers.ts +0 -111
- package/src/monitor/message-handler.preflight.test.ts +0 -1623
- package/src/monitor/message-handler.preflight.ts +0 -679
- package/src/monitor/message-handler.preflight.types.ts +0 -110
- package/src/monitor/message-handler.process.test.ts +0 -1369
- package/src/monitor/message-handler.process.ts +0 -686
- package/src/monitor/message-handler.queue.test.ts +0 -496
- package/src/monitor/message-handler.routing-preflight.ts +0 -112
- package/src/monitor/message-handler.test-harness.ts +0 -99
- package/src/monitor/message-handler.test-helpers.ts +0 -75
- package/src/monitor/message-handler.ts +0 -274
- package/src/monitor/message-media.ts +0 -509
- package/src/monitor/message-run-queue.ts +0 -101
- package/src/monitor/message-text.ts +0 -171
- package/src/monitor/message-utils.test.ts +0 -1157
- package/src/monitor/message-utils.ts +0 -32
- package/src/monitor/model-picker-preferences.test.ts +0 -67
- package/src/monitor/model-picker-preferences.ts +0 -184
- package/src/monitor/model-picker.state.ts +0 -364
- package/src/monitor/model-picker.test-utils.ts +0 -26
- package/src/monitor/model-picker.test.ts +0 -794
- package/src/monitor/model-picker.ts +0 -38
- package/src/monitor/model-picker.view.ts +0 -695
- package/src/monitor/monitor.agent-components.test.ts +0 -375
- package/src/monitor/monitor.test.ts +0 -849
- package/src/monitor/monitor.threading-utils.test.ts +0 -598
- package/src/monitor/native-command-agent-reply.ts +0 -125
- package/src/monitor/native-command-arg-ui.ts +0 -233
- package/src/monitor/native-command-auth.ts +0 -308
- package/src/monitor/native-command-bypass.ts +0 -13
- package/src/monitor/native-command-context.test.ts +0 -98
- package/src/monitor/native-command-context.ts +0 -103
- package/src/monitor/native-command-dispatch.ts +0 -35
- package/src/monitor/native-command-model-picker-apply.ts +0 -177
- package/src/monitor/native-command-model-picker-interaction.ts +0 -461
- package/src/monitor/native-command-model-picker-ui.ts +0 -368
- package/src/monitor/native-command-reply.test.ts +0 -68
- package/src/monitor/native-command-reply.ts +0 -185
- package/src/monitor/native-command-route.ts +0 -91
- package/src/monitor/native-command-status.ts +0 -76
- package/src/monitor/native-command-ui.ts +0 -26
- package/src/monitor/native-command-ui.types.ts +0 -20
- package/src/monitor/native-command.args.ts +0 -45
- package/src/monitor/native-command.command-arg.test.ts +0 -99
- package/src/monitor/native-command.commands-allowfrom.test.ts +0 -490
- package/src/monitor/native-command.model-picker.test.ts +0 -767
- package/src/monitor/native-command.options.test.ts +0 -369
- package/src/monitor/native-command.options.ts +0 -153
- package/src/monitor/native-command.plugin-dispatch.test.ts +0 -961
- package/src/monitor/native-command.runtime.ts +0 -50
- package/src/monitor/native-command.status-direct.test.ts +0 -272
- package/src/monitor/native-command.test-helpers.ts +0 -64
- package/src/monitor/native-command.think-autocomplete.test.ts +0 -416
- package/src/monitor/native-command.ts +0 -700
- package/src/monitor/native-command.types.ts +0 -9
- package/src/monitor/native-interaction-channel-context.ts +0 -50
- package/src/monitor/preflight-audio.runtime.ts +0 -9
- package/src/monitor/preflight-audio.test.ts +0 -157
- package/src/monitor/preflight-audio.ts +0 -130
- package/src/monitor/presence-cache.ts +0 -61
- package/src/monitor/presence.test.ts +0 -44
- package/src/monitor/presence.ts +0 -50
- package/src/monitor/provider-session.runtime.ts +0 -12
- package/src/monitor/provider.acp.ts +0 -89
- package/src/monitor/provider.allowlist.test.ts +0 -149
- package/src/monitor/provider.allowlist.ts +0 -394
- package/src/monitor/provider.cleanup.ts +0 -41
- package/src/monitor/provider.commands.ts +0 -129
- package/src/monitor/provider.config-log.ts +0 -45
- package/src/monitor/provider.deploy-errors.ts +0 -362
- package/src/monitor/provider.deploy.ts +0 -221
- package/src/monitor/provider.interactions.ts +0 -160
- package/src/monitor/provider.lifecycle.test.ts +0 -713
- package/src/monitor/provider.lifecycle.ts +0 -552
- package/src/monitor/provider.proxy.test.ts +0 -745
- package/src/monitor/provider.rest-proxy.test.ts +0 -121
- package/src/monitor/provider.runtime.ts +0 -1
- package/src/monitor/provider.skill-dedupe.test.ts +0 -42
- package/src/monitor/provider.startup-log.ts +0 -32
- package/src/monitor/provider.startup.test.ts +0 -426
- package/src/monitor/provider.startup.ts +0 -330
- package/src/monitor/provider.test.ts +0 -1111
- package/src/monitor/provider.ts +0 -713
- package/src/monitor/reply-context.ts +0 -64
- package/src/monitor/reply-delivery.test.ts +0 -244
- package/src/monitor/reply-delivery.ts +0 -203
- package/src/monitor/rest-fetch.ts +0 -43
- package/src/monitor/route-resolution.test.ts +0 -204
- package/src/monitor/route-resolution.ts +0 -140
- package/src/monitor/sender-identity.ts +0 -81
- package/src/monitor/startup-status.test.ts +0 -30
- package/src/monitor/startup-status.ts +0 -10
- package/src/monitor/status.ts +0 -22
- package/src/monitor/system-events.ts +0 -55
- package/src/monitor/thread-bindings.config.ts +0 -35
- package/src/monitor/thread-bindings.discord-api.test.ts +0 -229
- package/src/monitor/thread-bindings.discord-api.ts +0 -310
- package/src/monitor/thread-bindings.lifecycle.test.ts +0 -1871
- package/src/monitor/thread-bindings.lifecycle.ts +0 -354
- package/src/monitor/thread-bindings.manager.ts +0 -553
- package/src/monitor/thread-bindings.messages.ts +0 -6
- package/src/monitor/thread-bindings.persona.test.ts +0 -34
- package/src/monitor/thread-bindings.persona.ts +0 -25
- package/src/monitor/thread-bindings.session-adapter.ts +0 -229
- package/src/monitor/thread-bindings.session-shared.ts +0 -59
- package/src/monitor/thread-bindings.session-updates.ts +0 -35
- package/src/monitor/thread-bindings.shared-state.test.ts +0 -36
- package/src/monitor/thread-bindings.state.ts +0 -540
- package/src/monitor/thread-bindings.ts +0 -48
- package/src/monitor/thread-bindings.types.ts +0 -83
- package/src/monitor/thread-channel-context.ts +0 -112
- package/src/monitor/thread-session-close.test.ts +0 -180
- package/src/monitor/thread-session-close.ts +0 -63
- package/src/monitor/thread-title.generate.test.ts +0 -197
- package/src/monitor/thread-title.test.ts +0 -31
- package/src/monitor/thread-title.ts +0 -181
- package/src/monitor/threading.auto-thread.test.ts +0 -327
- package/src/monitor/threading.auto-thread.ts +0 -287
- package/src/monitor/threading.cache.ts +0 -45
- package/src/monitor/threading.parent-info.test.ts +0 -156
- package/src/monitor/threading.starter.test.ts +0 -260
- package/src/monitor/threading.starter.ts +0 -287
- package/src/monitor/threading.ts +0 -20
- package/src/monitor/threading.types.ts +0 -102
- package/src/monitor/timeouts.ts +0 -84
- package/src/monitor/typing.test.ts +0 -42
- package/src/monitor/typing.ts +0 -17
- package/src/monitor.gateway.test.ts +0 -187
- package/src/monitor.gateway.ts +0 -75
- package/src/monitor.test.ts +0 -1397
- package/src/monitor.ts +0 -28
- package/src/normalize.test.ts +0 -56
- package/src/normalize.ts +0 -86
- package/src/outbound-adapter.interactive-order.test.ts +0 -64
- package/src/outbound-adapter.test-harness.ts +0 -207
- package/src/outbound-adapter.test.ts +0 -696
- package/src/outbound-adapter.ts +0 -291
- package/src/outbound-approval.ts +0 -29
- package/src/outbound-components.ts +0 -81
- package/src/outbound-payload.contract.test.ts +0 -38
- package/src/outbound-payload.ts +0 -134
- package/src/outbound-send-context.ts +0 -92
- package/src/outbound-session-route.test.ts +0 -34
- package/src/outbound-session-route.ts +0 -72
- package/src/pluralkit.test.ts +0 -67
- package/src/pluralkit.ts +0 -58
- package/src/preview-streaming.ts +0 -32
- package/src/probe.intents.test.ts +0 -94
- package/src/probe.parse-token.test.ts +0 -43
- package/src/probe.runtime.ts +0 -1
- package/src/probe.ts +0 -237
- package/src/proxy-fetch.ts +0 -92
- package/src/proxy-request-client.test.ts +0 -78
- package/src/proxy-request-client.ts +0 -21
- package/src/recipient-resolution.ts +0 -39
- package/src/resolve-allowlist-common.test.ts +0 -36
- package/src/resolve-allowlist-common.ts +0 -39
- package/src/resolve-channels.test.ts +0 -340
- package/src/resolve-channels.ts +0 -369
- package/src/resolve-users.test.ts +0 -222
- package/src/resolve-users.ts +0 -184
- package/src/retry.test.ts +0 -83
- package/src/retry.ts +0 -98
- package/src/runtime-api.ts +0 -64
- package/src/runtime.ts +0 -23
- package/src/secret-config-contract.ts +0 -140
- package/src/security-audit.runtime.ts +0 -1
- package/src/security-audit.test.ts +0 -246
- package/src/security-audit.ts +0 -208
- package/src/security-contract.ts +0 -47
- package/src/security-doctor.test.ts +0 -25
- package/src/security-doctor.ts +0 -20
- package/src/security.ts +0 -60
- package/src/send-target-parsing.ts +0 -14
- package/src/send.channels.ts +0 -139
- package/src/send.components.test.ts +0 -275
- package/src/send.components.ts +0 -381
- package/src/send.creates-thread.test.ts +0 -643
- package/src/send.emojis-stickers.ts +0 -57
- package/src/send.guild.ts +0 -170
- package/src/send.message-request.ts +0 -97
- package/src/send.messages.test.ts +0 -53
- package/src/send.messages.ts +0 -225
- package/src/send.outbound.ts +0 -413
- package/src/send.permissions.authz.test.ts +0 -188
- package/src/send.permissions.ts +0 -283
- package/src/send.reactions.ts +0 -155
- package/src/send.sends-basic-channel-messages.test.ts +0 -941
- package/src/send.shared.ts +0 -447
- package/src/send.test-harness.ts +0 -56
- package/src/send.ts +0 -82
- package/src/send.types.ts +0 -188
- package/src/send.typing.test.ts +0 -41
- package/src/send.typing.ts +0 -9
- package/src/send.voice.ts +0 -134
- package/src/send.webhook-activity.test.ts +0 -105
- package/src/send.webhook.proxy.test.ts +0 -191
- package/src/send.webhook.ts +0 -133
- package/src/session-contract.ts +0 -3
- package/src/session-key-normalization.test.ts +0 -44
- package/src/session-key-normalization.ts +0 -47
- package/src/setup-account-state.test.ts +0 -91
- package/src/setup-account-state.ts +0 -144
- package/src/setup-adapter.ts +0 -12
- package/src/setup-core.ts +0 -212
- package/src/setup-runtime-helpers.ts +0 -10
- package/src/setup-surface.test.ts +0 -137
- package/src/setup-surface.ts +0 -129
- package/src/shared-interactive.test.ts +0 -153
- package/src/shared-interactive.ts +0 -124
- package/src/shared.test.ts +0 -165
- package/src/shared.ts +0 -190
- package/src/status-issues.test.ts +0 -70
- package/src/status-issues.ts +0 -169
- package/src/subagent-hooks.test.ts +0 -432
- package/src/subagent-hooks.ts +0 -214
- package/src/target-parsing.ts +0 -53
- package/src/target-resolver.ts +0 -129
- package/src/targets.test.ts +0 -367
- package/src/targets.ts +0 -12
- package/src/test-http-helpers.ts +0 -10
- package/src/test-support/component-runtime.ts +0 -190
- package/src/test-support/config.ts +0 -7
- package/src/test-support/configured-binding-runtime.ts +0 -29
- package/src/test-support/partial-channel.ts +0 -26
- package/src/test-support/provider.test-support.ts +0 -545
- package/src/token.test.ts +0 -107
- package/src/token.ts +0 -60
- package/src/ui-colors.ts +0 -27
- package/src/ui.ts +0 -20
- package/src/voice/access.test.ts +0 -217
- package/src/voice/access.ts +0 -124
- package/src/voice/audio.ts +0 -173
- package/src/voice/capture-state.test.ts +0 -48
- package/src/voice/capture-state.ts +0 -120
- package/src/voice/command.test.ts +0 -164
- package/src/voice/command.ts +0 -283
- package/src/voice/config.ts +0 -8
- package/src/voice/manager.e2e.test.ts +0 -928
- package/src/voice/manager.ready-listener.test.ts +0 -37
- package/src/voice/manager.runtime.ts +0 -11
- package/src/voice/manager.ts +0 -691
- package/src/voice/prompt.test.ts +0 -16
- package/src/voice/prompt.ts +0 -17
- package/src/voice/receive-recovery.test.ts +0 -79
- package/src/voice/receive-recovery.ts +0 -159
- package/src/voice/sanitize.test.ts +0 -34
- package/src/voice/sanitize.ts +0 -32
- package/src/voice/sdk-runtime.ts +0 -14
- package/src/voice/segment.ts +0 -156
- package/src/voice/session.ts +0 -50
- package/src/voice/speaker-context.ts +0 -127
- package/src/voice/tts.ts +0 -125
- package/src/voice-message.test.ts +0 -234
- package/src/voice-message.ts +0 -444
- package/subagent-hooks-api.ts +0 -27
- package/test-api.ts +0 -4
- package/thread-binding-api.ts +0 -1
- package/timeouts.ts +0 -6
- package/tsconfig.json +0 -16
package/src/monitor.test.ts
DELETED
|
@@ -1,1397 +0,0 @@
|
|
|
1
|
-
import { typedCases } from "openclaw/plugin-sdk/test-fixtures";
|
|
2
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
import { ChannelType, type Guild } from "./internal/discord.js";
|
|
4
|
-
import {
|
|
5
|
-
allowListMatches,
|
|
6
|
-
type DiscordGuildEntryResolved,
|
|
7
|
-
isDiscordGroupAllowedByPolicy,
|
|
8
|
-
normalizeDiscordAllowList,
|
|
9
|
-
normalizeDiscordSlug,
|
|
10
|
-
resolveDiscordChannelConfig,
|
|
11
|
-
resolveDiscordChannelConfigWithFallback,
|
|
12
|
-
resolveDiscordGuildEntry,
|
|
13
|
-
resolveDiscordOwnerAccess,
|
|
14
|
-
resolveDiscordShouldRequireMention,
|
|
15
|
-
resolveGroupDmAllow,
|
|
16
|
-
shouldEmitDiscordReactionNotification,
|
|
17
|
-
} from "./monitor/allow-list.js";
|
|
18
|
-
import { buildDiscordMediaPayload } from "./monitor/message-utils.js";
|
|
19
|
-
import { resolveDiscordReplyTarget, sanitizeDiscordThreadName } from "./monitor/threading.js";
|
|
20
|
-
type DiscordReactionEvent = Parameters<
|
|
21
|
-
import("./monitor/listeners.js").DiscordReactionListener["handle"]
|
|
22
|
-
>[0];
|
|
23
|
-
type DiscordReactionClient = Parameters<
|
|
24
|
-
import("./monitor/listeners.js").DiscordReactionListener["handle"]
|
|
25
|
-
>[1];
|
|
26
|
-
|
|
27
|
-
const readAllowFromStoreMock = vi.hoisted(() => vi.fn());
|
|
28
|
-
|
|
29
|
-
vi.mock("openclaw/plugin-sdk/conversation-runtime", async () => {
|
|
30
|
-
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/conversation-runtime")>(
|
|
31
|
-
"openclaw/plugin-sdk/conversation-runtime",
|
|
32
|
-
);
|
|
33
|
-
return {
|
|
34
|
-
...actual,
|
|
35
|
-
readChannelAllowFromStore: (...args: unknown[]) => readAllowFromStoreMock(...args),
|
|
36
|
-
};
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const fakeGuild = (id: string, name: string) => ({ id, name }) as Guild;
|
|
40
|
-
|
|
41
|
-
const makeEntries = (
|
|
42
|
-
entries: Record<string, Partial<DiscordGuildEntryResolved>>,
|
|
43
|
-
): Record<string, DiscordGuildEntryResolved> => {
|
|
44
|
-
const out: Record<string, DiscordGuildEntryResolved> = {};
|
|
45
|
-
for (const [key, value] of Object.entries(entries)) {
|
|
46
|
-
out[key] = {
|
|
47
|
-
slug: value.slug,
|
|
48
|
-
requireMention: value.requireMention,
|
|
49
|
-
reactionNotifications: value.reactionNotifications,
|
|
50
|
-
users: value.users,
|
|
51
|
-
roles: value.roles,
|
|
52
|
-
channels: value.channels,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
return out;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
function createAutoThreadMentionContext() {
|
|
59
|
-
const guildInfo: DiscordGuildEntryResolved = {
|
|
60
|
-
requireMention: true,
|
|
61
|
-
channels: {
|
|
62
|
-
general: { enabled: true, autoThread: true },
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
const channelConfig = resolveDiscordChannelConfig({
|
|
66
|
-
guildInfo,
|
|
67
|
-
channelId: "1",
|
|
68
|
-
channelName: "General",
|
|
69
|
-
channelSlug: "general",
|
|
70
|
-
});
|
|
71
|
-
return { guildInfo, channelConfig };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
beforeEach(() => {
|
|
75
|
-
vi.useRealTimers();
|
|
76
|
-
readAllowFromStoreMock.mockReset().mockResolvedValue([]);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
describe("registerDiscordListener", () => {
|
|
80
|
-
class FakeListener {
|
|
81
|
-
readonly testListener = true;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
it("dedupes listeners by constructor", () => {
|
|
85
|
-
const listeners: object[] = [];
|
|
86
|
-
|
|
87
|
-
expect(registerDiscordListener(listeners, new FakeListener())).toBe(true);
|
|
88
|
-
expect(registerDiscordListener(listeners, new FakeListener())).toBe(false);
|
|
89
|
-
expect(listeners).toHaveLength(1);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe("DiscordMessageListener", () => {
|
|
94
|
-
function createDeferred() {
|
|
95
|
-
let resolve: (() => void) | null = null;
|
|
96
|
-
const promise = new Promise<void>((done) => {
|
|
97
|
-
resolve = done;
|
|
98
|
-
});
|
|
99
|
-
return {
|
|
100
|
-
promise,
|
|
101
|
-
resolve: () => {
|
|
102
|
-
if (typeof resolve === "function") {
|
|
103
|
-
(resolve as () => void)();
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async function flushAsyncWork() {
|
|
110
|
-
await Promise.resolve();
|
|
111
|
-
await Promise.resolve();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
it("returns immediately while handler continues in background", async () => {
|
|
115
|
-
let handlerResolved = false;
|
|
116
|
-
const deferred = createDeferred();
|
|
117
|
-
const handler = vi.fn(async () => {
|
|
118
|
-
await deferred.promise;
|
|
119
|
-
handlerResolved = true;
|
|
120
|
-
});
|
|
121
|
-
const listener = new DiscordMessageListener(handler);
|
|
122
|
-
|
|
123
|
-
const handlePromise = listener.handle(
|
|
124
|
-
{} as unknown as import("./monitor/listeners.js").DiscordMessageEvent,
|
|
125
|
-
{} as unknown as import("./internal/discord.js").Client,
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
// handle() returns immediately while the background queue starts on the next tick.
|
|
129
|
-
await expect(handlePromise).resolves.toBeUndefined();
|
|
130
|
-
await flushAsyncWork();
|
|
131
|
-
expect(handler).toHaveBeenCalledOnce();
|
|
132
|
-
expect(handlerResolved).toBe(false);
|
|
133
|
-
|
|
134
|
-
// Release and let background handler finish.
|
|
135
|
-
deferred.resolve();
|
|
136
|
-
await Promise.resolve();
|
|
137
|
-
expect(handlerResolved).toBe(true);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("dispatches subsequent events concurrently without blocking on prior handler", async () => {
|
|
141
|
-
const first = createDeferred();
|
|
142
|
-
const second = createDeferred();
|
|
143
|
-
let runCount = 0;
|
|
144
|
-
const handler = vi.fn(async () => {
|
|
145
|
-
runCount += 1;
|
|
146
|
-
if (runCount === 1) {
|
|
147
|
-
await first.promise;
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
await second.promise;
|
|
151
|
-
});
|
|
152
|
-
const listener = new DiscordMessageListener(handler);
|
|
153
|
-
|
|
154
|
-
await expect(
|
|
155
|
-
listener.handle(
|
|
156
|
-
{} as unknown as import("./monitor/listeners.js").DiscordMessageEvent,
|
|
157
|
-
{} as unknown as import("./internal/discord.js").Client,
|
|
158
|
-
),
|
|
159
|
-
).resolves.toBeUndefined();
|
|
160
|
-
await expect(
|
|
161
|
-
listener.handle(
|
|
162
|
-
{} as unknown as import("./monitor/listeners.js").DiscordMessageEvent,
|
|
163
|
-
{} as unknown as import("./internal/discord.js").Client,
|
|
164
|
-
),
|
|
165
|
-
).resolves.toBeUndefined();
|
|
166
|
-
|
|
167
|
-
// Both handlers are dispatched concurrently (fire-and-forget).
|
|
168
|
-
await flushAsyncWork();
|
|
169
|
-
expect(handler).toHaveBeenCalledTimes(2);
|
|
170
|
-
|
|
171
|
-
first.resolve();
|
|
172
|
-
second.resolve();
|
|
173
|
-
await Promise.resolve();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("logs handler failures", async () => {
|
|
177
|
-
const logger = {
|
|
178
|
-
warn: vi.fn(),
|
|
179
|
-
error: vi.fn(),
|
|
180
|
-
} as unknown as ReturnType<
|
|
181
|
-
typeof import("openclaw/plugin-sdk/logging-core").createSubsystemLogger
|
|
182
|
-
>;
|
|
183
|
-
const handler = vi.fn(async () => {
|
|
184
|
-
throw new Error("boom");
|
|
185
|
-
});
|
|
186
|
-
const listener = new DiscordMessageListener(handler, logger);
|
|
187
|
-
|
|
188
|
-
await listener.handle(
|
|
189
|
-
{} as unknown as import("./monitor/listeners.js").DiscordMessageEvent,
|
|
190
|
-
{} as unknown as import("./internal/discord.js").Client,
|
|
191
|
-
);
|
|
192
|
-
await flushAsyncWork();
|
|
193
|
-
expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("discord handler failed"));
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("does not apply its own slow-listener logging", async () => {
|
|
197
|
-
const deferred = createDeferred();
|
|
198
|
-
const handler = vi.fn(() => deferred.promise);
|
|
199
|
-
const logger = {
|
|
200
|
-
warn: vi.fn(),
|
|
201
|
-
error: vi.fn(),
|
|
202
|
-
} as unknown as ReturnType<
|
|
203
|
-
typeof import("openclaw/plugin-sdk/logging-core").createSubsystemLogger
|
|
204
|
-
>;
|
|
205
|
-
const listener = new DiscordMessageListener(handler, logger);
|
|
206
|
-
|
|
207
|
-
const handlePromise = listener.handle(
|
|
208
|
-
{} as unknown as import("./monitor/listeners.js").DiscordMessageEvent,
|
|
209
|
-
{} as unknown as import("./internal/discord.js").Client,
|
|
210
|
-
);
|
|
211
|
-
await expect(handlePromise).resolves.toBeUndefined();
|
|
212
|
-
|
|
213
|
-
deferred.resolve();
|
|
214
|
-
await flushAsyncWork();
|
|
215
|
-
expect(handler).toHaveBeenCalledOnce();
|
|
216
|
-
// The listener no longer wraps message handlers with slow-listener logging.
|
|
217
|
-
expect(logger.warn).not.toHaveBeenCalled();
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
describe("discord allowlist helpers", () => {
|
|
222
|
-
it("normalizes slugs", () => {
|
|
223
|
-
expect(normalizeDiscordSlug("Friends of OpenClaw")).toBe("friends-of-openclaw");
|
|
224
|
-
expect(normalizeDiscordSlug("#General")).toBe("general");
|
|
225
|
-
expect(normalizeDiscordSlug("Dev__Chat")).toBe("dev-chat");
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it("matches ids by default and names only when enabled", () => {
|
|
229
|
-
const allow = normalizeDiscordAllowList(
|
|
230
|
-
["123", "steipete", "Friends of OpenClaw"],
|
|
231
|
-
["discord:", "user:", "guild:", "channel:"],
|
|
232
|
-
);
|
|
233
|
-
expect(allow).not.toBeNull();
|
|
234
|
-
if (!allow) {
|
|
235
|
-
throw new Error("Expected allow list to be normalized");
|
|
236
|
-
}
|
|
237
|
-
expect(allowListMatches(allow, { id: "123" })).toBe(true);
|
|
238
|
-
expect(allowListMatches(allow, { name: "steipete" })).toBe(false);
|
|
239
|
-
expect(allowListMatches(allow, { name: "friends-of-openclaw" })).toBe(false);
|
|
240
|
-
expect(allowListMatches(allow, { name: "steipete" }, { allowNameMatching: true })).toBe(true);
|
|
241
|
-
expect(
|
|
242
|
-
allowListMatches(allow, { name: "friends-of-openclaw" }, { allowNameMatching: true }),
|
|
243
|
-
).toBe(true);
|
|
244
|
-
expect(allowListMatches(allow, { name: "other" })).toBe(false);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it("matches pk-prefixed allowlist entries", () => {
|
|
248
|
-
const allow = normalizeDiscordAllowList(["pk:member-123"], ["discord:", "user:", "pk:"]);
|
|
249
|
-
expect(allow).not.toBeNull();
|
|
250
|
-
if (!allow) {
|
|
251
|
-
throw new Error("Expected allow list to be normalized");
|
|
252
|
-
}
|
|
253
|
-
expect(allowListMatches(allow, { id: "member-123" })).toBe(true);
|
|
254
|
-
expect(allowListMatches(allow, { id: "member-999" })).toBe(false);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it("does not treat DM wildcard access as owner access", () => {
|
|
258
|
-
const wildcardOnly = resolveDiscordOwnerAccess({
|
|
259
|
-
allowFrom: ["*"],
|
|
260
|
-
sender: { id: "123" },
|
|
261
|
-
});
|
|
262
|
-
expect(wildcardOnly.ownerAllowList).toBeNull();
|
|
263
|
-
expect(wildcardOnly.ownerAllowed).toBe(false);
|
|
264
|
-
|
|
265
|
-
const explicitOwner = resolveDiscordOwnerAccess({
|
|
266
|
-
allowFrom: ["*", "user:123"],
|
|
267
|
-
sender: { id: "123" },
|
|
268
|
-
});
|
|
269
|
-
expect(explicitOwner.ownerAllowList).not.toBeNull();
|
|
270
|
-
expect(explicitOwner.ownerAllowed).toBe(true);
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
describe("discord guild/channel resolution", () => {
|
|
275
|
-
it("resolves guild entry by id", () => {
|
|
276
|
-
const guildEntries = makeEntries({
|
|
277
|
-
"123": { slug: "friends-of-openclaw" },
|
|
278
|
-
});
|
|
279
|
-
const resolved = resolveDiscordGuildEntry({
|
|
280
|
-
guild: fakeGuild("123", "Friends of OpenClaw"),
|
|
281
|
-
guildEntries,
|
|
282
|
-
});
|
|
283
|
-
expect(resolved?.id).toBe("123");
|
|
284
|
-
expect(resolved?.slug).toBe("friends-of-openclaw");
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
it("resolves guild entry by raw guild id when guild object is missing", () => {
|
|
288
|
-
const guildEntries = makeEntries({
|
|
289
|
-
"123": { slug: "friends-of-openclaw" },
|
|
290
|
-
});
|
|
291
|
-
const resolved = resolveDiscordGuildEntry({
|
|
292
|
-
guildId: "123",
|
|
293
|
-
guildEntries,
|
|
294
|
-
});
|
|
295
|
-
expect(resolved?.id).toBe("123");
|
|
296
|
-
expect(resolved?.slug).toBe("friends-of-openclaw");
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
it("resolves guild entry by slug key", () => {
|
|
300
|
-
const guildEntries = makeEntries({
|
|
301
|
-
"friends-of-openclaw": { slug: "friends-of-openclaw" },
|
|
302
|
-
});
|
|
303
|
-
const resolved = resolveDiscordGuildEntry({
|
|
304
|
-
guild: fakeGuild("123", "Friends of OpenClaw"),
|
|
305
|
-
guildEntries,
|
|
306
|
-
});
|
|
307
|
-
expect(resolved?.id).toBe("123");
|
|
308
|
-
expect(resolved?.slug).toBe("friends-of-openclaw");
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
it("falls back to wildcard guild entry", () => {
|
|
312
|
-
const guildEntries = makeEntries({
|
|
313
|
-
"*": { requireMention: false },
|
|
314
|
-
});
|
|
315
|
-
const resolved = resolveDiscordGuildEntry({
|
|
316
|
-
guild: fakeGuild("123", "Friends of OpenClaw"),
|
|
317
|
-
guildEntries,
|
|
318
|
-
});
|
|
319
|
-
expect(resolved?.id).toBe("123");
|
|
320
|
-
expect(resolved?.requireMention).toBe(false);
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it("resolves channel config by slug", () => {
|
|
324
|
-
const guildInfo: DiscordGuildEntryResolved = {
|
|
325
|
-
channels: {
|
|
326
|
-
general: { enabled: true },
|
|
327
|
-
help: {
|
|
328
|
-
enabled: true,
|
|
329
|
-
requireMention: true,
|
|
330
|
-
skills: ["search"],
|
|
331
|
-
users: ["123"],
|
|
332
|
-
systemPrompt: "Use short answers.",
|
|
333
|
-
autoThread: true,
|
|
334
|
-
},
|
|
335
|
-
},
|
|
336
|
-
};
|
|
337
|
-
const channel = resolveDiscordChannelConfig({
|
|
338
|
-
guildInfo,
|
|
339
|
-
channelId: "456",
|
|
340
|
-
channelName: "General",
|
|
341
|
-
channelSlug: "general",
|
|
342
|
-
});
|
|
343
|
-
expect(channel?.allowed).toBe(true);
|
|
344
|
-
expect(channel?.requireMention).toBeUndefined();
|
|
345
|
-
|
|
346
|
-
const help = resolveDiscordChannelConfig({
|
|
347
|
-
guildInfo,
|
|
348
|
-
channelId: "789",
|
|
349
|
-
channelName: "Help",
|
|
350
|
-
channelSlug: "help",
|
|
351
|
-
});
|
|
352
|
-
expect(help?.allowed).toBe(true);
|
|
353
|
-
expect(help?.requireMention).toBe(true);
|
|
354
|
-
expect(help?.skills).toEqual(["search"]);
|
|
355
|
-
expect(help?.enabled).toBe(true);
|
|
356
|
-
expect(help?.users).toEqual(["123"]);
|
|
357
|
-
expect(help?.systemPrompt).toBe("Use short answers.");
|
|
358
|
-
expect(help?.autoThread).toBe(true);
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it("denies channel when config present but no match", () => {
|
|
362
|
-
const guildInfo: DiscordGuildEntryResolved = {
|
|
363
|
-
channels: {
|
|
364
|
-
general: { enabled: true },
|
|
365
|
-
},
|
|
366
|
-
};
|
|
367
|
-
const channel = resolveDiscordChannelConfig({
|
|
368
|
-
guildInfo,
|
|
369
|
-
channelId: "999",
|
|
370
|
-
channelName: "random",
|
|
371
|
-
channelSlug: "random",
|
|
372
|
-
});
|
|
373
|
-
expect(channel?.allowed).toBe(false);
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
it("treats empty channel config map as no channel allowlist", () => {
|
|
377
|
-
const guildInfo: DiscordGuildEntryResolved = {
|
|
378
|
-
channels: {},
|
|
379
|
-
};
|
|
380
|
-
const channel = resolveDiscordChannelConfig({
|
|
381
|
-
guildInfo,
|
|
382
|
-
channelId: "999",
|
|
383
|
-
channelName: "random",
|
|
384
|
-
channelSlug: "random",
|
|
385
|
-
});
|
|
386
|
-
expect(channel).toBeNull();
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it("inherits parent config for thread channels", () => {
|
|
390
|
-
const guildInfo: DiscordGuildEntryResolved = {
|
|
391
|
-
channels: {
|
|
392
|
-
general: { enabled: true },
|
|
393
|
-
random: { enabled: false },
|
|
394
|
-
},
|
|
395
|
-
};
|
|
396
|
-
const thread = resolveDiscordChannelConfigWithFallback({
|
|
397
|
-
guildInfo,
|
|
398
|
-
channelId: "thread-123",
|
|
399
|
-
channelName: "topic",
|
|
400
|
-
channelSlug: "topic",
|
|
401
|
-
parentId: "999",
|
|
402
|
-
parentName: "random",
|
|
403
|
-
parentSlug: "random",
|
|
404
|
-
scope: "thread",
|
|
405
|
-
});
|
|
406
|
-
expect(thread?.allowed).toBe(false);
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
it("does not match thread name/slug when resolving allowlists", () => {
|
|
410
|
-
const guildInfo: DiscordGuildEntryResolved = {
|
|
411
|
-
channels: {
|
|
412
|
-
general: { enabled: true },
|
|
413
|
-
random: { enabled: false },
|
|
414
|
-
},
|
|
415
|
-
};
|
|
416
|
-
const thread = resolveDiscordChannelConfigWithFallback({
|
|
417
|
-
guildInfo,
|
|
418
|
-
channelId: "thread-999",
|
|
419
|
-
channelName: "general",
|
|
420
|
-
channelSlug: "general",
|
|
421
|
-
parentId: "999",
|
|
422
|
-
parentName: "random",
|
|
423
|
-
parentSlug: "random",
|
|
424
|
-
scope: "thread",
|
|
425
|
-
});
|
|
426
|
-
expect(thread?.allowed).toBe(false);
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
it("applies wildcard channel config when no specific match", () => {
|
|
430
|
-
const guildInfo: DiscordGuildEntryResolved = {
|
|
431
|
-
channels: {
|
|
432
|
-
general: { enabled: true, requireMention: false },
|
|
433
|
-
"*": { enabled: true, autoThread: true, requireMention: true },
|
|
434
|
-
},
|
|
435
|
-
};
|
|
436
|
-
// Specific channel should NOT use wildcard
|
|
437
|
-
const general = resolveDiscordChannelConfig({
|
|
438
|
-
guildInfo,
|
|
439
|
-
channelId: "123",
|
|
440
|
-
channelName: "general",
|
|
441
|
-
channelSlug: "general",
|
|
442
|
-
});
|
|
443
|
-
expect(general?.allowed).toBe(true);
|
|
444
|
-
expect(general?.requireMention).toBe(false);
|
|
445
|
-
expect(general?.autoThread).toBeUndefined();
|
|
446
|
-
expect(general?.matchSource).toBe("direct");
|
|
447
|
-
|
|
448
|
-
// Unknown channel should use wildcard
|
|
449
|
-
const random = resolveDiscordChannelConfig({
|
|
450
|
-
guildInfo,
|
|
451
|
-
channelId: "999",
|
|
452
|
-
channelName: "random",
|
|
453
|
-
channelSlug: "random",
|
|
454
|
-
});
|
|
455
|
-
expect(random?.allowed).toBe(true);
|
|
456
|
-
expect(random?.autoThread).toBe(true);
|
|
457
|
-
expect(random?.requireMention).toBe(true);
|
|
458
|
-
expect(random?.matchSource).toBe("wildcard");
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
it("falls back to wildcard when thread channel and parent are missing", () => {
|
|
462
|
-
const guildInfo: DiscordGuildEntryResolved = {
|
|
463
|
-
channels: {
|
|
464
|
-
"*": { enabled: true, requireMention: false },
|
|
465
|
-
},
|
|
466
|
-
};
|
|
467
|
-
const thread = resolveDiscordChannelConfigWithFallback({
|
|
468
|
-
guildInfo,
|
|
469
|
-
channelId: "thread-123",
|
|
470
|
-
channelName: "topic",
|
|
471
|
-
channelSlug: "topic",
|
|
472
|
-
parentId: "parent-999",
|
|
473
|
-
parentName: "general",
|
|
474
|
-
parentSlug: "general",
|
|
475
|
-
scope: "thread",
|
|
476
|
-
});
|
|
477
|
-
expect(thread?.allowed).toBe(true);
|
|
478
|
-
expect(thread?.matchKey).toBe("*");
|
|
479
|
-
expect(thread?.matchSource).toBe("wildcard");
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
it("treats empty channel config map as no thread allowlist", () => {
|
|
483
|
-
const guildInfo: DiscordGuildEntryResolved = {
|
|
484
|
-
channels: {},
|
|
485
|
-
};
|
|
486
|
-
const thread = resolveDiscordChannelConfigWithFallback({
|
|
487
|
-
guildInfo,
|
|
488
|
-
channelId: "thread-123",
|
|
489
|
-
channelName: "topic",
|
|
490
|
-
channelSlug: "topic",
|
|
491
|
-
parentId: "parent-999",
|
|
492
|
-
parentName: "general",
|
|
493
|
-
parentSlug: "general",
|
|
494
|
-
scope: "thread",
|
|
495
|
-
});
|
|
496
|
-
expect(thread).toBeNull();
|
|
497
|
-
});
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
describe("discord mention gating", () => {
|
|
501
|
-
it("requires mention by default", () => {
|
|
502
|
-
const guildInfo: DiscordGuildEntryResolved = {
|
|
503
|
-
requireMention: true,
|
|
504
|
-
channels: {
|
|
505
|
-
general: { enabled: true },
|
|
506
|
-
},
|
|
507
|
-
};
|
|
508
|
-
const channelConfig = resolveDiscordChannelConfig({
|
|
509
|
-
guildInfo,
|
|
510
|
-
channelId: "1",
|
|
511
|
-
channelName: "General",
|
|
512
|
-
channelSlug: "general",
|
|
513
|
-
});
|
|
514
|
-
expect(
|
|
515
|
-
resolveDiscordShouldRequireMention({
|
|
516
|
-
isGuildMessage: true,
|
|
517
|
-
isThread: false,
|
|
518
|
-
channelConfig,
|
|
519
|
-
guildInfo,
|
|
520
|
-
}),
|
|
521
|
-
).toBe(true);
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
it("applies autoThread mention rules based on thread ownership", () => {
|
|
525
|
-
const cases = [
|
|
526
|
-
{ name: "bot-owned thread", threadOwnerId: "bot123", expected: false },
|
|
527
|
-
{ name: "user-owned thread", threadOwnerId: "user456", expected: true },
|
|
528
|
-
{ name: "unknown thread owner", threadOwnerId: undefined, expected: true },
|
|
529
|
-
] as const;
|
|
530
|
-
|
|
531
|
-
for (const testCase of cases) {
|
|
532
|
-
const { guildInfo, channelConfig } = createAutoThreadMentionContext();
|
|
533
|
-
expect(
|
|
534
|
-
resolveDiscordShouldRequireMention({
|
|
535
|
-
isGuildMessage: true,
|
|
536
|
-
isThread: true,
|
|
537
|
-
botId: "bot123",
|
|
538
|
-
threadOwnerId: testCase.threadOwnerId,
|
|
539
|
-
channelConfig,
|
|
540
|
-
guildInfo,
|
|
541
|
-
}),
|
|
542
|
-
testCase.name,
|
|
543
|
-
).toBe(testCase.expected);
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
it("inherits parent channel mention rules for threads", () => {
|
|
548
|
-
const guildInfo: DiscordGuildEntryResolved = {
|
|
549
|
-
requireMention: true,
|
|
550
|
-
channels: {
|
|
551
|
-
"parent-1": { enabled: true, requireMention: false },
|
|
552
|
-
},
|
|
553
|
-
};
|
|
554
|
-
const channelConfig = resolveDiscordChannelConfigWithFallback({
|
|
555
|
-
guildInfo,
|
|
556
|
-
channelId: "thread-1",
|
|
557
|
-
channelName: "topic",
|
|
558
|
-
channelSlug: "topic",
|
|
559
|
-
parentId: "parent-1",
|
|
560
|
-
parentName: "Parent",
|
|
561
|
-
parentSlug: "parent",
|
|
562
|
-
scope: "thread",
|
|
563
|
-
});
|
|
564
|
-
expect(channelConfig?.matchSource).toBe("parent");
|
|
565
|
-
expect(channelConfig?.matchKey).toBe("parent-1");
|
|
566
|
-
expect(
|
|
567
|
-
resolveDiscordShouldRequireMention({
|
|
568
|
-
isGuildMessage: true,
|
|
569
|
-
isThread: true,
|
|
570
|
-
channelConfig,
|
|
571
|
-
guildInfo,
|
|
572
|
-
}),
|
|
573
|
-
).toBe(false);
|
|
574
|
-
});
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
describe("discord groupPolicy gating", () => {
|
|
578
|
-
it("applies open/disabled/allowlist policy rules", () => {
|
|
579
|
-
const cases = [
|
|
580
|
-
{
|
|
581
|
-
name: "open policy always allows",
|
|
582
|
-
input: {
|
|
583
|
-
groupPolicy: "open" as const,
|
|
584
|
-
guildAllowlisted: false,
|
|
585
|
-
channelAllowlistConfigured: false,
|
|
586
|
-
channelAllowed: false,
|
|
587
|
-
},
|
|
588
|
-
expected: true,
|
|
589
|
-
},
|
|
590
|
-
{
|
|
591
|
-
name: "disabled policy always blocks",
|
|
592
|
-
input: {
|
|
593
|
-
groupPolicy: "disabled" as const,
|
|
594
|
-
guildAllowlisted: true,
|
|
595
|
-
channelAllowlistConfigured: true,
|
|
596
|
-
channelAllowed: true,
|
|
597
|
-
},
|
|
598
|
-
expected: false,
|
|
599
|
-
},
|
|
600
|
-
{
|
|
601
|
-
name: "allowlist blocks when guild not allowlisted",
|
|
602
|
-
input: {
|
|
603
|
-
groupPolicy: "allowlist" as const,
|
|
604
|
-
guildAllowlisted: false,
|
|
605
|
-
channelAllowlistConfigured: false,
|
|
606
|
-
channelAllowed: true,
|
|
607
|
-
},
|
|
608
|
-
expected: false,
|
|
609
|
-
},
|
|
610
|
-
{
|
|
611
|
-
name: "allowlist allows when guild allowlisted and no channel allowlist",
|
|
612
|
-
input: {
|
|
613
|
-
groupPolicy: "allowlist" as const,
|
|
614
|
-
guildAllowlisted: true,
|
|
615
|
-
channelAllowlistConfigured: false,
|
|
616
|
-
channelAllowed: true,
|
|
617
|
-
},
|
|
618
|
-
expected: true,
|
|
619
|
-
},
|
|
620
|
-
{
|
|
621
|
-
name: "allowlist allows when channel is allowed",
|
|
622
|
-
input: {
|
|
623
|
-
groupPolicy: "allowlist" as const,
|
|
624
|
-
guildAllowlisted: true,
|
|
625
|
-
channelAllowlistConfigured: true,
|
|
626
|
-
channelAllowed: true,
|
|
627
|
-
},
|
|
628
|
-
expected: true,
|
|
629
|
-
},
|
|
630
|
-
{
|
|
631
|
-
name: "allowlist blocks when channel is not allowed",
|
|
632
|
-
input: {
|
|
633
|
-
groupPolicy: "allowlist" as const,
|
|
634
|
-
guildAllowlisted: true,
|
|
635
|
-
channelAllowlistConfigured: true,
|
|
636
|
-
channelAllowed: false,
|
|
637
|
-
},
|
|
638
|
-
expected: false,
|
|
639
|
-
},
|
|
640
|
-
] as const;
|
|
641
|
-
|
|
642
|
-
for (const testCase of cases) {
|
|
643
|
-
expect(isDiscordGroupAllowedByPolicy(testCase.input), testCase.name).toBe(testCase.expected);
|
|
644
|
-
}
|
|
645
|
-
});
|
|
646
|
-
});
|
|
647
|
-
|
|
648
|
-
describe("discord group DM gating", () => {
|
|
649
|
-
it("allows all when no allowlist", () => {
|
|
650
|
-
expect(
|
|
651
|
-
resolveGroupDmAllow({
|
|
652
|
-
channels: undefined,
|
|
653
|
-
channelId: "1",
|
|
654
|
-
channelName: "dm",
|
|
655
|
-
channelSlug: "dm",
|
|
656
|
-
}),
|
|
657
|
-
).toBe(true);
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
it("matches group DM allowlist", () => {
|
|
661
|
-
expect(
|
|
662
|
-
resolveGroupDmAllow({
|
|
663
|
-
channels: ["openclaw-dm"],
|
|
664
|
-
channelId: "1",
|
|
665
|
-
channelName: "OpenClaw DM",
|
|
666
|
-
channelSlug: "openclaw-dm",
|
|
667
|
-
}),
|
|
668
|
-
).toBe(true);
|
|
669
|
-
expect(
|
|
670
|
-
resolveGroupDmAllow({
|
|
671
|
-
channels: ["openclaw-dm"],
|
|
672
|
-
channelId: "1",
|
|
673
|
-
channelName: "Other",
|
|
674
|
-
channelSlug: "other",
|
|
675
|
-
}),
|
|
676
|
-
).toBe(false);
|
|
677
|
-
});
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
describe("discord reply target selection", () => {
|
|
681
|
-
it("handles off/first/all reply modes", () => {
|
|
682
|
-
const cases = [
|
|
683
|
-
{ name: "off mode", replyToMode: "off" as const, hasReplied: false, expected: undefined },
|
|
684
|
-
{
|
|
685
|
-
name: "first mode before reply",
|
|
686
|
-
replyToMode: "first" as const,
|
|
687
|
-
hasReplied: false,
|
|
688
|
-
expected: "123",
|
|
689
|
-
},
|
|
690
|
-
{
|
|
691
|
-
name: "first mode after reply",
|
|
692
|
-
replyToMode: "first" as const,
|
|
693
|
-
hasReplied: true,
|
|
694
|
-
expected: undefined,
|
|
695
|
-
},
|
|
696
|
-
{
|
|
697
|
-
name: "all mode before reply",
|
|
698
|
-
replyToMode: "all" as const,
|
|
699
|
-
hasReplied: false,
|
|
700
|
-
expected: "123",
|
|
701
|
-
},
|
|
702
|
-
{
|
|
703
|
-
name: "all mode after reply",
|
|
704
|
-
replyToMode: "all" as const,
|
|
705
|
-
hasReplied: true,
|
|
706
|
-
expected: "123",
|
|
707
|
-
},
|
|
708
|
-
] as const;
|
|
709
|
-
|
|
710
|
-
for (const testCase of cases) {
|
|
711
|
-
expect(
|
|
712
|
-
resolveDiscordReplyTarget({
|
|
713
|
-
replyToMode: testCase.replyToMode,
|
|
714
|
-
replyToId: "123",
|
|
715
|
-
hasReplied: testCase.hasReplied,
|
|
716
|
-
}),
|
|
717
|
-
testCase.name,
|
|
718
|
-
).toBe(testCase.expected);
|
|
719
|
-
}
|
|
720
|
-
});
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
describe("discord autoThread name sanitization", () => {
|
|
724
|
-
it("strips mentions and collapses whitespace", () => {
|
|
725
|
-
const name = sanitizeDiscordThreadName(" <@123> <@&456> <#789> Help here ", "msg-1");
|
|
726
|
-
expect(name).toBe("Help here");
|
|
727
|
-
});
|
|
728
|
-
|
|
729
|
-
it("falls back to thread + id when empty after cleaning", () => {
|
|
730
|
-
const name = sanitizeDiscordThreadName(" <@123>", "abc");
|
|
731
|
-
expect(name).toBe("Thread abc");
|
|
732
|
-
});
|
|
733
|
-
});
|
|
734
|
-
|
|
735
|
-
describe("discord reaction notification gating", () => {
|
|
736
|
-
it("applies mode-specific reaction notification rules", () => {
|
|
737
|
-
const cases = typedCases<{
|
|
738
|
-
name: string;
|
|
739
|
-
input: Parameters<typeof shouldEmitDiscordReactionNotification>[0];
|
|
740
|
-
expected: boolean;
|
|
741
|
-
}>([
|
|
742
|
-
{
|
|
743
|
-
name: "unset defaults to own (author is bot)",
|
|
744
|
-
input: {
|
|
745
|
-
mode: undefined,
|
|
746
|
-
botId: "bot-1",
|
|
747
|
-
messageAuthorId: "bot-1",
|
|
748
|
-
userId: "user-1",
|
|
749
|
-
},
|
|
750
|
-
expected: true,
|
|
751
|
-
},
|
|
752
|
-
{
|
|
753
|
-
name: "unset defaults to own (author is not bot)",
|
|
754
|
-
input: {
|
|
755
|
-
mode: undefined,
|
|
756
|
-
botId: "bot-1",
|
|
757
|
-
messageAuthorId: "user-1",
|
|
758
|
-
userId: "user-2",
|
|
759
|
-
},
|
|
760
|
-
expected: false,
|
|
761
|
-
},
|
|
762
|
-
{
|
|
763
|
-
name: "off mode",
|
|
764
|
-
input: {
|
|
765
|
-
mode: "off" as const,
|
|
766
|
-
botId: "bot-1",
|
|
767
|
-
messageAuthorId: "bot-1",
|
|
768
|
-
userId: "user-1",
|
|
769
|
-
},
|
|
770
|
-
expected: false,
|
|
771
|
-
},
|
|
772
|
-
{
|
|
773
|
-
name: "all mode",
|
|
774
|
-
input: {
|
|
775
|
-
mode: "all" as const,
|
|
776
|
-
botId: "bot-1",
|
|
777
|
-
messageAuthorId: "user-1",
|
|
778
|
-
userId: "user-2",
|
|
779
|
-
},
|
|
780
|
-
expected: true,
|
|
781
|
-
},
|
|
782
|
-
{
|
|
783
|
-
name: "all mode blocks non-allowlisted guild member",
|
|
784
|
-
input: {
|
|
785
|
-
mode: "all" as const,
|
|
786
|
-
botId: "bot-1",
|
|
787
|
-
messageAuthorId: "user-1",
|
|
788
|
-
userId: "user-2",
|
|
789
|
-
guildInfo: { users: ["trusted-user"] },
|
|
790
|
-
},
|
|
791
|
-
expected: false,
|
|
792
|
-
},
|
|
793
|
-
{
|
|
794
|
-
name: "own mode with bot-authored message",
|
|
795
|
-
input: {
|
|
796
|
-
mode: "own" as const,
|
|
797
|
-
botId: "bot-1",
|
|
798
|
-
messageAuthorId: "bot-1",
|
|
799
|
-
userId: "user-2",
|
|
800
|
-
},
|
|
801
|
-
expected: true,
|
|
802
|
-
},
|
|
803
|
-
{
|
|
804
|
-
name: "own mode with non-bot-authored message",
|
|
805
|
-
input: {
|
|
806
|
-
mode: "own" as const,
|
|
807
|
-
botId: "bot-1",
|
|
808
|
-
messageAuthorId: "user-2",
|
|
809
|
-
userId: "user-3",
|
|
810
|
-
},
|
|
811
|
-
expected: false,
|
|
812
|
-
},
|
|
813
|
-
{
|
|
814
|
-
name: "own mode still blocks member outside users allowlist",
|
|
815
|
-
input: {
|
|
816
|
-
mode: "own" as const,
|
|
817
|
-
botId: "bot-1",
|
|
818
|
-
messageAuthorId: "bot-1",
|
|
819
|
-
userId: "user-3",
|
|
820
|
-
guildInfo: { users: ["trusted-user"] },
|
|
821
|
-
},
|
|
822
|
-
expected: false,
|
|
823
|
-
},
|
|
824
|
-
{
|
|
825
|
-
name: "allowlist mode without match",
|
|
826
|
-
input: {
|
|
827
|
-
mode: "allowlist" as const,
|
|
828
|
-
botId: "bot-1",
|
|
829
|
-
messageAuthorId: "user-1",
|
|
830
|
-
userId: "user-2",
|
|
831
|
-
allowlist: [] as string[],
|
|
832
|
-
},
|
|
833
|
-
expected: false,
|
|
834
|
-
},
|
|
835
|
-
{
|
|
836
|
-
name: "allowlist mode with id match",
|
|
837
|
-
input: {
|
|
838
|
-
mode: "allowlist" as const,
|
|
839
|
-
botId: "bot-1",
|
|
840
|
-
messageAuthorId: "user-1",
|
|
841
|
-
userId: "123",
|
|
842
|
-
userName: "steipete",
|
|
843
|
-
guildInfo: { users: ["123", "other"] },
|
|
844
|
-
},
|
|
845
|
-
expected: true,
|
|
846
|
-
},
|
|
847
|
-
{
|
|
848
|
-
name: "allowlist mode does not match usernames by default",
|
|
849
|
-
input: {
|
|
850
|
-
mode: "allowlist" as const,
|
|
851
|
-
botId: "bot-1",
|
|
852
|
-
messageAuthorId: "user-1",
|
|
853
|
-
userId: "999",
|
|
854
|
-
userName: "trusted-user",
|
|
855
|
-
guildInfo: { users: ["trusted-user"] },
|
|
856
|
-
},
|
|
857
|
-
expected: false,
|
|
858
|
-
},
|
|
859
|
-
{
|
|
860
|
-
name: "allowlist mode matches usernames when explicitly enabled",
|
|
861
|
-
input: {
|
|
862
|
-
mode: "allowlist" as const,
|
|
863
|
-
botId: "bot-1",
|
|
864
|
-
messageAuthorId: "user-1",
|
|
865
|
-
userId: "999",
|
|
866
|
-
userName: "trusted-user",
|
|
867
|
-
guildInfo: { users: ["trusted-user"] },
|
|
868
|
-
allowNameMatching: true,
|
|
869
|
-
},
|
|
870
|
-
expected: true,
|
|
871
|
-
},
|
|
872
|
-
{
|
|
873
|
-
name: "allowlist mode matches allowed role",
|
|
874
|
-
input: {
|
|
875
|
-
mode: "allowlist" as const,
|
|
876
|
-
botId: "bot-1",
|
|
877
|
-
messageAuthorId: "user-1",
|
|
878
|
-
userId: "999",
|
|
879
|
-
guildInfo: { roles: ["role:trusted-role"] },
|
|
880
|
-
memberRoleIds: ["trusted-role"],
|
|
881
|
-
},
|
|
882
|
-
expected: true,
|
|
883
|
-
},
|
|
884
|
-
]);
|
|
885
|
-
|
|
886
|
-
for (const testCase of cases) {
|
|
887
|
-
expect(
|
|
888
|
-
shouldEmitDiscordReactionNotification({
|
|
889
|
-
...testCase.input,
|
|
890
|
-
}),
|
|
891
|
-
testCase.name,
|
|
892
|
-
).toBe(testCase.expected);
|
|
893
|
-
}
|
|
894
|
-
});
|
|
895
|
-
});
|
|
896
|
-
|
|
897
|
-
describe("discord media payload", () => {
|
|
898
|
-
it("preserves attachment order for MediaPaths/MediaUrls", () => {
|
|
899
|
-
const payload = buildDiscordMediaPayload([
|
|
900
|
-
{ path: "/tmp/a.png", contentType: "image/png" },
|
|
901
|
-
{ path: "/tmp/b.png", contentType: "image/png" },
|
|
902
|
-
{ path: "/tmp/c.png", contentType: "image/png" },
|
|
903
|
-
]);
|
|
904
|
-
expect(payload.MediaPath).toBe("/tmp/a.png");
|
|
905
|
-
expect(payload.MediaUrl).toBe("/tmp/a.png");
|
|
906
|
-
expect(payload.MediaType).toBe("image/png");
|
|
907
|
-
expect(payload.MediaPaths).toEqual(["/tmp/a.png", "/tmp/b.png", "/tmp/c.png"]);
|
|
908
|
-
expect(payload.MediaUrls).toEqual(["/tmp/a.png", "/tmp/b.png", "/tmp/c.png"]);
|
|
909
|
-
});
|
|
910
|
-
});
|
|
911
|
-
|
|
912
|
-
// --- DM reaction integration tests ---
|
|
913
|
-
// These test that handleDiscordReactionEvent (via DiscordReactionListener)
|
|
914
|
-
// properly handles DM reactions instead of silently dropping them.
|
|
915
|
-
|
|
916
|
-
const { enqueueSystemEventSpy, resolveAgentRouteMock } = vi.hoisted(() => ({
|
|
917
|
-
enqueueSystemEventSpy: vi.fn(),
|
|
918
|
-
resolveAgentRouteMock: vi.fn((params: unknown) => ({
|
|
919
|
-
agentId: "default",
|
|
920
|
-
channel: "discord",
|
|
921
|
-
accountId: "acc-1",
|
|
922
|
-
sessionKey: "discord:acc-1:dm:user-1",
|
|
923
|
-
mainSessionKey: "discord:acc-1:dm:user-1",
|
|
924
|
-
lastRoutePolicy: "session" as const,
|
|
925
|
-
matchedBy: "default" as const,
|
|
926
|
-
...(typeof params === "object" && params !== null ? { _params: params } : {}),
|
|
927
|
-
})),
|
|
928
|
-
}));
|
|
929
|
-
|
|
930
|
-
const channelRuntimeModule = await import("openclaw/plugin-sdk/system-event-runtime");
|
|
931
|
-
vi.spyOn(channelRuntimeModule, "enqueueSystemEvent").mockImplementation(enqueueSystemEventSpy);
|
|
932
|
-
|
|
933
|
-
const routingModule = await import("openclaw/plugin-sdk/routing");
|
|
934
|
-
vi.spyOn(routingModule, "resolveAgentRoute").mockImplementation(resolveAgentRouteMock);
|
|
935
|
-
|
|
936
|
-
const { DiscordMessageListener, DiscordReactionListener, registerDiscordListener } =
|
|
937
|
-
await import("./monitor/listeners.js");
|
|
938
|
-
|
|
939
|
-
function makeReactionEvent(overrides?: {
|
|
940
|
-
guildId?: string;
|
|
941
|
-
channelId?: string;
|
|
942
|
-
userId?: string;
|
|
943
|
-
messageId?: string;
|
|
944
|
-
emojiName?: string;
|
|
945
|
-
botAsAuthor?: boolean;
|
|
946
|
-
messageAuthorId?: string;
|
|
947
|
-
messageFetch?: ReturnType<typeof vi.fn>;
|
|
948
|
-
guild?: { name?: string; id?: string };
|
|
949
|
-
memberRoleIds?: string[];
|
|
950
|
-
}) {
|
|
951
|
-
const userId = overrides?.userId ?? "user-1";
|
|
952
|
-
const messageId = overrides?.messageId ?? "msg-1";
|
|
953
|
-
const channelId = overrides?.channelId ?? "channel-1";
|
|
954
|
-
const messageFetch =
|
|
955
|
-
overrides?.messageFetch ??
|
|
956
|
-
vi.fn(async () => ({
|
|
957
|
-
author: {
|
|
958
|
-
id: overrides?.messageAuthorId ?? (overrides?.botAsAuthor ? "bot-1" : "other-user"),
|
|
959
|
-
username: overrides?.botAsAuthor ? "bot" : "otheruser",
|
|
960
|
-
discriminator: "0",
|
|
961
|
-
},
|
|
962
|
-
}));
|
|
963
|
-
return {
|
|
964
|
-
guild_id: overrides?.guildId,
|
|
965
|
-
channel_id: channelId,
|
|
966
|
-
message_id: messageId,
|
|
967
|
-
emoji: { name: overrides?.emojiName ?? "👍", id: null },
|
|
968
|
-
guild: overrides?.guild,
|
|
969
|
-
rawMember: overrides?.memberRoleIds ? { roles: overrides.memberRoleIds } : undefined,
|
|
970
|
-
user: {
|
|
971
|
-
id: userId,
|
|
972
|
-
bot: false,
|
|
973
|
-
username: "testuser",
|
|
974
|
-
discriminator: "0",
|
|
975
|
-
},
|
|
976
|
-
message: {
|
|
977
|
-
fetch: messageFetch,
|
|
978
|
-
},
|
|
979
|
-
} as unknown as DiscordReactionEvent;
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
function makeReactionClient(options?: {
|
|
983
|
-
channelType?: ChannelType;
|
|
984
|
-
channelName?: string;
|
|
985
|
-
parentId?: string;
|
|
986
|
-
parentName?: string;
|
|
987
|
-
}) {
|
|
988
|
-
const channelType = options?.channelType ?? ChannelType.DM;
|
|
989
|
-
const channelName =
|
|
990
|
-
options?.channelName ?? (channelType === ChannelType.DM ? undefined : "test-channel");
|
|
991
|
-
const parentId = options?.parentId;
|
|
992
|
-
const parentName = options?.parentName ?? "parent-channel";
|
|
993
|
-
|
|
994
|
-
return {
|
|
995
|
-
fetchChannel: vi.fn(async (channelId: string) => {
|
|
996
|
-
if (parentId && channelId === parentId) {
|
|
997
|
-
return { type: ChannelType.GuildText, name: parentName, parentId: undefined };
|
|
998
|
-
}
|
|
999
|
-
return { type: channelType, name: channelName, parentId };
|
|
1000
|
-
}),
|
|
1001
|
-
} as unknown as DiscordReactionClient;
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
function getReactionClientFetchChannelMock(client: DiscordReactionClient) {
|
|
1005
|
-
return (client as unknown as { fetchChannel: ReturnType<typeof vi.fn> }).fetchChannel;
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
function makeReactionListenerParams(overrides?: {
|
|
1009
|
-
botUserId?: string;
|
|
1010
|
-
dmEnabled?: boolean;
|
|
1011
|
-
groupDmEnabled?: boolean;
|
|
1012
|
-
groupDmChannels?: string[];
|
|
1013
|
-
dmPolicy?: "open" | "pairing" | "allowlist" | "disabled";
|
|
1014
|
-
allowFrom?: string[];
|
|
1015
|
-
groupPolicy?: "open" | "allowlist" | "disabled";
|
|
1016
|
-
allowNameMatching?: boolean;
|
|
1017
|
-
guildEntries?: Record<string, DiscordGuildEntryResolved>;
|
|
1018
|
-
}) {
|
|
1019
|
-
return {
|
|
1020
|
-
cfg: {} as import("openclaw/plugin-sdk/config-types").OpenClawConfig,
|
|
1021
|
-
accountId: "acc-1",
|
|
1022
|
-
runtime: {} as import("openclaw/plugin-sdk/runtime-env").RuntimeEnv,
|
|
1023
|
-
botUserId: overrides?.botUserId ?? "bot-1",
|
|
1024
|
-
dmEnabled: overrides?.dmEnabled ?? true,
|
|
1025
|
-
groupDmEnabled: overrides?.groupDmEnabled ?? true,
|
|
1026
|
-
groupDmChannels: overrides?.groupDmChannels ?? [],
|
|
1027
|
-
dmPolicy: overrides?.dmPolicy ?? "open",
|
|
1028
|
-
allowFrom: overrides?.allowFrom ?? ["*"],
|
|
1029
|
-
groupPolicy: overrides?.groupPolicy ?? "open",
|
|
1030
|
-
allowNameMatching: overrides?.allowNameMatching ?? false,
|
|
1031
|
-
guildEntries: overrides?.guildEntries,
|
|
1032
|
-
logger: {
|
|
1033
|
-
info: vi.fn(),
|
|
1034
|
-
warn: vi.fn(),
|
|
1035
|
-
error: vi.fn(),
|
|
1036
|
-
debug: vi.fn(),
|
|
1037
|
-
} as unknown as ReturnType<
|
|
1038
|
-
typeof import("openclaw/plugin-sdk/logging-core").createSubsystemLogger
|
|
1039
|
-
>,
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
describe("discord DM reaction handling", () => {
|
|
1044
|
-
beforeEach(() => {
|
|
1045
|
-
enqueueSystemEventSpy.mockClear();
|
|
1046
|
-
resolveAgentRouteMock.mockClear();
|
|
1047
|
-
readAllowFromStoreMock.mockReset().mockResolvedValue([]);
|
|
1048
|
-
});
|
|
1049
|
-
|
|
1050
|
-
it("processes DM reactions with or without guild allowlists", async () => {
|
|
1051
|
-
const cases = [
|
|
1052
|
-
{ name: "no guild allowlist", guildEntries: undefined },
|
|
1053
|
-
{
|
|
1054
|
-
name: "guild allowlist configured",
|
|
1055
|
-
guildEntries: makeEntries({
|
|
1056
|
-
"guild-123": { slug: "guild-123" },
|
|
1057
|
-
}),
|
|
1058
|
-
},
|
|
1059
|
-
] as const;
|
|
1060
|
-
|
|
1061
|
-
for (const testCase of cases) {
|
|
1062
|
-
enqueueSystemEventSpy.mockClear();
|
|
1063
|
-
resolveAgentRouteMock.mockClear();
|
|
1064
|
-
|
|
1065
|
-
const data = makeReactionEvent({ botAsAuthor: true });
|
|
1066
|
-
const client = makeReactionClient({ channelType: ChannelType.DM });
|
|
1067
|
-
const listener = new DiscordReactionListener(
|
|
1068
|
-
makeReactionListenerParams({ guildEntries: testCase.guildEntries }),
|
|
1069
|
-
);
|
|
1070
|
-
|
|
1071
|
-
await listener.handle(data, client);
|
|
1072
|
-
|
|
1073
|
-
expect(enqueueSystemEventSpy, testCase.name).toHaveBeenCalledOnce();
|
|
1074
|
-
const [text, opts] = enqueueSystemEventSpy.mock.calls[0];
|
|
1075
|
-
expect(text, testCase.name).toContain("Discord reaction added");
|
|
1076
|
-
expect(text, testCase.name).toContain("👍");
|
|
1077
|
-
expect(text, testCase.name).toContain("dm");
|
|
1078
|
-
expect(text, testCase.name).not.toContain("undefined");
|
|
1079
|
-
expect(opts.sessionKey, testCase.name).toBe("discord:acc-1:dm:user-1");
|
|
1080
|
-
}
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
it("blocks DM reactions when dmPolicy is disabled", async () => {
|
|
1084
|
-
const data = makeReactionEvent({ botAsAuthor: true });
|
|
1085
|
-
const client = makeReactionClient({ channelType: ChannelType.DM });
|
|
1086
|
-
const listener = new DiscordReactionListener(
|
|
1087
|
-
makeReactionListenerParams({ dmPolicy: "disabled" }),
|
|
1088
|
-
);
|
|
1089
|
-
|
|
1090
|
-
await listener.handle(data, client);
|
|
1091
|
-
|
|
1092
|
-
expect(enqueueSystemEventSpy).not.toHaveBeenCalled();
|
|
1093
|
-
});
|
|
1094
|
-
|
|
1095
|
-
it("blocks DM reactions for unauthorized sender in allowlist mode", async () => {
|
|
1096
|
-
const data = makeReactionEvent({ botAsAuthor: true, userId: "user-1" });
|
|
1097
|
-
const client = makeReactionClient({ channelType: ChannelType.DM });
|
|
1098
|
-
const listener = new DiscordReactionListener(
|
|
1099
|
-
makeReactionListenerParams({
|
|
1100
|
-
dmPolicy: "allowlist",
|
|
1101
|
-
allowFrom: ["user:user-2"],
|
|
1102
|
-
}),
|
|
1103
|
-
);
|
|
1104
|
-
|
|
1105
|
-
await listener.handle(data, client);
|
|
1106
|
-
|
|
1107
|
-
expect(enqueueSystemEventSpy).not.toHaveBeenCalled();
|
|
1108
|
-
});
|
|
1109
|
-
|
|
1110
|
-
it("allows DM reactions for authorized sender in allowlist mode", async () => {
|
|
1111
|
-
const data = makeReactionEvent({ botAsAuthor: true, userId: "user-1" });
|
|
1112
|
-
const client = makeReactionClient({ channelType: ChannelType.DM });
|
|
1113
|
-
const listener = new DiscordReactionListener(
|
|
1114
|
-
makeReactionListenerParams({
|
|
1115
|
-
dmPolicy: "allowlist",
|
|
1116
|
-
allowFrom: ["user:user-1"],
|
|
1117
|
-
}),
|
|
1118
|
-
);
|
|
1119
|
-
|
|
1120
|
-
await listener.handle(data, client);
|
|
1121
|
-
|
|
1122
|
-
expect(enqueueSystemEventSpy).toHaveBeenCalledOnce();
|
|
1123
|
-
});
|
|
1124
|
-
|
|
1125
|
-
it("blocks group DM reactions when group DMs are disabled", async () => {
|
|
1126
|
-
const data = makeReactionEvent({ botAsAuthor: true });
|
|
1127
|
-
const client = makeReactionClient({ channelType: ChannelType.GroupDM });
|
|
1128
|
-
const listener = new DiscordReactionListener(
|
|
1129
|
-
makeReactionListenerParams({ groupDmEnabled: false }),
|
|
1130
|
-
);
|
|
1131
|
-
|
|
1132
|
-
await listener.handle(data, client);
|
|
1133
|
-
|
|
1134
|
-
expect(enqueueSystemEventSpy).not.toHaveBeenCalled();
|
|
1135
|
-
});
|
|
1136
|
-
|
|
1137
|
-
it("blocks guild reactions when groupPolicy is disabled", async () => {
|
|
1138
|
-
const data = makeReactionEvent({
|
|
1139
|
-
guildId: "guild-123",
|
|
1140
|
-
botAsAuthor: true,
|
|
1141
|
-
guild: { id: "guild-123", name: "Guild" },
|
|
1142
|
-
});
|
|
1143
|
-
const client = makeReactionClient({ channelType: ChannelType.GuildText });
|
|
1144
|
-
const listener = new DiscordReactionListener(
|
|
1145
|
-
makeReactionListenerParams({ groupPolicy: "disabled" }),
|
|
1146
|
-
);
|
|
1147
|
-
|
|
1148
|
-
await listener.handle(data, client);
|
|
1149
|
-
|
|
1150
|
-
expect(getReactionClientFetchChannelMock(client)).not.toHaveBeenCalled();
|
|
1151
|
-
expect(enqueueSystemEventSpy).not.toHaveBeenCalled();
|
|
1152
|
-
});
|
|
1153
|
-
|
|
1154
|
-
it("blocks guild reactions for sender outside users allowlist", async () => {
|
|
1155
|
-
const data = makeReactionEvent({
|
|
1156
|
-
guildId: "guild-123",
|
|
1157
|
-
userId: "attacker-user",
|
|
1158
|
-
botAsAuthor: true,
|
|
1159
|
-
guild: { id: "guild-123", name: "Test Guild" },
|
|
1160
|
-
});
|
|
1161
|
-
const client = makeReactionClient({ channelType: ChannelType.GuildText });
|
|
1162
|
-
const listener = new DiscordReactionListener(
|
|
1163
|
-
makeReactionListenerParams({
|
|
1164
|
-
guildEntries: makeEntries({
|
|
1165
|
-
"guild-123": {
|
|
1166
|
-
users: ["user:trusted-user"],
|
|
1167
|
-
},
|
|
1168
|
-
}),
|
|
1169
|
-
}),
|
|
1170
|
-
);
|
|
1171
|
-
|
|
1172
|
-
await listener.handle(data, client);
|
|
1173
|
-
|
|
1174
|
-
expect(enqueueSystemEventSpy).not.toHaveBeenCalled();
|
|
1175
|
-
expect(resolveAgentRouteMock).not.toHaveBeenCalled();
|
|
1176
|
-
});
|
|
1177
|
-
|
|
1178
|
-
it("allows guild reactions for sender in channel role allowlist override", async () => {
|
|
1179
|
-
resolveAgentRouteMock.mockReturnValueOnce({
|
|
1180
|
-
agentId: "default",
|
|
1181
|
-
channel: "discord",
|
|
1182
|
-
accountId: "acc-1",
|
|
1183
|
-
sessionKey: "discord:acc-1:guild-123:channel-1",
|
|
1184
|
-
mainSessionKey: "discord:acc-1:guild-123:channel-1",
|
|
1185
|
-
lastRoutePolicy: "session",
|
|
1186
|
-
matchedBy: "default",
|
|
1187
|
-
});
|
|
1188
|
-
|
|
1189
|
-
const data = makeReactionEvent({
|
|
1190
|
-
guildId: "guild-123",
|
|
1191
|
-
userId: "member-user",
|
|
1192
|
-
botAsAuthor: true,
|
|
1193
|
-
guild: { id: "guild-123", name: "Test Guild" },
|
|
1194
|
-
memberRoleIds: ["trusted-role"],
|
|
1195
|
-
});
|
|
1196
|
-
const client = makeReactionClient({ channelType: ChannelType.GuildText });
|
|
1197
|
-
const listener = new DiscordReactionListener(
|
|
1198
|
-
makeReactionListenerParams({
|
|
1199
|
-
guildEntries: makeEntries({
|
|
1200
|
-
"guild-123": {
|
|
1201
|
-
roles: ["role:blocked-role"],
|
|
1202
|
-
channels: {
|
|
1203
|
-
"channel-1": {
|
|
1204
|
-
enabled: true,
|
|
1205
|
-
roles: ["role:trusted-role"],
|
|
1206
|
-
},
|
|
1207
|
-
},
|
|
1208
|
-
},
|
|
1209
|
-
}),
|
|
1210
|
-
}),
|
|
1211
|
-
);
|
|
1212
|
-
|
|
1213
|
-
await listener.handle(data, client);
|
|
1214
|
-
|
|
1215
|
-
expect(getReactionClientFetchChannelMock(client)).toHaveBeenCalled();
|
|
1216
|
-
expect(enqueueSystemEventSpy).toHaveBeenCalledOnce();
|
|
1217
|
-
const [text] = enqueueSystemEventSpy.mock.calls[0];
|
|
1218
|
-
expect(text).toContain("Discord reaction added");
|
|
1219
|
-
});
|
|
1220
|
-
|
|
1221
|
-
it("routes DM reactions with peer kind 'direct' and user id", async () => {
|
|
1222
|
-
enqueueSystemEventSpy.mockClear();
|
|
1223
|
-
resolveAgentRouteMock.mockClear();
|
|
1224
|
-
|
|
1225
|
-
const data = makeReactionEvent({ userId: "user-42", botAsAuthor: true });
|
|
1226
|
-
const client = makeReactionClient({ channelType: ChannelType.DM });
|
|
1227
|
-
const listener = new DiscordReactionListener(makeReactionListenerParams());
|
|
1228
|
-
|
|
1229
|
-
await listener.handle(data, client);
|
|
1230
|
-
|
|
1231
|
-
expect(resolveAgentRouteMock).toHaveBeenCalledOnce();
|
|
1232
|
-
const routeArgs = (resolveAgentRouteMock.mock.calls[0]?.[0] ?? {}) as {
|
|
1233
|
-
peer?: unknown;
|
|
1234
|
-
};
|
|
1235
|
-
if (!routeArgs) {
|
|
1236
|
-
throw new Error("expected route arguments");
|
|
1237
|
-
}
|
|
1238
|
-
expect(routeArgs.peer).toEqual({ kind: "direct", id: "user-42" });
|
|
1239
|
-
});
|
|
1240
|
-
|
|
1241
|
-
it("routes group DM reactions with peer kind 'group'", async () => {
|
|
1242
|
-
enqueueSystemEventSpy.mockClear();
|
|
1243
|
-
resolveAgentRouteMock.mockClear();
|
|
1244
|
-
|
|
1245
|
-
const data = makeReactionEvent({ botAsAuthor: true });
|
|
1246
|
-
const client = makeReactionClient({ channelType: ChannelType.GroupDM });
|
|
1247
|
-
const listener = new DiscordReactionListener(makeReactionListenerParams());
|
|
1248
|
-
|
|
1249
|
-
await listener.handle(data, client);
|
|
1250
|
-
|
|
1251
|
-
expect(resolveAgentRouteMock).toHaveBeenCalledOnce();
|
|
1252
|
-
const routeArgs = (resolveAgentRouteMock.mock.calls[0]?.[0] ?? {}) as {
|
|
1253
|
-
peer?: unknown;
|
|
1254
|
-
};
|
|
1255
|
-
if (!routeArgs) {
|
|
1256
|
-
throw new Error("expected route arguments");
|
|
1257
|
-
}
|
|
1258
|
-
expect(routeArgs.peer).toEqual({ kind: "group", id: "channel-1" });
|
|
1259
|
-
});
|
|
1260
|
-
});
|
|
1261
|
-
|
|
1262
|
-
describe("discord reaction notification modes", () => {
|
|
1263
|
-
const guildId = "guild-900";
|
|
1264
|
-
const guild = fakeGuild(guildId, "Mode Guild");
|
|
1265
|
-
|
|
1266
|
-
it("applies message-fetch behavior across notification modes and channel types", async () => {
|
|
1267
|
-
const cases = typedCases<{
|
|
1268
|
-
name: string;
|
|
1269
|
-
reactionNotifications: "off" | "all" | "allowlist" | "own";
|
|
1270
|
-
users: string[] | undefined;
|
|
1271
|
-
userId: string | undefined;
|
|
1272
|
-
channelType: ChannelType;
|
|
1273
|
-
channelId: string | undefined;
|
|
1274
|
-
parentId: string | undefined;
|
|
1275
|
-
messageAuthorId: string;
|
|
1276
|
-
expectedFetchChannelCalls: number;
|
|
1277
|
-
expectedMessageFetchCalls: number;
|
|
1278
|
-
expectedEnqueueCalls: number;
|
|
1279
|
-
}>([
|
|
1280
|
-
{
|
|
1281
|
-
name: "off mode",
|
|
1282
|
-
reactionNotifications: "off" as const,
|
|
1283
|
-
users: undefined,
|
|
1284
|
-
userId: undefined,
|
|
1285
|
-
channelType: ChannelType.GuildText,
|
|
1286
|
-
channelId: undefined,
|
|
1287
|
-
parentId: undefined,
|
|
1288
|
-
messageAuthorId: "other-user",
|
|
1289
|
-
expectedFetchChannelCalls: 0,
|
|
1290
|
-
expectedMessageFetchCalls: 0,
|
|
1291
|
-
expectedEnqueueCalls: 0,
|
|
1292
|
-
},
|
|
1293
|
-
{
|
|
1294
|
-
name: "all mode",
|
|
1295
|
-
reactionNotifications: "all" as const,
|
|
1296
|
-
users: undefined,
|
|
1297
|
-
userId: undefined,
|
|
1298
|
-
channelType: ChannelType.GuildText,
|
|
1299
|
-
channelId: undefined,
|
|
1300
|
-
parentId: undefined,
|
|
1301
|
-
messageAuthorId: "other-user",
|
|
1302
|
-
expectedFetchChannelCalls: 1,
|
|
1303
|
-
expectedMessageFetchCalls: 0,
|
|
1304
|
-
expectedEnqueueCalls: 1,
|
|
1305
|
-
},
|
|
1306
|
-
{
|
|
1307
|
-
name: "allowlist mode",
|
|
1308
|
-
reactionNotifications: "allowlist" as const,
|
|
1309
|
-
users: ["123"] as string[],
|
|
1310
|
-
userId: "123",
|
|
1311
|
-
channelType: ChannelType.GuildText,
|
|
1312
|
-
channelId: undefined,
|
|
1313
|
-
parentId: undefined,
|
|
1314
|
-
messageAuthorId: "other-user",
|
|
1315
|
-
expectedFetchChannelCalls: 1,
|
|
1316
|
-
expectedMessageFetchCalls: 0,
|
|
1317
|
-
expectedEnqueueCalls: 1,
|
|
1318
|
-
},
|
|
1319
|
-
{
|
|
1320
|
-
name: "allowlist mode denied without channel overrides",
|
|
1321
|
-
reactionNotifications: "allowlist" as const,
|
|
1322
|
-
users: ["trusted-user"] as string[],
|
|
1323
|
-
userId: "untrusted-user",
|
|
1324
|
-
channelType: ChannelType.GuildText,
|
|
1325
|
-
channelId: undefined,
|
|
1326
|
-
parentId: undefined,
|
|
1327
|
-
messageAuthorId: "other-user",
|
|
1328
|
-
expectedFetchChannelCalls: 0,
|
|
1329
|
-
expectedMessageFetchCalls: 0,
|
|
1330
|
-
expectedEnqueueCalls: 0,
|
|
1331
|
-
},
|
|
1332
|
-
{
|
|
1333
|
-
name: "own mode",
|
|
1334
|
-
reactionNotifications: "own" as const,
|
|
1335
|
-
users: undefined,
|
|
1336
|
-
userId: undefined,
|
|
1337
|
-
channelType: ChannelType.GuildText,
|
|
1338
|
-
channelId: undefined,
|
|
1339
|
-
parentId: undefined,
|
|
1340
|
-
messageAuthorId: "bot-1",
|
|
1341
|
-
expectedFetchChannelCalls: 1,
|
|
1342
|
-
expectedMessageFetchCalls: 1,
|
|
1343
|
-
expectedEnqueueCalls: 1,
|
|
1344
|
-
},
|
|
1345
|
-
{
|
|
1346
|
-
name: "all mode thread channel",
|
|
1347
|
-
reactionNotifications: "all" as const,
|
|
1348
|
-
users: undefined,
|
|
1349
|
-
userId: undefined,
|
|
1350
|
-
channelType: ChannelType.PublicThread,
|
|
1351
|
-
channelId: "thread-1",
|
|
1352
|
-
parentId: "parent-1",
|
|
1353
|
-
messageAuthorId: "other-user",
|
|
1354
|
-
expectedFetchChannelCalls: 2,
|
|
1355
|
-
expectedMessageFetchCalls: 0,
|
|
1356
|
-
expectedEnqueueCalls: 1,
|
|
1357
|
-
},
|
|
1358
|
-
]);
|
|
1359
|
-
|
|
1360
|
-
for (const testCase of cases) {
|
|
1361
|
-
enqueueSystemEventSpy.mockClear();
|
|
1362
|
-
resolveAgentRouteMock.mockClear();
|
|
1363
|
-
|
|
1364
|
-
const messageFetch = vi.fn(async () => ({
|
|
1365
|
-
author: { id: testCase.messageAuthorId, username: "author", discriminator: "0" },
|
|
1366
|
-
}));
|
|
1367
|
-
const data = makeReactionEvent({
|
|
1368
|
-
guildId,
|
|
1369
|
-
guild,
|
|
1370
|
-
userId: testCase.userId,
|
|
1371
|
-
channelId: testCase.channelId,
|
|
1372
|
-
messageFetch,
|
|
1373
|
-
});
|
|
1374
|
-
const client = makeReactionClient({
|
|
1375
|
-
channelType: testCase.channelType,
|
|
1376
|
-
parentId: testCase.parentId,
|
|
1377
|
-
});
|
|
1378
|
-
const guildEntries = makeEntries({
|
|
1379
|
-
[guildId]: {
|
|
1380
|
-
reactionNotifications: testCase.reactionNotifications,
|
|
1381
|
-
users: testCase.users ? [...testCase.users] : undefined,
|
|
1382
|
-
},
|
|
1383
|
-
});
|
|
1384
|
-
const listener = new DiscordReactionListener(makeReactionListenerParams({ guildEntries }));
|
|
1385
|
-
|
|
1386
|
-
await listener.handle(data, client);
|
|
1387
|
-
|
|
1388
|
-
expect(getReactionClientFetchChannelMock(client), testCase.name).toHaveBeenCalledTimes(
|
|
1389
|
-
testCase.expectedFetchChannelCalls,
|
|
1390
|
-
);
|
|
1391
|
-
expect(messageFetch, testCase.name).toHaveBeenCalledTimes(testCase.expectedMessageFetchCalls);
|
|
1392
|
-
expect(enqueueSystemEventSpy, testCase.name).toHaveBeenCalledTimes(
|
|
1393
|
-
testCase.expectedEnqueueCalls,
|
|
1394
|
-
);
|
|
1395
|
-
}
|
|
1396
|
-
});
|
|
1397
|
-
});
|