diff --git a/.project-docs/30-worklog/tasks/20261013-agent-controls-correction-6d42b9e1.md b/.project-docs/30-worklog/tasks/20261013-agent-controls-correction-6d42b9e1.md
new file mode 100644
index 00000000..f4232069
--- /dev/null
+++ b/.project-docs/30-worklog/tasks/20261013-agent-controls-correction-6d42b9e1.md
@@ -0,0 +1,41 @@
+# Task: Correct TOOL SKILL labels and knowledge upload card placement
+
+## Identity
+
+- Task ID: 20261013-agent-controls-correction-6d42b9e1
+- Mode: Feature
+- Branch: codex/20261013-agent-controls-correction-6d42b9e1-agent-controls-correction
+- Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20261013-agent-controls-correction-6d42b9e1
+- Base commit: 020b36673d9034fed53f7e896b7685aa56e4cecb
+- Owner: codex
+- Status: In Progress
+
+## Scope
+
+- Correct advanced configuration headers/buttons/search labels to TOOL/选择TOOL and SKILL/选择SKILL. Move knowledge upload action into the left 上传自己的文件 card beside 新建知识库; preserve existing selection widths, refresh button styling and upload/pending authority. Enable corrected Renderer in current temporary app.
+
+## Intent And Constraints
+
+- User explicitly corrects both labels and upload-button placement, superseding prior Chinese-label/right-card choices. No API/schema, cloud mutations, real upload, model call, WeChat actions, Main restart, profile/credential inspection or user-input discard is needed. Preserve active020b366 full client baseline and MainPID12660/Host58003.
+- Concurrent Gate Passed: official check/start/status verified exact Identity task/mode/branch/absoluteworktree/base. Read AGENTS, entry/memory-index, active record, positioning, integrated state, decision-index/system-overview, personal-cloud ADR, community boundary, domain/success criteria and relevant indexes. Integrated shared docs are historical; active runtime current.json and source020b are delivery baseline.48 peer scopes read only; independent semantic audit finds prior label/layout decisions superseded by this user correction, with no unresolved conflict. Only owned task doc may change; no canonical/peer writes.
+- Project Context Loaded: goal remains ordinary creative/client workflow with Main-owned auth/cloud API; current scope is Renderer presentation only. Configuration tools/skills/preload_skills and knowledge permissions/pending operation IDs remain authoritative. Relevant files ConfigurationFields.tsx, CloudKnowledge.tsx, agent-config.css and existing unit/geometry/Electron fixtures. Design skill recommends stable flex wrapping, focus/contrast and responsive checks; retain current established palette/components. Gate result: Passed before implementation plan.
+- Plan: move the stable portal target into left action row without remounting documents; change display labels; update existing assertions and wide/narrow geometry expectations. Run typecheck, relevant units/lint, browser geometry and existing Electron fixture, default fullbuild. Prepare immutable Renderer overlay over unchanged Main artifacts, clean/idle native CUA reload and verify actual corrected controls before final metadata.
+
+## Outcome
+
+- Implemented exact TOOL/SKILL headings and generated selection/search labels, with SKILL preload copy consistent. Knowledge upload portal target moved into the left stable create-action row beside 新建知识库; row wraps at narrow widths. Document component/key, request intent, permissions and pending lock logic remain unchanged. Right management row contains select/refresh only; previous widths and selected-list alignment are retained.
+- Current active Main/shared/resources/package/lock/build-config source delta is empty; only three Renderer product files change. Immutable overlay and native saved/idle reload remain to deliver.
+
+## Verification
+
+- Typecheck, two scoped ESLint runs, default full build:vite (NIANCODE_RENDERER_ONLY unset) and git diff --check passed.
+-57 relevant unit cases passed:40 configuration/selector preservation +17 owned-knowledge permission/pending/cancel/retry cases.14 browser geometry/platform scenarios passed, with knowledge checks covering375/768/1024/1440 and long retry wording. Two focused screenshot retakes passed in an independent output directory; independent visual review PASS for all four widths and left/right card membership/alignment.
+- Native Electron cloud-agents full fixture passed on quiet rerun (8.3s). Initial run had a simulated voice stop-button observation timeout after changed-label configuration checks already passed; voice source is unchanged and rerun passes the entire scenario. No real microphone/recording, upload, provider or cloud account mutation was used.
+
+## Follow-ups
+
+- None recorded.
+
+## Promotion Candidates
+
+- None recorded.
diff --git a/README.md b/README.md
index 04a64db9..1a6fc37b 100644
--- a/README.md
+++ b/README.md
@@ -12,13 +12,13 @@ Makelore 是一个面向软件、视觉创作、智能机器人与个人云智
- `Makelore Code|AI 编程`:管理本地项目、项目智能体、对话、文件上下文、代码变更和运行时。
会话工作台采用浅蓝项目栏、彩色通用手柄图标与卡片选中态;中间对话/作品使用圆角标签,作品预览的后退、前进、刷新与网址输入统一放在标签右侧;只在作品页显示,窄窗口自动换行。右侧智能体咨询使用奶油色标题区。沿用真实项目和会话入口、侧栏伸缩与账户菜单,样式只作用于 Code 会话工作台。可编辑的空对话在输入区上方的可用区域中央显示童趣欢迎语;「去作品广场逛逛」入口紧贴输入框上方,两者一起位于底部。老师介绍以固定宽度浮在右上角,不占用中间布局;欢迎语和底部输入区始终相对项目栏右侧的整个内容区域居中;小窗口的欢迎内容独立滚动;发送首条消息后收起欢迎区。侧栏保留「作品广场」入口,只有点击后才在系统浏览器打开公共广场,不请求、摘取或内嵌展示广场作品。打开广场保留当前项目、对话、草稿和附件,不自动发送消息;老师求助按钮保留临时应用的最新文案「帮我整体看🧠」。
-- `Makelore Agents|AI 智能体`:从模块首页进入时先打开“我的智能体”卡片列表,保留此前对话与操作恢复记录;返回首页箭头与侧栏折叠按钮同排,折叠后仍可返回;智能体模块的侧栏只通过按钮点击或键盘激活展开,鼠标经过左侧、收纳按钮或顶栏不会展开,切换模块不沿用旧悬停预览;其他模块仍保留原预览方式。“我的智能体”使用浅紫对话机器人图标,“渠道”使用浅蓝连接节点图标。macOS 原生窗口按钮留出安全空间。首页围绕自己的智能体和创建入口,显示童趣问候“Hi,今天想创造还是想聊聊?”,不再显示更多、收到的分享、活动或归档筛选。卡片恢复原有尺寸与留白,显示名称、可为空的一句话介绍及状态,支持聊天、调整、继续制作和确认删除;删除沿用云端归档机制,移出列表并停用新调用,历史和费用保留。创建先填写名称并选择平台像素头像或上传头像,下一步进入独立配置。顶部名称有编辑图标,头像可点击修改;未设置头像时使用同一张默认像素头像,从创建到配置、改名、保存和重新打开保持一致。基础页为“介绍与提示词”,一句话介绍可留空,完整提示词保留原始文本;模型作为高级设置第一项必选,未选择时不能开始使用,草稿可分步保存,不自动选模型。介绍与提示词采用浅蓝介绍卡和浅紫提示词卡;高级设置按模型、工具箱(Tool)、做事手册(Skill)、MCP 服务和知识库纵向呈现;移除子智能体选择模块,保存其他配置时保留历史子智能体绑定,不再设内部分类切换;不展示 Tool/Skill 解释、资源开放规则提示、云端能力管理、费用上限或运行限制卡片,保存其他配置时保留既有运行限制,费用规则不变。顶层页签切换保留搜索、资源选择、知识表单及未保存输入。配置页移除聊天、自动任务和使用与接入导航,独立聊天仍从卡片进入,微信账号仍在侧栏渠道管理。草稿明确保存(Ctrl/⌘+S);保存只更新草稿,“应用最新修改”使新配置生效,失败重试保持原操作身份;点击“开始使用”完成必要的私有启用后直接打开聊天,不再展示二次确认页;未保存修改、缺少模型、冲突或尚未确认的请求会留在配置页处理,打开聊天不自动发送消息。独立对话沿用奶油底色和浅蓝、浅紫、薄荷色:顶部展示返回、只读头像和名称;有附件时在同一行右侧显示回形针“附件”入口和文件数量,不展示调整、保存/应用状态、客户端/微信来源、费用/版本说明或历史/新对话图标;配置由列表卡片的“调整”进入。空对话展示当前智能体头像与名称,示例只填入草稿;用户消息使用浅紫圆角气泡,助手 Markdown、表格和代码在消息区内滚动。文件或图片从输入框内添加,上传成功后以待发送卡片呈现,可移除本次携带;移除不会删除云端文件,添加不会自动发送。确认失败沿用同一文件和对话重试,发送前需完成或取消添加;支持仅发送附件,使用明确的附件提问。输入框沿用 Code 的浅蓝边框、圆形按钮及一至六行输入规则;Enter 发送、Shift+Enter 换行,保留中文输入法确认。麦克风复用应用级语音输入,识别只追加当前草稿,不自动发送;切换账号、对话或隐藏页面后丢弃过期识别。支持拖入文件、粘贴图片和多文件选择,上传与确认仍由 Main 持有认证并转发;失败重试保留同一批文件身份。输入框的“+”添加文件或图片;右上角“附件”打开右侧面板,按“你上传的”和“助手生成的”分组,支持再次选用已上传附件及保存明确交付的生成文件。生成文件仅取当前对话已保存助手回复中的本地文件链接(含图片与引用式链接),核对实际文件后展示和计数;只读取链接所在的文件夹,不遍历全部工作文件。未交付的脚本、缓存和工具记录不展示、不计数,明确交付的代码照常展示;仅写了路径的旧成果可能暂时隐藏,后续接入 Yuxi 的正式交付记录。没有文件时隐藏入口;打开、关闭面板保留聊天与输入,Escape 关闭并返回入口焦点;最近对话自动恢复、审批、停止和未发送内容的离开保护保留。模型返回的思考按消息独立于正文展示;正文到来后或完成时折叠,可手动展开;无思考或工具内容时不显示过程区。重连事件去重并隔离当前运行与主对话,终态历史未到达时保留已收到的片段并有限补读;服务端返回的最终回答思考可在历史中回看,不进入后续提问。历史回看需要配套 Yuxi API 的思考和工具记录投影;实时过程沿用既有事件流。平台工具和内置做事手册使用 Yuxi 下发的通俗名称与说明;逐条消息的思考、工具输入/结果/失败独立于 Markdown 正文,实时与历史工具卡片共用展示文案,展开可查看原始工具标识。个人上传手册保留作者文案,资源选择仍保存原始标识。头像上传、Main-owned Host API、访问隔离、费用与不可变版本规则保持;介绍留空需要配套 Yuxi DraftCreate/DraftSave 的空字符串契约。
+- `Makelore Agents|AI 智能体`:从模块首页进入时先打开“我的智能体”卡片列表,保留此前对话与操作恢复记录;返回首页箭头与侧栏折叠按钮同排,折叠后仍可返回;智能体模块的侧栏只通过按钮点击或键盘激活展开,鼠标经过左侧、收纳按钮或顶栏不会展开,切换模块不沿用旧悬停预览;其他模块仍保留原预览方式。“我的智能体”使用浅紫对话机器人图标,“渠道”使用浅蓝连接节点图标。macOS 原生窗口按钮留出安全空间。首页围绕自己的智能体和创建入口,显示童趣问候“Hi,今天想创造还是想聊聊?”,不再显示更多、收到的分享、活动或归档筛选。卡片恢复原有尺寸与留白,显示名称、可为空的一句话介绍及状态,支持聊天、调整、继续制作和确认删除;删除沿用云端归档机制,移出列表并停用新调用,历史和费用保留。创建先填写名称并选择平台像素头像或上传头像,下一步进入独立配置。顶部名称有编辑图标,头像可点击修改;未设置头像时使用同一张默认像素头像,从创建到配置、改名、保存和重新打开保持一致。基础页为“介绍与提示词”,一句话介绍可留空,完整提示词保留原始文本;模型作为高级设置第一项必选,未选择时不能开始使用,草稿可分步保存,不自动选模型。介绍与提示词采用浅蓝介绍卡和浅紫提示词卡;高级设置按模型、TOOL、SKILL、MCP 服务和知识库纵向呈现,资源选择按钮分别显示「选择TOOL」「选择SKILL」;移除子智能体选择模块,保存其他配置时保留历史子智能体绑定,不再设内部分类切换;不展示 Tool/Skill 解释、资源开放规则提示、云端能力管理、费用上限或运行限制卡片,保存其他配置时保留既有运行限制,费用规则不变。顶层页签切换保留搜索、资源选择、知识表单及未保存输入。配置页移除聊天、自动任务和使用与接入导航,独立聊天仍从卡片进入,微信账号仍在侧栏渠道管理。草稿明确保存(Ctrl/⌘+S);保存只更新草稿,“应用最新修改”使新配置生效,失败重试保持原操作身份;点击“开始使用”完成必要的私有启用后直接打开聊天,不再展示二次确认页;未保存修改、缺少模型、冲突或尚未确认的请求会留在配置页处理,打开聊天不自动发送消息。独立对话沿用奶油底色和浅蓝、浅紫、薄荷色:顶部展示返回、只读头像和名称;有附件时在同一行右侧显示回形针“附件”入口和文件数量,不展示调整、保存/应用状态、客户端/微信来源、费用/版本说明或历史/新对话图标;配置由列表卡片的“调整”进入。空对话展示当前智能体头像与名称,示例只填入草稿;用户消息使用浅紫圆角气泡,助手 Markdown、表格和代码在消息区内滚动。文件或图片从输入框内添加,上传成功后以待发送卡片呈现,可移除本次携带;移除不会删除云端文件,添加不会自动发送。确认失败沿用同一文件和对话重试,发送前需完成或取消添加;支持仅发送附件,使用明确的附件提问。输入框沿用 Code 的浅蓝边框、圆形按钮及一至六行输入规则;Enter 发送、Shift+Enter 换行,保留中文输入法确认。麦克风复用应用级语音输入,识别只追加当前草稿,不自动发送;切换账号、对话或隐藏页面后丢弃过期识别。支持拖入文件、粘贴图片和多文件选择,上传与确认仍由 Main 持有认证并转发;失败重试保留同一批文件身份。输入框的“+”添加文件或图片;右上角“附件”打开右侧面板,按“你上传的”和“助手生成的”分组,支持再次选用已上传附件及保存明确交付的生成文件。生成文件仅取当前对话已保存助手回复中的本地文件链接(含图片与引用式链接),核对实际文件后展示和计数;只读取链接所在的文件夹,不遍历全部工作文件。未交付的脚本、缓存和工具记录不展示、不计数,明确交付的代码照常展示;仅写了路径的旧成果可能暂时隐藏,后续接入 Yuxi 的正式交付记录。没有文件时隐藏入口;打开、关闭面板保留聊天与输入,Escape 关闭并返回入口焦点;最近对话自动恢复、审批、停止和未发送内容的离开保护保留。模型返回的思考按消息独立于正文展示;正文到来后或完成时折叠,可手动展开;无思考或工具内容时不显示过程区。重连事件去重并隔离当前运行与主对话,终态历史未到达时保留已收到的片段并有限补读;服务端返回的最终回答思考可在历史中回看,不进入后续提问。历史回看需要配套 Yuxi API 的思考和工具记录投影;实时过程沿用既有事件流。平台工具和内置做事手册使用 Yuxi 下发的通俗名称与说明;逐条消息的思考、工具输入/结果/失败独立于 Markdown 正文,实时与历史工具卡片共用展示文案,展开可查看原始工具标识。个人上传手册保留作者文案,资源选择仍保存原始标识。头像上传、Main-owned Host API、访问隔离、费用与不可变版本规则保持;介绍留空需要配套 Yuxi DraftCreate/DraftSave 的空字符串契约。
个人智能体聊天在每条提问与回答下方显示云端记录的消息时间;发送时间位于紫色消息气泡外下方并靠右,助手回复时间保持在回答下方:今天显示时分,跨日增加日期,悬停可查看完整日期与秒数;发送时间与回复时间沿用原消息记录,重开对话不改为当前时间。流式回答尚无可靠时间时显示「回复中…」,历史到达后展示原时间;缺失或无效时间显示「时间未记录」。正式对话、草稿试用和上次试用对比使用相同规则,不新增 Yuxi 接口。
历史待确认记录收起在智能体列表标题区的「创建记录/待处理记录」入口,只有点击后才打开记录窗口;配置、聊天和创建页不再置顶这些旧提醒。记录保留智能体名称和原请求身份;应用设置的待确认记录显示“网络可能不稳定,请再点一次「应用」。”,点击「应用」继续原来的提交,成功后显示“设置已应用。”。其他记录仍可明确「再试一次」或「不再提醒」,不会自动重试或清除。当前创建失败继续在创建表单内提示;关闭记录窗口保留已收到的结果和密钥,切换配置、聊天或创建页后可返回列表继续查看。密钥须明确关闭结果后,才可处理其他记录。
- 高级配置的知识库支持下拉多选,分为「MakeLore知识」和「我的知识库」,已选项可单独移除。MakeLore知识来自当前账号有权读取的 Yuxi 共享目录,只显示名称与简介;移除仅取消当前智能体选用,不删除原库。知识库区域上方左右分区,两张卡片在并排时保持等高:左侧「上传自己的文件」仅放「新建知识库」入口及展开的创建表单,右侧「查看我的知识库」通过下拉框查看本人知识库、从电脑追加资料和管理文件。宽窗口中新建按钮与右侧选择、刷新、上传控件共用居中的水平线,管理下拉框约为原宽度的一半,两种目录刷新均显示为带边框按钮;下方独立「选择知识库」,选择智能体使用的「MakeLore知识」和「我的知识库」,下拉框缩至原宽度的约2/3,并与已选知识库框左右对齐。小窗口按新建、查看、选用顺序纵向排列,控件允许换行并保持可读。创建成功后在右侧打开新库;新建和查看均不会自动加入智能体配置,选用上限不影响管理与新建。仅提供「从电脑上传」作为添加文件入口,未选择管理目标时上传按钮置灰;聊天文件导入入口已移除。上传后自动解析、建立索引并持续显示真实处理状态;失败可重试且保留已有可用内容。暂不可选用的个人库仍可管理文件,已有绑定保留并提示不可用;未确认请求在当前库保留同一操作身份供重试。知识库选择只改草稿,显式保存/应用后生效;个人云端执行已自动接入所选知识库的检索,无需另外勾选 knowledge-base Skill。此交互复用现有接口,无需新增后端补丁。
+ 高级配置的知识库支持下拉多选,分为「MakeLore知识」和「我的知识库」,已选项可单独移除。MakeLore知识来自当前账号有权读取的 Yuxi 共享目录,只显示名称与简介;移除仅取消当前智能体选用,不删除原库。知识库区域上方左右分区,两张卡片在并排时保持等高:左侧「上传自己的文件」将「新建知识库」和「从电脑上传」左右并排居中,下方保留展开的创建表单;右侧「查看我的知识库」通过下拉框选择本人知识库,保留刷新入口和文件管理。宽窗口中左侧两个按钮与右侧选择、刷新控件共用居中的水平线,管理下拉框约为原宽度的一半,两种目录刷新均显示为带边框按钮;下方独立「选择知识库」,选择智能体使用的「MakeLore知识」和「我的知识库」,下拉框缩至原宽度的约2/3,并与已选知识库框左右对齐。小窗口按新建、查看、选用顺序纵向排列,控件允许换行并保持可读。创建成功后在右侧打开新库;新建和查看均不会自动加入智能体配置,选用上限不影响管理与新建。仅提供「从电脑上传」作为添加文件入口,未选择管理目标时上传按钮置灰;聊天文件导入入口已移除。上传后自动解析、建立索引并持续显示真实处理状态;失败可重试且保留已有可用内容。暂不可选用的个人库仍可管理文件,已有绑定保留并提示不可用;未确认请求在当前库保留同一操作身份供重试。知识库选择只改草稿,显式保存/应用后生效;个人云端执行已自动接入所选知识库的检索,无需另外勾选 knowledge-base Skill。此交互复用现有接口,无需新增后端补丁。
云知识库的 embedding 模型只由平台管理员在 Yuxi 后台维护:在「智能体管理 → 模型供应商」启用供应商,按供应商要求填写有效的服务地址和 API Key,添加并启用 embedding 模型并填写实际向量维度;embedding 向量化费用由平台承担,不计入用户用量、词元点数或智能体费用上限;one-api 不参与 embedding 配置,Works Square 只负责其他应计费模型调用的创建者词元点数账本。客户端区分目录加载中、加载失败与无可用模型,空状态提供管理员配置说明;「刷新知识库与模型」保留未提交名称,未确认的创建请求沿用原操作身份。
diff --git a/src/pages/CloudAgents/CloudKnowledge.tsx b/src/pages/CloudAgents/CloudKnowledge.tsx
index c07d0b5e..d863a400 100644
--- a/src/pages/CloudAgents/CloudKnowledge.tsx
+++ b/src/pages/CloudAgents/CloudKnowledge.tsx
@@ -100,6 +100,9 @@ export function CloudKnowledgePanel({ slug, onCreated, onDeleted, onCatalog, ref
上传自己的文件
+
+ {!selectedKnowledge && }
+
{creationDetails}
@@ -119,9 +122,6 @@ export function CloudKnowledgePanel({ slug, onCreated, onDeleted, onCatalog, ref
)}
-
- {!selectedKnowledge && }
-
{errorTarget === 'management' && error &&
{error}
}
diff --git a/src/pages/CloudAgents/ConfigurationFields.tsx b/src/pages/CloudAgents/ConfigurationFields.tsx
index 74667e34..5494ea19 100644
--- a/src/pages/CloudAgents/ConfigurationFields.tsx
+++ b/src/pages/CloudAgents/ConfigurationFields.tsx
@@ -8,8 +8,8 @@ import { AgentConfigHeading } from './AgentConfigHeading';
import type { CloudAgentConfiguration, CloudCatalog } from '../../../shared/cloud-agents';
const resourceSections = [
- { key: 'tools', label: '工具箱', title: '工具箱(Tool)', icon: Wrench, tone: 'blue' },
- { key: 'skills', label: '做事手册', title: '做事手册(Skill)', icon: BookOpen, tone: 'lilac' },
+ { key: 'tools', label: 'TOOL', title: 'TOOL', icon: Wrench, tone: 'blue' },
+ { key: 'skills', label: 'SKILL', title: 'SKILL', icon: BookOpen, tone: 'lilac' },
{ key: 'mcps', label: 'MCP 服务', title: 'MCP 服务', icon: Plug, tone: 'neutral' },
{ key: 'knowledges', label: '知识库', title: '知识库', icon: Library, tone: 'mint' },
] as const;
@@ -75,7 +75,7 @@ export function ConfigurationFields({ slug, value, disabled, onChange, section =
>}
{key === 'skills' && value.skills.length > 0 &&
}
+ onChange={e => change('preload_skills', e.target.checked ? [...value.skills] : [])} />开始对话前,先读好已选SKILL的使用方法}
;
})}
;
diff --git a/src/pages/CloudAgents/agent-config.css b/src/pages/CloudAgents/agent-config.css
index 41601706..e1f1256d 100644
--- a/src/pages/CloudAgents/agent-config.css
+++ b/src/pages/CloudAgents/agent-config.css
@@ -47,7 +47,7 @@
.agent-knowledge-upload, .agent-knowledge-management, .agent-knowledge-selection { min-width: 0; padding: 18px; border: 1px solid #dce5e0; border-radius: 17px; background: #fffdfb; overflow-wrap: anywhere; }
.agent-knowledge-upload { border-color: #dce5f3; }
.agent-knowledge-upload, .agent-knowledge-management { display: grid; grid-template-rows: subgrid; grid-row: span 3; }
-.agent-knowledge-create-action { display: flex; align-items: center; justify-content: center; }
+.agent-knowledge-create-action { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 12px; min-width: 0; }
.agent-knowledge-management-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; min-width: 0; }
.agent-config-card .agent-knowledge-management-controls select { width: calc((100% - 144px - 8px) / 2); flex: none; padding-inline: 8px; font-size: 14px; }
.agent-knowledge-upload-action { display: flex; min-width: 0; }
diff --git a/tests/e2e/cloud-agent-knowledge.spec.ts b/tests/e2e/cloud-agent-knowledge.spec.ts
index 5368ca7c..0a1c377a 100644
--- a/tests/e2e/cloud-agent-knowledge.spec.ts
+++ b/tests/e2e/cloud-agent-knowledge.spec.ts
@@ -186,9 +186,9 @@ async function expectKnowledgeLayout(page: Page) {
const selection = knowledge.getByRole('region', { name: '选择知识库', exact: true });
await expect(creation.getByRole('button', { name: '新建知识库', exact: true })).toBeVisible();
await expect(creation.getByRole('combobox', { name: '管理的知识库', exact: true })).toHaveCount(0);
- await expect(creation.getByRole('button', { name: '从电脑上传', exact: true })).toHaveCount(0);
+ await expect(creation.getByRole('button', { name: '从电脑上传', exact: true })).toBeVisible();
await expect(management.getByRole('combobox', { name: '管理的知识库', exact: true })).toBeVisible();
- await expect(management.getByRole('button', { name: '从电脑上传', exact: true })).toBeVisible();
+ await expect(management.getByRole('button', { name: '从电脑上传', exact: true })).toHaveCount(0);
await expect(management.getByRole('button', { name: '新建知识库', exact: true })).toHaveCount(0);
await expect(management.locator('summary').filter({ hasText: '使用说明与费用' })).toHaveCount(0);
await expect(management.getByText('上传到知识库的文档可供获准使用此智能体的人检索。解析并建立索引后才会生效;embedding 向量化由平台承担,不计入用户用量、词元点数或智能体费用上限。知识处理如触发对话模型调用,仍按原模型实际用量计费。', { exact: true })).toHaveCount(0);
@@ -212,8 +212,11 @@ async function expectKnowledgeLayout(page: Page) {
const create = await creation.getByRole('button', { name: '新建知识库', exact: true }).boundingBox();
const manager = await management.getByRole('combobox', { name: '管理的知识库', exact: true }).boundingBox();
const refresh = await management.getByRole('button', { name: '刷新知识库与模型', exact: true }).boundingBox();
- const upload = await management.getByRole('button', { name: '从电脑上传', exact: true }).boundingBox();
+ const upload = await creation.getByRole('button', { name: '从电脑上传', exact: true }).boundingBox();
const controls = await management.locator('.agent-knowledge-management-controls').boundingBox();
+ expect(create!.x + create!.width).toBeLessThanOrEqual(upload!.x);
+ expect(upload!.x + upload!.width).toBeLessThanOrEqual(creationBounds!.x + creationBounds!.width);
+ expect(manager!.x + manager!.width).toBeLessThanOrEqual(refresh!.x);
for (const control of [manager, refresh, upload]) {
expect(Math.abs(control!.y + control!.height / 2 - create!.y - create!.height / 2)).toBeLessThanOrEqual(1);
}
@@ -305,7 +308,7 @@ for (const width of [375, 1440]) {
const creation = knowledge.getByRole('region', { name: '上传自己的文件', exact: true });
const management = knowledge.getByRole('region', { name: '查看我的知识库', exact: true });
const manager = management.getByRole('combobox', { name: '管理的知识库', exact: true });
- const upload = management.getByRole('button', { name: '从电脑上传', exact: true });
+ const upload = creation.getByRole('button', { name: '从电脑上传', exact: true });
const create = creation.getByRole('button', { name: '新建知识库', exact: true });
await expect(upload).toBeDisabled();
await expectKnowledgeLayout(page);
@@ -382,15 +385,22 @@ test('cancelled and failed uploads preserve shared choices and the personal draf
// An uncertain upload response keeps its retry attached to the same library.
await expect(knowledge.getByRole('combobox', { name: '管理的知识库', exact: true })).toBeDisabled();
await expect(knowledge.getByRole('button', { name: '新建知识库', exact: true })).toBeDisabled();
- await page.setViewportSize({ width: 1440, height: 850 });
+ const creationCard = knowledge.getByRole('region', { name: '上传自己的文件', exact: true });
const managementCard = knowledge.getByRole('region', { name: '查看我的知识库', exact: true });
- const managementBounds = await managementCard.boundingBox();
- for (const control of await managementCard.locator('.agent-knowledge-management-controls').getByRole('button').all()) {
- const bounds = await control.boundingBox();
- expect(bounds!.x).toBeGreaterThanOrEqual(managementBounds!.x);
- expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(managementBounds!.x + managementBounds!.width);
+ await expect(creationCard.getByRole('button', { name: '重新选择同一文件并重试', exact: true })).toBeEnabled();
+ await expect(managementCard.getByRole('button', { name: '重新选择同一文件并重试', exact: true })).toHaveCount(0);
+ for (const width of [375, 768, 1024, 1440]) {
+ await page.setViewportSize({ width, height: 850 });
+ for (const [card, row] of [[creationCard, '.agent-knowledge-create-action'], [managementCard, '.agent-knowledge-management-controls']] as const) {
+ const cardBounds = await card.boundingBox();
+ for (const control of await card.locator(row).getByRole('button').all()) {
+ const bounds = await control.boundingBox();
+ expect(bounds!.x).toBeGreaterThanOrEqual(cardBounds!.x);
+ expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(cardBounds!.x + cardBounds!.width);
+ }
+ }
+ await expectNoClipping(page);
}
- await expectNoClipping(page);
await page.setViewportSize({ width: 375, height: 850 });
const failedUpload = (await calls(page)).filter(call => call.path.endsWith('/knowledge/pick')).at(-1)!;
await page.evaluate(() => { (window as unknown as FixtureWindow).__knowledgeFixture.upload = 'cancel'; });
diff --git a/tests/e2e/cloud-agent-platform-avatars.spec.ts b/tests/e2e/cloud-agent-platform-avatars.spec.ts
index 54865fb6..ce5c5bd6 100644
--- a/tests/e2e/cloud-agent-platform-avatars.spec.ts
+++ b/tests/e2e/cloud-agent-platform-avatars.spec.ts
@@ -187,8 +187,9 @@ for (const width of [375, 768, 1024, 1440]) {
await expect(tabs.getByRole('tab', { name: '高级设置', exact: true })).toHaveAttribute('aria-selected', 'true');
await expect(advanced.getByRole('navigation')).toHaveCount(0);
await expect(advanced.getByRole('tablist')).toHaveCount(0);
- const headings = ['模型', '工具箱(Tool)', '做事手册(Skill)', 'MCP 服务', '子智能体', '知识库'];
+ const headings = ['模型', 'TOOL', 'SKILL', 'MCP 服务', '知识库'];
for (const name of headings) await expect(advanced.getByRole('heading', { name, exact: true })).toBeVisible();
+ await expect(advanced.getByRole('heading', { name: '子智能体', exact: true })).toHaveCount(0);
await expect(advanced.getByRole('heading').first()).toHaveText('模型');
await expect(advanced.getByLabel('模型', { exact: true })).toHaveAttribute('required', '');
for (const copy of ['选它做事时能用的工具。', '给它一本手册,照着方法做。', '管理我的云端能力']) {
diff --git a/tests/e2e/cloud-agents.spec.ts b/tests/e2e/cloud-agents.spec.ts
index fca06e41..ffeb28c8 100644
--- a/tests/e2e/cloud-agents.spec.ts
+++ b/tests/e2e/cloud-agents.spec.ts
@@ -290,11 +290,11 @@ test('personal cloud Agent identity, configuration and separate published chat i
await expect(page.getByRole('group', { name: /^知识库/ })).toBeVisible();
await expect(page.getByRole('heading', { name: '费用上限', exact: true })).toHaveCount(0);
await expect(page.getByRole('heading', { name: '运行限制', exact: true })).toHaveCount(0);
- await page.getByRole('button', { name: '选择工具箱', exact: true }).click();
+ await page.getByRole('button', { name: '选择TOOL', exact: true }).click();
await expect(page.getByRole('checkbox', { name: /上网查资料.*在网上查找相关信息/ })).toBeVisible();
await page.getByRole('checkbox', { name: '资料整理工具', exact: true }).check();
- await page.getByRole('button', { name: '选择做事手册', exact: true }).click();
- await page.getByLabel('搜索做事手册', { exact: true }).fill('出处');
+ await page.getByRole('button', { name: '选择SKILL', exact: true }).click();
+ await page.getByLabel('搜索SKILL', { exact: true }).fill('出处');
await expect(page.getByRole('checkbox', { name: /查资料,做研究.*出处/ })).toBeVisible();
await instructionsTab.click();
await expect(introduction).toHaveValue('一起梳理故事和文章');
diff --git a/tests/unit/cloud-agent-subagent-selector.test.tsx b/tests/unit/cloud-agent-subagent-selector.test.tsx
index 22b5f085..256da0b9 100644
--- a/tests/unit/cloud-agent-subagent-selector.test.tsx
+++ b/tests/unit/cloud-agent-subagent-selector.test.tsx
@@ -39,7 +39,7 @@ it('removes subagent selection while other capability edits preserve historical
expect(screen.queryByText(/missing-child/)).not.toBeInTheDocument();
expect(changed).not.toHaveBeenCalled();
fireEvent.change(screen.getByRole('combobox', { name: '模型' }), { target: { value: 'new-model' } });
- for (const [button, checkbox] of [['选择工具箱', '查询工具'], ['选择做事手册', '研究手册'], ['选择MCP 服务', '资料连接']]) {
+ for (const [button, checkbox] of [['选择TOOL', '查询工具'], ['选择SKILL', '研究手册'], ['选择MCP 服务', '资料连接']]) {
fireEvent.click(screen.getByRole('button', { name: button, exact: true }));
fireEvent.click(screen.getByRole('checkbox', { name: checkbox, exact: true }));
}
diff --git a/tests/unit/cloud-agents-page.test.tsx b/tests/unit/cloud-agents-page.test.tsx
index 0ab04ff7..e35683ef 100644
--- a/tests/unit/cloud-agents-page.test.tsx
+++ b/tests/unit/cloud-agents-page.test.tsx
@@ -486,8 +486,8 @@ it('shows only model and capability settings together and preserves unfinished s
fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false });
const model = screen.getByLabelText('模型');
- const toolbox = screen.getByRole('group', { name: /^工具箱(Tool)/ });
- const handbook = screen.getByRole('group', { name: /^做事手册(Skill)/ });
+ const toolbox = screen.getByRole('group', { name: 'TOOL', exact: true });
+ const handbook = screen.getByRole('group', { name: 'SKILL', exact: true });
expect(model).toBeVisible();
expect(model).toBeRequired();
expect(toolbox).toBeVisible();
@@ -504,10 +504,10 @@ it('shows only model and capability settings together and preserves unfinished s
expect(screen.getAllByRole('tab').map(tab => tab.textContent)).toEqual(['介绍与提示词', '高级设置']);
fireEvent.change(model, { target: { value: 'model-a' } });
- fireEvent.click(screen.getByRole('button', { name: '选择做事手册', exact: true }));
+ fireEvent.click(screen.getByRole('button', { name: '选择SKILL', exact: true }));
fireEvent.click(screen.getByRole('checkbox', { name: '写作方法', exact: true }));
- fireEvent.click(screen.getByRole('button', { name: '选择工具箱', exact: true }));
- const search = screen.getByLabelText('搜索工具箱');
+ fireEvent.click(screen.getByRole('button', { name: '选择TOOL', exact: true }));
+ const search = screen.getByLabelText('搜索TOOL');
fireEvent.change(search, { target: { value: '文章' } });
fireEvent.click(screen.getByRole('checkbox', { name: '文章整理工具', exact: true }));
@@ -520,7 +520,7 @@ it('shows only model and capability settings together and preserves unfinished s
expect(screen.getByLabelText('模型')).toBe(model);
expect(model).toHaveValue('model-a');
- expect(screen.getByLabelText('搜索工具箱')).toBe(search);
+ expect(screen.getByLabelText('搜索TOOL')).toBe(search);
expect(search).toHaveValue('文章');
expect(screen.getByRole('checkbox', { name: '文章整理工具', exact: true })).toBeChecked();
expect(screen.getByRole('checkbox', { name: '写作方法', exact: true })).toBeChecked();
@@ -1003,7 +1003,7 @@ it('preserves hidden runtime policies while saving model, prompt and capability
fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false });
await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled());
fireEvent.change(screen.getByLabelText('模型'), { target: { value: 'another-model' } });
- fireEvent.click(screen.getByRole('button', { name: '选择工具箱', exact: true }));
+ fireEvent.click(screen.getByRole('button', { name: '选择TOOL', exact: true }));
fireEvent.click(screen.getByRole('checkbox', { name: '文章整理工具', exact: true }));
expect(screen.queryByLabelText('最多执行步数')).not.toBeInTheDocument();
expect(screen.queryByLabelText('每日上限(词元点数)')).not.toBeInTheDocument();
@@ -1054,19 +1054,19 @@ it('searches capabilities while retaining existing selections outside the result
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false });
expect(await screen.findByRole('checkbox', { name: /missing-tool/ })).toBeChecked();
- fireEvent.click(screen.getByRole('button', { name: '选择工具箱', exact: true }));
- fireEvent.change(screen.getByLabelText('搜索工具箱'), { target: { value: '资料' } });
+ fireEvent.click(screen.getByRole('button', { name: '选择TOOL', exact: true }));
+ fireEvent.change(screen.getByLabelText('搜索TOOL'), { target: { value: '资料' } });
fireEvent.mouseDown(screen.getByRole('tab', { name: '介绍与提示词' }), { button: 0, ctrlKey: false });
fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false });
- expect(screen.getByLabelText('搜索工具箱')).toHaveValue('资料');
+ expect(screen.getByLabelText('搜索TOOL')).toHaveValue('资料');
expect(screen.queryByRole('checkbox', { name: '当前时间' })).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('checkbox', { name: /上网查资料/ }));
fireEvent.click(screen.getByRole('button', { name: '完成选择' }));
expect(screen.getByRole('checkbox', { name: /missing-tool/ })).toBeChecked();
- fireEvent.click(screen.getByRole('button', { name: '选择做事手册', exact: true }));
- fireEvent.change(screen.getByLabelText('搜索做事手册'), { target: { value: '出处' } });
+ fireEvent.click(screen.getByRole('button', { name: '选择SKILL', exact: true }));
+ fireEvent.change(screen.getByLabelText('搜索SKILL'), { target: { value: '出处' } });
fireEvent.click(screen.getByRole('checkbox', { name: /查资料,做研究/ }));
- fireEvent.click(screen.getByRole('checkbox', { name: '开始对话前,先读好已选做事手册的使用方法' }));
+ fireEvent.click(screen.getByRole('checkbox', { name: '开始对话前,先读好已选SKILL的使用方法' }));
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
await waitFor(() => expect(api.save).toHaveBeenCalledWith(draft.slug, expect.objectContaining({
configuration: expect.objectContaining({ tools: ['missing-tool', 'web_search'], skills: ['deep-research'], preload_skills: ['deep-research'] }),
diff --git a/tests/unit/cloud-knowledge-picker.test.tsx b/tests/unit/cloud-knowledge-picker.test.tsx
index 930f5abc..5ab3587e 100644
--- a/tests/unit/cloud-knowledge-picker.test.tsx
+++ b/tests/unit/cloud-knowledge-picker.test.tsx
@@ -50,9 +50,9 @@ it('joins readable IDs to the owned-only catalog and selects multiple libraries
const selection = screen.getByRole('region', { name: '选择知识库', exact: true });
expect(within(creation).getByRole('button', { name: '新建知识库', exact: true })).toBeVisible();
expect(within(creation).queryByRole('combobox', { name: '管理的知识库' })).not.toBeInTheDocument();
- expect(within(creation).queryByRole('button', { name: '从电脑上传', exact: true })).not.toBeInTheDocument();
+ expect(within(creation).getByRole('button', { name: '从电脑上传', exact: true })).toBeDisabled();
expect(within(management).getByRole('combobox', { name: '管理的知识库' })).toBeVisible();
- expect(within(management).getByRole('button', { name: '从电脑上传', exact: true })).toBeDisabled();
+ expect(within(management).queryByRole('button', { name: '从电脑上传', exact: true })).not.toBeInTheDocument();
expect(within(management).queryByRole('button', { name: '新建知识库', exact: true })).not.toBeInTheDocument();
expect(within(selection).getByRole('combobox', { name: '选择知识库', exact: true })).toBeVisible();
expect(creation.compareDocumentPosition(management) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
@@ -150,10 +150,15 @@ it('limits bindings to 100 libraries without preventing browsing or creation', a
it('browses an owned library without binding it and only explicit selection changes the draft', async () => {
render();
await openOwn();
+ const creation = screen.getByRole('region', { name: '上传自己的文件', exact: true });
+ const management = screen.getByRole('region', { name: '查看我的知识库', exact: true });
+ const upload = within(creation).getByRole('button', { name: '从电脑上传', exact: true });
+ expect(upload).toBeEnabled();
+ expect(within(management).queryByRole('button', { name: '从电脑上传', exact: true })).not.toBeInTheDocument();
expect(changes).not.toHaveBeenCalled();
expect(screen.queryByRole('button', { name: '移除知识库 ' + own.name })).not.toBeInTheDocument();
api.uploadKnowledge.mockResolvedValue(null);
- fireEvent.click(screen.getByRole('button', { name: '从电脑上传', exact: true }));
+ fireEvent.click(upload);
await waitFor(() => expect(api.uploadKnowledge).toHaveBeenCalledTimes(1));
await waitFor(() => expect(screen.getByRole('button', { name: '从电脑上传', exact: true })).toBeEnabled());
expect(api.call.mock.calls.every(([operation]) => operation !== 'processKnowledge')).toBe(true);
@@ -163,7 +168,7 @@ it('browses an owned library without binding it and only explicit selection chan
expect(changes).toHaveBeenLastCalledWith(expect.objectContaining({ knowledges: ['shared', own.kb_id] }));
});
-it('keeps an upload failure in the management column while the creation form is open', async () => {
+it('keeps document errors in management when the left-card upload is used beside an open creation form', async () => {
api.uploadKnowledge.mockRejectedValue(new Error('上传结果未确认'));
render();
await openOwn();
@@ -171,8 +176,10 @@ it('keeps an upload failure in the management column while the creation form is
const management = screen.getByRole('region', { name: '查看我的知识库', exact: true });
fireEvent.click(within(creation).getByRole('button', { name: '新建知识库', exact: true }));
fireEvent.change(within(creation).getByRole('textbox', { name: '知识库名称' }), { target: { value: '还没创建的资料' } });
- fireEvent.click(within(management).getByRole('button', { name: '从电脑上传', exact: true }));
+ fireEvent.click(within(creation).getByRole('button', { name: '从电脑上传', exact: true }));
expect(await within(management).findByRole('alert')).toHaveTextContent('上传结果未确认');
+ expect(within(creation).getByRole('button', { name: '重新选择同一文件并重试', exact: true })).toBeEnabled();
+ expect(within(management).queryByRole('button', { name: '重新选择同一文件并重试', exact: true })).not.toBeInTheDocument();
expect(within(creation).queryByRole('alert')).not.toBeInTheDocument();
expect(within(creation).getByRole('textbox', { name: '知识库名称' })).toHaveValue('还没创建的资料');
expect(changes).not.toHaveBeenCalled();