diff --git a/apps/web/tests/agent-preset-authoring.e2e.ts b/apps/web/tests/agent-preset-authoring.e2e.ts index 88ae171e85..2572b1a5ff 100644 --- a/apps/web/tests/agent-preset-authoring.e2e.ts +++ b/apps/web/tests/agent-preset-authoring.e2e.ts @@ -78,30 +78,68 @@ describe('web e2e: agent-preset authoring is a host-side copy', () => { await dialog.getByRole('heading', { name: 'Agent 预设' }).waitFor({ timeout: 10_000 }) // The intro copy also names 标准模式. Wait for the roster's own action so // the snapshot cannot land between the section shell and its cards. - await dialog.getByRole('button', { name: '查看: 标准模式', exact: true }).waitFor({ timeout: 10_000 }) + await dialog.getByRole('button', { name: '查看配置: 标准模式', exact: true }).waitFor({ timeout: 10_000 }) const snapshot = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd) await compareOrRefreshGolden(SECTION_EXPECTED, snapshot, MODE) - const toggle = dialog.getByRole('switch', { name: '允许切换agent模式' }) + const toggle = dialog.getByRole('switch', { name: '新任务可选择模式' }) expect(await toggle.getAttribute('aria-checked')).toBe('true') - // The intro states the copy path directly, and the shipped rows offer + // The authoring entry is visible, and the shipped rows offer // view/copy but never delete or a location — their // install is overwritten by upgrades and is not the user's to manage. - expect(snapshot).toContain('或用「创造模式」让 Agent 帮你创建') + expect(snapshot).toContain('让 Agent 帮我创建预设模式') expect(snapshot).not.toContain('新建预设') - expect(snapshot).toContain('查看: 标准模式') + expect(snapshot).toContain('查看配置: 标准模式') expect(snapshot).not.toContain('删除: 标准模式') expect(snapshot).not.toContain('打开目录') // The rest of this scenario exercises the existing default and Creator // actions with the beta picker enabled by default. }, 60_000) + it('reads technical details and examples without changing the new-task default', async () => { + const settings = settingsDialog() + const trigger = settings.getByRole('button', { name: '模式说明: PTC 模式', exact: true }) + await trigger.click() + const guide = page.getByRole('dialog', { name: 'PTC 模式', exact: true }) + await guide.getByRole('heading', { name: '怎样调用工具', exact: true }).waitFor() + const beforeSwitch = await guide.boundingBox() + expect(await guide.textContent()).toContain('run_code') + await guide.getByRole('tab', { name: '如何使用', exact: true }).click() + await guide.getByRole('heading', { name: '批量检查配置文件', exact: true }).waitFor() + expect(await guide.boundingBox()).toEqual(beforeSwitch) + await guide.getByRole('tab', { name: '如何使用', exact: true }).press('ArrowLeft') + expect(await guide.getByRole('tab', { name: '模式说明', exact: true }).getAttribute('aria-selected')).toBe('true') + await guide.getByRole('tab', { name: '模式说明', exact: true }).press('Escape') + await guide.waitFor({ state: 'detached' }) + expect(await trigger.evaluate(element => document.activeElement === element)).toBe(true) + expect(await settings.isVisible()).toBe(true) + expect(await settings.getByRole('button', { name: '新任务默认: 标准模式', exact: true }).getAttribute('aria-pressed')).toBe('true') + + await settings.getByRole('button', { name: '如何使用: 创造模式', exact: true }).click() + const creator = page.getByRole('dialog', { name: '创造模式', exact: true }) + await creator.getByRole('heading', { name: '添加一个界面', exact: true }).waitFor() + expect(await creator.getByRole('heading', { name: '添加一个工具', exact: true }).isVisible()).toBe(true) + const closePosition = await creator.getByRole('button', { name: '关闭', exact: true }).boundingBox() + const examples = creator.getByRole('tabpanel', { name: '如何使用', exact: true }) + const scrolledTo = await examples.evaluate((element) => { + element.scrollTop = element.scrollHeight + return element.scrollTop + }) + expect(scrolledTo).toBeGreaterThan(0) + expect(await creator.getByRole('button', { name: '关闭', exact: true }).boundingBox()).toEqual(closePosition) + await creator.getByRole('tab', { name: '模式说明', exact: true }).click() + await creator.getByRole('tab', { name: '如何使用', exact: true }).click() + expect(await examples.evaluate(element => element.scrollTop)).toBe(scrolledTo) + await creator.getByRole('button', { name: '关闭', exact: true }).click() + await creator.waitFor({ state: 'detached' }) + }, 60_000) + it('views a shipped composition read-only instead of editing it', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-preset-authoring-view')) const dialog = settingsDialog() - await dialog.getByRole('button', { name: '查看: 标准模式' }).click() - const viewer = page.getByRole('dialog', { name: '查看 · 标准模式' }) + await dialog.getByRole('button', { name: '查看配置: 标准模式' }).click() + const viewer = page.getByRole('dialog', { name: '查看配置 · 标准模式' }) await viewer.waitFor({ timeout: 10_000 }) // The real shipped composition, not a golden: the viewer shows whatever @@ -118,7 +156,7 @@ describe('web e2e: agent-preset authoring is a host-side copy', () => { it('copies 极简模式 whole under a new id and lands in its files', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-preset-authoring-copy')) const dialog = settingsDialog() - await dialog.getByRole('button', { name: '复制: 极简模式' }).click() + await dialog.getByRole('button', { name: '复制预设: 极简模式' }).click() const copyDialog = page.getByRole('dialog', { name: '复制预设 · 复制自 极简模式' }) await copyDialog.waitFor({ timeout: 10_000 }) @@ -131,7 +169,7 @@ describe('web e2e: agent-preset authoring is a host-side copy', () => { expect(dialogSnapshot).not.toContain('描述') await copyDialog.getByPlaceholder('my-agent').fill('my-agent') - await copyDialog.getByPlaceholder('选择器中显示的名字,缺省用标识符').fill('我的模式') + await copyDialog.getByPlaceholder('在模式列表中显示的名称,不填则使用标识符').fill('我的模式') await copyDialog.getByRole('button', { name: '创建' }).click() await copyDialog.waitFor({ state: 'detached', timeout: 10_000 }) @@ -175,7 +213,7 @@ describe('web e2e: agent-preset authoring is a host-side copy', () => { // The custom group outlives its only member: the heading stays with the // creator entry so the place to author a preset never disappears. expect(await dialog.getByRole('heading', { name: '自定义' }).count()).toBe(1) - expect(await dialog.getByRole('button', { name: '用「创造模式」创作自定义预设' }).count()).toBe(1) + expect(await dialog.getByRole('button', { name: '让 Agent 帮我创建预设模式' }).count()).toBe(1) expect(await dialog.getByText('标准模式').count()).toBeGreaterThan(0) }, 60_000) @@ -205,10 +243,10 @@ describe('web e2e: agent-preset authoring is a host-side copy', () => { expect(snapshot).toContain('not valid YAML') expect(snapshot).toContain('agent.cordis.yml is missing') expect(await dialog.getByRole('button', { name: '加载失败: broken-yaml' }).isDisabled()).toBe(true) - expect(await dialog.getByRole('button', { name: '复制: 幽灵预设' }).isDisabled()).toBe(true) + expect(await dialog.getByRole('button', { name: '复制预设: 幽灵预设' }).isDisabled()).toBe(true) // A broken card offers no "set default" affordance at all — the aria name // IS the broken marking, so the picking name must not exist. - expect(await dialog.getByRole('button', { name: '设为默认: broken-yaml' }).count()).toBe(0) + expect(await dialog.getByRole('button', { name: '设为新任务默认: broken-yaml' }).count()).toBe(0) // The ghost's way out is the card's own delete — and the id it blocked // is claimable again immediately afterwards. @@ -220,13 +258,13 @@ describe('web e2e: agent-preset authoring is a host-side copy', () => { await expect.poll(async () => dialog.getByText('幽灵预设').count(), { timeout: 10_000 }).toBe(0) expect(existsSync(join(userRoot, 'ghost'))).toBe(false) - await dialog.getByRole('button', { name: '复制: 极简模式' }).click() + await dialog.getByRole('button', { name: '复制预设: 极简模式' }).click() const copyDialog = page.getByRole('dialog', { name: '复制预设 · 复制自 极简模式' }) await copyDialog.waitFor({ timeout: 10_000 }) await copyDialog.getByPlaceholder('my-agent').fill('ghost') await copyDialog.getByRole('button', { name: '创建' }).click() await copyDialog.waitFor({ state: 'detached', timeout: 10_000 }) - await dialog.getByRole('button', { name: '设为默认: ghost' }).waitFor({ timeout: 10_000 }) + await dialog.getByRole('button', { name: '设为新任务默认: ghost' }).waitFor({ timeout: 10_000 }) // Leave the roster as the earlier tests shaped it. await dialog.getByRole('button', { name: '删除: ghost' }).click() @@ -248,7 +286,7 @@ describe('web e2e: agent-preset authoring is a host-side copy', () => { const dialog = settingsDialog() await dialog.waitFor({ timeout: 10_000 }) await dialog.getByRole('button', { name: 'Agent 预设' }).click() - await dialog.getByRole('button', { name: '用「创造模式」创作自定义预设' }).click() + await dialog.getByRole('button', { name: '让 Agent 帮我创建预设模式' }).click() // Leaving settings is part of the gesture: the flow lands on the // new-session screen with the self-referential preset staged, and the diff --git a/apps/web/tests/agent-preset-selection.e2e.ts b/apps/web/tests/agent-preset-selection.e2e.ts index b7facbb598..6f9a7d8044 100644 --- a/apps/web/tests/agent-preset-selection.e2e.ts +++ b/apps/web/tests/agent-preset-selection.e2e.ts @@ -265,7 +265,7 @@ describe('web e2e: agent-preset selection', () => { await page.getByRole('button', { name: 'Settings', exact: true }).click() const dialog = page.getByRole('dialog', { name: 'Settings' }) await dialog.getByRole('button', { name: 'Agent presets' }).click() - const toggle = dialog.getByRole('switch', { name: 'Allow switching Agent modes' }) + const toggle = dialog.getByRole('switch', { name: 'Choose a mode for new tasks' }) await dialog.getByRole('button', { name: 'New task default: Standard mode' }).waitFor({ timeout: 10_000 }) expect(await toggle.getAttribute('aria-checked')).toBe('true') await dialog.getByRole('button', { name: 'Close' }).last().click() @@ -365,13 +365,13 @@ describe('web e2e: agent-preset selection', () => { await page.getByRole('button', { name: 'Settings', exact: true }).click() const dialog = page.getByRole('dialog', { name: 'Settings' }) await dialog.getByRole('button', { name: 'Agent presets' }).click() - await dialog.getByRole('button', { name: 'Set as default: Minimal mode' }).click() + await dialog.getByRole('button', { name: 'Set as new task default: Minimal mode' }).click() await dialog.getByRole('button', { name: 'New task default: Minimal mode' }).waitFor({ timeout: 10_000 }) await expect.poll(() => livePreset(scaffold), { timeout: 15_000 }).toBe('minimal') - const toggle = dialog.getByRole('switch', { name: 'Allow switching Agent modes' }) + const toggle = dialog.getByRole('switch', { name: 'Choose a mode for new tasks' }) await toggle.click() await expect.poll(() => toggle.getAttribute('aria-checked')).toBe('false') - await dialog.getByRole('button', { name: 'Default: Standard mode' }).waitFor({ timeout: 10_000 }) + await dialog.getByRole('button', { name: 'Application default: Standard mode' }).waitFor({ timeout: 10_000 }) await dialog.getByRole('button', { name: 'Close' }).last().click() await expect.poll(() => page.getByRole('button', { name: / mode$/ }).count()).toBe(0) @@ -382,7 +382,7 @@ describe('web e2e: agent-preset selection', () => { await page.getByRole('button', { name: 'Settings', exact: true }).click() const reopened = page.getByRole('dialog', { name: 'Settings' }) await reopened.getByRole('button', { name: 'Agent presets' }).click() - const reopenedToggle = reopened.getByRole('switch', { name: 'Allow switching Agent modes' }) + const reopenedToggle = reopened.getByRole('switch', { name: 'Choose a mode for new tasks' }) await reopenedToggle.click() await expect.poll(() => reopenedToggle.getAttribute('aria-checked')).toBe('true') await reopened.getByRole('button', { name: 'New task default: Minimal mode' }).waitFor({ timeout: 10_000 }) diff --git a/apps/web/tests/expected/agent-preset-authoring/copy-dialog.expected.md b/apps/web/tests/expected/agent-preset-authoring/copy-dialog.expected.md index 6e0cbf8c14..f3d67b4e69 100644 --- a/apps/web/tests/expected/agent-preset-authoring/copy-dialog.expected.md +++ b/apps/web/tests/expected/agent-preset-authoring/copy-dialog.expected.md @@ -1,13 +1,13 @@ - dialog "复制预设 · 复制自 极简模式": - heading "复制预设 · 复制自 极简模式" [level=2] - button "关闭" - - paragraph: 整个预设会在本机复制一份。标识符将成为目录名,事后无法更改;其余内容之后直接在预设自己的文件里编辑。 + - paragraph: 复制后会在本机保存一份预设,你可以打开目录修改配置。标识符用作目录名,创建后不能修改。 - text: 标识符 - textbox "标识符": - /placeholder: my-agent - text: 名称 - textbox "名称": - - /placeholder: 选择器中显示的名字,缺省用标识符 + - /placeholder: 在模式列表中显示的名称,不填则使用标识符 - alert: 请填写标识符。 - button "取消" - button "创建" [disabled] diff --git a/apps/web/tests/expected/agent-preset-authoring/created.expected.md b/apps/web/tests/expected/agent-preset-authoring/created.expected.md index f55d4098d2..6a52ce25ac 100644 --- a/apps/web/tests/expected/agent-preset-authoring/created.expected.md +++ b/apps/web/tests/expected/agent-preset-authoring/created.expected.md @@ -8,46 +8,59 @@ - button "打开配置文件" - button "关闭" - heading "Agent 预设" [level=2] - - paragraph: 预设即一个会话的 Agent 所运行的插件组装 —— 它的工具、提示词与能力。复制一份既有预设改成自己的,或用「创造模式」让 Agent 帮你创建。 - - text: 允许切换agent模式 beta - - paragraph: 开启后,新任务可选择标准、PTC、创造、极简及自定义模式;关闭后统一使用默认模式(默认为标准模式,可自定义)。仅影响新任务。 - - switch "允许切换agent模式" [checked] + - paragraph: 选择 Agent 的工具和工作方式。日常任务用「标准模式」,扩展 DSH 的能力用「创造模式」。 + - text: 新任务可选择模式 Beta + - paragraph: 开启后,可为每个新任务选择模式,并在这里设置默认值。关闭后,新任务使用应用配置的默认预设。已有任务不受影响。 + - switch "新任务可选择模式" [checked] - heading "内置" [level=3] - list: - listitem: - 'button "新任务默认: 标准模式" [disabled] [pressed]': - - text: 标准模式 内置 新任务默认 功能完整的编码 Agent,支持文件编辑、Shell、文件与网页检索、Skills、计划、目标、子代理和工作流。 + - text: 标准模式 新任务默认 - code: standard - - 'button "查看: 标准模式"': 查看 - - 'button "复制: 标准模式"': 复制 + - text: 处理代码、文件和资料,适合大多数任务。Agent 会按需使用检索、编辑和终端等工具。 + - 'button "模式说明: 标准模式"': 模式说明 + - 'button "如何使用: 标准模式"': 如何使用 + - 'button "查看配置: 标准模式"': 查看配置 + - 'button "复制预设: 标准模式"': 复制预设 - listitem: - - 'button "设为默认: PTC 模式"': - - text: PTC 模式 内置 功能完整的编码 Agent,但默认不提供 workflow 工具;其他工具通过 PTC 模式 SDK 呈现,让模型用一个 TypeScript 程序组合多步操作。 + - 'button "设为新任务默认: PTC 模式"': + - text: PTC 模式 内置 - code: ptc - - 'button "查看: PTC 模式"': 查看 - - 'button "复制: PTC 模式"': 复制 + - text: 包含标准模式的所有能力,让 Agent 用代码串联多个工具,完成批量处理、筛选和汇总。你只需描述任务。 + - 'button "模式说明: PTC 模式"': 模式说明 + - 'button "如何使用: PTC 模式"': 如何使用 + - 'button "查看配置: PTC 模式"': 查看配置 + - 'button "复制预设: PTC 模式"': 复制预设 - listitem: - - 'button "设为默认: 极简模式"': - - text: 极简模式 内置 仅提供持久 shell 的单工具编码 Agent。 + - 'button "设为新任务默认: 极简模式"': + - text: 极简模式 内置 - code: minimal - - 'button "查看: 极简模式"': 查看 - - 'button "复制: 极简模式"': 复制 + - text: Agent 仅使用终端工具完成任务,适合测试和对比其基础表现。 + - 'button "模式说明: 极简模式"': 模式说明 + - 'button "如何使用: 极简模式"': 如何使用 + - 'button "查看配置: 极简模式"': 查看配置 + - 'button "复制预设: 极简模式"': 复制预设 - listitem: - - 'button "设为默认: 创造模式"': - - text: 创造模式 内置 用于创建自定义 Agent preset:具备标准模式的全部能力,并提供运行时检查、持久化插件管理和 preset 创作指导。 + - 'button "设为新任务默认: 创造模式"': + - text: 创造模式 内置 - code: cordis - - 'button "查看: 创造模式"': 查看 - - 'button "复制: 创造模式"': 复制 + - text: 用对话定制 DSH:让 Agent 编写插件,添加新功能或界面;也能组合工具和提示词,创建自己的模式。 + - 'button "模式说明: 创造模式"': 模式说明 + - 'button "如何使用: 创造模式"': 如何使用 + - 'button "查看配置: 创造模式"': 查看配置 + - 'button "复制预设: 创造模式"': 复制预设 - heading "自定义" [level=3] - list: - listitem: - - 'button "设为默认: 我的模式"': - - text: 我的模式 自定义 仅提供持久 shell 的单工具编码 Agent。 + - 'button "设为新任务默认: 我的模式"': + - text: 我的模式 自定义 - code: my-agent + - text: 仅提供持久 shell 的单工具编码 Agent。 - 'button "查看路径: 我的模式"': 查看路径 - - 'button "复制: 我的模式"': 复制 + - 'button "复制预设: 我的模式"': 复制预设 - 'button "删除: 我的模式"': 删除 - paragraph: - text: 预设文件: - code: {{presetRoot}}/my-agent - - button "用「创造模式」创作自定义预设" + - button "让 Agent 帮我创建预设模式" diff --git a/apps/web/tests/expected/agent-preset-authoring/damaged.expected.md b/apps/web/tests/expected/agent-preset-authoring/damaged.expected.md index 2d4db679fb..3a032b92f1 100644 --- a/apps/web/tests/expected/agent-preset-authoring/damaged.expected.md +++ b/apps/web/tests/expected/agent-preset-authoring/damaged.expected.md @@ -8,52 +8,66 @@ - button "打开配置文件" - button "关闭" - heading "Agent 预设" [level=2] - - paragraph: 预设即一个会话的 Agent 所运行的插件组装 —— 它的工具、提示词与能力。复制一份既有预设改成自己的,或用「创造模式」让 Agent 帮你创建。 - - text: 允许切换agent模式 beta - - paragraph: 开启后,新任务可选择标准、PTC、创造、极简及自定义模式;关闭后统一使用默认模式(默认为标准模式,可自定义)。仅影响新任务。 - - switch "允许切换agent模式" [checked] + - paragraph: 选择 Agent 的工具和工作方式。日常任务用「标准模式」,扩展 DSH 的能力用「创造模式」。 + - text: 新任务可选择模式 Beta + - paragraph: 开启后,可为每个新任务选择模式,并在这里设置默认值。关闭后,新任务使用应用配置的默认预设。已有任务不受影响。 + - switch "新任务可选择模式" [checked] - heading "内置" [level=3] - list: - listitem: - 'button "新任务默认: 标准模式" [disabled] [pressed]': - - text: 标准模式 内置 新任务默认 功能完整的编码 Agent,支持文件编辑、Shell、文件与网页检索、Skills、计划、目标、子代理和工作流。 + - text: 标准模式 新任务默认 - code: standard - - 'button "查看: 标准模式"': 查看 - - 'button "复制: 标准模式"': 复制 + - text: 处理代码、文件和资料,适合大多数任务。Agent 会按需使用检索、编辑和终端等工具。 + - 'button "模式说明: 标准模式"': 模式说明 + - 'button "如何使用: 标准模式"': 如何使用 + - 'button "查看配置: 标准模式"': 查看配置 + - 'button "复制预设: 标准模式"': 复制预设 - listitem: - - 'button "设为默认: PTC 模式"': - - text: PTC 模式 内置 功能完整的编码 Agent,但默认不提供 workflow 工具;其他工具通过 PTC 模式 SDK 呈现,让模型用一个 TypeScript 程序组合多步操作。 + - 'button "设为新任务默认: PTC 模式"': + - text: PTC 模式 内置 - code: ptc - - 'button "查看: PTC 模式"': 查看 - - 'button "复制: PTC 模式"': 复制 + - text: 包含标准模式的所有能力,让 Agent 用代码串联多个工具,完成批量处理、筛选和汇总。你只需描述任务。 + - 'button "模式说明: PTC 模式"': 模式说明 + - 'button "如何使用: PTC 模式"': 如何使用 + - 'button "查看配置: PTC 模式"': 查看配置 + - 'button "复制预设: PTC 模式"': 复制预设 - listitem: - - 'button "设为默认: 极简模式"': - - text: 极简模式 内置 仅提供持久 shell 的单工具编码 Agent。 + - 'button "设为新任务默认: 极简模式"': + - text: 极简模式 内置 - code: minimal - - 'button "查看: 极简模式"': 查看 - - 'button "复制: 极简模式"': 复制 + - text: Agent 仅使用终端工具完成任务,适合测试和对比其基础表现。 + - 'button "模式说明: 极简模式"': 模式说明 + - 'button "如何使用: 极简模式"': 如何使用 + - 'button "查看配置: 极简模式"': 查看配置 + - 'button "复制预设: 极简模式"': 复制预设 - listitem: - - 'button "设为默认: 创造模式"': - - text: 创造模式 内置 用于创建自定义 Agent preset:具备标准模式的全部能力,并提供运行时检查、持久化插件管理和 preset 创作指导。 + - 'button "设为新任务默认: 创造模式"': + - text: 创造模式 内置 - code: cordis - - 'button "查看: 创造模式"': 查看 - - 'button "复制: 创造模式"': 复制 + - text: 用对话定制 DSH:让 Agent 编写插件,添加新功能或界面;也能组合工具和提示词,创建自己的模式。 + - 'button "模式说明: 创造模式"': 模式说明 + - 'button "如何使用: 创造模式"': 如何使用 + - 'button "查看配置: 创造模式"': 查看配置 + - 'button "复制预设: 创造模式"': 复制预设 - heading "自定义" [level=3] - list: - listitem: - 'button "加载失败: broken-yaml" [disabled]': - - text: broken-yaml 加载失败 自定义 暂无描述。 - - alert: "the composition is not valid YAML: unexpected end of the stream within a flow collection (3:1)" + - text: broken-yaml 加载失败 自定义 - code: broken-yaml + - text: 暂无描述。 + - alert: "the composition is not valid YAML: unexpected end of the stream within a flow collection (3:1)" - 'button "查看路径: broken-yaml"': 查看路径 - - 'button "复制: broken-yaml" [disabled]': 预设加载失败,不能复制 + - 'button "复制预设: broken-yaml" [disabled]': 预设加载失败,不能复制 - 'button "删除: broken-yaml"': 删除 - listitem: - 'button "加载失败: 幽灵预设" [disabled]': - - text: 幽灵预设 加载失败 自定义 composition 已被手动删除。 - - alert: the composition file agent.cordis.yml is missing — the directory still occupies the id; delete it or restore the file + - text: 幽灵预设 加载失败 自定义 - code: ghost + - text: composition 已被手动删除。 + - alert: the composition file agent.cordis.yml is missing — the directory still occupies the id; delete it or restore the file - 'button "查看路径: 幽灵预设"': 查看路径 - - 'button "复制: 幽灵预设" [disabled]': 预设加载失败,不能复制 + - 'button "复制预设: 幽灵预设" [disabled]': 预设加载失败,不能复制 - 'button "删除: 幽灵预设"': 删除 - - button "用「创造模式」创作自定义预设" + - button "让 Agent 帮我创建预设模式" diff --git a/apps/web/tests/expected/agent-preset-authoring/section.expected.md b/apps/web/tests/expected/agent-preset-authoring/section.expected.md index 077c88b625..20ee272950 100644 --- a/apps/web/tests/expected/agent-preset-authoring/section.expected.md +++ b/apps/web/tests/expected/agent-preset-authoring/section.expected.md @@ -8,35 +8,47 @@ - button "打开配置文件" - button "关闭" - heading "Agent 预设" [level=2] - - paragraph: 预设即一个会话的 Agent 所运行的插件组装 —— 它的工具、提示词与能力。复制一份既有预设改成自己的,或用「创造模式」让 Agent 帮你创建。 - - text: 允许切换agent模式 beta - - paragraph: 开启后,新任务可选择标准、PTC、创造、极简及自定义模式;关闭后统一使用默认模式(默认为标准模式,可自定义)。仅影响新任务。 - - switch "允许切换agent模式" [checked] + - paragraph: 选择 Agent 的工具和工作方式。日常任务用「标准模式」,扩展 DSH 的能力用「创造模式」。 + - text: 新任务可选择模式 Beta + - paragraph: 开启后,可为每个新任务选择模式,并在这里设置默认值。关闭后,新任务使用应用配置的默认预设。已有任务不受影响。 + - switch "新任务可选择模式" [checked] - heading "内置" [level=3] - list: - listitem: - 'button "新任务默认: 标准模式" [disabled] [pressed]': - - text: 标准模式 内置 新任务默认 功能完整的编码 Agent,支持文件编辑、Shell、文件与网页检索、Skills、计划、目标、子代理和工作流。 + - text: 标准模式 新任务默认 - code: standard - - 'button "查看: 标准模式"': 查看 - - 'button "复制: 标准模式"': 复制 + - text: 处理代码、文件和资料,适合大多数任务。Agent 会按需使用检索、编辑和终端等工具。 + - 'button "模式说明: 标准模式"': 模式说明 + - 'button "如何使用: 标准模式"': 如何使用 + - 'button "查看配置: 标准模式"': 查看配置 + - 'button "复制预设: 标准模式"': 复制预设 - listitem: - - 'button "设为默认: PTC 模式"': - - text: PTC 模式 内置 功能完整的编码 Agent,但默认不提供 workflow 工具;其他工具通过 PTC 模式 SDK 呈现,让模型用一个 TypeScript 程序组合多步操作。 + - 'button "设为新任务默认: PTC 模式"': + - text: PTC 模式 内置 - code: ptc - - 'button "查看: PTC 模式"': 查看 - - 'button "复制: PTC 模式"': 复制 + - text: 包含标准模式的所有能力,让 Agent 用代码串联多个工具,完成批量处理、筛选和汇总。你只需描述任务。 + - 'button "模式说明: PTC 模式"': 模式说明 + - 'button "如何使用: PTC 模式"': 如何使用 + - 'button "查看配置: PTC 模式"': 查看配置 + - 'button "复制预设: PTC 模式"': 复制预设 - listitem: - - 'button "设为默认: 极简模式"': - - text: 极简模式 内置 仅提供持久 shell 的单工具编码 Agent。 + - 'button "设为新任务默认: 极简模式"': + - text: 极简模式 内置 - code: minimal - - 'button "查看: 极简模式"': 查看 - - 'button "复制: 极简模式"': 复制 + - text: Agent 仅使用终端工具完成任务,适合测试和对比其基础表现。 + - 'button "模式说明: 极简模式"': 模式说明 + - 'button "如何使用: 极简模式"': 如何使用 + - 'button "查看配置: 极简模式"': 查看配置 + - 'button "复制预设: 极简模式"': 复制预设 - listitem: - - 'button "设为默认: 创造模式"': - - text: 创造模式 内置 用于创建自定义 Agent preset:具备标准模式的全部能力,并提供运行时检查、持久化插件管理和 preset 创作指导。 + - 'button "设为新任务默认: 创造模式"': + - text: 创造模式 内置 - code: cordis - - 'button "查看: 创造模式"': 查看 - - 'button "复制: 创造模式"': 复制 + - text: 用对话定制 DSH:让 Agent 编写插件,添加新功能或界面;也能组合工具和提示词,创建自己的模式。 + - 'button "模式说明: 创造模式"': 模式说明 + - 'button "如何使用: 创造模式"': 如何使用 + - 'button "查看配置: 创造模式"': 查看配置 + - 'button "复制预设: 创造模式"': 复制预设 - heading "自定义" [level=3] - - button "用「创造模式」创作自定义预设" + - button "让 Agent 帮我创建预设模式" diff --git a/apps/web/tests/expected/agent-preset-selection/menu.expected.md b/apps/web/tests/expected/agent-preset-selection/menu.expected.md index d6dfa09aad..a765eb87ac 100644 --- a/apps/web/tests/expected/agent-preset-selection/menu.expected.md +++ b/apps/web/tests/expected/agent-preset-selection/menu.expected.md @@ -1,6 +1,6 @@ - menu: - - menuitem "Standard mode Full coding agent with file editing, shell, file and web search, skills, planning, goals, subagents, and workflows." - - menuitem "PTC mode Full coding agent without the workflow tool; other tools are exposed through the PTC mode SDK so the model can combine multi-step operations in one TypeScript program." - - menuitem "Minimal mode Single-tool coding agent with a persistent shell." - - menuitem "Creator mode Built for creating custom agent presets, with all Standard mode capabilities plus runtime inspection, persistent plugin management, and preset-authoring guidance." + - menuitem "Standard mode Work with code, files, and information. Suitable for most tasks, with search, editing, terminal commands, and other tools available as needed." + - menuitem "PTC mode Includes all Standard mode capabilities, with the agent connecting tools through code for batch processing, filtering, and summaries. Just describe your task." + - menuitem "Minimal mode The agent works using only a terminal tool. Useful for testing and comparing its basic performance." + - menuitem "Creator mode Customize DSH through conversation. Let the agent write plugins that add features or UI, or combine tools and prompts to create your own mode." - menuitem "Refusing mode Resolves, then refuses to start." diff --git a/packages/client/ui-agent-preset/README.i18n.yaml b/packages/client/ui-agent-preset/README.i18n.yaml index 3b67b4ca8a..f4b6a9b41d 100644 --- a/packages/client/ui-agent-preset/README.i18n.yaml +++ b/packages/client/ui-agent-preset/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-agent-preset/README.md -README.md: 5f2c9acaa5840e71d6ca237c5fc5eccb52724cda -README.zh.md: 5c61823c25f145c0e516f3f2a16c2de05c250ef1 +README.md: e1fb0bff3c35255c610347a8d50c7ff642cad918 +README.zh.md: 9b462d42f315c8dc9b78949de28df43cc0e5bad0 diff --git a/packages/client/ui-agent-preset/README.md b/packages/client/ui-agent-preset/README.md index 5f2c9acaa5..e1fb0bff3c 100644 --- a/packages/client/ui-agent-preset/README.md +++ b/packages/client/ui-agent-preset/README.md @@ -33,6 +33,8 @@ Mount this plugin alongside the settings and conversation packages; once Develop The settings section shows the roster as cards: a copy dialog is the only way a preset is created — the browser edits no composition text — and every custom card keeps a location action that opens the preset's own files. The visibility switch changes only whether the saved user default is active: the Host uses the deployment default while hidden and restores the saved default when the picker is shown again. While the picker is enabled, choosing a healthy non-default card writes a new user default for later sessions; if the current new-task surface already reuses a blank session, that explicit Settings action carries the same preset to that exact blank session through the existing selection path. Started and historical sessions remain unchanged. The switch is disabled while saving, and a failed write keeps the prior preference and shows an error. Hiding the picker disables default selection and the Creator launch but leaves roster viewing, copying, location, and deletion available. Deleting removes the preset directory while sessions already composed from it keep running. A shipped preset opens in a read-only viewer and offers no location or delete. A roster row carrying `broken` renders as a marked card whose body and duplication are disabled, because a copy of a broken preset is another broken preset; broken custom rows keep their location and delete actions so the files can be fixed and ghost directories cleared. The card face still shows the preset's own description — a chooser cannot act on a package specifier there — and the host's reason rides the badge as a tooltip, plus a visually hidden alert that carries it to assistive technology, which a disabled card body cannot. +Known shipped presets offer mode details and usage examples in a read-only dialog. Its tabs preserve each page's scroll position; closing returns focus to the opening action. Help does not change the new-task default. The default badge replaces the card's source badge, and the preset id appears beside the title. Guide copy and examples belong to this package; the tab control comes from `ui-primitives`. + ### The conversational entry When the roster carries the self-referential `cordis` preset, its dashed add-card stays disabled until the picker is enabled. It then stages `cordis` and starts a new session — the section closes the settings panel and the new-session chip's own applier composes the blank session the workspace flow produces. diff --git a/packages/client/ui-agent-preset/README.zh.md b/packages/client/ui-agent-preset/README.zh.md index 5c61823c25..9b462d42f3 100644 --- a/packages/client/ui-agent-preset/README.zh.md +++ b/packages/client/ui-agent-preset/README.zh.md @@ -33,6 +33,8 @@ kind: "package-reference" 设置分区把名单呈现为卡片:复制对话框是创建 preset 的唯一入口——浏览器不编辑任何组装文本——每张自定义卡片都保留一个打开 preset 自身文件的位置动作。可见性开关只决定已保存的用户默认值是否生效:宿主在隐藏期间使用部署默认值,再次显示选择器时恢复已保存的默认值。选择器开启期间,选择健康且非默认的卡片会为后续会话写入新的用户默认值;如果当前新任务页已经复用一个空白会话,这次在设置中的明确选择也会通过既有选择链路把同一 preset 带到这个精确的空白会话。已开始及历史会话保持不变。保存期间开关会被禁用;写入失败时,界面保留先前的偏好并显示错误。隐藏选择器会禁用默认值选择与 Creator 启动,但名单查看、复制、位置和删除仍然可用。删除会移除 preset 目录,而已据其组装的会话继续运行。随附 preset 在只读查看器中打开,不提供位置或删除。名单行携带 `broken` 时渲染为标记卡片,其主体与复制均被禁用,因为损坏 preset 的副本只是另一个损坏 preset;损坏的自定义行保留位置与删除动作,以便修复文件、清掉幽灵目录。卡片正面仍显示 preset 自己的描述——在选择器里,一个包说明符不足以让人采取行动——宿主给出的原因作为工具提示附在徽标上,另有一个视觉隐藏的 alert 将该原因传达给辅助技术,而被禁用的卡片主体无法做到这一点。 +已知的内置预设提供模式说明与使用示例,通过只读弹窗展示。标签切换保留各页的滚动位置,关闭后焦点回到打开入口。查看帮助不会改变新任务默认模式。默认标签替换卡片的来源标签,预设标识显示在标题旁。说明文案和案例由本包维护,标签切换控件来自 `ui-primitives`。 + ### 对话式入口 名单携带自指的 `cordis` preset 时,其虚线添加卡在选择器开启前保持禁用。开启后,它会暂存 `cordis` 并启动新会话——分区关闭设置面板,新建会话 chip 自己的应用器负责组装工作区流程产出的空白会话。 diff --git a/packages/client/ui-agent-preset/src/client/AgentPresetSection.module.css b/packages/client/ui-agent-preset/src/client/AgentPresetSection.module.css index 91111b0682..76e6b536e3 100644 --- a/packages/client/ui-agent-preset/src/client/AgentPresetSection.module.css +++ b/packages/client/ui-agent-preset/src/client/AgentPresetSection.module.css @@ -84,9 +84,7 @@ padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(268px, 1fr)); - /* Every row the same height, so a short description does not make its card - shorter than the one beside it. */ - grid-auto-rows: 1fr; + /* Match neighboring cards within a row, without stretching shorter rows. */ gap: 12px; } @@ -214,7 +212,7 @@ cursor: pointer; display: flex; flex-direction: column; - gap: 8px; + gap: 12px; padding: 14px 16px 12px; border-radius: 12px 12px 0 0; } @@ -230,38 +228,45 @@ .cardHead { display: flex; - align-items: center; - gap: 8px; + align-items: flex-start; + gap: 12px; /* Anchors the broken badge's tooltip: the badge itself stays unpositioned so - its `::after` resolves against the card's own width instead of against a + the tooltip resolves against the card's own width instead of against a badge that sits partway across it. A tooltip grown from the badge would run past the card, and past the section for a card in the last column. */ position: relative; } +.cardIdentity { + flex: 1; + min-width: 0; + display: flex; + align-items: center; + gap: 6px; +} + .cardName { + min-width: 0; font-size: 15px; font-weight: 600; line-height: 1.4; -} - -/* Placement only: the capsule geometry and palette come from `Tag`. */ -.inUse { - margin-left: auto; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } /* Bounded to four lines. A preset publishes its own description, so one long - one would otherwise stretch every card in its grid row (`.cards` sizes rows - 1fr). Clamping is CSS alone: the whole text stays in the DOM for assistive + one would otherwise stretch every card in its grid row. Clamping is CSS + alone: the whole text stays in the DOM for assistive tech, and the card offers it on hover when it is actually cut off. The description does not grow to fill the card — `-webkit-line-clamp` on a flex-stretched box leaves the clamp height and the box height disagreeing, - so `.cardId` takes the free space with an auto margin instead. */ + so auto margins center the text between the header and footer instead. */ .cardDesc { + margin-block: auto; font-size: 13px; line-height: 1.55; color: var(--dsw-alias-label-secondary); - min-height: 42px; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; @@ -271,20 +276,47 @@ } .cardId { - margin-top: auto; + flex-shrink: 0; + max-width: 35%; font-family: var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 11px; + line-height: 21px; color: var(--dsw-alias-label-tertiary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .cardFoot { display: flex; + align-items: center; + flex-wrap: wrap; justify-content: flex-end; gap: 2px; padding: 6px 10px; border-top: 0.5px solid var(--dsw-alias-border-l2); } +.cardHelp { + display: flex; + align-items: center; + gap: 4px; + margin-right: auto; +} + +.helpButton { + padding: 5px 6px; + min-height: 28px; + font-size: 12px; + font-weight: 400; + color: var(--dsw-alias-label-tertiary); +} + +.helpButton:hover, +.helpButton:focus-visible { + color: var(--dsw-alias-label-primary); +} + /* Icon-only actions: the label rides `title` so the row stays quiet until someone reaches for it. */ .iconButton { diff --git a/packages/client/ui-agent-preset/src/client/AgentPresetSection.tsx b/packages/client/ui-agent-preset/src/client/AgentPresetSection.tsx index d05fa305bf..cc3e9157ff 100644 --- a/packages/client/ui-agent-preset/src/client/AgentPresetSection.tsx +++ b/packages/client/ui-agent-preset/src/client/AgentPresetSection.tsx @@ -20,6 +20,7 @@ import type { ObservableSnapshot, SnapshotStore } from '@deepseek-ai/dsh-client- import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots' import { draftBlocker, type AgentPresetSectionState } from './section-store.ts' import { presetDisplayText, type AgentPresetSettingsKey } from './locales.ts' +import { PresetGuideDialog, presetGuide, type PresetGuidePage } from './PresetGuideDialog.tsx' import css from './AgentPresetSection.module.css' /** Registration-side business face for the management section. */ @@ -184,6 +185,10 @@ export function AgentPresetSection(props: AgentPresetSectionProps): ReactNode { const { useAgentPresetSection, t, load } = props const state = useAgentPresetSection(snapshot => snapshot) const developerTools = props.useDeveloperTools(enabled => enabled) + const [guide, setGuide] = useState<{ + content: NonNullable> + page: PresetGuidePage + } | null>(null) const viewedId = state.view?.id const viewedRow = viewedId === undefined ? undefined : state.rows.find(row => row.id === viewedId) const viewedTitle = state.view === null @@ -263,6 +268,7 @@ export function AgentPresetSection(props: AgentPresetSectionProps): ReactNode { .map(row => ({ row, text: presetDisplayText(row, t), + help: presetGuide(row.id, row.trust), selectionAction: row.broken !== undefined ? t('brokenBadge') : row.isDefault @@ -278,7 +284,7 @@ export function AgentPresetSection(props: AgentPresetSectionProps): ReactNode {

{heading}

{group.length === 0 ? null : (