fix: preserve staged Agent configuration update
This commit is contained in:
1 parent
5a8b8d75e5
commit
2b2f7b96b0
6 files changed
+67
-6
No files matched your search
@@ -72,3 +72,11 @@
|
||||
- Original operation IDs, expected revisions, journal state, explicit retry/discard and duplicate-click guards are unchanged. There are no Main/shared/backend changes or real cloud apply/retry/discard. README reflects current application copy.
|
||||
- Pinned corepack pnpm10.33.4 verification: 98 unit tests across 5 focused suites passed; Renderer typecheck passed; 10 changed source/test files ESLint passed; build:vite passed. Updated browser Host-IPC fixture E2E passed (1); the 375px screenshot was reviewed and the modal fits. Tests verify original ID reuse after network failure and preserve 401/403/409/422/429 explanations.
|
||||
- Compared all 66 Main/preload/utility/resources/package artifact files to immutable live Main7c: exact hash equality, with no missing/extra files. This enables a Renderer-only temporary-app update. That update is the next authorized step; current inputs and UI remain protected, with no reuse of previous one-time input-discard approvals.
|
||||
|
||||
## Concurrent trial update reconciliation
|
||||
|
||||
- Initial immutable stage application-message-20261013-5dd70a31 was prepared for clean source5a8b8d7. Activation was safely held first by a visible dialog, then a nonempty input; no symlink/pointer/runtime mutation was performed by this task. Later the exact predecessor guard refused because another authorized task switched the compatible file payload.
|
||||
- The separate remove-subagent trial task6d9a8e32 staged source2e5411c, containing only the subagent-selector removal and supporting committed product/test/README updates; its peer Scope/Intent/Promotion were read-only and compatible. The human explicitly approved immediate refresh in that chat, but the root could not use its CUA window and left the stage as files-activated-awaiting-cua-refresh, with global pointer still289. No foreign task source/docs/stage were changed here.
|
||||
- Imported only source2e5411c's committed product/test/README diff into this owned worktree, retaining our application copy and all prior knowledge/home/account updates. Relative to staged2e, owned product delta remains exactly the five application files. No copied foreign task records.
|
||||
- Combined verification:99 unit tests/6 suites, typecheck, changed-imported-file ESLint, full build:vite passed. All66 Main/resources/package files again match immutable7c exactly. Initial application fixture/screenshot evidence remains applicable to unchanged application paths.
|
||||
- Runtime source reconciliation will bind a fresh task-owned overlay to both old current-pointer289 and the exact staged-but-not-acknowledged2e predecessor. All input/lease/dialog/user-interaction/focus guards remain, with no input-discard or business-action authorization inferred. Peer stage remains intact; successful activation must establish new own compound-build proof rather than claiming the peer's activation was verified.
|
||||
@@ -12,7 +12,7 @@ Makelore 是一个面向软件、视觉创作、智能机器人与个人云智
|
||||
|
||||
- `Makelore Code|AI 编程`:管理本地项目、项目智能体、对话、文件上下文、代码变更和运行时。
|
||||
会话工作台采用浅蓝项目栏、彩色通用手柄图标与卡片选中态;中间对话/作品使用圆角标签,作品预览的后退、前进、刷新与网址输入统一放在标签右侧;只在作品页显示,窄窗口自动换行。右侧智能体咨询使用奶油色标题区。沿用真实项目和会话入口、侧栏伸缩与账户菜单,样式只作用于 Code 会话工作台。可编辑的空对话在输入区上方的可用区域中央显示童趣欢迎语;「去作品广场逛逛」入口紧贴输入框上方,两者一起位于底部。老师介绍以固定宽度浮在右上角,不占用中间布局;欢迎语和底部输入区始终相对项目栏右侧的整个内容区域居中;小窗口的欢迎内容独立滚动;发送首条消息后收起欢迎区。侧栏保留「作品广场」入口,只有点击后才在系统浏览器打开公共广场,不请求、摘取或内嵌展示广场作品。打开广场保留当前项目、对话、草稿和附件,不自动发送消息;老师求助按钮保留临时应用的最新文案「帮我整体看🧠」。
|
||||
- `Makelore Agents|AI 智能体`:从模块首页进入时先打开“我的智能体”卡片列表,保留此前对话与操作恢复记录;返回首页箭头与侧栏折叠按钮同排,折叠后仍可返回;智能体模块的侧栏只通过按钮点击或键盘激活展开,鼠标经过左侧、收纳按钮或顶栏不会展开,切换模块不沿用旧悬停预览;其他模块仍保留原预览方式。“我的智能体”使用浅紫对话机器人图标,“渠道”使用浅蓝连接节点图标。macOS 原生窗口按钮留出安全空间。首页围绕自己的智能体和创建入口,显示童趣问候“Hi,今天想创造还是想聊聊?”,不再显示更多、收到的分享、活动或归档筛选。卡片恢复原有尺寸与留白,显示名称、可为空的一句话介绍及状态,支持聊天、调整、继续制作和确认删除;删除沿用云端归档机制,移出列表并停用新调用,历史和费用保留。创建先填写名称并选择平台像素头像或上传头像,下一步进入独立配置。顶部名称有编辑图标,头像可点击修改;未设置头像时使用同一张默认像素头像,从创建到配置、改名、保存和重新打开保持一致。基础页为“介绍与提示词”,一句话介绍可留空,完整提示词保留原始文本;模型作为高级设置第一项必选,未选择时不能开始使用,草稿可分步保存,不自动选模型。介绍与提示词采用浅蓝介绍卡和浅紫提示词卡;高级设置按模型、工具箱(Tool)、做事手册(Skill)、MCP 服务、子智能体和知识库纵向呈现,不再设内部分类切换;不展示 Tool/Skill 解释、资源开放规则提示、云端能力管理、费用上限或运行限制卡片,保存其他配置时保留既有运行限制,费用规则不变。顶层页签切换保留搜索、资源选择、知识表单及未保存输入。配置页移除聊天、自动任务和使用与接入导航,独立聊天仍从卡片进入,微信账号仍在侧栏渠道管理。草稿明确保存(Ctrl/⌘+S);保存只更新草稿,“应用最新修改”使新配置生效,失败重试保持原操作身份;点击“开始使用”完成必要的私有启用后直接打开聊天,不再展示二次确认页;未保存修改、缺少模型、冲突或尚未确认的请求会留在配置页处理,打开聊天不自动发送消息。独立对话沿用奶油底色和浅蓝、浅紫、薄荷色:顶部展示返回、只读头像和名称;有附件时在同一行右侧显示回形针“附件”入口和文件数量,不展示调整、保存/应用状态、客户端/微信来源、费用/版本说明或历史/新对话图标;配置由列表卡片的“调整”进入。空对话展示当前智能体头像与名称,示例只填入草稿;用户消息使用浅紫圆角气泡,助手 Markdown、表格和代码在消息区内滚动。文件或图片从输入框内添加,上传成功后以待发送卡片呈现,可移除本次携带;移除不会删除云端文件,添加不会自动发送。确认失败沿用同一文件和对话重试,发送前需完成或取消添加;支持仅发送附件,使用明确的附件提问。输入框的“+”添加文件或图片;右上角“附件”打开右侧面板,按“你上传的”和“助手生成的”分组,支持再次选用已上传附件及保存明确交付的生成文件。生成文件仅取当前对话已保存助手回复中的本地文件链接(含图片与引用式链接),核对实际文件后展示和计数;只读取链接所在的文件夹,不遍历全部工作文件。未交付的脚本、缓存和工具记录不展示、不计数,明确交付的代码照常展示;仅写了路径的旧成果可能暂时隐藏,后续接入 Yuxi 的正式交付记录。没有文件时隐藏入口;打开、关闭面板保留聊天与输入,Escape 关闭并返回入口焦点;最近对话自动恢复、审批、停止和未发送内容的离开保护保留。模型返回的思考按消息独立于正文展示;正文到来后或完成时折叠,可手动展开;无思考或工具内容时不显示过程区。重连事件去重并隔离当前运行与主对话,终态历史未到达时保留已收到的片段并有限补读;服务端返回的最终回答思考可在历史中回看,不进入后续提问。历史回看需要配套 Yuxi API 的思考和工具记录投影;实时过程沿用既有事件流。平台工具和内置做事手册使用 Yuxi 下发的通俗名称与说明;逐条消息的思考、工具输入/结果/失败独立于 Markdown 正文,实时与历史工具卡片共用展示文案,展开可查看原始工具标识。个人上传手册保留作者文案,资源选择仍保存原始标识。头像上传、Main-owned Host API、访问隔离、费用与不可变版本规则保持;介绍留空需要配Line truncated
|
||||
- `Makelore Agents|AI 智能体`:从模块首页进入时先打开“我的智能体”卡片列表,保留此前对话与操作恢复记录;返回首页箭头与侧栏折叠按钮同排,折叠后仍可返回;智能体模块的侧栏只通过按钮点击或键盘激活展开,鼠标经过左侧、收纳按钮或顶栏不会展开,切换模块不沿用旧悬停预览;其他模块仍保留原预览方式。“我的智能体”使用浅紫对话机器人图标,“渠道”使用浅蓝连接节点图标。macOS 原生窗口按钮留出安全空间。首页围绕自己的智能体和创建入口,显示童趣问候“Hi,今天想创造还是想聊聊?”,不再显示更多、收到的分享、活动或归档筛选。卡片恢复原有尺寸与留白,显示名称、可为空的一句话介绍及状态,支持聊天、调整、继续制作和确认删除;删除沿用云端归档机制,移出列表并停用新调用,历史和费用保留。创建先填写名称并选择平台像素头像或上传头像,下一步进入独立配置。顶部名称有编辑图标,头像可点击修改;未设置头像时使用同一张默认像素头像,从创建到配置、改名、保存和重新打开保持一致。基础页为“介绍与提示词”,一句话介绍可留空,完整提示词保留原始文本;模型作为高级设置第一项必选,未选择时不能开始使用,草稿可分步保存,不自动选模型。介绍与提示词采用浅蓝介绍卡和浅紫提示词卡;高级设置按模型、工具箱(Tool)、做事手册(Skill)、MCP 服务和知识库纵向呈现;移除子智能体选择模块,保存其他配置时保留历史子智能体绑定,不再设内部分类切换;不展示 Tool/Skill 解释、资源开放规则提示、云端能力管理、费用上限或运行限制卡片,保存其他配置时保留既有运行限制,费用规则不变。顶层页签切换保留搜索、资源选择、知识表单及未保存输入。配置页移除聊天、自动任务和使用与接入导航,独立聊天仍从卡片进入,微信账号仍在侧栏渠道管理。草稿明确保存(Ctrl/⌘+S);保存只更新草稿,“应用最新修改”使新配置生效,失败重试保持原操作身份;点击“开始使用”完成必要的私有启用后直接打开聊天,不再展示二次确认页;未保存修改、缺少模型、冲突或尚未确认的请求会留在配置页处理,打开聊天不自动发送消息。独立对话沿用奶油底色和浅蓝、浅紫、薄荷色:顶部展示返回、只读头像和名称;有附件时在同一行右侧显示回形针“附件”入口和文件数量,不展示调整、保存/应用状态、客户端/微信来源、费用/版本说明或历史/新对话图标;配置由列表卡片的“调整”进入。空对话展示当前智能体头像与名称,示例只填入草稿;用户消息使用浅紫圆角气泡,助手 Markdown、表格和代码在消息区内滚动。文件或图片从输入框内添加,上传成功后以待发送卡片呈现,可移除本次携带;移除不会删除云端文件,添加不会自动发送。确认失败沿用同一文件和对话重试,发送前需完成或取消添加;支持仅发送附件,使用明确的附件提问。输入框的“+”添加文件或图片;右上角“附件”打开右侧面板,按“你上传的”和“助手生成的”分组,支持再次选用已上传附件及保存明确交付的生成文件。生成文件仅取当前对话已保存助手回复中的本地文件链接(含图片与引用式链接),核对实际文件后展示和计数;只读取链接所在的文件夹,不遍历全部工作文件。未交付的脚本、缓存和工具记录不展示、不计数,明确交付的代码照常展示;仅写了路径的旧成果可能暂时隐藏,后续接入 Yuxi 的正式交付记录。没有文件时隐藏入口;打开、关闭面板保留聊天与输入,Escape 关闭并返回入口焦点;最近对话自动恢复、审批、停止和未发送内容的离开保护保留。模型返回的思考按消息独立于正文展示;正文到来后或完成时折叠,可手动展开;无思考或工具内容时不显示过程区。重连事件去重并隔离当前运行与主对话,终态历史未到达时保留已收到的片段并有限补读;服务端返回的最终回答思考可在历史中回看,不进入后续提问。历史回看需要配套 Yuxi API 的思考和工具记录投影;实时过程沿用既有事件流。平台工具和内置做事手册使用 Yuxi 下发的通俗名称与说明;逐条消息的思考、工具输入/结果/失败独立于 Markdown 正文,实时与历史工具卡片共用展示文案,展开可查看原始工具标识。个人上传手册保留作者文案,资源选择仍保存原始标识。头像上传、Main-owned Host API、访Line truncated
|
||||
|
||||
历史待确认记录收起在智能体列表标题区的「创建记录/待处理记录」入口,只有点击后才打开记录窗口;配置、聊天和创建页不再置顶这些旧提醒。记录保留智能体名称和原请求身份;应用设置的待确认记录显示“网络可能不稳定,请再点一次「应用」。”,点击「应用」继续原来的提交,成功后显示“设置已应用。”。其他记录仍可明确「再试一次」或「不再提醒」,不会自动重试或清除。当前创建失败继续在创建表单内提示;关闭记录窗口保留已收到的结果和密钥,切换配置、聊天或创建页后可返回列表继续查看。密钥须明确关闭结果后,才可处理其他记录。
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export function CloudResources({ onChanged }: { onChanged: () => void }) {
|
||||
return <details className="rounded-lg border p-4" onToggle={e => { if (e.currentTarget.open && !data) void act(refresh); }}>
|
||||
<summary className="min-h-8 cursor-pointer text-sm font-medium">管理我的云端能力</summary>
|
||||
<div className="mt-4 space-y-6 text-sm">
|
||||
<p className="text-xs leading-5 text-muted-foreground">连接、做事手册和子智能体属于你的个人账号。创建后在上面的目录选用,保存并发布后生效。</p>
|
||||
<p className="text-xs leading-5 text-muted-foreground">连接与做事手册属于你的个人账号。创建后在目录选用,保存并发布后生效。</p>
|
||||
{error && <p role="alert" className="text-destructive">{error} <Button variant="ghost" onClick={() => void act(refresh)}>刷新能力</Button></p>}
|
||||
<section className="space-y-3"><div className="flex items-center justify-between"><h3 className="font-medium">MCP 连接</h3><Button type="button" size="sm" variant="outline" disabled={busy || Boolean(mcp)} onClick={() => setMcp('new')}>添加 MCP</Button></div>
|
||||
<p className="text-xs text-muted-foreground">连接由云端访问。凭据只用于你的连接,分享调用可在你已发布的授权范围内使用这些工具。</p>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { BookOpen, Brain, Library, Plug, RefreshCw, Users, Wrench } from 'lucide-react';
|
||||
import { BookOpen, Brain, Library, Plug, RefreshCw, Wrench } from 'lucide-react';
|
||||
import { cloudAgentsApi } from '@/lib/cloud-agents-api';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -11,7 +11,6 @@ const resourceSections = [
|
||||
{ key: 'tools', label: '工具箱', title: '工具箱(Tool)', icon: Wrench, tone: 'blue' },
|
||||
{ key: 'skills', label: '做事手册', title: '做事手册(Skill)', icon: BookOpen, tone: 'lilac' },
|
||||
{ key: 'mcps', label: 'MCP 服务', title: 'MCP 服务', icon: Plug, tone: 'neutral' },
|
||||
{ key: 'subagents', label: '子智能体', title: '子智能体', icon: Users, tone: 'neutral' },
|
||||
{ key: 'knowledges', label: '知识库', title: '知识库', icon: Library, tone: 'mint' },
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ test('personal cloud Agent identity, configuration and separate published chat i
|
||||
const knowledgeFile = { file_id: 'document', name: '本体(Ontology)学习路线图.pdf', size: 34000, error: null };
|
||||
let limits: { request_limit_points: string | null; daily_limit_points: string | null } = { request_limit_points: null, daily_limit_points: null };
|
||||
const jobs: Record<string, unknown>[] = [];
|
||||
const configuration = { model: '', tools: [], knowledges: [], mcps: [], skills: [], preload_skills: [], subagents: [], tool_approval_mode: 'default', max_execution_steps: 40, max_output_tokens: 4096, max_run_seconds: 600 };
|
||||
const configuration = { model: '', tools: [], knowledges: [], mcps: [], skills: [], preload_skills: [], subagents: ['historical-child'], tool_approval_mode: 'default', max_execution_steps: 40, max_output_tokens: 4096, max_run_seconds: 600 };
|
||||
const result = (json: unknown) => ({ ok: true, data: { status: 200, ok: true, json } });
|
||||
ipcMain.removeHandler('hostapi:fetch');
|
||||
ipcMain.handle('hostapi:fetch', async (_event, request: { path?: string; method?: string; body?: string }) => {
|
||||
@@ -244,6 +244,8 @@ test('personal cloud Agent identity, configuration and separate published chat i
|
||||
await expect(page.getByText('给它一本手册,照着方法做。', { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByText('管理我的云端能力', { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByText('未选择的资源不会开放给此智能体。通过微信或 API 使用时也遵循本配置。', { exact: true })).toHaveCount(0);
|
||||
await expect(page.getByRole('group', { name: '子智能体', exact: true })).toHaveCount(0);
|
||||
await expect(page.getByRole('button', { name: '选择子智能体', exact: true })).toHaveCount(0);
|
||||
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);
|
||||
@@ -263,7 +265,7 @@ test('personal cloud Agent identity, configuration and separate published chat i
|
||||
await expect(prompt).toHaveValue(initialPrompt);
|
||||
await page.getByRole('button', { name: '保存草稿', exact: true }).click();
|
||||
await expect(page.getByRole('button', { name: '已保存', exact: true })).toBeDisabled();
|
||||
expect((await fixtureState()).draft).toMatchObject({ purpose: '一起梳理故事和文章', system_prompt: initialPrompt, configuration: { model: 'reasoning-model', tools: ['fixture-tool'] } });
|
||||
expect((await fixtureState()).draft).toMatchObject({ purpose: '一起梳理故事和文章', system_prompt: initialPrompt, configuration: { model: 'reasoning-model', tools: ['fixture-tool'], subagents: ['historical-child'] } });
|
||||
await editor.getByRole('button', { name: '我的智能体', exact: true }).click();
|
||||
const card = page.getByRole('article', { name: '我的写作搭档', exact: true });
|
||||
await expect(card.getByText('一起梳理故事和文章', { exact: true })).toBeVisible();
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useState } from 'react';
|
||||
import { cleanup, fireEvent, render, screen, within } from '@testing-library/react';
|
||||
import { afterEach, expect, it, vi } from 'vitest';
|
||||
import { ConfigurationFields } from '@/pages/CloudAgents/ConfigurationFields';
|
||||
import { EMPTY_CLOUD_CONFIGURATION, type CloudAgentConfiguration } from '../../shared/cloud-agents';
|
||||
|
||||
const api = vi.hoisted(() => ({ call: vi.fn() }));
|
||||
vi.mock('@/lib/cloud-agents-api', () => ({ cloudAgentsApi: api }));
|
||||
afterEach(() => { cleanup(); vi.resetAllMocks(); });
|
||||
|
||||
it('removes subagent selection while other capability edits preserve historical bindings', async () => {
|
||||
api.call.mockImplementation(async operation => {
|
||||
if (operation === 'catalog') return {
|
||||
models: [{ id: 'old-model', name: '原模型' }, { id: 'new-model', name: '新模型' }],
|
||||
resources: {
|
||||
tools: [{ key: 'tool', name: '查询工具' }], skills: [{ key: 'skill', name: '研究手册' }],
|
||||
mcps: [{ key: 'mcp', name: '资料连接' }], knowledges: [{ key: 'kb', name: '已有资料' }],
|
||||
subagents: [{ key: 'historical-child', name: '调研探索员' }],
|
||||
},
|
||||
};
|
||||
if (operation === 'knowledge') return { databases: [], models: [] };
|
||||
throw new Error('Unexpected operation ' + operation);
|
||||
});
|
||||
const initial: CloudAgentConfiguration = {
|
||||
...EMPTY_CLOUD_CONFIGURATION, model: 'old-model', subagents: ['historical-child', 'missing-child'],
|
||||
max_execution_steps: 30, max_output_tokens: 2048, max_run_seconds: 300,
|
||||
};
|
||||
const changed = vi.fn();
|
||||
function Harness() {
|
||||
const [value, setValue] = useState(initial);
|
||||
return <ConfigurationFields slug={'ml-' + 'a'.repeat(32)} value={value} disabled={false}
|
||||
onChange={next => { changed(next); setValue(next); }} />;
|
||||
}
|
||||
render(<Harness />);
|
||||
await screen.findByRole('option', { name: '新模型' });
|
||||
expect(screen.queryByRole('group', { name: '子智能体', exact: true })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '选择子智能体', exact: true })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('调研探索员')).not.toBeInTheDocument();
|
||||
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 服务', '资料连接']]) {
|
||||
fireEvent.click(screen.getByRole('button', { name: button, exact: true }));
|
||||
fireEvent.click(screen.getByRole('checkbox', { name: checkbox, exact: true }));
|
||||
}
|
||||
await within(screen.getByRole('combobox', { name: '选择知识库' })).findByRole('option', { name: '已有资料' });
|
||||
fireEvent.change(screen.getByRole('combobox', { name: '选择知识库' }), { target: { value: 'kb' } });
|
||||
expect(changed).toHaveBeenLastCalledWith({
|
||||
...initial, model: 'new-model', tools: ['tool'], skills: ['skill'], mcps: ['mcp'], knowledges: ['kb'],
|
||||
});
|
||||
expect(api.call.mock.calls.every(([operation]) => ['catalog', 'knowledge'].includes(operation))).toBe(true);
|
||||
});
|
||||
Reference in new issue
Block a user