Spaces:
Running
Running
File size: 4,285 Bytes
11330a4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | // Small CDP client and retry policy shared with the harness regression test.
export function nextAttempt(index, position, attempt, maxRetries) {
if (position > index) return { index: Math.max(index + 1, position), attempt: 0 };
if (attempt >= maxRetries) return { index: index + 1, attempt: 0, gaveUp: true };
// Never rewind to a lagging follower after giving up on an earlier event.
return { index, attempt: attempt + 1, retry: true };
}
// A retry is scheduled after the bounded wait, but a slow follower can commit
// during the beat of grace before that retry is emitted. Re-read the tracker
// immediately before dispatch so a stale retry cannot re-strike an event that
// has already advanced.
export function retryAlreadyCommitted(index, position) {
return Number.isFinite(position) && position > index;
}
export function notePilotTarget(targets) {
return targets.find(t => t.type === 'page' && /^http:\/\/(localhost|127\.0\.0\.1):8000\//.test(t.url));
}
export function summariseRun(trace) {
const attempts = trace.events.filter(e => e.type === 'input.testEvent');
const commits = trace.events.filter(e => e.type === 'follow.advance' && e.accepted !== false);
const indices = new Set(attempts.map(e => e.index));
const onTime = new Set(), late = new Set();
const unmatched = [];
const latencies = [];
for (const c of commits) {
const previous = attempts.filter(a => a.index === c.toIndex && a.t <= c.t);
const a = previous.at(-1);
if (!a) { unmatched.push({index:c.toIndex,t:c.t,reason:'no prior attempt for this score event'}); continue; }
const lag = c.t - a.t;
if (lag <= 450) { onTime.add(c.toIndex); latencies.push(lag); }
else late.add(c.toIndex);
}
return {
attemptedEvents:indices.size, attempts:attempts.length,
detectedWithin450ms:onTime.size,
detectedOnlyLate:[...late].filter(i => !onTime.has(i)).length,
undetected:[...indices].filter(i => !onTime.has(i) && !late.has(i)),
commitsWithoutPriorAttempt:unmatched,
retries:attempts.filter(a => a.attempt > 0).length,
autoSkips:trace.events.filter(e => e.type === 'skip' && e.reason === 'auto')
.map(e => ({index:e.fromIndex,heldMs:e.heldMs,dueHeldMs:e.dueHeldMs})),
medianDetectionMs:latencies.length ? latencies.sort((a,b)=>a-b)[Math.floor(latencies.length/2)] : null,
invalid:trace.events.filter(e => e.type === 'test.invalid').map(e => e.reason),
};
}
export async function connectNotePilot(port = 9222) {
const targets = await (await fetch(`http://127.0.0.1:${port}/json/list`, {signal:AbortSignal.timeout(5000)})).json();
const page = notePilotTarget(targets);
if (!page) throw Error('No NotePilot tab found on localhost:8000');
const ws = new WebSocket(page.webSocketDebuggerUrl);
await new Promise((resolve, reject) => {
const timer = setTimeout(() => { ws.close(); reject(Error('CDP connection timed out')); }, 5000);
ws.onopen = () => { clearTimeout(timer); resolve(); };
ws.onerror = () => { clearTimeout(timer); reject(Error('CDP connection failed')); };
});
let id = 0;
const pending = new Map();
ws.onmessage = e => {
const msg = JSON.parse(e.data), call = pending.get(msg.id);
if (!call) return;
pending.delete(msg.id); clearTimeout(call.timer);
if (msg.error) call.reject(Error(JSON.stringify(msg.error))); else call.resolve(msg.result);
};
ws.onclose = () => {
for (const call of pending.values()) { clearTimeout(call.timer); call.reject(Error('CDP closed')); }
pending.clear();
};
const send = (method, params = {}, timeout = 15000) => new Promise((resolve, reject) => {
const requestId = ++id;
const timer = setTimeout(() => { pending.delete(requestId); reject(Error(`CDP timed out: ${method}`)); }, timeout);
pending.set(requestId, {resolve, reject, timer});
ws.send(JSON.stringify({id:requestId, method, params}));
});
const evaluate = async (expression, timeout = 15000) => {
const r = await send('Runtime.evaluate', {expression, awaitPromise:true, returnByValue:true, userGesture:true}, timeout);
if (r.exceptionDetails) throw Error(r.exceptionDetails.exception?.description || JSON.stringify(r.exceptionDetails));
return r.result.value;
};
return {send, evaluate, close: () => ws.close()};
}
|