@openclaw/googlechat 2026.6.2-beta.1 → 2026.6.5-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.
@@ -1,12 +1,161 @@
1
- import { p as fetchWithSsrFGuard$1 } from "./runtime-api-BbVoWRxq.js";
2
- import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
1
+ import { l as resolveGoogleChatAccount, r as formatGoogleChatAllowFromEntry } from "./channel-base-Qh87GFW_.js";
2
+ import { T as resolveChannelMediaMaxBytes, _ as missingTargetError, g as loadOutboundMediaFromUrl, i as PAIRING_APPROVED_MESSAGE, o as chunkTextForOutbound, p as fetchWithSsrFGuard$1, x as readRemoteMediaBuffer } from "./runtime-api-BbVoWRxq.js";
3
+ import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
4
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
3
5
  import crypto from "node:crypto";
4
6
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
5
7
  import { parseMediaContentLength } from "openclaw/plugin-sdk/media-runtime";
6
8
  import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
7
9
  import { buildHostnameAllowlistPolicyFromSuffixAllowlist, fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
10
+ import { createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, sanitizeForPlainText } from "openclaw/plugin-sdk/channel-outbound";
8
11
  import fs from "node:fs/promises";
9
12
  import { resolveUserPath } from "openclaw/plugin-sdk/text-utility-runtime";
13
+ import { adaptScopedAccountAccessor } from "openclaw/plugin-sdk/channel-config-helpers";
14
+ import { composeAccountWarningCollectors, createAllowlistProviderOpenWarningCollector, resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
15
+ import { createChannelDirectoryAdapter, listResolvedDirectoryGroupEntriesFromMapKeys, listResolvedDirectoryUserEntriesFromAllowFrom } from "openclaw/plugin-sdk/directory-runtime";
16
+ //#region extensions/googlechat/src/approval-card-actions.ts
17
+ const GOOGLECHAT_APPROVAL_ACTION = "openclaw.approval";
18
+ const GOOGLECHAT_APPROVAL_ACTION_PARAM = "openclaw_action";
19
+ const GOOGLECHAT_APPROVAL_TOKEN_PARAM = "token";
20
+ const GOOGLECHAT_APPROVAL_ACTION_VALUE = "approval";
21
+ const MANUAL_EXEC_APPROVAL_COMMAND_RE = /(?:^|[\s`])\/approve[ \t]+([^ \t\r\n`|]+)[ \t]+(allow-once|allow-always|deny)(?=$|[\s`|.,;:!?])/giu;
22
+ const approvalCardBindings = /* @__PURE__ */ new Map();
23
+ const approvalCardResolvingTokens = /* @__PURE__ */ new Set();
24
+ const manualApprovalFollowupSuppressions = /* @__PURE__ */ new Map();
25
+ function createGoogleChatApprovalToken() {
26
+ return crypto.randomBytes(18).toString("base64url");
27
+ }
28
+ function buildGoogleChatApprovalActionParameters(token) {
29
+ return [{
30
+ key: GOOGLECHAT_APPROVAL_ACTION_PARAM,
31
+ value: GOOGLECHAT_APPROVAL_ACTION_VALUE
32
+ }, {
33
+ key: GOOGLECHAT_APPROVAL_TOKEN_PARAM,
34
+ value: token
35
+ }];
36
+ }
37
+ function collectEventParameters(event) {
38
+ const params = {};
39
+ for (const [key, value] of Object.entries(event.common?.parameters ?? {})) if (typeof value === "string") params[key] = value;
40
+ for (const [key, value] of Object.entries(event.commonEventObject?.parameters ?? {})) if (typeof value === "string") params[key] = value;
41
+ for (const item of event.action?.parameters ?? []) if (typeof item.key === "string" && typeof item.value === "string") params[item.key] = item.value;
42
+ return params;
43
+ }
44
+ function readGoogleChatApprovalActionToken(event) {
45
+ const params = collectEventParameters(event);
46
+ if (params[GOOGLECHAT_APPROVAL_ACTION_PARAM] !== GOOGLECHAT_APPROVAL_ACTION_VALUE) return null;
47
+ const actionName = normalizeOptionalString(event.action?.actionMethodName) ?? normalizeOptionalString(event.common?.invokedFunction) ?? normalizeOptionalString(event.commonEventObject?.invokedFunction);
48
+ if (actionName && actionName !== "openclaw.approval" && !actionName.startsWith("https://")) return null;
49
+ return normalizeOptionalString(params[GOOGLECHAT_APPROVAL_TOKEN_PARAM]) ?? null;
50
+ }
51
+ function registerGoogleChatApprovalCardBinding(binding) {
52
+ if (binding.expiresAtMs <= Date.now()) return false;
53
+ approvalCardBindings.set(binding.token, binding);
54
+ registerGoogleChatManualApprovalFollowupSuppression({
55
+ approvalId: binding.approvalId,
56
+ approvalKind: binding.approvalKind,
57
+ allowedDecisions: binding.allowedDecisions,
58
+ expiresAtMs: binding.expiresAtMs
59
+ });
60
+ return true;
61
+ }
62
+ function getGoogleChatApprovalCardBinding(token) {
63
+ const binding = approvalCardBindings.get(token);
64
+ if (!binding) return null;
65
+ if (binding.expiresAtMs <= Date.now()) {
66
+ approvalCardBindings.delete(token);
67
+ return null;
68
+ }
69
+ return binding;
70
+ }
71
+ function normalizeApprovalRef(value) {
72
+ const normalized = value.trim().toLowerCase();
73
+ return normalized ? normalized : null;
74
+ }
75
+ function manualApprovalFollowupSuppressionKey(approvalId) {
76
+ return normalizeApprovalRef(approvalId);
77
+ }
78
+ function registerGoogleChatManualApprovalFollowupSuppression(suppression) {
79
+ if (suppression.expiresAtMs <= Date.now()) return false;
80
+ const key = manualApprovalFollowupSuppressionKey(suppression.approvalId);
81
+ if (!key) return false;
82
+ manualApprovalFollowupSuppressions.set(key, suppression);
83
+ return true;
84
+ }
85
+ function unregisterGoogleChatManualApprovalFollowupSuppression(approvalId) {
86
+ const key = manualApprovalFollowupSuppressionKey(approvalId);
87
+ if (key) manualApprovalFollowupSuppressions.delete(key);
88
+ }
89
+ function approvalRefMatches(bindingApprovalId, approvalRef) {
90
+ const normalizedBindingId = normalizeApprovalRef(bindingApprovalId);
91
+ const normalizedRef = normalizeApprovalRef(approvalRef);
92
+ if (!normalizedBindingId || !normalizedRef) return false;
93
+ return normalizedRef === normalizedBindingId || normalizedRef.length >= 8 && normalizedBindingId.startsWith(normalizedRef);
94
+ }
95
+ function pruneExpiredGoogleChatApprovalCardBindings(nowMs) {
96
+ for (const [token, binding] of approvalCardBindings) if (binding.expiresAtMs <= nowMs) {
97
+ approvalCardBindings.delete(token);
98
+ approvalCardResolvingTokens.delete(token);
99
+ }
100
+ for (const [approvalId, suppression] of manualApprovalFollowupSuppressions) if (suppression.expiresAtMs <= nowMs) manualApprovalFollowupSuppressions.delete(approvalId);
101
+ }
102
+ function hasActiveGoogleChatExecApprovalCardForManualCommand(params) {
103
+ pruneExpiredGoogleChatApprovalCardBindings(params.nowMs);
104
+ for (const binding of approvalCardBindings.values()) if (binding.approvalKind === "exec" && binding.allowedDecisions.includes(params.decision) && approvalRefMatches(binding.approvalId, params.approvalRef)) return true;
105
+ for (const suppression of manualApprovalFollowupSuppressions.values()) if (suppression.approvalKind === "exec" && suppression.allowedDecisions.includes(params.decision) && approvalRefMatches(suppression.approvalId, params.approvalRef)) return true;
106
+ return false;
107
+ }
108
+ function shouldSuppressGoogleChatManualExecApprovalFollowupText(text, nowMs = Date.now()) {
109
+ for (const match of text.matchAll(MANUAL_EXEC_APPROVAL_COMMAND_RE)) {
110
+ const approvalRef = match[1];
111
+ const decision = match[2]?.toLowerCase();
112
+ if (approvalRef && decision && hasActiveGoogleChatExecApprovalCardForManualCommand({
113
+ approvalRef,
114
+ decision,
115
+ nowMs
116
+ })) return true;
117
+ }
118
+ return false;
119
+ }
120
+ function hasSendableMedia(payload) {
121
+ return Boolean(payload.mediaUrl?.trim() || payload.mediaUrls?.some((url) => url.trim()));
122
+ }
123
+ function hasStructuredPayloadPart(payload) {
124
+ return Boolean(hasSendableMedia(payload) || payload.presentation || payload.interactive || payload.btw || payload.spokenText || payload.ttsSupplement);
125
+ }
126
+ function shouldSuppressGoogleChatManualExecApprovalFollowupPayload(payload, nowMs = Date.now()) {
127
+ const text = payload.text?.trim();
128
+ if (!text || hasStructuredPayloadPart(payload)) return false;
129
+ return shouldSuppressGoogleChatManualExecApprovalFollowupText(text, nowMs);
130
+ }
131
+ function claimGoogleChatApprovalCardBinding(token) {
132
+ const binding = getGoogleChatApprovalCardBinding(token);
133
+ if (!binding) return { kind: "missing" };
134
+ if (approvalCardResolvingTokens.has(token)) return { kind: "in-flight" };
135
+ approvalCardResolvingTokens.add(token);
136
+ return {
137
+ kind: "claimed",
138
+ binding
139
+ };
140
+ }
141
+ function completeGoogleChatApprovalCardBinding(token) {
142
+ const binding = approvalCardBindings.get(token);
143
+ approvalCardResolvingTokens.delete(token);
144
+ approvalCardBindings.delete(token);
145
+ if (binding) unregisterGoogleChatManualApprovalFollowupSuppression(binding.approvalId);
146
+ }
147
+ function releaseGoogleChatApprovalCardBinding(token) {
148
+ approvalCardResolvingTokens.delete(token);
149
+ }
150
+ function unregisterGoogleChatApprovalCardBindings(tokens) {
151
+ for (const token of tokens) {
152
+ const binding = approvalCardBindings.get(token);
153
+ approvalCardBindings.delete(token);
154
+ approvalCardResolvingTokens.delete(token);
155
+ if (binding) unregisterGoogleChatManualApprovalFollowupSuppression(binding.approvalId);
156
+ }
157
+ }
158
+ //#endregion
10
159
  //#region extensions/googlechat/src/google-auth.runtime.ts
11
160
  const GOOGLE_AUTH_POLICY = buildHostnameAllowlistPolicyFromSuffixAllowlist(["accounts.google.com", "googleapis.com"]);
12
161
  const GOOGLE_AUTH_AUDIT_CONTEXT = "googlechat.auth.google-auth";
@@ -541,9 +690,11 @@ async function fetchBuffer(account, url, init, options) {
541
690
  });
542
691
  }
543
692
  async function sendGoogleChatMessage(params) {
544
- const { account, space, text, thread, attachments } = params;
693
+ const { account, space, text, thread, cardsV2, attachments } = params;
694
+ if (text && (!cardsV2 || cardsV2.length === 0) && (!attachments || attachments.length === 0) && shouldSuppressGoogleChatManualExecApprovalFollowupText(text)) return null;
545
695
  const body = {};
546
696
  if (text) body.text = text;
697
+ if (cardsV2 && cardsV2.length > 0) body.cardsV2 = cardsV2;
547
698
  if (thread) body.thread = { name: thread };
548
699
  if (attachments && attachments.length > 0) body.attachment = attachments.map((item) => Object.assign({ attachmentDataRef: { attachmentUploadToken: item.attachmentUploadToken } }, item.contentName ? { contentName: item.contentName } : {}));
549
700
  const urlObj = new URL(`${CHAT_API_BASE}/${space}/messages`);
@@ -555,10 +706,16 @@ async function sendGoogleChatMessage(params) {
555
706
  return result ? { messageName: result.name } : null;
556
707
  }
557
708
  async function updateGoogleChatMessage(params) {
558
- const { account, messageName, text } = params;
559
- return { messageName: (await fetchJson(account, `${CHAT_API_BASE}/${messageName}?updateMask=text`, {
709
+ const { account, messageName, text, cardsV2 } = params;
710
+ const updateMask = [...text !== void 0 ? ["text"] : [], ...cardsV2 !== void 0 ? ["cardsV2"] : []];
711
+ if (updateMask.length === 0) throw new Error("Google Chat message update requires text or cardsV2.");
712
+ const url = `${CHAT_API_BASE}/${messageName}?updateMask=${updateMask.join(",")}`;
713
+ const body = {};
714
+ if (text !== void 0) body.text = text;
715
+ if (cardsV2 !== void 0) body.cardsV2 = cardsV2;
716
+ return { messageName: (await fetchJson(account, url, {
560
717
  method: "PATCH",
561
- body: JSON.stringify({ text })
718
+ body: JSON.stringify(body)
562
719
  })).name };
563
720
  }
564
721
  async function deleteGoogleChatMessage(params) {
@@ -631,4 +788,275 @@ async function probeGoogleChat(account) {
631
788
  }
632
789
  }
633
790
  //#endregion
634
- export { findGoogleChatDirectMessage as a, sendGoogleChatMessage as c, verifyGoogleChatRequest as d, downloadGoogleChatMedia as i, updateGoogleChatMessage as l, deleteGoogleChatMessage as n, listGoogleChatReactions as o, deleteGoogleChatReaction as r, probeGoogleChat as s, createGoogleChatReaction as t, uploadGoogleChatAttachment as u };
791
+ //#region extensions/googlechat/src/targets.ts
792
+ function normalizeGoogleChatTarget(raw) {
793
+ const trimmed = raw?.trim();
794
+ if (!trimmed) return;
795
+ const normalized = trimmed.replace(/^(googlechat|google-chat|gchat):/i, "").replace(/^user:(users\/)?/i, "users/").replace(/^space:(spaces\/)?/i, "spaces/");
796
+ if (isGoogleChatUserTarget(normalized)) {
797
+ const suffix = normalized.slice(6);
798
+ return suffix.includes("@") ? `users/${normalizeLowercaseStringOrEmpty(suffix)}` : normalized;
799
+ }
800
+ if (isGoogleChatSpaceTarget(normalized)) return normalized;
801
+ if (normalized.includes("@")) return `users/${normalizeLowercaseStringOrEmpty(normalized)}`;
802
+ return normalized;
803
+ }
804
+ function isGoogleChatUserTarget(value) {
805
+ return normalizeLowercaseStringOrEmpty(value).startsWith("users/");
806
+ }
807
+ function isGoogleChatSpaceTarget(value) {
808
+ return normalizeLowercaseStringOrEmpty(value).startsWith("spaces/");
809
+ }
810
+ function stripMessageSuffix(target) {
811
+ const index = target.indexOf("/messages/");
812
+ if (index === -1) return target;
813
+ return target.slice(0, index);
814
+ }
815
+ async function resolveGoogleChatOutboundSpace(params) {
816
+ const normalized = normalizeGoogleChatTarget(params.target);
817
+ if (!normalized) throw new Error("Missing Google Chat target.");
818
+ const base = stripMessageSuffix(normalized);
819
+ if (isGoogleChatSpaceTarget(base)) return base;
820
+ if (isGoogleChatUserTarget(base)) {
821
+ const dm = await findGoogleChatDirectMessage({
822
+ account: params.account,
823
+ userName: base
824
+ });
825
+ if (!dm?.name) throw new Error(`No Google Chat DM found for ${base}`);
826
+ return dm.name;
827
+ }
828
+ return base;
829
+ }
830
+ //#endregion
831
+ //#region extensions/googlechat/src/group-policy.ts
832
+ function resolveGoogleChatGroupRequireMention(params) {
833
+ return resolveChannelGroupRequireMention({
834
+ cfg: params.cfg,
835
+ channel: "googlechat",
836
+ groupId: params.groupId,
837
+ accountId: params.accountId
838
+ });
839
+ }
840
+ //#endregion
841
+ //#region extensions/googlechat/src/channel.adapters.ts
842
+ const loadGoogleChatChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-CbSK_Zox.js"), "googleChatChannelRuntime");
843
+ function createGoogleChatSendReceipt(params) {
844
+ const messageId = params.messageId?.trim();
845
+ return createMessageReceiptFromOutboundResults({
846
+ results: messageId ? [{
847
+ channel: "googlechat",
848
+ messageId,
849
+ chatId: params.chatId,
850
+ conversationId: params.chatId
851
+ }] : [],
852
+ threadId: params.chatId,
853
+ kind: params.kind
854
+ });
855
+ }
856
+ const formatAllowFromEntry = formatGoogleChatAllowFromEntry;
857
+ const collectGoogleChatSecurityWarnings = composeAccountWarningCollectors(createAllowlistProviderOpenWarningCollector({
858
+ providerConfigPresent: (cfg) => cfg.channels?.googlechat !== void 0,
859
+ resolveGroupPolicy: (account) => account.config.groupPolicy,
860
+ buildOpenWarning: {
861
+ surface: "Google Chat spaces",
862
+ openBehavior: "allows any space to trigger (mention-gated)",
863
+ remediation: "Set channels.googlechat.groupPolicy=\"allowlist\" and configure channels.googlechat.groups"
864
+ }
865
+ }), (account) => account.config.dm?.policy === "open" && "- Google Chat DMs are open to anyone. Set channels.googlechat.dm.policy=\"pairing\" or \"allowlist\".");
866
+ const googlechatGroupsAdapter = { resolveRequireMention: resolveGoogleChatGroupRequireMention };
867
+ const googlechatDirectoryAdapter = createChannelDirectoryAdapter({
868
+ listPeers: async (params) => listResolvedDirectoryUserEntriesFromAllowFrom({
869
+ ...params,
870
+ resolveAccount: adaptScopedAccountAccessor(resolveGoogleChatAccount),
871
+ resolveAllowFrom: (account) => account.config.dm?.allowFrom,
872
+ normalizeId: (entry) => normalizeGoogleChatTarget(entry) ?? entry
873
+ }),
874
+ listGroups: async (params) => listResolvedDirectoryGroupEntriesFromMapKeys({
875
+ ...params,
876
+ resolveAccount: adaptScopedAccountAccessor(resolveGoogleChatAccount),
877
+ resolveGroups: (account) => account.config.groups
878
+ })
879
+ });
880
+ const googlechatSecurityAdapter = {
881
+ dm: {
882
+ channelKey: "googlechat",
883
+ resolvePolicy: (account) => account.config.dm?.policy,
884
+ resolveAllowFrom: (account) => account.config.dm?.allowFrom,
885
+ allowFromPathSuffix: "dm.",
886
+ normalizeEntry: (raw) => formatAllowFromEntry(raw)
887
+ },
888
+ collectWarnings: collectGoogleChatSecurityWarnings
889
+ };
890
+ const googlechatThreadingAdapter = {
891
+ scopedAccountReplyToMode: {
892
+ resolveAccount: (cfg, accountId) => resolveGoogleChatAccount({
893
+ cfg,
894
+ accountId
895
+ }),
896
+ resolveReplyToMode: (account, _chatType) => account.config.replyToMode,
897
+ fallback: "off"
898
+ },
899
+ buildToolContext: ({ cfg, accountId, context, hasRepliedRef }) => {
900
+ const currentChannelId = normalizeGoogleChatTarget(context.To);
901
+ const replyToId = normalizeOptionalString(context.ReplyToIdFull) ?? normalizeOptionalString(context.ReplyToId);
902
+ return {
903
+ currentChannelId,
904
+ currentMessageId: replyToId,
905
+ currentThreadTs: replyToId,
906
+ replyToMode: resolveGoogleChatAccount({
907
+ cfg,
908
+ accountId
909
+ }).config.replyToMode,
910
+ hasRepliedRef
911
+ };
912
+ }
913
+ };
914
+ const googlechatPairingTextAdapter = {
915
+ idLabel: "googlechatUserId",
916
+ message: PAIRING_APPROVED_MESSAGE,
917
+ normalizeAllowEntry: (entry) => formatAllowFromEntry(entry),
918
+ notify: async ({ cfg, id, message, accountId }) => {
919
+ const account = resolveGoogleChatAccount({
920
+ cfg,
921
+ accountId
922
+ });
923
+ if (account.credentialSource === "none") return;
924
+ const user = normalizeGoogleChatTarget(id) ?? id;
925
+ const space = await resolveGoogleChatOutboundSpace({
926
+ account,
927
+ target: isGoogleChatUserTarget(user) ? user : `users/${user}`
928
+ });
929
+ const { sendGoogleChatMessage } = await loadGoogleChatChannelRuntime();
930
+ await sendGoogleChatMessage({
931
+ account,
932
+ space,
933
+ text: message
934
+ });
935
+ }
936
+ };
937
+ const googlechatOutboundAdapter = {
938
+ base: {
939
+ deliveryMode: "direct",
940
+ chunker: chunkTextForOutbound,
941
+ chunkerMode: "markdown",
942
+ textChunkLimit: 4e3,
943
+ sanitizeText: ({ text }) => sanitizeForPlainText(text),
944
+ normalizePayload: ({ payload }) => shouldSuppressGoogleChatManualExecApprovalFollowupPayload(payload) ? null : payload,
945
+ resolveTarget: ({ to }) => {
946
+ const trimmed = normalizeOptionalString(to) ?? "";
947
+ if (trimmed) {
948
+ const normalized = normalizeGoogleChatTarget(trimmed);
949
+ if (!normalized) return {
950
+ ok: false,
951
+ error: missingTargetError("Google Chat", "<spaces/{space}|users/{user}>")
952
+ };
953
+ return {
954
+ ok: true,
955
+ to: normalized
956
+ };
957
+ }
958
+ return {
959
+ ok: false,
960
+ error: missingTargetError("Google Chat", "<spaces/{space}|users/{user}>")
961
+ };
962
+ }
963
+ },
964
+ attachedResults: {
965
+ channel: "googlechat",
966
+ sendText: async ({ cfg, to, text, accountId, replyToId, threadId }) => {
967
+ const account = resolveGoogleChatAccount({
968
+ cfg,
969
+ accountId
970
+ });
971
+ const space = await resolveGoogleChatOutboundSpace({
972
+ account,
973
+ target: to
974
+ });
975
+ const thread = typeof threadId === "number" ? String(threadId) : threadId ?? replyToId ?? void 0;
976
+ const { sendGoogleChatMessage } = await loadGoogleChatChannelRuntime();
977
+ const messageId = (await sendGoogleChatMessage({
978
+ account,
979
+ space,
980
+ text,
981
+ thread
982
+ }))?.messageName ?? "";
983
+ return {
984
+ messageId,
985
+ chatId: space,
986
+ receipt: createGoogleChatSendReceipt({
987
+ messageId,
988
+ chatId: space,
989
+ kind: "text"
990
+ })
991
+ };
992
+ },
993
+ sendMedia: async ({ cfg, to, text, mediaUrl, mediaAccess, mediaLocalRoots, mediaReadFile, accountId, replyToId, threadId }) => {
994
+ if (!mediaUrl) throw new Error("Google Chat mediaUrl is required.");
995
+ const account = resolveGoogleChatAccount({
996
+ cfg,
997
+ accountId
998
+ });
999
+ const space = await resolveGoogleChatOutboundSpace({
1000
+ account,
1001
+ target: to
1002
+ });
1003
+ const thread = typeof threadId === "number" ? String(threadId) : threadId ?? replyToId ?? void 0;
1004
+ const effectiveMaxBytes = resolveChannelMediaMaxBytes({
1005
+ cfg,
1006
+ resolveChannelLimitMb: ({ cfg: cfgLocal, accountId: accountIdLocal }) => (cfgLocal.channels?.googlechat)?.accounts?.[accountIdLocal]?.mediaMaxMb ?? (cfgLocal.channels?.googlechat)?.mediaMaxMb,
1007
+ accountId
1008
+ }) ?? (account.config.mediaMaxMb ?? 20) * 1024 * 1024;
1009
+ const loaded = /^https?:\/\//i.test(mediaUrl) ? await readRemoteMediaBuffer({
1010
+ url: mediaUrl,
1011
+ maxBytes: effectiveMaxBytes
1012
+ }) : await loadOutboundMediaFromUrl(mediaUrl, {
1013
+ maxBytes: effectiveMaxBytes,
1014
+ mediaAccess,
1015
+ mediaLocalRoots,
1016
+ mediaReadFile
1017
+ });
1018
+ const { sendGoogleChatMessage, uploadGoogleChatAttachment } = await loadGoogleChatChannelRuntime();
1019
+ const upload = await uploadGoogleChatAttachment({
1020
+ account,
1021
+ space,
1022
+ filename: loaded.fileName ?? "attachment",
1023
+ buffer: loaded.buffer,
1024
+ contentType: loaded.contentType
1025
+ });
1026
+ const messageId = (await sendGoogleChatMessage({
1027
+ account,
1028
+ space,
1029
+ text,
1030
+ thread,
1031
+ attachments: upload.attachmentUploadToken ? [{
1032
+ attachmentUploadToken: upload.attachmentUploadToken,
1033
+ contentName: loaded.fileName
1034
+ }] : void 0
1035
+ }))?.messageName ?? "";
1036
+ return {
1037
+ messageId,
1038
+ chatId: space,
1039
+ receipt: createGoogleChatSendReceipt({
1040
+ messageId,
1041
+ chatId: space,
1042
+ kind: "media"
1043
+ })
1044
+ };
1045
+ }
1046
+ }
1047
+ };
1048
+ const googlechatMessageAdapter = defineChannelMessageAdapter({
1049
+ id: "googlechat",
1050
+ durableFinal: { capabilities: {
1051
+ text: true,
1052
+ media: true,
1053
+ thread: true,
1054
+ messageSendingHooks: true
1055
+ } },
1056
+ send: {
1057
+ text: googlechatOutboundAdapter.attachedResults.sendText,
1058
+ media: googlechatOutboundAdapter.attachedResults.sendMedia
1059
+ }
1060
+ });
1061
+ //#endregion
1062
+ export { registerGoogleChatManualApprovalFollowupSuppression as A, buildGoogleChatApprovalActionParameters as C, getGoogleChatApprovalCardBinding as D, createGoogleChatApprovalToken as E, unregisterGoogleChatApprovalCardBindings as M, unregisterGoogleChatManualApprovalFollowupSuppression as N, readGoogleChatApprovalActionToken as O, GOOGLECHAT_APPROVAL_ACTION as S, completeGoogleChatApprovalCardBinding as T, probeGoogleChat as _, googlechatPairingTextAdapter as a, uploadGoogleChatAttachment as b, isGoogleChatSpaceTarget as c, resolveGoogleChatOutboundSpace as d, createGoogleChatReaction as f, listGoogleChatReactions as g, downloadGoogleChatMedia as h, googlechatOutboundAdapter as i, releaseGoogleChatApprovalCardBinding as j, registerGoogleChatApprovalCardBinding as k, isGoogleChatUserTarget as l, deleteGoogleChatReaction as m, googlechatGroupsAdapter as n, googlechatSecurityAdapter as o, deleteGoogleChatMessage as p, googlechatMessageAdapter as r, googlechatThreadingAdapter as s, googlechatDirectoryAdapter as t, normalizeGoogleChatTarget as u, sendGoogleChatMessage as v, claimGoogleChatApprovalCardBinding as w, verifyGoogleChatRequest as x, updateGoogleChatMessage as y };
@@ -1,13 +1,83 @@
1
+ import { D as getGoogleChatApprovalCardBinding, O as readGoogleChatApprovalActionToken, T as completeGoogleChatApprovalCardBinding, _ as probeGoogleChat, b as uploadGoogleChatAttachment, h as downloadGoogleChatMedia, j as releaseGoogleChatApprovalCardBinding, p as deleteGoogleChatMessage, v as sendGoogleChatMessage, w as claimGoogleChatApprovalCardBinding, x as verifyGoogleChatRequest, y as updateGoogleChatMessage } from "./channel.adapters-BQoq1F4R.js";
1
2
  import { E as resolveDefaultGroupPolicy, M as warnMissingProviderGroupPolicyFallbackOnce, O as resolveInboundRouteEnvelopeBuilderWithRuntime, P as getGoogleChatRuntime, k as resolveWebhookPath, m as isDangerousNameMatchingEnabled, n as GROUP_POLICY_BLOCKED_LABEL, u as createChannelPairingController, w as resolveAllowlistProviderRuntimeGroupPolicy } from "./runtime-api-BbVoWRxq.js";
2
- import { c as sendGoogleChatMessage, d as verifyGoogleChatRequest, i as downloadGoogleChatMedia, l as updateGoogleChatMessage, n as deleteGoogleChatMessage, s as probeGoogleChat, u as uploadGoogleChatAttachment } from "./api-DPvlxpWa.js";
3
+ import { n as googleChatApprovalAuth } from "./approval-auth-C-vWY4bY.js";
3
4
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
4
5
  import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
5
6
  import { recordChannelBotPairLoopAndCheckSuppression } from "openclaw/plugin-sdk/channel-inbound";
6
7
  import { WEBHOOK_RATE_LIMIT_DEFAULTS, createFixedWindowRateLimiter, normalizeWebhookPath, resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
7
8
  import { registerWebhookTargetWithPluginRoute, resolveWebhookTargetWithAuthOrReject, withResolvedWebhookRequestPipeline } from "openclaw/plugin-sdk/webhook-targets";
8
9
  import { createWebhookInFlightLimiter, readJsonWebhookBodyOrReject } from "openclaw/plugin-sdk/webhook-request-guards";
10
+ import { resolveApprovalOverGateway } from "openclaw/plugin-sdk/approval-gateway-runtime";
9
11
  import { channelIngressRoutes, createChannelIngressResolver, defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
10
12
  import { deliverTextOrMediaReply, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
13
+ //#region extensions/googlechat/src/approval-card-click.ts
14
+ function logIgnored(target, message) {
15
+ target.runtime.log?.(`[${target.account.accountId}] googlechat approval ignored: ${message}`);
16
+ }
17
+ async function maybeHandleGoogleChatApprovalCardClick(params) {
18
+ if ((params.event.type ?? params.event.eventType) !== "CARD_CLICKED") return false;
19
+ const token = readGoogleChatApprovalActionToken(params.event);
20
+ if (!token) return false;
21
+ const binding = getGoogleChatApprovalCardBinding(token);
22
+ if (!binding) {
23
+ logIgnored(params.target, "unknown or expired card token");
24
+ return true;
25
+ }
26
+ if (binding.accountId !== params.target.account.accountId) {
27
+ logIgnored(params.target, "card token account mismatch");
28
+ return true;
29
+ }
30
+ if (params.event.space?.name !== binding.spaceName) {
31
+ logIgnored(params.target, "card token space mismatch");
32
+ return true;
33
+ }
34
+ if (params.event.message?.name && params.event.message.name !== binding.messageName) {
35
+ logIgnored(params.target, "card token message mismatch");
36
+ return true;
37
+ }
38
+ if (!binding.allowedDecisions.includes(binding.decision)) {
39
+ logIgnored(params.target, "card token decision is no longer allowed");
40
+ return true;
41
+ }
42
+ const actor = params.event.user?.name;
43
+ if (!(googleChatApprovalAuth.authorizeActorAction?.({
44
+ cfg: params.target.config,
45
+ accountId: params.target.account.accountId,
46
+ senderId: actor,
47
+ action: "approve",
48
+ approvalKind: binding.approvalKind
49
+ }))?.authorized) {
50
+ logIgnored(params.target, `unauthorized actor ${actor || "unknown"}`);
51
+ return true;
52
+ }
53
+ const claim = claimGoogleChatApprovalCardBinding(token);
54
+ if (claim.kind === "missing") {
55
+ logIgnored(params.target, "card token already consumed");
56
+ return true;
57
+ }
58
+ if (claim.kind === "in-flight") {
59
+ logIgnored(params.target, "card token resolve already in flight");
60
+ return true;
61
+ }
62
+ const consumed = claim.binding;
63
+ try {
64
+ await resolveApprovalOverGateway({
65
+ cfg: params.target.config,
66
+ approvalId: consumed.approvalId,
67
+ decision: consumed.decision,
68
+ senderId: actor,
69
+ allowPluginFallback: consumed.approvalKind === "exec",
70
+ clientDisplayName: `Google Chat approval (${actor?.trim() || "unknown"})`
71
+ });
72
+ } catch (error) {
73
+ releaseGoogleChatApprovalCardBinding(token);
74
+ throw error;
75
+ }
76
+ completeGoogleChatApprovalCardBinding(token);
77
+ params.target.runtime.log?.(`[${params.target.account.accountId}] googlechat approval resolved id=${consumed.approvalId} decision=${consumed.decision} sender=${actor || "unknown"}`);
78
+ return true;
79
+ }
80
+ //#endregion
11
81
  //#region extensions/googlechat/src/monitor-access.ts
12
82
  function normalizeUserId(raw) {
13
83
  const trimmed = normalizeOptionalString(raw) ?? "";
@@ -419,6 +489,14 @@ function extractBearerToken(header) {
419
489
  }
420
490
  const ADD_ON_PREAUTH_MAX_BYTES = 16 * 1024;
421
491
  const ADD_ON_PREAUTH_TIMEOUT_MS = 3e3;
492
+ function recordParamsToActionParameters(params) {
493
+ if (!params) return;
494
+ const entries = Object.entries(params).filter((entry) => typeof entry[1] === "string").map(([key, value]) => ({
495
+ key,
496
+ value
497
+ }));
498
+ return entries.length > 0 ? entries : void 0;
499
+ }
422
500
  function parseGoogleChatInboundPayload(raw, res) {
423
501
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
424
502
  res.statusCode = 400;
@@ -428,6 +506,7 @@ function parseGoogleChatInboundPayload(raw, res) {
428
506
  let eventPayload = raw;
429
507
  let addOnBearerToken = "";
430
508
  const rawObj = raw;
509
+ if (rawObj.commonEventObject?.hostApp === "CHAT") addOnBearerToken = typeof rawObj.authorizationEventObject?.systemIdToken === "string" ? rawObj.authorizationEventObject.systemIdToken.trim() : "";
431
510
  if (rawObj.commonEventObject?.hostApp === "CHAT" && rawObj.chat?.messagePayload) {
432
511
  const chat = rawObj.chat;
433
512
  const messagePayload = chat.messagePayload;
@@ -438,7 +517,28 @@ function parseGoogleChatInboundPayload(raw, res) {
438
517
  user: chat.user,
439
518
  eventTime: chat.eventTime
440
519
  };
441
- addOnBearerToken = typeof rawObj.authorizationEventObject?.systemIdToken === "string" ? rawObj.authorizationEventObject.systemIdToken.trim() : "";
520
+ } else if (rawObj.commonEventObject?.hostApp === "CHAT") {
521
+ const chat = rawObj.chat;
522
+ const buttonClickedPayload = chat?.buttonClickedPayload;
523
+ if (buttonClickedPayload) {
524
+ const invokedFunction = rawObj.commonEventObject.invokedFunction;
525
+ const actionParameters = recordParamsToActionParameters(rawObj.commonEventObject.parameters);
526
+ eventPayload = {
527
+ type: "CARD_CLICKED",
528
+ space: buttonClickedPayload.space,
529
+ message: buttonClickedPayload.message,
530
+ user: buttonClickedPayload.user ?? chat.user,
531
+ eventTime: chat.eventTime,
532
+ action: buttonClickedPayload.action ?? {
533
+ ...typeof invokedFunction === "string" ? { actionMethodName: invokedFunction } : {},
534
+ ...actionParameters ? { parameters: actionParameters } : {}
535
+ },
536
+ commonEventObject: {
537
+ ...typeof invokedFunction === "string" ? { invokedFunction } : {},
538
+ parameters: rawObj.commonEventObject.parameters
539
+ }
540
+ };
541
+ }
442
542
  }
443
543
  const event = eventPayload;
444
544
  const eventType = event.type ?? eventPayload.eventType;
@@ -458,6 +558,12 @@ function parseGoogleChatInboundPayload(raw, res) {
458
558
  res.end("invalid payload");
459
559
  return { ok: false };
460
560
  }
561
+ } else if (eventType === "CARD_CLICKED") {
562
+ if (!event.user || typeof event.user !== "object" || Array.isArray(event.user)) {
563
+ res.statusCode = 400;
564
+ res.end("invalid payload");
565
+ return { ok: false };
566
+ }
461
567
  }
462
568
  return {
463
569
  ok: true,
@@ -677,7 +783,15 @@ function shouldSuppressGoogleChatBotLoop(params) {
677
783
  return true;
678
784
  }
679
785
  async function processGoogleChatEvent(event, target) {
680
- if ((event.type ?? event.eventType) !== "MESSAGE") return;
786
+ const eventType = event.type ?? event.eventType;
787
+ if (eventType === "CARD_CLICKED") {
788
+ await maybeHandleGoogleChatApprovalCardClick({
789
+ event,
790
+ target
791
+ });
792
+ return;
793
+ }
794
+ if (eventType !== "MESSAGE") return;
681
795
  if (!event.message || !event.space) return;
682
796
  await processMessageWithPipeline({
683
797
  event,
@@ -1,4 +1,4 @@
1
- import { t as googlechatDirectoryAdapter } from "./channel.adapters-LlD3wKl5.js";
1
+ import { t as googlechatDirectoryAdapter } from "./channel.adapters-BQoq1F4R.js";
2
2
  //#region extensions/googlechat/directory-contract-api.ts
3
3
  const googlechatDirectoryContractPlugin = {
4
4
  id: "googlechat",
@@ -1,4 +1,4 @@
1
- import { n as createGoogleChatPluginBase } from "./channel-base-DpPD0ef1.js";
1
+ import { n as createGoogleChatPluginBase } from "./channel-base-Qh87GFW_.js";
2
2
  //#region extensions/googlechat/src/channel.setup.ts
3
3
  const googlechatSetupPlugin = createGoogleChatPluginBase();
4
4
  //#endregion
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@openclaw/googlechat",
3
- "version": "2026.6.2-beta.1",
3
+ "version": "2026.6.5-beta.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/googlechat",
9
- "version": "2026.6.2-beta.1",
9
+ "version": "2026.6.5-beta.1",
10
10
  "dependencies": {
11
11
  "gaxios": "7.1.4",
12
12
  "google-auth-library": "10.6.2",
13
13
  "zod": "4.4.3"
14
14
  },
15
15
  "peerDependencies": {
16
- "openclaw": ">=2026.6.2-beta.1"
16
+ "openclaw": ">=2026.6.5-beta.1"
17
17
  },
18
18
  "peerDependenciesMeta": {
19
19
  "openclaw": {