5408 Commits
Author SHA1 Message Date
Wei Chengqian 9441402872 fix: preserve Telegram hidden links in converted messages (#10557) 2026-10-16 00:01:41 +08:00
Soulter db4ce0c4b0 chore: bump version to 4.29.0 (#10560) v4.29.0 2026-10-14 20:22:22 +08:00
Wei Chengqian 626c6d33dc fix: handle Dify workflow stream errors (#10545)
* fix: handle Dify workflow stream errors

* fix: keep Dify workflow diagnostics in server logs
2026-10-14 19:35:00 +08:00
Wei Chengqian f9e363bc28 fix: handle Coze chat failures and incomplete streams (#10523) 2026-10-14 19:29:03 +08:00
Wei Chengqian 34aa123083 fix: preserve Anthropic tool parameter schemas (#10520)
* fix: preserve Anthropic tool parameter schemas

* fix: reject non-object Anthropic tool schemas
2026-10-14 19:25:48 +08:00
dependabot[bot] 6ca3931925 chore(deps): bump github/codeql-action (#10552)
Bumps the github-actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 4.38.2 to 4.38.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v4.38.2...v4.38.3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.38.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-14 19:01:05 +08:00
Soulter ffd77c90e6 feat: improve log filtering and console workspace (#10514) 2026-10-14 19:00:42 +08:00
Wei Chengqian 44def64716 fix: preserve Lark rich text titles and whitespace (#10543)
* fix: preserve Lark rich text titles and whitespace

Preserve titles, paragraph breaks, indentation, and adjacent text in received and quoted Lark posts. Keep command matching compatible with leading whitespace and bot mentions, and add focused regression coverage.

* refactor: use event extras for Lark quote metadata
2026-10-14 18:19:28 +08:00
Wei Chengqian 770bd0e069 fix: migrate pnpm configuration for pnpm 11 compatibility (#10541) 2026-10-13 21:22:18 +08:00
Soulter 03906b986e fix: make plugin view info usable on mobile (#10519) 2026-10-13 21:20:37 +08:00
Soulter f0f983c849 fix(cron): move dynamic job context after conversation history (#10549) 2026-10-13 21:17:34 +08:00
lingyun14 48ba71ad74 fix: missing dashboard i18n keys, add a key check to CI, and drop orphaned ru-RU entries. (#10505)
* fix(i18n): add missing dashboard translation keys

Several call sites reference keys that exist in no locale bundle, so the
i18n helper returns its truthy "[MISSING: key]" placeholder and the UI
leaks that instead of a translation.

- TemplateListEditor.vue used core.common.collapse/expand for the
  expand/collapse tooltip; neither key existed. Add both to zh-CN,
  en-US, ja-JP and ru-RU.
- BackupDialog.vue alerts core.common.unauthorized, which was absent
  from every bundle. Add it to all four.
- PersonaSelector.vue reads core.shared.personaSelector.editPersona;
  it existed in ru-RU only. Add it to zh-CN, en-US and ja-JP, matching
  the wording of the existing createPersona entries.
- KBList.vue uses create.embeddingModelRequired as the validation rule
  when creating a knowledge base. Sibling keys exist in all four
  bundles but this one was missed.

The unauthorized values mirror the wording already used by the
equivalent messages.errors.auth.unauthorized entries.

* feat(i18n): add a dashboard i18n key check and fix key drift

The i18n helper returns "[MISSING: <key>]" (a truthy string) when a key is
absent, so `t(key) || 'fallback'` never falls back and the placeholder
leaks into the UI. A key missing from *every* locale is invisible to
locale-vs-locale comparison (src/i18n/validator.ts), because all bundles
agree on it, so those defects can only be caught by checking source usage
against the bundles.

Add dashboard/scripts/check-i18n-keys.mjs, run via `pnpm run i18n:check`
and wired into the Dashboard CI workflow. It assembles locale trees by
parsing each locale index.ts (bundle filenames are not runtime
namespaces: features/tool-use.json -> features.tooluse), strips comments
before scanning, respects the module-relative t()/tm() bindings from
useModuleI18n(), and skips plugin-provided strings. It has no dependency
on installed packages, so it runs without node_modules.

It reported two classes of problem, both fixed here:

- Used in source but absent from all four locales:
  features.knowledge-base.detail.retrieval.debugMode and
  .tsneVisualization (RetrievalTab.vue debug switch and t-SNE image alt).
- Locale drift against the zh-CN baseline:
  features.subagent.form.personaPreview missing from en-US and ru-RU, and
  the config-metadata use_markdown description/hint missing from ja-JP.

ru-RU additionally carries ~42 keys with no zh-CN counterpart (notably
agent_runner_type and the per-provider agent runner ids, which the backend
does use). Those are pre-existing and left alone; this check only fails on
keys that source code actually references.

* chore(i18n): remove orphaned ru-RU translation keys

Two groups of ru-RU keys reference paths the dashboard no longer reads,
left behind when zh-CN was migrated and ru-RU was not:

- features.tooluse.dialogs.syncProvider.* (22 leaves)
  The syncProvider subtree now lives at the namespace root, which is what
  McpServersSection.vue reads via useModuleI18n('features/tooluse'). PR #9932
  relocated it for zh-CN but left ru-RU's copy under `dialogs`, so ru-RU
  carried both.

- 20 features.config-metadata entries under provider_settings
  Those paths were restructured into agent_runner.config.* by PR #9821 for
  zh-CN. Only the entries with no zh-CN counterpart are removed here; most
  provider_settings children are still live under their new paths and are
  kept. platform_group.platform.kook_bot_nickname has no backend field at
  all (no reference anywhere in astrbot/).

All four locales now expose an identical key set (4130 leaves each, no
missing and no extra keys), so this also removes the last locale drift the
i18n check could see.

* fix(i18n): make the key check enforce drift and scan injected translators

Addresses both blocking findings from the review.

1. Locale drift did not fail the check. The drift branch only printed its
   results and never set the failure flag, and it compared in one direction
   only (baseline keys absent elsewhere), so a locale could gain keys the
   baseline lacks without any complaint. It now compares both directions and
   fails on either, which is safe to enforce because the key sets are already
   identical across all four locales (4130 leaves each).

2. Keys read through an injected translator were never scanned. The call
   pattern only matched bare `t(` / `tm(`, so `props.tm('models.metadata.image')`
   in ProviderModelsPanel.vue (8 call sites, reached via :tm="tm" from
   ProviderPage / ProviderChatCompletionPanel) escaped validation entirely. A
   key that resolves nowhere previously passed; it is now reported.

   Injected usage carries no local module binding, so the parent's namespace is
   unknown at the call site. Rather than guess it, such a key is only reported
   when it resolves nowhere in any locale under any namespace, which keeps the
   new pass free of false positives. The tradeoff is documented in the script:
   a key that exists in some other namespace but not the injected one is not
   caught.

Verified: deleting a baseline-only key from en-US now fails (was PASS), adding
an extra key to en-US now fails (was ignored), and the injected-translator probe
in ProviderModelsPanel.vue now fails with the call site (was PASS). The clean
tree still passes all three sections.
2026-10-11 22:50:58 +08:00
DFGHJ43 f220171057 fix(dashboard): stop the mobile browser UI covering the sidebar bottom (#10512)
* fix(dashboard): keep the mobile shell and sidebar inside the visible viewport

The app shell and the sidebar were sized with 100vh, which on mobile browsers is the largest viewport (browser UI retracted). The drawer bottom - the last navigation entries plus the Settings/version footer - was painted underneath the browser UI, and the oversized flex box left no scrollable overflow, so the list could not be scrolled either.

Size the shell, the content card and the sidebar with dynamic viewport units (100dvh) instead. Desktop is unaffected because dvh equals vh there.

* fix(dashboard): size the chat sidebar with dynamic viewport units

The chat drawer kept 100vh, so on mobile its bottom - the session list tail and the Settings entry - was painted underneath the browser UI, exactly like the main sidebar. Its top: 0 is anchored to the same containing block (.v-application__wrap), so 100dvh is the matching fix.
2026-10-11 22:46:55 +08:00
Soulter 170eeca08b fix: disambiguate plugin view sidebar items (#10502)
* fix: disambiguate plugin view sidebar items

* docs: explain plugin view metadata

* docs: clarify plugin view metadata location
2026-10-11 18:07:46 +08:00
td1336065617andSoulter 595f990b64 fix(qq_official): 出站引用回复未填充 message_reference (#10489)
* fix(qq_official): 出站发送引用回复时填充 message_reference

QQ 官方 API 的 message_reference.message_id 对「非机器人发的消息」需从
消息事件的 message_scene.ext 数组的 msg_idx 字段获取,格式为 REFIDX_...;
它与 message.id(ROBOT1.0_...)不是同一个值。此前适配器从不填充该字段,
导致开启 reply_with_quote 后回复不显示引用气泡。

改动:
- 新增 _extract_self_ref_idx(),从 raw_data.message_scene.ext 取 msg_idx
- _post_send_one() 在消息链含 Reply 组件时填充 message_reference
- 群聊与单聊共用同一路径;无引用/取不到 ID 时保持原行为

参考:botpy http.py 在同时传 file_image 时会去除 message_reference。

* test(qq_official): 覆盖出站引用回复的 message_reference 填充

- 有 Reply 组件且能取到 msg_idx 时填充 message_reference
- 无 Reply 组件时不填充(不改变原有行为)
- 取不到 msg_idx 时静默降级
- 直接构造 PatchedGroupMessage/PatchedC2CMessage 走 send_streaming 端到端路径

* fix(qq_official): 出站支持 @ 发送人(At 组件转 <@openid>)

QQ 官方群消息用内容中的 <@openid> 标记表示 @,没有独立字段;此前 _parse_to_qqofficial() 只映射 Plain/Image/Record/Video/File,At 组件落到末尾 else 被丢弃,导致 reply_with_mention 开启后不生效。

AtAll 的 qq 为 all,官方群聊不支持该写法,显式跳过。

* test(qq_official): 覆盖 At 组件转 <@openid> 与 AtAll 跳过

* fix(qq_official): Reply.id 已是 REFIDX 时直接使用(响应评审)

评审指出:此前无条件使用当前入站消息的 msg_idx,当调用方在 Reply.id 中给出其它消息时,message_reference 会指向当前消息而非目标消息。

实测 message.id 为 ROBOT1.0_...(137 字符),message_scene.ext 的 msg_idx 为 REFIDX_...(118 字符),格式与内容都不同,因此不能直接把 Reply.id 当作 message_reference.message_id。

处理:Reply.id 以 REFIDX_ 开头时原样使用;否则回退到本条消息自身的 msg_idx。

* test(qq_official): 覆盖 Reply.id 为 REFIDX / 为 message.id 两种情形

* fix(qq_official): @ 改用官方现行嵌入文本协议 <qqbot-at-user id="" />

此前使用的 <@userid> 是官方标记「即将弃用」的旧格式,实测在群聊中会被客户端原样显示为文本,@ 不生效。官方文档(文本交互 / 使用 @ 能力)给出的现行格式为 <qqbot-at-user id="" />。

AtAll 对应的 <qqbot-at-everyone /> 官方限定「仅在文字子频道可用」,群聊场景继续跳过。

* test(qq_official): 断言改为官方现行 @ 协议 <qqbot-at-user id="" />

* revert(qq_official): 撤回 @ 相关改动

* revert(qq_official): 撤回 @ 相关测试

* fix(qq_official): 补上漏掉的 Reply 导入

重建文件时遗漏了 Reply 的导入,而 _post_send_one() 里的 isinstance(_component, Reply)位于 next(genexp, "") 中,只要消息链非空就会被求值,导致运行期 NameError、所有出站消息发送失败。

同时按 ruff format 要求修正切片空格。

* Delete tests/test_qqofficial_outbound_quote.py

---------

Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
2026-10-11 11:54:44 +08:00
Wei Chengqian c31c1dae36 fix: restore playback and downloads for sent WebChat media (#10483)
* fix(webchat): restore playback and downloads for sent media

* fix(webchat): wrap sent media attachments to prevent clipping
2026-10-11 11:50:38 +08:00
Wei Chengqian a457e59923 fix: include Discord reply text in model requests (#10452) 2026-10-11 11:46:44 +08:00
Soulter a06ea2e4b6 fix(dashboard): preserve data tab memory when leaving workspace (#10498) 2026-10-11 11:42:34 +08:00
Wei Chengqian b2696ce266 fix: localize LLM request errors in ChatUI (#10478)
* fix: localize LLM request errors in ChatUI

* refactor: limit LLM error localization to ChatUI rendering

* fix: localize built-in LLM errors using the request locale

* docs: regenerate OpenAPI artifacts for chat locale

* fix: preserve existing conversation error handling
2026-10-11 09:58:06 +08:00
Wei Chengqian 1d474855a1 fix: deliver Slack TTS audio with text fallback (#10496)
* fix: deliver Slack TTS audio with text fallback

* fix: avoid duplicate Slack TTS fallback text

* fix: preserve Slack audio order and retry text delivery
2026-10-11 09:56:54 +08:00
Soulter f9ad3003af chore: bump version to 4.29.0-beta.2 (#10493)
* chore: bump version to 4.29.0-beta.2

* docs: align beta.2 English release notes with Chinese
v4.29.0-beta.2
2026-10-11 00:16:04 +08:00
Soulter 736b1f1bbc fix(dashboard): simplify local isolation and improve usage documentation (#10487)
* fix(dashboard): simplify local isolation controls and setup guidance

* docs: organize plugin and built-in AI guides

* docs: expand plugin guides for newcomers

* docs: refresh AI and extension guides with current UI screenshots

* docs: remove WebUI route details from usage guides
2026-10-10 22:11:31 +08:00
一张白纸 fb49dfa54b fix(dashboard): update plugin market submit link and label (#10471)
* fix(dashboard): update plugin market submit link and label

- Point the "submit plugin" button to https://cloud.astrbot.app instead of
  the legacy AstrBot_Plugins_Collection repository link
- Change the button icon from mdi-github to mdi-upload
- Drop the "repository" wording from the submitRepo label:
  zh-CN "提交我的插件", en-US "Submit My Extension",
  ja-JP "プラグインを登録", ru-RU "Добавить мой плагин"

* fix(dashboard): edit `cloud.astrbot.app` site to `/publish`
2026-10-10 12:40:47 +08:00
一张白纸 845517e794 chore(github): update plugin submission links in issue template config (#10472)
Point the plugin submission contact links to the /publish page and rename the English entry from "Upload Plugin" to "Submit Plugin" so it matches the Chinese label.
2026-10-10 12:40:22 +08:00
ankaandSoulter 8763c58a98 feat(dashboard): add model settings shortcuts and access control section (#10321)
* feat(dashboard): add model settings shortcuts and access control section

* docs: align remaining configuration paths and permission guidance

---------

Co-authored-by: Soulter <905617992@qq.com>
2026-10-10 00:23:24 +08:00
Soulter 9fa49087b1 fix(dashboard): keep content frame fixed and remove sidebar customization (#10480)
* fix(dashboard): keep content frame fixed and remove sidebar customization

* fix(dashboard): pin config and settings panes and restore card borders
2026-10-09 23:00:48 +08:00
エイカク 33ef43be3f fix: keep async tool generators in one task (#10474)
* fix: add demand-driven task ownership for async generators

* fix: keep async tool generators in one task

* fix: publish generator cleanup errors without blocking

* style: format tool lifecycle regression tests
2026-10-09 20:10:37 +09:00
Niansia 28a10c17ab fix: warn when a background task result is not sent to the user (#10398)
When a background task finishes, the main agent is woken up and has to
call send_message_to_user to deliver the result. If it ends the turn
without doing so, the user never sees the result, and nothing was
logged because the existing warning only covers an empty response.

Log a warning with the tool name and task id when nothing reached the
user's session during the wake-up run.

Fixes #10395
2026-10-09 18:50:36 +09:00
エイカク c6f415092c fix: avoid recording cron sends without a matching platform (#10468) 2026-10-09 16:56:30 +09:00
一张白纸 b01285aa75 chore(github): add plugin submission links to issue template config (#10455)
- Add "Upload Plugin" and "提交插件" contact links to the new issue page
- Point both links to [AstrBot Cloud](https://cloud.astrbot.app) so users can submit plugins without opening a regular issue
2026-10-09 15:02:49 +08:00
lingyun14 74465625ed Fix/reject unresolvable image refs (#10434)
* fix: reject unresolvable image references instead of faking a local path

A plugin can hand over a media reference such as
`/api/v1/files/tokens/<token>`. Neither an existing file nor a valid
payload, it was still returned as a local file, so `to_path()` resolved it
into a path that does not exist on disk (on Windows
`C:\api\v1\files\tokens\<token>`). That fabricated path reached
`req.image_urls` and the prompt, and the real failure only surfaced later
as a warning while preparing model images.

`_materialize_media_ref()` now enforces its own contract:

- A reference that exists but is not a regular file (directory, FIFO,
  socket, device) raises ValueError for every media family.
- Image references must resolve to an existing file, URL or base64 payload.
  Images are read right after resolution, so a missing source is a failure
  rather than a file that may appear later.
- Audio, video and generic file references keep returning a local path that
  does not exist yet: `ensure_wav()` and the STT retry loop rely on that for
  platform races such as NapCat handing over a voice path before it is
  readable.

Supporting changes:

- `to_base64_data(strict=False)` still degrades to `None` when an image ref
  cannot be materialized, which payload assembly relies on to skip bad refs.
- `describe_media_ref()` reports unresolved refs as such instead of calling
  them a local media path.
- Unavailable image warnings now include the original reference.

Refs #10264

* fix: keep pathlib tolerance for over-long and non-regular media refs

`_materialize_media_ref()` replaced `path.exists()` with a bare `path.stat()`
and caught only OSError. pathlib's `exists()` also swallows ValueError, which
Windows raises for paths past the 32767 character limit and for embedded NUL
bytes. A bare base64 payload longer than that limit failed with
`ValueError: stat: path too long for Windows` instead of being decoded, and
audio/video/file references lost the pending-path behaviour for the same input.

`to_base64_data()` had the matching gap: it caught only OSError around
resolution, so a directory or empty reference escaped as ValueError even in
non-strict mode. Payload assembly (openai/anthropic/gemini sources) relies on
`None` there to skip one bad image instead of failing the whole request, and
`is_recoverable_image_error()` already classifies ValueError as recoverable.

Both sites now catch what pathlib and the module's own recovery predicate treat
as a missing or unusable local file. Regression tests cover an oversized bare
base64 image, an oversized pending audio path, and directory/empty references
through the non-strict image path.

* docs: describe the media reference error contract precisely

Review feedback on the pull request:

- `_materialize_media_ref()` only raises FileNotFoundError for a missing file
  URI when the reference is an image. Audio, video and file references return
  the pending path instead, which the `Raises:` section now states instead of
  reading as if it applied to every media family.
- `Image.convert_to_file_path()` can raise ValueError as well: for an existing
  non-regular path, and already for an empty url/file before this branch.

Docstrings only, no behaviour change.
2026-10-09 13:11:50 +08:00
Wei ChengqianandSoulter d2fb912e30 fix: prevent Discord self-reply loops (#10425)
Co-authored-by: Soulter <905617992@qq.com>
2026-10-09 12:48:00 +08:00
Wei Chengqian 03ce6525d8 fix: preserve outbound Slack user mentions (#10448) 2026-10-09 12:38:26 +08:00
Wei Chengqian 5994167942 fix: preserve links and mentions in Slack lists (#10447) 2026-10-09 12:37:53 +08:00
Wei Chengqian d8ef9dd3b0 fix: send Discord video components as attachments (#10423)
* fix: send Discord video components as attachments

* fix: stream Discord video attachments from disk

* fix: preserve Discord video filenames from URLs
2026-10-09 12:34:59 +08:00
Yufeng Heandzouyonghe 6b2e41791e fix(provider): merge custom server tools with registered tools in Anthropic requests (#10437)
* fix(provider): merge custom server tools with registered tools in Anthropic requests

* fix(provider): dedupe tool names and tolerate null custom body

Address review findings on the custom-server-tools merge:

- A custom tool sharing a name with a registered tool (or another
  custom tool) produced duplicate definitions that the API rejects.
  Merge by name instead; the custom entry wins, matching the override
  authority extra_body already had for every other top-level key.
- An explicit null custom_extra_body crashed on .get("tools"). Null
  or non-dict values now normalize to an empty body.

* fix(provider): avoid shadowed id in Anthropic streaming tool merge

---------

Co-authored-by: zouyonghe <62183434+zouyonghe@users.noreply.github.com>
2026-10-08 22:45:04 +09:00
Niansia a8bb0803ef fix: fall back from a failed context summary without long retries (#10436)
* fix: fall back from a failed context summary without long retries

The summary request in LLMSummaryCompressor used the provider's default
retries, so an exhausted quota (HTTP 429) was retried five times with
backoff before compression fell back to truncation, holding up the reply
even after switching the chat model.

Request the summary once, since a failure already falls back to
truncation. When the context still exceeds the limit, drop whole rounds,
oldest first, and recount after each one until it fits, instead of
halving the messages once without checking again. The latest round is
always kept.

Fixes #10433

* fix: leave the token count out of the fallback warning

CodeQL flags values from count_tokens() as sensitive data, so logging the
count in the new warning raised a clear-text logging alert. The count is
already in the "Compress completed" line just before, so drop it here.

* fix: log how many rounds the context fallback dropped
2026-10-08 22:43:14 +09:00
Niansia 30820a48fa fix(provider): keep Anthropic tool calls whose streamed input is empty (#10432)
* fix(provider): keep Anthropic tool calls whose streamed input is empty

* fix(provider): keep the input from the Anthropic tool_use start event

The tool_use buffer always started from an empty dict, so a provider
that puts the arguments in the content_block_start event instead of
streaming them lost them. Start from that input and let streamed JSON
replace it only when there is any.
2026-10-08 22:01:50 +09:00
Soulter 9248baf902 test(computer-tools): expect the Capabilities path in permission error assertion (#10446) 2026-10-08 00:14:35 +08:00
Soulter eef9712621 fix(computer-tools): point permission-denied hints at the Capabilities tab (#10445)
The Agent Computer Use settings moved from 'Config -> Normal Config ->
AI -> Agent Computer Use' to 'Config -> AI -> Capabilities -> Agent
Computer Use'. Update the three permission-denied messages returned to
the model/user so the remediation path matches the current WebUI
navigation, consistent with the existing docs.
2026-10-08 00:10:58 +08:00
Soulter cae8f1d13a feat(dashboard): add distinct hints for local permission access modes (#10444)
On platforms without a local process sandbox (e.g. Windows), the local
permission matrix collapses into a single access-mode dropdown. The
'File access only' and 'Unrestricted access' options previously shared
the same host-scope subtitle, making them look identical. Give each
mode its own hint that spells out whether Shell/Python execution and
network access are allowed.
2026-10-08 00:02:21 +08:00
Soulter 65cbe98f9a feat: ship astrbot CLI in Docker image for in-container password reset (#10442)
* feat: ship astrbot CLI in Docker image for in-container password reset

Install the project itself into the image so the `astrbot` console
script (including `astrbot password` and `astrbot conf set`) is
available inside the container, and mark /AstrBot as an initialized
AstrBot root via the .astrbot marker file.

This lets Docker users reset a forgotten dashboard password with:

    docker exec -it astrbot astrbot password

Document the workflow in the zh/en Docker deployment guides, including
the required container restart for the new password to take effect.

* docs: reorganize FAQ password reset section by deployment method

Split the forgot-password FAQ entry into Docker, uv/pip package, and
other deployment subsections. The Docker entry uses the in-container
CLI shipped with the image; the uv/pip entry covers `astrbot password`
and `astrbot run --reset-password`; the manual cmd_config.json edit
remains as the universal fallback.

* docs: mention --username option in uv/pip FAQ password reset entry

* docs: drop --reset-password mention from FAQ to keep reset steps unambiguous

* docs: clarify that the first astrbot in docker exec is the container name

* feat: use editable install for astrbot CLI in Docker image

Install the project with 'uv pip install -e .' so the astrbot console
script always reflects the source tree baked into the image, avoiding
drift between the CLI and the running server code.
2026-10-07 23:38:40 +08:00
Soulter 8831d95025 feat(dashboard): scoped path-token auth for plugin views and complete the views rename (#10415)
* refactor(dashboard): complete the plugin pages to views rename

Finish the remaining Pages -> Views rename on the dashboard:

- Fix the plugin card "Open View" button crash by pointing
  openPluginWebui at the renamed PluginView route
- Rename PluginPagePage.vue to PluginViewPage.vue and update its
  internals (loadPluginView, pluginViewTitle, .plugin-view-* classes)
- Rename openPluginWebui -> openPluginView and the open-webui emit to
  open-view; pluginPageTitle/Description -> pluginViewTitle/Description;
  isPluginPageRoute -> isPluginViewRoute
- Rename i18n keys (pluginWebui -> pluginView, openWebui -> openView,
  pluginPageType -> pluginViewType, messages) across en/zh/ja/ru locales

The postMessage bridge channel name (astrbot-plugin-page) is kept
unchanged because it is part of the wire protocol shared with
plugin_page_bridge.js.

* feat(dashboard): authenticate plugin view assets with scoped path tokens

Serve plugin view assets from
/api/v1/plugins/{id}/views/{page}/_t/{token}/... so the scoped token
travels in the URL path. Relative URLs inside the view then inherit the
token through normal path resolution, which removes the need for
server-side HTML/CSS/JS URL rewriting on this route.

- Add the _t routes with a require_plugin_view_token dependency
  (v1 paths bypass the dashboard auth middleware)
- Extend the plugin page asset token to a session-length TTL (7 days,
  aligned with the dashboard session) with a page_session purpose claim,
  still scoped to a single plugin and page
- Serialize the view entry content_path as the _t URL, keeping the
  asset_token query for plugins that parse location.search
- Keep the legacy /api/plugin/page/content/...?asset_token= form working
  during the transition
- Rename the OpenAPI tag Plugin Pages -> Plugin Views, document the new
  route, regenerate the frontend API client, and update the zh/en
  plugin-pages docs

* refactor(dashboard): remove the legacy plugin page content route and URL rewriting

The path-token view route makes the compatibility surface unnecessary:

- Drop /api/plugin/page/content/... (query asset_token form)
- Drop the redundant authenticated asset routes /plugins/page/assets,
  /plugins/view/assets, and /plugins/{id}/(pages|views)/{page}/assets/...
  which had no callers outside the generated client
- Remove the HTML/CSS/JS URL rewriting helpers; view assets are always
  served raw and only HTML gets bridge SDK injection plus theme handling
- Slim PluginPageAuth down to the bridge SDK endpoint, the only
  non-v1 path still authenticating scoped query tokens
- serialize_plugin_page no longer falls back to the legacy content path
- Update the plugin-pages docs (zh/en) and the OpenAPI spec, and
  regenerate the frontend API client

* refactor(dashboard): drop legacy plugin page API aliases

Remove the page-named plugin view API aliases now that the views rename
is complete and legacy compatibility is intentionally dropped:

- DELETE /api/v1/plugins/page(s) and /plugins/{plugin_id}/pages{,/{page_name}}
  aliases; /plugins/view(s) and /plugins/{plugin_id}/views{,/{page_name}}
  are the only entry config routes
- Remove the unused /api/v1/plugins/page-bridge-sdk.js route; the live
  bridge SDK stays at /api/plugin/page/bridge-sdk.js
- Dashboard calls the view-named endpoints; regenerate the API client
  and the OpenAPI scopes docs

* refactor(dashboard): keep plugin page APIs as deprecated view aliases

Restore the page-named plugin view routes removed in the previous commit
and keep them for a transition period, with view as the canonical API:

- /plugins/page(s) and /plugins/{plugin_id}/pages{,/{page_name}} serve as
  aliases of the corresponding /views routes (same handlers)
- The OpenAPI spec marks the page operations deprecated and points new
  clients at the view routes; the v1 page-bridge-sdk.js route is restored
- The dashboard keeps calling the view endpoints by default

* refactor(dashboard): rename the plugin view path parameter to view_name

The view entry and path-token asset routes now use {view_name} instead of
{page_name} in the OpenAPI-visible path, and the service layer renames its
internal page_name variables to match. Wire-compatible surfaces stay
unchanged: the page_name JWT claim, the pageName JSON field, query
parameters, and the deprecated /pages aliases. Error messages and the
plugin view docs (zh/en) complete the Page to View rename.
2026-10-06 18:18:22 +08:00
100yoGranny 2de9059293 fix: avoid blocking audio format probes (#10055) 2026-10-06 18:11:55 +08:00
Yufeng He e1d30f301c fix(provider): replace retired gemini-embedding-exp-03-07 default with gemini-embedding-001 (#10393)
The Gemini Embedding provider ships enabled by default with
gemini-embedding-exp-03-07 as both the config default and the
source-level fallback. Google retired that experimental model when
gemini-embedding-001 went GA, so a fresh deployment that enables the
provider fails on its first request.

gemini-embedding-001 keeps the configured output_dimensionality path
intact: it supports MRL, so the default 768 dimensions still hold.

Ref #10317
2026-10-06 15:09:44 +08:00
Soulter 93e446e9f7 perf(dashboard): reduce first-screen load (lazy-load VAD/Monaco/i18n/ApexCharts, gzip responses) (#10413)
* perf(dashboard): lazy-load VAD dependencies on demand

Load onnxruntime-web and vad-web via dynamic script injection when the
VAD initializes instead of render-blocking script tags in index.html,
so they no longer delay the first paint on every page load.

* perf(dashboard): shrink first-screen bundle and gzip responses

- Lazy-load Monaco Editor (~3.3 MB) via an async wrapper component
  instead of importing it in main.ts; the editor bundle now loads only
  when an editor is first mounted.
- Load i18n locale data on demand: each language becomes its own async
  chunk instead of bundling all four locales (~1.4 MB of JSON) into the
  entry.
- Register vue3-apexcharts locally in StatsPage instead of globally in
  main.ts, moving ~470 KB out of the entry chunk.
- Add Starlette GZipMiddleware to the dashboard ASGI app so static
  assets and API responses are compressed (SSE is excluded by default).

Entry chunk: 4.5 MB -> 648 KB (gzip: 1.25 MB -> 210 KB).
2026-10-06 15:08:06 +08:00
C₂₂H₂₅NO₆andC₂₂H₂₅NO₆ 55c2dd3d43 feat: remember the last opened tab on the Data & Logs page (#10251)
* feat: make Logs the default tab on the Data & Logs page

- Reorder the Data & Logs tabs to Logs -> Statistics -> Conversations -> Trace
- Redirect /data to the Console (Logs) route so the sidebar entry lands on logs
- Keep the legacy /dashboard/default and /observability redirects pointing at Stats to preserve old bookmark behavior
- Sync the documented tab order and default-tab note in both zh/en WebUI docs

* feat: 数据与日志页面记住上次打开的标签页

根据维护者的 review 意见,不再改变数据与日志页面的默认行为,改为记住用户上次打开的标签页。

- 撤回上一个提交中的改动:恢复标签页顺序(统计 → 对话 → 日志 → 追踪),activeTab 的回退值恢复为 statistics,/data 的默认跳转恢复为 Stats,并撤回中英文文档中「默认展示日志」的描述。
- DataPage.vue:监听当前激活的标签页(immediate),将其对应的路由名写入 localStorage 的 data_last_tab。旧版对话页(conversations/legacy)归属于对话标签页,记录为 Conversation。
- MainRoutes.ts:访问 /data 时读取 data_last_tab,若为有效的数据标签页路由名(Stats / Conversation / Console / Trace)则跳转到该标签页,否则回退到 Stats,因此首次访问的默认行为保持不变;query 与 hash 照常透传。
- /dashboard/default、/observability 等旧入口以及 /data/logs 等直接地址的行为不变。
- docs/zh、docs/en 的 webui.md:补充说明首次默认显示统计,之后会回到上次打开的标签页。

---------

Co-authored-by: C₂₂H₂₅NO₆ <Sisyphbaous-DT-Project@users.noreply.github.com>
2026-10-06 14:17:14 +08:00
Niansia 900a700b32 fix: match command group names as whole words (#10372)
* fix: match command group names as whole words

CommandGroupFilter matched messages with a plain str.startswith, so a
group named "math" also matched "mathematics". Once the group was
restricted to admins, non-admins got the permission-denied reply for any
message that merely started with the group name, such as a normal
private chat message or another plugin's command.

Match group names the same way CommandFilter does: collapse whitespace,
then require an exact match or the name followed by a space.

* fix: normalize command group equality whitespace
2026-10-06 13:32:02 +08:00
俞可 · YU KEandSoulter c1356bb076 fix(dify): handle CRLF event separators in streaming responses (#10392)
* fix(dify): handle CRLF event separators in streaming responses

* Delete tests/test_dify_api_client.py

---------

Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
2026-10-06 13:18:17 +08:00
一张白纸 1c655b3719 feat(dashboard): preview staged chat input images in ChatUI (#10376)
Click a staged image thumbnail in the ChatUI input area to open a full-screen preview overlay, matching the behavior of sent message images. The preview state is self-contained within ChatInput so both Chat and StandaloneChat get it without extra wiring.
2026-10-06 13:17:04 +08:00