diff --git a/docs/features/task-and-job/README.md b/docs/features/task-and-job/README.md index 1c572f85..0123047d 100644 --- a/docs/features/task-and-job/README.md +++ b/docs/features/task-and-job/README.md @@ -17,7 +17,7 @@ [当前会话统计 Demo](./hierarchy-demo.html) 用于评审任务中心中的紧凑统计区:上方显示当前会话的消息数与 Agent 累计回复时长,下方管理当前项目全部会话的任务及用时。切换会话只更新会话统计,任务范围固定为当前项目,没有全局或所有项目入口。支持状态筛选、定位所属会话,以及暂停、继续和取消任务的模拟操作。新建任务支持填写内容、关联本项目已有或新建会话、立即或定时执行;仅在页面内模拟,刷新后恢复样例。 配套 `hierarchy-demo.css` 与 `hierarchy-demo.js` 仅服务于该静态原型;使用演示数据,未接入产品。 -项目任务标题右侧依次为 `+` 新建按钮与筛选图标,状态筛选默认收起;应用筛选后图标高亮并显示当前筛选条件。 +应用中的项目任务标题右侧仅保留 `+` 新建按钮;展开列表后直接显示状态选项,默认选中“进行中”,收起后保留所选状态。静态 Demo 的筛选入口仍为旧交互。 时间为固定样例,Demo 从每次回复的起止秒数计算。正式功能按请求有效区间累计,包含轮内工具和审批等待、不计轮间空闲;任务用时按 `schedule_runs` 关联请求汇总,不按父任务生命周期计算。缺失历史有提示,远程缺少可靠证据时不推算用时,数据来源与边界以技术设计为准。 先阅读统一领域模型。其他功能文档不得重新定义 Task、Conversation、Job、Run 或 Subagent。 diff --git a/docs/features/task-and-job/task-center-prd.md b/docs/features/task-and-job/task-center-prd.md index d4912f4f..aa9b8428 100644 --- a/docs/features/task-and-job/task-center-prd.md +++ b/docs/features/task-and-job/task-center-prd.md @@ -77,10 +77,10 @@ FR-TC3:任务累计执行时间按其历次执行请求汇总,周期触发 ## 5. 交互 - 点击条目打开关联 Conversation,并定位到该 Task。 -- FR-TC4:项目任务标题右侧依次放置“+”新建按钮和筛选图标。状态筛选默认收起、默认全部, - 项目任务列表默认收起,标题与数量、新建及筛选入口保持可见;点击标题切换展开状态,点击筛选同时展开列表。 +- FR-TC4:项目任务标题右侧放置“+”新建按钮,不再提供独立筛选图标。 + 项目任务列表默认收起,标题与数量、新建入口保持可见;点击标题切换展开状态。 标题旁同时显示当前项目顶层任务中处于 `running` 状态的数量(如“2 运行中”),不受筛选或收起影响;为零时省略。 - 展开后提供全部、进行中、暂停、已结束;收起不清除已选条件,非全部时图标高亮并显示筛选条件。 + 展开后直接显示全部、进行中、暂停、已结束状态选项,初始选中进行中;收起不清除已选条件。 - 进行中包含 `idle`、`queued`、`running`、`waiting_approval`、`failed` 和 `interrupted`; 暂停对应 `paused`,已结束包含 `completed` 和 `cancelled`。分类只影响列表筛选, 不改变任务状态语义;任务行保留具体状态、错误信息和原有操作。 @@ -158,7 +158,7 @@ FR-TC3:任务累计执行时间按其历次执行请求汇总,周期触发 - [ ] FR-TC1:仅显示当前项目任务,切换会话不缩小列表;无项目时显示选择项目提示。 - [ ] FR-TC2:消息总数与累计回复时间随当前会话更新;轮内工具和审批计时,轮间空闲不计时,历史缺失及远程证据不足有提示。 - [ ] FR-TC3:多次执行汇总到同一任务,不按父 Task 生命周期计算用时。 -- [ ] FR-TC4:标题右侧先新建、后筛选;筛选默认收起且为全部,收起后保留所选条件。 +- [x] FR-TC4:标题右侧仅保留新建;展开列表直接显示状态选项,默认进行中,收起后保留所选条件。 - [ ] FR-TC5:创建默认立即执行和当前会话,可选择本项目其他会话、新会话及定时周期,空名称从内容生成。 - [x] 任务中心保持单实例、默认存在且不可关闭。 diff --git a/src/renderer/src/RightAssistantSidebar.resize.test.tsx b/src/renderer/src/RightAssistantSidebar.resize.test.tsx index aa4e74c8..bf411e91 100644 --- a/src/renderer/src/RightAssistantSidebar.resize.test.tsx +++ b/src/renderer/src/RightAssistantSidebar.resize.test.tsx @@ -363,6 +363,7 @@ describe('RightAssistantSidebar resizing', () => { } const view = render(sidebarElement(props)) fireEvent.click(screen.getByRole('button', { name: '项目任务 2' })) + fireEvent.click(screen.getByRole('button', { name: '全部' })) const stats = within(screen.getByRole('region', { name: '当前会话统计' })) expect(stats.getByText('01:01:01')).toBeVisible() expect(stats.getByText('24')).toBeVisible() @@ -385,29 +386,22 @@ describe('RightAssistantSidebar resizing', () => { for (const task of tasks) expect(screen.queryByText(task.title)).not.toBeInTheDocument() }) - it('keeps an applied filter visible when collapsed and clears it without opening the filter panel', () => { + it('shows the task status tabs when expanded and defaults to active tasks', () => { renderSidebar({ tasks: [{ id: 'completed', projectId: currentProject.id, title: 'Completed task', instructions: '', origin: 'schedule', status: 'completed', createdAt: '2026-09-17T00:00:00Z' }] }) - const toggle = screen.getByRole('button', { name: '筛选任务' }) - expect(screen.getByText('Completed task')).toBeVisible() - fireEvent.click(toggle) - fireEvent.click(screen.getByRole('button', { name: '进行中' })) - fireEvent.click(toggle) - expect(toggle).toHaveAttribute('aria-expanded', 'false') - expect(toggle).toHaveAttribute('data-active', 'true') - expect(screen.queryByRole('group', { name: '筛选任务' })).not.toBeInTheDocument() - expect(screen.getByText('筛选:进行中 · 0 项')).toBeVisible() - expect(screen.getByText('当前筛选条件下没有任务。')).toBeVisible() - fireEvent.click(screen.getByRole('button', { name: '清除筛选' })) - expect(toggle).toHaveAttribute('data-active', 'false') + expect(screen.queryByText('Completed task')).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: '筛选任务' })).not.toBeInTheDocument() + const filters = within(screen.getByRole('group', { name: '筛选任务' })) + expect(filters.getByRole('button', { name: '进行中' })).toHaveAttribute('aria-pressed', 'true') + expect(screen.queryByText('Completed task')).not.toBeInTheDocument() + fireEvent.click(filters.getByRole('button', { name: '已结束' })) expect(screen.getByText('Completed task')).toBeVisible() }) it('scrolls the focused task filter into view when keyboard navigation reaches either end', () => { renderSidebar() - fireEvent.click(screen.getByRole('button', { name: '筛选任务' })) const filters = within(screen.getByRole('group', { name: '筛选任务' })) const first = filters.getByRole('button', { name: '全部' }) const last = filters.getByRole('button', { name: '已结束' }) @@ -658,7 +652,7 @@ describe('RightAssistantSidebar resizing', () => { expect(list).toBeVisible() fireEvent.click(toggle) expect(list).not.toBeVisible() - fireEvent.click(screen.getByRole('button', { name: '筛选任务' })) + fireEvent.click(toggle) expect(toggle).toHaveAttribute('aria-expanded', 'true') expect(list).toBeVisible() }) @@ -676,7 +670,7 @@ describe('RightAssistantSidebar resizing', () => { expect(within(stats).getByText(`项目:${currentProject.name}`)).toBeVisible() expect(screen.getByRole('button', { name: '项目任务 4' })).toHaveAttribute('aria-expanded', 'false') expect(screen.getByText('2 运行中')).toBeVisible() - fireEvent.click(screen.getByRole('button', { name: '筛选任务' })) + fireEvent.click(screen.getByRole('button', { name: '项目任务 4' })) fireEvent.click(within(screen.getByRole('group', { name: '筛选任务' })).getByRole('button', { name: '已结束' })) expect(screen.getByText('2 运行中')).toBeVisible() view.rerender(sidebarElement({ tab: 'tasks', tasks: [] })) @@ -701,10 +695,7 @@ describe('RightAssistantSidebar resizing', () => { ) fireEvent.click(newTaskButton) expect(onCreateCustomTask).toHaveBeenCalledOnce() - expect( - screen.queryByRole('group', { name: '筛选任务' }) - ).not.toBeInTheDocument() - expect(screen.getByRole('button', { name: '筛选任务' })).toHaveAttribute('aria-expanded', 'false') + expect(screen.getByRole('group', { name: '筛选任务' })).toBeVisible() expect(screen.queryByLabelText('定时任务标题')).not.toBeInTheDocument() expect(screen.queryByText('最近任务')).not.toBeInTheDocument() }) @@ -736,7 +727,6 @@ describe('RightAssistantSidebar resizing', () => { approvals: [approval], onOpenTask, onRespondApproval }) - fireEvent.click(screen.getByRole('button', { name: '筛选任务' })) const filters = within(screen.getByRole('group', { name: '筛选任务' })) expect(filters.getAllByRole('button').map((button) => button.textContent)) .toEqual(['全部', '进行中', '暂停', '已结束']) @@ -782,7 +772,6 @@ describe('RightAssistantSidebar resizing', () => { } const onRespondApproval = vi.fn() renderSidebar({ tasks: [task], approvals: [approval], onRespondApproval }) - fireEvent.click(screen.getByRole('button', { name: '筛选任务' })) const row = screen.getByText(task.title).closest('article')! expect(within(row).getByLabelText('等待审批: Confirm write')).toBeVisible() expect(screen.queryByRole('heading', { name: '等待审批' })).not.toBeInTheDocument() diff --git a/src/renderer/src/RightAssistantSidebar.tsx b/src/renderer/src/RightAssistantSidebar.tsx index 6c5d53ee..b89183b3 100644 --- a/src/renderer/src/RightAssistantSidebar.tsx +++ b/src/renderer/src/RightAssistantSidebar.tsx @@ -7,7 +7,6 @@ import { Copy, ExternalLink, FileText, - Filter, Maximize2, Minimize2, Plus, @@ -48,7 +47,6 @@ import { isBrowserViewportOccluded } from './browser-viewport-occlusion' import { - findTaskSchedule, TaskScheduleActions } from './TaskScheduleActions' import type { @@ -883,8 +881,7 @@ export function RightAssistantSidebar({ useState('') const [taskFilter, setTaskFilter] = useState< 'all' | 'active' | 'paused' | 'finished' - >('all') - const [taskFiltersExpanded, setTaskFiltersExpanded] = useState(false) + >('active') const [taskListExpanded, setTaskListExpanded] = useState(false) const [actionErrorState, setActionErrorState] = useState({ scope: { instanceId: activeWorkbarInstanceId, open }, @@ -1027,6 +1024,14 @@ export function RightAssistantSidebar({ }), [approvalsByTask, taskFilter, topLevelTasks] ) + const schedulesByTaskKey = useMemo(() => { + const byKey = new Map() + for (const schedule of schedules) { + byKey.set(schedule.id, schedule) + byKey.set(schedule.taskId, schedule) + } + return byKey + }, [schedules]) useEffect(() => { // Context switches preserve the selected instance and its pinned binding. @@ -1981,21 +1986,8 @@ export function RightAssistantSidebar({ >