@openclaw/mattermost 2026.7.2-beta.1 → 2026.7.2-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.
@@ -0,0 +1,83 @@
1
+ import { f as normalizeMattermostBaseUrl } from "./client-BzLj4dyf.js";
2
+ import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
3
+ import { resolveChannelPreviewStreamMode, resolveChannelStreamingBlockCoalesce, resolveChannelStreamingBlockEnabled, resolveChannelStreamingChunkMode } from "openclaw/plugin-sdk/channel-outbound";
4
+ import { createAccountListHelpers, hasConfiguredAccountValue } from "openclaw/plugin-sdk/account-helpers";
5
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
6
+ import { resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
7
+ import { buildSecretInputSchema, hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
8
+ //#region extensions/mattermost/src/mattermost/accounts.ts
9
+ const mattermostAccountHelpers = createAccountListHelpers("mattermost", { hasImplicitDefaultAccount: (cfg) => {
10
+ const mattermost = cfg.channels?.mattermost;
11
+ return Boolean(mattermost?.baseUrl?.trim() && (hasConfiguredAccountValue(mattermost.botToken) || process.env.MATTERMOST_BOT_TOKEN?.trim()));
12
+ } });
13
+ function listMattermostAccountIds(cfg) {
14
+ return mattermostAccountHelpers.listAccountIds(cfg);
15
+ }
16
+ function resolveDefaultMattermostAccountId(cfg) {
17
+ return mattermostAccountHelpers.resolveDefaultAccountId(cfg);
18
+ }
19
+ function mergeMattermostAccountConfig(cfg, accountId) {
20
+ return resolveMergedAccountConfig({
21
+ channelConfig: cfg.channels?.mattermost,
22
+ accounts: cfg.channels?.mattermost?.accounts,
23
+ accountId,
24
+ omitKeys: ["defaultAccount"],
25
+ nestedObjectKeys: ["commands"]
26
+ });
27
+ }
28
+ function resolveMattermostRequireMention(config) {
29
+ if (config.chatmode === "oncall") return true;
30
+ if (config.chatmode === "onmessage") return false;
31
+ if (config.chatmode === "onchar") return true;
32
+ return config.requireMention;
33
+ }
34
+ function resolveMattermostAccount(params) {
35
+ const accountId = normalizeAccountId(params.accountId ?? resolveDefaultMattermostAccountId(params.cfg));
36
+ const baseEnabled = params.cfg.channels?.mattermost?.enabled !== false;
37
+ const merged = mergeMattermostAccountConfig(params.cfg, accountId);
38
+ const accountEnabled = merged.enabled !== false;
39
+ const enabled = baseEnabled && accountEnabled;
40
+ const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
41
+ const envToken = allowEnv ? process.env.MATTERMOST_BOT_TOKEN?.trim() : void 0;
42
+ const envUrl = allowEnv ? process.env.MATTERMOST_URL?.trim() : void 0;
43
+ const configToken = params.allowUnresolvedSecretRef ? normalizeSecretInputString(merged.botToken) : normalizeResolvedSecretInputString({
44
+ value: merged.botToken,
45
+ path: `channels.mattermost.accounts.${accountId}.botToken`
46
+ });
47
+ const configUrl = merged.baseUrl?.trim();
48
+ const botToken = configToken || envToken;
49
+ const baseUrl = normalizeMattermostBaseUrl(configUrl || envUrl);
50
+ const requireMention = resolveMattermostRequireMention(merged);
51
+ const botTokenSource = configToken ? "config" : envToken ? "env" : "none";
52
+ const baseUrlSource = configUrl ? "config" : envUrl ? "env" : "none";
53
+ return {
54
+ accountId,
55
+ enabled,
56
+ name: normalizeOptionalString(merged.name),
57
+ botToken,
58
+ baseUrl,
59
+ botTokenSource,
60
+ baseUrlSource,
61
+ config: merged,
62
+ chatmode: merged.chatmode,
63
+ oncharPrefixes: merged.oncharPrefixes,
64
+ requireMention,
65
+ textChunkLimit: merged.textChunkLimit,
66
+ chunkMode: resolveChannelStreamingChunkMode(merged),
67
+ streamingMode: resolveChannelPreviewStreamMode(merged, "partial"),
68
+ blockStreaming: resolveChannelStreamingBlockEnabled(merged),
69
+ blockStreamingCoalesce: resolveChannelStreamingBlockCoalesce(merged)
70
+ };
71
+ }
72
+ /**
73
+ * Resolve the effective replyToMode for a given chat type.
74
+ * Direct messages stay flat unless explicitly opted into a per-chat-type mode.
75
+ */
76
+ function resolveMattermostReplyToMode(account, kind) {
77
+ const scopedMode = account.config.replyToModeByChatType?.[kind];
78
+ if (scopedMode !== void 0) return scopedMode;
79
+ if (kind === "direct") return "off";
80
+ return account.config.replyToMode ?? "off";
81
+ }
82
+ //#endregion
83
+ export { buildSecretInputSchema as a, resolveMattermostReplyToMode as i, resolveDefaultMattermostAccountId as n, hasConfiguredSecretInput as o, resolveMattermostAccount as r, listMattermostAccountIds as t };
package/dist/api.js CHANGED
@@ -1,2 +1,3 @@
1
- import { r as isMattermostSenderAllowed } from "./monitor-auth-BiDuyvOc.js";
2
- export { isMattermostSenderAllowed };
1
+ import { r as isMattermostSenderAllowed } from "./monitor-auth-dEHa1_On.js";
2
+ import { t as buildButtonAttachments } from "./interactions-DO7J-ND1.js";
3
+ export { buildButtonAttachments, isMattermostSenderAllowed };
@@ -1,4 +1,4 @@
1
- import { a as describeMattermostAccount, c as mattermostMeta, i as MattermostChannelConfigSchema, n as mattermostSetupWizard, o as isMattermostConfigured, r as mattermostSetupAdapter, s as mattermostConfigAdapter, t as mattermostPlugin } from "./channel-plugin-runtime-BkzZgYxP.js";
1
+ import { a as MattermostChannelConfigSchema, c as mattermostConfigAdapter, l as mattermostMeta, n as mattermostSetupWizard, o as describeMattermostAccount, r as mattermostSetupAdapter, s as isMattermostConfigured, t as mattermostPlugin } from "./channel-plugin-runtime-BXmjNn6B.js";
2
2
  import { t as resolveMattermostGatewayAuthBypassPaths } from "./gateway-auth-bypass-CL1PxV3z.js";
3
3
  //#region extensions/mattermost/src/channel.setup.ts
4
4
  const mattermostSetupPlugin = {
@@ -1,30 +1,31 @@
1
- import { C as hasConfiguredSecretInput, S as buildSecretInputSchema, g as normalizeMattermostBaseUrl, i as resolveMattermostReplyToMode, n as resolveDefaultMattermostAccountId, r as resolveMattermostAccount, t as listMattermostAccountIds } from "./accounts-B2NRPlqr.js";
1
+ import { f as normalizeMattermostBaseUrl } from "./client-BzLj4dyf.js";
2
+ import { a as buildSecretInputSchema, i as resolveMattermostReplyToMode, n as resolveDefaultMattermostAccountId, o as hasConfiguredSecretInput, r as resolveMattermostAccount, t as listMattermostAccountIds } from "./accounts-C4Azn6rA.js";
2
3
  import { t as resolveMattermostGatewayAuthBypassPaths } from "./gateway-auth-bypass-CL1PxV3z.js";
3
4
  import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-BaUvVh8e.js";
4
5
  import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-B4R5Bmhv.js";
5
6
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
7
+ import { buildPassiveProbedChannelStatusSummary } from "openclaw/plugin-sdk/extension-shared";
8
+ import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
9
+ import { z } from "zod";
6
10
  import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
7
11
  import { createLoggedPairingApprovalNotifier } from "openclaw/plugin-sdk/channel-pairing";
8
12
  import { createAccountStatusSink, createChannelMessageAdapterFromOutbound } from "openclaw/plugin-sdk/channel-outbound";
9
13
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, buildChannelOutboundSessionRoute, buildThreadAwareOutboundSessionRoute, stripChannelTargetPrefix, stripTargetKindPrefix } from "openclaw/plugin-sdk/core";
10
14
  import { createChannelConfigUiHints, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
11
- import { buildChannelGroupsScopeTree, createDangerousNameMatchingMutableAllowlistWarningCollector, createRestrictSendersChannelSecurity, resolveScopeRequireMention } from "openclaw/plugin-sdk/channel-policy";
15
+ import { buildChannelGroupsScopeTree, buildMutableAllowEntryDetector, collectStandardAllowlistLists, createDangerousNameMatchingMutableAllowlistWarningCollector, createRestrictSendersChannelSecurity, resolveScopeRequireMention } from "openclaw/plugin-sdk/channel-policy";
12
16
  import { attachChannelToResult, createAttachedChannelResultAdapter } from "openclaw/plugin-sdk/channel-send-result";
13
17
  import { createChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
14
- import { buildPassiveProbedChannelStatusSummary } from "openclaw/plugin-sdk/extension-shared";
15
18
  import { normalizeMessagePresentation, renderMessagePresentationFallbackText, resolveMessagePresentationButtonAction, resolveMessagePresentationControlValue } from "openclaw/plugin-sdk/interactive-runtime";
16
19
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
17
20
  import { resolvePayloadMediaUrls, sendTextMediaPayload } from "openclaw/plugin-sdk/reply-payload";
18
- import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
19
21
  import { createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
20
22
  import { chunkTextForOutbound, sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
21
23
  import { createChannelApprovalAuth } from "openclaw/plugin-sdk/approval-auth-runtime";
22
24
  import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
23
25
  import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
24
- import { z } from "zod";
25
26
  import { adaptScopedAccountAccessor, createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
26
- import { BlockStreamingCoalesceSchema, DmPolicySchema, GroupPolicySchema, MarkdownConfigSchema, buildChannelConfigSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
27
- import { applyAccountNameToChannelSection, applySetupAccountConfigPatch, createSetupTranslator, createStandardChannelSetupStatus, formatDocsLink, migrateBaseNameToDefaultAccount } from "openclaw/plugin-sdk/setup";
27
+ import { BlockStreamingCoalesceSchema, DmPolicySchema, GroupPolicySchema, MarkdownConfigSchema, buildChannelConfigSchema, buildGroupEntrySchema, buildMultiAccountChannelSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
28
+ import { applyAccountNameToChannelSection, applySetupAccountConfigPatch, baseUrlTextInput, createSetupTranslator, createStandardChannelSetupStatus, defineTokenCredential, formatDocsLink, migrateBaseNameToDefaultAccount, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
28
29
  import { createSetupInputPresenceValidator } from "openclaw/plugin-sdk/setup-runtime";
29
30
  //#region extensions/mattermost/src/approval-auth.ts
30
31
  const MATTERMOST_USER_ID_RE = /^[a-z0-9]{26}$/;
@@ -99,9 +100,14 @@ function describeMattermostAccount(account) {
99
100
  }
100
101
  //#endregion
101
102
  //#region extensions/mattermost/src/config-schema-core.ts
102
- const MattermostGroupSchema = z.object({
103
- /** Whether mentions are required to trigger the bot in this group. */
104
- requireMention: z.boolean().optional() }).strict();
103
+ const MattermostGroupSchema = buildGroupEntrySchema().omit({
104
+ tools: true,
105
+ toolsBySender: true,
106
+ skills: true,
107
+ enabled: true,
108
+ allowFrom: true,
109
+ systemPrompt: true
110
+ });
105
111
  function requireMattermostOpenAllowFrom(params) {
106
112
  requireOpenAllowFrom({
107
113
  policy: params.policy,
@@ -151,9 +157,13 @@ const MattermostStreamingProgressSchema = z.object({
151
157
  labels: z.array(z.string()).optional(),
152
158
  maxLines: z.number().int().positive().optional(),
153
159
  maxLineChars: z.number().int().positive().optional(),
154
- toolProgress: z.boolean().optional()
160
+ toolProgress: z.boolean().optional(),
161
+ commandText: z.enum(["raw", "status"]).optional()
162
+ }).strict();
163
+ const MattermostStreamingPreviewSchema = z.object({
164
+ toolProgress: z.boolean().optional(),
165
+ commandText: z.enum(["raw", "status"]).optional()
155
166
  }).strict();
156
- const MattermostStreamingPreviewSchema = z.object({ toolProgress: z.boolean().optional() }).strict();
157
167
  const MattermostStreamingBlockSchema = z.object({
158
168
  enabled: z.boolean().optional(),
159
169
  coalesce: BlockStreamingCoalesceSchema.optional()
@@ -176,7 +186,9 @@ const MattermostReplyToModeByChatTypeSchema = z.object({
176
186
  group: MattermostReplyToModeSchema.optional(),
177
187
  channel: MattermostReplyToModeSchema.optional()
178
188
  }).strict();
179
- const MattermostAccountSchemaBase = z.object({
189
+ //#endregion
190
+ //#region extensions/mattermost/src/config-surface.ts
191
+ const MattermostChannelConfigSchema = buildChannelConfigSchema(buildMultiAccountChannelSchema(z.object({
180
192
  name: z.string().optional(),
181
193
  capabilities: z.array(z.string()).optional(),
182
194
  dangerouslyAllowNameMatching: z.boolean().optional(),
@@ -213,25 +225,15 @@ const MattermostAccountSchemaBase = z.object({
213
225
  network: MattermostNetworkSchema,
214
226
  /** Retry configuration for DM channel creation */
215
227
  dmChannelRetry: DmChannelRetrySchema
216
- }).strict();
217
- const MattermostAccountSchema = MattermostAccountSchemaBase.superRefine((value, ctx) => {
218
- requireMattermostOpenAllowFrom({
219
- policy: value.dmPolicy,
220
- allowFrom: value.allowFrom,
221
- ctx
222
- });
223
- });
224
- //#endregion
225
- //#region extensions/mattermost/src/config-surface.ts
226
- const MattermostChannelConfigSchema = buildChannelConfigSchema(MattermostAccountSchemaBase.extend({
227
- accounts: z.record(z.string(), MattermostAccountSchema.optional()).optional(),
228
- defaultAccount: z.string().optional()
229
- }).superRefine((value, ctx) => {
230
- requireMattermostOpenAllowFrom({
231
- policy: value.dmPolicy,
232
- allowFrom: value.allowFrom,
233
- ctx
234
- });
228
+ }).strict(), {
229
+ optionalAccount: true,
230
+ refine: (value, ctx) => {
231
+ requireMattermostOpenAllowFrom({
232
+ policy: value.dmPolicy,
233
+ allowFrom: value.allowFrom,
234
+ ctx
235
+ });
236
+ }
235
237
  }), { uiHints: {
236
238
  "": {
237
239
  label: "Mattermost",
@@ -270,28 +272,13 @@ const MattermostChannelConfigSchema = buildChannelConfigSchema(MattermostAccount
270
272
  help: "Merge streamed Mattermost block replies before final delivery."
271
273
  }
272
274
  } });
273
- //#endregion
274
- //#region extensions/mattermost/src/doctor.ts
275
- function isMattermostMutableAllowEntry(raw) {
276
- const text = raw.trim();
277
- if (!text || text === "*") return false;
278
- const lowered = normalizeLowercaseStringOrEmpty(text.replace(/^(mattermost|user):/i, "").replace(/^@/, "").trim());
279
- if (/^[a-z0-9]{26}$/.test(lowered)) return false;
280
- return true;
281
- }
282
275
  const mattermostDoctor = {
283
276
  legacyConfigRules,
284
277
  normalizeCompatibilityConfig,
285
278
  collectMutableAllowlistWarnings: createDangerousNameMatchingMutableAllowlistWarningCollector({
286
279
  channel: "mattermost",
287
- detector: isMattermostMutableAllowEntry,
288
- collectLists: (scope) => [{
289
- pathLabel: `${scope.prefix}.allowFrom`,
290
- list: scope.account.allowFrom
291
- }, {
292
- pathLabel: `${scope.prefix}.groupAllowFrom`,
293
- list: scope.account.groupAllowFrom
294
- }]
280
+ detector: buildMutableAllowEntryDetector({ stableIdPattern: /^(?:(?:mattermost|user):)?@?[a-z0-9]{26}$/i }),
281
+ collectLists: (scope) => collectStandardAllowlistLists(scope)
295
282
  })
296
283
  };
297
284
  //#endregion
@@ -489,60 +476,50 @@ const mattermostSetupWizard = {
489
476
  patch: {}
490
477
  })
491
478
  },
492
- credentials: [{
479
+ credentials: [defineTokenCredential({
493
480
  inputKey: "botToken",
481
+ configKey: "botToken",
494
482
  providerHint: channel,
495
483
  credentialLabel: t("wizard.mattermost.botToken"),
496
484
  preferredEnvVar: "MATTERMOST_BOT_TOKEN",
497
485
  envPrompt: t("wizard.mattermost.envPrompt"),
498
486
  keepPrompt: t("wizard.mattermost.botTokenKeep"),
499
487
  inputPrompt: t("wizard.mattermost.botTokenInput"),
500
- inspect: ({ cfg, accountId }) => {
501
- const resolvedAccount = resolveMattermostAccountWithSecrets(cfg, accountId);
502
- return {
503
- accountConfigured: isMattermostConfigured(resolvedAccount),
504
- hasConfiguredValue: hasConfiguredSecretInput(resolvedAccount.config.botToken)
505
- };
506
- },
507
- applySet: async ({ cfg, accountId, value }) => applyMattermostSetupConfigPatch({
488
+ resolveAccount: ({ cfg, accountId }) => resolveMattermostAccountWithSecrets(cfg, accountId),
489
+ accountConfigured: isMattermostConfigured,
490
+ patchAccount: ({ cfg, accountId, patch }) => applyMattermostSetupConfigPatch({
508
491
  cfg,
509
492
  accountId,
510
- patch: { botToken: value }
511
- })
512
- }],
513
- textInputs: [{
493
+ patch
494
+ }),
495
+ set: {}
496
+ })],
497
+ textInputs: [baseUrlTextInput({
514
498
  inputKey: "httpUrl",
499
+ configKey: "baseUrl",
515
500
  message: t("wizard.mattermost.baseUrlPrompt"),
516
501
  confirmCurrentValue: false,
517
- currentValue: ({ cfg, accountId }) => resolveMattermostAccountWithSecrets(cfg, accountId).baseUrl ?? process.env.MATTERMOST_URL?.trim(),
518
- initialValue: ({ cfg, accountId }) => resolveMattermostAccountWithSecrets(cfg, accountId).baseUrl ?? process.env.MATTERMOST_URL?.trim(),
502
+ resolveAccount: ({ cfg, accountId }) => resolveMattermostAccountWithSecrets(cfg, accountId),
503
+ currentValue: (account) => account.baseUrl ?? process.env.MATTERMOST_URL?.trim(),
504
+ includeInitialValue: true,
519
505
  shouldPrompt: ({ cfg, accountId, credentialValues, currentValue }) => {
520
506
  const resolvedAccount = resolveMattermostAccountWithSecrets(cfg, accountId);
521
507
  const tokenConfigured = Boolean(resolvedAccount.botToken?.trim()) || hasConfiguredSecretInput(resolvedAccount.config.botToken);
522
508
  return Boolean(credentialValues.botToken) || !tokenConfigured || !currentValue;
523
509
  },
524
- validate: ({ value }) => normalizeMattermostBaseUrl(value) ? void 0 : "Mattermost base URL must include a valid base URL.",
525
- normalizeValue: ({ value }) => normalizeMattermostBaseUrl(value) ?? value.trim(),
526
- applySet: async ({ cfg, accountId, value }) => applyMattermostSetupConfigPatch({
510
+ validate: (value) => normalizeMattermostBaseUrl(value) ? void 0 : "Mattermost base URL must include a valid base URL.",
511
+ normalize: (value) => normalizeMattermostBaseUrl(value) ?? value.trim(),
512
+ patchAccount: ({ cfg, accountId, patch }) => applyMattermostSetupConfigPatch({
527
513
  cfg,
528
514
  accountId,
529
- patch: { baseUrl: value }
515
+ patch
530
516
  })
531
- }],
532
- disable: (cfg) => ({
533
- ...cfg,
534
- channels: {
535
- ...cfg.channels,
536
- mattermost: {
537
- ...cfg.channels?.mattermost,
538
- enabled: false
539
- }
540
- }
541
- })
517
+ })],
518
+ disable: (cfg) => setSetupChannelEnabled(cfg, channel, false)
542
519
  };
543
520
  //#endregion
544
521
  //#region extensions/mattermost/src/channel.ts
545
- const loadMattermostChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-Cbs451W2.js"));
522
+ const loadMattermostChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-CKb2-z0p.js"));
546
523
  function buildMattermostPresentationButtons(presentation) {
547
524
  return presentation.blocks.filter((block) => block.type === "buttons").map((block) => block.buttons.flatMap((button) => {
548
525
  if (button.action) return [];
@@ -567,7 +544,7 @@ const MATTERMOST_PRESENTATION_CAPABILITIES = {
567
544
  function hasMattermostPresentationNavigation(presentation) {
568
545
  return presentation.blocks.some((block) => block.type === "buttons" && block.buttons.some((button) => {
569
546
  const action = resolveMessagePresentationButtonAction(button);
570
- return action?.type === "url" || action?.type === "web-app";
547
+ return action?.type === "url" || action?.type === "web-app" && Boolean(action.url);
571
548
  }));
572
549
  }
573
550
  function readMattermostPresentationButtons(payload) {
@@ -705,21 +682,26 @@ const mattermostMessageActions = {
705
682
  supportsAction: ({ action }) => {
706
683
  return action === "send" || action === "react";
707
684
  },
708
- handleAction: async ({ action, params, cfg, accountId, mediaAccess, mediaLocalRoots, mediaReadFile }) => {
685
+ handleAction: async ({ action, params, cfg, accountId, mediaAccess, mediaLocalRoots, mediaReadFile, conversationReadOrigin }) => {
709
686
  if (action === "react") {
710
687
  const resolvedAccountId = accountId ?? resolveDefaultMattermostAccountId(cfg);
711
688
  const mattermostConfig = cfg.channels?.mattermost;
712
- if (!(resolveMattermostAccount({
689
+ const account = resolveMattermostAccount({
713
690
  cfg,
714
691
  accountId: resolvedAccountId
715
- }).config.actions?.reactions ?? mattermostConfig?.actions?.reactions ?? true)) throw new Error("Mattermost reactions are disabled in config");
692
+ });
693
+ if (!account.enabled) throw new Error(`Mattermost account "${resolvedAccountId}" is disabled`);
694
+ if (!(account.config.actions?.reactions ?? mattermostConfig?.actions?.reactions ?? true)) throw new Error("Mattermost reactions are disabled in config");
716
695
  const { postId, emojiName, remove } = parseMattermostReactActionParams(params);
696
+ const authorizedTarget = normalizeOptionalString(params.to);
717
697
  if (remove) {
718
698
  const result = await (await loadMattermostChannelRuntime()).removeMattermostReaction({
719
699
  cfg,
720
700
  postId,
721
701
  emojiName,
722
- accountId: resolvedAccountId
702
+ accountId: resolvedAccountId,
703
+ authorizedTarget,
704
+ conversationReadOrigin
723
705
  });
724
706
  if (!result.ok) throw new Error(result.error);
725
707
  return {
@@ -734,7 +716,9 @@ const mattermostMessageActions = {
734
716
  cfg,
735
717
  postId,
736
718
  emojiName,
737
- accountId: resolvedAccountId
719
+ accountId: resolvedAccountId,
720
+ authorizedTarget,
721
+ conversationReadOrigin
738
722
  });
739
723
  if (!result.ok) throw new Error(result.error);
740
724
  return {
@@ -848,13 +832,12 @@ function collectMattermostAttachmentMedia(params) {
848
832
  readMattermostStringParam(params, "fileUrl")
849
833
  ];
850
834
  mediaUrlCandidates.push(...readMattermostStringArrayParam(params, "mediaUrls"));
851
- let hasUnsupportedAttachmentPayload = typeof params.buffer === "string" || typeof params.base64 === "string";
835
+ let hasUnsupportedAttachmentPayload = Boolean(readMattermostStringParam(params, "buffer") ?? readMattermostStringParam(params, "base64"));
852
836
  if (Array.isArray(params.attachments)) for (const attachment of params.attachments) {
853
837
  if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) continue;
854
838
  const record = attachment;
855
839
  mediaUrlCandidates.push(readMattermostStringParam(record, "media"), readMattermostStringParam(record, "mediaUrl"), readMattermostStringParam(record, "path"), readMattermostStringParam(record, "filePath"), readMattermostStringParam(record, "fileUrl"), readMattermostStringParam(record, "url"));
856
- hasUnsupportedAttachmentPayload ||= typeof record.buffer === "string";
857
- hasUnsupportedAttachmentPayload ||= typeof record.base64 === "string";
840
+ hasUnsupportedAttachmentPayload ||= Boolean(readMattermostStringParam(record, "buffer") ?? readMattermostStringParam(record, "base64"));
858
841
  }
859
842
  return {
860
843
  mediaUrls: collectNonBlankStrings(mediaUrlCandidates),
@@ -1013,6 +996,8 @@ const mattermostPlugin = createChatChannelPlugin({
1013
996
  }),
1014
997
  messaging: {
1015
998
  targetPrefixes: ["mattermost"],
999
+ directTargetStyle: "user-prefixed",
1000
+ targetIdComparison: "case-sensitive",
1016
1001
  defaultMarkdownTableMode: "off",
1017
1002
  normalizeTarget: normalizeMattermostMessagingTarget,
1018
1003
  resolveDeliveryTarget: ({ conversationId, parentConversationId }) => {
@@ -1140,4 +1125,4 @@ const mattermostPlugin = createChatChannelPlugin({
1140
1125
  outbound: mattermostOutbound
1141
1126
  });
1142
1127
  //#endregion
1143
- export { describeMattermostAccount as a, mattermostMeta as c, MattermostChannelConfigSchema as i, mattermostSetupWizard as n, isMattermostConfigured$1 as o, mattermostSetupAdapter as r, mattermostConfigAdapter as s, mattermostPlugin as t };
1128
+ export { MattermostChannelConfigSchema as a, mattermostConfigAdapter as c, normalizeMattermostMessagingTarget as i, mattermostMeta as l, mattermostSetupWizard as n, describeMattermostAccount as o, mattermostSetupAdapter as r, isMattermostConfigured$1 as s, mattermostPlugin as t };
@@ -1,2 +1,2 @@
1
- import { t as mattermostPlugin } from "./channel-plugin-runtime-BkzZgYxP.js";
1
+ import { t as mattermostPlugin } from "./channel-plugin-runtime-BXmjNn6B.js";
2
2
  export { mattermostPlugin };
@@ -1,25 +1,28 @@
1
- import { C as resolveChannelMediaMaxBytes, E as warnMissingProviderGroupPolicyFallbackOnce, S as resolveAllowlistProviderRuntimeGroupPolicy, T as resolveDefaultGroupPolicy, _ as logInboundDrop, c as buildAgentMediaPayload, d as createChannelMessageReplyPipeline, f as createChannelPairingController, h as listSkillCommandsForAgents, i as normalizeMattermostAllowEntry, l as buildModelsProviderData, n as formatMattermostDirectMessageDropLog, o as resolveMattermostMonitorInboundAccess, s as DEFAULT_GROUP_HISTORY_LIMIT, t as authorizeMattermostCommandInvocation, u as createChannelHistoryWindow, v as logTypingFailure, x as registerPluginHttpRoute, y as parseTcpPort } from "./monitor-auth-BiDuyvOc.js";
2
- import { _ as readMattermostError, a as MattermostPostSchema, b as updateMattermostPost, c as createMattermostPost, f as fetchMattermostMe, g as normalizeMattermostBaseUrl, h as fetchMattermostUserTeams, i as resolveMattermostReplyToMode, l as deleteMattermostPost, o as createMattermostClient, p as fetchMattermostUser, r as resolveMattermostAccount, t as listMattermostAccountIds, u as fetchMattermostChannel, y as sendMattermostTyping } from "./accounts-B2NRPlqr.js";
3
1
  import { n as getOptionalMattermostRuntime, t as getMattermostRuntime } from "./runtime-CNB4YGqJ.js";
4
- import { C as registerSlashCommands, S as isSlashCommandsEnabled, T as resolveSlashCommandConfig, _ as renderMattermostModelsPickerView, a as sendMessageMattermost, b as DEFAULT_COMMAND_SPECS, c as computeInteractionCallbackUrl, d as setInteractionCallbackUrl, f as setInteractionSecret, g as parseMattermostModelPickerContext, h as buildMattermostAllowedModelRefs, l as createMattermostInteractionHandler, m as deliverMattermostReplyPayload, n as deactivateSlashCommands, o as resolveMattermostOpaqueTarget, p as createMattermostReplyDeliveryBarrier, r as getSlashCommandState, s as buildButtonProps, t as activateSlashCommands, u as resolveInteractionCallbackPath, v as renderMattermostProviderPickerView, w as resolveCallbackUrl, x as cleanupSlashCommands, y as resolveMattermostModelPickerCurrentModel } from "./slash-state-Cr58Xd12.js";
2
+ import { a as deleteMattermostPost, c as fetchMattermostMe, d as fetchMattermostUserTeams, f as normalizeMattermostBaseUrl, g as updateMattermostPost, h as sendMattermostTyping, i as createMattermostPost, l as fetchMattermostUser, n as createMattermostClient, o as fetchMattermostChannel, p as readMattermostError, t as MattermostPostSchema } from "./client-BzLj4dyf.js";
3
+ import { C as resolveChannelMediaMaxBytes, E as warnMissingProviderGroupPolicyFallbackOnce, S as resolveAllowlistProviderRuntimeGroupPolicy, T as resolveDefaultGroupPolicy, _ as logInboundDrop, c as buildAgentMediaPayload, d as createChannelMessageReplyPipeline, f as createChannelPairingController, h as listSkillCommandsForAgents, i as normalizeMattermostAllowEntry, l as buildModelsProviderData, n as formatMattermostDirectMessageDropLog, o as resolveMattermostMonitorInboundAccess, s as DEFAULT_GROUP_HISTORY_LIMIT, t as authorizeMattermostCommandInvocation, u as createChannelHistoryWindow, v as logTypingFailure, x as registerPluginHttpRoute, y as parseTcpPort } from "./monitor-auth-dEHa1_On.js";
4
+ import { a as resolveInteractionCallbackPath, c as setInteractionSecret, i as createMattermostInteractionHandler, n as buildButtonProps, r as computeInteractionCallbackUrl, s as setInteractionCallbackUrl } from "./interactions-DO7J-ND1.js";
5
+ import { i as resolveMattermostReplyToMode, r as resolveMattermostAccount, t as listMattermostAccountIds } from "./accounts-C4Azn6rA.js";
6
+ import { i as normalizeMattermostMessagingTarget } from "./channel-plugin-runtime-BXmjNn6B.js";
7
+ import { _ as registerSlashCommands, a as sendMessageMattermost, c as deliverMattermostReplyPayload, d as renderMattermostModelsPickerView, f as renderMattermostProviderPickerView, g as isSlashCommandsEnabled, h as cleanupSlashCommands, l as buildMattermostAllowedModelRefs, m as DEFAULT_COMMAND_SPECS, n as deactivateSlashCommands, o as resolveMattermostOpaqueTarget, p as resolveMattermostModelPickerCurrentModel, r as getSlashCommandState, s as createMattermostReplyDeliveryBarrier, t as activateSlashCommands, u as parseMattermostModelPickerContext, v as resolveCallbackUrl, y as resolveSlashCommandConfig } from "./slash-state-C9bpBN40.js";
8
+ import { randomUUID } from "node:crypto";
9
+ import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
5
10
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringEntries, normalizeTrimmedStringList, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
11
+ import { safeParseJsonWithSchema, safeParseWithSchema } from "openclaw/plugin-sdk/extension-shared";
12
+ import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
13
+ import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
14
+ import { fetchWithSsrFGuard, isPrivateNetworkOptInEnabled, ssrfPolicyFromPrivateNetworkOptIn } from "openclaw/plugin-sdk/ssrf-runtime";
15
+ import { z } from "zod";
6
16
  import { formatInboundFromLabel, formatInboundMediaUnavailableText } from "openclaw/plugin-sdk/channel-inbound";
7
17
  import { buildChannelProgressDraftLineForEntry, createChannelProgressDraftCompositor, createFinalizableDraftLifecycle, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, resolveChannelStreamingPreviewToolProgress } from "openclaw/plugin-sdk/channel-outbound";
8
18
  import { rawDataToString } from "openclaw/plugin-sdk/webhook-ingress";
9
- import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
10
- import { safeParseJsonWithSchema, safeParseWithSchema } from "openclaw/plugin-sdk/extension-shared";
11
19
  import { buildTtsSupplementMediaPayload, countOutboundMedia, getReplyPayloadTtsSupplement, isReasoningReplyPayload } from "openclaw/plugin-sdk/reply-payload";
12
- import { fetchWithSsrFGuard, isPrivateNetworkOptInEnabled, ssrfPolicyFromPrivateNetworkOptIn } from "openclaw/plugin-sdk/ssrf-runtime";
13
- import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
14
- import { z } from "zod";
15
20
  import { resolveInboundLastRouteSessionKey, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
16
- import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
17
21
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
18
- import { randomUUID } from "node:crypto";
19
22
  import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
20
23
  import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
21
24
  import { chunkMarkdownTextWithMode } from "openclaw/plugin-sdk/reply-chunking";
22
- import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
25
+ import { createChannelReplayGuard } from "openclaw/plugin-sdk/persistent-dedupe";
23
26
  import { captureWsEvent, createDebugProxyWebSocketAgent, resolveDebugProxySettings } from "openclaw/plugin-sdk/proxy-capture";
24
27
  import WebSocket from "ws";
25
28
  import { createPersistentDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
@@ -620,34 +623,28 @@ function stripOncharPrefix(text, prefixes) {
620
623
  }
621
624
  //#endregion
622
625
  //#region extensions/mattermost/src/mattermost/monitor-replay.ts
623
- const recentInboundMessages = createClaimableDedupe({
624
- ttlMs: 5 * 6e4,
625
- memoryMaxSize: 2e3
626
- });
626
+ const RECENT_MATTERMOST_MESSAGE_TTL_MS = 5 * 6e4;
627
+ const RECENT_MATTERMOST_MESSAGE_MAX = 2e3;
627
628
  function buildMattermostInboundReplayKeys(params) {
628
- return uniqueStrings(params.messageIds.map((id) => `${params.accountId}:${id.trim()}`)).filter((key) => !key.endsWith(":"));
629
+ return params.messageIds.map((id) => id.trim() ? `${params.accountId}:${id.trim()}` : "");
630
+ }
631
+ function createMattermostInboundReplayGuard() {
632
+ return createChannelReplayGuard({
633
+ dedupe: {
634
+ ttlMs: RECENT_MATTERMOST_MESSAGE_TTL_MS,
635
+ memoryMaxSize: RECENT_MATTERMOST_MESSAGE_MAX
636
+ },
637
+ buildReplayKey: buildMattermostInboundReplayKeys
638
+ });
629
639
  }
640
+ const recentInboundMessages = createMattermostInboundReplayGuard();
630
641
  async function processMattermostReplayGuardedPost(params) {
631
642
  const replayGuard = params.replayGuard ?? recentInboundMessages;
632
- const replayKeys = buildMattermostInboundReplayKeys({
643
+ const event = {
633
644
  accountId: params.accountId,
634
645
  messageIds: params.messageIds
635
- });
636
- if (replayKeys.length === 0) {
637
- await params.handlePost();
638
- return "processed";
639
- }
640
- const claimedKeys = [];
641
- for (const replayKey of replayKeys) if ((await replayGuard.claim(replayKey)).kind === "claimed") claimedKeys.push(replayKey);
642
- if (claimedKeys.length === 0) return "duplicate";
643
- try {
644
- await params.handlePost();
645
- await Promise.all(claimedKeys.map((replayKey) => replayGuard.commit(replayKey)));
646
- return "processed";
647
- } catch (error) {
648
- await Promise.all(claimedKeys.map((replayKey) => replayGuard.commit(replayKey)));
649
- throw error;
650
- }
646
+ };
647
+ return (await replayGuard.processGuarded(event, params.handlePost, { onError: "commit" })).kind === "processed" ? "processed" : "duplicate";
651
648
  }
652
649
  //#endregion
653
650
  //#region extensions/mattermost/src/mattermost/monitor-resources.ts
@@ -887,6 +884,7 @@ async function registerMattermostMonitorSlashCommands(params) {
887
884
  //#endregion
888
885
  //#region extensions/mattermost/src/mattermost/monitor-websocket.ts
889
886
  const MATTERMOST_WEBSOCKET_MAX_PAYLOAD_BYTES = 16 * 1024 * 1024;
887
+ const MATTERMOST_WEBSOCKET_HANDSHAKE_TIMEOUT_MS = 3e4;
890
888
  const MattermostEventPayloadSchema = z.object({
891
889
  event: z.string().optional(),
892
890
  data: z.object({
@@ -920,11 +918,11 @@ var WebSocketClosedBeforeOpenError = class extends Error {
920
918
  this.name = "WebSocketClosedBeforeOpenError";
921
919
  }
922
920
  };
923
- const defaultMattermostWebSocketFactory = (url) => {
921
+ const defaultMattermostWebSocketFactory = (url, options) => {
924
922
  const agent = createDebugProxyWebSocketAgent(resolveDebugProxySettings());
925
923
  return new WebSocket(url, {
926
- ...agent ? { agent } : {},
927
- maxPayload: MATTERMOST_WEBSOCKET_MAX_PAYLOAD_BYTES
924
+ ...options,
925
+ ...agent ? { agent } : {}
928
926
  });
929
927
  };
930
928
  function parsePostedPayload(payload) {
@@ -945,7 +943,10 @@ function createMattermostConnectOnce(opts) {
945
943
  const pongTimeoutMs = opts.pongTimeoutMs ?? 1e4;
946
944
  return async () => {
947
945
  const flowId = randomUUID();
948
- const ws = webSocketFactory(opts.wsUrl);
946
+ const ws = webSocketFactory(opts.wsUrl, {
947
+ maxPayload: MATTERMOST_WEBSOCKET_MAX_PAYLOAD_BYTES,
948
+ handshakeTimeout: MATTERMOST_WEBSOCKET_HANDSHAKE_TIMEOUT_MS
949
+ });
949
950
  const onAbort = () => ws.terminate();
950
951
  opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
951
952
  const getBotUpdateAt = opts.getBotUpdateAt;
@@ -2818,6 +2819,42 @@ async function removeMattermostReaction(params) {
2818
2819
  mutation: deleteReaction
2819
2820
  });
2820
2821
  }
2822
+ function parseAuthorizedReactionTarget(rawTarget) {
2823
+ const normalized = rawTarget ? normalizeMattermostMessagingTarget(rawTarget) : void 0;
2824
+ if (!normalized) return null;
2825
+ if (normalized.startsWith("channel:")) {
2826
+ const id = normalized.slice(8).trim();
2827
+ return id ? {
2828
+ kind: "channel",
2829
+ id
2830
+ } : null;
2831
+ }
2832
+ if (normalized.startsWith("user:")) {
2833
+ const id = normalized.slice(5).trim();
2834
+ return id ? {
2835
+ kind: "user",
2836
+ id
2837
+ } : null;
2838
+ }
2839
+ return null;
2840
+ }
2841
+ async function authorizeMattermostReactionResource(params) {
2842
+ const target = parseAuthorizedReactionTarget(params.authorizedTarget);
2843
+ if (!target) throw new Error("Mattermost delegated reactions require a canonical authorized conversation target.");
2844
+ const postChannelId = (await params.client.request(`/posts/${encodeURIComponent(params.postId)}`)).channel_id?.trim();
2845
+ if (!postChannelId) throw new Error("Mattermost reaction post is missing its conversation binding.");
2846
+ if (target.kind === "channel") {
2847
+ if (postChannelId !== target.id) throw new Error("Mattermost reaction post belongs to a different conversation.");
2848
+ return;
2849
+ }
2850
+ const botUserId = await resolveBotUserId(params.client, params.cacheKey);
2851
+ if (!botUserId) throw new Error("Mattermost reactions failed: could not resolve bot user id.");
2852
+ const channel = await fetchMattermostChannel(params.client, postChannelId);
2853
+ const participants = channel.name?.split("__").map((entry) => entry.trim()).filter(Boolean).toSorted() ?? [];
2854
+ const authorizedParticipants = [botUserId, target.id].toSorted();
2855
+ if (channel.type !== "D" || participants.length !== 2 || participants[0] !== authorizedParticipants[0] || participants[1] !== authorizedParticipants[1]) throw new Error("Mattermost reaction post belongs to a different direct conversation.");
2856
+ return botUserId;
2857
+ }
2821
2858
  async function runMattermostReaction(params, options) {
2822
2859
  const resolved = resolveMattermostAccount({
2823
2860
  cfg: params.cfg,
@@ -2835,12 +2872,18 @@ async function runMattermostReaction(params, options) {
2835
2872
  fetchImpl: params.fetchImpl,
2836
2873
  allowPrivateNetwork: isPrivateNetworkOptInEnabled(resolved.config)
2837
2874
  });
2838
- const userId = await resolveBotUserId(client, `${baseUrl}:${botToken}`);
2839
- if (!userId) return {
2840
- ok: false,
2841
- error: "Mattermost reactions failed: could not resolve bot user id."
2842
- };
2875
+ const cacheKey = `${baseUrl}:${botToken}`;
2843
2876
  try {
2877
+ const userId = (params.conversationReadOrigin === "direct-operator" ? void 0 : await authorizeMattermostReactionResource({
2878
+ client,
2879
+ cacheKey,
2880
+ postId: params.postId,
2881
+ authorizedTarget: params.authorizedTarget
2882
+ })) ?? await resolveBotUserId(client, cacheKey);
2883
+ if (!userId) return {
2884
+ ok: false,
2885
+ error: "Mattermost reactions failed: could not resolve bot user id."
2886
+ };
2844
2887
  await options.mutation(client, {
2845
2888
  userId,
2846
2889
  postId: params.postId,