mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-24 16:54:43 +00:00
With multiple full windows, each renderer independently reacts to the same backend event, so one-shot cues fired N times: OS notifications (the per-renderer throttle can't see other windows), the turn-end sound (playCompletionSound runs on every message.complete, ungated by focus), and auto-spoken replies (double voice when a chat is open in two windows). Add a single race-free owner in the main process (electron/event-dedupe.ts): main handles IPC serially, so the first window to claim a key within a short window wins and peers stay quiet. Notifications collapse at the hermes:notify choke point; the sound and spoken replies claim via a new hermes:ambient:claim IPC (keyed by session / reply id). Off Electron the claim falls back to "emit", preserving single-window behavior. The sound's mute check runs before the claim so a muted window can't win the cue and silence an audible peer.
37 lines
1.2 KiB
TypeScript
37 lines
1.2 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
|
|
import { test } from 'vitest'
|
|
|
|
import { createEventDeduper } from './event-dedupe'
|
|
|
|
test('collapses the same key inside the window (two windows, one event)', () => {
|
|
const isDup = createEventDeduper(1000)
|
|
|
|
assert.equal(isDup('input:s1', 0), false, 'first window claims')
|
|
assert.equal(isDup('input:s1', 5), true, 'second window is deduped')
|
|
})
|
|
|
|
test('distinct keys are independent', () => {
|
|
const isDup = createEventDeduper(1000)
|
|
|
|
assert.equal(isDup('input:s1', 0), false)
|
|
assert.equal(isDup('approval:s1', 0), false, 'different kind')
|
|
assert.equal(isDup('input:s2', 0), false, 'different session')
|
|
})
|
|
|
|
test('re-fires once the window elapses', () => {
|
|
const isDup = createEventDeduper(1000)
|
|
|
|
assert.equal(isDup('turnDone:s1', 0), false)
|
|
assert.equal(isDup('turnDone:s1', 999), true, 'still within window')
|
|
assert.equal(isDup('turnDone:s1', 1000), false, 'window elapsed → fires again')
|
|
})
|
|
|
|
test('prunes stale keys so the map cannot grow unbounded', () => {
|
|
const isDup = createEventDeduper(1000)
|
|
|
|
for (let i = 0; i < 100; i += 1) {
|
|
// Each far-apart key is pruned before the next, so none linger as duplicates.
|
|
assert.equal(isDup(`turnDone:s${i}`, i * 2000), false)
|
|
}
|
|
})
|