File size: 1,966 Bytes
4e23b01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
/**
 * Higher-level wait helpers built on top of `WsClient.waitForFrame` and
 * `HttpClient.getSession`. Kept separate from `client.ts` so scenarios can
 * import them directly without dragging the whole `DaemonClient` class.
 */
import type { Session } from '@moonshot-ai/kap-server/protocol/session';

import type { HttpClient } from './http.js';
import type { AnyFrame, WsClient } from './ws.js';

/** Default 60s wait for a single event frame — matches approval/question TTL. */
export const DEFAULT_FRAME_TIMEOUT_MS = 60_000;

/**
 * Wait for the first WS frame matching `predicate`. Thin wrapper that fills
 * the default timeout — most scenarios shouldn't have to think about it.
 */
export function waitForFrame(
  ws: WsClient,
  predicate: (frame: AnyFrame) => boolean,
  opts?: { timeoutMs?: number },
): Promise<AnyFrame> {
  return ws.waitForFrame(predicate, opts?.timeoutMs ?? DEFAULT_FRAME_TIMEOUT_MS);
}

/**
 * Poll `GET /sessions/{sid}` until aggregate `busy` matches. Useful as a
 * final synchronization point — the server's `turn.ended` arrives before
 * the session work projection flips to false, so scenarios that want a
 * quiescent session must poll.
 */
export async function waitForSessionBusy(
  http: HttpClient,
  sid: string,
  busy: boolean,
  opts?: { timeoutMs?: number; pollMs?: number },
): Promise<Session> {
  const timeoutMs = opts?.timeoutMs ?? DEFAULT_FRAME_TIMEOUT_MS;
  const pollMs = opts?.pollMs ?? 250;
  const deadline = Date.now() + timeoutMs;
  let last: Session | null = null;
  while (Date.now() < deadline) {
    const session = await http.getSession(sid);
    last = session;
    if (session.busy === busy) return session;
    await sleep(pollMs);
  }
  throw new Error(
    `session ${sid} did not reach busy=${busy} within ${timeoutMs}ms ` +
      `(last busy=${last?.busy ?? 'unknown'})`,
  );
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}