Merge pull request 'Hotfix: recognize past-tense vomiting in deterministic chat safety' (#51) from timmy/48-vomiting-phrase-hotfix into main
All checks were successful
Quality gates / quality (push) Successful in 1m26s
All checks were successful
Quality gates / quality (push) Successful in 1m26s
This commit is contained in:
commit
ca31e6d38b
Binary file not shown.
|
Before Width: | Height: | Size: 314 KiB After Width: | Height: | Size: 311 KiB |
|
|
@ -122,7 +122,7 @@ await page.locator('#chat-message').fill('What pattern do you see?');
|
||||||
await tap('#send-chat', 500);
|
await tap('#send-chat', 500);
|
||||||
await page.getByText(/mostly Type 4/i).waitFor();
|
await page.getByText(/mostly Type 4/i).waitFor();
|
||||||
await caption('Hermes keeps credentials, tools, and session continuity server-side. Photos stay out of chat.', 1600);
|
await caption('Hermes keeps credentials, tools, and session continuity server-side. Photos stay out of chat.', 1600);
|
||||||
await page.locator('#chat-message').fill('I have rectal bleeding');
|
await page.locator('#chat-message').fill('I barfed');
|
||||||
await tap('#send-chat', 450);
|
await tap('#send-chat', 450);
|
||||||
await page.getByText(/Pause and get medical help/i).waitFor();
|
await page.getByText(/Pause and get medical help/i).waitFor();
|
||||||
if (hermesCalls !== 1) throw new Error('Urgent chat must be intercepted before Hermes');
|
if (hermesCalls !== 1) throw new Error('Urgent chat must be intercepted before Hermes');
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ const URGENT_TEXT_PATTERNS = Object.freeze([
|
||||||
['blood', /\b(?:rectal bleeding|bleeding from (?:the )?(?:rectum|bottom)|blood(?:y)? (?:in|on|with) (?:my |the )?(?:stool|poop|bowel movement)|(?:stool|poop) (?:has|contains|with) blood)\b/i],
|
['blood', /\b(?:rectal bleeding|bleeding from (?:the )?(?:rectum|bottom)|blood(?:y)? (?:in|on|with) (?:my |the )?(?:stool|poop|bowel movement)|(?:stool|poop) (?:has|contains|with) blood)\b/i],
|
||||||
['blackOrDarkRed', /\b(?:(?:black|dark[- ]?red) (?:stool|poop|bowel movement)|(?:stool|poop|bowel movement) (?:is|looks?) (?:black|dark[- ]?red))s?\b/i],
|
['blackOrDarkRed', /\b(?:(?:black|dark[- ]?red) (?:stool|poop|bowel movement)|(?:stool|poop|bowel movement) (?:is|looks?) (?:black|dark[- ]?red))s?\b/i],
|
||||||
['severePain', /\b(?:severe|constant|unrelenting) (?:abdominal|stomach|belly) pain\b/i],
|
['severePain', /\b(?:severe|constant|unrelenting) (?:abdominal|stomach|belly) pain\b/i],
|
||||||
['vomiting', /\b(?:vomit(?:ing|ed)?|throwing up)\b/i],
|
['vomiting', /\b(?:vomit(?:ing|ed|s)?|throw(?:ing|s)? up|threw up|thrown up|puk(?:e|ed|ing|es)|barf(?:ed|ing|s)?|upchuck(?:ed|ing|s)?|toss(?:ed|ing|es)? (?:my|your|his|her|our|their|the) cookies|los(?:e|t|ing|es) (?:my|your|his|her|our|their|the) lunch|(?:i|we|you|he|she|they|someone) (?:(?:have|had|just|already|recently|am|are|was|were|kept) )?(?:hurl(?:s|ed|ing)?|spew(?:s|ed|ing)?)(?=\s*(?:[.!?]|$|again\b|twice\b|all night\b))|emesis)\b/i],
|
||||||
['fever', /\bfever(?:ish)?\b/i],
|
['fever', /\bfever(?:ish)?\b/i],
|
||||||
['cannotPassGas', /\b(?:cannot|can['’]?t|cant|unable to|not able to) pass gas\b/i],
|
['cannotPassGas', /\b(?:cannot|can['’]?t|cant|unable to|not able to) pass gas\b/i],
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,35 @@ test('detects common urgent symptom language without matching unrelated blood wo
|
||||||
'My stool is black',
|
'My stool is black',
|
||||||
'I have severe stomach pain',
|
'I have severe stomach pain',
|
||||||
'I am throwing up and have a fever',
|
'I am throwing up and have a fever',
|
||||||
|
'I threw up',
|
||||||
|
'I puked twice',
|
||||||
|
'I am barfing',
|
||||||
|
'I barfed',
|
||||||
|
'I hurled',
|
||||||
|
'She hurls',
|
||||||
|
'I am upchucking',
|
||||||
|
'I spewed',
|
||||||
|
'She spews',
|
||||||
|
'I tossed my cookies',
|
||||||
|
'She tosses her cookies',
|
||||||
|
'He tossed his cookies',
|
||||||
|
'Someone is tossing their cookies',
|
||||||
|
'I lost my lunch',
|
||||||
|
'She loses her lunch',
|
||||||
|
'He lost his lunch',
|
||||||
|
'Someone is losing their lunch',
|
||||||
|
'I have emesis',
|
||||||
'I am unable to pass gas',
|
'I am unable to pass gas',
|
||||||
]) assert.equal(detectUrgentText(message).urgent, true, message);
|
]) assert.equal(detectUrgentText(message).urgent, true, message);
|
||||||
assert.equal(detectUrgentText('My blood pressure was checked').urgent, false);
|
assert.equal(detectUrgentText('My blood pressure was checked').urgent, false);
|
||||||
|
for (const nonVomiting of [
|
||||||
|
'She hurled the javelin across the field.',
|
||||||
|
'He hurls insults when angry.',
|
||||||
|
'They are hurling rocks at the wall.',
|
||||||
|
'He spewed hateful rhetoric.',
|
||||||
|
'The volcano spews ash.',
|
||||||
|
'The pipe is spewing water.',
|
||||||
|
]) assert.equal(detectUrgentText(nonVomiting).urgent, false, nonVomiting);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('detects urgent flags or language in confirmed ledger context', () => {
|
test('detects urgent flags or language in confirmed ledger context', () => {
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,10 @@ test('authoritative chat service intercepts urgent message and ledger symptoms b
|
||||||
await service.unlock({ origin, accessCode: 'test-agent-access-code-2026' });
|
await service.unlock({ origin, accessCode: 'test-agent-access-code-2026' });
|
||||||
|
|
||||||
const messageResult = await service.chat({ origin, cookieToken: 'safety-cookie', payload: { message: 'I have rectal bleeding', ledger: [] } });
|
const messageResult = await service.chat({ origin, cookieToken: 'safety-cookie', payload: { message: 'I have rectal bleeding', ledger: [] } });
|
||||||
|
const pastTenseVomitingResult = await service.chat({ origin, cookieToken: 'safety-cookie', payload: { message: 'I threw up', ledger: [] } });
|
||||||
|
const slangVomitingResult = await service.chat({ origin, cookieToken: 'safety-cookie', payload: { message: 'I barfed', ledger: [] } });
|
||||||
|
const possessiveIdiomResult = await service.chat({ origin, cookieToken: 'safety-cookie', payload: { message: 'She tosses her cookies', ledger: [] } });
|
||||||
|
const thirdPersonVomitingResult = await service.chat({ origin, cookieToken: 'safety-cookie', payload: { message: 'She hurls', ledger: [] } });
|
||||||
const ledgerResult = await service.chat({ origin, cookieToken: 'safety-cookie', payload: {
|
const ledgerResult = await service.chat({ origin, cookieToken: 'safety-cookie', payload: {
|
||||||
message: 'What does my journal show?',
|
message: 'What does my journal show?',
|
||||||
ledger: [{ bristolType: 4, color: 'brown', note: '', symptoms: { cannotPassGas: true } }],
|
ledger: [{ bristolType: 4, color: 'brown', note: '', symptoms: { cannotPassGas: true } }],
|
||||||
|
|
@ -138,7 +142,7 @@ test('authoritative chat service intercepts urgent message and ledger symptoms b
|
||||||
} });
|
} });
|
||||||
|
|
||||||
assert.equal(calls.length, 0);
|
assert.equal(calls.length, 0);
|
||||||
for (const result of [messageResult, ledgerResult, noteResult]) {
|
for (const result of [messageResult, pastTenseVomitingResult, slangVomitingResult, possessiveIdiomResult, thirdPersonVomitingResult, ledgerResult, noteResult]) {
|
||||||
assert.equal(result.safetyOverride, true);
|
assert.equal(result.safetyOverride, true);
|
||||||
assert.match(result.reply, /medical help/i);
|
assert.match(result.reply, /medical help/i);
|
||||||
assert.doesNotMatch(result.reply, /unsafe upstream/i);
|
assert.doesNotMatch(result.reply, /unsafe upstream/i);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ test('release demo visibly explains the CI-protected browser path without overst
|
||||||
assert.match(demo, /AI may suggest visible form, broad color, and image quality — never symptoms or diagnosis/);
|
assert.match(demo, /AI may suggest visible form, broad color, and image quality — never symptoms or diagnosis/);
|
||||||
assert.match(demo, /Hermes Agent connected/);
|
assert.match(demo, /Hermes Agent connected/);
|
||||||
assert.match(demo, /Photos stay out of chat/);
|
assert.match(demo, /Photos stay out of chat/);
|
||||||
assert.match(demo, /I have rectal bleeding/);
|
assert.match(demo, /I barfed/);
|
||||||
assert.match(demo, /Urgent language is intercepted deterministically before Hermes/);
|
assert.match(demo, /Urgent language is intercepted deterministically before Hermes/);
|
||||||
assert.match(demo, /SLEEK\. SIMPLE\.<br>HERMES-POWERED\./);
|
assert.match(demo, /SLEEK\. SIMPLE\.<br>HERMES-POWERED\./);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ assert.equal(chatRequest.message, 'What pattern do you see?');
|
||||||
assert.ok(Array.isArray(chatRequest.ledger));
|
assert.ok(Array.isArray(chatRequest.ledger));
|
||||||
assert.equal(JSON.stringify(chatRequest).includes('photoDataUrl'), false, 'photos never enter chat context');
|
assert.equal(JSON.stringify(chatRequest).includes('photoDataUrl'), false, 'photos never enter chat context');
|
||||||
const previousRequest = chatRequest;
|
const previousRequest = chatRequest;
|
||||||
await page.locator('#chat-message').fill('I am unable to pass gas');
|
await page.locator('#chat-message').fill('I barfed');
|
||||||
await page.locator('#send-chat').click();
|
await page.locator('#send-chat').click();
|
||||||
await page.waitForSelector('.bubble.timmy >> text=Pause and get medical help');
|
await page.waitForSelector('.bubble.timmy >> text=Pause and get medical help');
|
||||||
assert.equal(chatRequest, previousRequest, 'urgent language must be intercepted before the Hermes request');
|
assert.equal(chatRequest, previousRequest, 'urgent language must be intercepted before the Hermes request');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user