refactor(tasks): 移植主 Agent 同款合并活动折叠(数据层)

把上一轮验证过的数据层搬到工作图基座上,图/树两种模式共用:

- 新增 src/process-activity.ts:宿主 ui-chat 的 process group 算法移植
  (activity() 分类表逐条照抄、liveToolDetail 键优先级与 160 字截断、
  counts 计数排序与并列规则、preparing 语义、按 user/reply 边界切范围)。
  按本轮设计裁剪掉逐阶段列表(阶段改由徽标 + 颜色表达)。新增一条
  偏差:最新范围为空(子代理刚回复完)时回退到上一个「做过事」的范围,
  否则已结算卡片的小条只会显示「已完成分析」。
- chatT:从宿主 chat 命名空间读 message.stepProcess.* 措辞(方法调用 +
  try/catch 兜底,key 未命中即回退),4 个回归用例钉住「不许解绑 bind」。
- subagents.live 载荷换新:SidebarChildLiveView { running?, text?, summary?,
  lastEventTime? },报告树内每个子节点(含已结算)与拓扑根;删除
  expand/stages 机制;childActivity → childLive(「不在 map 里 = 未运行」
  的读法随旧协议一起作废)。
- tasks-model 消费新形状;LiveLine 与节点详情浮窗改显示合并活动措辞。
- 删除 src/subagent-activity.ts 与 tests/subagent-activity.spec.ts。

验证:typecheck / lint 通过;pnpm test 1252 passed / 9 skipped。

(cherry picked from commit 5e5284f214846b0c366048acbee459f3a7a79456)
This commit is contained in:
Menghuan1918
2026-09-27 22:18:35 +08:00
parent 2a39a0c8b2
commit a454cfc9fe
21 changed files with 1198 additions and 338 deletions
+10 -9
View File
@@ -26,10 +26,10 @@ import type {
SidebarTeamMemberView,
SidebarTeamTaskView,
} from '../context-types.ts'
import type { LastActivity } from '../subagent-activity.ts'
import type { SidebarChildLiveView } from '../context-types.ts'
import type { WorkflowRunView } from '../workflow-runs.ts'
import { isSideThreadSummary } from './subagent-detect.ts'
import { childActivity, isKnownLeaf, type SubagentCatalogView } from './subagent-catalog.ts'
import { childLive, isKnownLeaf, type SubagentCatalogView } from './subagent-catalog.ts'
/** Display state of one agent node (drives the dot + fold candidacy). */
export type TasksNodeState = 'running' | 'idle' | 'done' | 'error'
@@ -59,8 +59,8 @@ export interface TasksAgentNode {
activity: 'running' | 'inactive'
/** The on-screen session (the "you are here" marker). */
current: boolean
/** Live tail of a running child (the icon + tool + args line). */
live?: LastActivity
/** Live tail of a running child (the merged activity + newest text). */
live?: SidebarChildLiveView
/** Team enrichment (membership view matched by session id). */
team?: {
role: 'lead' | 'teammate'
@@ -108,7 +108,7 @@ export interface TasksModelInput {
catalogs: Readonly<Record<string, SubagentCatalogView | undefined>>
rootId: string
currentSessionId: string
live: Readonly<Record<string, LastActivity | undefined>>
live: Readonly<Record<string, SidebarChildLiveView | undefined>>
runs: readonly WorkflowRunView[]
teamMembers: readonly SidebarTeamMemberView[]
/** The team's shared tasks; each lands on its OWNER's node. */
@@ -215,9 +215,10 @@ export function buildTasksModel(input: TasksModelInput): TasksNode[] {
if (entry.label?.startsWith('Side: ') ?? false) continue
const team = teamOf(entry.id)
// DSH 0.1.7's catalog row carries the identity only: the live channel
// folds RUNNING children (absence = not running) and a row keeps its
// disclosure unless its own catalog is known-empty.
const activity = childActivity(live, entry.id)
// reports EVERY child with an explicit `running` flag, and a row keeps
// its disclosure unless its own catalog is known-empty.
const view = childLive(live, entry.id)
const activity: 'running' | 'inactive' = view?.running === true ? 'running' : 'inactive'
const state: TasksNodeState = activity === 'running'
? 'running'
: team !== undefined ? teamState(team.status) : 'done'
@@ -232,7 +233,7 @@ export function buildTasksModel(input: TasksModelInput): TasksNode[] {
state,
activity,
current: entry.id === currentSessionId,
...(live[entry.id] !== undefined ? { live: live[entry.id] } : {}),
...(view !== undefined ? { live: view } : {}),
...(team !== undefined ? { team } : {}),
...(tasksByNode.get(entry.id) !== undefined ? { tasks: tasksByNode.get(entry.id) } : {}),
hasChildren: !isKnownLeaf(catalogs, entry.id),