fix(voice): keep a stopped round's actions and follow-up memory

A reply stopped partway through a round now keeps the actions that ran,
answering the calls it never reached as skipped, so the history stays
paired for both providers. A failed typed turn no longer resets the
pointer, referents and follow-up memory, since its conversation continues.

The server now tells a rejected key (401, or Gemini's 400 with reason
API_KEY_INVALID, matched exactly), denied access (403) and an account out
of credit (OpenAI's 429 insufficient_quota) apart; each cools down for a
minute and gets its own message in the chat.
This commit is contained in:
manjunath22466 committed 2026-11-03 22:34:21 +05:30
1 parent 9d4d7b3ee0
commit 0a8d824124
19 files changed
+238 -51

No files matched your search

+18
View File
@@ -472,6 +472,24 @@ test('native connecting phases retain output observation; idle starts before fir
assert.equal(adapter.stops, 1);
});
test('a failed typed turn keeps follow-up memory; a stop or voice error clears it', async (t) => {
let resets = 0;
const runner = async () => ({ ok: true });
runner.resetConversation = () => resets++;
const f = mounted(t, { runner });
const adapter = f.instances[0];
adapter.getStartOptions = () => ({ textOnly: true });
await f.commands.session.start({ textOnly: true });
adapter.hooks.emit({ type: 'state', state: 'error', detail: 'Busy' });
assert.equal(resets, 0, 'the typed conversation continues');
f.commands.session.stop();
assert.equal(resets, 1);
adapter.getStartOptions = () => ({});
await f.commands.session.start();
adapter.hooks.emit({ type: 'state', state: 'error', detail: 'Lost' });
assert.equal(resets, 2);
});
test('the voice inactivity timeout never ends a typed chat session', async (t) => {
const f = mounted(t, { native: true });
const adapter = f.instances[0];