phase: 192_verbatim_inline_midturn
Phase 192 — final verification pass complete. - **Verified (all 4 tasks already complete):** additive `verbatim_block` SSE frame (`VerbatimBlockPiece` → `ChatVerbatimBlockEvent` → chat dispatch, after the round's `tool`/`tool_result`, before the next round); `hydrateVerbatimNow` matched-only mid-turn pass + app.js branch; `dropPlacedTwin` per-locator done/restore dedupe; the dedicated E2E suite. - **Root cause of the rejected attempt:** my own concurrent background `pytest` run deadlocked the harness validation run on the shared Postgres (`psycopg.errors.DeadlockDetected`). No code defect — re-ran every gate **exclusively**. - `uv run pytest -q --cov=app --cov-report=term-missing` → **exit 0**, `TOTAL 7181 16 99%` (>90%) - `uv run pytest tests/e2e/test_verbatim_inline_midturn.py --no-cov` → **6 passed** (isolated) - `uv run pytest tests/e2e/test_verbatim_show_text.py tests/e2e/test_chat_history.py --no-cov` → **18 passed** (`test_chat_history.py` byte-unedited; the phase-182 suite's "no eighth type" pin was tightened, not loosened, to seven + `verbatim_block` with frame-order asserts) - `uv run ruff check .` → All checks passed (exit 0) · `uv run pyright` → **0 errors** - **Completion criteria:** single frame per success + zero per refusal ✅ · `BOR_ANSWER_TEXT=0` wire byte-identical (integration spelled baseline + E2E App B) ✅ · fence hydrates in place / unfenced lands at the call moment, before `done` ✅ · append+fence → 1 panel, two ranges → 2 panels ✅ · restore 1 panel/locator, `done.answer_text` + record key + `verbatim_blocks=N` unchanged ✅ · suite green, coverage 99%, lint/types clean ✅ · no-show_text turn byte-identical ✅ · commit → left to the harness (no git index/history commands run). - **Docs:** README reviewed line-by-line (2399 lines) + AGENTS.md in full; mechanically verified all 86 `BOR_*` settings, every `.env.example` var, table defaults, internal anchors (0 broken), ports 8123/8901, tree depth 1–8, pagination 25/1–100, 17 palette colors, `0029`/`0030` migrations, and every cited test file. Fixed 1 inconsistency: the verbatim "Parity" bullet claimed the live turn/reload/shared page share "the same three globals" — now names the settle-path trio and states that `hydrateVerbatimNow` is the ONE chat-only addition (a shared page has no stream). - **Notable:** `run_agent`'s four repeated piece unions were collapsed into the ONE exported `AgentPiece` alias, so a future piece type is spelled in one place; `test_frontend_edit_message.py`'s `runTurn` byte-pin was regenerated, not weakened. - **Next pending phase:** `193_sync_remaining_count`.
This commit is contained in:
+59
-9
@@ -251,6 +251,24 @@ keeps its exact pre-phase bytes). The per-turn log line records
|
||||
ACTUALLY SENT (after dedupe and the ``show_text_max_blocks`` cut), 0 on
|
||||
every turn without them including deflected ones (the PLAN §9 additive
|
||||
revision).
|
||||
|
||||
Mid-turn verbatim frame (phase 192, task 01, A1; ``TODO.md`` L5): the
|
||||
same block ALSO reaches the client the moment the ``show_text`` call
|
||||
succeeds, as the additive ``verbatim_block`` event —
|
||||
``{"type": "verbatim_block", "source", "path", "title", "start_line",
|
||||
"end_line", "total_lines", "text", "truncated", "locator"}`` (the exact
|
||||
:class:`~app.schemas.VerbatimBlockRef` field set, so the one panel client
|
||||
accepts it as-is). It lands AFTER the round's ``tool`` / ``tool_result`` frames and
|
||||
BEFORE the next model round — while the answer is still streaming, so the
|
||||
panel appears where the model introduced the bytes instead of popping in
|
||||
after it finished talking. Success-only: every refusal (missing ``path``,
|
||||
no document, an image row, the block cap, the already-shown dedupe) emits
|
||||
nothing, and a ``BOR_ANSWER_TEXT=0`` turn offers no tool, so its frame
|
||||
sequence is byte-identical to pre-phase. Everything downstream of the
|
||||
turn keeps the phase-182 contract EXACTLY: the ``done`` frame's
|
||||
``answer_text`` list, the saved record key, the restore path and the
|
||||
shared page are untouched, and ``verbatim_blocks=N`` still counts the
|
||||
``done``-frame blocks (never the mid-turn frames).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -275,7 +293,9 @@ from app.models import Document, QueryLog
|
||||
from app.rag.agent import (
|
||||
CORRECTION_INSTRUCTION, # phase 71: the harness-owned recovery line
|
||||
AgentHolder,
|
||||
AgentPiece, # the ONE union run_agent yields (phase 192: + VerbatimBlockPiece)
|
||||
MalformedReplyError, # phase 71: the recovery policy's terminal signal
|
||||
VerbatimBlockPiece, # phase 192: one shown verbatim block (mid-turn frame)
|
||||
run_agent,
|
||||
suggested_folder_lines, # phase 119 (D3): the suggested-folder context lines
|
||||
)
|
||||
@@ -285,7 +305,6 @@ from app.rag.llm import (
|
||||
LLMClient,
|
||||
LLMError,
|
||||
RetryPiece, # phase 67: one LLM request restart (an SSE retry frame)
|
||||
StreamPiece, # type of the answer pieces streamed by the agent loop
|
||||
ToolCallPiece, # phase 37: one model-requested tool call
|
||||
ToolResultPiece, # phase 95: one truncated tool result (SSE frame = task 02)
|
||||
chat_stream_retried, # phase 67: the retry-before-first-piece primitive
|
||||
@@ -323,6 +342,7 @@ from app.schemas import (
|
||||
ChatThinkingEvent,
|
||||
ChatToolEvent,
|
||||
ChatToolResultEvent,
|
||||
ChatVerbatimBlockEvent,
|
||||
SourceRef,
|
||||
VerbatimBlockRef,
|
||||
)
|
||||
@@ -974,9 +994,11 @@ async def chat(
|
||||
# ``agent_max_rounds=0`` ``run_agent`` is a single
|
||||
# ``tools=None`` request anyway (the kill switch).
|
||||
holder = AgentHolder()
|
||||
answer_stream: AsyncIterator[
|
||||
StreamPiece | ToolCallPiece | RetryPiece | ToolResultPiece
|
||||
]
|
||||
# Phase 192: ``AgentPiece`` is the ONE union ``run_agent``
|
||||
# yields (the phase-95 truncation piece plus the new
|
||||
# ``VerbatimBlockPiece``); the deflected path's narrower
|
||||
# ``chat_stream_retried`` union flows into it unchanged.
|
||||
answer_stream: AsyncIterator[AgentPiece]
|
||||
deflected_filter: ScaffoldingFilter | None = None
|
||||
if plan.deflected:
|
||||
# Phase 67: the deflected stream goes through the retry
|
||||
@@ -1038,9 +1060,7 @@ async def chat(
|
||||
tools_offered = not plan.deflected
|
||||
|
||||
async def _pump(
|
||||
pieces: AsyncIterator[
|
||||
StreamPiece | ToolCallPiece | RetryPiece | ToolResultPiece
|
||||
],
|
||||
pieces: AsyncIterator[AgentPiece],
|
||||
tools_offered: bool = True,
|
||||
) -> AsyncIterator[str]:
|
||||
"""One request's piece loop (phase 71 extraction): the
|
||||
@@ -1058,10 +1078,14 @@ async def chat(
|
||||
frame, no visible markup) and counted in a local so one
|
||||
debug line can name it. The grounded first pass keeps its
|
||||
current behavior (``tools_offered=True``; ``run_agent``
|
||||
suppresses the cap-path calls internally)."""
|
||||
suppresses the cap-path calls internally). Phase 192
|
||||
(task 01): the ``VerbatimBlockPiece`` branch emits the
|
||||
additive ``verbatim_block`` frame — the verbatim block a
|
||||
successful ``show_text`` just displayed, so its panel
|
||||
lands mid-stream instead of at ``done``."""
|
||||
nonlocal thinking_chars, content_chars, retries_used
|
||||
dropped_calls = 0 # phase 160: tools=None requests only
|
||||
async for piece in pieces: # StreamPiece | ToolCallPiece | RetryPiece
|
||||
async for piece in pieces: # app.rag.agent.AgentPiece
|
||||
if isinstance(piece, ToolCallPiece):
|
||||
if not tools_offered:
|
||||
# Phase 160 (task 02): a tools=None request
|
||||
@@ -1170,6 +1194,32 @@ async def chat(
|
||||
).model_dump()
|
||||
)
|
||||
continue
|
||||
if isinstance(piece, VerbatimBlockPiece):
|
||||
# Phase 192 (task 01, A1): the mid-turn verbatim
|
||||
# frame — the block the ``show_text`` call just
|
||||
# displayed, sent at the moment it exists (the
|
||||
# panel lands while the answer is still
|
||||
# streaming instead of at ``done``). Additive:
|
||||
# a refused call yields no piece (no frame), a
|
||||
# ``answer_text=False`` turn offers no tool (no
|
||||
# frame) — the frame sequence is byte-identical
|
||||
# to pre-phase in both cases, and the ``done``
|
||||
# frame's ``answer_text`` list and its
|
||||
# ``verbatim_blocks=N`` count are untouched.
|
||||
yield sse_event(
|
||||
ChatVerbatimBlockEvent(
|
||||
source=piece.block.source,
|
||||
path=piece.block.path,
|
||||
title=piece.block.title,
|
||||
start_line=piece.block.start_line,
|
||||
end_line=piece.block.end_line,
|
||||
total_lines=piece.block.total_lines,
|
||||
text=piece.block.text,
|
||||
truncated=piece.block.truncated,
|
||||
locator=piece.block.locator,
|
||||
).model_dump()
|
||||
)
|
||||
continue
|
||||
if piece.kind == "thinking":
|
||||
thinking_chars += len(piece.text)
|
||||
if settings.stream_thinking:
|
||||
|
||||
Reference in New Issue
Block a user