@openclaw/discord 2026.3.2 → 2026.3.8-beta.1
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/index.ts +2 -2
- package/package.json +1 -1
- package/src/channel.test.ts +1 -1
- package/src/channel.ts +77 -77
- package/src/runtime.ts +5 -13
- package/src/subagent-hooks.test.ts +2 -2
- package/src/subagent-hooks.ts +2 -2
package/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
-
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/discord";
|
|
2
|
+
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/discord";
|
|
3
3
|
import { discordPlugin } from "./src/channel.js";
|
|
4
4
|
import { setDiscordRuntime } from "./src/runtime.js";
|
|
5
5
|
import { registerDiscordSubagentHooks } from "./src/subagent-hooks.js";
|
package/package.json
CHANGED
package/src/channel.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/discord";
|
|
2
2
|
import { describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { discordPlugin } from "./channel.js";
|
|
4
4
|
import { setDiscordRuntime } from "./runtime.js";
|
package/src/channel.ts
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
|
+
import { createScopedChannelConfigBase } from "openclaw/plugin-sdk/compat";
|
|
2
|
+
import {
|
|
3
|
+
buildAccountScopedDmSecurityPolicy,
|
|
4
|
+
collectOpenProviderGroupPolicyWarnings,
|
|
5
|
+
collectOpenGroupPolicyConfiguredRouteWarnings,
|
|
6
|
+
createScopedAccountConfigAccessors,
|
|
7
|
+
formatAllowFromLowercase,
|
|
8
|
+
} from "openclaw/plugin-sdk/compat";
|
|
1
9
|
import {
|
|
2
10
|
applyAccountNameToChannelSection,
|
|
11
|
+
buildComputedAccountStatusSnapshot,
|
|
3
12
|
buildChannelConfigSchema,
|
|
4
13
|
buildTokenChannelStatusSummary,
|
|
5
14
|
collectDiscordAuditChannelIds,
|
|
6
15
|
collectDiscordStatusIssues,
|
|
7
16
|
DEFAULT_ACCOUNT_ID,
|
|
8
|
-
deleteAccountFromConfigSection,
|
|
9
17
|
discordOnboardingAdapter,
|
|
10
18
|
DiscordConfigSchema,
|
|
11
|
-
formatPairingApproveHint,
|
|
12
19
|
getChatChannelMeta,
|
|
20
|
+
inspectDiscordAccount,
|
|
13
21
|
listDiscordAccountIds,
|
|
14
22
|
listDiscordDirectoryGroupsFromConfig,
|
|
15
23
|
listDiscordDirectoryPeersFromConfig,
|
|
@@ -19,17 +27,16 @@ import {
|
|
|
19
27
|
normalizeDiscordMessagingTarget,
|
|
20
28
|
normalizeDiscordOutboundTarget,
|
|
21
29
|
PAIRING_APPROVED_MESSAGE,
|
|
30
|
+
projectCredentialSnapshotFields,
|
|
31
|
+
resolveConfiguredFromCredentialStatuses,
|
|
22
32
|
resolveDiscordAccount,
|
|
23
33
|
resolveDefaultDiscordAccountId,
|
|
24
34
|
resolveDiscordGroupRequireMention,
|
|
25
35
|
resolveDiscordGroupToolPolicy,
|
|
26
|
-
resolveOpenProviderRuntimeGroupPolicy,
|
|
27
|
-
resolveDefaultGroupPolicy,
|
|
28
|
-
setAccountEnabledInConfigSection,
|
|
29
36
|
type ChannelMessageActionAdapter,
|
|
30
37
|
type ChannelPlugin,
|
|
31
38
|
type ResolvedDiscordAccount,
|
|
32
|
-
} from "openclaw/plugin-sdk";
|
|
39
|
+
} from "openclaw/plugin-sdk/discord";
|
|
33
40
|
import { getDiscordRuntime } from "./runtime.js";
|
|
34
41
|
|
|
35
42
|
const meta = getChatChannelMeta("discord");
|
|
@@ -48,6 +55,22 @@ const discordMessageActions: ChannelMessageActionAdapter = {
|
|
|
48
55
|
},
|
|
49
56
|
};
|
|
50
57
|
|
|
58
|
+
const discordConfigAccessors = createScopedAccountConfigAccessors({
|
|
59
|
+
resolveAccount: ({ cfg, accountId }) => resolveDiscordAccount({ cfg, accountId }),
|
|
60
|
+
resolveAllowFrom: (account: ResolvedDiscordAccount) => account.config.dm?.allowFrom,
|
|
61
|
+
formatAllowFrom: (allowFrom) => formatAllowFromLowercase({ allowFrom }),
|
|
62
|
+
resolveDefaultTo: (account: ResolvedDiscordAccount) => account.config.defaultTo,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const discordConfigBase = createScopedChannelConfigBase({
|
|
66
|
+
sectionKey: "discord",
|
|
67
|
+
listAccountIds: listDiscordAccountIds,
|
|
68
|
+
resolveAccount: (cfg, accountId) => resolveDiscordAccount({ cfg, accountId }),
|
|
69
|
+
inspectAccount: (cfg, accountId) => inspectDiscordAccount({ cfg, accountId }),
|
|
70
|
+
defaultAccountId: resolveDefaultDiscordAccountId,
|
|
71
|
+
clearBaseFields: ["token", "name"],
|
|
72
|
+
});
|
|
73
|
+
|
|
51
74
|
export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
|
52
75
|
id: "discord",
|
|
53
76
|
meta: {
|
|
@@ -78,24 +101,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
|
|
78
101
|
reload: { configPrefixes: ["channels.discord"] },
|
|
79
102
|
configSchema: buildChannelConfigSchema(DiscordConfigSchema),
|
|
80
103
|
config: {
|
|
81
|
-
|
|
82
|
-
resolveAccount: (cfg, accountId) => resolveDiscordAccount({ cfg, accountId }),
|
|
83
|
-
defaultAccountId: (cfg) => resolveDefaultDiscordAccountId(cfg),
|
|
84
|
-
setAccountEnabled: ({ cfg, accountId, enabled }) =>
|
|
85
|
-
setAccountEnabledInConfigSection({
|
|
86
|
-
cfg,
|
|
87
|
-
sectionKey: "discord",
|
|
88
|
-
accountId,
|
|
89
|
-
enabled,
|
|
90
|
-
allowTopLevel: true,
|
|
91
|
-
}),
|
|
92
|
-
deleteAccount: ({ cfg, accountId }) =>
|
|
93
|
-
deleteAccountFromConfigSection({
|
|
94
|
-
cfg,
|
|
95
|
-
sectionKey: "discord",
|
|
96
|
-
accountId,
|
|
97
|
-
clearBaseFields: ["token", "name"],
|
|
98
|
-
}),
|
|
104
|
+
...discordConfigBase,
|
|
99
105
|
isConfigured: (account) => Boolean(account.token?.trim()),
|
|
100
106
|
describeAccount: (account) => ({
|
|
101
107
|
accountId: account.accountId,
|
|
@@ -104,58 +110,49 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
|
|
104
110
|
configured: Boolean(account.token?.trim()),
|
|
105
111
|
tokenSource: account.tokenSource,
|
|
106
112
|
}),
|
|
107
|
-
|
|
108
|
-
(resolveDiscordAccount({ cfg, accountId }).config.dm?.allowFrom ?? []).map((entry) =>
|
|
109
|
-
String(entry),
|
|
110
|
-
),
|
|
111
|
-
formatAllowFrom: ({ allowFrom }) =>
|
|
112
|
-
allowFrom
|
|
113
|
-
.map((entry) => String(entry).trim())
|
|
114
|
-
.filter(Boolean)
|
|
115
|
-
.map((entry) => entry.toLowerCase()),
|
|
116
|
-
resolveDefaultTo: ({ cfg, accountId }) =>
|
|
117
|
-
resolveDiscordAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined,
|
|
113
|
+
...discordConfigAccessors,
|
|
118
114
|
},
|
|
119
115
|
security: {
|
|
120
116
|
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
:
|
|
126
|
-
|
|
127
|
-
policy: account.config.dm?.policy ?? "pairing",
|
|
117
|
+
return buildAccountScopedDmSecurityPolicy({
|
|
118
|
+
cfg,
|
|
119
|
+
channelKey: "discord",
|
|
120
|
+
accountId,
|
|
121
|
+
fallbackAccountId: account.accountId ?? DEFAULT_ACCOUNT_ID,
|
|
122
|
+
policy: account.config.dm?.policy,
|
|
128
123
|
allowFrom: account.config.dm?.allowFrom ?? [],
|
|
129
|
-
|
|
130
|
-
approveHint: formatPairingApproveHint("discord"),
|
|
124
|
+
allowFromPathSuffix: "dm.",
|
|
131
125
|
normalizeEntry: (raw) => raw.replace(/^(discord|user):/i, "").replace(/^<@!?(\d+)>$/, "$1"),
|
|
132
|
-
};
|
|
126
|
+
});
|
|
133
127
|
},
|
|
134
128
|
collectWarnings: ({ account, cfg }) => {
|
|
135
|
-
const warnings: string[] = [];
|
|
136
|
-
const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg);
|
|
137
|
-
const { groupPolicy } = resolveOpenProviderRuntimeGroupPolicy({
|
|
138
|
-
providerConfigPresent: cfg.channels?.discord !== undefined,
|
|
139
|
-
groupPolicy: account.config.groupPolicy,
|
|
140
|
-
defaultGroupPolicy,
|
|
141
|
-
});
|
|
142
129
|
const guildEntries = account.config.guilds ?? {};
|
|
143
130
|
const guildsConfigured = Object.keys(guildEntries).length > 0;
|
|
144
131
|
const channelAllowlistConfigured = guildsConfigured;
|
|
145
132
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
133
|
+
return collectOpenProviderGroupPolicyWarnings({
|
|
134
|
+
cfg,
|
|
135
|
+
providerConfigPresent: cfg.channels?.discord !== undefined,
|
|
136
|
+
configuredGroupPolicy: account.config.groupPolicy,
|
|
137
|
+
collect: (groupPolicy) =>
|
|
138
|
+
collectOpenGroupPolicyConfiguredRouteWarnings({
|
|
139
|
+
groupPolicy,
|
|
140
|
+
routeAllowlistConfigured: channelAllowlistConfigured,
|
|
141
|
+
configureRouteAllowlist: {
|
|
142
|
+
surface: "Discord guilds",
|
|
143
|
+
openScope: "any channel not explicitly denied",
|
|
144
|
+
groupPolicyPath: "channels.discord.groupPolicy",
|
|
145
|
+
routeAllowlistPath: "channels.discord.guilds.<id>.channels",
|
|
146
|
+
},
|
|
147
|
+
missingRouteAllowlist: {
|
|
148
|
+
surface: "Discord guilds",
|
|
149
|
+
openBehavior:
|
|
150
|
+
"with no guild/channel allowlist; any channel can trigger (mention-gated)",
|
|
151
|
+
remediation:
|
|
152
|
+
'Set channels.discord.groupPolicy="allowlist" and configure channels.discord.guilds.<id>.channels',
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
155
|
+
});
|
|
159
156
|
},
|
|
160
157
|
},
|
|
161
158
|
groups: {
|
|
@@ -302,10 +299,11 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
|
|
302
299
|
textChunkLimit: 2000,
|
|
303
300
|
pollMaxOptions: 10,
|
|
304
301
|
resolveTarget: ({ to }) => normalizeDiscordOutboundTarget(to),
|
|
305
|
-
sendText: async ({ to, text, accountId, deps, replyToId, silent }) => {
|
|
302
|
+
sendText: async ({ cfg, to, text, accountId, deps, replyToId, silent }) => {
|
|
306
303
|
const send = deps?.sendDiscord ?? getDiscordRuntime().channel.discord.sendMessageDiscord;
|
|
307
304
|
const result = await send(to, text, {
|
|
308
305
|
verbose: false,
|
|
306
|
+
cfg,
|
|
309
307
|
replyTo: replyToId ?? undefined,
|
|
310
308
|
accountId: accountId ?? undefined,
|
|
311
309
|
silent: silent ?? undefined,
|
|
@@ -313,6 +311,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
|
|
313
311
|
return { channel: "discord", ...result };
|
|
314
312
|
},
|
|
315
313
|
sendMedia: async ({
|
|
314
|
+
cfg,
|
|
316
315
|
to,
|
|
317
316
|
text,
|
|
318
317
|
mediaUrl,
|
|
@@ -325,6 +324,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
|
|
325
324
|
const send = deps?.sendDiscord ?? getDiscordRuntime().channel.discord.sendMessageDiscord;
|
|
326
325
|
const result = await send(to, text, {
|
|
327
326
|
verbose: false,
|
|
327
|
+
cfg,
|
|
328
328
|
mediaUrl,
|
|
329
329
|
mediaLocalRoots,
|
|
330
330
|
replyTo: replyToId ?? undefined,
|
|
@@ -333,8 +333,9 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
|
|
333
333
|
});
|
|
334
334
|
return { channel: "discord", ...result };
|
|
335
335
|
},
|
|
336
|
-
sendPoll: async ({ to, poll, accountId, silent }) =>
|
|
336
|
+
sendPoll: async ({ cfg, to, poll, accountId, silent }) =>
|
|
337
337
|
await getDiscordRuntime().channel.discord.sendPollDiscord(to, poll, {
|
|
338
|
+
cfg,
|
|
338
339
|
accountId: accountId ?? undefined,
|
|
339
340
|
silent: silent ?? undefined,
|
|
340
341
|
}),
|
|
@@ -386,19 +387,21 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
|
|
386
387
|
return { ...audit, unresolvedChannels };
|
|
387
388
|
},
|
|
388
389
|
buildAccountSnapshot: ({ account, runtime, probe, audit }) => {
|
|
389
|
-
const configured =
|
|
390
|
+
const configured =
|
|
391
|
+
resolveConfiguredFromCredentialStatuses(account) ?? Boolean(account.token?.trim());
|
|
390
392
|
const app = runtime?.application ?? (probe as { application?: unknown })?.application;
|
|
391
393
|
const bot = runtime?.bot ?? (probe as { bot?: unknown })?.bot;
|
|
392
|
-
|
|
394
|
+
const base = buildComputedAccountStatusSnapshot({
|
|
393
395
|
accountId: account.accountId,
|
|
394
396
|
name: account.name,
|
|
395
397
|
enabled: account.enabled,
|
|
396
398
|
configured,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
399
|
+
runtime,
|
|
400
|
+
probe,
|
|
401
|
+
});
|
|
402
|
+
return {
|
|
403
|
+
...base,
|
|
404
|
+
...projectCredentialSnapshotFields(account),
|
|
402
405
|
connected: runtime?.connected ?? false,
|
|
403
406
|
reconnectAttempts: runtime?.reconnectAttempts,
|
|
404
407
|
lastConnectedAt: runtime?.lastConnectedAt ?? null,
|
|
@@ -406,10 +409,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
|
|
406
409
|
lastEventAt: runtime?.lastEventAt ?? null,
|
|
407
410
|
application: app ?? undefined,
|
|
408
411
|
bot: bot ?? undefined,
|
|
409
|
-
probe,
|
|
410
412
|
audit,
|
|
411
|
-
lastInboundAt: runtime?.lastInboundAt ?? null,
|
|
412
|
-
lastOutboundAt: runtime?.lastOutboundAt ?? null,
|
|
413
413
|
};
|
|
414
414
|
},
|
|
415
415
|
},
|
package/src/runtime.ts
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/compat";
|
|
2
|
+
import type { PluginRuntime } from "openclaw/plugin-sdk/discord";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
runtime = next;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function getDiscordRuntime(): PluginRuntime {
|
|
10
|
-
if (!runtime) {
|
|
11
|
-
throw new Error("Discord runtime not initialized");
|
|
12
|
-
}
|
|
13
|
-
return runtime;
|
|
14
|
-
}
|
|
4
|
+
const { setRuntime: setDiscordRuntime, getRuntime: getDiscordRuntime } =
|
|
5
|
+
createPluginRuntimeStore<PluginRuntime>("Discord runtime not initialized");
|
|
6
|
+
export { getDiscordRuntime, setDiscordRuntime };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/discord";
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { registerDiscordSubagentHooks } from "./subagent-hooks.js";
|
|
4
4
|
|
|
@@ -35,7 +35,7 @@ const hookMocks = vi.hoisted(() => ({
|
|
|
35
35
|
unbindThreadBindingsBySessionKey: vi.fn(() => []),
|
|
36
36
|
}));
|
|
37
37
|
|
|
38
|
-
vi.mock("openclaw/plugin-sdk", () => ({
|
|
38
|
+
vi.mock("openclaw/plugin-sdk/discord", () => ({
|
|
39
39
|
resolveDiscordAccount: hookMocks.resolveDiscordAccount,
|
|
40
40
|
autoBindSpawnedDiscordSubagent: hookMocks.autoBindSpawnedDiscordSubagent,
|
|
41
41
|
listThreadBindingsBySessionKey: hookMocks.listThreadBindingsBySessionKey,
|
package/src/subagent-hooks.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/discord";
|
|
2
2
|
import {
|
|
3
3
|
autoBindSpawnedDiscordSubagent,
|
|
4
4
|
listThreadBindingsBySessionKey,
|
|
5
5
|
resolveDiscordAccount,
|
|
6
6
|
unbindThreadBindingsBySessionKey,
|
|
7
|
-
} from "openclaw/plugin-sdk";
|
|
7
|
+
} from "openclaw/plugin-sdk/discord";
|
|
8
8
|
|
|
9
9
|
function summarizeError(err: unknown): string {
|
|
10
10
|
if (err instanceof Error) {
|