Skip to content

@W-24333476 - Fix welcome-leak race via position-based classifier - #16

Open
sf-samarth-urs wants to merge 1 commit into
mainfrom
feature/w-24333476-welcome-race-fix
Open

sf-samarth-urs wants to merge 1 commit into
mainfrom
feature/w-24333476-welcome-race-fix

Conversation

@sf-samarth-urs

Copy link
Copy Markdown
Collaborator

Summary

Fixes the race where the agent's auto-welcome greeting ("Welcome to NTO! I'm your AI shopping assistant...") leaks into the widget as if it were the answer to the shopper's question — visible in the opener-pill flow on zyss-001 PDPs.

Root cause

The previous timing-based classifier (hasUserSent + welcomeDropped + 5s gate) failed on answer-before-welcome:

  1. Shopper clicks an opener pill → sendMessage() starts the handshake, opens the 5s welcome-gate.
  2. Handshake exceeds 5s → gate times out, user POST goes out, hasUserSent flips true on sendMessage() resolve.
  3. SCRT2 delivers the real answer first (fast agent / server-side ordering).
  4. Classifier sees hasUserSent=true, welcomeDropped=false → drop-welcome (post-send late-welcome branch) → answer swallowed.
  5. Welcome arrives next → classifier sees welcomeDropped=true → render-answer → welcome renders as the answer.

That matches the W-24333476 repro screenshot exactly: shopper clicked "Can I machine wash this?" → saw the greeting as the response.

Fix — position-based classifier

Replace timing + ordering heuristics with a server-authoritative cutoff: the EndUser echo's transcriptedTimestamp.

  • SDK (sdk/client.ts) — stop silently dropping EndUser CONVERSATION_MESSAGE entries. Emit a new user_echo event carrying { messageId, timestamp }. sendMessage() now returns the client-generated message id for correlation.
  • Provider (WidgetProvider.tsx) — hold echoCutoffRef (ISO-8601) + a pre-echo message buffer. Chatbot entries arriving before the echo are buffered, not dropped. On echo, buffer drains through the classifier.
  • Tracker (welcome-tracker.ts) — pure classifier: Chatbot timestamp ≤ cutoff → welcome (drop); > cutoff → answer (render). Server timestamps, so no clock skew; ISO-8601 UTC strings sort lexicographically.

What's deleted

  • src/provider/welcome-gate.ts + its test — the send-side await is no longer needed. Correctness is now entirely on the receive side.
  • hasUserSentRef, welcomeDroppedRef, welcomeTimeoutRef state — subsumed by echoCutoffRef.

What's kept

  • A 5s UI-liveness fallback that re-enables Send on genuinely silent agents. Independent of welcome correctness (buffered entries still classify correctly when the echo eventually lands).
  • Session-restore path — pre-sets the cutoff to a sentinel 0000-01-01T00:00:00.000Z so replayed historical entries render as answers.

Diff shape

 src/provider/WidgetProvider.tsx        | +64 / -234
 src/provider/welcome-gate.ts           | deleted (-80)
 src/provider/welcome-tracker.ts        | -72 / +83 (full rewrite, smaller)
 src/sdk/client.ts                      | +11 / -3
 src/sdk/types.ts                       | +16 / -2
 tests/provider/welcome-gate.test.ts    | deleted (-181)
 tests/provider/welcome-tracker.test.ts | -92 / +50 (rewrite, position-based)
 tests/sdk/client.test.ts               | +12 / -7

Net: -431 lines, same guarantees.

Test plan

  • npm test -- --run → 175 tests pass, 12 files
  • npx tsc --noEmit → clean
  • New regression test in welcome-tracker.test.ts covers the answer-before-welcome window explicitly
  • Manual verification on zyss-001 after cartridge rebuild — pill-click flow renders the real answer, no welcome bubble appears
  • Manual verification: cold incognito load → welcome still suppressed (buffered pre-echo entries drain correctly)
  • Manual verification: slow handshake (>5s) → Send re-enables (UI-liveness fallback), subsequent send still classifies correctly

Related

  • W-24333476 — Inline PDP opener question pills (parent WI, this is a follow-up fix)
  • Screenshot repro: /Users/surs/pwa-kit/.agents/artifacts/Unexpected-WelcomeMessage-SFRA.png

The timing-based welcome-tracker (hasUserSent + welcomeDropped + 5s gate)
failed on answer-before-welcome. When the handshake exceeded the gate's
5s timeout, the user POST went out, hasUserSent flipped on sendMessage()
resolve, and if the real answer arrived before the welcome it got
labelled "late welcome" and dropped — while the actual welcome then
rendered as the answer. Reproduced via opener-pill click on PDP (see
screenshot in W-24333476).

Replace with a position-based classifier anchored on the server's
EndUser-echo timestamp:

- SDK emits a new user_echo event when SCRT2 echoes an EndUser
  CONVERSATION_MESSAGE, carrying entry.identifier + transcriptedTimestamp.
  Previously these entries were silently dropped.
- WidgetProvider holds echoCutoffRef (ISO-8601 server timestamp) and a
  pre-echo message buffer. Chatbot entries arriving before the echo are
  buffered, not dropped. On echo arrival the buffer drains through the
  classifier.
- Chatbot timestamp <= echo cutoff → welcome (drop).
- Chatbot timestamp > echo cutoff → answer (render).
- Server-generated timestamps, so no clock skew; ISO-8601 UTC strings
  sort lexicographically.

Removed:
- welcome-gate.ts (and its test file) — the send-side await is no longer
  needed; correctness is on the receive side.
- hasUserSentRef, welcomeDroppedRef, welcomeTimeoutRef — subsumed by
  echoCutoffRef.

Kept:
- A 5s UI-liveness fallback that re-enables Send on silent agents.
  Independent of correctness.

sendMessage() now returns the client-generated message id so callers can
correlate a specific send with its user_echo event if they need to.

Tests: 175 passing; welcome-tracker.test.ts rewritten for the position
model with an explicit regression test for the answer-before-welcome
window.
@sf-samarth-urs
sf-samarth-urs force-pushed the feature/w-24333476-welcome-race-fix branch from a88c75e to 88bd8ee Compare October 8, 2026 19:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant