diff --git a/extensions/googlechat/src/channel.ts b/extensions/googlechat/src/channel.ts index 8dc3598a188..bbcbd8ce008 100644 --- a/extensions/googlechat/src/channel.ts +++ b/extensions/googlechat/src/channel.ts @@ -27,7 +27,6 @@ import { googleChatApprovalAuth } from "./approval-auth.js"; import { buildChannelConfigSchema, chunkTextForOutbound, - createAccountStatusSink, DEFAULT_ACCOUNT_ID, fetchRemoteMedia, GoogleChatConfigSchema, @@ -42,13 +41,13 @@ import { resolveDefaultGoogleChatAccountId, resolveGoogleChatAccount, resolveGoogleChatOutboundSpace, - runPassiveAccountLifecycle, type ChannelMessageActionAdapter, type ChannelStatusIssue, type OpenClawConfig, type ResolvedGoogleChatAccount, } from "./channel.deps.runtime.js"; import { collectGoogleChatMutableAllowlistWarnings } from "./doctor.js"; +import { startGoogleChatGatewayAccount } from "./gateway.js"; import { resolveGoogleChatGroupRequireMention } from "./group-policy.js"; import { collectRuntimeConfigAssignments, secretTargetRegistryEntries } from "./secret-contract.js"; import { googlechatSetupAdapter } from "./setup-core.js"; @@ -293,45 +292,7 @@ export const googlechatPlugin = createChatChannelPlugin({ }), }), gateway: { - startAccount: async (ctx) => { - const account = ctx.account; - const statusSink = createAccountStatusSink({ - accountId: account.accountId, - setStatus: ctx.setStatus, - }); - ctx.log?.info(`[${account.accountId}] starting Google Chat webhook`); - const { resolveGoogleChatWebhookPath, startGoogleChatMonitor } = - await loadGoogleChatChannelRuntime(); - statusSink({ - running: true, - lastStartAt: Date.now(), - webhookPath: resolveGoogleChatWebhookPath({ account }), - audienceType: account.config.audienceType, - audience: account.config.audience, - }); - await runPassiveAccountLifecycle({ - abortSignal: ctx.abortSignal, - start: async () => - await startGoogleChatMonitor({ - account, - config: ctx.cfg, - runtime: ctx.runtime, - abortSignal: ctx.abortSignal, - webhookPath: account.config.webhookPath, - webhookUrl: account.config.webhookUrl, - statusSink, - }), - stop: async (unregister) => { - unregister?.(); - }, - onStop: async () => { - statusSink({ - running: false, - lastStopAt: Date.now(), - }); - }, - }); - }, + startAccount: startGoogleChatGatewayAccount, }, }, pairing: { diff --git a/extensions/googlechat/src/gateway.ts b/extensions/googlechat/src/gateway.ts new file mode 100644 index 00000000000..776a4c9b885 --- /dev/null +++ b/extensions/googlechat/src/gateway.ts @@ -0,0 +1,61 @@ +import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime"; +import { + createAccountStatusSink, + runPassiveAccountLifecycle, + type OpenClawConfig, + type ResolvedGoogleChatAccount, +} from "./channel.deps.runtime.js"; + +const loadGoogleChatChannelRuntime = createLazyRuntimeNamedExport( + () => import("./channel.runtime.js"), + "googleChatChannelRuntime", +); + +export async function startGoogleChatGatewayAccount(ctx: { + account: ResolvedGoogleChatAccount; + cfg: OpenClawConfig; + runtime: unknown; + abortSignal: AbortSignal; + setStatus: (patch: Record) => void; + log?: { + info?: (message: string) => void; + }; +}): Promise { + const account = ctx.account; + const statusSink = createAccountStatusSink({ + accountId: account.accountId, + setStatus: ctx.setStatus, + }); + ctx.log?.info?.(`[${account.accountId}] starting Google Chat webhook`); + const { resolveGoogleChatWebhookPath, startGoogleChatMonitor } = + await loadGoogleChatChannelRuntime(); + statusSink({ + running: true, + lastStartAt: Date.now(), + webhookPath: resolveGoogleChatWebhookPath({ account }), + audienceType: account.config.audienceType, + audience: account.config.audience, + }); + await runPassiveAccountLifecycle({ + abortSignal: ctx.abortSignal, + start: async () => + await startGoogleChatMonitor({ + account, + config: ctx.cfg, + runtime: ctx.runtime, + abortSignal: ctx.abortSignal, + webhookPath: account.config.webhookPath, + webhookUrl: account.config.webhookUrl, + statusSink, + }), + stop: async (unregister) => { + unregister?.(); + }, + onStop: async () => { + statusSink({ + running: false, + lastStopAt: Date.now(), + }); + }, + }); +} diff --git a/extensions/googlechat/src/setup.test.ts b/extensions/googlechat/src/setup.test.ts index 427337295fd..2e4bc7d85dc 100644 --- a/extensions/googlechat/src/setup.test.ts +++ b/extensions/googlechat/src/setup.test.ts @@ -15,8 +15,13 @@ import { } from "../../../test/helpers/plugins/start-account-lifecycle.js"; import type { OpenClawConfig } from "../runtime-api.js"; import { resolveGoogleChatAccount, type ResolvedGoogleChatAccount } from "./accounts.js"; -import { googlechatPlugin } from "./channel.js"; +import { + listGoogleChatAccountIds, + resolveDefaultGoogleChatAccountId, +} from "./channel.deps.runtime.js"; +import { startGoogleChatGatewayAccount } from "./gateway.js"; import { googlechatSetupAdapter } from "./setup-core.js"; +import { googlechatSetupWizard } from "./setup-surface.js"; const hoisted = vi.hoisted(() => ({ startGoogleChatMonitor: vi.fn(), @@ -30,8 +35,20 @@ vi.mock("./monitor.js", async () => { }; }); -const googlechatConfigure = createPluginSetupWizardConfigure(googlechatPlugin); -const googlechatStatus = createPluginSetupWizardStatus(googlechatPlugin); +const googlechatSetupPlugin = { + id: "googlechat", + meta: { + label: "Google Chat", + }, + config: { + defaultAccountId: resolveDefaultGoogleChatAccountId, + listAccountIds: listGoogleChatAccountIds, + }, + setupWizard: googlechatSetupWizard, +} as never; + +const googlechatConfigure = createPluginSetupWizardConfigure(googlechatSetupPlugin); +const googlechatStatus = createPluginSetupWizardStatus(googlechatSetupPlugin); function buildAccount(): ResolvedGoogleChatAccount { return { @@ -165,7 +182,7 @@ describe("googlechat setup", () => { it("reads the named-account DM policy instead of the channel root", () => { expect( - googlechatPlugin.setupWizard?.dmPolicy?.getCurrent( + googlechatSetupWizard.dmPolicy?.getCurrent( { channels: { googlechat: { @@ -234,7 +251,7 @@ describe("googlechat setup", () => { }); it("reports account-scoped config keys for named accounts", () => { - expect(googlechatPlugin.setupWizard?.dmPolicy?.resolveConfigKeys?.({}, "alerts")).toEqual({ + expect(googlechatSetupWizard.dmPolicy?.resolveConfigKeys?.({}, "alerts")).toEqual({ policyKey: "channels.googlechat.accounts.alerts.dm.policy", allowFromKey: "channels.googlechat.accounts.alerts.dm.allowFrom", }); @@ -260,13 +277,13 @@ describe("googlechat setup", () => { }, } as OpenClawConfig; - expect(googlechatPlugin.setupWizard?.dmPolicy?.getCurrent(cfg)).toBe("allowlist"); - expect(googlechatPlugin.setupWizard?.dmPolicy?.resolveConfigKeys?.(cfg)).toEqual({ + expect(googlechatSetupWizard.dmPolicy?.getCurrent(cfg)).toBe("allowlist"); + expect(googlechatSetupWizard.dmPolicy?.resolveConfigKeys?.(cfg)).toEqual({ policyKey: "channels.googlechat.accounts.alerts.dm.policy", allowFromKey: "channels.googlechat.accounts.alerts.dm.allowFrom", }); - const next = googlechatPlugin.setupWizard?.dmPolicy?.setPolicy(cfg, "open"); + const next = googlechatSetupWizard.dmPolicy?.setPolicy(cfg, "open"); expect(next?.channels?.googlechat?.dm?.policy).toBe("disabled"); expect(next?.channels?.googlechat?.accounts?.alerts?.dm?.policy).toBe("open"); }); @@ -277,7 +294,7 @@ describe("googlechat setup", () => { text: vi.fn(async () => "users/123456789"), }; - const next = await googlechatPlugin.setupWizard?.dmPolicy?.promptAllowFrom?.({ + const next = await googlechatSetupWizard.dmPolicy?.promptAllowFrom?.({ cfg: { channels: { googlechat: { @@ -306,7 +323,7 @@ describe("googlechat setup", () => { }); it('writes open DM policy to the named account and preserves inherited allowFrom with "*"', () => { - const next = googlechatPlugin.setupWizard?.dmPolicy?.setPolicy( + const next = googlechatSetupWizard.dmPolicy?.setPolicy( { channels: { googlechat: { @@ -335,7 +352,7 @@ describe("googlechat setup", () => { hoisted.startGoogleChatMonitor.mockResolvedValue(unregister); const { abort, patches, task, isSettled } = startAccountAndTrackLifecycle({ - startAccount: googlechatPlugin.gateway!.startAccount!, + startAccount: startGoogleChatGatewayAccount, account: buildAccount(), }); await expectPendingUntilAbort({