diff --git a/CHANGELOG.md b/CHANGELOG.md index 18477ac74..cd8b18851 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ - Group `/new` resets now work with @mentions so activation guidance appears on fresh sessions. - Group chat activation context is now injected into the system prompt at session start (and after activation changes), including /new greetings. - Typing indicators now start only once a reply payload is produced (no "thinking" typing for silent runs). +- WhatsApp group typing now starts immediately only when the bot is mentioned; otherwise it waits until real output exists. +- Streamed `` segments are stripped before partial replies are emitted. - Canvas defaults/A2UI auto-nav aligned; debug status overlay centered; redundant await removed in `CanvasManager`. - Gateway launchd loop fixed by removing redundant `kickstart -k`. - CLI now hints when Peekaboo is unauthorized. diff --git a/src/agents/pi-embedded-subscribe.ts b/src/agents/pi-embedded-subscribe.ts index 68b265259..6ab029295 100644 --- a/src/agents/pi-embedded-subscribe.ts +++ b/src/agents/pi-embedded-subscribe.ts @@ -13,6 +13,29 @@ import { inferToolMetaFromArgs, } from "./pi-embedded-utils.js"; +const THINKING_TAG_RE = /<\s*\/?\s*think(?:ing)?\s*>/gi; + +function stripThinkingSegments(text: string): string { + if (!text || !THINKING_TAG_RE.test(text)) return text; + THINKING_TAG_RE.lastIndex = 0; + let result = ""; + let lastIndex = 0; + let inThinking = false; + for (const match of text.matchAll(THINKING_TAG_RE)) { + const idx = match.index ?? 0; + if (!inThinking) { + result += text.slice(lastIndex, idx); + } + const tag = match[0].toLowerCase(); + inThinking = !tag.includes("/"); + lastIndex = idx + match[0].length; + } + if (!inThinking) { + result += text.slice(lastIndex); + } + return result; +} + export function subscribeEmbeddedPiSession(params: { session: AgentSession; runId: string; @@ -159,7 +182,7 @@ export function subscribeEmbeddedPiSession(params: { : ""; if (chunk) { deltaBuffer += chunk; - const next = deltaBuffer.trim(); + const next = stripThinkingSegments(deltaBuffer).trim(); if (next && next !== lastStreamedAssistant) { lastStreamedAssistant = next; const { text: cleanedText, mediaUrls } = @@ -194,7 +217,9 @@ export function subscribeEmbeddedPiSession(params: { if (evt.type === "message_end") { const msg = (evt as AgentEvent & { message: AppMessage }).message; if (msg?.role === "assistant") { - const text = extractAssistantText(msg as AssistantMessage); + const text = stripThinkingSegments( + extractAssistantText(msg as AssistantMessage), + ); if (text) assistantTexts.push(text); deltaBuffer = ""; } diff --git a/src/auto-reply/reply.ts b/src/auto-reply/reply.ts index 66239f3a7..98e5754be 100644 --- a/src/auto-reply/reply.ts +++ b/src/auto-reply/reply.ts @@ -653,8 +653,14 @@ export async function getReplyFromConfig( } const isFirstTurnInSession = isNewSession || !systemSent; + const isGroupChat = sessionCtx.ChatType === "group"; + const wasMentioned = ctx.WasMentioned === true; + const shouldEagerType = !isGroupChat || wasMentioned; + if (shouldEagerType) { + await startTypingLoop(); + } const shouldInjectGroupIntro = - sessionCtx.ChatType === "group" && + isGroupChat && (isFirstTurnInSession || sessionEntry?.groupActivationNeedsSystemIntro); const groupIntro = shouldInjectGroupIntro diff --git a/src/auto-reply/templating.ts b/src/auto-reply/templating.ts index 964c0aa47..a4519a94b 100644 --- a/src/auto-reply/templating.ts +++ b/src/auto-reply/templating.ts @@ -16,6 +16,7 @@ export type MsgContext = { SenderName?: string; SenderE164?: string; Surface?: string; + WasMentioned?: boolean; }; export type TemplateContext = MsgContext & { diff --git a/src/web/auto-reply.ts b/src/web/auto-reply.ts index 517d6b399..824ef5934 100644 --- a/src/web/auto-reply.ts +++ b/src/web/auto-reply.ts @@ -1143,6 +1143,7 @@ export async function monitorWebProvider( ), SenderName: msg.senderName, SenderE164: msg.senderE164, + WasMentioned: msg.wasMentioned, Surface: "whatsapp", }, { @@ -1306,6 +1307,7 @@ export async function monitorWebProvider( "group mention debug", ); const wasMentioned = mentionDebug.wasMentioned; + msg.wasMentioned = wasMentioned; const activation = resolveGroupActivationFor(conversationId); const requireMention = activation !== "always"; if (!shouldBypassMention && requireMention && !wasMentioned) { diff --git a/src/web/inbound.ts b/src/web/inbound.ts index 71010684c..a847fbcba 100644 --- a/src/web/inbound.ts +++ b/src/web/inbound.ts @@ -58,6 +58,7 @@ export type WebInboundMessage = { mediaPath?: string; mediaType?: string; mediaUrl?: string; + wasMentioned?: boolean; }; export async function monitorWebInbox(options: {