Address review: one Go per tab, declines for a granted miss, and grace per overlay

Four review threads on the agent-target protocol and the hook stand-down.

Overlay: a tab acting on one target is busy for every other target
(`agent_target_in_flight`), so two held generate requests can never both
be claimed by one tab and the second Go can never overwrite the session
the first one minted. Every exit from actOnAgentTarget ends the acting
state, and teardown clears the target ledger, so a Go that never happened
does not refuse the next connection's targets. A miss after a granted
claim now declines (handing the lease back so another page or a remount
can serve) instead of posting a result that ended the request for every
tab.

Hook: the live-preview marker probe runs before the per-session edit cap,
so a file already past the cap stands down for a variants wrap instead
of emitting the suppression notice.

Server: each overlay's first no_match word extends the resolution grace
by the full window (its watch re-reports do not), so an overlay that
reports after another page's grace lapsed still gets its late-mount
watch instead of completing the roll call with a no_match verdict.

Tests: a Rust and a Node protocol case for the late overlay's grace, a
hook case for the cap-then-wrap order, and contract pins for the busy
check, the decline on a granted miss, and the teardown clear.

Written with AI assistance (Claude).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Abdul Wahab
2026-09-15 05:45:49 +05:00
committed by Abdul Wahab
co-authored by Claude Fable 5
parent 79a27051a2
commit a3bb21cbde
9 changed files with 194 additions and 18 deletions
+35
View File
@@ -704,6 +704,41 @@ describe('POST /agent-target', { skip: ENGINE_BIN ? false : ENGINE_MISSING_MESSA
}
});
it('extends the grace on a late overlay\'s first no_match word, so it still gets its watch', async () => {
const tabA = await openSseClient(server, { clientId: 'tab-a' });
const tabB = await openSseClient(server, { clientId: 'tab-b' });
try {
await tabA.next((m) => m.type === 'connected');
await tabB.next((m) => m.type === 'connected');
const held = postJson(server, '/agent-target', {
token: server.token, selector: 'h1', action: 'bolder', count: 3,
});
const pushed = await tabA.next((m) => m.type === 'agent_target');
const decline = (clientId) => postJson(server, '/agent-target-claim', {
token: server.token, targetId: pushed.targetId, clientId, eligible: false, state: 'IDLE', reason: 'no_match',
result: { ok: false, error: 'no_match', matchCount: 0, rawMatchCount: 0 },
});
assert.equal((await (await decline('tab-a')).json()).pending, true);
// Tab A's grace (150ms) lapses before tab B says its first word.
await new Promise((r) => setTimeout(r, 200));
const late = await (await decline('tab-b')).json();
assert.equal(late.pending, true, 'a late overlay\'s first no_match word extends the grace');
await new Promise((r) => setTimeout(r, 60));
const claim = await (await postJson(server, '/agent-target-claim', {
token: server.token, targetId: pushed.targetId, clientId: 'tab-b', eligible: true,
})).json();
assert.equal(claim.granted, true, 'the late overlay\'s watcher claims within its grace');
await postJson(server, '/agent-target-result', {
token: server.token, targetId: pushed.targetId, ok: true, matchCount: 1, sessionId: 'aabbccdd',
});
const verdict = await (await held).json();
assert.equal(verdict.sessionId, 'aabbccdd');
} finally {
tabA.close();
tabB.close();
}
});
it('prefers busy over no_match, so the agent retries when the right page is mid-session', async () => {
const tabA = await openSseClient(server, { clientId: 'tab-a' });
const tabB = await openSseClient(server, { clientId: 'tab-b' });
+14
View File
@@ -826,6 +826,10 @@ describe('live-browser source contracts', () => {
it('re-claims busy-declined agent targets only while the overlay can still serve them', () => {
const teardownSource = SOURCE.match(/function teardown\(\) \{[\s\S]*?\n \}/)?.[0] || '';
const clearAt = teardownSource.indexOf('busyDeclinedTargets.clear();');
assert.ok(
teardownSource.includes('agentTargetsSeen.clear();'),
'teardown clears the target ledger, so a stale acting entry never refuses the next connection\'s targets',
);
const idleAt = teardownSource.indexOf("setLiveState('IDLE')");
assert.ok(clearAt >= 0 && idleAt > clearAt, 'teardown must drop declined targets before its IDLE transition, or a dead overlay re-claims a lease');
assert.match(
@@ -856,6 +860,16 @@ describe('live-browser source contracts', () => {
/if \(claim\.granted\) \{ noteAgentTarget\(msg\.targetId, 'acting'\); actOnAgentTarget\(msg\); return; \}/,
'a granted claim marks the target as acting before Go',
);
assert.match(
SOURCE,
/function agentTargetBusyReason\(exceptTargetId\) \{[\s\S]{0,500}?status === 'acting' && targetId !== exceptTargetId\) return 'agent_target_in_flight';/,
'a tab acting on one target is busy for every other target, so two held requests can never both mint a session here',
);
assert.match(
SOURCE,
/function actOnAgentTarget\(msg\) \{[\s\S]{0,900}?if \(resolved\.error\) \{[\s\S]{0,400}?reportAgentTargetUnresolvable\(msg, resolved\.error\);/,
'a miss after a granted claim declines (handing the lease back) instead of ending the request for every tab',
);
// A page that cannot resolve the target never claims it: a first-wins
// claim would otherwise let the wrong page answer no_match for a target
// another page has.