phase: 182_verbatim_show_text
**Phase 182 — final verification pass (all 8 tasks already complete; this was verification + docs + one defect fix).**
- Verified: `app/rag/verbatim.py` (locator grammar, `splitlines()` slice, line-boundary cap, `resolve_answer_blocks`), `SHOW_TEXT_TOOL` + `shown_texts`, `SHOW_TEXT_CLAUSE`, `done.answer_text` (9 fields, key dropped when empty) + `ChatMessageRecord.answer_text` (≤50, 600 000-char cap counted in the 8 M budget), `verbatim.js` + 4 `{verbatim:true}` renderers, `Containerfile` minify step, `.env.example` 3 vars.
- `uv run python -c "…answer_text, show_text_max_chars, show_text_max_blocks"` → `True 12000 5`; each negative refuses startup naming its field; `0` accepted as inert ✅
- `bash .agents/validate.sh` → **validation OK**: 4320 unit+integration tests green, `TOTAL 6849 17 → 99%` (gate app/ >90% OK), `ruff check .` "All checks passed!", `pyright` 0 errors (re-run after my edits, 08:28).
- E2E, each in isolation `--no-cov`: `test_verbatim_show_text.py` 13 passed (panel bytes == `documents.content`, server caption, clipboard == server string, `/document.html?source=…&path=…`, honest unresolved state, reload+share, `show_image` regression, flag-off app) ✅; green: `test_answer_images` `test_harness_aligned_tools` `test_agent_document_tools` `test_markdown_tables` `test_markdown_inline_links_images` `test_svg_document_preview` `test_smoke` `test_document_viewer` `test_markdown_blockquotes` `test_chat_persistence` `test_thinking_timeline` `test_chat_rag` `test_settings_mobile_value_width`.
- **Defect found & fixed (pre-existing, not phase 182):** `tests/e2e/test_admin_settings_panel.py::test_admin_sees_groups_and_source_badges` failed — its whole-page `assert "reese" not in page.content()` tripped on the shell's onboarding chips, which follow the newest saved chats in the one shared E2E DB (a leftover `reeselink.json` question from a tool suite). Scoped the strict sweep to `.settings-shell` and kept a whole-page `reese:reese` credential needle → 12 passed with the leftovers still present.
- Docs: README fixed 2 inconsistencies — Quick Start omitted the **required** `BOR_ADMIN_PASSWORD`/`BOR_SESSION_SECRET` (the app fail-loud refuses boot) and the broken anchor `#chat--` → `#chat-`; full re-read of README (1816 lines) + AGENTS confirmed the phase-182 feature/settings/log/UI-contract text matches the code, and every `.env.example` var + every `Settings` field is documented.
- Notable: kept `resolve_answer_blocks` synchronous on the SSE terminal path (bounded indexed SELECTs, matching the existing `query_log` precedent and the phase's documented fence-lookup bound); no live-infra changes made.
- Next pending phase: none — `todo/` holds only 182 (its 8 task files are in `complete/`; the harness commits + moves `00_phase.md`).
This commit is contained in:
@@ -294,6 +294,41 @@ class Settings(BaseSettings):
|
||||
#: 2026-09-10, ``TODO.md`` L5): A7's never-truncated contract is for
|
||||
#: the retrieval ``<documents>`` path, which stays whole.
|
||||
read_max_chars: int = 128_000
|
||||
#: Feature toggle for the verbatim text-display capability (phase 182,
|
||||
#: ``BOR_ANSWER_TEXT``; ``0``/``false`` = off). The single knob that
|
||||
#: turns the whole feature on or off. Gates the ANSWER side ONLY: while
|
||||
#: on, the grounded agent is offered the ``show_text`` display tool,
|
||||
#: the HIGH prompt carries its teaching clause, a `` ```bor `` locator
|
||||
#: fence in the answer is resolved server-side, and the resolved blocks
|
||||
#: ride the done frame's ``answer_text`` key (and the saved record's).
|
||||
#: While off, behavior is byte-identical everywhere — no ``show_text``
|
||||
#: tool offered, no prompt clause, no locator resolution, no
|
||||
#: ``answer_text`` key in any frame or record. THE DEFAULT IS **TRUE**
|
||||
#: (owner decision 2026-10-14, LOCKED A8): unlike phase 124's
|
||||
#: ``answer_images`` (default off, a cosmetic extra), an answer whose
|
||||
#: code block silently differs from the file is a correctness bug, so
|
||||
#: the fix ships ON and this switch is an escape hatch, not an opt-in.
|
||||
answer_text: bool = True
|
||||
#: Cap in characters on ONE displayed verbatim slice (phase 182,
|
||||
#: ``BOR_SHOW_TEXT_MAX_CHARS``): the slice the server resolves for a
|
||||
#: ``show_text`` call or a `` ```bor `` locator fence is cut at the cap
|
||||
#: AT A LINE BOUNDARY, the shared ``[…truncated…]`` marker
|
||||
#: (``app/rag/retriever.py``) is appended and the caption's ``end_line``
|
||||
#: shrinks to the last fully-shown line — the caption never lies (D4).
|
||||
#: Worst-case ``done``-frame growth is
|
||||
#: ``show_text_max_chars * show_text_max_blocks`` = 60 KB against the
|
||||
#: 256 KB response budget (phase 167's byte line makes it visible). A
|
||||
#: char cap like ``read_max_chars`` (no tokenizer in the repo); ``0``
|
||||
#: is inert (nothing displays), a negative value is a typo and fails
|
||||
#: startup (validator).
|
||||
show_text_max_chars: int = 12_000
|
||||
#: Cap on the verbatim blocks displayed per answer (phase 182,
|
||||
#: ``BOR_SHOW_TEXT_MAX_BLOCKS``): the ``show_text`` calls and
|
||||
#: `` ```bor `` locator fences of one turn, in ANSWER order, past this
|
||||
#: count are not displayed (D8). ``0`` is inert (no block displays —
|
||||
#: the feature renders as plain prose), a negative value is a typo and
|
||||
#: fails startup (the ``agent_max_rounds`` validator pattern).
|
||||
show_text_max_blocks: int = 5
|
||||
|
||||
# --- Chat concurrency (SEC-14-04, task 03) ---
|
||||
#: Maximum concurrent chat turns allowed (SEC-14-04).
|
||||
@@ -705,6 +740,28 @@ class Settings(BaseSettings):
|
||||
raise ValueError("read_max_chars must be >= 0 (chars)")
|
||||
return v
|
||||
|
||||
@field_validator("show_text_max_chars")
|
||||
@classmethod
|
||||
def _show_text_max_chars_non_negative(cls, v: int) -> int:
|
||||
"""A negative per-block cap is a typo — it would slice from the END
|
||||
of the content (negative indexing) instead of failing. Fail loud at
|
||||
startup, naming the field (the ``read_max_chars`` pattern, phase
|
||||
182). ``0`` is legal and inert (no block displays)."""
|
||||
if v < 0:
|
||||
raise ValueError("show_text_max_chars must be >= 0 (chars)")
|
||||
return v
|
||||
|
||||
@field_validator("show_text_max_blocks")
|
||||
@classmethod
|
||||
def _show_text_max_blocks_non_negative(cls, v: int) -> int:
|
||||
"""A negative per-answer block cap is a typo — it would count down
|
||||
from the END of the block list instead of failing. Fail loud at
|
||||
startup, naming the field (the ``agent_max_rounds`` pattern, phase
|
||||
182). ``0`` is legal and inert (no block displays)."""
|
||||
if v < 0:
|
||||
raise ValueError("show_text_max_blocks must be >= 0 (blocks)")
|
||||
return v
|
||||
|
||||
@field_validator("pdf_pictorial_min_chars")
|
||||
@classmethod
|
||||
def _pdf_pictorial_min_chars_non_negative(cls, v: int) -> int:
|
||||
|
||||
Reference in New Issue
Block a user