Merge pull request #6097 from nesquena/stage/6081-noresp

Release exp: suppress false no-response after tool-limit turn with a final answer (#6081)
This commit is contained in:
nesquena-hermes authored and GitHub committed 2026-07-15 12:43:22 -07:00
commit ad88366baa
3 files changed
+18 -10

No files matched your search

+2
View File
@@ -67,6 +67,8 @@
### Fixed
- **No more spurious "No response from provider" after a tool-limit turn that did answer.** When a turn hit the per-turn tool-iteration limit (`max_iterations_reached`) but the model had already produced a valid final answer, the settlement classifier still appended a "No response from provider" error card after the good reply — a regression of the earlier false-`no_response` fix, because the suppression path admitted only `_soft_partial_terminal_failure` and deliberately excluded `_tool_limit_reached`. The suppression now also covers `_tool_limit_reached`, but *only* when the transcript already holds a final answer; a tool-limit turn with no final answer still fails honestly, and explicit errors / cancellations / interruptions stay outside the suppression. Thanks @coyaSONG. (#6081, #6079)
- **New conversations can no longer become unstartable seconds after you create them.** On installs with a saved browser/password-manager credential and more than a few hundred sessions, the manager would autofill the account name into the sidebar conversation filter, silently firing a content search that walked every session through the cache and evicted the brand-new, not-yet-persisted session (its only copy lived in the in-memory cache) — so `/api/session/draft` and `/api/chat/start` then 404'd and the chat could never be started. Two fixes: the search/filter inputs are now `type="search"` with per-manager opt-out attributes and the Settings + onboarding password fields get a real `<form>` owner (`display:contents`, layout unchanged) so managers stop treating the sidebar filter as a username field; and `_session_is_evictable()` now proves persistence before evicting even a zero-message session, so an unsaved session you're still composing stays resident. Thanks @ai-ag2026. (#6083, #4765)
- **Live reasoning no longer drops or double-renders when the turn anchor defers a paint.** On the streaming reasoning render, when the assistant-turn anchor accepted a reasoning event but deferred actually painting it, the reasoning text could go missing — and earlier attempts to fix that could briefly show the same reasoning under two owners or, with multiple reasoning segments, update the wrong row. The fallback now matches the exact anchor row identity (`run_id:seq`), so the right segment updates in place, other rows are untouched, and no duplicate owner appears — across Transparent, Compact, and hide-all modes. Thanks @franksong2702. (#6048, #5720)
+1 -1
View File
@@ -8891,7 +8891,7 @@ def _run_agent_streaming(
)
if (
_terminal_failure
and _soft_partial_terminal_failure
and (_soft_partial_terminal_failure or _tool_limit_reached)
and _classification['type'] == 'no_response'
and not _saved_transcript_lacks_final_answer
):
+15 -9
View File
@@ -255,8 +255,9 @@ def test_streaming_tool_limit_with_final_answer_persists_clean_done_state(tmp_pa
assert assistant["_statusCard"]["title"] == "Tool iteration limit reached"
def test_streaming_tool_limit_without_final_answer_emits_no_final_apperror(tmp_path, monkeypatch):
def test_streaming_tool_limit_partial_without_final_answer_emits_no_final_apperror(tmp_path, monkeypatch):
result = {
"status": "partial",
"turn_exit_reason": "max_iterations_reached(30/30)",
"messages": [
{"role": "user", "content": "Do the long task."},
@@ -406,7 +407,10 @@ def test_maybe_inject_max_iteration_summary_fallback_skips_when_no_fallback():
assert out == messages
def test_streaming_tool_limit_terminal_failure_does_not_mark_final_answer(tmp_path, monkeypatch):
def test_streaming_tool_limit_partial_with_final_answer_suppresses_false_no_response(
tmp_path,
monkeypatch,
):
result = {
"status": "partial",
"turn_exit_reason": "max_iterations_reached(30/30)",
@@ -418,19 +422,21 @@ def test_streaming_tool_limit_terminal_failure_does_not_mark_final_answer(tmp_pa
events, payload = _run_streaming_with_fake_agent(tmp_path, monkeypatch, result)
apperror_payloads = [payload for event, payload in events if event == "apperror"]
assert apperror_payloads, "expected terminal-failure apperror"
assert apperror_payloads[-1]["type"] == "tool_limit_reached"
assert not [payload for event, payload in events if event == "done"]
assert not [event_payload for event, event_payload in events if event == "apperror"]
done_payloads = [event_payload for event, event_payload in events if event == "done"]
assert done_payloads, "expected done SSE payload"
assert done_payloads[-1]["terminal_state"] == "tool_limit_reached"
assert done_payloads[-1]["terminal_reason"] == "max_iterations"
assistant = next(
message
for message in payload["messages"]
if message.get("role") == "assistant"
and message.get("content") == "I reached the limit; here is the summary."
)
assert "_terminal_state" not in assistant
assert "_statusCard" not in assistant
assert payload["messages"][-1]["_error"] is True
assert assistant["_terminal_state"] == "tool_limit_reached"
assert assistant["_terminal_reason"] == "max_iterations"
assert assistant["_statusCard"]["title"] == "Tool iteration limit reached"
assert payload["messages"][-1] is assistant
def test_streaming_historical_synthetic_prompt_normal_result_does_not_emit_tool_limit(tmp_path, monkeypatch):