Skip to content

fix(twilio): mark a cut-off reply where playback stopped, and not after a timeout - #1474

Merged
yileicn merged 2 commits into
SciSharp:masterfrom
yuyixg:fix/conversation-relay-interrupt-review
Nov 2, 2026
Merged

yileicn merged 2 commits into
SciSharp:masterfrom
yuyixg:fix/conversation-relay-interrupt-review

Conversation

@yuyixg

@yuyixg yuyixg commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #1467, addressing its two review findings.

The problems

  1. The mark kept more than the caller heard. Text goes out to Twilio faster than it is spoken, so when the caller cut in, what had been sent could run well past what was played. The history then took the sent part as heard, and a question the caller never heard was still treated as asked: the very thing feat(twilio): keep a reply the caller cut off as the part they heard #1467 set out to stop.
  2. A timed-out reply could be marked as cut in on. After a turn times out, FailAsync drops the stream and plays the apology, but _isProcessing and _streamedThisTurn stay set. A caller talking over the apology set _cutInMidReply, and the orphaned turn's reply was stored as one the caller interrupted.

What this does

  • The interrupt handler reads utteranceUntilInterrupt, where Twilio says playback stopped. MarkWhatTheCallerCutOff cuts there when it is the start of what went out, and where the sending stopped otherwise (field missing, or the cut-in landed on something else, such as a filler); that fallback is logged.
  • An interrupt once the stream was already dropped, or after the session ended, no longer marks the turn.

A side effect of (1): a reply fully sent but not yet stored when the caller cuts in used to be left unmarked (nothing unsent), and is now marked where playback stopped.

Note

Twilio's docs show utteranceUntilInterrupt only in an example (a partial sentence), without a formal definition. The cut relies on it being the part played before the interruption; if it is not, the fallback keeps today's behaviour.

Testing

Covered downstream, where the relay session runs against a fake WebSocket:

  • a cut-in with utteranceUntilInterrupt matching the sent text stores only the heard part plus the mark;
  • one that does not match falls back to the sent text;
  • a caller talking over the timeout apology leaves the orphaned reply unmarked.

The first and third fail on current master and pass with this change. There is no test project here that covers the Twilio plugin.

🤖 Generated with Claude Code

…er a timeout

Addresses the review on SciSharp#1467.

- Text goes out faster than it is spoken, so what was sent when the caller
  cut in is more than they heard. The interrupt's utteranceUntilInterrupt
  says where playback stopped; the history mark now cuts there when it is
  the start of what went out, and where the sending stopped otherwise.
- An interrupt once the stream was already dropped (the turn timed out and
  the apology is playing) or the session ended no longer marks the
  orphaned turn's reply as cut in on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@qodo-code-review

qodo-code-review Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Code Review by Qodo

Grey Divider

Sorry, something went wrong

We weren't able to complete the code review on our side. Please try again manually by commenting /agentic_review on this PR.

Grey Divider

Qodo Logo

@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Mark Twilio replies at the playback cutoff, not after a timeout

🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

AI Description

• Use Twilio’s playback cutoff to keep unheard reply text out of conversation history.
Diagram

graph TD
  T["Twilio interrupt"] --> G{"Active stream?"} -->|yes| P{"Heard prefix?"} -->|yes| H["Heard cutoff"] --> R["History rewrite"]
  P -->|no| F["Sent cutoff"] --> R
  G -->|no| N["No cut mark"]
Loading
High-Level Assessment

Use Twilio’s reported cutoff with prefix validation and the existing sent-text fallback. Sent text alone cannot establish what was played, while the guard prevents timeout-apology interruptions from marking an orphaned reply.

Files changed (1) +25 / -5

Bug fix (1) +25 / -5
ConversationRelaySession.csTrack the heard cutoff and exclude dropped turns from cut-in marking +25/-5

Track the heard cutoff and exclude dropped turns from cut-in marking

• Captures and resets Twilio’s utteranceUntilInterrupt for each turn, then uses it to mark unheard reply text when it prefixes the streamed text. Falls back to the sent-text position when it does not match or is absent, and ignores interrupts after the stream was dropped or the session ended.

src/Plugins/BotSharp.Plugin.Twilio/Services/ConversationRelaySession.cs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

An empty interruption prefix is treated as a missing playback boundary, leaving fully sent replies unmarked as unheard in conversation history even when the caller heard none of the reply.

1 open issue
What changed in this PR

This follow-up to #1467 refines how Twilio interruptions are recorded in conversation history, using playback progress rather than only the text sent.

Changes:

  • Use utteranceUntilInterrupt to locate the cut-off point, with a logged fallback to the sent text.
  • Prevent interrupts from marking replies after the stream was dropped or the session ended.
  • Reset playback-boundary tracking for each turn and update the documentation.
File Description
src/​Plugins/​BotSharp.Plugin.Twilio/​Services/​ConversationRelaySession.cs Tracks playback boundaries and tightens interruption handling for history updates.

Review effort: Balanced

Twilio reports an empty utteranceUntilInterrupt when the caller cut in before
any of the reply played. That was taken as no boundary at all, so a reply
fully sent was kept unmarked. Only a missing one falls back to the sent text
now; an empty one marks all of the reply as unheard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

There are no active review issues for the Twilio interruption-handling changes.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@yileicn

yileicn commented Nov 2, 2026

Copy link
Copy Markdown
Member

reviewed

@yileicn
yileicn merged commit 541b8b3 into SciSharp:master Nov 2, 2026
4 checks passed
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.

3 participants