fix(googlechat): cap approval binding registries (#101744)

Co-authored-by: NIO <nocodet@mail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
NIO 2026-07-09 18:12:32 +08:00 committed by GitHub
parent 6ad8a77b51
commit 32dee52329
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 83 additions and 0 deletions

View file

@ -1,6 +1,7 @@
import { beforeEach, describe, expect, it } from "vitest";
import {
clearGoogleChatApprovalCardBindingsForTest,
getGoogleChatApprovalCardBinding,
registerGoogleChatManualApprovalFollowupSuppression,
registerGoogleChatApprovalCardBinding,
shouldSuppressGoogleChatManualExecApprovalFollowupPayload,
@ -110,4 +111,72 @@ describe("Google Chat approval card action registry", () => {
}),
).toBe(false);
});
it("evicts oldest approval card bindings once the cache exceeds its cap", () => {
const firstToken = "token-first";
registerGoogleChatApprovalCardBinding({
token: firstToken,
accountId: "default",
approvalId: "approval-first",
approvalKind: "exec",
decision: "allow-once",
allowedDecisions: ["allow-once", "deny"],
spaceName: "spaces/AAA",
messageName: "spaces/AAA/messages/msg-1",
expiresAtMs: Date.now() + 60_000,
});
expect(getGoogleChatApprovalCardBinding(firstToken)).not.toBeNull();
for (let i = 1; i <= 1024; i += 1) {
registerGoogleChatApprovalCardBinding({
token: `token-fill-${i}`,
accountId: "default",
approvalId: `approval-fill-${i}`,
approvalKind: "exec",
decision: "allow-once",
allowedDecisions: ["allow-once", "deny"],
spaceName: "spaces/AAA",
messageName: `spaces/AAA/messages/msg-${i}`,
expiresAtMs: Date.now() + 60_000,
});
}
expect(getGoogleChatApprovalCardBinding(firstToken)).toBeNull();
expect(getGoogleChatApprovalCardBinding("token-fill-1024")).not.toBeNull();
});
it("evicts oldest manual approval follow-up suppressions once the cache exceeds its cap", () => {
const firstApprovalId = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa";
registerGoogleChatManualApprovalFollowupSuppression({
approvalId: firstApprovalId,
approvalKind: "exec",
allowedDecisions: ["allow-once", "deny"],
expiresAtMs: Date.now() + 60_000,
});
expect(
shouldSuppressGoogleChatManualExecApprovalFollowupText(
`/approve ${firstApprovalId.slice(0, 8)} allow-once`,
),
).toBe(true);
for (let i = 1; i <= 1024; i += 1) {
registerGoogleChatManualApprovalFollowupSuppression({
approvalId: `${i.toString().padStart(8, "0")}-aaaa-aaaa-aaaa-aaaaaaaaaaaa`,
approvalKind: "exec",
allowedDecisions: ["allow-once", "deny"],
expiresAtMs: Date.now() + 60_000,
});
}
expect(
shouldSuppressGoogleChatManualExecApprovalFollowupText(
`/approve ${firstApprovalId.slice(0, 8)} allow-once`,
),
).toBe(false);
expect(
shouldSuppressGoogleChatManualExecApprovalFollowupText(
`/approve ${"1024".padStart(8, "0")} allow-once`,
),
).toBe(true);
});
});

View file

@ -1,5 +1,6 @@
import crypto from "node:crypto";
import type { ExecApprovalDecision } from "openclaw/plugin-sdk/approval-runtime";
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
import type { GoogleChatActionParameter, GoogleChatEvent } from "./types.js";
@ -25,6 +26,8 @@ type GoogleChatApprovalCardBinding = {
const approvalCardBindings = new Map<string, GoogleChatApprovalCardBinding>();
const approvalCardResolvingTokens = new Set<string>();
const GOOGLECHAT_APPROVAL_CARD_BINDING_MAX_ENTRIES = 1024;
const GOOGLECHAT_MANUAL_APPROVAL_SUPPRESSION_MAX_ENTRIES = 1024;
type GoogleChatManualApprovalSuppressionPayload = {
text?: string;
@ -113,7 +116,11 @@ export function registerGoogleChatApprovalCardBinding(
if (binding.expiresAtMs <= Date.now()) {
return false;
}
if (approvalCardBindings.has(binding.token)) {
approvalCardBindings.delete(binding.token);
}
approvalCardBindings.set(binding.token, binding);
pruneMapToMaxSize(approvalCardBindings, GOOGLECHAT_APPROVAL_CARD_BINDING_MAX_ENTRIES);
registerGoogleChatManualApprovalFollowupSuppression({
approvalId: binding.approvalId,
approvalKind: binding.approvalKind,
@ -156,7 +163,14 @@ export function registerGoogleChatManualApprovalFollowupSuppression(
if (!key) {
return false;
}
if (manualApprovalFollowupSuppressions.has(key)) {
manualApprovalFollowupSuppressions.delete(key);
}
manualApprovalFollowupSuppressions.set(key, suppression);
pruneMapToMaxSize(
manualApprovalFollowupSuppressions,
GOOGLECHAT_MANUAL_APPROVAL_SUPPRESSION_MAX_ENTRIES,
);
return true;
}