From effb4d0b7c6f33930be319f85b4d8bdf90a08c99 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 27 Nov 2025 14:07:21 +0100 Subject: [PATCH] feat(mom): reduce tool verbosity in main Slack messages - During execution: show tool labels, thinking, and text in main message - After completion: replace main message with only final assistant text - Post thinking (italic) and text to thread for full audit trail - Add promise queue to ensure ctx.respond calls execute sequentially - Add log.logThinking() and log.logResponse() for console output - Get final text from agent.state.messages instead of tracking Closes #65 --- packages/mom/src/agent.ts | 67 +++++++++++++++++++++++++++++++++------ packages/mom/src/log.ts | 22 ++++++++++--- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/packages/mom/src/agent.ts b/packages/mom/src/agent.ts index 213d77a2..d24d0e9a 100644 --- a/packages/mom/src/agent.ts +++ b/packages/mom/src/agent.ts @@ -377,6 +377,22 @@ export function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner { // Track stop reason let stopReason = "stop"; + // Promise queue to ensure ctx.respond/respondInThread calls execute in order + const queue = { + chain: Promise.resolve(), + enqueue(fn: () => Promise): Promise { + const result = this.chain.then(fn); + this.chain = result.then( + () => {}, + () => {}, + ); // swallow errors for chain + return result; + }, + flush(): Promise { + return this.chain; + }, + }; + // Subscribe to events agent.subscribe(async (event: AgentEvent) => { switch (event.type) { @@ -405,7 +421,7 @@ export function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner { }); // Show label in main message only - await ctx.respond(`_${label}_`); + queue.enqueue(() => ctx.respond(`_${label}_`)); break; } @@ -453,11 +469,11 @@ export function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner { threadMessage += "*Result:*\n```\n" + threadResult + "\n```"; - await ctx.respondInThread(threadMessage); + queue.enqueue(() => ctx.respondInThread(threadMessage)); // Show brief error in main message if failed if (event.isError) { - await ctx.respond(`_Error: ${truncate(resultStr, 200)}_`); + queue.enqueue(() => ctx.respond(`_Error: ${truncate(resultStr, 200)}_`)); } break; } @@ -495,17 +511,32 @@ export function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner { totalUsage.cost.total += assistantMsg.usage.cost.total; } - // Extract text from assistant message + // Extract thinking and text from assistant message const content = event.message.content; - let text = ""; + const thinkingParts: string[] = []; + const textParts: string[] = []; for (const part of content) { - if (part.type === "text") { - text += part.text; + if (part.type === "thinking") { + thinkingParts.push(part.thinking); + } else if (part.type === "text") { + textParts.push(part.text); } } + + const text = textParts.join("\n"); + + // Post thinking to main message and thread + for (const thinking of thinkingParts) { + log.logThinking(logCtx, thinking); + queue.enqueue(() => ctx.respond(`_${thinking}_`)); + queue.enqueue(() => ctx.respondInThread(`_${thinking}_`)); + } + + // Post text to main message and thread if (text.trim()) { - await ctx.respond(text); - log.logResponseComplete(logCtx, text.length); + log.logResponse(logCtx, text); + queue.enqueue(() => ctx.respond(text)); + queue.enqueue(() => ctx.respondInThread(text)); } } break; @@ -523,10 +554,26 @@ export function createAgentRunner(sandboxConfig: SandboxConfig): AgentRunner { await agent.prompt(userPrompt); + // Wait for all queued respond calls to complete + await queue.flush(); + + // Get final assistant message text from agent state and replace main message + const messages = agent.state.messages; + const lastAssistant = messages.filter((m) => m.role === "assistant").pop(); + const finalText = + lastAssistant?.content + .filter((c): c is { type: "text"; text: string } => c.type === "text") + .map((c) => c.text) + .join("\n") || ""; + if (finalText.trim()) { + await ctx.replaceMessage(finalText); + } + // Log usage summary if there was any usage if (totalUsage.cost.total > 0) { const summary = log.logUsageSummary(logCtx, totalUsage); - await ctx.respondInThread(summary); + queue.enqueue(() => ctx.respondInThread(summary)); + await queue.flush(); } return { stopReason }; diff --git a/packages/mom/src/log.ts b/packages/mom/src/log.ts index 8ac04f3f..5b84e7d5 100644 --- a/packages/mom/src/log.ts +++ b/packages/mom/src/log.ts @@ -118,10 +118,24 @@ export function logResponseStart(ctx: LogContext): void { console.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} → Streaming response...`)); } -export function logResponseComplete(ctx: LogContext, charCount: number): void { - console.log( - chalk.yellow(`${timestamp()} ${formatContext(ctx)} ✓ Response sent (${charCount.toLocaleString()} chars)`), - ); +export function logThinking(ctx: LogContext, thinking: string): void { + console.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💭 Thinking`)); + const truncated = truncate(thinking, 1000); + const indented = truncated + .split("\n") + .map((line) => ` ${line}`) + .join("\n"); + console.log(chalk.dim(indented)); +} + +export function logResponse(ctx: LogContext, text: string): void { + console.log(chalk.yellow(`${timestamp()} ${formatContext(ctx)} 💬 Response`)); + const truncated = truncate(text, 1000); + const indented = truncated + .split("\n") + .map((line) => ` ${line}`) + .join("\n"); + console.log(chalk.dim(indented)); } // Attachments