From 769fb865e17bcfb893b1e5c08bf098dd9db67bcb Mon Sep 17 00:00:00 2001 From: dimavrem22 Date: Wed, 15 Jul 2026 02:00:53 +0000 Subject: [PATCH 1/8] ci: use gpt-5.4-mini for live intelligence --- .env.example | 2 +- scripts/live-aut.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 583ecd3..8d93396 100644 --- a/.env.example +++ b/.env.example @@ -30,7 +30,7 @@ INKBOX_SIGNING_KEY=whsec_xxxxxxxxxxxx # --- opencode --- # INKBOX_GATEWAY_AGENT=inkbox-channel # opencode agent for gateway sessions -# INKBOX_GATEWAY_MODEL=openai/gpt-4o # "provider/model" override +# INKBOX_GATEWAY_MODEL=openai/gpt-5.4-mini # "provider/model" override # OPENCODE_SERVER_URL=http://127.0.0.1:4096 # attach to an existing server instead # INKBOX_GATEWAY_SERVE_PORT=4097 # port for the managed `opencode serve` # INKBOX_OPENCODE_BIN=opencode # binary for the managed server diff --git a/scripts/live-aut.sh b/scripts/live-aut.sh index 99029a5..5f43655 100755 --- a/scripts/live-aut.sh +++ b/scripts/live-aut.sh @@ -83,7 +83,7 @@ else cat > opencode.json <<'EOF' { "$schema": "https://opencode.ai/config.json" } EOF - GATEWAY_MODEL="openai/gpt-4o" + GATEWAY_MODEL="openai/gpt-5.4-mini" fi SERVE_LOG="$WORKDIR/serve.log" From 123aaa71a4f42e3c2f6b17b7d5770214a82c8a61 Mon Sep 17 00:00:00 2001 From: dimavrem22 Date: Wed, 15 Jul 2026 03:42:34 +0000 Subject: [PATCH 2/8] test: allow live voice driver through contact rules --- tests/live/voice.test.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/live/voice.test.ts b/tests/live/voice.test.ts index dcc5b5e..6276b93 100644 --- a/tests/live/voice.test.ts +++ b/tests/live/voice.test.ts @@ -8,6 +8,7 @@ // inbound_inkbox — driver calls the agent; agent answers Inkbox STT/TTS. // outbound_realtime — driver texts "call me"; agent calls back on Realtime. import { readFileSync } from "node:fs"; +import { PhoneRuleAction, PhoneRuleMatchType } from "@inkbox/sdk"; import { describe, expect, it } from "vitest"; import { AUT_KEY, @@ -35,6 +36,27 @@ function driverState(): DriverState { return JSON.parse(readFileSync(STATE_FILE, "utf-8")); } +async function ensureDriverAllowed( + aut: ReturnType, + driverNumber: string, +): Promise { + const mailbox = (await aut.mailboxes.list())[0]; + if (!mailbox) throw new Error("AUT identity has no mailbox"); + const handle = mailbox.emailAddress.split("@", 1)[0]; + const rules = await aut.phoneIdentityContactRules.list(handle); + const activeAllow = rules.some( + (rule) => + rule.matchTarget === driverNumber && rule.action === "allow" && rule.status === "active", + ); + if (!activeAllow) { + await aut.phoneIdentityContactRules.create(handle, { + action: PhoneRuleAction.ALLOW, + matchType: PhoneRuleMatchType.EXACT_NUMBER, + matchTarget: driverNumber, + }); + } +} + const tail = (s: string) => s.replace(/\D/g, "").slice(-10); describe.skipIf(!LIVE || !REAL_MODEL)("live voice", () => { @@ -47,6 +69,11 @@ describe.skipIf(!LIVE || !REAL_MODEL)("live voice", () => { const aut = client(AUT_KEY as string); const autPhone = await phoneOf(aut); + // Server-side contact rules run before the plugin or its local allow-all + // setting. Whitelisted smoke identities therefore need the driver allowed + // explicitly or the call is rejected before either media WS connects. + await ensureDriverAllowed(aut, st.number); + // Place the call to the agent, handing Inkbox the driver's own media WS. const call = await remote.calls.place({ toNumber: autPhone.number, From b07b41bc9667ea87e754a8c9a3ae3bb288c507da Mon Sep 17 00:00:00 2001 From: dimavrem22 Date: Wed, 15 Jul 2026 03:53:24 +0000 Subject: [PATCH 3/8] ci: diagnose stalled inbound voice calls --- tests/live/voice.test.ts | 49 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/tests/live/voice.test.ts b/tests/live/voice.test.ts index 6276b93..1772d8a 100644 --- a/tests/live/voice.test.ts +++ b/tests/live/voice.test.ts @@ -32,6 +32,31 @@ interface DriverState { number_id: string; handle: string; } + +const callSummary = (call: { + id: string; + direction: string; + status: string; + localPhoneNumber: string | null; + remotePhoneNumber: string; + clientWebsocketUrl: string | null; + useInkboxTts: boolean | null; + useInkboxStt: boolean | null; + hangupReason: string | null; + isBlocked: boolean; +}) => ({ + id: call.id, + direction: call.direction, + status: call.status, + localPhoneNumber: call.localPhoneNumber, + remotePhoneNumber: call.remotePhoneNumber, + clientWebsocketUrl: call.clientWebsocketUrl, + useInkboxTts: call.useInkboxTts, + useInkboxStt: call.useInkboxStt, + hangupReason: call.hangupReason, + isBlocked: call.isBlocked, +}); + function driverState(): DriverState { return JSON.parse(readFileSync(STATE_FILE, "utf-8")); } @@ -80,7 +105,29 @@ describe.skipIf(!LIVE || !REAL_MODEL)("live voice", () => { fromNumber: st.number, clientWebsocketUrl: st.ws_url, }); - const agentSaid = await waitTwoWayCall(remote, call.id, VOICE_TIMEOUT_MS); + console.info(`inbound call placed: ${JSON.stringify(callSummary(call))}`); + let agentSaid: string; + try { + agentSaid = await waitTwoWayCall(remote, call.id, VOICE_TIMEOUT_MS); + } catch (error) { + const [driverCall, autCalls, incomingAction, rules] = await Promise.all([ + remote.calls.get(call.id).catch((cause) => ({ error: String(cause) })), + aut.calls.list({ limit: 10 }).catch((cause) => [{ error: String(cause) }]), + aut.incomingCallAction.get().catch((cause) => ({ error: String(cause) })), + aut.phoneIdentityContactRules + .list((await aut.mailboxes.list())[0]?.emailAddress.split("@", 1)[0] ?? "") + .catch((cause) => [{ error: String(cause) }]), + ]); + throw new Error( + `${String(error)}; inbound diagnostics=${JSON.stringify({ + placedCall: callSummary(call), + driverCall, + autCalls, + incomingAction, + rules, + })}`, + ); + } expect(agentSaid.length).toBeGreaterThan(0); const mode = await autSpeechMode(aut, "inbound", st.number); From 4649c502fae8733c40e73902478ea3bee1ee3e5e Mon Sep 17 00:00:00 2001 From: dimavrem22 Date: Wed, 15 Jul 2026 03:57:20 +0000 Subject: [PATCH 4/8] ci: quarantine carrier-dependent inbound voice --- .github/workflows/live-stack.yml | 3 +++ .github/workflows/live-voice.yml | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/live-stack.yml b/.github/workflows/live-stack.yml index 16bc390..8b855e5 100644 --- a/.github/workflows/live-stack.yml +++ b/.github/workflows/live-stack.yml @@ -41,6 +41,9 @@ jobs: uses: ./.github/workflows/live-voice.yml with: orchestrated: true + # The inbound PSTN carrier currently rejects the call before it reaches + # the AUT. Keep that leg manually runnable; gate PRs on plugin-owned voice. + include_inbound: false secrets: inherit external-events: diff --git a/.github/workflows/live-voice.yml b/.github/workflows/live-voice.yml index e45cf2c..7e3f87f 100644 --- a/.github/workflows/live-voice.yml +++ b/.github/workflows/live-voice.yml @@ -20,11 +20,20 @@ on: required: false type: boolean default: false + include_inbound: + description: "Run the carrier-dependent inbound PSTN diagnostic" + required: false + type: boolean + default: false workflow_dispatch: inputs: timeout_s: description: "Seconds to wait for the call/transcript" default: "220" + include_inbound: + description: "Run the carrier-dependent inbound PSTN diagnostic" + type: boolean + default: true permissions: contents: read @@ -42,7 +51,7 @@ jobs: fail-fast: false max-parallel: 1 # legs share the AUT + driver identities → one at a time matrix: - scenario: [inbound_inkbox, outbound_realtime] + scenario: ${{ fromJSON(inputs.include_inbound && '["inbound_inkbox","outbound_realtime"]' || '["outbound_realtime"]') }} steps: - uses: actions/checkout@v7 From ae29e28bacbe67426744f519e6befbb6e08af152 Mon Sep 17 00:00:00 2001 From: dimavrem22 Date: Wed, 15 Jul 2026 05:54:35 +0000 Subject: [PATCH 5/8] ci: hang up live voice calls through API --- package-lock.json | 8 +-- package.json | 2 +- tests/live/voice.test.ts | 118 +++++++++++++++++++++++++-------------- 3 files changed, 81 insertions(+), 47 deletions(-) diff --git a/package-lock.json b/package-lock.json index df74eef..e6f58dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@inkbox/sdk": ">=0.4.19 <1.0.0", + "@inkbox/sdk": ">=0.4.24 <1.0.0", "@opencode-ai/sdk": ">=1.17.18 <1.19.0", "ws": "^8.21.0", "zod": "4.1.8" @@ -605,9 +605,9 @@ } }, "node_modules/@inkbox/sdk": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/@inkbox/sdk/-/sdk-0.4.19.tgz", - "integrity": "sha512-rfjNXMYXsZPd/JY5K9ynwZkIpjj9j7PNEjhvt1EKGZsS6VQ5vaj/b2/MEpzmr+/2uKIe37pEV7sbau/sRA/y7Q==", + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/@inkbox/sdk/-/sdk-0.4.24.tgz", + "integrity": "sha512-x5u4aXnGuB063Q91B5NVDKVLUQs+Atab4KQn7PZShC1jZ/apPsbLydJRDQfzZv7MVWtVa2QA/ybqG7TsVgd6aw==", "license": "MIT", "dependencies": { "@peculiar/x509": "^2.0.0", diff --git a/package.json b/package.json index bf51467..40b06a0 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "opencode": ">=1.15.0 <1.19.0" }, "dependencies": { - "@inkbox/sdk": ">=0.4.19 <1.0.0", + "@inkbox/sdk": ">=0.4.24 <1.0.0", "@opencode-ai/sdk": ">=1.17.18 <1.19.0", "ws": "^8.21.0", "zod": "4.1.8" diff --git a/tests/live/voice.test.ts b/tests/live/voice.test.ts index 1772d8a..4d4be0b 100644 --- a/tests/live/voice.test.ts +++ b/tests/live/voice.test.ts @@ -84,6 +84,31 @@ async function ensureDriverAllowed( const tail = (s: string) => s.replace(/\D/g, "").slice(-10); +async function hangupCall( + inkbox: ReturnType, + callId: string | undefined, +): Promise { + if (!callId) return; + try { + await inkbox.calls.hangup(callId); + return; + } catch (hangupError) { + let status = "unknown"; + for (let attempt = 0; attempt < 20; attempt += 1) { + try { + status = ((await inkbox.calls.get(callId)).status ?? "").toLowerCase(); + } catch { + status = "unknown"; + } + if (["completed", "canceled", "failed"].includes(status)) return; + await new Promise((resolve) => setTimeout(resolve, 500)); + } + throw new Error( + `failed to hang up live test call ${callId}; status=${JSON.stringify(status)}; error=${String(hangupError)}`, + ); + } +} + describe.skipIf(!LIVE || !REAL_MODEL)("live voice", () => { it.skipIf(SCENARIO !== "inbound_inkbox")( "inbound: driver calls, agent answers via Inkbox STT/TTS and replies", @@ -106,36 +131,40 @@ describe.skipIf(!LIVE || !REAL_MODEL)("live voice", () => { clientWebsocketUrl: st.ws_url, }); console.info(`inbound call placed: ${JSON.stringify(callSummary(call))}`); - let agentSaid: string; try { - agentSaid = await waitTwoWayCall(remote, call.id, VOICE_TIMEOUT_MS); - } catch (error) { - const [driverCall, autCalls, incomingAction, rules] = await Promise.all([ - remote.calls.get(call.id).catch((cause) => ({ error: String(cause) })), - aut.calls.list({ limit: 10 }).catch((cause) => [{ error: String(cause) }]), - aut.incomingCallAction.get().catch((cause) => ({ error: String(cause) })), - aut.phoneIdentityContactRules - .list((await aut.mailboxes.list())[0]?.emailAddress.split("@", 1)[0] ?? "") - .catch((cause) => [{ error: String(cause) }]), - ]); - throw new Error( - `${String(error)}; inbound diagnostics=${JSON.stringify({ - placedCall: callSummary(call), - driverCall, - autCalls, - incomingAction, - rules, - })}`, - ); + let agentSaid: string; + try { + agentSaid = await waitTwoWayCall(remote, call.id, VOICE_TIMEOUT_MS); + } catch (error) { + const [driverCall, autCalls, incomingAction, rules] = await Promise.all([ + remote.calls.get(call.id).catch((cause) => ({ error: String(cause) })), + aut.calls.list({ limit: 10 }).catch((cause) => [{ error: String(cause) }]), + aut.incomingCallAction.get().catch((cause) => ({ error: String(cause) })), + aut.phoneIdentityContactRules + .list((await aut.mailboxes.list())[0]?.emailAddress.split("@", 1)[0] ?? "") + .catch((cause) => [{ error: String(cause) }]), + ]); + throw new Error( + `${String(error)}; inbound diagnostics=${JSON.stringify({ + placedCall: callSummary(call), + driverCall, + autCalls, + incomingAction, + rules, + })}`, + ); + } + expect(agentSaid.length).toBeGreaterThan(0); + + const mode = await autSpeechMode(aut, "inbound", st.number); + expect(mode, "no answered inbound AUT call with the driver").toBeDefined(); + expect( + mode?.tts && mode?.stt, + `inbound should be Inkbox STT/TTS, got ${JSON.stringify(mode)}`, + ).toBe(true); + } finally { + await hangupCall(remote, call.id); } - expect(agentSaid.length).toBeGreaterThan(0); - - const mode = await autSpeechMode(aut, "inbound", st.number); - expect(mode, "no answered inbound AUT call with the driver").toBeDefined(); - expect( - mode?.tts && mode?.stt, - `inbound should be Inkbox STT/TTS, got ${JSON.stringify(mode)}`, - ).toBe(true); }, ); @@ -162,20 +191,25 @@ describe.skipIf(!LIVE || !REAL_MODEL)("live voice", () => { text: "Please call me right now by phone — give me a ring.", }); - const call = await pollUntil( - "agent call-back", - async () => (await inboundFromAut()).find((c) => !before.has(c.id)), - VOICE_TIMEOUT_MS, - ); - const agentSaid = await waitTwoWayCall(remote, call.id, VOICE_TIMEOUT_MS); - expect(agentSaid.length).toBeGreaterThan(0); - - const mode = await autSpeechMode(aut, "outbound", st.number); - expect(mode, "no answered outbound AUT call with the driver").toBeDefined(); - expect( - mode?.tts === false && mode?.stt === false, - `outbound should be Realtime, got ${JSON.stringify(mode)}`, - ).toBe(true); + let call: Awaited>[number] | undefined; + try { + call = await pollUntil( + "agent call-back", + async () => (await inboundFromAut()).find((c) => !before.has(c.id)), + VOICE_TIMEOUT_MS, + ); + const agentSaid = await waitTwoWayCall(remote, call.id, VOICE_TIMEOUT_MS); + expect(agentSaid.length).toBeGreaterThan(0); + + const mode = await autSpeechMode(aut, "outbound", st.number); + expect(mode, "no answered outbound AUT call with the driver").toBeDefined(); + expect( + mode?.tts === false && mode?.stt === false, + `outbound should be Realtime, got ${JSON.stringify(mode)}`, + ).toBe(true); + } finally { + await hangupCall(remote, call?.id); + } }, ); }); From b2ce9d8d90aa2ede3c0837e3a3781ec6e2edf0c7 Mon Sep 17 00:00:00 2001 From: dimavrem22 Date: Wed, 15 Jul 2026 05:56:08 +0000 Subject: [PATCH 6/8] ci: restore inbound voice coverage --- .github/workflows/live-stack.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/live-stack.yml b/.github/workflows/live-stack.yml index 8b855e5..1ab477a 100644 --- a/.github/workflows/live-stack.yml +++ b/.github/workflows/live-stack.yml @@ -43,7 +43,7 @@ jobs: orchestrated: true # The inbound PSTN carrier currently rejects the call before it reaches # the AUT. Keep that leg manually runnable; gate PRs on plugin-owned voice. - include_inbound: false + include_inbound: true secrets: inherit external-events: From 42bac3ac1cce587f6248092ed936ebdf9ffeb010 Mon Sep 17 00:00:00 2001 From: dimavrem22 Date: Wed, 15 Jul 2026 06:14:16 +0000 Subject: [PATCH 7/8] ci: fail fast on terminated voice calls --- tests/live/helpers.ts | 17 ++++++++++++++++- tests/live/voice.test.ts | 23 ++++++++++++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/tests/live/helpers.ts b/tests/live/helpers.ts index bfa9823..189770d 100644 --- a/tests/live/helpers.ts +++ b/tests/live/helpers.ts @@ -144,6 +144,7 @@ export async function waitTwoWayCall( callId: string, timeoutMs = TIMEOUT_MS, ): Promise { + const terminalFailureStatuses = new Set(["canceled", "failed"]); return pollUntil( "two-way call transcript", async () => { @@ -151,7 +152,21 @@ export async function waitTwoWayCall( agent: [], driver: [], })); - return agent.length > 0 && drv.length > 0 ? agent.join(" | ") : undefined; + if (agent.length > 0 && drv.length > 0) return agent.join(" | "); + + const call = await driver.calls.get(callId).catch(() => undefined); + const status = (call?.status ?? "").toLowerCase(); + if (terminalFailureStatuses.has(status)) { + throw new Error( + `two-way call ended before both parties spoke: ${JSON.stringify({ + status: call?.status, + hangupReason: call?.hangupReason, + startedAt: call?.startedAt, + endedAt: call?.endedAt, + })}`, + ); + } + return undefined; }, timeoutMs, ); diff --git a/tests/live/voice.test.ts b/tests/live/voice.test.ts index 4d4be0b..48fd558 100644 --- a/tests/live/voice.test.ts +++ b/tests/live/voice.test.ts @@ -14,6 +14,7 @@ import { AUT_KEY, autSpeechMode, client, + inboundTextsFrom, LIVE, phoneOf, pollUntil, @@ -186,6 +187,11 @@ describe.skipIf(!LIVE || !REAL_MODEL)("live voice", () => { ); const before = new Set((await inboundFromAut()).map((c) => c.id)); + const beforeTexts = new Set( + (await inboundTextsFrom(remote, st.number_id, autPhone.number)).map( + (message) => message.id, + ), + ); await remote.texts.send(st.number_id, { to: autPhone.number, text: "Please call me right now by phone — give me a ring.", @@ -193,11 +199,18 @@ describe.skipIf(!LIVE || !REAL_MODEL)("live voice", () => { let call: Awaited>[number] | undefined; try { - call = await pollUntil( - "agent call-back", - async () => (await inboundFromAut()).find((c) => !before.has(c.id)), - VOICE_TIMEOUT_MS, - ); + try { + call = await pollUntil( + "agent call-back", + async () => (await inboundFromAut()).find((c) => !before.has(c.id)), + VOICE_TIMEOUT_MS, + ); + } catch (error) { + const replies = (await inboundTextsFrom(remote, st.number_id, autPhone.number)).filter( + (message) => !beforeTexts.has(message.id), + ); + throw new Error(`${String(error)}; AUT SMS replies=${JSON.stringify(replies)}`); + } const agentSaid = await waitTwoWayCall(remote, call.id, VOICE_TIMEOUT_MS); expect(agentSaid.length).toBeGreaterThan(0); From b39287f9b1a3e5557cdbf7cd82a81246a14bb9e6 Mon Sep 17 00:00:00 2001 From: dimavrem22 Date: Wed, 15 Jul 2026 06:28:41 +0000 Subject: [PATCH 8/8] ci: clean up all calls created by live jobs --- tests/live/setup.ts | 108 ++++++++++++++++++++++++++++++++++++++++++ vitest.live.config.ts | 1 + 2 files changed, 109 insertions(+) create mode 100644 tests/live/setup.ts diff --git a/tests/live/setup.ts b/tests/live/setup.ts new file mode 100644 index 0000000..a75a865 --- /dev/null +++ b/tests/live/setup.ts @@ -0,0 +1,108 @@ +import { afterAll, beforeAll } from "vitest"; +import { AUT_KEY, client, phoneOf } from "./helpers.js"; + +const ENDED_CALL_STATUSES = new Set(["completed", "failed", "canceled"]); +const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +type InkboxClient = ReturnType; +type Call = Awaited>[number]; + +let aut: InkboxClient | undefined; +let localPhone = ""; +let baseline = new Set(); +let watcher: ReturnType | undefined; +let checking = false; +const attempted = new Set(); + +const statusOf = (call: Call) => (call.status ?? "").toLowerCase(); + +async function ownedCalls(): Promise> { + if (!aut) return new Map(); + const calls = await aut.calls.list({ limit: 100 }); + return new Map( + calls.filter((call) => call.localPhoneNumber === localPhone).map((call) => [call.id, call]), + ); +} + +async function hangup(call: Call): Promise { + if (!aut || ENDED_CALL_STATUSES.has(statusOf(call))) return undefined; + try { + await aut.calls.hangup(call.id); + return undefined; + } catch (error) { + try { + const current = await aut.calls.get(call.id); + if (ENDED_CALL_STATUSES.has(statusOf(current))) return undefined; + return `hangup=${String(error)}; status=${JSON.stringify(statusOf(current))}`; + } catch (getError) { + return `hangup=${String(error)}; get=${String(getError)}`; + } + } +} + +async function newLiveCalls(): Promise> { + const current = await ownedCalls(); + return new Map( + [...current].filter( + ([callId, call]) => !baseline.has(callId) && !ENDED_CALL_STATUSES.has(statusOf(call)), + ), + ); +} + +async function watchOnce(): Promise { + for (const [callId, call] of await newLiveCalls()) { + if (attempted.has(callId)) continue; + attempted.add(callId); + await hangup(call); + } +} + +async function finishNewCalls(): Promise { + const deadline = Date.now() + 12_000; + const errors = new Map(); + for (;;) { + const live = await newLiveCalls(); + if (live.size === 0) return; + for (const [callId, call] of live) { + const error = await hangup(call); + if (error) errors.set(callId, error); + } + if (Date.now() >= deadline) { + throw new Error( + `live-test calls remained active after API cleanup: states=${JSON.stringify( + Object.fromEntries([...live].map(([id, call]) => [id, statusOf(call)])), + )} errors=${JSON.stringify(Object.fromEntries(errors))}`, + ); + } + await delay(500); + } +} + +beforeAll(async () => { + if (!AUT_KEY) return; + aut = client(AUT_KEY); + localPhone = (await phoneOf(aut)).number; + baseline = new Set((await ownedCalls()).keys()); + + // Non-voice tests must never create calls. Stop an accidental model tool + // choice immediately; voice tests own their expected call ids directly. + if (!process.env.VOICE_SCENARIO) { + watcher = setInterval(() => { + if (checking) return; + checking = true; + void watchOnce() + .catch(() => undefined) + .finally(() => { + checking = false; + }); + }, 1000); + } +}, 30_000); + +afterAll(async () => { + if (!aut) return; + if (watcher) clearInterval(watcher); + while (checking) await delay(50); + await delay(1000); + await finishNewCalls(); +}, 30_000); diff --git a/vitest.live.config.ts b/vitest.live.config.ts index 799f0ed..79b77be 100644 --- a/vitest.live.config.ts +++ b/vitest.live.config.ts @@ -5,5 +5,6 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { include: ["tests/live/**/*.test.ts"], + setupFiles: ["tests/live/setup.ts"], }, });