diff --git a/extensions/googlechat/src/api.ts b/extensions/googlechat/src/api.ts index e41f4de63..7757ad0fb 100644 --- a/extensions/googlechat/src/api.ts +++ b/extensions/googlechat/src/api.ts @@ -153,7 +153,7 @@ export async function uploadGoogleChatAttachment(params: { contentType?: string; }): Promise<{ attachmentUploadToken?: string }> { const { account, space, filename, buffer, contentType } = params; - const boundary = `moltbot-${crypto.randomUUID()}`; + const boundary = `clawdbot-${crypto.randomUUID()}`; const metadata = JSON.stringify({ filename }); const header = `--${boundary}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n${metadata}\r\n`; const mediaHeader = `--${boundary}\r\nContent-Type: ${contentType ?? "application/octet-stream"}\r\n\r\n`; @@ -257,3 +257,20 @@ export async function probeGoogleChat(account: ResolvedGoogleChatAccount): Promi return { ok: false, error: err instanceof Error ? err.message : String(err) }; } } + +export async function getGoogleChatMessage(params: { + account: ResolvedGoogleChatAccount; + messageName: string; +}): Promise<{ text?: string; name?: string; thread?: { name?: string } } | null> { + const { account, messageName } = params; + const url = `${CHAT_API_BASE}/${messageName}`; + try { + return await fetchJson<{ text?: string; name?: string; thread?: { name?: string } }>( + account, + url, + { method: "GET" } + ); + } catch (err) { + return null; + } +} diff --git a/extensions/googlechat/src/monitor.ts b/extensions/googlechat/src/monitor.ts index 459902795..836012106 100644 --- a/extensions/googlechat/src/monitor.ts +++ b/extensions/googlechat/src/monitor.ts @@ -1,6 +1,6 @@ import type { IncomingMessage, ServerResponse } from "node:http"; -import type { MoltbotConfig } from "clawdbot/plugin-sdk"; +import type { ClawdbotConfig } from "clawdbot/plugin-sdk"; import { resolveMentionGatingWithBypass } from "clawdbot/plugin-sdk"; import { @@ -11,10 +11,10 @@ import { deleteGoogleChatMessage, sendGoogleChatMessage, updateGoogleChatMessage, + getGoogleChatMessage, } from "./api.js"; import { verifyGoogleChatRequest, type GoogleChatAudienceType } from "./auth.js"; import { getGoogleChatRuntime } from "./runtime.js"; -import { extractSpaceInfoFromEvent, buildSpaceCachePatch } from "./space-cache.js"; import type { GoogleChatAnnotation, GoogleChatAttachment, @@ -31,7 +31,7 @@ export type GoogleChatRuntimeEnv = { export type GoogleChatMonitorOptions = { account: ResolvedGoogleChatAccount; - config: MoltbotConfig; + config: ClawdbotConfig; runtime: GoogleChatRuntimeEnv; abortSignal: AbortSignal; webhookPath?: string; @@ -43,7 +43,7 @@ type GoogleChatCoreRuntime = ReturnType; type WebhookTarget = { account: ResolvedGoogleChatAccount; - config: MoltbotConfig; + config: ClawdbotConfig; runtime: GoogleChatRuntimeEnv; core: GoogleChatCoreRuntime; path: string; @@ -264,19 +264,19 @@ export async function handleGoogleChatWebhookRequest( } selected.statusSink?.({ lastInboundAt: Date.now() }); - - // For synchronous responses in spaces, handle non-MESSAGE events immediately + + // For synchronous responses in spaces, we need to return a proper message const evtType = (event.type ?? (event as { eventType?: string }).eventType)?.toUpperCase(); const isGroup = event.space?.type?.toUpperCase() !== "DM"; - + // For non-MESSAGE events in groups (like ADDED_TO_SPACE), return an acknowledgment if (isGroup && evtType !== "MESSAGE") { res.statusCode = 200; res.setHeader("Content-Type", "application/json"); - res.end(JSON.stringify({ text: "Hello! I'm ready to help. 🦞" })); + res.end(JSON.stringify({ text: "Hello! I'm Chopper! 🦌" })); return true; } - + processGoogleChatEvent(event, selected).catch((err) => { selected?.runtime.error?.( `[${selected.account.accountId}] Google Chat webhook failed: ${String(err)}`, @@ -371,24 +371,24 @@ function extractMentionInfo(annotations: GoogleChatAnnotation[], botUser?: strin * Resolve bot display name with fallback chain: * 1. Account config name * 2. Agent name from config - * 3. "Moltbot" as generic fallback + * 3. "Clawdbot" as generic fallback */ function resolveBotDisplayName(params: { accountName?: string; agentId: string; - config: MoltbotConfig; + config: ClawdbotConfig; }): string { const { accountName, agentId, config } = params; if (accountName?.trim()) return accountName.trim(); const agent = config.agents?.list?.find((a) => a.id === agentId); if (agent?.name?.trim()) return agent.name.trim(); - return "Moltbot"; + return "Clawdbot"; } async function processMessageWithPipeline(params: { event: GoogleChatEvent; account: ResolvedGoogleChatAccount; - config: MoltbotConfig; + config: ClawdbotConfig; runtime: GoogleChatRuntimeEnv; core: GoogleChatCoreRuntime; statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void; @@ -408,18 +408,6 @@ async function processMessageWithPipeline(params: { const senderName = sender?.displayName ?? ""; const senderEmail = sender?.email ?? undefined; - // Cache space mapping for proactive messaging - if (senderId && spaceId) { - const spaceInfo = extractSpaceInfoFromEvent(event); - if (spaceInfo) { - const cachePatch = buildSpaceCachePatch(spaceInfo, account.accountId); - core.config.patchConfig(cachePatch).catch((err: Error) => { - logVerbose(core, runtime, `failed to cache space: ${err.message}`); - }); - logVerbose(core, runtime, `cached space ${spaceId} for user ${senderId}`); - } - } - const allowBots = account.config.allowBots === true; if (!allowBots) { if (sender?.type?.toUpperCase() === "BOT") { @@ -435,7 +423,7 @@ async function processMessageWithPipeline(params: { const messageText = (message.argumentText ?? message.text ?? "").trim(); const attachments = message.attachment ?? []; const hasMedia = attachments.length > 0; - const rawBody = messageText || (hasMedia ? "" : ""); + let rawBody = messageText || (hasMedia ? "" : ""); if (!rawBody) return; const defaultGroupPolicy = config.channels?.defaults?.groupPolicy; @@ -612,6 +600,21 @@ async function processMessageWithPipeline(params: { agentId: route.agentId, }); const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(config); + // Fetch quoted message content BEFORE creating body + let quotedMessageText: string | undefined; + const quotedName = message.quotedMessageMetadata?.name; + if (quotedName) { + try { + const quotedMsg = await getGoogleChatMessage({ + account, + messageName: quotedName, + }); + quotedMessageText = quotedMsg?.text; + } catch { + // Ignore fetch errors + } + } + const previousTimestamp = core.channel.session.readSessionUpdatedAt({ storePath, sessionKey: route.sessionKey, @@ -658,6 +661,7 @@ async function processMessageWithPipeline(params: { // Thread reply context IsThreadReply: message.threadReply, QuotedMessageId: message.quotedMessageMetadata?.name, + QuotedMessageText: quotedMessageText, }); void core.channel.session @@ -755,7 +759,7 @@ async function deliverGoogleChatReply(params: { spaceId: string; runtime: GoogleChatRuntimeEnv; core: GoogleChatCoreRuntime; - config: MoltbotConfig; + config: ClawdbotConfig; statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void; typingMessageName?: string; }): Promise {