Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions ops/agent-phone.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
15 changes: 15 additions & 0 deletions test/agent-phone.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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: '' });
Expand Down
Loading