Skip to content

Dev#1823

Merged
SengokuCola merged 18 commits into
mainfrom
dev
Jun 16, 2026
Merged

Dev#1823
SengokuCola merged 18 commits into
mainfrom
dev

Conversation

@SengokuCola

@SengokuCola SengokuCola commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator
  • ✅ 接受:与main直接相关的Bug修复:提交到dev分支
  • 新增功能类pr需要经过issue提前讨论,否则不会被合并
  • 🌐 i18n 提醒:除 bootstrap 或紧急修复外,请不要把非 zh-CN 目标翻译作为常规 GitHub 编辑面;常规翻译以 Crowdin -> l10n_* PR 回流为准,详见 docs/i18n.md

请填写以下内容

(删除掉中括号内的空格,并替换为小写的x

    • main 分支 禁止修改,请确认本次提交的分支 不是 main 分支
    • 我确认我阅读了贡献指南
    • 本次更新类型为:BUG修复
    • 本次更新类型为:功能新增
    • 本次更新是否经过测试
    • 如果本次修改涉及 src/A_memorix,我确认已阅读 src/A_memorix/MODIFICATION_POLICY.md,不涉及则无需勾选
  1. 请填写破坏性更新的具体内容(如有):
  2. 请简要说明本次更新的内容和目的:

其他信息

  • 关联 Issue:Close #
  • 截图/GIF
  • 附加信息:

Summary by CodeRabbit

版本 1.0.5 发布说明

  • 新功能

    • 新增行为学习配置与互通组支持
    • 插件市场支持嵌入模式
  • 优化

    • 仪表盘图标系统升级,支持Streamline图标库
    • 主题系统重构,增强future-retro风格定制能力
    • 长期记忆数据库迁移流程优化,降低存储体积与性能要求
    • 人物画像证据分类温度参数可配置
  • 样式

    • UI组件样式统一与布局调整
    • 移除登录页波浪背景特效

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 541fb59c-a7b6-41e3-8d3b-244c44965896

📥 Commits

Reviewing files that changed from the base of the PR and between 76dc5eb and a6f61ea.

📒 Files selected for processing (9)
  • README.md
  • dashboard/src/hooks/use-auth.ts
  • dashboard/src/router.tsx
  • dashboard/src/routes/plugin-detail.tsx
  • dashboard/src/routes/plugins/InstalledTab.tsx
  • dashboard/src/routes/plugins/MarketplaceTab.tsx
  • dashboard/src/routes/plugins/PluginCard.tsx
  • dashboard/src/routes/plugins/PluginMarketplacePage.tsx
  • dashboard/src/routes/plugins/UpdatesTab.tsx

Walkthrough

该 PR 发布 1.0.5/1.4.5 版本,重构 Dashboard 主题与样式配置,新增插件嵌入页面与详情弹窗流程,扩展行为学习和人物画像配置,并调整后端工具记录、数据库迁移、统计与表达方式查询逻辑。

Changes

主发布改动

Layer / File(s) Summary
版本发布与版本注入
AGENTS.md, changelogs/changelog.md, dashboard/app-version.ts, dashboard/*vite*.ts, dashboard/src/lib/version.ts, src/common/version.py, src/plugin_runtime/__init__.py, pyproject.toml, requirements.txt, README.md
更新 1.0.5 / 1.4.5 版本信息,并把 Dashboard 与主程序版本改为从 package.jsonpyproject.toml 读取后注入。
Dashboard 图标、布局与样式基础设施
dashboard/src/components/layout/*, dashboard/src/components/ui/*, dashboard/src/components/search-dialog.tsx, dashboard/src/index.css, dashboard/src/routes/config/model/..., dashboard/src/routes/config/modelProvider/..., dashboard/src/routes/index.tsx, dashboard/src/routes/settings/AboutTab.tsx
新增 Streamline 图标与 AccentPanel,重写侧边栏/顶栏布局令牌和 future-retro 样式覆盖,并同步更新多处通用 UI 与列表操作图标。
主题配置重构与认证外观
dashboard/src/components/animation-provider.tsx, dashboard/src/lib/theme/*, dashboard/src/routes/auth.tsx, dashboard/src/routes/settings/*, dashboard/src/i18n/locales/*, dashboard/src/hooks/use-auth.ts, dashboard/src/hooks/use-background.ts
主题配置从全局覆盖迁移为按 dashboardStyle 分支存储,移除波浪背景设置,更新认证页复古装饰、设置页控件和对应文案。
Dashboard 页面与资源控制台重排
dashboard/src/components/chat-scope-filter-panel.tsx, dashboard/src/components/embed-page-shell.tsx, dashboard/src/routes/resource/..., dashboard/src/routes/config/prompts.tsx, dashboard/src/components/memory/*, dashboard/src/components/dynamic-form/DynamicConfigForm.tsx
新增范围筛选面板与嵌入壳组件,重排表达方式、黑话、行为学习、知识库与提示词页面的容器和交互布局。
插件市场嵌入与详情弹窗流程
dashboard/src/router.tsx, dashboard/src/routes/plugins/*, dashboard/src/routes/plugin-detail.tsx, dashboard/src/routes/plugin-*-embed.tsx
新增插件市场、插件配置和镜像设置的嵌入路由,并将插件详情改为由外层传入回调控制的页面/弹窗双模式。
配置模型、行为学习与人物画像参数
src/config/*, src/common/utils/utils_config.py, src/learners/behavior_selector.py, src/A_memorix/*, dashboard/src/routes/config/bot/*, src/llm_models/utils_model.py
官方配置与 Bot 配置页新增行为学习/互通组字段和人物画像证据分类温度参数,并接入行为作用域解析与相关表单 hook。
后端记录、迁移与统计查询
src/common/database/migrations/v27_to_v28.py, src/maisaka/*, src/services/*, src/webui/routers/expression.py
工具执行记录改为统一载荷构造与落库后处理,数据库迁移改为空表重建,维护任务/统计缓存更新,表达方式查询增加有效会话过滤。

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Mai-with-u/MaiBot#1820: 同样修改了 PersonProfileServiceperson_profile.evidence_classification_temperature 相关代码路径。
  • Mai-with-u/MaiBot#1818: 两者都修改了 src/common/database/migrations/v27_to_v28.py 与工具记录 payload 清理相关实现。
  • Mai-with-u/MaiBot#1713: 两者都直接改动了 dashboard/src/routes/plugins/UpdatesTab.tsx 与插件详情交互链路。
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
src/services/image_path_maintenance_service.py (1)

10-24: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

import 顺序不符合项目规范。

根据 AGENTS.md 的 import 规范,标准库(asyncio)应该放在第三方库(richsqlalchemy)之前。当前 import asyncio 在第三方库导入之后,需要调整顺序。

📦 建议的 import 顺序
+import asyncio
+
 from rich.console import Console
 from rich.progress import (
     BarColumn,
     MofNCompleteColumn,
     Progress,
     ProgressColumn,
     Task,
     TimeElapsedColumn,
     TimeRemainingColumn,
 )
 from rich.text import Text
 from sqlalchemy import text
 
-import asyncio
-
 from src.common.database.database import get_db_session

根据项目编码规范,标准库和第三方库的导入应该放在本地模块导入的前面,各个导入块之间应该使用一个空行进行分隔。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/image_path_maintenance_service.py` around lines 10 - 24, The
import statement order in the file does not follow the project's import
specification. Currently, the standard library import (asyncio) is placed after
third-party library imports (rich and sqlalchemy), but according to the project
coding standards, standard library imports must come before third-party library
imports. Reorganize the imports by moving the asyncio import to the beginning of
the import section, before the rich and sqlalchemy imports, and ensure there is
one blank line separating the standard library imports from the third-party
library imports.

Source: Coding guidelines

dashboard/src/routes/resource/behavior/index.tsx (1)

1620-1650: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

补齐取消捕获场景的清理逻辑,避免节点拖拽状态“卡死”

Line 1620 仅在 onPointerUp 中清理 pointerRefnode.fixed。当出现 pointercancel / 丢失捕获时,这段清理不会执行,节点可能长期保持 fixed=true,后续布局与交互会异常。

建议修复
+  const resetPointerState = useCallback(
+    (target?: HTMLCanvasElement, pointerId?: number) => {
+      const pointer = pointerRef.current
+      if (pointer.nodeId) {
+        const node = nodesRef.current.find((item) => item.id === pointer.nodeId)
+        if (node) node.fixed = false
+        heatRef.current = Math.max(heatRef.current, 0.55)
+      }
+      pointerRef.current = { mode: null, nodeId: null, lastX: 0, lastY: 0 }
+      if (target && pointerId !== undefined) {
+        try {
+          target.releasePointerCapture(pointerId)
+        } catch {
+          // ignore capture-release race
+        }
+      }
+    },
+    []
+  )
+
-  const handlePointerUp = useCallback((event: PointerEvent<HTMLCanvasElement>) => {
-    const pointer = pointerRef.current
-    if (pointer.nodeId) {
-      const node = nodesRef.current.find((item) => item.id === pointer.nodeId)
-      if (node) node.fixed = false
-      heatRef.current = Math.max(heatRef.current, 0.55)
-    }
-    pointerRef.current = { mode: null, nodeId: null, lastX: 0, lastY: 0 }
-    event.currentTarget.releasePointerCapture(event.pointerId)
-  }, [])
+  const handlePointerUp = useCallback((event: PointerEvent<HTMLCanvasElement>) => {
+    resetPointerState(event.currentTarget, event.pointerId)
+  }, [resetPointerState])
       <canvas
         ref={canvasRef}
         className="h-full w-full touch-none"
         onDoubleClick={handleDoubleClick}
         onPointerDown={handlePointerDown}
         onPointerMove={handlePointerMove}
         onPointerUp={handlePointerUp}
+        onPointerCancel={handlePointerUp}
+        onLostPointerCapture={() => resetPointerState()}
       />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/routes/resource/behavior/index.tsx` around lines 1620 - 1650,
The handlePointerUp callback cleans up the pointer state and resets node.fixed
to false, but this cleanup only executes on the onPointerUp event. When a
pointercancel event occurs or pointer capture is lost, the cleanup logic does
not run, leaving nodes stuck in a fixed state. Create a shared cleanup function
that resets the pointerRef and unfixes the node, then call this function from
both handlePointerUp and a new handlePointerCancel callback. Add the
onPointerCancel handler to the canvas element alongside the existing pointer
event handlers to ensure cleanup occurs in all pointer termination scenarios.
dashboard/src/routes/settings/AppearanceTab.tsx (2)

1080-1082: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

继承态判断要覆盖所有非 page 背景层。

useBackground 对任意非 page 层只要 inherit 为 true 都会返回 page 背景;这里仅禁用 sidebar/header,会导致 card/dialog 开启继承后仍可编辑实际不会生效的资源、效果和 CSS。

🐛 建议修复继承层判断
-                            const isInheritedLayer =
-                              (layerId === 'sidebar' || layerId === 'header') &&
-                              (bgConfig[layerId]?.inherit ?? false)
+                            const isInheritedLayer =
+                              layerId !== 'page' && (bgConfig[layerId]?.inherit ?? false)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/routes/settings/AppearanceTab.tsx` around lines 1080 - 1082,
The inheritance state check in the isInheritedLayer condition is too
restrictive, only covering sidebar and header layers. Since useBackground
returns the page background for any non-page layer when inherit is true, the
condition should be expanded to cover all non-page layers including card and
dialog. Replace the explicit layer name checks (layerId === 'sidebar' || layerId
=== 'header') with a more general check that covers any layer that is not
'page', so that all layers with inherit enabled are properly marked as inherited
and prevented from being edited.

256-272: ⚠️ Potential issue | 🟠 Major

应该存储 sanitizeCSS 返回的净化后 CSS,而不是原始输入

代码调用 sanitizeCSS(val) 获取了净化结果,但存储到 styleCustomCSS 的仍是原始的 val。虽然 applyThemePipeline 在应用前会再次调用 sanitizeCSS 进行净化(pipeline.ts 第 204 行),但应该在保存时就使用净化后的 CSS (result.css),而不是依赖后续的再次净化。请改为:

[dashboardStyle]: result.css,

这样可以避免在配置中积累未净化的 CSS,确保存储层的安全性和清晰度。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/routes/settings/AppearanceTab.tsx` around lines 256 - 272, The
handleCSSChange function calls sanitizeCSS(val) to obtain sanitized CSS content
but stores the original unsanitized val in the styleCustomCSS configuration
instead of using the sanitized result.css from the sanitizeCSS return value.
Update the updateThemeConfig call to store result.css in the [dashboardStyle]
key instead of val. This ensures that only sanitized CSS is persisted in the
configuration, improving security and clarity at the storage layer rather than
relying on subsequent re-sanitization during theme application.
dashboard/src/routes/config/modelProvider/ProviderList.tsx (1)

289-301: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

“测试连接”按钮也应提供可访问名称。

该按钮当前只渲染图标,建议与编辑/删除保持一致补充 aria-label

建议修复
                           <Button
                             variant="outline"
                             size="sm"
                             onClick={() => onTest(provider.name)}
                             disabled={testingProviders.has(provider.name)}
                             title="测试连接"
+                            aria-label={`测试厂商 ${provider.name} 连接`}
                           >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/routes/config/modelProvider/ProviderList.tsx` around lines 289
- 301, The test connection button (the one with onClick={() =>
onTest(provider.name)} that renders a Loader2 or Zap icon) is missing an
aria-label attribute. Add an aria-label attribute to the Button component with
an appropriate accessible label (e.g., "测试连接") to provide screen reader support,
following the same pattern as the edit/delete buttons in this component.
dashboard/src/routes/auth.tsx (1)

268-274: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

为主题切换按钮补充 aria-label

当前是纯图标按钮,只有 title;建议补充 aria-label,避免读屏器无法稳定读出控件名称。

建议修复
         <button
           type="button"
           data-auth-theme-toggle="true"
           onClick={toggleTheme}
           className="absolute right-4 top-4 rounded-lg p-2 hover:bg-accent transition-colors z-10 text-foreground"
           title={actualTheme === 'dark' ? t('auth.switchToLight') : t('auth.switchToDark')}
+          aria-label={actualTheme === 'dark' ? t('auth.switchToLight') : t('auth.switchToDark')}
         >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/routes/auth.tsx` around lines 268 - 274, The theme toggle
button in the auth route has a title attribute for accessibility but is missing
an aria-label attribute, which is needed for screen readers to reliably announce
the button's purpose. Add an aria-label attribute to the button element with the
same conditional logic as the title attribute, using the translation keys
t('auth.switchToLight') or t('auth.switchToDark') based on the current theme, to
ensure both visual tooltips and screen reader users have proper access to the
button's function.
dashboard/src/routes/config/model/components/Pagination.tsx (1)

85-96: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

首尾分页按钮缺少可访问名称(仅图标)。

Line [85]-Line [96] 与 Line [136]-Line [147] 的按钮都没有可读标签,读屏用户无法识别按钮语义(首页/末页)。

建议修复(示例)
<Button
  variant="outline"
  size="sm"
  onClick={() => onPageChange(1)}
  disabled={page === 1}
  className="hidden sm:flex"
+ aria-label="第一页"
+ title="第一页"
>
...
<Button
  variant="outline"
  size="sm"
  onClick={() => onPageChange(totalPages)}
  disabled={page >= totalPages}
  className="hidden sm:flex"
+ aria-label="最后一页"
+ title="最后一页"
>

Also applies to: 136-147

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/routes/config/model/components/Pagination.tsx` around lines 85
- 96, The pagination buttons at lines 85-96 (first page button calling
onPageChange(1)) and lines 136-147 (last page button) lack accessible labels,
preventing screen reader users from understanding their purpose. Add an
aria-label attribute to both Button components: use "Go to first page" for the
button at lines 85-96 and "Go to last page" for the button at lines 136-147.
This will ensure assistive technology users can identify the pagination controls
and their semantics.
dashboard/src/routes/config/model/components/ModelTable.tsx (1)

90-98: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

避免用对象引用计算 actualIndex,会导致错位编辑/删除风险。

在 Line [90] 使用 m === model 做索引定位过于脆弱;一旦分页/过滤过程返回了新对象实例,actualIndex 会变成 -1,随后 Line [141]-Line [154] 的编辑、删除、勾选操作都会落到错误索引。dashboard/src/routes/config/model/components/ModelCardList.tsx Line [46] 也有同样问题,建议一并修复。

建议修复(示例)
- const actualIndex = allModels.findIndex((m) => m === model)
+ const actualIndex = allModels.findIndex(
+   (m) =>
+     m.name === model.name &&
+     m.model_identifier === model.model_identifier &&
+     m.api_provider === model.api_provider
+ )
+ const canOperate = actualIndex >= 0
...
- onClick={() => onEdit(model, actualIndex)}
+ onClick={() => canOperate && onEdit(model, actualIndex)}
+ disabled={!canOperate}
...
- onClick={() => onDelete(actualIndex)}
+ onClick={() => canOperate && onDelete(actualIndex)}
+ disabled={!canOperate}

Also applies to: 141-154

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/routes/config/model/components/ModelTable.tsx` around lines 90
- 98, The code uses object reference equality (m === model) to find actualIndex
in ModelTable.tsx, which fails when models are recreated during pagination or
filtering, resulting in actualIndex becoming -1. This causes subsequent edit,
delete, and selection operations at lines 141-154 to target wrong indices.
Replace the object reference comparison with a unique identifier comparison
(such as model.id or model.name) in the findIndex call. Apply the same fix to
ModelCardList.tsx at line 46 where the same pattern exists.
🧹 Nitpick comments (7)
AGENTS.md (1)

50-50: ⚡ Quick win

句子不完整。

Line 50 的规则描述不完整:"禁止改动 legacy_migration,此文件以固定" 缺少后半句。建议补充为"此文件以固定形式存在"或类似的完整表述。

✏️ 建议的修正
-禁止改动 legacy_migration,此文件以固定
+禁止改动 legacy_migration,此文件以固定形式存在
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` at line 50, The sentence on line 50 in AGENTS.md describing the
legacy_migration rule is incomplete and cuts off mid-thought. Complete the
sentence by adding the missing second half after "此文件以固定" to make it
grammatically complete and convey the full intended meaning, such as by
appending descriptive text like "形式存在" or another appropriate completion that
clarifies the constraint on the legacy_migration file.
src/common/version.py (1)

8-8: 💤 Low value

可选:为 PROJECT_ROOT 添加注释说明。

PROJECT_ROOT 的计算依赖于当前文件位置(src/common/version.py),使用 parents[2] 向上两级到达项目根目录。建议添加注释说明这一假设,以便未来移动文件时能及时调整。

💡 建议的注释
+# 项目根目录:从当前文件 src/common/version.py 向上两级
 PROJECT_ROOT: Path = Path(__file__).resolve().parents[2]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/version.py` at line 8, The PROJECT_ROOT variable calculation in
src/common/version.py relies on a hardcoded parent directory depth of 2, which
assumes the current file (version.py) remains at src/common/. Add a comment
above the PROJECT_ROOT assignment explaining this directory structure dependency
and noting that the parents[2] index must be adjusted if the file location
changes in the future.
src/services/image_path_maintenance_service.py (1)

384-455: ⚖️ Poor tradeoff

评估移除批次间节流的影响。

重构后的实现移除了原先每批处理后的 asyncio.sleep(_BATCH_SLEEP_SECONDS) 节流逻辑。虽然这能加快处理速度,但可能会在短时间内产生较高的数据库 I/O 负载。

建议:

  1. 在低峰期测试移除节流后的系统负载表现
  2. 如果观察到明显的性能影响(如阻塞其他数据库操作),可以考虑保留一个较短的 sleep(如 0.01 秒)作为"呼吸时间"

当前实现添加了丰富的进度显示,用户体验得到提升 ✓

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/image_path_maintenance_service.py` around lines 384 - 455, The
refactored `run_image_path_maintenance_background()` function removed the
batch-to-batch throttling (asyncio.sleep call that previously occurred between
batch processing iterations). To mitigate potential high database I/O load
spikes, add back a brief async sleep interval after each batch is processed in
the while loop. Insert an `await asyncio.sleep()` call with a short duration
(such as 0.01 seconds) after the progress update operations complete and before
the next batch iteration begins. This provides a "breathing time" for the system
to handle other database operations without causing excessive load.
src/maisaka/utils/tool_post_execution.py (1)

56-59: ⚡ Quick win

避免在关键依赖缺失时静默跳过反馈入队。

Line [56]-[59] 在 saved_recordchat_stream 缺失时直接 return,会让“应创建的反馈任务”悄然丢失且缺少排障线索。建议至少记录 warning(含 tool_call_id 与缺失字段)。

♻️ 建议改动
 async def _enqueue_memory_feedback_task(
@@
-    if saved_record is None:
-        return
-    if chat_stream is None:
-        return
+    if saved_record is None:
+        logger.warning(
+            f"{log_prefix} 跳过反馈纠错任务入队: 缺少 saved_record, tool_call_id={invocation.call_id}"
+        )
+        return
+    if chat_stream is None:
+        logger.warning(
+            f"{log_prefix} 跳过反馈纠错任务入队: 缺少 chat_stream, tool_call_id={invocation.call_id}"
+        )
+        return
Based on learnings: “Avoid excessive fallback logic; when errors occur, ensure they are exposed completely and immediately rather than being masked by fallback mechanisms.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/maisaka/utils/tool_post_execution.py` around lines 56 - 59, The early
return statements when `saved_record` or `chat_stream` are None in the
tool_post_execution function silently skip critical feedback enqueueing without
any logging, making it difficult to debug when these dependencies are missing.
Instead of returning silently, add warning-level log messages before each return
that include diagnostic context such as the `tool_call_id` and the specific
field that is missing (either "saved_record" or "chat_stream"). This ensures
that missing critical dependencies are properly exposed in logs rather than
being masked.

Source: Learnings

src/maisaka/reasoning_engine.py (1)

27-28: ⚡ Quick win

按项目规范整理这次触达的本地导入块。

新增的 src.maisaka.utils 导入应和其他 from src.maisaka... 跨目录本地导入放在同一组,并避免被同级相对导入 .chat_loop_service 打断;否则 Ruff/项目导入规范可能继续报错。As per coding guidelines, “本地同级模块使用相对导入,跨目录使用以 from src 开头的绝对导入”,且本地导入需按项目规范分组。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/maisaka/reasoning_engine.py` around lines 27 - 28, The newly added
imports from src.maisaka.utils (handle_tool_post_execution_effects,
build_tool_record_payload, and normalize_tool_record_value) are not organized
according to project conventions. Reorganize the import block in
reasoning_engine.py so that all absolute imports starting with "from
src.maisaka..." are grouped together, separate from relative imports like
".chat_loop_service". The two utils imports should be placed with other
cross-directory absolute imports from src.maisaka, not intermixed with
same-level relative imports, to comply with the project's import grouping
standards and avoid Ruff linting errors.

Source: Coding guidelines

dashboard/src/lib/theme/storage.ts (1)

136-143: 💤 Low value

parseJSONStorage 异常未在调用点外处理时可能导致问题。

parseJSONStorage 在 JSON 格式无效时会抛出异常,调用点已用 try/catch 包裹。但函数本身返回 undefined 表示键不存在,与抛异常表示格式错误的语义不一致。

当前实现可行,但建议统一返回 undefined 处理所有无效情况以简化调用点。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/lib/theme/storage.ts` around lines 136 - 143, The
parseJSONStorage function has inconsistent error handling: it returns undefined
when a key doesn't exist, but throws an exception when JSON.parse fails due to
invalid format. Wrap the JSON.parse(value) call in a try/catch block to catch
any JSON parsing errors, and return undefined when parsing fails instead of
allowing the exception to propagate. This unifies the function's behavior to
always return undefined for any invalid case (missing key or invalid JSON
format), eliminating the need for callers to handle exceptions from this
function.
src/common/database/migrations/v27_to_v28.py (1)

135-155: 💤 Low value

_count_tool_records_for_rebuild 重复调用。

该函数在 migrate_v27_to_v28 (line 22) 和 _replace_tool_records_with_empty_v28_table (line 140) 中被调用两次,执行相同的 COUNT 查询。

考虑将第一次调用的结果传递给 _replace_tool_records_with_empty_v28_table 以避免重复查询。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/database/migrations/v27_to_v28.py` around lines 135 - 155, The
function `_count_tool_records_for_rebuild` is being called redundantly in both
`migrate_v27_to_v28` and `_replace_tool_records_with_empty_v28_table`, causing
the same COUNT query to execute twice. Refactor by removing the call to
`_count_tool_records_for_rebuild` from within
`_replace_tool_records_with_empty_v28_table` and instead add it as a parameter
to that function. Call `_count_tool_records_for_rebuild` once in
`migrate_v27_to_v28`, capture the result, and pass it to
`_replace_tool_records_with_empty_v28_table` so the function uses the passed
value instead of executing the query again.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@changelogs/changelog.md`:
- Line 9: On line 9 of the changelog.md file, adjust the Chinese wording of the
changelog entry to improve its naturalness and clarity. The current phrasing
needs to be refined to ensure it reads more naturally in Chinese while
maintaining the same meaning about completing configuration item names and
descriptions.

In `@dashboard/src/components/layout/Header.tsx`:
- Around line 154-157: The aria-label for the mobile menu toggle button in
Header.tsx is hardcoded to display "close menu" regardless of the actual menu
state, which creates accessibility confusion. Replace the fixed aria-label value
with a conditional expression that dynamically selects between "open menu" and
"close menu" translation keys based on the mobileMenuOpen boolean state. When
mobileMenuOpen is false, use the open menu translation; when true, use the close
menu translation. This ensures the aria-label accurately describes the action
the button will perform.
- Around line 264-273: The window.open() call in the Button component's onClick
handler is missing security parameters that prevent reverse tabnapping attacks.
Add the 'noopener,noreferrer' parameter as the third argument to the
window.open() call when opening the documentation link
'https://docs.mai-mai.org' in a new tab. This protects against the opened page
from accessing and controlling the current window via window.opener.

In `@dashboard/src/index.css`:
- Line 143: Fix multiple Stylelint violations in dashboard/src/index.css to
unblock the CI pipeline. At lines 143, 153, 165, and 176, correct the
font-family property quotes to use the appropriate quote style expected by
Stylelint. At lines 573, 575, 583, and 593, fix the currentColor keyword
capitalization to match Stylelint requirements. At lines 942 and 1628, add or
remove the required blank line before declarations as specified by Stylelint
rules. These fixes address all static linting errors preventing merge.

In `@dashboard/src/lib/theme/storage.ts`:
- Around line 21-30: The storage key names in THEME_STORAGE_KEYS have been
changed from maibot-theme-overrides, maibot-theme-custom-css, and
maibot-theme-background to maibot-theme-style-overrides,
maibot-theme-style-custom-css, and maibot-theme-style-background, but existing
user data stored under the old keys will be lost. Add a one-time migration
function in loadThemeConfig that checks for the presence of old storage keys,
retrieves and converts their values to match the new key structure, stores them
under the new keys, and then removes the old keys from localStorage to prevent
data loss during upgrades.

In `@dashboard/src/routes/config/model/components/TaskConfigCard.tsx`:
- Around line 175-180: The TooltipTrigger component in TaskConfigCard.tsx is
wrapping a non-focusable StreamlineIcon element, which prevents keyboard users
from accessing the tooltip. Replace the icon as the direct child of
TooltipTrigger with a button element that wraps the StreamlineIcon, and add an
appropriate aria-label attribute to the button to provide an accessible label
for screen readers. This ensures keyboard users can focus on and interact with
the tooltip trigger.

In `@dashboard/src/routes/plugin-config-embed.tsx`:
- Around line 5-9: The PluginConfigPage component does not support the embedded
parameter, causing navigation inconsistency in embedded mode. First, modify the
PluginConfigEmbedPage function to pass the embedded prop to PluginConfigPage
(similar to how PluginDetailPage receives it). Then, update the PluginConfigPage
component signature to accept an embedded parameter with a default value of
false. Finally, update the closePluginConfig callback function to use this
embedded parameter to determine the correct return route: return to
/plugin-config/embed when embedded is true, otherwise return to /plugin-config.

In `@dashboard/src/routes/plugins/PluginMarketplacePage.tsx`:
- Around line 103-129: The readPluginMarketplaceViewState function directly
calls JSON.parse on the savedState without error handling, which will throw an
exception if sessionStorage contains corrupted or invalid JSON data, causing
page initialization to fail. Wrap the JSON.parse call and the subsequent parsing
logic in a try/catch block, ensuring that if any parsing error occurs, the
function returns DEFAULT_PLUGIN_MARKET_VIEW_STATE as a safe fallback instead of
crashing the application.

In `@dashboard/src/routes/settings/AppearanceTab.tsx`:
- Around line 459-499: The theme mode selector currently uses tablist and tab
ARIA roles, but this component implements a single-selection radio button
pattern without tabpanel children or keyboard navigation, which is semantically
incorrect. Change the outer container's role from "tablist" to "radiogroup" and
the button elements' role from "tab" to "radio". Additionally, replace the
aria-selected attribute with aria-checked on each button to correctly represent
the radio button state.

In `@src/config/config.py`:
- Around line 641-645: The call to mark_legacy_config_migration_completed() at
line 642 performs a database write operation that is not wrapped in exception
handling. If the database becomes temporarily unavailable, this will cause the
entire configuration loading to fail even though the configuration itself is
already available. Wrap the mark_legacy_config_migration_completed() call in a
try-except block to isolate any database-related exceptions, log the error for
debugging purposes, but allow the configuration loading to continue regardless
of whether the migration completion marking succeeds or fails.

In `@src/config/official_configs.py`:
- Around line 4011-4023: The docstring for the record_tool_structured_content
field only mentions the database size impact but fails to highlight privacy
risks. Update the docstring to include a warning that the structured content
saved may contain sensitive information such as user data, external API
responses, or debug information. The privacy warning should be prominently
featured in addition to or prioritized over the existing note about database
volume to ensure users understand the security and privacy implications when
enabling this setting.
- Around line 4147-4159: When the api_server_host is configured to listen
externally (0.0.0.0), the system must enforce that api_server_allowed_api_keys
is not empty to prevent exposing unauthenticated APIs. Add validation in the
model_post_init method to check that if api_server_host is "0.0.0.0", the
api_server_allowed_api_keys list must be non-empty; if validation fails, raise
an error. Alternatively, change the default value of api_server_host from
"0.0.0.0" to "127.0.0.1" to restrict to localhost by default. This validation
applies at the location around line 4147 and also at the related configuration
site around line 4217-4229.
- Around line 5284-5294: The render field in the config currently uses
default_factory=PluginRuntimeRenderConfig, which enables browser rendering by
default with unsandboxed parameters (--no-sandbox/--disable-setuid-sandbox).
This creates a security risk when rendering untrusted content. Change the render
field to not enable the rendering capability by default—either remove the
default_factory parameter to make render optional/null by default, or modify
PluginRuntimeRenderConfig's default initialization to exclude the no-sandbox
parameters and require users to explicitly enable them when needed in container
environments.

In `@src/maisaka/reasoning_engine.py`:
- Around line 539-546: The direct await calls to
handle_tool_post_execution_effects in the tool execution flow are not protected
against exceptions, meaning that failures in memory feedback queuing or other
post-execution effects will interrupt the main tool chain. Create a safe wrapper
function (or use try-except blocks) around each invocation of
handle_tool_post_execution_effects that catches any exceptions, logs them for
debugging, and allows the main execution flow to continue uninterrupted. This
wrapper should not re-raise exceptions. Apply this safe wrapping to all four
affected locations: in src/maisaka/reasoning_engine.py around lines 539-546 (in
the first await handle_tool_post_execution_effects call), lines 556-563 (in the
second call), lines 2272-2279 (in the third call), and lines 2306-2317 (in the
fourth call).

In `@src/maisaka/utils/tool_record_payload.py`:
- Around line 12-38: The normalize_tool_record_value function lacks circular
reference protection, causing RecursionError when processing self-referencing
objects from external tool return values. Add a visited set parameter to track
processed objects by their id, pass this set through all recursive calls, and
check if an object's id is already in the visited set before processing dict,
list, tuple, set, model_dump(), or __dict__ recursively. When a circular
reference is detected, return a placeholder string like "<circular_reference>"
instead of attempting further recursion.
- Around line 137-148: The payload construction in the tool record payload
builder currently only applies large media omission (via
build_tool_record_structured_content) to the structured_content field, but the
arguments and metadata fields can also contain large inline media like
image_base64, audio_base64, or data that should be omitted. Modify the payload
dictionary to apply large media omission to both the arguments field
(invocation.arguments) and the metadata field (result.metadata) using the same
omission strategy applied to structured_content, ensuring that large binary data
is consistently excluded from the database write across all relevant fields.
- Around line 122-127: The code unconditionally calls
normalize_tool_record_value on the input value before checking if
record_tool_structured_content is disabled, causing unnecessary memory and CPU
consumption for large structures even when the feature is off. Reorder the logic
to check global_config.debug.record_tool_structured_content first: if disabled,
immediately call _build_omitted_structured_content_marker without any
normalization; only call normalize_tool_record_value when the feature is
actually enabled, then pass the normalized value to either
_build_omitted_structured_content_marker (if now disabled) or
_omit_tool_record_large_media (if enabled).

---

Outside diff comments:
In `@dashboard/src/routes/auth.tsx`:
- Around line 268-274: The theme toggle button in the auth route has a title
attribute for accessibility but is missing an aria-label attribute, which is
needed for screen readers to reliably announce the button's purpose. Add an
aria-label attribute to the button element with the same conditional logic as
the title attribute, using the translation keys t('auth.switchToLight') or
t('auth.switchToDark') based on the current theme, to ensure both visual
tooltips and screen reader users have proper access to the button's function.

In `@dashboard/src/routes/config/model/components/ModelTable.tsx`:
- Around line 90-98: The code uses object reference equality (m === model) to
find actualIndex in ModelTable.tsx, which fails when models are recreated during
pagination or filtering, resulting in actualIndex becoming -1. This causes
subsequent edit, delete, and selection operations at lines 141-154 to target
wrong indices. Replace the object reference comparison with a unique identifier
comparison (such as model.id or model.name) in the findIndex call. Apply the
same fix to ModelCardList.tsx at line 46 where the same pattern exists.

In `@dashboard/src/routes/config/model/components/Pagination.tsx`:
- Around line 85-96: The pagination buttons at lines 85-96 (first page button
calling onPageChange(1)) and lines 136-147 (last page button) lack accessible
labels, preventing screen reader users from understanding their purpose. Add an
aria-label attribute to both Button components: use "Go to first page" for the
button at lines 85-96 and "Go to last page" for the button at lines 136-147.
This will ensure assistive technology users can identify the pagination controls
and their semantics.

In `@dashboard/src/routes/config/modelProvider/ProviderList.tsx`:
- Around line 289-301: The test connection button (the one with onClick={() =>
onTest(provider.name)} that renders a Loader2 or Zap icon) is missing an
aria-label attribute. Add an aria-label attribute to the Button component with
an appropriate accessible label (e.g., "测试连接") to provide screen reader support,
following the same pattern as the edit/delete buttons in this component.

In `@dashboard/src/routes/resource/behavior/index.tsx`:
- Around line 1620-1650: The handlePointerUp callback cleans up the pointer
state and resets node.fixed to false, but this cleanup only executes on the
onPointerUp event. When a pointercancel event occurs or pointer capture is lost,
the cleanup logic does not run, leaving nodes stuck in a fixed state. Create a
shared cleanup function that resets the pointerRef and unfixes the node, then
call this function from both handlePointerUp and a new handlePointerCancel
callback. Add the onPointerCancel handler to the canvas element alongside the
existing pointer event handlers to ensure cleanup occurs in all pointer
termination scenarios.

In `@dashboard/src/routes/settings/AppearanceTab.tsx`:
- Around line 1080-1082: The inheritance state check in the isInheritedLayer
condition is too restrictive, only covering sidebar and header layers. Since
useBackground returns the page background for any non-page layer when inherit is
true, the condition should be expanded to cover all non-page layers including
card and dialog. Replace the explicit layer name checks (layerId === 'sidebar'
|| layerId === 'header') with a more general check that covers any layer that is
not 'page', so that all layers with inherit enabled are properly marked as
inherited and prevented from being edited.
- Around line 256-272: The handleCSSChange function calls sanitizeCSS(val) to
obtain sanitized CSS content but stores the original unsanitized val in the
styleCustomCSS configuration instead of using the sanitized result.css from the
sanitizeCSS return value. Update the updateThemeConfig call to store result.css
in the [dashboardStyle] key instead of val. This ensures that only sanitized CSS
is persisted in the configuration, improving security and clarity at the storage
layer rather than relying on subsequent re-sanitization during theme
application.

In `@src/services/image_path_maintenance_service.py`:
- Around line 10-24: The import statement order in the file does not follow the
project's import specification. Currently, the standard library import (asyncio)
is placed after third-party library imports (rich and sqlalchemy), but according
to the project coding standards, standard library imports must come before
third-party library imports. Reorganize the imports by moving the asyncio import
to the beginning of the import section, before the rich and sqlalchemy imports,
and ensure there is one blank line separating the standard library imports from
the third-party library imports.

---

Nitpick comments:
In `@AGENTS.md`:
- Line 50: The sentence on line 50 in AGENTS.md describing the legacy_migration
rule is incomplete and cuts off mid-thought. Complete the sentence by adding the
missing second half after "此文件以固定" to make it grammatically complete and convey
the full intended meaning, such as by appending descriptive text like "形式存在" or
another appropriate completion that clarifies the constraint on the
legacy_migration file.

In `@dashboard/src/lib/theme/storage.ts`:
- Around line 136-143: The parseJSONStorage function has inconsistent error
handling: it returns undefined when a key doesn't exist, but throws an exception
when JSON.parse fails due to invalid format. Wrap the JSON.parse(value) call in
a try/catch block to catch any JSON parsing errors, and return undefined when
parsing fails instead of allowing the exception to propagate. This unifies the
function's behavior to always return undefined for any invalid case (missing key
or invalid JSON format), eliminating the need for callers to handle exceptions
from this function.

In `@src/common/database/migrations/v27_to_v28.py`:
- Around line 135-155: The function `_count_tool_records_for_rebuild` is being
called redundantly in both `migrate_v27_to_v28` and
`_replace_tool_records_with_empty_v28_table`, causing the same COUNT query to
execute twice. Refactor by removing the call to
`_count_tool_records_for_rebuild` from within
`_replace_tool_records_with_empty_v28_table` and instead add it as a parameter
to that function. Call `_count_tool_records_for_rebuild` once in
`migrate_v27_to_v28`, capture the result, and pass it to
`_replace_tool_records_with_empty_v28_table` so the function uses the passed
value instead of executing the query again.

In `@src/common/version.py`:
- Line 8: The PROJECT_ROOT variable calculation in src/common/version.py relies
on a hardcoded parent directory depth of 2, which assumes the current file
(version.py) remains at src/common/. Add a comment above the PROJECT_ROOT
assignment explaining this directory structure dependency and noting that the
parents[2] index must be adjusted if the file location changes in the future.

In `@src/maisaka/reasoning_engine.py`:
- Around line 27-28: The newly added imports from src.maisaka.utils
(handle_tool_post_execution_effects, build_tool_record_payload, and
normalize_tool_record_value) are not organized according to project conventions.
Reorganize the import block in reasoning_engine.py so that all absolute imports
starting with "from src.maisaka..." are grouped together, separate from relative
imports like ".chat_loop_service". The two utils imports should be placed with
other cross-directory absolute imports from src.maisaka, not intermixed with
same-level relative imports, to comply with the project's import grouping
standards and avoid Ruff linting errors.

In `@src/maisaka/utils/tool_post_execution.py`:
- Around line 56-59: The early return statements when `saved_record` or
`chat_stream` are None in the tool_post_execution function silently skip
critical feedback enqueueing without any logging, making it difficult to debug
when these dependencies are missing. Instead of returning silently, add
warning-level log messages before each return that include diagnostic context
such as the `tool_call_id` and the specific field that is missing (either
"saved_record" or "chat_stream"). This ensures that missing critical
dependencies are properly exposed in logs rather than being masked.

In `@src/services/image_path_maintenance_service.py`:
- Around line 384-455: The refactored `run_image_path_maintenance_background()`
function removed the batch-to-batch throttling (asyncio.sleep call that
previously occurred between batch processing iterations). To mitigate potential
high database I/O load spikes, add back a brief async sleep interval after each
batch is processed in the while loop. Insert an `await asyncio.sleep()` call
with a short duration (such as 0.01 seconds) after the progress update
operations complete and before the next batch iteration begins. This provides a
"breathing time" for the system to handle other database operations without
causing excessive load.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 376c8a3f-da8f-461c-8679-d32a78954572

📥 Commits

Reviewing files that changed from the base of the PR and between 5eb5bb2 and 76dc5eb.

⛔ Files ignored due to path filters (2)
  • dashboard/package-lock.json is excluded by !**/package-lock.json
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (109)
  • AGENTS.md
  • changelogs/changelog.md
  • dashboard/app-version.ts
  • dashboard/electron.vite.config.ts
  • dashboard/package.json
  • dashboard/src/components/animation-provider.tsx
  • dashboard/src/components/chat-scope-filter-panel.tsx
  • dashboard/src/components/dynamic-form/DynamicConfigForm.tsx
  • dashboard/src/components/dynamic-form/DynamicField.tsx
  • dashboard/src/components/embed-page-shell.tsx
  • dashboard/src/components/layout/Header.tsx
  • dashboard/src/components/layout/Layout.tsx
  • dashboard/src/components/layout/LogoArea.tsx
  • dashboard/src/components/layout/NavItem.tsx
  • dashboard/src/components/layout/Sidebar.tsx
  • dashboard/src/components/layout/constants.ts
  • dashboard/src/components/layout/types.ts
  • dashboard/src/components/memory/MemoryEpisodeManager.tsx
  • dashboard/src/components/memory/MemoryMaintenanceManager.tsx
  • dashboard/src/components/memory/MemoryMiniTabs.tsx
  • dashboard/src/components/memory/MemoryProfileManager.tsx
  • dashboard/src/components/search-dialog.tsx
  • dashboard/src/components/theme-provider.tsx
  • dashboard/src/components/ui/accent-panel.tsx
  • dashboard/src/components/ui/alert.tsx
  • dashboard/src/components/ui/card.tsx
  • dashboard/src/components/ui/checkbox.tsx
  • dashboard/src/components/ui/radio-group.tsx
  • dashboard/src/components/ui/slider.tsx
  • dashboard/src/components/ui/streamline-icon.tsx
  • dashboard/src/components/ui/streamline-menu-icon.tsx
  • dashboard/src/components/ui/table.tsx
  • dashboard/src/components/ui/toast.tsx
  • dashboard/src/components/ui/tooltip.tsx
  • dashboard/src/components/waves-background.tsx
  • dashboard/src/hooks/use-background.ts
  • dashboard/src/i18n/locales/en.json
  • dashboard/src/i18n/locales/ja.json
  • dashboard/src/i18n/locales/ko.json
  • dashboard/src/i18n/locales/zh.json
  • dashboard/src/index.css
  • dashboard/src/lib/animation-context.ts
  • dashboard/src/lib/settings-manager.ts
  • dashboard/src/lib/theme-context.ts
  • dashboard/src/lib/theme/palette.ts
  • dashboard/src/lib/theme/pipeline.ts
  • dashboard/src/lib/theme/storage.ts
  • dashboard/src/lib/theme/tokens.ts
  • dashboard/src/lib/version.ts
  • dashboard/src/router.tsx
  • dashboard/src/routes/auth.tsx
  • dashboard/src/routes/config/bot.tsx
  • dashboard/src/routes/config/bot/hooks/ListItemEditorHookFactory.tsx
  • dashboard/src/routes/config/bot/hooks/complexFieldHooks.tsx
  • dashboard/src/routes/config/bot/hooks/index.ts
  • dashboard/src/routes/config/bot/types.ts
  • dashboard/src/routes/config/model/components/ModelCardList.tsx
  • dashboard/src/routes/config/model/components/ModelTable.tsx
  • dashboard/src/routes/config/model/components/Pagination.tsx
  • dashboard/src/routes/config/model/components/TaskConfigCard.tsx
  • dashboard/src/routes/config/modelProvider/ProviderCard.tsx
  • dashboard/src/routes/config/modelProvider/ProviderList.tsx
  • dashboard/src/routes/config/prompts.tsx
  • dashboard/src/routes/index.tsx
  • dashboard/src/routes/plugin-config-embed.tsx
  • dashboard/src/routes/plugin-detail-embed.tsx
  • dashboard/src/routes/plugin-detail.tsx
  • dashboard/src/routes/plugin-mirrors-embed.tsx
  • dashboard/src/routes/plugin-mirrors.tsx
  • dashboard/src/routes/plugins/MarketplaceTab.tsx
  • dashboard/src/routes/plugins/PluginCard.tsx
  • dashboard/src/routes/plugins/PluginMarketplacePage.tsx
  • dashboard/src/routes/plugins/embed.tsx
  • dashboard/src/routes/resource/behavior/index.tsx
  • dashboard/src/routes/resource/expression/ExpressionList.tsx
  • dashboard/src/routes/resource/expression/index.tsx
  • dashboard/src/routes/resource/jargon/JargonList.tsx
  • dashboard/src/routes/resource/jargon/index.tsx
  • dashboard/src/routes/resource/knowledge-base.tsx
  • dashboard/src/routes/settings/AboutTab.tsx
  • dashboard/src/routes/settings/AppearanceTab.tsx
  • dashboard/src/routes/settings/index.tsx
  • dashboard/vite.config.ts
  • dashboard/vitest.config.ts
  • pyproject.toml
  • requirements.txt
  • src/A_memorix/CONFIG_REFERENCE.md
  • src/A_memorix/QUICK_START.md
  • src/A_memorix/config_schema.json
  • src/A_memorix/core/utils/person_profile_service.py
  • src/common/database/migrations/v27_to_v28.py
  • src/common/database/tool_record_payload_cleanup.py
  • src/common/utils/utils_config.py
  • src/common/version.py
  • src/config/config.py
  • src/config/legacy_migration.py
  • src/config/model_configs.py
  • src/config/official_configs.py
  • src/learners/behavior_selector.py
  • src/llm_models/utils_model.py
  • src/maisaka/builtin_tool/query_memory.py
  • src/maisaka/reasoning_engine.py
  • src/maisaka/utils/__init__.py
  • src/maisaka/utils/tool_post_execution.py
  • src/maisaka/utils/tool_record_payload.py
  • src/plugin_runtime/__init__.py
  • src/services/image_path_maintenance_service.py
  • src/services/statistics_service.py
  • src/webui/routers/expression.py
💤 Files with no reviewable changes (6)
  • dashboard/src/lib/animation-context.ts
  • dashboard/src/components/waves-background.tsx
  • dashboard/src/components/theme-provider.tsx
  • dashboard/src/components/animation-provider.tsx
  • src/common/database/tool_record_payload_cleanup.py
  • dashboard/src/lib/settings-manager.ts

Comment thread changelogs/changelog.md

## WebUI

- 补全部分配置项的名称和说明

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

修正文案“补全部分”为“补全部分”以避免语义不顺。

Line 9 当前表达不够自然,建议改为“补全部分配置项的名称和说明”。

✏️ 建议修改
-- 补全部分配置项的名称和说明
+- 补全部分配置项的名称和说明
🧰 Tools
🪛 LanguageTool

[uncategorized] ~9-~9: 您的意思是“"不"全”?
Context: ...d)。 # [1.0.5] - 2026-6-16 ## WebUI - 补全部分配置项的名称和说明 - 移除启动页特效以适配低性能设备 - 优化多个页面的...

(BU)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@changelogs/changelog.md` at line 9, On line 9 of the changelog.md file,
adjust the Chinese wording of the changelog entry to improve its naturalness and
clarity. The current phrasing needs to be refined to ensure it reads more
naturally in Chinese while maintaining the same meaning about completing
configuration item names and descriptions.

Source: Linters/SAST tools

Comment on lines +154 to +157
<button
onClick={onMobileMenuToggle}
aria-label={t('a11y.closeMenu')}
aria-expanded={mobileMenuOpen}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

移动端菜单按钮的 aria-label 与实际状态不一致。

Line 156 当前固定为“关闭菜单”,在菜单未展开时语义错误,建议按 mobileMenuOpen 动态切换“打开/关闭”。

♿ 建议修改
- aria-label={t('a11y.closeMenu')}
+ aria-label={mobileMenuOpen ? t('a11y.closeMenu') : t('a11y.openMenu')}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
onClick={onMobileMenuToggle}
aria-label={t('a11y.closeMenu')}
aria-expanded={mobileMenuOpen}
<button
onClick={onMobileMenuToggle}
aria-label={mobileMenuOpen ? t('a11y.closeMenu') : t('a11y.openMenu')}
aria-expanded={mobileMenuOpen}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/components/layout/Header.tsx` around lines 154 - 157, The
aria-label for the mobile menu toggle button in Header.tsx is hardcoded to
display "close menu" regardless of the actual menu state, which creates
accessibility confusion. Replace the fixed aria-label value with a conditional
expression that dynamically selects between "open menu" and "close menu"
translation keys based on the mobileMenuOpen boolean state. When mobileMenuOpen
is false, use the open menu translation; when true, use the close menu
translation. This ensures the aria-label accurately describes the action the
button will perform.

Comment on lines +264 to +273
<Button
variant="ghost"
size="icon"
onClick={() => window.open('https://docs.mai-mai.org', '_blank')}
className="hidden sm:inline-flex"
title={t('header.viewDocs')}
aria-label={t('header.viewDocs')}
>
<BookOpen className="h-4 w-4" />
</Button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 只读检查:定位项目中所有 target=_blank 或 window.open 用法,确认是否统一添加防护
rg -nP "window\.open\s*\(" dashboard/src
rg -nP "target\s*=\s*['\"]_blank['\"]" dashboard/src

Repository: Mai-with-u/MaiBot

Length of output: 1895


window.open() 缺少安全参数,存在反向标签劫持风险。

第 267 行直接调用 window.open('https://docs.mai-mai.org', '_blank') 会暴露 window.opener,允许外部文档反向控制当前页面。需要添加 'noopener,noreferrer' 参数以防止此风险。

修改建议
- onClick={() => window.open('https://docs.mai-mai.org', '_blank')}
+ onClick={() => window.open('https://docs.mai-mai.org', '_blank', 'noopener,noreferrer')}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
variant="ghost"
size="icon"
onClick={() => window.open('https://docs.mai-mai.org', '_blank')}
className="hidden sm:inline-flex"
title={t('header.viewDocs')}
aria-label={t('header.viewDocs')}
>
<BookOpen className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => window.open('https://docs.mai-mai.org', '_blank', 'noopener,noreferrer')}
className="hidden sm:inline-flex"
title={t('header.viewDocs')}
aria-label={t('header.viewDocs')}
>
<BookOpen className="h-4 w-4" />
</Button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/components/layout/Header.tsx` around lines 264 - 273, The
window.open() call in the Button component's onClick handler is missing security
parameters that prevent reverse tabnapping attacks. Add the
'noopener,noreferrer' parameter as the third argument to the window.open() call
when opening the documentation link 'https://docs.mai-mai.org' in a new tab.
This protects against the opened page from accessing and controlling the current
window via window.opener.

Comment thread dashboard/src/index.css
}

@font-face {
font-family: 'MaiRetroText';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

修复 Stylelint 阻塞项,避免 CI 失败。

当前有明确的静态检查错误:Line 143/153/165/176 的 font-family 名称引号、Line 573/575/583/593 的 currentColor 大小写、以及 Line 942 与 Line 1628 的声明前空行。建议在本 PR 内一并修复,否则会持续阻塞合并流水线。

Also applies to: 153-153, 165-165, 176-176, 573-573, 575-575, 583-583, 593-593, 942-942, 1628-1628

🧰 Tools
🪛 Stylelint (17.13.0)

[error] 143-143: Expected no quotes around "MaiRetroText" (font-family-name-quotes)

(font-family-name-quotes)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/index.css` at line 143, Fix multiple Stylelint violations in
dashboard/src/index.css to unblock the CI pipeline. At lines 143, 153, 165, and
176, correct the font-family property quotes to use the appropriate quote style
expected by Stylelint. At lines 573, 575, 583, and 593, fix the currentColor
keyword capitalization to match Stylelint requirements. At lines 942 and 1628,
add or remove the required blank line before declarations as specified by
Stylelint rules. These fixes address all static linting errors preventing merge.

Source: Linters/SAST tools

Comment on lines 21 to 30
export const THEME_STORAGE_KEYS = {
MODE: 'maibot-theme-mode',
PRESET: 'maibot-theme-preset',
ACCENT: 'maibot-theme-accent',
OVERRIDES: 'maibot-theme-overrides',
CUSTOM_CSS: 'maibot-theme-custom-css',
BACKGROUND_CONFIG: 'maibot-theme-background',
STYLE_OVERRIDES: 'maibot-theme-style-overrides',
STYLE_CUSTOM_CSS: 'maibot-theme-style-custom-css',
STYLE_BACKGROUND_CONFIG: 'maibot-theme-style-background',
DASHBOARD_STYLE: 'maibot-theme-dashboard-style',
STYLE_CONFIG: 'maibot-theme-style-config',
} as const

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

现有用户主题配置将丢失:缺少 localStorage 迁移逻辑。

存储键从 maibot-theme-overrides/maibot-theme-custom-css/maibot-theme-background 更名为 maibot-theme-style-*,但未提供从旧键到新键的迁移逻辑。已有自定义主题的用户升级后将丢失所有配置。

建议在 loadThemeConfig 中添加一次性迁移:检测旧键存在时读取并转换为新结构,然后删除旧键。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dashboard/src/lib/theme/storage.ts` around lines 21 - 30, The storage key
names in THEME_STORAGE_KEYS have been changed from maibot-theme-overrides,
maibot-theme-custom-css, and maibot-theme-background to
maibot-theme-style-overrides, maibot-theme-style-custom-css, and
maibot-theme-style-background, but existing user data stored under the old keys
will be lost. Add a one-time migration function in loadThemeConfig that checks
for the presence of old storage keys, retrieves and converts their values to
match the new key structure, stores them under the new keys, and then removes
the old keys from localStorage to prevent data loss during upgrades.

Comment on lines +5284 to +5294
render: PluginRuntimeRenderConfig = Field(
default_factory=PluginRuntimeRenderConfig,
json_schema_extra={
"label": {
"zh_CN": "浏览器渲染",
"en_US": "Browser rendering",
"ja_JP": "ブラウザ描画",
},
},
)
"""插件需要网页截图或渲染时使用的浏览器配置。"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

避免默认启用无沙箱浏览器渲染。

新增 render 配置后会默认启用 PluginRuntimeRenderConfig,而其默认启动参数包含 --no-sandbox/--disable-setuid-sandbox。如果插件渲染不可信页面或插件内容,默认无沙箱会放大浏览器漏洞或恶意页面的影响面;建议默认关闭渲染能力,或移除无沙箱参数并要求需要兼容容器环境的用户显式配置。

建议修复
 class PluginRuntimeRenderConfig(ConfigBase):
     enabled: bool = Field(
-        default=True,
+        default=False,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/config/official_configs.py` around lines 5284 - 5294, The render field in
the config currently uses default_factory=PluginRuntimeRenderConfig, which
enables browser rendering by default with unsandboxed parameters
(--no-sandbox/--disable-setuid-sandbox). This creates a security risk when
rendering untrusted content. Change the render field to not enable the rendering
capability by default—either remove the default_factory parameter to make render
optional/null by default, or modify PluginRuntimeRenderConfig's default
initialization to exclude the no-sandbox parameters and require users to
explicitly enable them when needed in container environments.

Comment on lines +539 to +546
saved_record = await self._store_tool_execution_record(invocation, result, None)
await handle_tool_post_execution_effects(
invocation=invocation,
result=result,
saved_record=saved_record,
chat_stream=self._runtime.chat_stream,
log_prefix=self._runtime.log_prefix,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

隔离工具后置副作用失败,避免中断主工具链路。

这些新增调用直接 await handle_tool_post_execution_effects(...);如果记忆反馈入队或其他后置效果异常,当前工具已经执行/可能已落库,但历史写回、监控结果和后续工具都会被异常打断。建议用一个安全包装只记录异常,不让非关键后置副作用影响主流程。Based on relevant context, handle_tool_post_execution_effects 会根据 result.metadata 触发记忆反馈任务入队。

建议修复
+    async def _handle_tool_post_execution_effects_safely(
+        self,
+        *,
+        invocation: ToolInvocation,
+        result: ToolExecutionResult,
+        saved_record: Optional[dict[str, Any]],
+    ) -> None:
+        try:
+            await handle_tool_post_execution_effects(
+                invocation=invocation,
+                result=result,
+                saved_record=saved_record,
+                chat_stream=self._runtime.chat_stream,
+                log_prefix=self._runtime.log_prefix,
+            )
+        except Exception:
+            logger.exception(
+                f"{self._runtime.log_prefix} 处理工具后置副作用失败: "
+                f"工具={invocation.tool_name} 调用编号={invocation.call_id}"
+            )

然后将各处直接调用替换为:

-            await handle_tool_post_execution_effects(
-                invocation=invocation,
-                result=result,
-                saved_record=saved_record,
-                chat_stream=self._runtime.chat_stream,
-                log_prefix=self._runtime.log_prefix,
-            )
+            await self._handle_tool_post_execution_effects_safely(
+                invocation=invocation,
+                result=result,
+                saved_record=saved_record,
+            )

Also applies to: 556-563, 2272-2279, 2306-2317

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/maisaka/reasoning_engine.py` around lines 539 - 546, The direct await
calls to handle_tool_post_execution_effects in the tool execution flow are not
protected against exceptions, meaning that failures in memory feedback queuing
or other post-execution effects will interrupt the main tool chain. Create a
safe wrapper function (or use try-except blocks) around each invocation of
handle_tool_post_execution_effects that catches any exceptions, logs them for
debugging, and allows the main execution flow to continue uninterrupted. This
wrapper should not re-raise exceptions. Apply this safe wrapping to all four
affected locations: in src/maisaka/reasoning_engine.py around lines 539-546 (in
the first await handle_tool_post_execution_effects call), lines 556-563 (in the
second call), lines 2272-2279 (in the third call), and lines 2306-2317 (in the
fourth call).

Comment on lines +12 to +38
def normalize_tool_record_value(value: Any) -> Any:
"""将工具记录值规范化为可 JSON 序列化的结构。"""

if value is None or isinstance(value, (str, int, float, bool)):
return value
if isinstance(value, datetime):
return value.isoformat()
if isinstance(value, dict):
normalized_dict: dict[str, Any] = {}
for key, item in value.items():
normalized_dict[str(key)] = normalize_tool_record_value(item)
return normalized_dict
if isinstance(value, (list, tuple, set)):
return [normalize_tool_record_value(item) for item in value]
if isinstance(value, bytes):
return f"<bytes:{len(value)}>"
if hasattr(value, "model_dump"):
try:
return normalize_tool_record_value(value.model_dump())
except Exception:
return str(value)
if hasattr(value, "__dict__"):
try:
return normalize_tool_record_value(dict(value.__dict__))
except Exception:
return str(value)
return str(value)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

给递归规范化增加循环引用保护。

工具返回值/metadata 是外部扩展可控结构;当前递归处理 dict/list/set/__dict__ 时没有 visited 集合,遇到自引用对象会 RecursionError,中断工具记录落库流程。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/maisaka/utils/tool_record_payload.py` around lines 12 - 38, The
normalize_tool_record_value function lacks circular reference protection,
causing RecursionError when processing self-referencing objects from external
tool return values. Add a visited set parameter to track processed objects by
their id, pass this set through all recursive calls, and check if an object's id
is already in the visited set before processing dict, list, tuple, set,
model_dump(), or __dict__ recursively. When a circular reference is detected,
return a placeholder string like "<circular_reference>" instead of attempting
further recursion.

Comment on lines +122 to +127
if value is None:
return None
normalized_value = normalize_tool_record_value(value)
if not global_config.debug.record_tool_structured_content:
return _build_omitted_structured_content_marker(normalized_value)
return _omit_tool_record_large_media(normalized_value)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

关闭 structured_content 记录时不要先完整规范化。

record_tool_structured_content=false 本应避免大结构落库成本,但这里仍先递归规范化整个 value,大 base64/深层对象仍会消耗内存和 CPU,循环结构也会先崩溃。先判断开关,再构造轻量 marker。

🐛 建议先按配置短路
     if value is None:
         return None
-    normalized_value = normalize_tool_record_value(value)
     if not global_config.debug.record_tool_structured_content:
-        return _build_omitted_structured_content_marker(normalized_value)
+        return _build_omitted_structured_content_marker(value)
+    normalized_value = normalize_tool_record_value(value)
     return _omit_tool_record_large_media(normalized_value)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if value is None:
return None
normalized_value = normalize_tool_record_value(value)
if not global_config.debug.record_tool_structured_content:
return _build_omitted_structured_content_marker(normalized_value)
return _omit_tool_record_large_media(normalized_value)
if value is None:
return None
if not global_config.debug.record_tool_structured_content:
return _build_omitted_structured_content_marker(value)
normalized_value = normalize_tool_record_value(value)
return _omit_tool_record_large_media(normalized_value)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/maisaka/utils/tool_record_payload.py` around lines 122 - 127, The code
unconditionally calls normalize_tool_record_value on the input value before
checking if record_tool_structured_content is disabled, causing unnecessary
memory and CPU consumption for large structures even when the feature is off.
Reorder the logic to check global_config.debug.record_tool_structured_content
first: if disabled, immediately call _build_omitted_structured_content_marker
without any normalization; only call normalize_tool_record_value when the
feature is actually enabled, then pass the normalized value to either
_build_omitted_structured_content_marker (if now disabled) or
_omit_tool_record_large_media (if enabled).

Comment on lines +137 to +148
payload: dict[str, Any] = {
"call_id": invocation.call_id,
"session_id": invocation.session_id,
"stream_id": invocation.stream_id,
"arguments": normalize_tool_record_value(invocation.arguments),
"success": result.success,
"content": result.content,
"error_message": result.error_message,
"history_content": result.get_history_content(),
"structured_content": build_tool_record_structured_content(result.structured_content),
"metadata": normalize_tool_record_value(result.metadata),
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

arguments 和 metadata 也需要省略内联大媒体。

当前只有 structured_content 经过 _omit_tool_record_large_media;如果工具参数或 metadata 携带 image_base64/audio_base64/data,仍会完整写入数据库,绕过本模块的大字段省略策略。

🐛 建议统一套用大媒体省略
-        "arguments": normalize_tool_record_value(invocation.arguments),
+        "arguments": _omit_tool_record_large_media(normalize_tool_record_value(invocation.arguments)),
@@
-        "metadata": normalize_tool_record_value(result.metadata),
+        "metadata": _omit_tool_record_large_media(normalize_tool_record_value(result.metadata)),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/maisaka/utils/tool_record_payload.py` around lines 137 - 148, The payload
construction in the tool record payload builder currently only applies large
media omission (via build_tool_record_structured_content) to the
structured_content field, but the arguments and metadata fields can also contain
large inline media like image_base64, audio_base64, or data that should be
omitted. Modify the payload dictionary to apply large media omission to both the
arguments field (invocation.arguments) and the metadata field (result.metadata)
using the same omission strategy applied to structured_content, ensuring that
large binary data is consistently excluded from the database write across all
relevant fields.

@SengokuCola SengokuCola merged commit 88c3618 into main Jun 16, 2026
1 check was pending
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants