Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions packages/evals/src/cli/messageLogDeduper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { MessageLogDeduper } from "./messageLogDeduper.js"

describe("MessageLogDeduper", () => {
it("dedupes identical messages for same action+ts", () => {
const d = new MessageLogDeduper()
const msg = { ts: 123, type: "say", say: "reasoning", text: "hello", partial: false }

expect(d.shouldLog("updated", msg)).toBe(true)
expect(d.shouldLog("updated", msg)).toBe(false)
})

it("logs again if payload changes for same action+ts", () => {
const d = new MessageLogDeduper()
expect(d.shouldLog("updated", { ts: 123, text: "a" })).toBe(true)
expect(d.shouldLog("updated", { ts: 123, text: "b" })).toBe(true)
})

it("does not dedupe across different actions", () => {
const d = new MessageLogDeduper()
const msg = { ts: 123, text: "same" }
expect(d.shouldLog("created", msg)).toBe(true)
expect(d.shouldLog("updated", msg)).toBe(true)
})

it("evicts oldest entries", () => {
const d = new MessageLogDeduper(2)

expect(d.shouldLog("updated", { ts: 1, text: "a" })).toBe(true)
expect(d.shouldLog("updated", { ts: 2, text: "b" })).toBe(true)
// causes eviction of ts:1
expect(d.shouldLog("updated", { ts: 3, text: "c" })).toBe(true)
// ts:1 was evicted so it should log again
expect(d.shouldLog("updated", { ts: 1, text: "a" })).toBe(true)
})
})
50 changes: 50 additions & 0 deletions packages/evals/src/cli/messageLogDeduper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
export class MessageLogDeduper {
private readonly lastLoggedByKey = new Map<string, string>()

constructor(private readonly maxEntries = 10_000) {}

/**
* Returns true if this message should be logged.
* Dedupe key: `${action}:${message.ts}`.
* Dedupe rule: skip if payload is identical to the last logged payload for that key.
*/
public shouldLog(action: string | undefined, message: unknown): boolean {
if (!action || !message || typeof message !== "object") {
return true
}

const ts = (message as { ts?: unknown }).ts
if (typeof ts !== "number") {
return true
}

let serialized: string
try {
serialized = JSON.stringify(message)
} catch {
// If serialization fails, prefer logging.
return true
}

const key = `${action}:${ts}`
const prev = this.lastLoggedByKey.get(key)
if (prev === serialized) {
return false
}

// Refresh insertion order so eviction removes true oldest.
if (this.lastLoggedByKey.has(key)) {
this.lastLoggedByKey.delete(key)
}
this.lastLoggedByKey.set(key, serialized)

if (this.lastLoggedByKey.size > this.maxEntries) {
const oldestKey = this.lastLoggedByKey.keys().next().value as string | undefined
if (oldestKey) {
this.lastLoggedByKey.delete(oldestKey)
}
}

return true
}
}
11 changes: 11 additions & 0 deletions packages/evals/src/cli/runTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { EVALS_REPO_PATH } from "../exercises/index.js"
import { Logger, getTag, isDockerContainer } from "./utils.js"
import { redisClient, getPubSubKey, registerRunner, deregisterRunner } from "./redis.js"
import { runUnitTest } from "./runUnitTest.js"
import { MessageLogDeduper } from "./messageLogDeduper.js"

class SubprocessTimeoutError extends Error {
constructor(timeout: number) {
Expand Down Expand Up @@ -305,6 +306,7 @@ export const runTask = async ({ run, task, publish, logger, jobToken }: RunTaskO
]

let isApiUnstable = false
const messageLogDeduper = new MessageLogDeduper()

client.on(IpcMessageType.TaskEvent, async (taskEvent) => {
const { eventName, payload } = taskEvent
Expand All @@ -330,6 +332,15 @@ export const runTask = async ({ run, task, publish, logger, jobToken }: RunTaskO
(payload[0].message.say && loggableSays.includes(payload[0].message.say)) ||
payload[0].message.partial !== true)
) {
// Dedupe identical repeated message events (same message.ts + same payload)
if (eventName === RooCodeEventName.Message) {
const action = payload[0]?.action as string | undefined
const message = payload[0]?.message
if (!messageLogDeduper.shouldLog(action, message)) {
return
}
}

// Extract tool name for tool-related messages for clearer logging
let logEventName: string = eventName
if (eventName === RooCodeEventName.Message && payload[0]?.message?.ask === "tool") {
Expand Down
Loading