phase: 95_read_truncation_cap
All gates are green. Final verification is complete — no fixes were needed; the phase is fully implemented and passing. **Phase 95 final verification report** - Verified all 3 tasks already implemented (task files in `complete/`): read cap + notice + `ToolResultPiece` (task 01), `tool_result` SSE + live/saved/shared marker (task 02), E2E suite (task 03). - Code checks passed: `BOR_READ_MAX_CHARS` (default 128 000, validator), pinned `READ_TRUNCATION_NOTICE` + `TRUNCATION_MARKER`, byte-identical at/under-cap read, holder `read_truncations` (no `tool_calls` impact), piece order (after `tool`, before next round), `ChatToolResultEvent`, `ToolCall` back-compat fields, `app.js` live/restore handlers, `shared.js`, `.truncated-note` CSS, `.env.example` entry, mock-LLM echo script. - **Tests:** `uv run pytest` → 1966 passed; `uv run pytest --cov=app --cov-report=term-missing` → all green, TOTAL **99%** (>90% gate). - **E2E:** `uv run pytest tests/e2e/test_read_truncation_cap.py -v --no-cov` → **3 passed** (frame order + live marker + LLM notice via echo; save→shared fidelity; short-read control). - **Regression (isolated):** `test_agent_document_tools` 4 ✓, `test_chat_history` 5 ✓, `test_share_chat` 4 ✓, `test_big_read_progress` 4 ✓, `test_stop_generation` 3 ✓. - **Lint/types:** `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors, 0 warnings. **Completion criteria:** ① over-cap read → first-cap-chars + marker + pinned notice — ✓ (unit-pinned: at-cap/cap+1/notice tests); ② user marker live/saved/shared — ✓ (E2E + frontend tests); ③ at/under cap byte-identical, no frame — ✓ (unit + control E2E); ④ top-2 `<documents>` retrieval untouched — ✓ (`app/rag/retriever.py` unmodified vs HEAD); ⑤ suite green, >90% coverage, ruff+pyright clean — ✓; ⑥ no completed-phase behavior change — ✓ (all gates green; commit left to harness per pass rules). - No defects found; no changes made this pass. Next pending phase: none in `todo/` (96 is the next free number).
This commit is contained in:
@@ -153,6 +153,35 @@ class ChatToolEvent(BaseModel):
|
||||
argument: str | None = None # the single string argument passed, or null
|
||||
|
||||
|
||||
class ChatToolResultEvent(BaseModel):
|
||||
"""SSE frame for one executed tool call whose result was truncated
|
||||
(phase 95, ``TODO.md`` L5).
|
||||
|
||||
A15 extension (owner permission 2026-09-10 — recorded in the phase 95
|
||||
overview ``00_phase.md``; PLAN.md is being redone by the owner): the
|
||||
SSE event-type list grows from six to SEVEN — ``thinking``,
|
||||
``tool``, ``retry``, ``delta``, ``done``, ``error`` and this
|
||||
optional ``tool_result``. The frame is strictly ADDITIVE: existing
|
||||
frames and clients are untouched (a client that does not know the
|
||||
type simply ignores it), and it is emitted ONLY for a truncated
|
||||
``read`` — one frame per truncated read, carrying the counts the UI
|
||||
renders as "(truncated — showing N of M chars)". It always follows
|
||||
the matching :class:`ChatToolEvent` frame for the same call (the
|
||||
line is already on screen; the marker lands a beat later — the
|
||||
phase-37/48 tool-line lifecycle is untouched). ``argument`` is the
|
||||
combined ``source/path`` the model passed (identical to the matching
|
||||
``tool`` frame's argument, so the client can match the two); a
|
||||
non-truncated read streams NO frame of this type.
|
||||
"""
|
||||
|
||||
type: str = "tool_result"
|
||||
name: str # the tool that was executed (always "read" today)
|
||||
argument: str | None = None # the model's argument (combined source/path)
|
||||
truncated: bool = True # always True on a sent frame (the emission trigger)
|
||||
chars_shown: int = Field(ge=0) # the cap kept (settings.read_max_chars)
|
||||
chars_total: int = Field(ge=0) # the document's true length
|
||||
|
||||
|
||||
class ChatDoneEvent(BaseModel):
|
||||
"""Final SSE event of a chat turn: metadata for the finished answer."""
|
||||
|
||||
@@ -456,10 +485,21 @@ class ToolCall(BaseModel):
|
||||
``AGENT_TOOLS`` names are short) and ``argument`` ≤ 2000 (the
|
||||
combined ``source/path`` identity is ≤ 120 + 1 + 1000; 2 000 is 2×
|
||||
headroom for a grep pattern).
|
||||
|
||||
Phase 95 (task 02): the truncation marker the UI renders next to the
|
||||
Reading line rides the SAME record — ``truncated`` (default False:
|
||||
the pre-phase-95 shape) + the two non-negative counts. Small
|
||||
additive fields with defaults, no migration (``ChatMessage.tools``
|
||||
is JSON) — a saved chat written before phase 95 (no fields) validates
|
||||
UNCHANGED (the phase-50 backward-compat rule) and renders without
|
||||
the marker.
|
||||
"""
|
||||
|
||||
name: str = Field(max_length=100)
|
||||
argument: str | None = Field(default=None, max_length=2000)
|
||||
truncated: bool = False
|
||||
chars_shown: int | None = Field(default=None, ge=0)
|
||||
chars_total: int | None = Field(default=None, ge=0)
|
||||
|
||||
|
||||
#: One suggestion chip (phase 83, A1): a short deterministic string —
|
||||
|
||||
Reference in New Issue
Block a user