@openclaw/googlechat 2026.5.12 → 2026.5.14-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.
@@ -1,7 +1,7 @@
1
- import { i as resolveGoogleChatAccount, t as listEnabledGoogleChatAccounts } from "./accounts-Dt3H6z0l.js";
2
- import { P as getGoogleChatRuntime } from "./runtime-api-CAQFEJsQ.js";
3
- import { c as sendGoogleChatMessage, o as listGoogleChatReactions, r as deleteGoogleChatReaction, t as createGoogleChatReaction, u as uploadGoogleChatAttachment } from "./api-DubPSfme.js";
4
- import { i as resolveGoogleChatOutboundSpace } from "./targets-C_U5SbnT.js";
1
+ import { o as resolveGoogleChatAccount, r as listEnabledGoogleChatAccounts } from "./setup-surface-BlDqtKML.js";
2
+ import { P as getGoogleChatRuntime } from "./runtime-api-DuEfWHnr.js";
3
+ import { c as sendGoogleChatMessage, o as listGoogleChatReactions, r as deleteGoogleChatReaction, t as createGoogleChatReaction, u as uploadGoogleChatAttachment } from "./api-BJ9DOsBR.js";
4
+ import { n as resolveGoogleChatOutboundSpace } from "./channel-DzmqN0DY.js";
5
5
  import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
6
6
  import { createActionGate, jsonResult, readNumberParam, readReactionParams, readStringParam } from "openclaw/plugin-sdk/channel-actions";
7
7
  import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
@@ -19,7 +19,7 @@ function resolveAppUserNames(account) {
19
19
  }
20
20
  async function loadGoogleChatActionMedia(params) {
21
21
  const runtime = getGoogleChatRuntime();
22
- return /^https?:\/\//i.test(params.mediaUrl) ? await runtime.channel.media.fetchRemoteMedia({
22
+ return /^https?:\/\//i.test(params.mediaUrl) ? await runtime.channel.media.readRemoteMediaBuffer({
23
23
  url: params.mediaUrl,
24
24
  maxBytes: params.maxBytes
25
25
  }) : await loadOutboundMediaFromUrl(params.mediaUrl, {
@@ -1,7 +1,8 @@
1
- import { m as fetchWithSsrFGuard$1 } from "./runtime-api-CAQFEJsQ.js";
1
+ import { p as fetchWithSsrFGuard$1 } from "./runtime-api-DuEfWHnr.js";
2
2
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
3
3
  import crypto from "node:crypto";
4
4
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
5
+ import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
5
6
  import { buildHostnameAllowlistPolicyFromSuffixAllowlist, fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
6
7
  import fs from "node:fs/promises";
7
8
  import { resolveUserPath } from "openclaw/plugin-sdk/text-utility-runtime";
@@ -313,6 +314,13 @@ const CHAT_SCOPE = "https://www.googleapis.com/auth/chat.bot";
313
314
  const CHAT_ISSUER = "chat@system.gserviceaccount.com";
314
315
  const ADDON_ISSUER_PATTERN = /^service-\d+@gcp-sa-gsuiteaddons\.iam\.gserviceaccount\.com$/;
315
316
  const CHAT_CERTS_URL = "https://www.googleapis.com/service_accounts/v1/metadata/x509/chat@system.gserviceaccount.com";
317
+ async function readGoogleChatCertsResponse(response) {
318
+ try {
319
+ return await response.json();
320
+ } catch (cause) {
321
+ throw new Error("Google Chat cert fetch failed: malformed JSON response", { cause });
322
+ }
323
+ }
316
324
  const MAX_AUTH_CACHE_SIZE = 32;
317
325
  const authCache = /* @__PURE__ */ new Map();
318
326
  let cachedCerts = null;
@@ -377,7 +385,7 @@ async function fetchChatCerts() {
377
385
  });
378
386
  try {
379
387
  if (!response.ok) throw new Error(`Failed to fetch Chat certs (${response.status})`);
380
- const certs = await response.json();
388
+ const certs = await readGoogleChatCertsResponse(response);
381
389
  cachedCerts = {
382
390
  fetchedAt: now,
383
391
  certs
@@ -451,6 +459,13 @@ async function verifyGoogleChatRequest(params) {
451
459
  //#region extensions/googlechat/src/api.ts
452
460
  const CHAT_API_BASE = "https://chat.googleapis.com/v1";
453
461
  const CHAT_UPLOAD_BASE = "https://chat.googleapis.com/upload/v1";
462
+ async function readGoogleChatJsonResponse(response, label) {
463
+ try {
464
+ return await response.json();
465
+ } catch (cause) {
466
+ throw new Error(`${label}: malformed JSON response`, { cause });
467
+ }
468
+ }
454
469
  const headersToObject = (headers) => headers instanceof Headers ? Object.fromEntries(headers.entries()) : Array.isArray(headers) ? Object.fromEntries(headers) : headers || {};
455
470
  async function withGoogleChatResponse(params) {
456
471
  const { account, url, init, auditContext, errorPrefix = "Google Chat API", handleResponse } = params;
@@ -488,7 +503,7 @@ async function fetchJson(account, url, init) {
488
503
  }
489
504
  },
490
505
  auditContext: "googlechat.api.json",
491
- handleResponse: async (response) => await response.json()
506
+ handleResponse: async (response) => await readGoogleChatJsonResponse(response, "Google Chat API request failed")
492
507
  });
493
508
  }
494
509
  async function fetchOk(account, url, init) {
@@ -513,26 +528,12 @@ async function fetchBuffer(account, url, init, options) {
513
528
  const length = Number(lengthHeader);
514
529
  if (Number.isFinite(length) && length > maxBytes) throw new Error(`Google Chat media exceeds max bytes (${maxBytes})`);
515
530
  }
516
- if (!maxBytes || !res.body) return {
531
+ if (!maxBytes) return {
517
532
  buffer: Buffer.from(await res.arrayBuffer()),
518
533
  contentType: res.headers.get("content-type") ?? void 0
519
534
  };
520
- const reader = res.body.getReader();
521
- const chunks = [];
522
- let total = 0;
523
- while (true) {
524
- const { done, value } = await reader.read();
525
- if (done) break;
526
- if (!value) continue;
527
- total += value.length;
528
- if (total > maxBytes) {
529
- await reader.cancel();
530
- throw new Error(`Google Chat media exceeds max bytes (${maxBytes})`);
531
- }
532
- chunks.push(Buffer.from(value));
533
- }
534
535
  return {
535
- buffer: Buffer.concat(chunks, total),
536
+ buffer: await readResponseWithLimit(res, maxBytes, { onOverflow: () => /* @__PURE__ */ new Error(`Google Chat media exceeds max bytes (${maxBytes})`) }),
536
537
  contentType: res.headers.get("content-type") ?? void 0
537
538
  };
538
539
  }
@@ -585,7 +586,7 @@ async function uploadGoogleChatAttachment(params) {
585
586
  },
586
587
  auditContext: "googlechat.upload",
587
588
  errorPrefix: "Google Chat upload",
588
- handleResponse: async (response) => await response.json()
589
+ handleResponse: async (response) => await readGoogleChatJsonResponse(response, "Google Chat upload failed")
589
590
  })).attachmentDataRef?.attachmentUploadToken };
590
591
  }
591
592
  async function downloadGoogleChatMedia(params) {
package/dist/api.js CHANGED
@@ -1,3 +1,3 @@
1
- import { t as googlechatPlugin } from "./channel-DoEjIr9p.js";
2
- import { n as googlechatSetupAdapter, t as googlechatSetupWizard } from "./setup-surface-HU4P5FzD.js";
1
+ import { n as googlechatSetupAdapter, t as googlechatSetupWizard } from "./setup-surface-BlDqtKML.js";
2
+ import { t as googlechatPlugin } from "./channel-DzmqN0DY.js";
3
3
  export { googlechatPlugin, googlechatSetupAdapter, googlechatSetupWizard };
@@ -1,9 +1,8 @@
1
- import { a as resolveGoogleChatConfigAccessorAccount, i as resolveGoogleChatAccount, n as listGoogleChatAccountIds, r as resolveDefaultGoogleChatAccountId } from "./accounts-Dt3H6z0l.js";
2
- import { T as resolveChannelMediaMaxBytes, _ as loadOutboundMediaFromUrl, a as buildChannelConfigSchema, i as PAIRING_APPROVED_MESSAGE, o as chunkTextForOutbound, p as fetchRemoteMedia, r as GoogleChatConfigSchema, t as DEFAULT_ACCOUNT_ID, v as missingTargetError } from "./runtime-api-CAQFEJsQ.js";
3
- import { i as resolveGoogleChatOutboundSpace, n as isGoogleChatUserTarget, r as normalizeGoogleChatTarget, t as isGoogleChatSpaceTarget } from "./targets-C_U5SbnT.js";
4
- import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-BsERSjSe.js";
5
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-DLAG2_NI.js";
6
- import { n as googlechatSetupAdapter, t as googlechatSetupWizard } from "./setup-surface-HU4P5FzD.js";
1
+ import { a as resolveDefaultGoogleChatAccountId, i as listGoogleChatAccountIds, n as googlechatSetupAdapter, o as resolveGoogleChatAccount, s as resolveGoogleChatConfigAccessorAccount, t as googlechatSetupWizard } from "./setup-surface-BlDqtKML.js";
2
+ import { T as resolveChannelMediaMaxBytes, _ as missingTargetError, a as buildChannelConfigSchema, g as loadOutboundMediaFromUrl, i as PAIRING_APPROVED_MESSAGE, o as chunkTextForOutbound, r as GoogleChatConfigSchema, t as DEFAULT_ACCOUNT_ID, x as readRemoteMediaBuffer } from "./runtime-api-DuEfWHnr.js";
3
+ import { a as findGoogleChatDirectMessage } from "./api-BJ9DOsBR.js";
4
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-CG1sLToP.js";
5
+ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-DtQ_IO7J.js";
7
6
  import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
8
7
  import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
9
8
  import { adaptScopedAccountAccessor, createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
@@ -19,6 +18,46 @@ import { createMessageReceiptFromOutboundResults, defineChannelMessageAdapter }
19
18
  import { composeAccountWarningCollectors, createAllowlistProviderOpenWarningCollector, createDangerousNameMatchingMutableAllowlistWarningCollector, resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
20
19
  import { createChannelDirectoryAdapter, listResolvedDirectoryGroupEntriesFromMapKeys, listResolvedDirectoryUserEntriesFromAllowFrom } from "openclaw/plugin-sdk/directory-runtime";
21
20
  import { sanitizeForPlainText } from "openclaw/plugin-sdk/outbound-runtime";
21
+ //#region extensions/googlechat/src/targets.ts
22
+ function normalizeGoogleChatTarget(raw) {
23
+ const trimmed = raw?.trim();
24
+ if (!trimmed) return;
25
+ const normalized = trimmed.replace(/^(googlechat|google-chat|gchat):/i, "").replace(/^user:(users\/)?/i, "users/").replace(/^space:(spaces\/)?/i, "spaces/");
26
+ if (isGoogleChatUserTarget(normalized)) {
27
+ const suffix = normalized.slice(6);
28
+ return suffix.includes("@") ? `users/${normalizeLowercaseStringOrEmpty(suffix)}` : normalized;
29
+ }
30
+ if (isGoogleChatSpaceTarget(normalized)) return normalized;
31
+ if (normalized.includes("@")) return `users/${normalizeLowercaseStringOrEmpty(normalized)}`;
32
+ return normalized;
33
+ }
34
+ function isGoogleChatUserTarget(value) {
35
+ return normalizeLowercaseStringOrEmpty(value).startsWith("users/");
36
+ }
37
+ function isGoogleChatSpaceTarget(value) {
38
+ return normalizeLowercaseStringOrEmpty(value).startsWith("spaces/");
39
+ }
40
+ function stripMessageSuffix(target) {
41
+ const index = target.indexOf("/messages/");
42
+ if (index === -1) return target;
43
+ return target.slice(0, index);
44
+ }
45
+ async function resolveGoogleChatOutboundSpace(params) {
46
+ const normalized = normalizeGoogleChatTarget(params.target);
47
+ if (!normalized) throw new Error("Missing Google Chat target.");
48
+ const base = stripMessageSuffix(normalized);
49
+ if (isGoogleChatSpaceTarget(base)) return base;
50
+ if (isGoogleChatUserTarget(base)) {
51
+ const dm = await findGoogleChatDirectMessage({
52
+ account: params.account,
53
+ userName: base
54
+ });
55
+ if (!dm?.name) throw new Error(`No Google Chat DM found for ${base}`);
56
+ return dm.name;
57
+ }
58
+ return base;
59
+ }
60
+ //#endregion
22
61
  //#region extensions/googlechat/src/approval-auth.ts
23
62
  function normalizeGoogleChatApproverId(value) {
24
63
  const normalized = normalizeGoogleChatTarget(String(value));
@@ -54,7 +93,7 @@ function resolveGoogleChatGroupRequireMention(params) {
54
93
  }
55
94
  //#endregion
56
95
  //#region extensions/googlechat/src/channel.adapters.ts
57
- const loadGoogleChatChannelRuntime$2 = createLazyRuntimeNamedExport(() => import("./channel.runtime-DMNTL2NR.js"), "googleChatChannelRuntime");
96
+ const loadGoogleChatChannelRuntime$2 = createLazyRuntimeNamedExport(() => import("./channel.runtime-DFdC1y3n.js"), "googleChatChannelRuntime");
58
97
  function createGoogleChatSendReceipt(params) {
59
98
  const messageId = params.messageId?.trim();
60
99
  return createMessageReceiptFromOutboundResults({
@@ -204,7 +243,7 @@ const googlechatOutboundAdapter = {
204
243
  resolveChannelLimitMb: ({ cfg, accountId }) => (cfg.channels?.googlechat)?.accounts?.[accountId]?.mediaMaxMb ?? (cfg.channels?.googlechat)?.mediaMaxMb,
205
244
  accountId
206
245
  }) ?? (account.config.mediaMaxMb ?? 20) * 1024 * 1024;
207
- const loaded = /^https?:\/\//i.test(mediaUrl) ? await fetchRemoteMedia({
246
+ const loaded = /^https?:\/\//i.test(mediaUrl) ? await readRemoteMediaBuffer({
208
247
  url: mediaUrl,
209
248
  maxBytes: effectiveMaxBytes
210
249
  }) : await loadOutboundMediaFromUrl(mediaUrl, {
@@ -295,7 +334,7 @@ const collectGoogleChatMutableAllowlistWarnings = createDangerousNameMatchingMut
295
334
  });
296
335
  //#endregion
297
336
  //#region extensions/googlechat/src/gateway.ts
298
- const loadGoogleChatChannelRuntime$1 = createLazyRuntimeNamedExport(() => import("./channel.runtime-DMNTL2NR.js"), "googleChatChannelRuntime");
337
+ const loadGoogleChatChannelRuntime$1 = createLazyRuntimeNamedExport(() => import("./channel.runtime-DFdC1y3n.js"), "googleChatChannelRuntime");
299
338
  async function startGoogleChatGatewayAccount(ctx) {
300
339
  const account = ctx.account;
301
340
  const statusSink = createAccountStatusSink({
@@ -335,7 +374,7 @@ async function startGoogleChatGatewayAccount(ctx) {
335
374
  }
336
375
  //#endregion
337
376
  //#region extensions/googlechat/src/channel.ts
338
- const loadGoogleChatChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-DMNTL2NR.js"), "googleChatChannelRuntime");
377
+ const loadGoogleChatChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-DFdC1y3n.js"), "googleChatChannelRuntime");
339
378
  const meta = {
340
379
  id: "googlechat",
341
380
  label: "Google Chat",
@@ -391,7 +430,7 @@ const googlechatActions = {
391
430
  },
392
431
  extractToolSend: ({ args }) => extractToolSend(args, "sendMessage"),
393
432
  handleAction: async (ctx) => {
394
- const { googlechatMessageActions } = await import("./actions-nBGKFgiH.js");
433
+ const { googlechatMessageActions } = await import("./actions-Cp9JuEzB.js");
395
434
  if (!googlechatMessageActions.handleAction) throw new Error("Google Chat actions are not available.");
396
435
  return await googlechatMessageActions.handleAction(ctx);
397
436
  }
@@ -542,4 +581,4 @@ const googlechatPlugin = createChatChannelPlugin({
542
581
  outbound: googlechatOutboundAdapter
543
582
  });
544
583
  //#endregion
545
- export { googlechatPlugin as t };
584
+ export { resolveGoogleChatOutboundSpace as n, googlechatPlugin as t };
@@ -1,2 +1,2 @@
1
- import { t as googlechatPlugin } from "./channel-DoEjIr9p.js";
1
+ import { t as googlechatPlugin } from "./channel-DzmqN0DY.js";
2
2
  export { googlechatPlugin };
@@ -1,8 +1,11 @@
1
- import { E as resolveDefaultGroupPolicy, M as warnMissingProviderGroupPolicyFallbackOnce, O as resolveInboundRouteEnvelopeBuilderWithRuntime, P as getGoogleChatRuntime, h as isDangerousNameMatchingEnabled, k as resolveWebhookPath, n as GROUP_POLICY_BLOCKED_LABEL, u as createChannelPairingController, w as resolveAllowlistProviderRuntimeGroupPolicy } from "./runtime-api-CAQFEJsQ.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-DubPSfme.js";
1
+ 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-DuEfWHnr.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-BJ9DOsBR.js";
3
3
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
4
+ import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
5
+ import { WEBHOOK_RATE_LIMIT_DEFAULTS, createFixedWindowRateLimiter, normalizeWebhookPath, resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
4
6
  import { registerWebhookTargetWithPluginRoute, resolveWebhookTargetWithAuthOrReject, withResolvedWebhookRequestPipeline } from "openclaw/plugin-sdk/webhook-targets";
5
7
  import { createWebhookInFlightLimiter, readJsonWebhookBodyOrReject } from "openclaw/plugin-sdk/webhook-request-guards";
8
+ import { recordChannelBotPairLoopAndCheckSuppression } from "openclaw/plugin-sdk/inbound-reply-dispatch";
6
9
  import { channelIngressRoutes, createChannelIngressResolver, defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
7
10
  import { deliverTextOrMediaReply, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
8
11
  //#region extensions/googlechat/src/monitor-access.ts
@@ -277,6 +280,7 @@ async function applyGoogleChatInboundAccessPolicy(params) {
277
280
  ok: true,
278
281
  commandAuthorized,
279
282
  effectiveWasMentioned,
283
+ groupBotLoopProtection: groupEntry?.botLoopProtection,
280
284
  groupSystemPrompt: normalizeOptionalString(groupEntry?.systemPrompt)
281
285
  };
282
286
  }
@@ -368,7 +372,7 @@ async function deliverGoogleChatReply(params) {
368
372
  },
369
373
  sendMedia: async ({ mediaUrl, caption }) => {
370
374
  try {
371
- const loaded = await core.channel.media.fetchRemoteMedia({
375
+ const loaded = await core.channel.media.readRemoteMediaBuffer({
372
376
  url: mediaUrl,
373
377
  maxBytes: (account.config.mediaMaxMb ?? 20) * 1024 * 1024
374
378
  });
@@ -512,12 +516,17 @@ function warnAppPrincipalMisconfiguration(params) {
512
516
  }
513
517
  function createGoogleChatWebhookRequestHandler(params) {
514
518
  return async (req, res) => {
519
+ const path = normalizeWebhookPath(new URL(req.url ?? "/", "http://localhost").pathname);
520
+ const config = params.webhookTargets.get(path)?.[0]?.config;
521
+ const clientIp = resolveRequestClientIp(req, config?.gateway?.trustedProxies, config?.gateway?.allowRealIpFallback === true) ?? "unknown";
515
522
  return await withResolvedWebhookRequestPipeline({
516
523
  req,
517
524
  res,
518
525
  targetsByPath: params.webhookTargets,
519
526
  allowMethods: ["POST"],
520
527
  requireJsonContentType: true,
528
+ rateLimiter: params.webhookRateLimiter,
529
+ rateLimitKey: `${path}:${clientIp}`,
521
530
  inFlightLimiter: params.webhookInFlightLimiter,
522
531
  handle: async ({ targets }) => {
523
532
  const headerBearer = extractBearerToken(req.headers.authorization);
@@ -587,6 +596,11 @@ function createGoogleChatWebhookRequestHandler(params) {
587
596
  //#endregion
588
597
  //#region extensions/googlechat/src/monitor-routing.ts
589
598
  const webhookTargets = /* @__PURE__ */ new Map();
599
+ const webhookRateLimiter = createFixedWindowRateLimiter({
600
+ windowMs: WEBHOOK_RATE_LIMIT_DEFAULTS.windowMs,
601
+ maxRequests: WEBHOOK_RATE_LIMIT_DEFAULTS.maxRequests,
602
+ maxTrackedKeys: WEBHOOK_RATE_LIMIT_DEFAULTS.maxTrackedKeys
603
+ });
590
604
  const webhookInFlightLimiter = createWebhookInFlightLimiter();
591
605
  let processGoogleChatEvent$1 = async () => {};
592
606
  function setGoogleChatWebhookEventProcessor(processEvent) {
@@ -594,6 +608,7 @@ function setGoogleChatWebhookEventProcessor(processEvent) {
594
608
  }
595
609
  const googleChatWebhookRequestHandler = createGoogleChatWebhookRequestHandler({
596
610
  webhookTargets,
611
+ webhookRateLimiter,
597
612
  webhookInFlightLimiter,
598
613
  processEvent: async (event, target) => {
599
614
  await processGoogleChatEvent$1(event, target);
@@ -634,6 +649,33 @@ function normalizeAudienceType(value) {
634
649
  if (normalized === "app-url" || normalized === "app_url" || normalized === "app") return "app-url";
635
650
  if (normalized === "project-number" || normalized === "project_number" || normalized === "project") return "project-number";
636
651
  }
652
+ function resolveGoogleChatTimestampMs(eventTime) {
653
+ if (!eventTime) return;
654
+ const parsed = Date.parse(eventTime);
655
+ return Number.isFinite(parsed) ? parsed : void 0;
656
+ }
657
+ function resolveGoogleChatBotLoopProtection(params) {
658
+ if (!params.allowBots || !params.isBotSender || !params.senderId || params.senderId === params.appUserId) return;
659
+ return {
660
+ scopeId: params.accountId,
661
+ conversationId: params.conversationId,
662
+ senderId: params.senderId,
663
+ receiverId: params.appUserId,
664
+ config: params.config,
665
+ defaultsConfig: params.defaultsConfig,
666
+ defaultEnabled: true,
667
+ nowMs: resolveGoogleChatTimestampMs(params.eventTime)
668
+ };
669
+ }
670
+ function resolveGoogleChatBotLoopProtectionConfig(params) {
671
+ return mergePairLoopGuardConfig(params.accountConfig, params.groupConfig);
672
+ }
673
+ function shouldSuppressGoogleChatBotLoop(params) {
674
+ if (!params.botLoopProtection) return false;
675
+ if (!recordChannelBotPairLoopAndCheckSuppression(params.botLoopProtection).suppressed) return false;
676
+ logVerbose(params.core, params.runtime, `skip bot-to-bot loop in ${params.botLoopProtection.conversationId}`);
677
+ return true;
678
+ }
637
679
  async function processGoogleChatEvent(event, target) {
638
680
  if ((event.type ?? event.eventType) !== "MESSAGE") return;
639
681
  if (!event.message || !event.space) return;
@@ -672,8 +714,11 @@ async function processMessageWithPipeline(params) {
672
714
  const senderId = sender?.name ?? "";
673
715
  const senderName = sender?.displayName ?? "";
674
716
  const senderEmail = sender?.email ?? void 0;
675
- if (!(account.config.allowBots === true)) {
676
- if (sender?.type?.toUpperCase() === "BOT") {
717
+ const isBotSender = sender?.type?.toUpperCase() === "BOT";
718
+ const appUserId = account.config.botUser?.trim() || "users/app";
719
+ const allowBots = account.config.allowBots === true;
720
+ if (!allowBots) {
721
+ if (isBotSender) {
677
722
  logVerbose(core, runtime, `skip bot-authored message (${senderId || "unknown"})`);
678
723
  return;
679
724
  }
@@ -702,7 +747,25 @@ async function processMessageWithPipeline(params) {
702
747
  logVerbose: (message) => logVerbose(core, runtime, message)
703
748
  });
704
749
  if (!access.ok) return;
705
- const { commandAuthorized, effectiveWasMentioned, groupSystemPrompt } = access;
750
+ const { commandAuthorized, effectiveWasMentioned, groupBotLoopProtection, groupSystemPrompt } = access;
751
+ if (shouldSuppressGoogleChatBotLoop({
752
+ botLoopProtection: resolveGoogleChatBotLoopProtection({
753
+ allowBots,
754
+ isBotSender,
755
+ senderId,
756
+ appUserId,
757
+ accountId: account.accountId,
758
+ conversationId: spaceId,
759
+ config: resolveGoogleChatBotLoopProtectionConfig({
760
+ accountConfig: account.config.botLoopProtection,
761
+ groupConfig: groupBotLoopProtection
762
+ }),
763
+ defaultsConfig: config.channels?.defaults?.botLoopProtection,
764
+ eventTime: event.eventTime
765
+ }),
766
+ core,
767
+ runtime
768
+ })) return;
706
769
  const { route, buildEnvelope } = resolveInboundRouteEnvelopeBuilderWithRuntime({
707
770
  cfg: config,
708
771
  channel: "googlechat",
@@ -1,3 +1,3 @@
1
- import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-BsERSjSe.js";
2
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-DLAG2_NI.js";
1
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-CG1sLToP.js";
2
+ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-DtQ_IO7J.js";
3
3
  export { collectRuntimeConfigAssignments, legacyConfigRules, normalizeCompatibilityConfig, secretTargetRegistryEntries };
@@ -1,2 +1,2 @@
1
- import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-BsERSjSe.js";
1
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-CG1sLToP.js";
2
2
  export { legacyConfigRules, normalizeCompatibilityConfig };
@@ -12,7 +12,7 @@ import { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
12
12
  import { GoogleChatConfigSchema } from "openclaw/plugin-sdk/bundled-channel-config-schema";
13
13
  import { GROUP_POLICY_BLOCKED_LABEL, resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
14
14
  import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
15
- import { fetchRemoteMedia, resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/media-runtime";
15
+ import { readRemoteMediaBuffer, resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/media-runtime";
16
16
  import { loadOutboundMediaFromUrl as loadOutboundMediaFromUrl$1 } from "openclaw/plugin-sdk/outbound-media";
17
17
  import { resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-inbound";
18
18
  import { resolveInboundRouteEnvelopeBuilderWithRuntime } from "openclaw/plugin-sdk/inbound-envelope";
@@ -26,4 +26,4 @@ const { setRuntime: setGoogleChatRuntime, getRuntime: getGoogleChatRuntime } = c
26
26
  errorMessage: "Google Chat runtime not initialized"
27
27
  });
28
28
  //#endregion
29
- export { resolveWebhookTargetWithAuthOrReject$1 as A, registerWebhookTargetWithPluginRoute$1 as C, resolveInboundMentionDecision as D, resolveDefaultGroupPolicy as E, setGoogleChatRuntime as F, warnMissingProviderGroupPolicyFallbackOnce as M, withResolvedWebhookRequestPipeline$1 as N, resolveInboundRouteEnvelopeBuilderWithRuntime as O, getGoogleChatRuntime as P, readStringParam$1 as S, resolveChannelMediaMaxBytes as T, loadOutboundMediaFromUrl$1 as _, buildChannelConfigSchema as a, readNumberParam$1 as b, createActionGate$1 as c, createWebhookInFlightLimiter$1 as d, extractToolSend$1 as f, jsonResult$1 as g, isDangerousNameMatchingEnabled as h, PAIRING_APPROVED_MESSAGE as i, runPassiveAccountLifecycle$1 as j, resolveWebhookPath as k, createChannelMessageReplyPipeline as l, fetchWithSsrFGuard$1 as m, GROUP_POLICY_BLOCKED_LABEL as n, chunkTextForOutbound as o, fetchRemoteMedia as p, GoogleChatConfigSchema as r, createAccountStatusSink$1 as s, DEFAULT_ACCOUNT_ID as t, createChannelPairingController as u, missingTargetError as v, resolveAllowlistProviderRuntimeGroupPolicy as w, readReactionParams$1 as x, readJsonWebhookBodyOrReject$1 as y };
29
+ export { resolveWebhookTargetWithAuthOrReject$1 as A, registerWebhookTargetWithPluginRoute$1 as C, resolveInboundMentionDecision as D, resolveDefaultGroupPolicy as E, setGoogleChatRuntime as F, warnMissingProviderGroupPolicyFallbackOnce as M, withResolvedWebhookRequestPipeline$1 as N, resolveInboundRouteEnvelopeBuilderWithRuntime as O, getGoogleChatRuntime as P, readStringParam$1 as S, resolveChannelMediaMaxBytes as T, missingTargetError as _, buildChannelConfigSchema as a, readReactionParams$1 as b, createActionGate$1 as c, createWebhookInFlightLimiter$1 as d, extractToolSend$1 as f, loadOutboundMediaFromUrl$1 as g, jsonResult$1 as h, PAIRING_APPROVED_MESSAGE as i, runPassiveAccountLifecycle$1 as j, resolveWebhookPath as k, createChannelMessageReplyPipeline as l, isDangerousNameMatchingEnabled as m, GROUP_POLICY_BLOCKED_LABEL as n, chunkTextForOutbound as o, fetchWithSsrFGuard$1 as p, GoogleChatConfigSchema as r, createAccountStatusSink$1 as s, DEFAULT_ACCOUNT_ID as t, createChannelPairingController as u, readJsonWebhookBodyOrReject$1 as v, resolveAllowlistProviderRuntimeGroupPolicy as w, readRemoteMediaBuffer as x, readNumberParam$1 as y };
@@ -1,2 +1,2 @@
1
- import { A as resolveWebhookTargetWithAuthOrReject, C as registerWebhookTargetWithPluginRoute, D as resolveInboundMentionDecision, E as resolveDefaultGroupPolicy, F as setGoogleChatRuntime, M as warnMissingProviderGroupPolicyFallbackOnce, N as withResolvedWebhookRequestPipeline, O as resolveInboundRouteEnvelopeBuilderWithRuntime, S as readStringParam, T as resolveChannelMediaMaxBytes, _ as loadOutboundMediaFromUrl, a as buildChannelConfigSchema, b as readNumberParam, c as createActionGate, d as createWebhookInFlightLimiter, f as extractToolSend, g as jsonResult, h as isDangerousNameMatchingEnabled, i as PAIRING_APPROVED_MESSAGE, j as runPassiveAccountLifecycle, k as resolveWebhookPath, l as createChannelMessageReplyPipeline, m as fetchWithSsrFGuard, n as GROUP_POLICY_BLOCKED_LABEL, o as chunkTextForOutbound, p as fetchRemoteMedia, r as GoogleChatConfigSchema, s as createAccountStatusSink, t as DEFAULT_ACCOUNT_ID, u as createChannelPairingController, v as missingTargetError, w as resolveAllowlistProviderRuntimeGroupPolicy, x as readReactionParams, y as readJsonWebhookBodyOrReject } from "./runtime-api-CAQFEJsQ.js";
2
- export { DEFAULT_ACCOUNT_ID, GROUP_POLICY_BLOCKED_LABEL, GoogleChatConfigSchema, PAIRING_APPROVED_MESSAGE, buildChannelConfigSchema, chunkTextForOutbound, createAccountStatusSink, createActionGate, createChannelMessageReplyPipeline, createChannelPairingController, createWebhookInFlightLimiter, extractToolSend, fetchRemoteMedia, fetchWithSsrFGuard, isDangerousNameMatchingEnabled, jsonResult, loadOutboundMediaFromUrl, missingTargetError, readJsonWebhookBodyOrReject, readNumberParam, readReactionParams, readStringParam, registerWebhookTargetWithPluginRoute, resolveAllowlistProviderRuntimeGroupPolicy, resolveChannelMediaMaxBytes, resolveDefaultGroupPolicy, resolveInboundMentionDecision, resolveInboundRouteEnvelopeBuilderWithRuntime, resolveWebhookPath, resolveWebhookTargetWithAuthOrReject, runPassiveAccountLifecycle, setGoogleChatRuntime, warnMissingProviderGroupPolicyFallbackOnce, withResolvedWebhookRequestPipeline };
1
+ import { A as resolveWebhookTargetWithAuthOrReject, C as registerWebhookTargetWithPluginRoute, D as resolveInboundMentionDecision, E as resolveDefaultGroupPolicy, F as setGoogleChatRuntime, M as warnMissingProviderGroupPolicyFallbackOnce, N as withResolvedWebhookRequestPipeline, O as resolveInboundRouteEnvelopeBuilderWithRuntime, S as readStringParam, T as resolveChannelMediaMaxBytes, _ as missingTargetError, a as buildChannelConfigSchema, b as readReactionParams, c as createActionGate, d as createWebhookInFlightLimiter, f as extractToolSend, g as loadOutboundMediaFromUrl, h as jsonResult, i as PAIRING_APPROVED_MESSAGE, j as runPassiveAccountLifecycle, k as resolveWebhookPath, l as createChannelMessageReplyPipeline, m as isDangerousNameMatchingEnabled, n as GROUP_POLICY_BLOCKED_LABEL, o as chunkTextForOutbound, p as fetchWithSsrFGuard, r as GoogleChatConfigSchema, s as createAccountStatusSink, t as DEFAULT_ACCOUNT_ID, u as createChannelPairingController, v as readJsonWebhookBodyOrReject, w as resolveAllowlistProviderRuntimeGroupPolicy, x as readRemoteMediaBuffer, y as readNumberParam } from "./runtime-api-DuEfWHnr.js";
2
+ export { DEFAULT_ACCOUNT_ID, GROUP_POLICY_BLOCKED_LABEL, GoogleChatConfigSchema, PAIRING_APPROVED_MESSAGE, buildChannelConfigSchema, chunkTextForOutbound, createAccountStatusSink, createActionGate, createChannelMessageReplyPipeline, createChannelPairingController, createWebhookInFlightLimiter, extractToolSend, fetchWithSsrFGuard, isDangerousNameMatchingEnabled, jsonResult, loadOutboundMediaFromUrl, missingTargetError, readJsonWebhookBodyOrReject, readNumberParam, readReactionParams, readRemoteMediaBuffer, readStringParam, registerWebhookTargetWithPluginRoute, resolveAllowlistProviderRuntimeGroupPolicy, resolveChannelMediaMaxBytes, resolveDefaultGroupPolicy, resolveInboundMentionDecision, resolveInboundRouteEnvelopeBuilderWithRuntime, resolveWebhookPath, resolveWebhookTargetWithAuthOrReject, runPassiveAccountLifecycle, setGoogleChatRuntime, warnMissingProviderGroupPolicyFallbackOnce, withResolvedWebhookRequestPipeline };
@@ -1,2 +1,2 @@
1
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries, t as channelSecrets } from "./secret-contract-DLAG2_NI.js";
1
+ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries, t as channelSecrets } from "./secret-contract-DtQ_IO7J.js";
2
2
  export { channelSecrets, collectRuntimeConfigAssignments, secretTargetRegistryEntries };
@@ -1,5 +1,4 @@
1
- import { a as resolveGoogleChatConfigAccessorAccount, i as resolveGoogleChatAccount, n as listGoogleChatAccountIds, r as resolveDefaultGoogleChatAccountId } from "./accounts-Dt3H6z0l.js";
2
- import { n as googlechatSetupAdapter, t as googlechatSetupWizard } from "./setup-surface-HU4P5FzD.js";
1
+ import { a as resolveDefaultGoogleChatAccountId, i as listGoogleChatAccountIds, n as googlechatSetupAdapter, o as resolveGoogleChatAccount, s as resolveGoogleChatConfigAccessorAccount, t as googlechatSetupWizard } from "./setup-surface-BlDqtKML.js";
3
2
  import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
4
3
  import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
5
4
  import { adaptScopedAccountAccessor, createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
@@ -1,7 +1,103 @@
1
- import { i as resolveGoogleChatAccount, r as resolveDefaultGoogleChatAccountId } from "./accounts-Dt3H6z0l.js";
1
+ import { safeParseJsonWithSchema, safeParseWithSchema } from "openclaw/plugin-sdk/extension-shared";
2
2
  import { normalizeOptionalString, normalizeStringifiedOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
3
+ import { DEFAULT_ACCOUNT_ID, createAccountListHelpers, normalizeAccountId, resolveAccountEntry, resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
4
+ import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
5
+ import { isSecretRef } from "openclaw/plugin-sdk/secret-input";
6
+ import { z } from "zod";
3
7
  import { createPatchedAccountSetupAdapter, createSetupInputPresenceValidator } from "openclaw/plugin-sdk/setup-runtime";
4
- import { DEFAULT_ACCOUNT_ID, addWildcardAllowFrom, applySetupAccountConfigPatch, createPromptParsedAllowFromForAccount, createStandardChannelSetupStatus, formatDocsLink, mergeAllowFromEntries, migrateBaseNameToDefaultAccount, splitSetupEntries } from "openclaw/plugin-sdk/setup";
8
+ import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, addWildcardAllowFrom, applySetupAccountConfigPatch, createPromptParsedAllowFromForAccount, createStandardChannelSetupStatus, formatDocsLink, mergeAllowFromEntries, migrateBaseNameToDefaultAccount, splitSetupEntries } from "openclaw/plugin-sdk/setup";
9
+ //#region extensions/googlechat/src/accounts.ts
10
+ const ENV_SERVICE_ACCOUNT$1 = "GOOGLE_CHAT_SERVICE_ACCOUNT";
11
+ const ENV_SERVICE_ACCOUNT_FILE$1 = "GOOGLE_CHAT_SERVICE_ACCOUNT_FILE";
12
+ const JsonRecordSchema = z.record(z.string(), z.unknown());
13
+ const { listAccountIds: listGoogleChatAccountIds, resolveDefaultAccountId: resolveDefaultGoogleChatAccountId } = createAccountListHelpers("googlechat");
14
+ function mergeGoogleChatAccountConfig(cfg, accountId) {
15
+ const raw = cfg.channels?.["googlechat"] ?? {};
16
+ const base = resolveMergedAccountConfig({
17
+ channelConfig: raw,
18
+ accounts: raw.accounts,
19
+ accountId,
20
+ omitKeys: ["defaultAccount"],
21
+ nestedObjectKeys: ["botLoopProtection"]
22
+ });
23
+ const defaultAccountConfig = resolveAccountEntry(raw.accounts, DEFAULT_ACCOUNT_ID) ?? {};
24
+ if (accountId === DEFAULT_ACCOUNT_ID) return base;
25
+ const { enabled: _ignoredEnabled, dangerouslyAllowNameMatching: _ignoredDangerouslyAllowNameMatching, serviceAccount: _ignoredServiceAccount, serviceAccountRef: _ignoredServiceAccountRef, serviceAccountFile: _ignoredServiceAccountFile, ...defaultAccountShared } = defaultAccountConfig;
26
+ const botLoopProtection = mergePairLoopGuardConfig(defaultAccountShared.botLoopProtection, base.botLoopProtection);
27
+ return {
28
+ ...defaultAccountShared,
29
+ ...base,
30
+ ...botLoopProtection ? { botLoopProtection } : {}
31
+ };
32
+ }
33
+ function resolveGoogleChatConfigAccessorAccount(params) {
34
+ const accountId = normalizeAccountId(params.accountId ?? params.cfg.channels?.googlechat?.defaultAccount);
35
+ return { config: mergeGoogleChatAccountConfig(params.cfg, accountId) };
36
+ }
37
+ function parseServiceAccount(value) {
38
+ if (isSecretRef(value)) return null;
39
+ if (typeof value === "string") {
40
+ const trimmed = value.trim();
41
+ if (!trimmed) return null;
42
+ return safeParseJsonWithSchema(JsonRecordSchema, trimmed);
43
+ }
44
+ return safeParseWithSchema(JsonRecordSchema, value);
45
+ }
46
+ function resolveCredentialsFromConfig(params) {
47
+ const { account, accountId } = params;
48
+ const inline = parseServiceAccount(account.serviceAccount);
49
+ if (inline) return {
50
+ credentials: inline,
51
+ source: "inline"
52
+ };
53
+ if (isSecretRef(account.serviceAccount)) throw new Error(`channels.googlechat.accounts.${accountId}.serviceAccount: unresolved SecretRef "${account.serviceAccount.source}:${account.serviceAccount.provider}:${account.serviceAccount.id}". Resolve this command against an active gateway runtime snapshot before reading it.`);
54
+ if (isSecretRef(account.serviceAccountRef)) throw new Error(`channels.googlechat.accounts.${accountId}.serviceAccount: unresolved SecretRef "${account.serviceAccountRef.source}:${account.serviceAccountRef.provider}:${account.serviceAccountRef.id}". Resolve this command against an active gateway runtime snapshot before reading it.`);
55
+ const file = normalizeOptionalString(account.serviceAccountFile);
56
+ if (file) return {
57
+ credentialsFile: file,
58
+ source: "file"
59
+ };
60
+ if (accountId === DEFAULT_ACCOUNT_ID) {
61
+ const envJson = process.env[ENV_SERVICE_ACCOUNT$1];
62
+ const envInline = parseServiceAccount(envJson);
63
+ if (envInline) return {
64
+ credentials: envInline,
65
+ source: "env"
66
+ };
67
+ const envFile = normalizeOptionalString(process.env[ENV_SERVICE_ACCOUNT_FILE$1]);
68
+ if (envFile) return {
69
+ credentialsFile: envFile,
70
+ source: "env"
71
+ };
72
+ }
73
+ return { source: "none" };
74
+ }
75
+ function resolveGoogleChatAccount(params) {
76
+ const accountId = normalizeAccountId(params.accountId ?? params.cfg.channels?.["googlechat"]?.defaultAccount);
77
+ const baseEnabled = params.cfg.channels?.["googlechat"]?.enabled !== false;
78
+ const merged = mergeGoogleChatAccountConfig(params.cfg, accountId);
79
+ const accountEnabled = merged.enabled !== false;
80
+ const enabled = baseEnabled && accountEnabled;
81
+ const credentials = resolveCredentialsFromConfig({
82
+ accountId,
83
+ account: merged
84
+ });
85
+ return {
86
+ accountId,
87
+ name: normalizeOptionalString(merged.name),
88
+ enabled,
89
+ config: merged,
90
+ credentialSource: credentials.source,
91
+ credentials: credentials.credentials,
92
+ credentialsFile: credentials.credentialsFile
93
+ };
94
+ }
95
+ function listEnabledGoogleChatAccounts(cfg) {
96
+ return listGoogleChatAccountIds(cfg).map((accountId) => resolveGoogleChatAccount({
97
+ cfg,
98
+ accountId
99
+ })).filter((account) => account.enabled);
100
+ }
5
101
  const googlechatSetupAdapter = createPatchedAccountSetupAdapter({
6
102
  channelKey: "googlechat",
7
103
  validateInput: createSetupInputPresenceValidator({
@@ -38,7 +134,7 @@ const googlechatDmPolicy = {
38
134
  channel,
39
135
  policyKey: "channels.googlechat.dm.policy",
40
136
  allowFromKey: "channels.googlechat.dm.allowFrom",
41
- resolveConfigKeys: (cfg, accountId) => (accountId ?? resolveDefaultGoogleChatAccountId(cfg)) !== DEFAULT_ACCOUNT_ID ? {
137
+ resolveConfigKeys: (cfg, accountId) => (accountId ?? resolveDefaultGoogleChatAccountId(cfg)) !== DEFAULT_ACCOUNT_ID$1 ? {
42
138
  policyKey: `channels.googlechat.accounts.${accountId ?? resolveDefaultGoogleChatAccountId(cfg)}.dm.policy`,
43
139
  allowFromKey: `channels.googlechat.accounts.${accountId ?? resolveDefaultGoogleChatAccountId(cfg)}.dm.allowFrom`
44
140
  } : {
@@ -129,7 +225,7 @@ const googlechatSetupWizard = {
129
225
  ]
130
226
  },
131
227
  prepare: async ({ cfg, accountId, credentialValues, prompter }) => {
132
- if (accountId === DEFAULT_ACCOUNT_ID && (Boolean(process.env[ENV_SERVICE_ACCOUNT]) || Boolean(process.env[ENV_SERVICE_ACCOUNT_FILE]))) {
228
+ if (accountId === DEFAULT_ACCOUNT_ID$1 && (Boolean(process.env[ENV_SERVICE_ACCOUNT]) || Boolean(process.env[ENV_SERVICE_ACCOUNT_FILE]))) {
133
229
  if (await prompter.confirm({
134
230
  message: "Use GOOGLE_CHAT_SERVICE_ACCOUNT env vars?",
135
231
  initialValue: true
@@ -214,4 +310,4 @@ const googlechatSetupWizard = {
214
310
  dmPolicy: googlechatDmPolicy
215
311
  };
216
312
  //#endregion
217
- export { googlechatSetupAdapter as n, googlechatSetupWizard as t };
313
+ export { resolveDefaultGoogleChatAccountId as a, listGoogleChatAccountIds as i, googlechatSetupAdapter as n, resolveGoogleChatAccount as o, listEnabledGoogleChatAccounts as r, resolveGoogleChatConfigAccessorAccount as s, googlechatSetupWizard as t };
package/dist/test-api.js CHANGED
@@ -1,3 +1,3 @@
1
- import { F as setGoogleChatRuntime } from "./runtime-api-CAQFEJsQ.js";
2
- import { t as googlechatPlugin } from "./channel-DoEjIr9p.js";
1
+ import { F as setGoogleChatRuntime } from "./runtime-api-DuEfWHnr.js";
2
+ import { t as googlechatPlugin } from "./channel-DzmqN0DY.js";
3
3
  export { googlechatPlugin, setGoogleChatRuntime };
@@ -41,6 +41,30 @@
41
41
  "allowBots": {
42
42
  "type": "boolean"
43
43
  },
44
+ "botLoopProtection": {
45
+ "type": "object",
46
+ "properties": {
47
+ "enabled": {
48
+ "type": "boolean"
49
+ },
50
+ "maxEventsPerWindow": {
51
+ "type": "integer",
52
+ "exclusiveMinimum": 0,
53
+ "maximum": 9007199254740991
54
+ },
55
+ "windowSeconds": {
56
+ "type": "integer",
57
+ "exclusiveMinimum": 0,
58
+ "maximum": 9007199254740991
59
+ },
60
+ "cooldownSeconds": {
61
+ "type": "integer",
62
+ "exclusiveMinimum": 0,
63
+ "maximum": 9007199254740991
64
+ }
65
+ },
66
+ "additionalProperties": false
67
+ },
44
68
  "dangerouslyAllowNameMatching": {
45
69
  "type": "boolean"
46
70
  },
@@ -83,6 +107,30 @@
83
107
  "requireMention": {
84
108
  "type": "boolean"
85
109
  },
110
+ "botLoopProtection": {
111
+ "type": "object",
112
+ "properties": {
113
+ "enabled": {
114
+ "type": "boolean"
115
+ },
116
+ "maxEventsPerWindow": {
117
+ "type": "integer",
118
+ "exclusiveMinimum": 0,
119
+ "maximum": 9007199254740991
120
+ },
121
+ "windowSeconds": {
122
+ "type": "integer",
123
+ "exclusiveMinimum": 0,
124
+ "maximum": 9007199254740991
125
+ },
126
+ "cooldownSeconds": {
127
+ "type": "integer",
128
+ "exclusiveMinimum": 0,
129
+ "maximum": 9007199254740991
130
+ }
131
+ },
132
+ "additionalProperties": false
133
+ },
86
134
  "users": {
87
135
  "type": "array",
88
136
  "items": {
@@ -464,6 +512,30 @@
464
512
  "allowBots": {
465
513
  "type": "boolean"
466
514
  },
515
+ "botLoopProtection": {
516
+ "type": "object",
517
+ "properties": {
518
+ "enabled": {
519
+ "type": "boolean"
520
+ },
521
+ "maxEventsPerWindow": {
522
+ "type": "integer",
523
+ "exclusiveMinimum": 0,
524
+ "maximum": 9007199254740991
525
+ },
526
+ "windowSeconds": {
527
+ "type": "integer",
528
+ "exclusiveMinimum": 0,
529
+ "maximum": 9007199254740991
530
+ },
531
+ "cooldownSeconds": {
532
+ "type": "integer",
533
+ "exclusiveMinimum": 0,
534
+ "maximum": 9007199254740991
535
+ }
536
+ },
537
+ "additionalProperties": false
538
+ },
467
539
  "dangerouslyAllowNameMatching": {
468
540
  "type": "boolean"
469
541
  },
@@ -506,6 +578,30 @@
506
578
  "requireMention": {
507
579
  "type": "boolean"
508
580
  },
581
+ "botLoopProtection": {
582
+ "type": "object",
583
+ "properties": {
584
+ "enabled": {
585
+ "type": "boolean"
586
+ },
587
+ "maxEventsPerWindow": {
588
+ "type": "integer",
589
+ "exclusiveMinimum": 0,
590
+ "maximum": 9007199254740991
591
+ },
592
+ "windowSeconds": {
593
+ "type": "integer",
594
+ "exclusiveMinimum": 0,
595
+ "maximum": 9007199254740991
596
+ },
597
+ "cooldownSeconds": {
598
+ "type": "integer",
599
+ "exclusiveMinimum": 0,
600
+ "maximum": 9007199254740991
601
+ }
602
+ },
603
+ "additionalProperties": false
604
+ },
509
605
  "users": {
510
606
  "type": "array",
511
607
  "items": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/googlechat",
3
- "version": "2026.5.12",
3
+ "version": "2026.5.14-beta.2",
4
4
  "description": "OpenClaw Google Chat channel plugin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,7 +17,7 @@
17
17
  "openclaw": "workspace:*"
18
18
  },
19
19
  "peerDependencies": {
20
- "openclaw": ">=2026.5.12"
20
+ "openclaw": ">=2026.5.14-beta.2"
21
21
  },
22
22
  "peerDependenciesMeta": {
23
23
  "openclaw": {
@@ -75,10 +75,10 @@
75
75
  "minHostVersion": ">=2026.4.10"
76
76
  },
77
77
  "compat": {
78
- "pluginApi": ">=2026.5.12"
78
+ "pluginApi": ">=2026.5.14-beta.2"
79
79
  },
80
80
  "build": {
81
- "openclawVersion": "2026.5.12"
81
+ "openclawVersion": "2026.5.14-beta.2"
82
82
  },
83
83
  "release": {
84
84
  "publishToClawHub": true,
@@ -1,96 +0,0 @@
1
- import { safeParseJsonWithSchema, safeParseWithSchema } from "openclaw/plugin-sdk/extension-shared";
2
- import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
3
- import { DEFAULT_ACCOUNT_ID, createAccountListHelpers, normalizeAccountId, resolveAccountEntry, resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
4
- import { isSecretRef } from "openclaw/plugin-sdk/secret-input";
5
- import { z } from "zod";
6
- //#region extensions/googlechat/src/accounts.ts
7
- const ENV_SERVICE_ACCOUNT = "GOOGLE_CHAT_SERVICE_ACCOUNT";
8
- const ENV_SERVICE_ACCOUNT_FILE = "GOOGLE_CHAT_SERVICE_ACCOUNT_FILE";
9
- const JsonRecordSchema = z.record(z.string(), z.unknown());
10
- const { listAccountIds: listGoogleChatAccountIds, resolveDefaultAccountId: resolveDefaultGoogleChatAccountId } = createAccountListHelpers("googlechat");
11
- function mergeGoogleChatAccountConfig(cfg, accountId) {
12
- const raw = cfg.channels?.["googlechat"] ?? {};
13
- const base = resolveMergedAccountConfig({
14
- channelConfig: raw,
15
- accounts: raw.accounts,
16
- accountId,
17
- omitKeys: ["defaultAccount"]
18
- });
19
- const defaultAccountConfig = resolveAccountEntry(raw.accounts, DEFAULT_ACCOUNT_ID) ?? {};
20
- if (accountId === DEFAULT_ACCOUNT_ID) return base;
21
- const { enabled: _ignoredEnabled, dangerouslyAllowNameMatching: _ignoredDangerouslyAllowNameMatching, serviceAccount: _ignoredServiceAccount, serviceAccountRef: _ignoredServiceAccountRef, serviceAccountFile: _ignoredServiceAccountFile, ...defaultAccountShared } = defaultAccountConfig;
22
- return {
23
- ...defaultAccountShared,
24
- ...base
25
- };
26
- }
27
- function resolveGoogleChatConfigAccessorAccount(params) {
28
- const accountId = normalizeAccountId(params.accountId ?? params.cfg.channels?.googlechat?.defaultAccount);
29
- return { config: mergeGoogleChatAccountConfig(params.cfg, accountId) };
30
- }
31
- function parseServiceAccount(value) {
32
- if (isSecretRef(value)) return null;
33
- if (typeof value === "string") {
34
- const trimmed = value.trim();
35
- if (!trimmed) return null;
36
- return safeParseJsonWithSchema(JsonRecordSchema, trimmed);
37
- }
38
- return safeParseWithSchema(JsonRecordSchema, value);
39
- }
40
- function resolveCredentialsFromConfig(params) {
41
- const { account, accountId } = params;
42
- const inline = parseServiceAccount(account.serviceAccount);
43
- if (inline) return {
44
- credentials: inline,
45
- source: "inline"
46
- };
47
- if (isSecretRef(account.serviceAccount)) throw new Error(`channels.googlechat.accounts.${accountId}.serviceAccount: unresolved SecretRef "${account.serviceAccount.source}:${account.serviceAccount.provider}:${account.serviceAccount.id}". Resolve this command against an active gateway runtime snapshot before reading it.`);
48
- if (isSecretRef(account.serviceAccountRef)) throw new Error(`channels.googlechat.accounts.${accountId}.serviceAccount: unresolved SecretRef "${account.serviceAccountRef.source}:${account.serviceAccountRef.provider}:${account.serviceAccountRef.id}". Resolve this command against an active gateway runtime snapshot before reading it.`);
49
- const file = normalizeOptionalString(account.serviceAccountFile);
50
- if (file) return {
51
- credentialsFile: file,
52
- source: "file"
53
- };
54
- if (accountId === DEFAULT_ACCOUNT_ID) {
55
- const envJson = process.env[ENV_SERVICE_ACCOUNT];
56
- const envInline = parseServiceAccount(envJson);
57
- if (envInline) return {
58
- credentials: envInline,
59
- source: "env"
60
- };
61
- const envFile = normalizeOptionalString(process.env[ENV_SERVICE_ACCOUNT_FILE]);
62
- if (envFile) return {
63
- credentialsFile: envFile,
64
- source: "env"
65
- };
66
- }
67
- return { source: "none" };
68
- }
69
- function resolveGoogleChatAccount(params) {
70
- const accountId = normalizeAccountId(params.accountId ?? params.cfg.channels?.["googlechat"]?.defaultAccount);
71
- const baseEnabled = params.cfg.channels?.["googlechat"]?.enabled !== false;
72
- const merged = mergeGoogleChatAccountConfig(params.cfg, accountId);
73
- const accountEnabled = merged.enabled !== false;
74
- const enabled = baseEnabled && accountEnabled;
75
- const credentials = resolveCredentialsFromConfig({
76
- accountId,
77
- account: merged
78
- });
79
- return {
80
- accountId,
81
- name: normalizeOptionalString(merged.name),
82
- enabled,
83
- config: merged,
84
- credentialSource: credentials.source,
85
- credentials: credentials.credentials,
86
- credentialsFile: credentials.credentialsFile
87
- };
88
- }
89
- function listEnabledGoogleChatAccounts(cfg) {
90
- return listGoogleChatAccountIds(cfg).map((accountId) => resolveGoogleChatAccount({
91
- cfg,
92
- accountId
93
- })).filter((account) => account.enabled);
94
- }
95
- //#endregion
96
- export { resolveGoogleChatConfigAccessorAccount as a, resolveGoogleChatAccount as i, listGoogleChatAccountIds as n, resolveDefaultGoogleChatAccountId as r, listEnabledGoogleChatAccounts as t };
@@ -1,43 +0,0 @@
1
- import { a as findGoogleChatDirectMessage } from "./api-DubPSfme.js";
2
- import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
3
- //#region extensions/googlechat/src/targets.ts
4
- function normalizeGoogleChatTarget(raw) {
5
- const trimmed = raw?.trim();
6
- if (!trimmed) return;
7
- const normalized = trimmed.replace(/^(googlechat|google-chat|gchat):/i, "").replace(/^user:(users\/)?/i, "users/").replace(/^space:(spaces\/)?/i, "spaces/");
8
- if (isGoogleChatUserTarget(normalized)) {
9
- const suffix = normalized.slice(6);
10
- return suffix.includes("@") ? `users/${normalizeLowercaseStringOrEmpty(suffix)}` : normalized;
11
- }
12
- if (isGoogleChatSpaceTarget(normalized)) return normalized;
13
- if (normalized.includes("@")) return `users/${normalizeLowercaseStringOrEmpty(normalized)}`;
14
- return normalized;
15
- }
16
- function isGoogleChatUserTarget(value) {
17
- return normalizeLowercaseStringOrEmpty(value).startsWith("users/");
18
- }
19
- function isGoogleChatSpaceTarget(value) {
20
- return normalizeLowercaseStringOrEmpty(value).startsWith("spaces/");
21
- }
22
- function stripMessageSuffix(target) {
23
- const index = target.indexOf("/messages/");
24
- if (index === -1) return target;
25
- return target.slice(0, index);
26
- }
27
- async function resolveGoogleChatOutboundSpace(params) {
28
- const normalized = normalizeGoogleChatTarget(params.target);
29
- if (!normalized) throw new Error("Missing Google Chat target.");
30
- const base = stripMessageSuffix(normalized);
31
- if (isGoogleChatSpaceTarget(base)) return base;
32
- if (isGoogleChatUserTarget(base)) {
33
- const dm = await findGoogleChatDirectMessage({
34
- account: params.account,
35
- userName: base
36
- });
37
- if (!dm?.name) throw new Error(`No Google Chat DM found for ${base}`);
38
- return dm.name;
39
- }
40
- return base;
41
- }
42
- //#endregion
43
- export { resolveGoogleChatOutboundSpace as i, isGoogleChatUserTarget as n, normalizeGoogleChatTarget as r, isGoogleChatSpaceTarget as t };