feat(tasks): 空闲成员 ≥3 时自动收进聚合,聚合并如实报出状态配比

用户要求优化显示:「比如空闲时也折叠」。

在此之前,一个跑完一轮的 teammate(活体通道不报 running)映射成 idle,
而 idle 不在「已完成/出错」的可折叠集合里 —— 于是 8 人团队永远摆 8 张卡,
而同样「不在执行」的普通子代理却是 done、会折叠。同一事实两种显示,才是
这个痛点的根。

- 可折叠集合放宽到「不在执行」(done | idle | error);运行中/部署中永不折叠。
- 空闲**不无条件收**:按用户确认的阈值,一个父节点下空闲成员 ≥3 才随页面级
  折叠规则一起收(FOLD_IDLE_MIN)。1–2 个待命成员就是这个团队的工作集,
  而且它们的卡上会挂任务行,留着看;3 个以上才是「在扫名册」。
- 聚合行如实报账:聚合节点新增 doneCount/idleCount/errorCount/hasIdle,
  小条写 `✓ N 已完成 · N 待命 · 出错 N`(为零的项省略,全已完成时与原先一样短),
  徽标在**没有已完成项**时改说 `N 待命` —— 否则「✓ N 已完成」会把待命和出错
  一起算成已完成,是撒谎。图卡片与树行共用 foldTally/foldBadgeCount。
- 手动折叠(卡片 chevron)不设阈值:那是读者点名要收的节点。
- i18n:新增 tasksFoldIdle ×20 词典。

测试:新增 6 例(阈值命中收 3 个空闲成员、2 个保持可见、运行中成员即使达标也不收、
混合聚合按状态计数、页面聚合行报「3 待命」且徽标不说已完成);pnpm test
1284 passed / 9 skipped,typecheck / lint 通过。

(cherry picked from commit 39d66ffb2e93eeb4095ec1cb2ac15933cce62742)
This commit is contained in:
Menghuan1918
2026-09-27 22:19:05 +08:00
parent a7338bee1a
commit c0ff3d1afd
27 changed files with 242 additions and 34 deletions
+14 -4
View File
@@ -31,16 +31,24 @@ export type CardKind = 'main' | 'subagent' | 'teammate' | 'workflow' | 'fold'
/**
* The kind badge's word: the SAME vocabulary the rest of the page uses (the
* header already calls the root "主代理"), so no sixth word is invented for it.
*
* The aggregate's badge says 已完成 only when something WAS completed: an
* aggregate that sweeps idle teammates (or only failed rows) and still claimed
* "✓ N 已完成" would be lying about what is inside it, so `foldBadgeIdle`
* switches it to the idle word for exactly that case.
* @param kind - card kind.
* @param count - folded member count (the fold aggregate's badge only).
* @param foldBadgeIdle - the aggregate holds no completed row (see above).
*/
export function cardKindLabel(kind: CardKind, count = 0): string {
export function cardKindLabel(kind: CardKind, count = 0, foldBadgeIdle = false): string {
switch (kind) {
case 'main': return t('subagentMainAgent')
case 'subagent': return t('tasksKindSubagent')
case 'teammate': return t('tasksKindTeammate')
case 'workflow': return t('tasksKindWorkflow')
case 'fold': return t('tasksFoldCompleted', { count })
case 'fold': return foldBadgeIdle
? t('tasksFoldIdle', { count })
: t('tasksFoldCompleted', { count })
}
}
@@ -69,6 +77,8 @@ export function CardTop(props: {
kind: CardKind
/** Folded member count, for the aggregate's badge. */
count?: number
/** Whether the aggregate holds no completed row (its badge switches word). */
badgeIdle?: boolean
/** Tree depth → the group colour of the badge and the card's left edge. */
depth: number
/** Workflow phase (a member's badge). */
@@ -82,12 +92,12 @@ export function CardTop(props: {
/** Extra rows the caller appends (the team task line). */
children?: ReactNode
}): ReactNode {
const { kind, count, depth, phase } = props
const { kind, count, badgeIdle, depth, phase } = props
return (
<span className={clsx(css.cardTop, depthClass(depth))}>
<span className={css.cardBadges}>
<span className={css.kindBadge} data-card-kind={kind}>
{cardKindLabel(kind, count ?? 0)}
{cardKindLabel(kind, count ?? 0, badgeIdle === true)}
</span>
{phase !== undefined && (
<span