Skip to content

voice-agent: deliver in-flight note view on reconnect - #246

Merged
sonichi merged 1 commit into
mainfrom
fix/note-viewing-reconnect-delivery
Apr 9, 2026
Merged

sonichi merged 1 commit into
mainfrom
fix/note-viewing-reconnect-delivery

Conversation

@sonichi

@sonichi sonichi commented Apr 9, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up to #241. The note-view debounce in startNoteViewingWatcher bumped lastNoteViewingTs before the session-active check in the voice-agent callback. Consequence: a note opened while voice was disconnected was marked "handled" even though the callback no-op'd, so the view was silently dropped and the user had to click the note again after reconnecting.

What this fixes

Scenario: Voice session closed → user opens a note in the web UI → user reconnects voice → Gemini has no idea what note is on screen unless the user re-clicks it.

Owner wanted this fixed as a follow-up to #241 (which merged as-is with the bug documented as non-blocking).

Changes

  1. startNoteViewingWatcher callback now returns boolean | void. The watcher only bumps lastNoteViewingTs when the callback returns anything other than false. Voice-agent callback returns true when it injects, false when the session isn't connected. Un-handled events stay pending for the next poll.

  2. New resetNoteViewingDebounce() called from the MainAgent greeting getter — fires on each fresh connect AND reconnect — so the current note-viewing event re-delivers within 2 seconds of reconnect (the watcher poll interval).

  3. Extracted readCurrentNoteViewing() as a shared reader so future synchronous injection paths don't need to re-implement the parse.

What this doesn't do

  • Doesn't inject synchronously at greeting time. The reset + 2s watcher poll is simpler and catches the same case without coupling the greeting path to the note payload. If a user perceives the 2s delay, we can revisit.
  • Doesn't change the 4000-char truncation or any other injection semantics from voice-agent: inject currently-viewed note into Gemini context #241.

Test plan

  • Start voice, open a note, verify inject fires
  • Close voice connection (keep browser tab)
  • Open a different note in the web UI
  • Reconnect voice → Gemini should have the second note as context within ~2s
  • Ask "what does this note say about X" without specifying the path → answer should reflect the second note, not the first
  • Regression: opening the same slug twice in a row still re-injects (timestamp advances each time)

🤖 Generated with Claude Code

PR #241 added note-view injection but the debounce in
startNoteViewingWatcher bumped lastNoteViewingTs before the
session-active check. Consequence: a note opened while voice
was disconnected was marked "handled" even though the callback
no-op'd, so the view was silently dropped and the user had to
click the note again after reconnecting.

Two small changes:

1. startNoteViewingWatcher callback now returns boolean | void.
   The watcher only bumps lastNoteViewingTs when the callback
   returns anything other than false. The voice-agent callback
   returns true when it actually injects, false when the session
   isn't connected. Un-handled events stay pending.

2. New resetNoteViewingDebounce() is called from the MainAgent
   greeting getter — which fires on each fresh connect AND
   reconnect — so the current note-viewing event re-delivers on
   the next watcher poll (within 2s of reconnect).

Also extracts readCurrentNoteViewing() as a shared reader so
future callers (e.g. the reconnect flow itself, if we want to
inject synchronously) don't need to reimplement the parse logic.

Verified locally with tsc --noEmit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sonichi
sonichi merged commit 5b8ba75 into main Apr 9, 2026
@sonichi
sonichi deleted the fix/note-viewing-reconnect-delivery branch April 9, 2026 17:06
sonichi added a commit that referenced this pull request Apr 9, 2026
PR #246's keep-pending-on-disconnect path retries an unhandled note-view
event every 2s until the client reconnects. Each retry also logged
"[TaskBridge] Note view detected: <slug>", producing ~30 identical log
lines per minute whenever a user opened a note while voice was
disconnected. Noticed this afternoon filling voice-agent.log with
`uiuc-trip-conflicts` spam when the user's voice session was idle.

Track the last *logged* ts separately from the last *handled* ts. The
detection log line now fires once per unique event.ts; resetNoteView-
ingDebounce() (called on reconnect) clears both so a reconnect event
is visible in the log again.

The underlying retry behavior from PR #246 is unchanged — we still try
to deliver every 2s, which is correct. Only the log side is quieter.

Test plan:
- ✓ tsc clean (`npx tsc --noEmit`)
- ✓ npm test: 48/48 pass (was 27/28, cartesia-tts now passes after
  today's bodhi bump pulled the optional dep)
- ✓ Restarted voice-agent against a still-viewed note with client
  disconnected → exactly 1 "Note view detected" line in 20s of log
  tail (was 30+ previously)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@sonichi sonichi mentioned this pull request Apr 9, 2026
3 tasks done
sonichi added a commit that referenced this pull request Apr 10, 2026
PR #246's keep-pending-on-disconnect path retries an unhandled note-view
event every 2s until the client reconnects. Each retry also logged
"[TaskBridge] Note view detected: <slug>", producing ~30 identical log
lines per minute whenever a user opened a note while voice was
disconnected. Noticed this afternoon filling voice-agent.log with
`uiuc-trip-conflicts` spam when the user's voice session was idle.

Track the last *logged* ts separately from the last *handled* ts. The
detection log line now fires once per unique event.ts; resetNoteView-
ingDebounce() (called on reconnect) clears both so a reconnect event
is visible in the log again.

The underlying retry behavior from PR #246 is unchanged — we still try
to deliver every 2s, which is correct. Only the log side is quieter.

Test plan:
- ✓ tsc clean (`npx tsc --noEmit`)
- ✓ npm test: 48/48 pass (was 27/28, cartesia-tts now passes after
  today's bodhi bump pulled the optional dep)
- ✓ Restarted voice-agent against a still-viewed note with client
  disconnected → exactly 1 "Note view detected" line in 20s of log
  tail (was 30+ previously)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
PR sonichi#241 added note-view injection but the debounce in
startNoteViewingWatcher bumped lastNoteViewingTs before the
session-active check. Consequence: a note opened while voice
was disconnected was marked "handled" even though the callback
no-op'd, so the view was silently dropped and the user had to
click the note again after reconnecting.

Two small changes:

1. startNoteViewingWatcher callback now returns boolean | void.
   The watcher only bumps lastNoteViewingTs when the callback
   returns anything other than false. The voice-agent callback
   returns true when it actually injects, false when the session
   isn't connected. Un-handled events stay pending.

2. New resetNoteViewingDebounce() is called from the MainAgent
   greeting getter — which fires on each fresh connect AND
   reconnect — so the current note-viewing event re-delivers on
   the next watcher poll (within 2s of reconnect).

Also extracts readCurrentNoteViewing() as a shared reader so
future callers (e.g. the reconnect flow itself, if we want to
inject synchronously) don't need to reimplement the parse logic.

Verified locally with tsc --noEmit.

Co-authored-by: Chi <wangchi@Chis-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
PR sonichi#246's keep-pending-on-disconnect path retries an unhandled note-view
event every 2s until the client reconnects. Each retry also logged
"[TaskBridge] Note view detected: <slug>", producing ~30 identical log
lines per minute whenever a user opened a note while voice was
disconnected. Noticed this afternoon filling voice-agent.log with
`uiuc-trip-conflicts` spam when the user's voice session was idle.

Track the last *logged* ts separately from the last *handled* ts. The
detection log line now fires once per unique event.ts; resetNoteView-
ingDebounce() (called on reconnect) clears both so a reconnect event
is visible in the log again.

The underlying retry behavior from PR sonichi#246 is unchanged — we still try
to deliver every 2s, which is correct. Only the log side is quieter.

Test plan:
- ✓ tsc clean (`npx tsc --noEmit`)
- ✓ npm test: 48/48 pass (was 27/28, cartesia-tts now passes after
  today's bodhi bump pulled the optional dep)
- ✓ Restarted voice-agent against a still-viewed note with client
  disconnected → exactly 1 "Note view detected" line in 20s of log
  tail (was 30+ previously)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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