From 7c6fb15f8e75bcbc660c1ad960eb556c7588a58a Mon Sep 17 00:00:00 2001 From: Willem Jiang Date: Tue, 6 Oct 2026 10:22:51 +0800 Subject: [PATCH] chore(doc):update the changelog for new merged the PRs (#6345) --- CHANGELOG.md | 961 +++++++++++++++++++++++++++++++++++++++++++++++- CHANGELOG_zh.md | 793 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 1732 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad75d487f6..216d230292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 This section accumulates work toward the **2.2.0** milestone ([2.2.0](https://github.com/bytedance/deer-flow/milestone/3)). -This release closes that milestone with **301 merged pull requests**. +This release closes that milestone with **439 merged pull requests**. ### Added +#### Scheduler + - **scheduler:** Opt-in conversation tools create and manage owner-bound schedules, support bounded automatic launches and per-occurrence goals, and let a scheduled agent request stopping its own schedule. Unmet goals and automatic pause use @@ -23,8 +25,6 @@ This release closes that milestone with **301 merged pull requests**. `relied_on_assumption`; interactive goal evaluation stays strict. Goal evaluator calls and tokens now count toward run usage. ([#6229]) -#### Scheduler - - **scheduler:** The tasks page shows the per-run goal and end conditions of conversation-created tasks. Run history shows whether a goal was met, including when it relied on stated assumptions; an unmet run shows a readable @@ -56,6 +56,7 @@ This release closes that milestone with **301 merged pull requests**. discard previously collected pages. `total_count` still covers all filtered matches and no new dependency, storage layer, or HTTP endpoint is involved. ([#5570]) + - **agents:** Middleware-declared tools are now covered by Layer-1 tool authorization on every assembly path (lead agent, native subagents, and the embedded client). LangChain merges each middleware's `tools` into the bound @@ -76,7 +77,8 @@ This release closes that milestone with **301 merged pull requests**. `AuthzRequest.context["tool_provenance"]` and binds the infrastructure exemption (the generated `tool_search` helper) to the concrete host-created tool object instead of its name, so a same-named foreign tool can no longer - inherit the exemption. + inherit the exemption. ([#6105]) + - **agents:** Custom agents can persist a default knowledge scope in agent settings, so a specialized agent starts each conversation with its own corpus instead of all operator-approved knowledge bases. When a new turn @@ -135,6 +137,7 @@ This release closes that milestone with **301 merged pull requests**. duplicate keys, and syntactically valid large numbers are allowed; a UTF-8 BOM is rejected. Syntax only — no schema or business-field checks; existing criteria and verdict semantics are unchanged. ([#5947]) + - **gateway:** The multi-process startup gate now fires on an explicit `deployment.multi_instance: true` (or `DEER_FLOW_MULTI_INSTANCE=1`) as well as `GATEWAY_WORKERS > 1`. The worker-count variables only see one process @@ -151,6 +154,31 @@ This release closes that milestone with **301 merged pull requests**. redis` or `DEER_FLOW_STREAM_BRIDGE_REDIS_URL` (docker-compose and the Helm chart already inject it). ([#6328]) +- **config:** `DEER_FLOW_ENV_FILE` selects one explicit UTF-8 dotenv file for the backend at + startup, shared by configuration loading, authentication startup, and the debug entry + point; relative paths resolve from the backend process working directory, existing process + variables keep precedence, and empty, missing, unreadable, or invalid-encoding selections + are rejected without fallback. Unset keeps default dotenv discovery; an explicit selection + errors when `PYTHON_DOTENV_DISABLED` disables loading. ([#6227]) + +- **agents:** Run events carry per-LLM-call telemetry: `llm.ai.response` metadata gains + optional keys for the LangChain run id, caller category (`lead_agent`, `middleware`, + `subagent`, `fallback`), provider, model, stop reason, provider-reported token counts, and + rendered request size; `llm.error` carries the same identity plus `error_type`; and the + summarization middleware emits `middleware:summarize`, flagging when the summarizer output + is identical to the existing `summary_text`. Observation only: prompts, requests, and token + accounting are unchanged, and every telemetry helper fails soft. ([#6236]) + +- **context:** Compaction skips the summarizer LLM call when the exact rendered + summary prompt previously produced the existing summary unchanged. A bounded + cache keyed on the rendered prompt plus the ordered model candidates stores + prompts whose first candidate returned the current summary byte-identical; + only first-candidate results are cached, so an unchanged fallback response + cannot suppress retrying a failed primary model, reuse also requires today's + previous summary to equal the cached output, and cache errors fall back to + normal generation. The `middleware:summarize` journal event gains + `llm_call_skipped` and `skip_count`. ([#6318]) + #### Memory - **memory:** Cross-session memory gains `user.cognitiveStyle` for stable @@ -254,6 +282,19 @@ This release closes that milestone with **301 merged pull requests**. `any`, `Public`, `Share`, `ShareCommercially`, `Modify`, `ModifyCommercially`); unset filters produce exactly the same request as before. ([#5723]) +- **search:** Serper `web_search` accepts the optional model argument + `time_range` of `day`, `week`, `month`, or `year`, mapped to Serper's + `tbs` parameter (`qdr:d|w|m|y`); omitting it or passing `null` keeps the + unrestricted request, invalid values are rejected before any HTTP call, and + Serper `image_search` is unchanged. ([#6113]) + +- **fetch:** Jina `web_fetch` supports opt-in bounded retries through + `max_retries` (default `0`, off) and `retry_budget_seconds` (default `30`) + on its `tools` entry. Only HTTP 502/503/504 and connection-establishment + errors retry; backoff waits are randomized inside a shared budget that caps + every attempt, and enabling retries can send up to `1 + max_retries` + upstream requests at additional cost. ([#6143]) + #### Skills - **skills:** Show which skills an answer loaded, with inspectable snapshots. @@ -344,6 +385,13 @@ This release closes that milestone with **301 merged pull requests**. silent, and a target disconnected while its delivery waited is dropped. WeCom implements proactive `send_notification`; other providers fail visibly in the outbox until they grow a push path. (issue #4254, [#4843], [#6135]) +- **channels:** A QQ channel connects over Tencent's official QQ Open Platform + WebSocket gateway — no public callback URL needed. It answers text C2C + messages and group @mentions with text replies while respecting QQ's passive + reply budgets (four C2C per 60 minutes, five group per 5 minutes per source), + and supports `allowed_users`, `channel_connections` one-time `/connect + ` binding, and per-sender group topics. ([#6081]) + #### Auth & guardrails - **authz:** Make the `skills` policy key govern visibility on the user-facing skill @@ -458,6 +506,58 @@ This release closes that milestone with **301 merged pull requests**. and timeouts pass the result through, and runs without a task store screen nothing. ([#5833]) +- **mcp:** The admin cache reset (`POST /api/mcp/cache/reset`) now publishes a + durable generation marker beside `extensions_config.json`, so every Gateway + worker sharing that directory retires its cached tools and pooled sessions + before the next MCP lookup — covering remote `tools/list` changes that + never touched the config file. The response reports `scope: shared_config` + for a shared-directory reset and `scope: process` when no config path + resolves; replicas with independent filesystems are not implicitly + covered. ([#6126]) + +- **extensions:** The Capability Center's extension gallery shows the + repository's bundled extension catalog by default — **Behavior change:** a + deployment without registered plugins now sees five example packages with + localized names, descriptions, search, and links to installation + instructions instead of an empty page. Runtime descriptors merge into the + same list by explicit namespace without duplicate rows. Catalog-only + entries are discovery metadata: they claim no installation or activation + state, never enter the module loader, and stay visible while runtime + discovery is pending or unavailable. ([#6188]) + +- **extensions:** Full-stack plugins can contribute candidates to the + composer's `@` mention picker. A browser module's optional + `mentionProviders` entries are searched alongside built-in skills, files, + and conversations; selecting one inserts an inline token, and submitting + sends readable `@label` text plus namespaced + `additional_kwargs.extension_mentions` metadata (at most 16 references). + Queries are debounced, truncated, viewer- and thread-bound, and fenced on + query, viewer, locale, or installed-snapshot changes; a failed or slow + provider cannot remove healthy candidates. The metadata is user input, not + authorization — plugins must revalidate referenced objects and viewer + permissions. ([#6189]) + +- **extensions:** Packaged extensions gain host-bound control of full Agent + runs (extension-api 0.2.5). Plugin backend actions and model tools receive + `context.agent_runs` — contributed routes can call + `require_agent_runs(request)` — and get `create_thread`, `start`, `resume`, + `get`, `get_state`, `wait`, and `cancel` operations that go through the + Gateway's normal admission, checkpoints, and cancellation. The handle is + bound to the authenticated user: permissions and ownership are rechecked on + every operation, the capability exists only for session or auth-disabled + credentials (PAT and internal/channel callers get none), idempotency keys + are scoped to the plugin namespace, and handles cannot be serialized or + survive a restart. ([#6190]) + +- **extensions:** A new agent-teams example plugin lets existing full Custom + Agents collaborate as a team: each member keeps its own persistent + conversation bound to its agent, members delegate work through native `@` + mentions and directed requests, and peer requests run asynchronously with a + shared activity feed on a dedicated team page. Teams are owner-scoped and + opt-in via `plugins:` in `config.yaml`; Capability Center lists the example + with localized installation information even before it is installed. + ([#6243]) + #### Persistence - **persistence:** Add a checkpoint retention service implementing the deletion @@ -477,6 +577,22 @@ This release closes that milestone with **301 merged pull requests**. externalized tool results are the named follow-ups), so unset deployments behave exactly as today. ([#5361]) +- **persistence:** With blob storage enabled, oversized tool results externalized + to a virtual `read_file` path are also persisted to the shared blob store, and + the Gateway handling the next model call restores the exact digest-verified + bytes locally — the full content previously lived only on the Gateway that ran + the tool, so a load-balanced follow-up read hit a broken path. Blob write + failures and outputs above the 64 MiB producer cap fall back inline instead of + advertising a non-durable path; blob storage stays disabled by default. ([#6159]) + +- **persistence:** With blob storage enabled, images viewed through `view_image` are + persisted to the shared blob store alongside their local path, so a later model + call landing on another Gateway rebuilds the image context from the validated + shared bytes instead of losing it when the writer's local file is gone. Store + write failures return a generic tool error instead of checkpointing state that + falsely claims cross-instance durability; blob storage stays disabled by + default. ([#6160]) + #### Frontend - **frontend:** Unify the Capability Center into a searchable catalog with plugin @@ -492,6 +608,19 @@ This release closes that milestone with **301 merged pull requests**. Bundles DingTalk/WeCom group notification and HubSpot CRM tools; existing MCP and Lark workflows are preserved and nothing is enabled automatically. ([#5497]) +- **frontend:** The composer supports inline `@` references: `@` at the cursor + or the `@` button opens a grouped picker that searches enabled skills, + current-project documents, and other conversations, inserting atomic tokens + that normal editing removes. Multiple skills can be selected in one message + (up to 16 unique skills), each validated against the user registry and agent + allowlist before activation. ([#6063]) + +- **frontend:** The composer's `/` menu now lists only the built-in `/goal` and + `/compact` commands. **Behavior change:** the web slash picker no longer selects + skills — select skills as inline `@` references instead; the legacy selected-skill + chip is removed, older drafts restore their skill selection as an inline reference, + and manually typed legacy slash text still submits as a normal message. ([#6154]) + ### Fixed - **sandbox:** With host bash enabled, the local sandbox no longer keeps a @@ -819,15 +948,7 @@ This release closes that milestone with **301 merged pull requests**. an unset or empty `DEER_FLOW_PROJECT_ROOT` becomes the checkout. An override the Gateway would reject fails `config.yaml found` with the Gateway's error, and the config checks skip. ([#5987]) -- **database:** `DatabaseConfig` now validates `pool_size`, `pool_recycle`, and - `command_timeout` strictly. Previously, YAML booleans (`true`/`false`) were - coerced to `1`/`0` respectively, allowing `pool_size: true` (pool size 1) and - `command_timeout: true` (a 1-second statement timeout) to silently pass - configuration loading. `pool_size` also accepted non-positive values (`0`, - `-1`), and `command_timeout` accepted `inf` (which never times out). - `pool_size` and `pool_recycle` now enforce positive integers, and - `command_timeout` rejects booleans and non-finite floats while retaining - `null` to disable timeouts. + - **frontend:** The optimistic human bubble keeps its quote and conversation reference chips once a file upload finishes. The upload-complete update replaced the bubble's `additional_kwargs` with only the uploaded files, so @@ -2789,6 +2910,628 @@ This release closes that milestone with **301 merged pull requests**. the started write settles, then the original `CancelledError` propagates. The read-only path keeps its existing cancellation semantics. ([#6093]) +- **sandbox:** The local container backend pins `encoding="utf-8"` and + `errors="replace"` on every text-mode docker/container-runtime subprocess + call. On hosts whose locale is not UTF-8 (e.g. cp936 Chinese Windows), + platform-default decoding previously lost entire output streams — + `stdout`/`stderr` came back `None` — crashing JSON-parsing call sites or + silently returning empty results. ([#5905]) + +- **uploads:** Cancelling an upload now waits for an already-running document + conversion worker to finish before removing its temporary source file, so + cleanup can no longer delete a file the converter is still reading. + Cancellation can therefore take as long as the conversion; it is still + propagated and no Markdown is written for the cancelled conversion. ([#5999]) + +- **agents:** The read-before-write block message now includes the existing + file's line count and a concrete `read_file` range hint instead of a vague + "read the last ~30 lines", and an inverted range returns + `(invalid line range: start_line must be <= end_line)` rather than the + misleading "no lines in range" wording that models misread as a short file. + In-band no-content `read_file` results no longer stamp a read mark that + would open the write gate. ([#6020]) + +- **frontend:** Artifact editing now requires complete file content: a + truncated byte-range preview no longer becomes the editing baseline (it can + carry the same ETag as the full file), fixing reverted edits that stayed + "unsaved" and deleted tails that silently returned. Existing drafts survive + partial reloads, and Exit editing stays available while a reload is loading, + truncated, or failing. ([#6036]) + +- **models:** The vLLM provider falls back to the legacy `reasoning_content` + wire field when `reasoning` is absent or null on non-streaming responses, + streaming deltas, and follow-up tool-call turns, so reasoning from older + vLLM-compatible endpoints is preserved; a payload carrying both fields keeps + `reasoning`. ([#6048]) + +- **discord:** When `discord.py` is missing, the Discord channel's startup + error now points at the optional dependency extra (`uv sync --extra + discord`) instead of `uv add discord.py`, which would rewrite + `pyproject.toml` in a tree that startups re-sync with `uv sync --locked`. + ([#6054]) + +- **telegram:** Outbound Telegram messages are now split (and streaming + previews truncated) by UTF-16 code units — the unit Telegram measures its + 4096-character limit in — instead of Python code points. Emoji-heavy replies + previously passed the code-point check yet exceeded the real limit, got a + deterministic 400 "Message is too long", and were dropped after retries; a + non-BMP character is also never split across two chunks now. ([#6067]) + +- **skills:** Custom-skill mutations (edit, delete, rollback, install, and + enable/disable) now drain their persistence tail across request + cancellation: the write, its history entry, and the skills prompt-cache + refresh settle together before cancellation propagates, so a cancelled + request can no longer leave a skill file without a matching history record + or a stale prompt cache. ([#6078]) + +- **subagents:** Tool results from `return_direct=True` tools (including tools + contributed by extension middleware) now reach the caller instead of being + lost to the preceding assistant message: direct-return outputs are returned + in tool-call order, and a failed direct tool marks the task failed through + the existing `subagent_error` contract while preserving the batch outputs, + including successful siblings. ([#6083]) + +- **memory:** Memory mutation endpoints (clear, fact create/update/delete, and + import) now drain their persistence write across request cancellation via + `await_drained`, so an admitted write settles before cancellation + propagates instead of the request unwinding while the storage worker is + still running. ([#6092]) + +- **paths:** Creating host-visible paths now rejects Windows reserved device + names (`CON`, `PRN`, `AUX`, `NUL`, `COM1-9`, `LPT1-9`) and segments ending + with a space or dot, on every OS, across upload filenames, local sandbox + tool and bash paths, and custom-skill support paths. Existing files stored + under such a name remain readable and editable. ([#6102]) + +- **frontend:** Web-fetch result titles no longer show trailing closing + Markdown hashes: `# Release Notes ###` now labels the workspace link + "Release Notes". Literal hashes are preserved (`C#`, escaped hashes, hashes + followed by non-ASCII whitespace), and extraction uses a backward suffix + scan to avoid quadratic backtracking on long titles. ([#6103]) + +- **agents:** Externalized tool outputs are now published atomically: content + is written to a sibling temp file and renamed into place, so a write that + fails part-way (a full disk, an interrupted request) no longer leaves a + truncated file under the final name. Externalized filenames now derive from + the sanitized tool call id instead of a random suffix, so host and sandbox + paths agree on one name per call and re-externalizing the same output no + longer leaves two files behind. ([#6109]) + +- **auth:** OIDC login no longer fails with a 500 when the provider's ID token + carries a non-ASCII or non-string `nonce` claim. Such claims are rejected + through the normal validation path, so the callback redirects to + `sso_failed` as with any other nonce mismatch; a matching non-ASCII nonce + is still accepted byte-for-byte. ([#6115]) + +- **sandbox:** In Lark broker mode, fresh AIO Bash runs close inherited pipe + stdin, so commands that read default input get immediate EOF instead of + hanging; persistent Shell commands are untouched, and explicit pipelines, + heredocs, and file redirections still supply input. The broker shim + forwards pipe/file stdin only after EOF and aborts with exit 124 without + executing when input idles past its window + (`DEERFLOW_LARK_BROKER_STDIN_GRACE_SECONDS` / + `DEERFLOW_LARK_BROKER_STDIN_TAIL_SECONDS`, default 2 seconds); broker logs + record only argument count, exit code, and elapsed time, never argument + values. ([#6117]) + +- **uploads:** Filenames matching the reserved staging pattern + `.upload-*.part` are rejected with a 400 and a rename hint before any file + in the batch is written or a sandbox acquired, so a reserved name can no + longer leave a partial upload that collides with temporary staging files. + The embedded SDK validates the whole batch before copying (`ValueError`), + and project shelf documents follow the same restriction; attaching a legacy + shelf document with a reserved name now returns 400 with a rename hint + instead of 500. **Behavior change:** filenames that previously uploaded + fine now require a rename, and older thread uploads matching the pattern + are not migrated. ([#6122]) + +- **frontend:** A re-persisted message keeps its original position in the + thread. Deduplicating a history row re-persisted under a later journal + sequence used to push the message towards the tail; the surviving copy is + now re-anchored to the earliest sequence its identity held, preferring the + earliest visible copy so a hidden control row cannot move its visible twin. + Content still converges to the newest copy. ([#6128]) + +- **frontend:** `.jl` files resolve to the Julia language again. The code-file + extension map keyed Julia by the language name instead of the `jl` + extension, so Julia files missed code-file detection and the editor + received the extension `jl` as the language rather than `julia`; the map + now carries both `jl` and `julia` keys pointing at `julia`. ([#6130]) + +- **persistence:** `RunEventStore.list_messages` honors both cursors at once: + with `after_seq` and `before_seq` supplied, the memory, JSONL, and database + backends now apply both exclusive bounds before `limit` and return the + first messages of that window in ascending order. The JSONL backend + previously ignored `after_seq` whenever `before_seq` was set and returned + the tail of the window; keep `before_seq` fixed and advance `after_seq` to + page forward through a bounded history range. ([#6136]) + +- **paths:** Uploads, new skill support files, and new local sandbox paths + reject Windows superscript device names on every platform — Windows treats + `COM¹`/`LPT¹` through `COM³`/`LPT³`, including extensions such as + `com².txt`, as reserved devices, so files created under those names were + unusable on Windows. Ordinary names like `COM⁴.txt` or `COM¹notes.txt` + remain allowed, and read/removal paths keep their existing exemptions. + ([#6148]) + +- **uploads:** The reserved staging-name check now also rejects Win32 aliases: + names that become `.upload-*.part` after trimming trailing dots and spaces + or case-folding — such as `.upload-notes.part.`, `.upload-notes.part `, or + `.UPLOAD-NOTES.PART` — are refused with a 400 before the batch touches + disk, since Windows would otherwise open them as the staging file itself. + New project shelf names apply the same check on upload, promotion, and + attach; legacy alias files already on disk stay visible and are not swept. + ([#6149]) + +- **agents:** Concurrent externalizations of the same tool output no longer + share one temporary file. Each writer creates a unique sibling + `.tool-output-*.tmp` with exclusive creation and publishes it by atomic + rename after close, so racing writers cannot truncate or delete each + other's pending output and the last successful publisher wins with complete + bytes. Published files keep the Gateway's normal umask-derived permissions, + so mounted sandboxes running under another UID keep read access; an unclean + shutdown can still leave `.tool-output-*.tmp` leftovers, which have no + automatic sweeper. ([#6150]) + +- **frontend:** Artifact files named `constructor`, `__proto__`, or dotted forms + like `data.constructor` are no longer misclassified as code files: language + detection now checks only the language map's own entries instead of its + inherited properties, so these filenames keep the existing download fallback + rather than failing to open an editor. ([#6152]) + +- **models:** The Codex provider returns the completed response as soon as the + `response.completed` SSE event arrives instead of reading until the connection + closes, so a stream the server keeps open can no longer turn a finished answer + into a read-timeout failure. `response.failed`, `response.incomplete`, and + `error` events now raise with the provider's error code/message or incomplete + reason, and partial output is never returned as a successful answer. ([#6155]) + +- **nginx:** Opening the stack at `http://127.0.0.1:2026` or `http://[::1]:2026` now + redirects (301, safe methods only) to the same URL on `localhost`, so the login + session created at one loopback spelling is visible at the other. Browsers scope + cookies per host, which previously split the session across the two origins. + API calls and WebSocket handshakes pass through untouched, and non-loopback + hostnames never match. ([#6158]) + +- **config:** The shipped Gemini example for Google's official OpenAI-compatible + endpoint now uses the standard reasoning contract (`thinking: required`, + `dialect: none`, supported `reasoning_effort` values) on + `gemini-3.1-pro-preview`, instead of a top-level `thinking` field that the + endpoint rejects with HTTP 400 `Unknown name "thinking"`. Migration notes cover + profiles copied from the previous example. ([#6161]) + +- **sandbox:** The Tenki cloud sandbox provider now requires SDK 1.4.0+, which can + return `timed_out=True` together with exit code zero. Timed-out commands keep + their partial output and report `Error: command timed out` with + `Exit Code: 124` instead of appearing to succeed; they are not retried and the + sandbox stays usable. Warm-pool health probes reject timed-out or failed probes + even when the output contains an `ok` line. ([#6162]) + +- **uploads:** Converted upload outlines and previews now validate the source's + modification timestamps in addition to device, inode, and size, so rewriting an + uploaded document in place with different bytes of the same length no longer + serves an outline describing the old content. **Behavior change:** legacy + ownership records without source timestamps are rejected — re-upload the source + with `uploads.auto_convert_documents: true` to restore conversion; files stay + available and the unvalidated Markdown shows as a standalone file in the + historical listing. ([#6165]) + +- **tui:** The TUI composer is now a multiline editor, so pasted code, stack traces, + and multi-paragraph prompts keep their line breaks and indentation, and `Enter` + sends the complete document. Up/Down move the cursor inside the input and fall + back to input history on the first/last visual row (wrapped lines included), + leaving the slash palette and PageUp/PageDown transcript keys unchanged. ([#6168]) + +- **config:** `tool_output` character/count budgets and every per-tool + `tool_overrides` value now require non-negative integers: YAML booleans are + rejected instead of being coerced to `0`/`1`, and negative overrides — which + previously, for example, silently disabled externalization for one tool via + `tool_overrides: {web_fetch: -1}` — fail configuration loading with the field + and tool named. Numeric strings, integer-valued floats, and explicit zero keep + working; a zero per-tool override still disables only externalization. ([#6172]) + +- **telegram:** The rich-message length check now measures the reply in UTF-16 code + units, the way Telegram enforces its cap, instead of Python code points. + Emoji-heavy replies that fit by code points previously passed the check, were + rejected by Telegram, and degraded to chunked plain text, losing the reply's + markdown formatting and wasting an API round trip. ([#6174]) + +- **community:** The Firecrawl and fastCRW web-search tools normalize a + configured `max_results` before handing it to the search client. The raw + YAML value previously went into the request's `limit` unvalidated; + booleans, fractional values such as `3.5`, zero, negative numbers, + non-numeric strings, and an explicit null now log a warning and fall back + to the default of 5, while integer strings such as `"8"` are accepted. + ([#6175]) + +- **frontend:** HTML artifact previews ignore literal `` text that is + not a real element. Injecting the artifact base href was previously skipped + by a regex that also matched a base tag in a comment, in script text, or + inside a `