Files
openclaw/extensions/googlechat/src/channel-base.ts
Josh Avant fbd330b7aa fix(channels): honor configured read target policies (#99905)
* fix(channels): enforce configured read targets

* test(channels): align policy checks with boundaries

* fix: bind channel reads to trusted turn context

* test: satisfy gateway lint

* fix: narrow message action channel imports

* fix(feishu): authorize message reads before provider access

* fix(slack): await reaction clear authorization

* fix(channels): align provider action contracts

* fix(matrix): read direct-room account data before sync

* fix(channels): reject unsupported attachment actions early

* fix: restore trusted operator conversation reads

* fix(matrix): authorize pin actions before provider reads

* fix: preserve trusted channel read workflows

* fix(discord): resolve current channel ids consistently

* fix(agents): preserve message action turn capability

* fix(plugins): enforce host-owned read provenance

* fix(channels): harden Teams and Discord read policy

* fix(channels): preserve exact-current action compatibility

* fix(imessage): authorize trusted current chat aliases

* fix(channels): preserve normalized current aliases

* fix(channels): preserve external current target aliases

* fix: reconcile channel policy with current main

* fix(discord): isolate DM read policy

* fix(channels): enforce provider read gates

* fix(gateway): await serialized message action identity tokens

* fix(ci): refresh channel protocol contracts
2026-07-10 22:29:37 -05:00

125 lines
3.8 KiB
TypeScript

// Googlechat plugin module implements channel base behavior.
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
import {
adaptScopedAccountAccessor,
createScopedChannelConfigAdapter,
} from "openclaw/plugin-sdk/channel-config-helpers";
import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
import {
type GoogleChatConfigAccessorAccount,
listGoogleChatAccountIds,
resolveDefaultGoogleChatAccountId,
resolveGoogleChatConfigAccessorAccount,
resolveGoogleChatAccount,
type ResolvedGoogleChatAccount,
} from "./accounts.js";
import { googlechatSetupAdapter } from "./setup-core.js";
import { googlechatSetupWizard } from "./setup-surface.js";
export const GOOGLECHAT_CHANNEL_ID = "googlechat" as const;
const googlechatMeta = {
id: GOOGLECHAT_CHANNEL_ID,
label: "Google Chat",
selectionLabel: "Google Chat (Chat API)",
docsPath: "/channels/googlechat",
docsLabel: "googlechat",
blurb: "Google Workspace Chat app with HTTP webhook.",
aliases: ["gchat", "google-chat"],
order: 55,
detailLabel: "Google Chat",
systemImage: "message.badge",
markdownCapable: true,
};
export const formatGoogleChatAllowFromEntry = (entry: string) =>
normalizeLowercaseStringOrEmpty(
entry
.trim()
.replace(/^(googlechat|google-chat|gchat):/i, "")
.replace(/^user:/i, "")
.replace(/^users\//i, ""),
);
const googleChatConfigAdapter = createScopedChannelConfigAdapter<
ResolvedGoogleChatAccount,
GoogleChatConfigAccessorAccount
>({
sectionKey: GOOGLECHAT_CHANNEL_ID,
listAccountIds: listGoogleChatAccountIds,
resolveAccount: adaptScopedAccountAccessor(resolveGoogleChatAccount),
resolveAccessorAccount: resolveGoogleChatConfigAccessorAccount,
defaultAccountId: resolveDefaultGoogleChatAccountId,
clearBaseFields: [
"serviceAccount",
"serviceAccountFile",
"audienceType",
"audience",
"webhookPath",
"webhookUrl",
"botUser",
"name",
],
resolveAllowFrom: (account) => account.config.dm?.allowFrom,
formatAllowFrom: (allowFrom) =>
formatNormalizedAllowFromEntries({
allowFrom,
normalizeEntry: formatGoogleChatAllowFromEntry,
}),
resolveDefaultTo: (account) => account.config.defaultTo,
});
type GoogleChatPluginBase = Pick<
ChannelPlugin<ResolvedGoogleChatAccount>,
| "id"
| "meta"
| "setup"
| "setupWizard"
| "capabilities"
| "streaming"
| "reload"
| "configSchema"
| "config"
>;
export function createGoogleChatPluginBase(
params: {
configSchema?: ChannelPlugin<ResolvedGoogleChatAccount>["configSchema"];
} = {},
): GoogleChatPluginBase {
return {
id: GOOGLECHAT_CHANNEL_ID,
meta: { ...googlechatMeta },
setup: googlechatSetupAdapter,
setupWizard: googlechatSetupWizard,
capabilities: {
chatTypes: ["direct", "group", "thread"],
threads: true,
// Inbound attachment download remains supported even though service-account
// authentication cannot use Google Chat's user-auth-only upload endpoint.
media: true,
nativeCommands: false,
blockStreaming: true,
},
streaming: {
blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 },
},
reload: { configPrefixes: ["channels.googlechat"] },
...(params.configSchema ? { configSchema: params.configSchema } : {}),
config: {
...googleChatConfigAdapter,
isConfigured: (account) => account.credentialSource !== "none",
describeAccount: (account) =>
describeAccountSnapshot({
account,
configured: account.credentialSource !== "none",
extra: {
credentialSource: account.credentialSource,
},
}),
},
};
}