test(wsl): wait for the chat provider's report to settle instead of reading it once (#35603)

* test(wsl): wait for the chat provider's report to settle instead of reading it once

* test(wsl): wait for the resumed turn's message, not just its launch
This commit is contained in:
Neil
2026-10-28 19:35:06 -07:00
committed by GitHub
parent eef196de06
commit 3b158cfb7c
3 changed files with 58 additions and 9 deletions
@@ -1,3 +1,4 @@
import { vi } from 'vitest'
import { z } from 'zod'
import { folderWorkspaceKey } from '../../shared/workspace-scope'
import { readFileSync } from 'node:fs'
@@ -126,3 +127,34 @@ export const WslChatProviderReport = z.object({
controlRequests: z.array(z.object({ request: z.object({ subtype: z.string() }) })),
controlResponses: z.array(z.object({ response: z.object({ response: z.unknown().optional() }) }))
})
const PROVIDER_REPORT_TIMEOUT_MS = 30_000
/** Scripted providers rewrite their report in place after each act, so one read can see a torn file or a stale one. */
export async function waitForProviderReport<T>(
host: WslChatHost,
path: string,
schema: z.ZodType<T>,
settled: (report: T) => boolean,
awaiting: string
): Promise<T> {
let lastRead = ''
try {
return await vi.waitFor(
async () => {
lastRead = await host.guest(`cat ${quotePosixShell(path)} 2>/dev/null || true`)
const report = schema.parse(JSON.parse(lastRead))
if (!settled(report)) {
throw new Error(`report does not show ${awaiting} yet`)
}
return report
},
{ timeout: PROVIDER_REPORT_TIMEOUT_MS, interval: 250 }
)
} catch (error) {
throw new Error(
`Provider report ${path} did not show ${awaiting} within ${PROVIDER_REPORT_TIMEOUT_MS} ms; last read ${JSON.stringify(lastRead.slice(-600))}`,
{ cause: error }
)
}
}
@@ -6,7 +6,10 @@ import type { AgentSessionSubscribeEvent } from '../../shared/agent-session-wire
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../shared/electron-remote-runtime-client-capabilities'
import { subscribeRemoteRuntimeRequest } from '../../shared/remote-runtime/remote-runtime-client'
import { quotePosixShell } from '../../shared/shell-argument-quoting'
import type { WslScriptedProviderInstall } from './wsl-orcad-chat-provider-fixture'
import {
waitForProviderReport,
type WslScriptedProviderInstall
} from './wsl-orcad-chat-provider-fixture'
import { assistantText } from './wsl-orcad-chat-assistant-text'
import { envelope } from './wsl-orcad-chat-envelope'
import type { ChatLane } from './wsl-orcad-structured-chat-contract'
@@ -167,8 +170,13 @@ export async function exerciseScriptedProviderChat(
},
{ timeout: 30_000 }
)
const report = await readReport()
expect(report.methods).toContain(provider.stopMethod)
const report = await waitForProviderReport(
lane,
install.reportPath,
ScriptedProviderReport,
(written) => written.methods.includes(provider.stopMethod),
provider.stopMethod
)
expect(report.cwd).toBe(cwd)
// The guest's own runtime ran the guest install found on its login PATH: never a /mnt/ copy.
expect(report.execPath).toMatch(/^\/(?!mnt\/)/u)
@@ -28,6 +28,7 @@ import {
import {
installWslChatFixture,
WslChatProviderReport,
waitForProviderReport,
writeChatFixtureFile,
type WslChatHost
} from './wsl-orcad-chat-provider-fixture'
@@ -180,8 +181,12 @@ async function exerciseChat(
envelope: envelope(sessionId, 'agentSession.cancel', {}, fence)
})
).toMatchObject({ ok: true, value: { cancelled: true } })
const report = WslChatProviderReport.parse(
JSON.parse(await lane.guest(`cat ${quotePosixShell(reportPath)}`))
const report = await waitForProviderReport(
lane,
reportPath,
WslChatProviderReport,
(written) => written.controlRequests.some((entry) => entry.request.subtype === 'interrupt'),
'the Stop interrupt'
)
expect(report.cwd).toBe(cwd)
expect(report.execPath).toMatch(/^\//u)
@@ -192,7 +197,6 @@ async function exerciseChat(
ORCA_AGENT_SESSION_ID: sessionId,
ORCA_STRUCTURED_SESSION: '1'
})
expect(report.controlRequests.some((entry) => entry.request.subtype === 'interrupt')).toBe(true)
expect(report.controlResponses).toEqual(
expect.arrayContaining([
expect.objectContaining({
@@ -257,11 +261,16 @@ async function exerciseChat(
},
{ timeout: 30_000 }
)
const resumed = WslChatProviderReport.parse(
JSON.parse(await lane.guest(`cat ${quotePosixShell(reportPath)}`))
const resumedArgv = new RegExp(`--resume(?:=| )${providerId}`)
const resumed = await waitForProviderReport(
lane,
reportPath,
WslChatProviderReport,
(written) => resumedArgv.test(written.argv.join(' ')) && written.userMessages.length > 0,
'the resumed turn'
)
expect(resumed.cwd).toBe(cwd)
expect(resumed.argv.join(' ')).toMatch(new RegExp(`--resume(?:=| )${providerId}`))
expect(resumed.argv.join(' ')).toMatch(resumedArgv)
expect(resumed.userMessages).toHaveLength(1)
return {
sessionId,