[feature][ai-chat] Наблюдаемость page_changed-диффа в истории/экспорте + усиление ноты против перезаписи правок #288

Merged
vvzvlad merged 2 commits from feature/ai-chat-page-change-observability into develop 2026-07-02 19:30:56 +03:00

2 Commits

Author SHA1 Message Date
agent_coder 438ef091f9 fix(#288 review): markdown-safe-escape the untrusted page title in chat export
F1: pc.title (untrusted cross-user page title) was interpolated raw into the
markdown export heading. Reusing escapeAttr alone (the prompt sink's XML-attribute
sanitizer, strips < > ") is insufficient here because the sink is MARKDOWN: link
/image syntax survives, so a title like ![x](http://evil) or [phish](http://evil)
injects a remote image / clickable link into the downloaded .md disguised as a
trusted system annotation. Add markdownHeadingSafe() = escapeAttr() + backslash-
escape [ and ] (disables both [text](url) and ![text](url); a bare (url) is inert).
F2: cover the title branch — a title that collapses to empty via escapeAttr falls
to the bare heading (no ("")), and a link/image-injection title is neutralized
(non-vacuous vs the escapeAttr-only version).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 15:46:44 +03:00
claude_code c39fab70c1 feat(ai-chat): persist page-change diff to history and harden stale-page note
The #274 page_changed marker lived only in the ephemeral system prompt, so the
diff the agent saw was invisible in the chat export/history, and the note was
too weak — the agent still overwrote the user's manual edits with a full-page
replace.

- Persist the diff the agent saw as metadata.pageChanged on the assistant row
  (flushAssistant), threaded into all five flush call sites in stream(). Model
  replay (rowToUiMessage/rowParts) reads only metadata.parts, so the sibling
  never re-injects the note into the model context on later turns.
- Render the persisted diff as a labelled block (en/ru) before the message body
  in the server-side Markdown export (chat-markdown.util.ts).
- Strengthen PAGE_CHANGED_NOTE: mandate a fresh getPage re-read and targeted
  edits (editPageText/patchNode/insertNode/deleteNode) instead of a whole-page
  replace, and never revert or overwrite the user's edits.

Tests: prompt, export and service specs updated; 114 pass, tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 14:31:41 +03:00