Anchor-owned process-prose rows kept the newest character as streaming-markdown
lookahead until the turn settled, so Compact Worklog prose immediately before a
tool card appeared one character short mid-stream. Finalize the cached parser
once when the row becomes 'completed' (running the same media/link/table cleanup
as the main body parser), guarded by a 'finalized' flag + self-heal on edit.
Co-authored-by: starship-s <starship-s@users.noreply.github.com>
Embed local MEDIA: image refs as inline base64 at snapshot-build time,
gated by a workspace/attachment-root allow-list, 512KiB cap, image-MIME
allow-list, and magic-byte content validation. Everything else (traversal,
symlink-escape, file://, non-image, SVG, oversize) becomes a static
placeholder. Closes P0 #6174 (public shares no longer emit authenticated
/api/media requests for local paths).
Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
* fix: resolve provider alias before matching _PROVIDER_MODELS (#6148)
* fix(backend): resolve model prefix through _PROVIDER_ALIASES before matching
- Fix IndentationError: dedent _canon_prefix assignment to 8 spaces
- Rebased onto upstream/master
- Add regression test: alias prefix (z-ai) resolves to canonical key (zai)
and routes cross-provider through OpenRouter (#6131)
* fix(#6131): canonicalize both sides via _canonicalise_provider_id (idempotent) + regression tests
Codex found _resolve_provider_alias breaks Qwen (qwen->alibaba, not in _PROVIDER_MODELS)
and cross-routes alias-of-active-provider (raw vs canon asymmetry). Use the idempotent
_canonicalise_provider_id for both membership and equality. Adds 2 regression tests.
* docs(changelog): stamp #6148 alias-prefix model routing fix
---------
Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com>
Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: nesquena-hermes <agent@webui>
* fix(#6389): remove clear_offline_buffer() teardown blocks that cause data loss on late reconnects
Keep the real leak fixes:
- unregister_stream_owner() in both streaming.py and gateway_chat.py
- AGENT_INSTANCES.pop() in gateway_chat.py
The clear_offline_buffer() calls could clear a channel's offline buffer
after a late reconnect already fetched the channel but before it subscribed,
causing the reconnecting client to lose all buffered events (including
stream_end) and hang indefinitely on heartbeats.
STREAMS.pop() already releases registry ownership; GC reclaims the buffer
once the last outstanding channel reference is dropped, correctly preserving
replay for in-flight reconnects.
* docs(changelog): stamp #6389 stream-teardown leak fix
---------
Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: nesquena-hermes <agent@webui>
- Add only .ts→text/plain and .tsx→text/plain to MIME_MAP (exclude .js,
.jsx, .css and other unsafe mappings that would make workspace files
executable as same-origin scripts)
- Add test_ts_artifact_served_as_text_plain_with_attachment — asserts
Content-Type: text/plain, Content-Disposition: attachment, and
X-Content-Type-Options: nosniff for .ts files via /api/media
- Add test_tsx_artifact_served_as_text_plain_with_attachment — same for .tsx
- Add test_file_raw_js_not_served_as_inline_executable — proves that a
workspace .js file falls through to application/octet-stream instead of
text/javascript via /api/file/raw
Closes#6372
- Fix test_stream_done_runs_scroll_preserving_collapse_pass_after_disarm:
assert _doneLiveScrollSnapshot is captured before arm, and the
argument-bearing _renderMessagesWithScrollSnapshot({_prescrollSnapshot:_doneLiveScrollSnapshot})
call is used after disarm (the no-arg literal no longer exists).
- Add test_prescroll_snapshot_bypasses_capture_no_option_fallback_still_captures:
Node.js behavioral harness that supplies a sentinel _prescrollSnapshot,
stubs _captureMessageScrollSnapshot with a counter, and proves:
1) sentinel reaches _restoreMessageScrollSnapshotSameFrame without a capture
2) no-option / empty-{} calls still capture normally (callers at
static/ui.js:9565, 14416, 14424).