diff --git a/apps/web/tests/snapshots/subagent-conversation/ui.expected.md b/apps/web/tests/snapshots/subagent-conversation/ui.expected.md
index 3a9b03fffe..14b397d9b4 100644
--- a/apps/web/tests/snapshots/subagent-conversation/ui.expected.md
+++ b/apps/web/tests/snapshots/subagent-conversation/ui.expected.md
@@ -3,8 +3,8 @@
- button "Ask a research subagent to"
- text: /
- button "event-sourcing researcher" [disabled]
- - button "1 subagents":
- - text: 1 subagents
+ - button "1 subagent":
+ - text: 1 subagent
- img
- tablist:
- tab "Chat" [selected]
diff --git a/apps/web/tests/subagent-conversation.e2e.ts b/apps/web/tests/subagent-conversation.e2e.ts
index 83ce78f3e4..904fcfcd09 100644
--- a/apps/web/tests/subagent-conversation.e2e.ts
+++ b/apps/web/tests/subagent-conversation.e2e.ts
@@ -331,7 +331,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () =
it('opens an unavailable persisted grandchild after recording the available child', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-subagent-grandchild'))
- await page.getByRole('button', { name: '1 subagents' }).click()
+ await page.getByRole('button', { name: '1 subagent' }).click()
await page.getByRole('treeitem', { name: new RegExp(NESTED_LABEL) }).click()
await page.getByText('The parent session is offline; reopen it to continue sending messages.').waitFor()
const hierarchy = page.getByRole('navigation', { name: 'Session hierarchy' })
diff --git a/packages/client/ui-subagent/src/client/SubagentCatalogAction.tsx b/packages/client/ui-subagent/src/client/SubagentCatalogAction.tsx
index 359827780f..14f8169d82 100644
--- a/packages/client/ui-subagent/src/client/SubagentCatalogAction.tsx
+++ b/packages/client/ui-subagent/src/client/SubagentCatalogAction.tsx
@@ -324,6 +324,8 @@ export function SubagentCatalogAction({
// The catalog can arrive before the session-list baseline; never undercount
// the already-visible direct rows during that short bootstrap window.
const descendantCount = Math.max(healthy.length, descendants.count)
+ const totalCountKey = descendantCount === 1 ? 'count.total.one' : 'count.total.other'
+ const runningCountKey = descendantCount === 1 ? 'count.running.one' : 'count.running.other'
const observeCatalog = (parentSessionId: SessionId, next: boolean): void => {
if (next) observedCatalogs.current.add(parentSessionId)
@@ -432,7 +434,7 @@ export function SubagentCatalogAction({
className={css.trigger}
aria-haspopup="tree"
aria-expanded={open}
- aria-label={t(descendants.running ? 'count.running' : 'count.total', { count: descendantCount })}
+ aria-label={t(descendants.running ? runningCountKey : totalCountKey, { count: descendantCount })}
onClick={() => { changeOpen(!open) }}
onKeyDown={(event) => {
if (event.key !== 'ArrowDown') return
@@ -444,7 +446,7 @@ export function SubagentCatalogAction({
{descendants.running && }
- {t('count.total', { count: descendantCount })}
+ {t(totalCountKey, { count: descendantCount })}
{open && (
diff --git a/packages/client/ui-subagent/src/client/locales.ts b/packages/client/ui-subagent/src/client/locales.ts
index 2ecf1be4f5..86562534b0 100644
--- a/packages/client/ui-subagent/src/client/locales.ts
+++ b/packages/client/ui-subagent/src/client/locales.ts
@@ -24,8 +24,10 @@ export const zh = {
'activity.inactive': '当前未运行',
'branch.collapse': '收起 {label} 的下级子代理',
'branch.expand': '展开 {label} 的下级子代理',
- 'count.total': '{count} 个子代理',
- 'count.running': '{count} 个子代理,正在运行',
+ 'count.total.one': '{count} 个子代理',
+ 'count.total.other': '{count} 个子代理',
+ 'count.running.one': '{count} 个子代理,正在运行',
+ 'count.running.other': '{count} 个子代理,正在运行',
'tree.aria': '子代理会话',
'readonly.oneShot.title': '一次性子代理记录',
'readonly.title': '此子代理暂时只读',
@@ -54,8 +56,10 @@ export const en: Record = {
'activity.inactive': 'not running',
'branch.collapse': 'Collapse {label} descendants',
'branch.expand': 'Expand {label} descendants',
- 'count.total': '{count} subagents',
- 'count.running': '{count} subagents running',
+ 'count.total.one': '{count} subagent',
+ 'count.total.other': '{count} subagents',
+ 'count.running.one': '{count} subagent running',
+ 'count.running.other': '{count} subagents running',
'tree.aria': 'Subagent sessions',
'readonly.oneShot.title': 'One-shot subagent record',
'readonly.title': 'This subagent is read-only for now',
diff --git a/packages/client/ui-subagent/tests/conversation-ui.spec.tsx b/packages/client/ui-subagent/tests/conversation-ui.spec.tsx
index 1687b1ffc1..5649b2b257 100644
--- a/packages/client/ui-subagent/tests/conversation-ui.spec.tsx
+++ b/packages/client/ui-subagent/tests/conversation-ui.spec.tsx
@@ -167,6 +167,24 @@ describe('SubagentCatalogAction', () => {
expect(input.setCatalogOpen).toHaveBeenLastCalledWith(PARENT, false)
})
+ it('selects singular count keys for one descendant', () => {
+ const base = props(catalog({
+ entries: [{
+ kind: 'child', id: CHILD, mode: 'continuable', label: 'worker',
+ activity: 'running', hasChildren: false,
+ }],
+ }), {}, {
+ [CHILD]: {
+ ...summary(CHILD, Date.now()), parentId: PARENT, origin: 'subagent', running: true,
+ },
+ })
+ const translate = vi.fn(base.t)
+ render()
+
+ expect(translate).toHaveBeenCalledWith('count.running.one', { count: 1 })
+ expect(translate).toHaveBeenCalledWith('count.total.one', { count: 1 })
+ })
+
it('supports trigger/menu keyboard traversal, Escape focus restore, and outside close', async () => {
const input = props(catalog())
render()