Rework pacing, pause poll, add user search, voice overview, and DM list

This commit is contained in:
Fisto
2026-09-13 15:47:43 +02:00
parent 32201b34cf
commit 4e1046fae7
13 changed files with 539 additions and 81 deletions
+68 -12
View File
@@ -1,34 +1,90 @@
/*
* Veskslop Phase 0 spike — global query pacing.
* Veskslop daemon — pacing.
*
* Current policy: every command sent to the client is spaced by a random
* 500-2800 ms. Commands are serialized through one queue, and identical
* concurrent queries are coalesced so the total query count stays minimal.
* Outbound sends use a human-emulation policy: the first send of a burst goes
* out quickly (30-200 ms), later sends in the same burst are spaced 500-2800 ms.
* A burst ends after a random 5-20 s of quiet. Read queries are serialized with
* a small gap so they do not look like a scan.
*
* The long-term human-emulation policy (snappy first action, slower bulk
* actions, risky fast mode) lives in AGENTS.md and is not active yet.
* The core adds a second, hard rate limit. See docs/core-bridge.md.
*/
export function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/** Current per-query delay: 500-2800 ms. */
export function queryDelay(): number {
return 500 + Math.floor(Math.random() * 2301);
export const PACING = {
firstMinMs: 30,
firstMaxMs: 200,
burstMinMs: 500,
burstMaxMs: 2800,
idleMinMs: 5000,
idleMaxMs: 20000,
readMinMs: 100,
readMaxMs: 300
};
function randomBetween(min: number, max: number): number {
return min + Math.floor(Math.random() * (max - min + 1));
}
/** Delay before the first send of a burst. */
export function firstActionDelay(): number {
return randomBetween(PACING.firstMinMs, PACING.firstMaxMs);
}
/** Delay between sends inside one burst. */
export function burstActionDelay(): number {
return randomBetween(PACING.burstMinMs, PACING.burstMaxMs);
}
/** How long the current burst stays active after a send. */
export function burstWindow(): number {
return randomBetween(PACING.idleMinMs, PACING.idleMaxMs);
}
/**
* Plan the next outbound send. Pure so the policy is testable.
* A send while `now > burstUntil` starts a new burst with a fast first action.
*/
export function planSend(now: number, burstUntil: number): { delay: number; burstUntil: number } {
const delay = now > burstUntil ? firstActionDelay() : burstActionDelay();
return { delay, burstUntil: now + burstWindow() };
}
let queue: Promise<unknown> = Promise.resolve();
let queryCount = 0;
let burstUntil = 0;
/** Serialize a REST query behind the queue with the configured delay. */
/** Serialize an outbound send with the human-emulation delay. */
export function pacedSend<T>(task: () => Promise<T>): Promise<T> {
const run = queue.then(async () => {
const plan = planSend(Date.now(), burstUntil);
burstUntil = plan.burstUntil;
await sleep(plan.delay);
queryCount++;
if (process.env.DEBUG === "1") {
console.error(`[send] query #${queryCount} (+${plan.delay}ms)`);
}
return task();
});
queue = run.then(
() => undefined,
() => undefined
);
return run;
}
/** Serialize a read query with a small gap. Reads do not affect the send burst. */
export function paced<T>(task: () => Promise<T>): Promise<T> {
const delay = queryDelay();
const delay = randomBetween(PACING.readMinMs, PACING.readMaxMs);
const run = queue.then(async () => {
await sleep(delay);
queryCount++;
if (process.env.DEBUG === "1") {
console.error(`[rest] query #${queryCount} (+${delay}ms)`);
console.error(`[read] query #${queryCount} (+${delay}ms)`);
}
return task();
});