From e89d2b031fe23b8bfe1386bc49db7516b1995cc5 Mon Sep 17 00:00:00 2001 From: Dudu-0223 Date: Sat, 7 Nov 2026 01:03:59 +0800 Subject: [PATCH] fix(ui): show subagent descriptions above names without arrows (#7730) * fix(ui): show subagent descriptions above names without disclosure arrows * fix(ui): narrow subagent result cards * Revert "fix(ui): narrow subagent result cards" This reverts commit 23a5a353ac9daf0e322f28e19c9d2c2cfbb15acb. * test(web): sample agent row geometry in one browser task --- apps/web/tests/tool-details.e2e.ts | 10 ++++++ packages/client/locale/src/locales/de.ts | 2 -- packages/client/locale/src/locales/en.ts | 2 -- packages/client/locale/src/locales/es.ts | 2 -- packages/client/locale/src/locales/fr.ts | 2 -- packages/client/locale/src/locales/ja.ts | 2 -- packages/client/locale/src/locales/pt.ts | 2 -- packages/client/locale/src/locales/ru.ts | 2 -- packages/client/locale/src/locales/zh.ts | 2 -- packages/client/ui-tool/README.i18n.yaml | 4 +-- packages/client/ui-tool/README.md | 2 +- packages/client/ui-tool/README.zh.md | 2 +- .../tool/components/ToolDetails.module.css | 36 ------------------- .../client/tool/components/ToolDetails.tsx | 22 +++--------- .../tool/models/control-details-model.ts | 11 ++++-- .../tests/details-models.client.spec.ts | 10 +++--- .../ui-tool/tests/details-row.client.spec.tsx | 21 ++++------- snapshots/web/tool-details/ui.expected.md | 18 +++------- 18 files changed, 43 insertions(+), 109 deletions(-) diff --git a/apps/web/tests/tool-details.e2e.ts b/apps/web/tests/tool-details.e2e.ts index 8a51e66d44..93963c070e 100644 --- a/apps/web/tests/tool-details.e2e.ts +++ b/apps/web/tests/tool-details.e2e.ts @@ -62,6 +62,16 @@ describe.skipIf(MODE === 'record')('web e2e: compact Tool details', () => { expect(await row.locator('[class*="ioCard"]').count()).toBe(0) expect(await row.getByRole('button', { name: 'Inspect', exact: true }).count()).toBe(1) } + const agentRow = page.locator('[data-tool="list_agents"]').getByRole('listitem').first() + expect(await agentRow.getByRole('button').count()).toBe(0) + expect(await agentRow.locator('svg').count()).toBe(0) + // Read both positions in one browser task so scrolling cannot move the viewport between measurements. + const agentLayout = await agentRow.evaluate((element) => { + const description = element.querySelector('[class*="heading"] > span')!.getBoundingClientRect() + const name = element.querySelector('[class*="subtitle"]')!.getBoundingClientRect() + return { descriptionBottom: description.bottom, nameTop: name.top } + }) + expect(agentLayout.descriptionBottom).toBeLessThanOrEqual(agentLayout.nameTop) const changedTodos = page.locator('[data-tool="todo_write"]').nth(1) await changedTodos.getByText('1 added · 1 updated · 1 removed', { exact: true }).waitFor() for (const summary of await page.locator('[data-tool] details > summary').all()) await summary.click() diff --git a/packages/client/locale/src/locales/de.ts b/packages/client/locale/src/locales/de.ts index cb0bb45579..dd88cf6fc4 100644 --- a/packages/client/locale/src/locales/de.ts +++ b/packages/client/locale/src/locales/de.ts @@ -33,8 +33,6 @@ export const de = { 'more': 'Mehr', 'collapse': 'Einklappen', 'expand': 'Ausklappen', - 'subtitle.expand': 'Beschreibung für {name} ausklappen', - 'subtitle.collapse': 'Beschreibung für {name} einklappen', 'back': 'Zurück', 'brand.localBuild': 'Lokaler DSH-Build', 'workspace.defaultName': 'Standardarbeitsbereich', diff --git a/packages/client/locale/src/locales/en.ts b/packages/client/locale/src/locales/en.ts index 47a03d69ef..b649bbc3dc 100644 --- a/packages/client/locale/src/locales/en.ts +++ b/packages/client/locale/src/locales/en.ts @@ -32,8 +32,6 @@ export const en = { 'more': 'More', 'collapse': 'Collapse', 'expand': 'Expand', - 'subtitle.expand': 'Expand description for {name}', - 'subtitle.collapse': 'Collapse description for {name}', 'back': 'Back', 'brand.localBuild': 'DSH Local Build', 'workspace.defaultName': 'Default workspace', diff --git a/packages/client/locale/src/locales/es.ts b/packages/client/locale/src/locales/es.ts index 4fba984885..7eb95374e1 100644 --- a/packages/client/locale/src/locales/es.ts +++ b/packages/client/locale/src/locales/es.ts @@ -33,8 +33,6 @@ export const es = { 'more': 'Más', 'collapse': 'Contraer', 'expand': 'Expandir', - 'subtitle.expand': 'Expandir la descripción de {name}', - 'subtitle.collapse': 'Contraer la descripción de {name}', 'back': 'Atrás', 'brand.localBuild': 'Compilación local de DSH', 'workspace.defaultName': 'Espacio de trabajo predeterminado', diff --git a/packages/client/locale/src/locales/fr.ts b/packages/client/locale/src/locales/fr.ts index f0e5030f41..c57f1a3b11 100644 --- a/packages/client/locale/src/locales/fr.ts +++ b/packages/client/locale/src/locales/fr.ts @@ -33,8 +33,6 @@ export const fr = { 'more': 'Plus', 'collapse': 'Réduire', 'expand': 'Développer', - 'subtitle.expand': 'Développer la description de {name}', - 'subtitle.collapse': 'Réduire la description de {name}', 'back': 'Retour', 'brand.localBuild': 'Version locale de DSH', 'workspace.defaultName': 'Espace de travail par défaut', diff --git a/packages/client/locale/src/locales/ja.ts b/packages/client/locale/src/locales/ja.ts index 2b70cbb156..1186838c31 100644 --- a/packages/client/locale/src/locales/ja.ts +++ b/packages/client/locale/src/locales/ja.ts @@ -33,8 +33,6 @@ export const ja = { 'more': 'その他', 'collapse': '折りたたむ', 'expand': '展開', - 'subtitle.expand': '{name} の説明を展開', - 'subtitle.collapse': '{name} の説明を折りたたむ', 'back': '戻る', 'brand.localBuild': 'DSHローカルビルド', 'workspace.defaultName': 'デフォルトワークスペース', diff --git a/packages/client/locale/src/locales/pt.ts b/packages/client/locale/src/locales/pt.ts index adf711152d..75cdb19c29 100644 --- a/packages/client/locale/src/locales/pt.ts +++ b/packages/client/locale/src/locales/pt.ts @@ -33,8 +33,6 @@ export const pt = { 'more': 'Mais', 'collapse': 'Recolher', 'expand': 'Expandir', - 'subtitle.expand': 'Expandir descrição de {name}', - 'subtitle.collapse': 'Recolher descrição de {name}', 'back': 'Voltar', 'brand.localBuild': 'Versão local do DSH', 'workspace.defaultName': 'Espaço de trabalho padrão', diff --git a/packages/client/locale/src/locales/ru.ts b/packages/client/locale/src/locales/ru.ts index e75620fe21..a8a6d829da 100644 --- a/packages/client/locale/src/locales/ru.ts +++ b/packages/client/locale/src/locales/ru.ts @@ -33,8 +33,6 @@ export const ru = { 'more': 'Ещё', 'collapse': 'Свернуть', 'expand': 'Развернуть', - 'subtitle.expand': 'Развернуть описание {name}', - 'subtitle.collapse': 'Свернуть описание {name}', 'back': 'Назад', 'brand.localBuild': 'Локальная сборка DSH', 'workspace.defaultName': 'Рабочая область по умолчанию', diff --git a/packages/client/locale/src/locales/zh.ts b/packages/client/locale/src/locales/zh.ts index 6350caae3c..11ff789b8a 100644 --- a/packages/client/locale/src/locales/zh.ts +++ b/packages/client/locale/src/locales/zh.ts @@ -30,8 +30,6 @@ export const zh = { 'more': '更多', 'collapse': '收起', 'expand': '展开', - 'subtitle.expand': '展开{name}的描述', - 'subtitle.collapse': '收起{name}的描述', 'back': '返回', 'brand.localBuild': 'DSH 本地构建', 'workspace.defaultName': '默认工作区', diff --git a/packages/client/ui-tool/README.i18n.yaml b/packages/client/ui-tool/README.i18n.yaml index 4182c93cfe..59dfd47110 100644 --- a/packages/client/ui-tool/README.i18n.yaml +++ b/packages/client/ui-tool/README.i18n.yaml @@ -15,8 +15,8 @@ en: d152484eb41ac6b4 zh: 09388d293f9be9cb /deepseek-ai-dsh-client-ui-tool/use-this-package: - en: 09231e7cc6515a01 - zh: c306bb141cf9bf9d + en: 4d8cb38024095705 + zh: 1baf6fa42f3fb80f /deepseek-ai-dsh-client-ui-tool/use-this-package/registering-a-business-tool-view: en: e458aff61e634190 zh: 650e417f450b3edf diff --git a/packages/client/ui-tool/README.md b/packages/client/ui-tool/README.md index b460da0f8f..448a4e197b 100644 --- a/packages/client/ui-tool/README.md +++ b/packages/client/ui-tool/README.md @@ -25,7 +25,7 @@ English | [中文](README.zh.md) ## Use this package -Tool calls appear in the conversation as cards: a root call tree with its nested subcalls, each atomic call rendered by its owning view. Every lifecycle state retains the tool's ordinary business glyph; failure and interruption remain explicit through the frozen call/result state, accessible status text, and failure summary. Users can open files or inspect calls through the Host callbacks. A collapsed `web_fetch` row links its http(s) URL, which opens in a new browser tab. Control receipts display the agent name supplied by the recorded call, or the agent ID in historical calls. Text agent lists show each agent name, or the historical ID, above a single-line description, with compact spacing and recorded status. Click the description to expand or collapse its full text; its accessible name follows the selected locale. +Tool calls appear in the conversation as cards: a root call tree with its nested subcalls, each atomic call rendered by its owning view. Every lifecycle state retains the tool's ordinary business glyph; failure and interruption remain explicit through the frozen call/result state, accessible status text, and failure summary. Users can open files or inspect calls through the Host callbacks. A collapsed `web_fetch` row links its http(s) URL, which opens in a new browser tab. Control receipts display the agent name supplied by the recorded call, or the agent ID in historical calls. Agent lists show the full description above the name, or the historical ID, with compact spacing and recorded status. Both lines wrap as needed and have no disclosure arrows; rows without a description show only the name. Settled foreground commands with a nonzero exit code, a terminating signal, or an explicit missing exit code keep their description in the ordinary gray text and show a neutral capsule with a red dot immediately after it. The capsule displays only the recorded code, signal, or localized missing-code status and stays on the same line; the description truncates with an ellipsis to reserve its space. Running, successful, and background calls omit it. Generic execution errors and stopped summaries retain their error and warning colors, including on hover. diff --git a/packages/client/ui-tool/README.zh.md b/packages/client/ui-tool/README.zh.md index 3b7811a823..37757c47a7 100644 --- a/packages/client/ui-tool/README.zh.md +++ b/packages/client/ui-tool/README.zh.md @@ -25,7 +25,7 @@ kind: "package-reference" ## 使用本包 -工具调用在对话中显示为卡片:一个根调用树带其嵌套子调用,每个原子调用由所属视图渲染。所有生命周期状态都保留工具的普通业务图标;失败与中断仍通过冻结调用/结果状态、无障碍状态文本和失败摘要明确表达。用户可通过宿主回调打开文件或检查调用。折叠的 `web_fetch` 行把其 http(s) URL 显示为链接,在新浏览器标签页中打开。控制工具回执显示记录的调用所提供的智能体名称,历史调用则显示智能体 ID。文本形式的智能体列表以紧凑间距展示智能体名称(历史记录为 ID)、下方的单行描述及记录中的状态。点击描述可展开或收起全文;其无障碍名称使用当前语言。 +工具调用在对话中显示为卡片:一个根调用树带其嵌套子调用,每个原子调用由所属视图渲染。所有生命周期状态都保留工具的普通业务图标;失败与中断仍通过冻结调用/结果状态、无障碍状态文本和失败摘要明确表达。用户可通过宿主回调打开文件或检查调用。折叠的 `web_fetch` 行把其 http(s) URL 显示为链接,在新浏览器标签页中打开。控制工具回执显示记录的调用所提供的智能体名称,历史调用则显示智能体 ID。智能体列表以紧凑间距在上方展示完整描述,下方展示名称(历史记录为 ID),并保留记录中的状态。两行均按需换行,不带展开箭头;没有描述时只显示名称。 已结束的前台命令若返回非零退出码、终止信号,或明确记录为无退出码,描述保持普通灰色,后面紧接带红点的中性胶囊。胶囊仅显示记录的退出码、信号或本地化的“未正常退出”状态,并保持在同一行;描述超出可用宽度时显示省略号,为胶囊预留空间。运行中、成功和后台调用不显示胶囊。通用执行错误与停止摘要仍保留错误色和警告色,悬停时也不改变。 diff --git a/packages/client/ui-tool/src/client/tool/components/ToolDetails.module.css b/packages/client/ui-tool/src/client/tool/components/ToolDetails.module.css index 0180f9fa0d..44d9f2ef8d 100644 --- a/packages/client/ui-tool/src/client/tool/components/ToolDetails.module.css +++ b/packages/client/ui-tool/src/client/tool/components/ToolDetails.module.css @@ -188,42 +188,6 @@ padding: 6px 10px 4px; } -.subtitleToggle { - display: flex; - align-items: flex-start; - gap: 4px; - width: 100%; - padding: 0; - border: 0; - background: transparent; - color: var(--dsw-alias-label-tertiary); - font: inherit; - font-size: 12px; - text-align: left; - cursor: pointer; -} - -.subtitleToggle > span { - min-width: 0; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.subtitleToggle > svg { - flex: none; - margin-top: 3px; -} - -.subtitleToggle[aria-expanded='true'] > span { - white-space: pre-wrap; - overflow-wrap: anywhere; -} - -.subtitleToggle[aria-expanded='true'] > svg { - transform: rotate(90deg); -} - .description { margin: 6px 0; color: var(--dsw-alias-label-caption); diff --git a/packages/client/ui-tool/src/client/tool/components/ToolDetails.tsx b/packages/client/ui-tool/src/client/tool/components/ToolDetails.tsx index 84ee30b93b..65e4db7c84 100644 --- a/packages/client/ui-tool/src/client/tool/components/ToolDetails.tsx +++ b/packages/client/ui-tool/src/client/tool/components/ToolDetails.tsx @@ -1,5 +1,4 @@ /** Compact, read-only fields and lists for recorded Tool results. */ -import { useState } from 'react' import { CodeBlock, MarkdownText, IconCheckOutlineRegular, IconChevronRightOutlineRegular, IconPlayOutlineRegular, } from '@deepseek-ai/dsh-client-ui-primitives' @@ -28,7 +27,7 @@ export interface ToolDetailItem { /** Localized display data shared by the built-in detail cards. */ export interface ToolDetailsModel { items: readonly ToolDetailItem[] - /** Tight rows with single-line subtitles that can expand in place. */ + /** Tight spacing for recorded entity rows. */ compact?: boolean summary?: string /** Header text while expanded, omitting a receipt status shown in the body. */ @@ -43,9 +42,8 @@ interface DetailContentProps { onOpenFile?: ((path: string, options?: OpenFileOptions) => void) | undefined } -function DetailItem({ item, compact, t, onOpenFile }: { item: ToolDetailItem; compact?: boolean | undefined } & DetailContentProps) { +function DetailItem({ item, t, onOpenFile }: { item: ToolDetailItem } & DetailContentProps) { const status = item.status - const [expanded, setExpanded] = useState(false) return (
  • {item.title !== undefined && ( @@ -77,19 +75,7 @@ function DetailItem({ item, compact, t, onOpenFile }: { item: ToolDetailItem; co )} )} - {item.subtitle !== undefined && (compact ? ( - - ) :
    {item.subtitle}
    )} + {item.subtitle !== undefined &&
    {item.subtitle}
    } {item.description !== undefined &&

    {item.description}

    } {item.fields.length > 0 && (
    @@ -144,7 +130,7 @@ export function ToolDetails({ model, hasInspect = false, t, onOpenFile }: { {model.caption !== undefined &&
    {model.caption}
    } {model.items.length === 0 ?

    {model.empty}

    : (
      - {model.items.map((item, index) => )} + {model.items.map((item, index) => )}
    )} {model.unchanged !== undefined && ( diff --git a/packages/client/ui-tool/src/client/tool/models/control-details-model.ts b/packages/client/ui-tool/src/client/tool/models/control-details-model.ts index ef6fb9a8a5..c7f73005b0 100644 --- a/packages/client/ui-tool/src/client/tool/models/control-details-model.ts +++ b/packages/client/ui-tool/src/client/tool/models/control-details-model.ts @@ -29,7 +29,14 @@ function teamTaskItems(value: unknown, t: DetailTranslate): DetailItem[] { } function agentList(text: string, json: unknown, t: DetailTranslate): ToolDetailsModel | null { - if (Array.isArray(json)) return detailList(inspectionItems(json, t), t('detail.agents.count', { count: json.length }), t) + if (Array.isArray(json)) { + const items = inspectionItems(json, t).map((item) => { + if (item.title === undefined || item.description === undefined) return item + const { title, description, ...rest } = item + return { ...rest, title: description, subtitle: title } + }) + return { ...detailList(items, t('detail.agents.count', { count: json.length }), t), compact: true } + } if (text === '(no subagents)') return detailList([], t('detail.agents.count', { count: 0 }), t) const items: DetailItem[] = [] for (const line of text.split('\n')) { @@ -39,7 +46,7 @@ function agentList(text: string, json: unknown, t: DetailTranslate): ToolDetails const [, agent, state, title] = match if (agent === undefined || state === undefined) return null items.push({ - title: agent, ...(title === undefined ? {} : { subtitle: title }), + title: title || agent, ...(title ? { subtitle: agent } : {}), badge: detailBadge(state, t), fields: [], }) diff --git a/packages/client/ui-tool/tests/details-models.client.spec.ts b/packages/client/ui-tool/tests/details-models.client.spec.ts index 7147aff0f7..bbf9f5f2e8 100644 --- a/packages/client/ui-tool/tests/details-models.client.spec.ts +++ b/packages/client/ui-tool/tests/details-models.client.spec.ts @@ -93,20 +93,20 @@ describe('control detail adapters', () => { const agents = details('list_agents', 'lead [running] — root agent\nreviewer/checker [inactive] — Check tests\nold-id [unavailable]') expect(agents?.summary).toBe('3 agents') expect(agents?.items).toEqual([ - { title: 'lead', subtitle: 'root agent', badge: { label: 'Running', tone: 'info' }, fields: [] }, - { title: 'reviewer/checker', subtitle: 'Check tests', badge: { label: 'Inactive', tone: 'warning' }, fields: [] }, + { title: 'root agent', subtitle: 'lead', badge: { label: 'Running', tone: 'info' }, fields: [] }, + { title: 'Check tests', subtitle: 'reviewer/checker', badge: { label: 'Inactive', tone: 'warning' }, fields: [] }, { title: 'old-id', badge: { label: 'Unavailable', tone: 'neutral' }, fields: [] }, ]) const historical = details('list_agents', 'a1 [running] — Build\na2 [ready] parent=a1 depth=1 — Review\na3 [diagnostic: corrupt] parent=a1 depth=1', { scope: 'descendants' }) expect(historical?.summary).toBe('3 agents') expect(historical?.items).toEqual([ - { title: 'a1', subtitle: 'Build', badge: { label: 'Running', tone: 'info' }, fields: [] }, - { title: 'a2', subtitle: 'Review', badge: { label: 'Ready', tone: 'neutral' }, fields: [] }, + { title: 'Build', subtitle: 'a1', badge: { label: 'Running', tone: 'info' }, fields: [] }, + { title: 'Review', subtitle: 'a2', badge: { label: 'Ready', tone: 'neutral' }, fields: [] }, { title: 'a3', badge: { label: 'diagnostic: corrupt', tone: 'neutral' }, fields: [] }, ]) const structured = details('list_agents', JSON.stringify([{ name: 'lead', status: 'running', description: 'root agent' }, { name: 'reviewer', status: 'inactive' }])) expect(structured?.items).toEqual([ - { title: 'lead', description: 'root agent', badge: { label: 'Running', tone: 'info' }, fields: [] }, + { title: 'root agent', subtitle: 'lead', badge: { label: 'Running', tone: 'info' }, fields: [] }, { title: 'reviewer', badge: { label: 'Inactive', tone: 'warning' }, fields: [] }, ]) expect(details('list_agents', 'reviewer running')).toBeNull() diff --git a/packages/client/ui-tool/tests/details-row.client.spec.tsx b/packages/client/ui-tool/tests/details-row.client.spec.tsx index b5dde4a667..e21206109f 100644 --- a/packages/client/ui-tool/tests/details-row.client.spec.tsx +++ b/packages/client/ui-tool/tests/details-row.client.spec.tsx @@ -153,26 +153,17 @@ describe('todosDetail', () => { }) describe('DetailsRow', () => { - it('puts agent names first and lets a compact description expand and collapse', () => { + it('shows the full agent description above its name without a disclosure', () => { const description = 'Verify every construction bound. '.repeat(20) const block = { ...result('list_agents', null), content: [{ type: 'text' as const, text: `reviewer/checker [running] — ${description}` }] } const model = detailsCardModel(block, t, 'en')! render() const name = screen.getByText('reviewer/checker') - const toggle = screen.getByRole('button', { name: 'Expand description for reviewer/checker' }) - expect(name.compareDocumentPosition(toggle) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy() - expect(toggle.textContent).toBe(description) - fireEvent.click(toggle) - expect(screen.getByRole('button', { name: 'Collapse description for reviewer/checker' }).getAttribute('aria-expanded')).toBe('true') - fireEvent.click(toggle) - expect(toggle.getAttribute('aria-expanded')).toBe('false') - }) - - it('uses an unqualified accessible name when a compact description has no title', () => { - render() - const toggle = screen.getByRole('button', { name: 'Expand' }) - fireEvent.click(toggle) - expect(screen.getByRole('button', { name: 'Collapse' })).toBe(toggle) + const title = screen.getByText(description.trim()) + expect(title.compareDocumentPosition(name) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy() + expect(screen.getByRole('listitem').textContent).toBe(`${description}Runningreviewer/checker`) + expect(screen.queryByRole('button')).toBeNull() + expect(screen.getByRole('listitem').querySelector('svg')).toBeNull() }) it('expands fields with the keyboard and keeps Inspect available', () => { diff --git a/snapshots/web/tool-details/ui.expected.md b/snapshots/web/tool-details/ui.expected.md index a069e23631..e55e355f3f 100644 --- a/snapshots/web/tool-details/ui.expected.md +++ b/snapshots/web/tool-details/ui.expected.md @@ -477,15 +477,9 @@ - button "List subagents 3 agents" [expanded] - text: Recorded result - list: - - listitem: - - text: agent-demo-review Running - - button "Expand description for agent-demo-review": 检查工具卡片布局 - - listitem: - - text: agent-demo-docs Idle - - button "Expand description for agent-demo-docs": 整理工具展示清单 - - listitem: - - text: agent-demo-icons Ready - - button "Expand description for agent-demo-icons": 检查状态图标 + - listitem: 检查工具卡片布局 Running agent-demo-review + - listitem: 整理工具展示清单 Idle agent-demo-docs + - listitem: 检查状态图标 Ready agent-demo-icons - button "Inspect" - button "Send message agent-demo-review" [expanded] - text: Recorded result @@ -658,8 +652,7 @@ - term: Role - definition: lead - listitem: - - text: ui-review Running - - paragraph: 检查工具卡片的可读性 + - text: 检查工具卡片的可读性 Running ui-review - term: ID - definition: agent-demo-review - term: Role @@ -669,8 +662,7 @@ - term: Context - definition: fresh - listitem: - - text: docs Inactive - - paragraph: 整理工具清单 + - text: 整理工具清单 Inactive docs - term: ID - definition: agent-demo-docs - term: Role