From b2bf72c741d31d8b3c3fc10f9e2a3e336e3b95ff Mon Sep 17 00:00:00 2001 From: Angel Galindo <131726962+AngelGalindo7@users.noreply.github.com> Date: Thu, 25 Jun 2026 22:54:38 -0700 Subject: [PATCH 1/2] fix(agent novelty): use Set.has() for recentStateIds membership check --- src/agent/expectations.js | 2 +- src/agent/novelty.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/agent/expectations.js b/src/agent/expectations.js index 23bedad..44e88ac 100644 --- a/src/agent/expectations.js +++ b/src/agent/expectations.js @@ -107,7 +107,7 @@ export function scoreState({ currentUrl = null, prevUrl = null, hardSignals = [], - recentStateIds = [], + recentStateIds = new Set(), currentStateId = null, lowSignalExtra = [], }) { diff --git a/src/agent/novelty.js b/src/agent/novelty.js index e94e0d6..4a2895e 100644 --- a/src/agent/novelty.js +++ b/src/agent/novelty.js @@ -48,11 +48,11 @@ export function scoreNovelty({ prevUrl = null, currUrl = null, currentStateId = null, - recentStateIds = [], + recentStateIds = new Set(), lowSignalExtra = [], }) { // 0.0 — we have been in this state cluster before. Refresh/scroll/no-op loops. - if (currentStateId && recentStateIds.includes(currentStateId)) { + if (currentStateId && recentStateIds.has(currentStateId)) { return { score: 0.0, reason: 'repeat state cluster' }; } From 3d3d897cc84d59ec296bcb80f483dbb80640aed3 Mon Sep 17 00:00:00 2001 From: Angel Galindo <131726962+AngelGalindo7@users.noreply.github.com> Date: Thu, 25 Jun 2026 22:55:06 -0700 Subject: [PATCH 2/2] test(agent novelty): update recentStateIds fixtures to Set, add bucket and guard coverage --- tests/unit/novelty.test.js | 42 +++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/tests/unit/novelty.test.js b/tests/unit/novelty.test.js index ec7e7e7..0e9b8e9 100644 --- a/tests/unit/novelty.test.js +++ b/tests/unit/novelty.test.js @@ -7,7 +7,7 @@ describe('novelty.scoreNovelty — discrete buckets', () => { it('0.0 when the current state cluster was seen recently', () => { const out = scoreNovelty({ currentStateId: 'abc123', - recentStateIds: ['xxx', 'abc123'], + recentStateIds: new Set(['xxx', 'abc123']), prevA11y: tree(), currA11y: tree([{ role: 'button', name: 'New' }]), }); @@ -42,11 +42,51 @@ describe('novelty.scoreNovelty — discrete buckets', () => { expect(out.score).toBe(0.5); }); + it('0.0 when stateId is in a Set of recent ids (Set.has semantics)', () => { + const out = scoreNovelty({ + currentStateId: 'seen', + recentStateIds: new Set(['other', 'seen']), + prevA11y: tree(), + currA11y: tree([{ role: 'button', name: 'X' }]), + }); + expect(out.score).toBe(0.0); + expect(out.reason).toMatch(/repeat/); + }); + it('0.0 when nothing visibly changed', () => { const same = tree([{ role: 'button', name: 'A' }]); const out = scoreNovelty({ prevA11y: same, currA11y: tree([{ role: 'button', name: 'A' }]) }); expect(out.score).toBe(0.0); }); + + it('0.2 when a control disappears with no new control appearing', () => { + const out = scoreNovelty({ + prevA11y: tree([{ role: 'button', name: 'Save' }, { role: 'status', name: 'Saving' }]), + currA11y: tree([{ role: 'button', name: 'Save' }]), + }); + expect(out.score).toBe(0.2); + expect(out.reason).toBe('text shifted'); + }); + + it('falls through to a11y scoring when currentStateId is null', () => { + const out = scoreNovelty({ + currentStateId: null, + recentStateIds: new Set(['x']), + prevA11y: tree(), + currA11y: tree([{ role: 'button', name: 'X' }]), + }); + expect(out.score).toBe(0.5); + }); + + it('falls through when recentStateIds is empty', () => { + const out = scoreNovelty({ + currentStateId: 'new-state', + recentStateIds: new Set(), + prevA11y: tree(), + currA11y: tree([{ role: 'button', name: 'X' }]), + }); + expect(out.score).toBe(0.5); + }); }); describe('novelty — low-signal name suppression', () => {