@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.
- package/dist/accounts-C4Azn6rA.js +83 -0
- package/dist/api.js +3 -2
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel-plugin-runtime-BkzZgYxP.js → channel-plugin-runtime-BXmjNn6B.js} +75 -90
- package/dist/channel-plugin-runtime.js +1 -1
- package/dist/{channel.runtime-Cbs451W2.js → channel.runtime-CKb2-z0p.js} +85 -42
- package/dist/{accounts-B2NRPlqr.js → client-BzLj4dyf.js} +3 -83
- package/dist/interactions-DO7J-ND1.js +375 -0
- package/dist/policy-api.js +1 -1
- package/dist/slash-route-api.js +1 -1
- package/dist/{slash-state-Cr58Xd12.js → slash-state-C9bpBN40.js} +10 -375
- package/npm-shrinkwrap.json +3 -3
- package/openclaw.plugin.json +28 -0
- package/package.json +4 -4
- package/dist/{monitor-auth-BiDuyvOc.js → monitor-auth-dEHa1_On.js} +3 -3
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import { a as normalizeMattermostAllowList, b as readRequestBodyWithLimit, d as createChannelMessageReplyPipeline, g as loadOutboundMediaFromUrl, l as buildModelsProviderData, m as isTrustedProxyAddress, p as isRequestBodyLimitError, t as authorizeMattermostCommandInvocation, v as logTypingFailure, w as resolveClientIp } from "./monitor-auth-BiDuyvOc.js";
|
|
2
|
-
import { b as updateMattermostPost, c as createMattermostPost, d as fetchMattermostChannelByName, f as fetchMattermostMe, g as normalizeMattermostBaseUrl, h as fetchMattermostUserTeams, m as fetchMattermostUserByUsername, o as createMattermostClient, p as fetchMattermostUser, r as resolveMattermostAccount, s as createMattermostDirectChannelWithRetry, u as fetchMattermostChannel, v as resolveMattermostReplyDeliveryBarrierTimeoutMs, x as uploadMattermostFile, y as sendMattermostTyping } from "./accounts-B2NRPlqr.js";
|
|
3
1
|
import { t as getMattermostRuntime } from "./runtime-CNB4YGqJ.js";
|
|
2
|
+
import { _ as uploadMattermostFile, c as fetchMattermostMe, d as fetchMattermostUserTeams, f as normalizeMattermostBaseUrl, h as sendMattermostTyping, i as createMattermostPost, l as fetchMattermostUser, m as resolveMattermostReplyDeliveryBarrierTimeoutMs, n as createMattermostClient, o as fetchMattermostChannel, r as createMattermostDirectChannelWithRetry, s as fetchMattermostChannelByName, u as fetchMattermostUserByUsername } from "./client-BzLj4dyf.js";
|
|
3
|
+
import { a as normalizeMattermostAllowList, b as readRequestBodyWithLimit, d as createChannelMessageReplyPipeline, g as loadOutboundMediaFromUrl, l as buildModelsProviderData, p as isRequestBodyLimitError, t as authorizeMattermostCommandInvocation, v as logTypingFailure } from "./monitor-auth-dEHa1_On.js";
|
|
4
|
+
import { c as setInteractionSecret, n as buildButtonProps, o as resolveInteractionCallbackUrl } from "./interactions-DO7J-ND1.js";
|
|
5
|
+
import { r as resolveMattermostAccount } from "./accounts-C4Azn6rA.js";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
4
8
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringifiedOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
9
|
+
import { asDateTimestampMs, parseStrictInteger, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
10
|
+
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
5
11
|
import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
|
|
6
12
|
import { resolveStoredModelOverride } from "openclaw/plugin-sdk/command-auth-native";
|
|
7
13
|
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
|
|
8
|
-
import { asDateTimestampMs, parseStrictInteger, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
9
14
|
import { deliverTextOrMediaReply, isReasoningReplyPayload, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
10
|
-
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
11
15
|
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-chunking";
|
|
12
16
|
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
13
17
|
import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared";
|
|
14
18
|
import { Readable } from "node:stream";
|
|
15
|
-
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
16
19
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
17
|
-
import { createHash, createHmac } from "node:crypto";
|
|
18
20
|
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
19
21
|
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
20
22
|
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
@@ -687,374 +689,6 @@ async function deliverMattermostReplyPayload(params) {
|
|
|
687
689
|
});
|
|
688
690
|
}
|
|
689
691
|
//#endregion
|
|
690
|
-
//#region extensions/mattermost/src/mattermost/interactions.ts
|
|
691
|
-
const INTERACTION_MAX_BODY_BYTES = 64 * 1024;
|
|
692
|
-
const INTERACTION_BODY_TIMEOUT_MS = 1e4;
|
|
693
|
-
const SIGNED_CHANNEL_ID_CONTEXT_KEY = "__openclaw_channel_id";
|
|
694
|
-
const callbackUrls = /* @__PURE__ */ new Map();
|
|
695
|
-
function setInteractionCallbackUrl(accountId, url) {
|
|
696
|
-
callbackUrls.set(accountId, url);
|
|
697
|
-
}
|
|
698
|
-
function resolveInteractionCallbackPath(accountId) {
|
|
699
|
-
return `/mattermost/interactions/${accountId}`;
|
|
700
|
-
}
|
|
701
|
-
function isWildcardBindHost(rawHost) {
|
|
702
|
-
const trimmed = rawHost.trim();
|
|
703
|
-
if (!trimmed) return false;
|
|
704
|
-
const host = trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
|
|
705
|
-
return host === "0.0.0.0" || host === "::" || host === "0:0:0:0:0:0:0:0" || host === "::0";
|
|
706
|
-
}
|
|
707
|
-
function normalizeCallbackBaseUrl(baseUrl) {
|
|
708
|
-
return baseUrl.trim().replace(/\/+$/, "");
|
|
709
|
-
}
|
|
710
|
-
function headerValue(value) {
|
|
711
|
-
if (Array.isArray(value)) return normalizeOptionalString(value[0]);
|
|
712
|
-
return normalizeOptionalString(value);
|
|
713
|
-
}
|
|
714
|
-
function isAllowedInteractionSource(params) {
|
|
715
|
-
const { allowedSourceIps } = params;
|
|
716
|
-
if (!allowedSourceIps?.length) return true;
|
|
717
|
-
return isTrustedProxyAddress(resolveClientIp({
|
|
718
|
-
remoteAddr: params.req.socket?.remoteAddress,
|
|
719
|
-
forwardedFor: headerValue(params.req.headers["x-forwarded-for"]),
|
|
720
|
-
realIp: headerValue(params.req.headers["x-real-ip"]),
|
|
721
|
-
trustedProxies: params.trustedProxies,
|
|
722
|
-
allowRealIpFallback: params.allowRealIpFallback
|
|
723
|
-
}), allowedSourceIps);
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* Resolve the interaction callback URL for an account.
|
|
727
|
-
* Falls back to computing it from interactions.callbackBaseUrl or gateway host config.
|
|
728
|
-
*/
|
|
729
|
-
function computeInteractionCallbackUrl(accountId, cfg) {
|
|
730
|
-
const path = resolveInteractionCallbackPath(accountId);
|
|
731
|
-
const callbackBaseUrl = normalizeOptionalString(cfg?.interactions?.callbackBaseUrl) ?? normalizeOptionalString(cfg?.channels?.mattermost?.interactions?.callbackBaseUrl);
|
|
732
|
-
if (callbackBaseUrl) return `${normalizeCallbackBaseUrl(callbackBaseUrl)}${path}`;
|
|
733
|
-
const port = typeof cfg?.gateway?.port === "number" ? cfg.gateway.port : 18789;
|
|
734
|
-
let host = cfg?.gateway?.customBindHost && !isWildcardBindHost(cfg.gateway.customBindHost) ? cfg.gateway.customBindHost.trim() : "localhost";
|
|
735
|
-
if (host.includes(":") && !(host.startsWith("[") && host.endsWith("]"))) host = `[${host}]`;
|
|
736
|
-
return `http://${host}:${port}${path}`;
|
|
737
|
-
}
|
|
738
|
-
/**
|
|
739
|
-
* Resolve the interaction callback URL for an account.
|
|
740
|
-
* Prefers the in-memory registered URL (set by the gateway monitor) so callers outside the
|
|
741
|
-
* monitor lifecycle can reuse the runtime-validated callback destination.
|
|
742
|
-
*/
|
|
743
|
-
function resolveInteractionCallbackUrl(accountId, cfg) {
|
|
744
|
-
const cached = callbackUrls.get(accountId);
|
|
745
|
-
if (cached) return cached;
|
|
746
|
-
return computeInteractionCallbackUrl(accountId, cfg);
|
|
747
|
-
}
|
|
748
|
-
const interactionSecrets = /* @__PURE__ */ new Map();
|
|
749
|
-
let defaultInteractionSecret;
|
|
750
|
-
function deriveInteractionSecret(botToken) {
|
|
751
|
-
return createHmac("sha256", "openclaw-mattermost-interactions").update(botToken).digest("hex");
|
|
752
|
-
}
|
|
753
|
-
function setInteractionSecret(accountIdOrBotToken, botToken) {
|
|
754
|
-
if (typeof botToken === "string") {
|
|
755
|
-
interactionSecrets.set(accountIdOrBotToken, deriveInteractionSecret(botToken));
|
|
756
|
-
return;
|
|
757
|
-
}
|
|
758
|
-
defaultInteractionSecret = deriveInteractionSecret(accountIdOrBotToken);
|
|
759
|
-
}
|
|
760
|
-
function getInteractionSecret(accountId) {
|
|
761
|
-
const scoped = accountId ? interactionSecrets.get(accountId) : void 0;
|
|
762
|
-
if (scoped) return scoped;
|
|
763
|
-
if (defaultInteractionSecret) return defaultInteractionSecret;
|
|
764
|
-
if (interactionSecrets.size === 1) {
|
|
765
|
-
const first = interactionSecrets.values().next().value;
|
|
766
|
-
if (typeof first === "string") return first;
|
|
767
|
-
}
|
|
768
|
-
throw new Error("Interaction secret not initialized — call setInteractionSecret(accountId, botToken) first");
|
|
769
|
-
}
|
|
770
|
-
function canonicalizeInteractionContext(value) {
|
|
771
|
-
if (Array.isArray(value)) return value.map((item) => canonicalizeInteractionContext(item));
|
|
772
|
-
if (value && typeof value === "object") {
|
|
773
|
-
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, entryValue]) => [key, canonicalizeInteractionContext(entryValue)]);
|
|
774
|
-
return Object.fromEntries(entries);
|
|
775
|
-
}
|
|
776
|
-
return value;
|
|
777
|
-
}
|
|
778
|
-
function generateInteractionToken(context, accountId) {
|
|
779
|
-
const secret = getInteractionSecret(accountId);
|
|
780
|
-
const payload = JSON.stringify(canonicalizeInteractionContext(context));
|
|
781
|
-
return createHmac("sha256", secret).update(payload).digest("hex");
|
|
782
|
-
}
|
|
783
|
-
function verifyInteractionToken(context, token, accountId) {
|
|
784
|
-
return safeEqualSecret(generateInteractionToken(context, accountId), token);
|
|
785
|
-
}
|
|
786
|
-
/**
|
|
787
|
-
* Build Mattermost `props.attachments` with interactive buttons.
|
|
788
|
-
*
|
|
789
|
-
* Each button includes an HMAC token in its integration context so the
|
|
790
|
-
* callback handler can verify the request originated from a legitimate
|
|
791
|
-
* button click (Mattermost's recommended security pattern).
|
|
792
|
-
*/
|
|
793
|
-
/**
|
|
794
|
-
* Sanitize a button ID so Mattermost's action router can match it.
|
|
795
|
-
* Mattermost uses the action ID in the URL path `/api/v4/posts/{id}/actions/{actionId}`
|
|
796
|
-
* and IDs containing hyphens or underscores break the server-side routing.
|
|
797
|
-
* See: https://github.com/mattermost/mattermost/issues/25747
|
|
798
|
-
*/
|
|
799
|
-
function sanitizeActionId(id) {
|
|
800
|
-
return id.replace(/[-_]/g, "");
|
|
801
|
-
}
|
|
802
|
-
function buildButtonAttachments(params) {
|
|
803
|
-
const actions = params.buttons.map((btn) => {
|
|
804
|
-
const safeId = sanitizeActionId(btn.id);
|
|
805
|
-
const context = {
|
|
806
|
-
action_id: safeId,
|
|
807
|
-
...btn.context
|
|
808
|
-
};
|
|
809
|
-
const token = generateInteractionToken(context, params.accountId);
|
|
810
|
-
return {
|
|
811
|
-
id: safeId,
|
|
812
|
-
type: "button",
|
|
813
|
-
name: btn.name,
|
|
814
|
-
style: btn.style,
|
|
815
|
-
integration: {
|
|
816
|
-
url: params.callbackUrl,
|
|
817
|
-
context: {
|
|
818
|
-
...context,
|
|
819
|
-
_token: token
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
};
|
|
823
|
-
});
|
|
824
|
-
return [{
|
|
825
|
-
text: params.text ?? "",
|
|
826
|
-
actions
|
|
827
|
-
}];
|
|
828
|
-
}
|
|
829
|
-
function buildButtonProps(params) {
|
|
830
|
-
const buttons = params.buttons.flatMap((item) => Array.isArray(item) ? item : [item]).map((btn) => ({
|
|
831
|
-
id: normalizeStringifiedOptionalString(btn.id ?? btn.callback_data) ?? "",
|
|
832
|
-
name: normalizeStringifiedOptionalString(btn.text ?? btn.name ?? btn.label) ?? "",
|
|
833
|
-
style: btn.style ?? "default",
|
|
834
|
-
context: typeof btn.context === "object" && btn.context !== null ? {
|
|
835
|
-
...btn.context,
|
|
836
|
-
[SIGNED_CHANNEL_ID_CONTEXT_KEY]: params.channelId
|
|
837
|
-
} : { [SIGNED_CHANNEL_ID_CONTEXT_KEY]: params.channelId }
|
|
838
|
-
})).filter((btn) => btn.id && btn.name);
|
|
839
|
-
if (buttons.length === 0) return;
|
|
840
|
-
return { attachments: buildButtonAttachments({
|
|
841
|
-
callbackUrl: params.callbackUrl,
|
|
842
|
-
accountId: params.accountId,
|
|
843
|
-
buttons,
|
|
844
|
-
text: params.text
|
|
845
|
-
}) };
|
|
846
|
-
}
|
|
847
|
-
function readInteractionBody(req) {
|
|
848
|
-
return readRequestBodyWithLimit(req, {
|
|
849
|
-
maxBytes: INTERACTION_MAX_BODY_BYTES,
|
|
850
|
-
timeoutMs: INTERACTION_BODY_TIMEOUT_MS
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
function createMattermostInteractionHandler(params) {
|
|
854
|
-
const { client, accountId, log } = params;
|
|
855
|
-
const core = getMattermostRuntime();
|
|
856
|
-
function parseInteractionPayload(raw) {
|
|
857
|
-
try {
|
|
858
|
-
return JSON.parse(raw);
|
|
859
|
-
} catch {
|
|
860
|
-
throw new Error("Mattermost interaction body was malformed JSON");
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
return async (req, res) => {
|
|
864
|
-
if (req.method !== "POST") {
|
|
865
|
-
res.statusCode = 405;
|
|
866
|
-
res.setHeader("Allow", "POST");
|
|
867
|
-
res.setHeader("Content-Type", "application/json");
|
|
868
|
-
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
if (!isAllowedInteractionSource({
|
|
872
|
-
req,
|
|
873
|
-
allowedSourceIps: params.allowedSourceIps,
|
|
874
|
-
trustedProxies: params.trustedProxies,
|
|
875
|
-
allowRealIpFallback: params.allowRealIpFallback
|
|
876
|
-
})) {
|
|
877
|
-
log?.(`mattermost interaction: rejected callback source remote=${req.socket?.remoteAddress ?? "?"}`);
|
|
878
|
-
res.statusCode = 403;
|
|
879
|
-
res.setHeader("Content-Type", "application/json");
|
|
880
|
-
res.end(JSON.stringify({ error: "Forbidden origin" }));
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
883
|
-
let payload;
|
|
884
|
-
try {
|
|
885
|
-
payload = parseInteractionPayload(await readInteractionBody(req));
|
|
886
|
-
} catch (err) {
|
|
887
|
-
log?.(`mattermost interaction: failed to parse body: ${String(err)}`);
|
|
888
|
-
res.statusCode = 400;
|
|
889
|
-
res.setHeader("Content-Type", "application/json");
|
|
890
|
-
res.end(JSON.stringify({ error: "Invalid request body" }));
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
const context = payload.context;
|
|
894
|
-
if (!context) {
|
|
895
|
-
res.statusCode = 400;
|
|
896
|
-
res.setHeader("Content-Type", "application/json");
|
|
897
|
-
res.end(JSON.stringify({ error: "Missing context" }));
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
const token = context["_token"];
|
|
901
|
-
if (typeof token !== "string") {
|
|
902
|
-
log?.("mattermost interaction: missing _token in context");
|
|
903
|
-
res.statusCode = 403;
|
|
904
|
-
res.setHeader("Content-Type", "application/json");
|
|
905
|
-
res.end(JSON.stringify({ error: "Missing token" }));
|
|
906
|
-
return;
|
|
907
|
-
}
|
|
908
|
-
const { _token, ...contextWithoutToken } = context;
|
|
909
|
-
if (!verifyInteractionToken(contextWithoutToken, token, accountId)) {
|
|
910
|
-
log?.("mattermost interaction: invalid _token");
|
|
911
|
-
res.statusCode = 403;
|
|
912
|
-
res.setHeader("Content-Type", "application/json");
|
|
913
|
-
res.end(JSON.stringify({ error: "Invalid token" }));
|
|
914
|
-
return;
|
|
915
|
-
}
|
|
916
|
-
const actionId = context.action_id;
|
|
917
|
-
if (typeof actionId !== "string") {
|
|
918
|
-
res.statusCode = 400;
|
|
919
|
-
res.setHeader("Content-Type", "application/json");
|
|
920
|
-
res.end(JSON.stringify({ error: "Missing action_id in context" }));
|
|
921
|
-
return;
|
|
922
|
-
}
|
|
923
|
-
const signedChannelId = typeof contextWithoutToken[SIGNED_CHANNEL_ID_CONTEXT_KEY] === "string" ? contextWithoutToken[SIGNED_CHANNEL_ID_CONTEXT_KEY].trim() : "";
|
|
924
|
-
if (signedChannelId && signedChannelId !== payload.channel_id) {
|
|
925
|
-
log?.(`mattermost interaction: signed channel mismatch payload=${payload.channel_id} signed=${signedChannelId}`);
|
|
926
|
-
res.statusCode = 403;
|
|
927
|
-
res.setHeader("Content-Type", "application/json");
|
|
928
|
-
res.end(JSON.stringify({ error: "Channel mismatch" }));
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
const userName = payload.user_name ?? payload.user_id;
|
|
932
|
-
let originalMessage;
|
|
933
|
-
let originalPost;
|
|
934
|
-
let clickedButtonName = null;
|
|
935
|
-
try {
|
|
936
|
-
originalPost = await client.request(`/posts/${payload.post_id}`);
|
|
937
|
-
const postChannelId = originalPost.channel_id?.trim();
|
|
938
|
-
if (!postChannelId || postChannelId !== payload.channel_id) {
|
|
939
|
-
log?.(`mattermost interaction: post channel mismatch payload=${payload.channel_id} post=${postChannelId ?? "<missing>"}`);
|
|
940
|
-
res.statusCode = 403;
|
|
941
|
-
res.setHeader("Content-Type", "application/json");
|
|
942
|
-
res.end(JSON.stringify({ error: "Post/channel mismatch" }));
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
945
|
-
originalMessage = originalPost.message ?? "";
|
|
946
|
-
const postAttachments = Array.isArray(originalPost?.props?.attachments) ? originalPost.props.attachments : [];
|
|
947
|
-
for (const att of postAttachments) {
|
|
948
|
-
const match = att.actions?.find((a) => a.id === actionId);
|
|
949
|
-
if (match?.name) {
|
|
950
|
-
clickedButtonName = match.name;
|
|
951
|
-
break;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
if (clickedButtonName === null) {
|
|
955
|
-
log?.(`mattermost interaction: action ${actionId} not found in post ${payload.post_id}`);
|
|
956
|
-
res.statusCode = 403;
|
|
957
|
-
res.setHeader("Content-Type", "application/json");
|
|
958
|
-
res.end(JSON.stringify({ error: "Unknown action" }));
|
|
959
|
-
return;
|
|
960
|
-
}
|
|
961
|
-
} catch (err) {
|
|
962
|
-
log?.(`mattermost interaction: failed to validate post ${payload.post_id}: ${String(err)}`);
|
|
963
|
-
res.statusCode = 500;
|
|
964
|
-
res.setHeader("Content-Type", "application/json");
|
|
965
|
-
res.end(JSON.stringify({ error: "Failed to validate interaction" }));
|
|
966
|
-
return;
|
|
967
|
-
}
|
|
968
|
-
if (!originalPost) {
|
|
969
|
-
log?.(`mattermost interaction: missing fetched post ${payload.post_id}`);
|
|
970
|
-
res.statusCode = 500;
|
|
971
|
-
res.setHeader("Content-Type", "application/json");
|
|
972
|
-
res.end(JSON.stringify({ error: "Failed to load interaction post" }));
|
|
973
|
-
return;
|
|
974
|
-
}
|
|
975
|
-
log?.(`mattermost interaction: action=${actionId} user=${payload.user_name ?? payload.user_id} post=${payload.post_id} channel=${payload.channel_id}`);
|
|
976
|
-
if (params.authorizeButtonClick) try {
|
|
977
|
-
const authorization = await params.authorizeButtonClick({
|
|
978
|
-
payload,
|
|
979
|
-
post: originalPost
|
|
980
|
-
});
|
|
981
|
-
if (!authorization.ok) {
|
|
982
|
-
res.statusCode = authorization.statusCode ?? 200;
|
|
983
|
-
res.setHeader("Content-Type", "application/json");
|
|
984
|
-
res.end(JSON.stringify(authorization.response ?? { ephemeral_text: "You are not allowed to use this action here." }));
|
|
985
|
-
return;
|
|
986
|
-
}
|
|
987
|
-
} catch (err) {
|
|
988
|
-
log?.(`mattermost interaction: authorization failed: ${String(err)}`);
|
|
989
|
-
res.statusCode = 500;
|
|
990
|
-
res.setHeader("Content-Type", "application/json");
|
|
991
|
-
res.end(JSON.stringify({ error: "Interaction authorization failed" }));
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
if (params.handleInteraction) try {
|
|
995
|
-
const response = await params.handleInteraction({
|
|
996
|
-
payload,
|
|
997
|
-
userName,
|
|
998
|
-
actionId,
|
|
999
|
-
actionName: clickedButtonName,
|
|
1000
|
-
originalMessage,
|
|
1001
|
-
context: contextWithoutToken,
|
|
1002
|
-
post: originalPost
|
|
1003
|
-
});
|
|
1004
|
-
if (response !== null) {
|
|
1005
|
-
res.statusCode = 200;
|
|
1006
|
-
res.setHeader("Content-Type", "application/json");
|
|
1007
|
-
res.end(JSON.stringify(response));
|
|
1008
|
-
return;
|
|
1009
|
-
}
|
|
1010
|
-
} catch (err) {
|
|
1011
|
-
log?.(`mattermost interaction: custom handler failed: ${String(err)}`);
|
|
1012
|
-
res.statusCode = 500;
|
|
1013
|
-
res.setHeader("Content-Type", "application/json");
|
|
1014
|
-
res.end(JSON.stringify({ error: "Interaction handler failed" }));
|
|
1015
|
-
return;
|
|
1016
|
-
}
|
|
1017
|
-
try {
|
|
1018
|
-
const eventLabel = `Mattermost button click: action="${actionId}" by ${payload.user_name ?? payload.user_id} in channel ${payload.channel_id}`;
|
|
1019
|
-
const sessionKey = params.resolveSessionKey ? await params.resolveSessionKey({
|
|
1020
|
-
channelId: payload.channel_id,
|
|
1021
|
-
userId: payload.user_id,
|
|
1022
|
-
post: originalPost
|
|
1023
|
-
}) : `agent:main:mattermost:${accountId}:${payload.channel_id}`;
|
|
1024
|
-
core.system.enqueueSystemEvent(eventLabel, {
|
|
1025
|
-
sessionKey,
|
|
1026
|
-
contextKey: `mattermost:interaction:${payload.post_id}:${actionId}`
|
|
1027
|
-
});
|
|
1028
|
-
} catch (err) {
|
|
1029
|
-
log?.(`mattermost interaction: system event dispatch failed: ${String(err)}`);
|
|
1030
|
-
}
|
|
1031
|
-
try {
|
|
1032
|
-
await updateMattermostPost(client, payload.post_id, {
|
|
1033
|
-
message: originalMessage,
|
|
1034
|
-
props: { attachments: [{ text: `✓ **${clickedButtonName}** selected by @${userName}` }] }
|
|
1035
|
-
});
|
|
1036
|
-
} catch (err) {
|
|
1037
|
-
log?.(`mattermost interaction: failed to update post ${payload.post_id}: ${String(err)}`);
|
|
1038
|
-
}
|
|
1039
|
-
res.statusCode = 200;
|
|
1040
|
-
res.setHeader("Content-Type", "application/json");
|
|
1041
|
-
res.end("{}");
|
|
1042
|
-
if (params.dispatchButtonClick) try {
|
|
1043
|
-
await params.dispatchButtonClick({
|
|
1044
|
-
channelId: payload.channel_id,
|
|
1045
|
-
userId: payload.user_id,
|
|
1046
|
-
userName,
|
|
1047
|
-
actionId,
|
|
1048
|
-
actionName: clickedButtonName,
|
|
1049
|
-
postId: payload.post_id,
|
|
1050
|
-
post: originalPost
|
|
1051
|
-
});
|
|
1052
|
-
} catch (err) {
|
|
1053
|
-
log?.(`mattermost interaction: dispatchButtonClick failed: ${String(err)}`);
|
|
1054
|
-
}
|
|
1055
|
-
};
|
|
1056
|
-
}
|
|
1057
|
-
//#endregion
|
|
1058
692
|
//#region extensions/mattermost/src/mattermost/target-resolution.ts
|
|
1059
693
|
const MATTERMOST_OPAQUE_TARGET_CACHE_MAX_ENTRIES = 1024;
|
|
1060
694
|
const mattermostOpaqueTargetCache = /* @__PURE__ */ new Map();
|
|
@@ -1154,6 +788,7 @@ async function resolveMattermostOpaqueTarget(params) {
|
|
|
1154
788
|
cfg: params.cfg,
|
|
1155
789
|
accountId: params.accountId
|
|
1156
790
|
}) : null;
|
|
791
|
+
if (account && !account.enabled) throw new Error(`Mattermost account "${account.accountId}" is disabled`);
|
|
1157
792
|
const token = normalizeOptionalString(params.token) ?? normalizeOptionalString(account?.botToken);
|
|
1158
793
|
const baseUrl = normalizeMattermostBaseUrl(params.baseUrl ?? account?.baseUrl);
|
|
1159
794
|
if (!token || !baseUrl) return null;
|
|
@@ -2289,4 +1924,4 @@ function registerSlashCommandRoute(api) {
|
|
|
2289
1924
|
});
|
|
2290
1925
|
}
|
|
2291
1926
|
//#endregion
|
|
2292
|
-
export { registerSlashCommands as
|
|
1927
|
+
export { registerSlashCommands as _, sendMessageMattermost as a, deliverMattermostReplyPayload as c, renderMattermostModelsPickerView as d, renderMattermostProviderPickerView as f, isSlashCommandsEnabled as g, cleanupSlashCommands as h, registerSlashCommandRoute as i, buildMattermostAllowedModelRefs as l, DEFAULT_COMMAND_SPECS as m, deactivateSlashCommands as n, resolveMattermostOpaqueTarget as o, resolveMattermostModelPickerCurrentModel as p, getSlashCommandState as r, createMattermostReplyDeliveryBarrier as s, activateSlashCommands as t, parseMattermostModelPickerContext as u, resolveCallbackUrl as v, resolveSlashCommandConfig as y };
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/mattermost",
|
|
3
|
-
"version": "2026.7.2-beta.
|
|
3
|
+
"version": "2026.7.2-beta.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openclaw/mattermost",
|
|
9
|
-
"version": "2026.7.2-beta.
|
|
9
|
+
"version": "2026.7.2-beta.2",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ws": "8.21.0",
|
|
12
12
|
"zod": "4.4.3"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"openclaw": ">=2026.7.2-beta.
|
|
15
|
+
"openclaw": ">=2026.7.2-beta.2"
|
|
16
16
|
},
|
|
17
17
|
"peerDependenciesMeta": {
|
|
18
18
|
"openclaw": {
|
package/openclaw.plugin.json
CHANGED
|
@@ -229,6 +229,13 @@
|
|
|
229
229
|
"properties": {
|
|
230
230
|
"toolProgress": {
|
|
231
231
|
"type": "boolean"
|
|
232
|
+
},
|
|
233
|
+
"commandText": {
|
|
234
|
+
"type": "string",
|
|
235
|
+
"enum": [
|
|
236
|
+
"raw",
|
|
237
|
+
"status"
|
|
238
|
+
]
|
|
232
239
|
}
|
|
233
240
|
},
|
|
234
241
|
"additionalProperties": false
|
|
@@ -265,6 +272,13 @@
|
|
|
265
272
|
},
|
|
266
273
|
"toolProgress": {
|
|
267
274
|
"type": "boolean"
|
|
275
|
+
},
|
|
276
|
+
"commandText": {
|
|
277
|
+
"type": "string",
|
|
278
|
+
"enum": [
|
|
279
|
+
"raw",
|
|
280
|
+
"status"
|
|
281
|
+
]
|
|
268
282
|
}
|
|
269
283
|
},
|
|
270
284
|
"additionalProperties": false
|
|
@@ -668,6 +682,13 @@
|
|
|
668
682
|
"properties": {
|
|
669
683
|
"toolProgress": {
|
|
670
684
|
"type": "boolean"
|
|
685
|
+
},
|
|
686
|
+
"commandText": {
|
|
687
|
+
"type": "string",
|
|
688
|
+
"enum": [
|
|
689
|
+
"raw",
|
|
690
|
+
"status"
|
|
691
|
+
]
|
|
671
692
|
}
|
|
672
693
|
},
|
|
673
694
|
"additionalProperties": false
|
|
@@ -704,6 +725,13 @@
|
|
|
704
725
|
},
|
|
705
726
|
"toolProgress": {
|
|
706
727
|
"type": "boolean"
|
|
728
|
+
},
|
|
729
|
+
"commandText": {
|
|
730
|
+
"type": "string",
|
|
731
|
+
"enum": [
|
|
732
|
+
"raw",
|
|
733
|
+
"status"
|
|
734
|
+
]
|
|
707
735
|
}
|
|
708
736
|
},
|
|
709
737
|
"additionalProperties": false
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/mattermost",
|
|
3
|
-
"version": "2026.7.2-beta.
|
|
3
|
+
"version": "2026.7.2-beta.2",
|
|
4
4
|
"description": "OpenClaw Mattermost channel plugin",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"zod": "4.4.3"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"openclaw": ">=2026.7.2-beta.
|
|
15
|
+
"openclaw": ">=2026.7.2-beta.2"
|
|
16
16
|
},
|
|
17
17
|
"peerDependenciesMeta": {
|
|
18
18
|
"openclaw": {
|
|
@@ -41,10 +41,10 @@
|
|
|
41
41
|
"allowInvalidConfigRecovery": true
|
|
42
42
|
},
|
|
43
43
|
"compat": {
|
|
44
|
-
"pluginApi": ">=2026.7.2-beta.
|
|
44
|
+
"pluginApi": ">=2026.7.2-beta.2"
|
|
45
45
|
},
|
|
46
46
|
"build": {
|
|
47
|
-
"openclawVersion": "2026.7.2-beta.
|
|
47
|
+
"openclawVersion": "2026.7.2-beta.2",
|
|
48
48
|
"bundledDist": false
|
|
49
49
|
},
|
|
50
50
|
"release": {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { parseAccessGroupAllowFromEntry } from "openclaw/plugin-sdk/access-groups";
|
|
2
|
-
import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
3
1
|
import { normalizeLowercaseStringOrEmpty, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
2
|
+
import { parseTcpPort } from "openclaw/plugin-sdk/number-runtime";
|
|
4
3
|
import { buildAgentMediaPayload } from "openclaw/plugin-sdk/agent-media-payload";
|
|
5
4
|
import { resolveAllowlistMatchSimple } from "openclaw/plugin-sdk/allow-from";
|
|
6
5
|
import { logInboundDrop } from "openclaw/plugin-sdk/channel-inbound";
|
|
@@ -17,7 +16,8 @@ import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "opencla
|
|
|
17
16
|
import { registerPluginHttpRoute } from "openclaw/plugin-sdk/webhook-targets";
|
|
18
17
|
import { isRequestBodyLimitError, readRequestBodyWithLimit } from "openclaw/plugin-sdk/webhook-ingress";
|
|
19
18
|
import { isTrustedProxyAddress, resolveClientIp } from "openclaw/plugin-sdk/core";
|
|
20
|
-
import {
|
|
19
|
+
import { parseAccessGroupAllowFromEntry } from "openclaw/plugin-sdk/access-groups";
|
|
20
|
+
import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
21
21
|
//#region extensions/mattermost/src/mattermost/monitor-auth.ts
|
|
22
22
|
const mattermostIngressIdentity = {
|
|
23
23
|
key: "sender-id",
|