mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-20 02:03:16 +00:00
mom: add centralized logging, usage tracking, and improve prompt caching
Major improvements to mom's logging and cost reporting: Centralized Logging System: - Add src/log.ts with type-safe logging functions - Colored console output (green=user, yellow=mom, dim=details) - Consistent format: [HH:MM:SS] [context] message - Replace scattered console.log/error calls throughout codebase Usage Tracking & Cost Reporting: - Track tokens (input, output, cache read/write) and costs per run - Display summary at end of each run in console and Slack thread - Example: 💰 Usage: 12,543 in + 847 out (5,234 cache read) = $0.0234 Prompt Caching Optimization: - Move recent messages from system prompt to user message - System prompt now mostly static (only changes with memory files) - Enables effective use of Anthropic's prompt caching - Significantly reduces costs on subsequent requests Model & Cost Improvements: - Switch from Claude Opus 4.5 to Sonnet 4.5 (~40% cost reduction) - Fix Claude Opus 4.5 cache pricing in ai package (was 3x too expensive) - Add manual override in generate-models.ts until upstream fix merges - Submitted PR to models.dev: https://github.com/sst/models.dev/pull/439 UI/UX Improvements: - Extract actual text from tool results instead of JSON wrapper - Cleaner Slack thread formatting with duration and labels - Tool args formatting shows paths with offset:limit notation - Add chalk for colored terminal output Dependencies: - Add chalk package for terminal colors
This commit is contained in:
parent
82d4ac93e1
commit
213bc4df1c
11 changed files with 478 additions and 63 deletions
|
|
@ -2,12 +2,14 @@ import { SocketModeClient } from "@slack/socket-mode";
|
|||
import { WebClient } from "@slack/web-api";
|
||||
import { readFileSync } from "fs";
|
||||
import { basename } from "path";
|
||||
import * as log from "./log.js";
|
||||
import { type Attachment, ChannelStore } from "./store.js";
|
||||
|
||||
export interface SlackMessage {
|
||||
text: string; // message content (mentions stripped)
|
||||
rawText: string; // original text with mentions
|
||||
user: string; // user ID
|
||||
userName?: string; // user handle
|
||||
channel: string; // channel ID
|
||||
ts: string; // timestamp (for threading)
|
||||
attachments: Attachment[]; // file attachments
|
||||
|
|
@ -15,6 +17,7 @@ export interface SlackMessage {
|
|||
|
||||
export interface SlackContext {
|
||||
message: SlackMessage;
|
||||
channelName?: string; // channel name for logging (e.g., #dev-team)
|
||||
store: ChannelStore;
|
||||
/** Send/update the main message (accumulates text) */
|
||||
respond(text: string): Promise<void>;
|
||||
|
|
@ -92,7 +95,7 @@ export class MomBot {
|
|||
// Log the mention (message event may not fire for app_mention)
|
||||
await this.logMessage(slackEvent);
|
||||
|
||||
const ctx = this.createContext(slackEvent);
|
||||
const ctx = await this.createContext(slackEvent);
|
||||
await this.handler.onChannelMention(ctx);
|
||||
});
|
||||
|
||||
|
|
@ -133,7 +136,7 @@ export class MomBot {
|
|||
|
||||
// Only trigger handler for DMs (channel mentions are handled by app_mention event)
|
||||
if (slackEvent.channel_type === "im") {
|
||||
const ctx = this.createContext({
|
||||
const ctx = await this.createContext({
|
||||
text: slackEvent.text || "",
|
||||
channel: slackEvent.channel,
|
||||
user: slackEvent.user,
|
||||
|
|
@ -167,16 +170,30 @@ export class MomBot {
|
|||
});
|
||||
}
|
||||
|
||||
private createContext(event: {
|
||||
private async createContext(event: {
|
||||
text: string;
|
||||
channel: string;
|
||||
user: string;
|
||||
ts: string;
|
||||
files?: Array<{ name: string; url_private_download?: string; url_private?: string }>;
|
||||
}): SlackContext {
|
||||
}): Promise<SlackContext> {
|
||||
const rawText = event.text;
|
||||
const text = rawText.replace(/<@[A-Z0-9]+>/gi, "").trim();
|
||||
|
||||
// Get user info for logging
|
||||
const { userName } = await this.getUserInfo(event.user);
|
||||
|
||||
// Get channel name for logging (best effort)
|
||||
let channelName: string | undefined;
|
||||
try {
|
||||
if (event.channel.startsWith("C")) {
|
||||
const result = await this.webClient.conversations.info({ channel: event.channel });
|
||||
channelName = result.channel?.name ? `#${result.channel.name}` : undefined;
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors - we'll just use the channel ID
|
||||
}
|
||||
|
||||
// Process attachments (for context, already logged by message handler)
|
||||
const attachments = event.files ? this.store.processAttachments(event.channel, event.files, event.ts) : [];
|
||||
|
||||
|
|
@ -191,10 +208,12 @@ export class MomBot {
|
|||
text,
|
||||
rawText,
|
||||
user: event.user,
|
||||
userName,
|
||||
channel: event.channel,
|
||||
ts: event.ts,
|
||||
attachments,
|
||||
},
|
||||
channelName,
|
||||
store: this.store,
|
||||
respond: async (responseText: string) => {
|
||||
// Queue updates to avoid race conditions
|
||||
|
|
@ -276,11 +295,11 @@ export class MomBot {
|
|||
const auth = await this.webClient.auth.test();
|
||||
this.botUserId = auth.user_id as string;
|
||||
await this.socketClient.start();
|
||||
console.log("⚡️ Mom bot connected and listening!");
|
||||
log.logConnected();
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
await this.socketClient.disconnect();
|
||||
console.log("Mom bot disconnected.");
|
||||
log.logDisconnected();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue