Files
vibn-frontend/app/api
Mark Henderson b395546529 fix(chat): never end a turn silent + loop detection + status nudge
The big UX failure: model fires 20 tool calls in silence, persists turn
with content_len=0, user has to re-prompt to get any answer. Confirmed
in prod (Dr Dave / "are you able to give me a preview url?" thread).

Five changes:

1. Recovery summary now fires on ANY silent-tool-tray turn end (not just
   MAX_TOOL_ROUNDS): hit the cap, broke a detected loop, OR ended with
   empty assistantText. Previously the recovery was gated to round-cap
   only, so voluntary silent stops slipped through.

2. Recovery summary has a deterministic fallback. If Gemini returns
   empty text on the recovery call, emit a static "ran N tools, didn't
   reach a clean stopping point" message instead of silently swallowing
   the empty string. The user always gets something readable.

3. Loop detection: track tool-call fingerprints (name + first 120
   chars of args) per turn; if the same fingerprint fires 3× within
   the last 8 calls, break the loop and surface to user via recovery
   summary. Kills the dev_server.start → logs → stop → start → ...
   pattern at its root.

4. Status nudge every 4 silent rounds: inject a synthetic system
   instruction telling the model to send a one-liner before any more
   tool calls. The user's only signal of life on long chains.

5. Prompt: soften "don't narrate intent" → "don't narrate SINGLE
   calls; on chains 3+ deep send a one-liner before each batch".
   Adds explicit "never end a turn silent" rule.

Also: error-path now uses safeClose() instead of bare controller.close()
to honor the streamClosed guard like every other close site.

Made-with: Cursor
2026-04-30 23:18:46 -07:00
..
2026-02-15 19:25:52 -08:00
2026-02-15 19:25:52 -08:00