Files
gods-eye-view/src/voice/aircraftIdentityNarration.test.mjs
T
Bilawal Sidhu a19897ad15 feat(voice): voice core — honest layer reach, spoken summary with on-screen card, narration, point-and-ask
- Voice layer manifest (src/voice/layerManifest.js) generates the layer enums,
  aliases and analyst field hints; a test fails when a catalog layer has no entry.
- analyst_query validates fields, operators, units, values and scopes, counts up
  to 250,000 loaded records per layer, reports complete/partial/unanswered, tells
  LAYER_OFF / NOT_READY / FEED_UNAVAILABLE apart from zero, refuses mismatched
  follow-ups and never lets a superseded query replace follow-up memory. Seven
  more layers expose analyst records.
- Tool results carry a code-built say line and a display card; the voice card in
  the mic control shows captions, plan steps and the result
  (src/voice/resultDisplay.js is the one adapter). Slow tools narrate their
  current step out of band; holding Space while the assistant talks cuts it off.
  Turn spans go to the debug log; caption transcription is metered; the log
  omits what was said unless GEV_VOICE_LOG_CONTENT=1.
- Point and ask: the Space-down pointer snapshot resolves "this", "that one" and
  "around here" through 'pointer' targets, and "the second one" through
  referent:n from the last numbered list.
- Prompt states each policy once (instructions + tools 48.5k -> 46.7k chars).
- Voice benchmark harness (scripts/qa-voice-bench.mjs, scripts/voice-bench/)
  shares its phrase table and grading with scripts/qa-voice-routing.mjs.
2026-09-28 21:19:23 -05:00

53 lines
1.8 KiB
JavaScript

import { readRealtimeSource } from '../testSupport/readRealtimeSource.mjs';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
import { responseInstructionForToolResult } from './realtimeProtocol.js';
import { attachVoiceResult } from './speech.js';
const voiceConfig = readFileSync(
new URL('../../server/providers/openai/instructions.js', import.meta.url),
'utf8',
);
const realtime = readRealtimeSource();
test('aircraft identity narration acknowledges missing enrichment', () => {
const start = voiceConfig.indexOf('\'For "what is this aircraft?"');
assert.ok(start >= 0, 'aircraft identity honesty instruction is missing');
const text = voiceConfig.slice(start, voiceConfig.indexOf('\n', start));
assert.match(text, /get_entity_context/);
assert.match(
text,
/callsign, operator, type and route from returned fields only/,
);
assert.match(text, /airport codes verbatim/);
assert.match(text, /Never infer them from the callsign/);
assert.match(
text,
/Other questions about the selection answer from its properties/,
);
// The follow-up now speaks the code-built say line, which always covers
// operator, type and route (see speech.test.mjs for the field rules).
const followup = responseInstructionForToolResult(
attachVoiceResult('get_entity_context', {
ok: true,
action: 'get_entity_context',
selected: {
id: 'a',
layerId: 'flights',
properties: { callsign: 'UAL1' },
},
}),
);
assert.match(
followup,
/asked what the aircraft is, speak its identityLine and add nothing/,
);
assert.match(
followup,
/any other question \(altitude, speed, registration\), answer from selected\.properties/,
);
assert.doesNotMatch(realtime, /Operator details are unavailable/);
});