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:
+35
-3
@@ -84,6 +84,25 @@ A15 — `thinking`, `tool`, unlimited tool rounds under
|
||||
never-truncated contract is unchanged; the `read`-tool cap is
|
||||
the only exception, owner-permitted 2026-09-10.
|
||||
|
||||
Phase 182 (verbatim text display, owner chat 2026-10-14) adds
|
||||
**`show_text(path, offset?, limit?)` as the fifth agent tool** — offered
|
||||
only when `BOR_ANSWER_TEXT=1` (the default), gated exactly like
|
||||
`show_image`, with its own `<tools>` clause and its own kill switch — and
|
||||
the **additive `done.answer_text`** field above. The new settings are
|
||||
`BOR_ANSWER_TEXT` (master switch, ON), `BOR_SHOW_TEXT_MAX_CHARS` (per-block
|
||||
budget, 12 000) and `BOR_SHOW_TEXT_MAX_BLOCKS` (per-answer budget, 5), all
|
||||
following the `agent_max_rounds` pattern (`0` inert, negative refuses
|
||||
startup). The principle in one sentence: **the model points, the server
|
||||
supplies the bytes** — a ` ```bor source/path 12-40 ` fence or a
|
||||
`show_text` call names a location (canonical locator `source/path` or
|
||||
`source/path#start-end`, 1-based and inclusive), and the displayed text is
|
||||
sliced from `documents.content` server-side, so what the user copies is the
|
||||
knowledge base, never a re-typed approximation. **A1–A6 and A15 are
|
||||
otherwise
|
||||
UNCHANGED:** no new SSE event type, no new table or column, no new model,
|
||||
endpoint or transport, no change to retrieval, the honesty gate, the
|
||||
persona text or the embedding dimension.
|
||||
|
||||
---
|
||||
|
||||
## 3. High-Level Architecture
|
||||
@@ -129,7 +148,9 @@ POST /api/chat {message, history?} (auth: require_user)
|
||||
→ cosine top-100 ∪ FTS top-30 → RRF fuse (k=60) [A7]
|
||||
├─ HIGH (cosine ≥ 0.62 OR fts_hits > 0):
|
||||
│ persona + <knowledge_base> + <tuning> + full top-2 <documents>
|
||||
│ + <tools> → agent loop (ls/read/grep; round-capped
|
||||
│ + <tools> → agent loop (ls/read/grep, plus show_image when
|
||||
│ BOR_ANSWER_IMAGES=1 and show_text when BOR_ANSWER_TEXT=1 — the two
|
||||
│ "show the user an artifact" tools, gated independently; round-capped
|
||||
│ BOR_AGENT_MAX_ROUNDS=10, 0 = tools off) → turbo streamed
|
||||
└─ LOW (A8): DEFLECT_MODE prompt (weak titles only, no tools,
|
||||
byte-identical direct-stream path) → turbo streamed
|
||||
@@ -139,7 +160,15 @@ POST /api/chat {message, history?} (auth: require_user)
|
||||
SSE frames (`data: <json>\n\n`): `thinking` (before first delta) →
|
||||
`tool` (grounded turns, one per model call) → `retry` (pre-first-frame
|
||||
restarts) → `delta` (answer tokens) → `done` `{deflected, sources[],
|
||||
suggestions[], budget_exhausted?}` (terminal). Failure: `error` (terminal — no `done`, no
|
||||
suggestions[], budget_exhausted?, answer_text[]?}` (terminal). **Phase 182
|
||||
extends the `done` frame ADDITIVELY** — `answer_text` is the list of
|
||||
server-resolved verbatim blocks (`locator`, `source`, `path`, `start`,
|
||||
`end`, `total_lines`, `text`, `format`, `truncated`) the answer displayed;
|
||||
there is **no eighth event type**, the key is OMITTED when no block was
|
||||
displayed, and with `BOR_ANSWER_TEXT=0` it is absent on every turn (the
|
||||
frame is then byte-identical to pre-phase). The same array persists on the
|
||||
saved chat record, so restore and the anonymous shared page re-render the
|
||||
panels from the server's bytes. Failure: `error` (terminal — no `done`, no
|
||||
`query_log` row; a pre-stream DB outage is a plain 503 JSON). A
|
||||
`tools=None` request never emits a `tool` frame; a round-capped turn
|
||||
whose final reply carries no visible content answers with the budget
|
||||
@@ -273,8 +302,11 @@ scripts.…`, and tests all honor it.
|
||||
question=… embed_ms=… top_score=… fts_hits=… summary_hits=… tuning=N
|
||||
kb_chars=N history_msgs=N threshold=… deflected=… sources=…
|
||||
thinking_chars=… tool_calls=N rounds=N cap_hit=0|1 total_ms=…
|
||||
retries=N scaffold_stripped=N
|
||||
retries=N scaffold_stripped=N verbatim_blocks=N
|
||||
```
|
||||
(`verbatim_blocks=` — phase 182 — is the count of verbatim display blocks
|
||||
actually SENT on the `done` frame, `0` on every turn without them,
|
||||
deflected included.)
|
||||
(`sources=` = retrieval + agent-read docs, deduped; `tool_calls=`
|
||||
executed only; a cancelled turn writes neither line nor `query_log`
|
||||
row.)
|
||||
|
||||
Reference in New Issue
Block a user