Skip to content

Commit a6dc5f1

Browse files
authored
test(daemon): fix steering-timestamp flake in message-delivery-mode-queue (#2554)
The "immediate steering while busy" test slept a fixed 2s after busy and then asserted steeredTimestamp < resultTimestamp. Under CI scheduling the sleep can outlast the whole dev-proxy mock turn (~1.6s): the steer job is then claimed milliseconds after the turn settles, takes the promote path into a fresh turn, and its consumption-aligned timestamp lands after the original turn's final result (both CI failures showed the steer claimed 1-2ms after settle, timestamp 6-7ms after the result). Waiting for assistant content does not fix it — the mock response is non-streaming, so the assistant message and the final result persist within the same millisecond (verified locally: content-triggered steers still landed 8-12ms after the result). Instead, steer as soon as the turn's kickoff flips to consumed (the SDK generator's admission signal, the earliest point a steer can be inserted), with busyness re-checked as the last signal before sending and a skip guard when the turn ends first. Local dev-proxy validation: steer consumed ~980ms after admission and 9.7-11.8s before the result, 8 consecutive passes.
1 parent 105f40d commit a6dc5f1

1 file changed

Lines changed: 46 additions & 2 deletions

File tree

packages/daemon/tests/online/features/message-delivery-mode-queue.test.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,43 @@ describe('Message delivery mode queue flow', () => {
8787
return false;
8888
}
8989

90+
/**
91+
* Wait until the busy turn's kickoff message has been consumed by the SDK,
92+
* so an immediate steer feeds a live generator mid-turn. The kickoff flips
93+
* to `consumed` at the generator's admission signal, which is the earliest
94+
* proof a steer can be inserted; steering on any LATER observable is a race
95+
* against the turn's own pacing — a dev-proxy mock response is
96+
* non-streaming, so the assistant message and the final result persist
97+
* within the same millisecond, and a steer sent after "some content" (or
98+
* after a fixed sleep that outlasts the turn) is claimed only after the
99+
* result: promoted to a fresh turn whose consumption timestamp lands after
100+
* the original result, failing the ordering this suite asserts. Steering
101+
* right after admission keeps the steered message's timestamp inside the
102+
* live turn, ahead of its final result by the whole response latency.
103+
* Returns false when the session goes idle (or the wait times out while
104+
* busy) before the kickoff is consumed — the steer-while-busy premise no
105+
* longer holds, so the caller should skip instead of asserting against a
106+
* promoted turn.
107+
*/
108+
async function waitForTurnKickoffConsumed(
109+
sessionId: string,
110+
timeoutMs = IS_MOCK ? 10000 : 30000
111+
): Promise<boolean> {
112+
const startedAt = Date.now();
113+
while (Date.now() - startedAt < timeoutMs) {
114+
if ((await getCountByStatus(sessionId, 'consumed')) >= 1) {
115+
// Verify busyness as late as possible — the steer must feed the live
116+
// turn, not one that settled since the kickoff's consumption.
117+
const state = await getProcessingState(daemon, sessionId);
118+
return state.status === 'queued' || state.status === 'processing';
119+
}
120+
const state = await getProcessingState(daemon, sessionId);
121+
if (state.status === 'idle') return false;
122+
await new Promise((resolve) => setTimeout(resolve, 25));
123+
}
124+
return false;
125+
}
126+
90127
test(
91128
'defer while busy should be saved then auto-dispatched on turn end',
92129
async () => {
@@ -201,8 +238,15 @@ describe('Message delivery mode queue flow', () => {
201238
return;
202239
}
203240

204-
// Wait a moment to ensure some assistant content has been streamed
205-
await new Promise((resolve) => setTimeout(resolve, 2000));
241+
// Steer only once the turn's kickoff has been admitted by the SDK —
242+
// a fixed sleep can outlast a fast (dev-proxy mock) turn entirely,
243+
// promoting the steer to a fresh turn that consumes after the
244+
// original turn's final result. See waitForTurnKickoffConsumed.
245+
const kickoffConsumed = await waitForTurnKickoffConsumed(sessionId);
246+
if (!kickoffConsumed) {
247+
console.log('Skipping: turn ended before its kickoff message was consumed');
248+
return;
249+
}
206250

207251
// Send a steering message (immediate while busy)
208252
const steerResult = await sendMessage(

0 commit comments

Comments
 (0)