* fix(context-compressor): guard token counting against quadratic BPE hang on long CJK runs
js-tiktoken's BPE merge loop is O(n^2) over the bytes of a single
pre-tokenizer piece. The GPT pat_str groups contiguous \p{L} characters
into one piece, and CJK text has no spaces, so a long CJK run becomes a
single huge piece. A ~14k-char CJK message (~42k bytes) produced ~1.8B
operations, pinning the Node event loop at 100% CPU. encode() never
throws in this case — it just hangs — so the existing catch-based
heuristic fallback never fired, and the whole server became unresponsive
(local HTTP requests timed out, nginx returned 504).
Detect a pathological contiguous letter/CJK run (>2000 chars) up front in
countTokens and countTokensForModel and use the cheap CJK-aware heuristic
instead. Normal text, even very long but space-separated, still takes the
exact tiktoken path.
Adds regression tests covering the CJK hang, normal text, and long
space-separated text.
* docs: add chat-chain-changes fragment for CJK token-counting guard
---------
Co-authored-by: paulocavallari <paulocavallari@users.noreply.github.com>
* feat(bridge): refactor compression to use DB history and add structured logging
- Extract buildDbHistory() to share message loading between buildCompressedHistory and forceCompressBridgeHistory
- forceCompressBridgeHistory now reads from local DB instead of using Python-provided messages, ensuring consistency with api_server path
- Pass sessionId to compressor for snapshot-aware compression
- Add force_compress flag to bridge chat requests
- Add bridgeLogger structured logging for compression lifecycle
- Simplify schemas, session-sync, and providers
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix bridge compression history handling
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>