Skip to content

Commit 9b4e37c

Browse files
committed
improvement(desktop): deliver the claim held across Stop to Sim even if Electron gave up on it, and require its 410
1 parent dc4bfa2 commit 9b4e37c

2 files changed

Lines changed: 24 additions & 9 deletions

File tree

‎apps/desktop/e2e/desktop-tools-live-sim.spec.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,8 @@ test.describe('desktop tools against a live Sim', () => {
548548
})
549549
const page = await openApp(user, 'Stop chat')
550550
const chatId = user.chats['Stop chat']
551-
const lateClaim = proxy.hold(isDesktopClaim)
551+
// Delivered to Sim on release even should Electron have given up on it meanwhile.
552+
const lateClaim = proxy.hold(isDesktopClaim, { deliverIfAbandoned: true })
552553
// Should Electron give up on the held claim first, the read reports its own failure; hold that
553554
// too, so the call's outcome is Stop's.
554555
const report = proxy.hold(isToolReport)
@@ -559,7 +560,8 @@ test.describe('desktop tools against a live Sim', () => {
559560
await expect.poll(() => callState(chatId), { timeout: 30_000 }).toMatch(/^cancelled/)
560561
lateClaim.release()
561562
report.release()
562-
// The claim held across Stop, and the same claim replayed afterwards, are both refused.
563+
// The claim held across Stop reaches Sim after it and is refused, and so is a replay of it.
564+
await expect.poll(() => claim.status, { timeout: 15_000 }).toBe(410)
563565
const [call] = await db.toolCalls(chatId)
564566
const replay = await page.evaluate(
565567
(toolCallId) =>

‎apps/desktop/e2e/fixtures/live-sim.ts‎

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ class HeldRequest {
108108
private releaseHeld: (() => void) | undefined
109109
private released = false
110110

111-
constructor(readonly matches: (method: string, path: string) => boolean) {
111+
constructor(
112+
readonly matches: (method: string, path: string) => boolean,
113+
/** Deliver the request to Sim on release even if its client gave up, as a late request would arrive. */
114+
readonly deliverIfAbandoned = false
115+
) {
112116
this.reached = new Promise((resolve) => {
113117
this.reach = resolve
114118
})
@@ -214,8 +218,11 @@ export class SimProxy {
214218
}
215219

216220
/** Holds the next request that matches until the returned handle releases it. */
217-
hold(matches: (method: string, path: string) => boolean): HeldRequest {
218-
const held = new HeldRequest(matches)
221+
hold(
222+
matches: (method: string, path: string) => boolean,
223+
options: { deliverIfAbandoned?: boolean } = {}
224+
): HeldRequest {
225+
const held = new HeldRequest(matches, options.deliverIfAbandoned)
219226
this.holds.push(held)
220227
return held
221228
}
@@ -258,7 +265,7 @@ export class SimProxy {
258265
const held = this.holds.find((candidate) => candidate.matches(method, url.pathname))
259266
if (held) {
260267
this.holds = this.holds.filter((candidate) => candidate !== held)
261-
if (!(await held.hold(entry, response))) return
268+
if (!(await held.hold(entry, response)) && !held.deliverIfAbandoned) return
262269
}
263270
if (this.chatBodyRewrite && method === 'POST' && url.pathname === '/api/mothership/chat') {
264271
const parsed: Record<string, unknown> = JSON.parse(body.toString('utf8'))
@@ -271,14 +278,20 @@ export class SimProxy {
271278
const { 'transfer-encoding': _chunked, ...forwarded } = request.headers
272279
const headers: IncomingHttpHeaders = { ...forwarded, 'content-length': String(body.length) }
273280
await new Promise<void>((resolve, reject) => {
281+
const clientGone = response.destroyed
274282
const upstream = httpRequest(target, { method, headers }, (upstreamResponse) => {
275283
entry.status = upstreamResponse.statusCode
276-
response.writeHead(upstreamResponse.statusCode ?? 502, upstreamResponse.headers)
277-
upstreamResponse.pipe(response)
278284
upstreamResponse.on('end', resolve)
279285
upstreamResponse.on('error', reject)
286+
// A request delivered after its client gave up is answered to no one.
287+
if (clientGone) {
288+
upstreamResponse.resume()
289+
return
290+
}
291+
response.writeHead(upstreamResponse.statusCode ?? 502, upstreamResponse.headers)
292+
upstreamResponse.pipe(response)
280293
})
281-
response.on('close', () => upstream.destroy())
294+
if (!clientGone) response.on('close', () => upstream.destroy())
282295
upstream.on('error', reject)
283296
upstream.end(body)
284297
})

0 commit comments

Comments
 (0)