diff --git a/ops/agent-phone.mjs b/ops/agent-phone.mjs index 25f3b5c..9f68737 100755 --- a/ops/agent-phone.mjs +++ b/ops/agent-phone.mjs @@ -457,10 +457,26 @@ export function coHostSpeech(pin, agent) { * all of the time. So compare: words that are in what the co-host is saying are * its echo; words that are not are a person, and a person always gets through. */ +/** + * Every multi-word spelling of an agent's name, longest first: the transcriber + * splits our made-up names into two words ("RiotCoder" comes back as "Riot + * Coder", "ChaosPunk" as "chaos punk"), and two tokens never match the one the + * host actually said. Only the multi-word aliases are collapsed - single generic + * ones like "security" or "chaos" would merge words the guest is entitled to use + * himself. + */ +const SPLIT_NAMES = Object.entries(NAME_ALIASES) + .flatMap(([agent, names]) => names.filter((n) => n.includes(' ')).map((n) => [n, agent.replace(/0/g, 'o')])) + .sort((a, b) => b[0].length - a[0].length); +/** One spelling of a name per token, so "Riot Coder" and "RiotCoder" are the same word. */ +function oneWordNames(text) { + let t = String(text).toLowerCase().replace(/0/g, 'o'); + for (const [alias, agent] of SPLIT_NAMES) t = t.split(alias).join(agent); + return t; +} export function echoesFloor(heard, floorText) { const words = (text) => - String(text) - .toLowerCase() + oneWordNames(text) .replace(/[^a-z0-9' ]+/g, ' ') .split(/\s+/) .filter((w) => w.length > 2); diff --git a/test/agent-phone.test.mjs b/test/agent-phone.test.mjs index b43d9f7..3facc87 100644 --- a/test/agent-phone.test.mjs +++ b/test/agent-phone.test.mjs @@ -282,6 +282,21 @@ test('each agent keeps a few recent lines, not the whole call', () => { worker.releaseFloor(pin, 'riotcoder'); }); +test('a host saying its own name is not the guest, however the transcriber spells it', () => { + // The rehearsal: riotcoder said "I'm RiotCoder, Anthony's AI assistant and + // co-host" and the transcriber wrote "I'm Riot Coder." Two tokens never + // matched the one word the host said, so the overlap fell to 1/3 and the host + // introducing itself was logged as `room:` - the guest. + const floor = "I'm RiotCoder, Anthony's AI assistant and co-host,"; + assert.equal(worker.echoesFloor("I'm Riot Coder.", floor), true); + assert.equal(worker.echoesFloor('Hello. I am Chaos Punk, an AI assistant and co host.', "Hello, I'm ChaosPunk, an AI assistant and co-host."), true); + // 0 read as o, both ways. + assert.equal(worker.echoesFloor('chaospunk here', 'cha0spunk here'), true); + // The guest still gets through, and a generic word is not a name. + assert.equal(worker.echoesFloor('Can you hear me?', floor), false); + assert.equal(worker.echoesFloor('We run our own security and nothing else', 'rootkey handles the scanning for us'), false); +}); + test('a model that says PASS is saying nothing, not reading the word out', () => { assert.deepEqual(worker.parseTurn('SAY: PASS'), { kind: 'pass', text: '' }); assert.deepEqual(worker.parseTurn('PASS'), { kind: 'pass', text: '' });