diff --git a/artifacts/sleek-hermes-chat-mobile.png b/artifacts/sleek-hermes-chat-mobile.png index 40563f3..cfd08ea 100644 Binary files a/artifacts/sleek-hermes-chat-mobile.png and b/artifacts/sleek-hermes-chat-mobile.png differ diff --git a/scripts/record_release_demo.mjs b/scripts/record_release_demo.mjs index afc099d..343c41a 100644 --- a/scripts/record_release_demo.mjs +++ b/scripts/record_release_demo.mjs @@ -122,7 +122,7 @@ await page.locator('#chat-message').fill('What pattern do you see?'); await tap('#send-chat', 500); 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 page.locator('#chat-message').fill('I have rectal bleeding'); +await page.locator('#chat-message').fill('I barfed'); await tap('#send-chat', 450); await page.getByText(/Pause and get medical help/i).waitFor(); if (hermesCalls !== 1) throw new Error('Urgent chat must be intercepted before Hermes'); diff --git a/src/domain.js b/src/domain.js index 4e5b42e..ef6fadc 100644 --- a/src/domain.js +++ b/src/domain.js @@ -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], ['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], - ['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) (?:(?:have|had|just|already|recently|am|are|was|were|kept) )?(?:hurl(?:ed|ing)?|spew(?:ed|ing)?)(?=\s*(?:[.!?]|$|again\b|twice\b|all night\b))|emesis)\b/i], ['fever', /\bfever(?:ish)?\b/i], ['cannotPassGas', /\b(?:cannot|can['’]?t|cant|unable to|not able to) pass gas\b/i], ]); diff --git a/tests/domain.test.js b/tests/domain.test.js index 8ac7646..87da74b 100644 --- a/tests/domain.test.js +++ b/tests/domain.test.js @@ -50,9 +50,33 @@ test('detects common urgent symptom language without matching unrelated blood wo 'My stool is black', 'I have severe stomach pain', 'I am throwing up and have a fever', + 'I threw up', + 'I puked twice', + 'I am barfing', + 'I barfed', + 'I hurled', + 'I am upchucking', + 'I spewed', + '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', ]) assert.equal(detectUrgentText(message).urgent, true, message); 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', () => { diff --git a/tests/hermes-agent-service.test.js b/tests/hermes-agent-service.test.js index 75d215e..a21706f 100644 --- a/tests/hermes-agent-service.test.js +++ b/tests/hermes-agent-service.test.js @@ -128,6 +128,9 @@ test('authoritative chat service intercepts urgent message and ledger symptoms b 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 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 ledgerResult = await service.chat({ origin, cookieToken: 'safety-cookie', payload: { message: 'What does my journal show?', ledger: [{ bristolType: 4, color: 'brown', note: '', symptoms: { cannotPassGas: true } }], @@ -138,7 +141,7 @@ test('authoritative chat service intercepts urgent message and ledger symptoms b } }); assert.equal(calls.length, 0); - for (const result of [messageResult, ledgerResult, noteResult]) { + for (const result of [messageResult, pastTenseVomitingResult, slangVomitingResult, possessiveIdiomResult, ledgerResult, noteResult]) { assert.equal(result.safetyOverride, true); assert.match(result.reply, /medical help/i); assert.doesNotMatch(result.reply, /unsafe upstream/i); diff --git a/tests/release-demo.test.js b/tests/release-demo.test.js index 21f69f2..44b8302 100644 --- a/tests/release-demo.test.js +++ b/tests/release-demo.test.js @@ -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, /Hermes Agent connected/); 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, /SLEEK\. SIMPLE\.
HERMES-POWERED\./); }); diff --git a/tests/sleek-chat.acceptance.mjs b/tests/sleek-chat.acceptance.mjs index c3c2e07..e8a2507 100644 --- a/tests/sleek-chat.acceptance.mjs +++ b/tests/sleek-chat.acceptance.mjs @@ -38,7 +38,7 @@ assert.equal(chatRequest.message, 'What pattern do you see?'); assert.ok(Array.isArray(chatRequest.ledger)); assert.equal(JSON.stringify(chatRequest).includes('photoDataUrl'), false, 'photos never enter chat context'); 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.waitForSelector('.bubble.timmy >> text=Pause and get medical help'); assert.equal(chatRequest, previousRequest, 'urgent language must be intercepted before the Hermes request');