fix(googlechat): fetch and include quoted message content in thread replies
- Add getGoogleChatMessage() API function to fetch messages by name - Reorder message processing to fetch quoted content before body creation - Include QuotedMessageText in agent context for thread replies - Update documentation with implementation details Fixes thread reply context for Google Chat
This commit is contained in:
parent
d7ede3cd68
commit
149ad09832
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<typeof getGoogleChatRuntime>;
|
||||
|
||||
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 ? "<media:attachment>" : "");
|
||||
let rawBody = messageText || (hasMedia ? "<media:attachment>" : "");
|
||||
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<void> {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user