feat(rag): retry a failed LLM request before the first token lands — BOR_LLM_RETRIES/BOR_LLM_RETRY_DELAY with a live 'retrying' status
This commit is contained in:
@@ -605,3 +605,56 @@ def test_index_messages_comment_documents_the_meta_actions() -> None:
|
||||
assert "every visitor" in comment.lower() or "everyone" in comment.lower(), (
|
||||
"Retry is documented as available to all visitors"
|
||||
)
|
||||
|
||||
|
||||
# ---------- llm retry status (phase 67, task 04) ----------
|
||||
|
||||
|
||||
def test_retry_frame_is_a_first_class_branch_between_tool_and_delta() -> None:
|
||||
"""Phase 67 (owner-locked A4, task 02's contract): a `retry` SSE
|
||||
frame (the server restarted the LLM request before its first piece
|
||||
— locked A2) is a first-class branch in runTurn's handler, ordered
|
||||
BETWEEN the `tool` and `delta` branches. It clears the 120s guard
|
||||
(a frame arrived), resolves n/N from the frame, and writes the
|
||||
owner-locked copy literal onto the EXISTING channels only — the
|
||||
#send-status live region + the typing-indicator aria-label. No DOM
|
||||
of its own: no addMessage, no appendToolLine, no error banner, no
|
||||
UI-state change. The gate covers BOTH live states: a later agent
|
||||
round may restart while the UI already streams."""
|
||||
js = _js()
|
||||
tool_idx = js.find('ev.type === "tool"')
|
||||
retry_idx = js.find('ev.type === "retry"')
|
||||
delta_idx = js.find('ev.type === "delta"')
|
||||
assert -1 < tool_idx < retry_idx < delta_idx, (
|
||||
"the turn handler must branch on retry frames, between tool and delta"
|
||||
)
|
||||
assert js.count('ev.type === "retry"') == 1, "exactly one retry branch"
|
||||
branch = js[retry_idx:delta_idx]
|
||||
assert "clearTurnTimeout()" in branch, "a frame arrived — the 120s guard clears"
|
||||
assert "Number(ev.attempt)" in branch, "n = the attempt about to be tried"
|
||||
assert "Number(ev.max_attempts)" in branch, "N = the configured total"
|
||||
assert (
|
||||
"`Communication interrupted — retrying (${attempt} of ${max})…`" in branch
|
||||
), "the owner-locked copy literal (A4)"
|
||||
assert "sendStatus.textContent = retryStatus" in branch, (
|
||||
"the existing #send-status live region carries the status"
|
||||
)
|
||||
assert "#typing-indicator .bubble" in branch, ("the typing-indicator is reused")
|
||||
assert 'setAttribute("aria-label", retryStatus)' in branch
|
||||
assert "UI_STATE.thinking" in branch and "UI_STATE.streaming" in branch, (
|
||||
"the gate covers BOTH live states (a later round may restart mid-stream)"
|
||||
)
|
||||
for forbidden in ("addMessage", "appendToolLine", "showErrorBanner", "setUiState"):
|
||||
assert forbidden not in branch, f"transient status only — no {forbidden}"
|
||||
|
||||
|
||||
def test_header_inventory_documents_the_retry_frame() -> None:
|
||||
"""The app.js file-header doc comment inventories every SSE frame
|
||||
type (house convention); phase 67 adds the `retry` frame there, with
|
||||
the owner-locked copy quoted, so the A4 literal has exactly two
|
||||
homes: the header doc and the handler branch."""
|
||||
js = _js()
|
||||
header = js[: js.find("import {")]
|
||||
assert "`retry`" in header, "the header must list the retry frame"
|
||||
assert "Communication interrupted — retrying" in header
|
||||
assert header.count("Communication interrupted — retrying") == 1
|
||||
|
||||
Reference in New Issue
Block a user