Keep the analyst's last answer when the voice runner moves to another engine

A runner retired by an engine switch ends its in-flight writes but leaves the shared follow-up memory for the next engine's runner; ending the voice conversation still forgets it.
This commit is contained in:
German Khokhlov committed 2026-11-03 19:41:12 +04:00
1 parent 8e1a3bab3e
commit c34a2c38fc
4 files changed
+45 -4

No files matched your search

+8 -3
View File
@@ -1554,10 +1554,15 @@ export function createGevActionRunner({
conversation++;
analystEngine?.reset();
};
/** Voice was removed: nothing may run or write again. */
runVoiceAction.dispose = () => {
/**
* Voice was removed, or this runner's globe left (`keepMemory`: a host
* that shares the analyst's memory with the next runner keeps the last
* answer): nothing may run or write again.
*/
runVoiceAction.dispose = ({ keepMemory = false } = {}) => {
if (disposed) return;
runVoiceAction.resetConversation();
conversation++;
if (!keepMemory) analystEngine?.reset();
disposed = true;
};
return runVoiceAction;
+32
View File
@@ -352,3 +352,35 @@ test('the cloud prompt states precedence and caveat speech once', () => {
/"approximately"\/"roughly" for a tool number/,
);
});
test('an engine switch keeps the last answer for a follow-up on the next engine', async () => {
const { createSceneRunner } = await import('./sceneRunner.js');
const analystMemory = { lastResult: null };
const layerEnabledAt = new Map();
const run = createSceneRunner({
build: () =>
harness({ runnerOptions: { analystMemory, layerEnabledAt } }).runner,
});
run.attach({ engine: { kind: 'cesium' } });
const first = await run('analyst_query', {
layers: ['flights'],
scope: { kind: 'anywhere' },
});
assert.equal(first.ok, true);
// The real runner of the outgoing engine is disposed by the switch.
run.detach();
run.attach({ engine: { kind: 'navara' } });
const followUp = await run('analyst_query', {
layers: ['flights'],
followUp: true,
});
assert.equal(followUp.ok, true, JSON.stringify(followUp));
assert.equal(followUp.engine, 'navara');
// The session ending still forgets it.
run.resetConversation();
const afterReset = await run('analyst_query', {
layers: ['flights'],
followUp: true,
});
assert.equal(afterReset.code, 'NO_RESULT_CONTEXT');
});
+3 -1
View File
@@ -139,7 +139,9 @@ export function createSceneRunner({
const previous = inner;
inner = null;
kind = null;
previous?.dispose?.();
// The conversation (the analyst's last answer) continues on the next
// engine: only this runner ends.
previous?.dispose?.({ keepMemory: true });
};
/** The page's switch settled: waiting calls look again. */
run.wake = wake;
+2
View File
@@ -40,6 +40,7 @@ export function harness({
flights = null,
pointer = null,
on = ['flights'],
runnerOptions = {},
} = {}) {
const modules = {
flights: flights || {
@@ -97,6 +98,7 @@ export function harness({
return true;
},
},
...runnerOptions,
});
return {
runner,