refactor(agents): migrate .agent/ planning tree to .agents/
Standardize on the .agents/ directory (shared with project skills): phases/, user_stories/, reports/, screenshots/, validate.sh, and phase-sessions/ + pipeline.log all move to .agents/ (git mv preserves history; runtime artifacts move alongside). Updates every reference in AGENTS.md, README.md, .gitignore, app docstrings, and test story headers. Historical KB content in data/ and the runtime pipeline.log transcript are left untouched.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
# Phase 48 — Stop / Cancel an In-Flight Answer
|
||||
|
||||
**Source:** `TODO.md` L3 — "Need a way to stop or cancel generation of text in the chat"
|
||||
**Story:** n/a (TODO-derived — owner roadmap confirmation 2026-08-29)
|
||||
**Context:** A chat turn is `POST /api/chat` → an SSE stream (`thinking` → `tool` → `delta` → `done`/`error`) produced by `app/api/chat.py`'s `stream()` over `app.rag.agents.run_agent` (grounded turns) or `LLMClient.chat_stream` (deflected turns — `app/rag/llm.py`, an `AsyncOpenAI` streaming request). The frontend consumes it in `frontend/assets/app.js` (`handleSend` → `fetch` → `readSSE`) with the never-stale state machine (`setUiState`: idle → thinking → streaming → done|error → idle, PLAN §7.4) and the phase-14 localStorage conversation (`bor.chat.v1`; optional per-record fields like `thinking`/`tools`/`stopped` are the no-version-bump convention).
|
||||
|
||||
## Objective
|
||||
The user can stop an in-flight answer at any time: while a turn is live the Send button becomes a **Stop** button; stopping keeps the partial answer on screen and in the persisted conversation (marked as stopped), settles the UI to idle with no error banner, and makes the server tear down the model's HTTP stream promptly so the local model stops generating.
|
||||
|
||||
## Dependencies
|
||||
- `14_chat_persistence` (complete) — the `bor.chat.v1` conversation records + save points; the stopped partial persists through the same helpers (new optional `stopped` field).
|
||||
- `17_thinking_display` + `20_sources_midstream_bug` (complete) — the thinking/tool frames + the "thinking-only turns persist nothing brain-side" convention the pre-token stop path follows.
|
||||
- `37_agent_document_tools` (complete) — the agent loop grounded turns run in; its per-round streams must tear down on abandon too.
|
||||
|
||||
## Tasks
|
||||
1. `01_llm_stream_teardown.md` — deterministic model-stream teardown on client disconnect + the cancelled-turn log line (no `query_log` row).
|
||||
2. `02_stop_button.md` — the Send↔Stop one-button morph, `AbortController` abort, partial keep + `stopped` persistence + restore note; CSS.
|
||||
3. `03_e2e_stop_generation.md` — the story Playwright suite + regressions + commit.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: `tests/unit/test_llm_stream_teardown.py` (the openai stream is `aclose`d on full consumption AND on mid-iteration abandon), `tests/unit/test_chat_cancel.py` (an SSE turn torn down mid-stream: fake LLM stream closed, cancel log line written, no `query_log` row, `error`/`done` paths unchanged) — both follow the fake/override patterns of `tests/unit/test_chat_gate.py` + `tests/fakes.py`.
|
||||
- Coverage: **>90%** on `app/` (validate.sh gate).
|
||||
- Frontend source pins per the house pattern (`tests/unit/test_frontend_feedback.py` style): in-flight enabled + "Stop" label, `AbortController` signal, `stopped` record key, no-error stop path.
|
||||
- E2E (mandatory, A16): `tests/e2e/test_stop_generation.py`, run in isolation.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] While a turn is in flight the button reads **Stop** (rose treatment, ≥44px, focus-visible); clicking it (or pressing Enter) stops the turn.
|
||||
- [ ] A mid-stream stop keeps the partial text, shows a "Stopped" note, no error banner; the `bor.chat.v1` record carries `stopped: true`; a reload restores it with the note.
|
||||
- [ ] A pre-token stop leaves the question in the conversation, no brain bubble, no error banner.
|
||||
- [ ] The model's stream is closed promptly on abandon (unit-proven); a cancelled turn logs `cancelled=true` and writes no `query_log` row; completed/error turns log and record exactly as before.
|
||||
- [ ] `uv run pytest` green; coverage TOTAL >90%.
|
||||
- [ ] `uv run pytest tests/e2e/test_stop_generation.py -v --no-cov` green in isolation (DB up).
|
||||
- [ ] Regression E2E suites green in isolation: `test_chat_persistence.py`, `test_loading_feedback.py`, `test_chat_rag.py` (the loading-feedback suite's in-flight button assertions are revised in place to the new contract — see task 02).
|
||||
- [ ] `uv run ruff check . && uv run pyright` clean.
|
||||
- [ ] One `--no-gpg-sign` commit; phase dir moved to `.agents/phases/complete/`.
|
||||
|
||||
## Locked decisions
|
||||
- **A10 untouched** — `/api/chat` stays stateless; stopping is a client disconnect, no new endpoint, no server session state.
|
||||
- **Owner-locked (2026-08-29, roadmap confirmation):** (1) one-button morph — Send becomes Stop while in flight; click *or* Enter stops; (2) the partial answer is kept, persisted with the optional `stopped` marker, no sources, no error banner; a pre-token stop persists nothing brain-side (phase-20 convention); (3) the server closes the model stream on disconnect, logs `cancelled=true`, and skips `query_log` for cancelled turns.
|
||||
- **Revised contract (owner-locked 2026-08-29):** the in-flight button is the Stop control — enabled, labeled "Stop", spinner hidden (the typing dots / Thinking block / tool lines remain the in-flight feedback per the phase-06/17 contract); `tests/e2e/test_loading_feedback.py` assertions that pinned the old disabled-"Thinking…"-button + visible-spinner state are updated in place to the new contract (its 120s-guard + live-region pins stay).
|
||||
- **A16/A17 honoured** — one story E2E suite, one atomic commit.
|
||||
|
||||
## Commit
|
||||
```bash
|
||||
git add -A .agents/ app/ frontend/ tests/ && git commit --no-gpg-sign -m "feat(chat): stop an in-flight answer — Send becomes Stop, the partial is kept and persisted, the model stream is torn down"
|
||||
```
|
||||
Reference in New Issue
Block a user