fix: simplify task center scopes and status filters

Task Center previously split unbound tasks into a global scope and tasks needing attention into a separate filter. Keep Current project and All projects, label unbound tasks, and restore legacy global scope preferences as all-projects without changing task data.

Keep Active, Paused, and Finished filters. Active includes idle, queued, running, waiting_approval, failed, and interrupted tasks; task details and project-scoped approvals remain available.

Release note: Task Center now offers Current project or All projects and groups tasks needing attention under Active, while retaining project labels and approval actions.
This commit is contained in:
mesalogo
2026-09-16 14:05:36 +08:00
parent e84d7bd185
commit cb245a6402
12 changed files with 149 additions and 68 deletions
+3 -2
View File
@@ -98,8 +98,9 @@ records are listed separately and do not introduce another feature status.
toggle sits beside the theme switch. The right workbar uses a persistent “+”
capability catalog and application tabs.
Task Center and Workspace are permanent singleton tabs; browser tabs are
independent instances. Task Center can show the current project, global
tasks, or all projects, and retains the selected scope with the layout.
independent instances. Task Center can show the current project or all projects,
and retains the selected scope with the layout. Tasks without a project appear
under all projects with an “Unbound project” label.
Users can open multiple independent terminals for the current local or
managed SSH project with bounded output, resizing, termination, and explicit
reconnection. Closing a terminal tab ends its Shell; restarting the app
+1 -1
View File
@@ -28,7 +28,7 @@
- [x] **工作区文件与 Git 管理**:支持文件和目录的新建、重命名、移动、删除、属性查看,以及本地/远程分支搜索与切换、新建分支、显式 Fetch、提交历史和单文件提交差异。工具按文件/Git 视图分组,分支搜索使用统一紧凑表单,子目录保留文字导航而根目录不再显示孤立图标。提交历史贴底,首次展开占 Git 内容区一半,支持调整比例、独立滚动和统一刷新;切换项目自动检测 Git,不沿用其他项目状态。移动限于工作区内,删除须确认,分支冲突不自动 stash 或丢弃改动;托管 SSH 操作需要 Agent `0.11.23`。
- [x] **富文本回答**:支持 GitHub Flavored Markdown、LaTeX 数学公式、受控 Mermaid 图表和会话内 HTML 静态预览。Agent 完成回复后,完整 HTML 与 HTML 代码块可在原位置预览,并通过图标按钮查看源码或打开全屏预览;该能力默认开启,可在“平台功能 / 通用设置”关闭,预览不执行脚本、联网、表单提交或窗口操作。
- [x] **AI 回复与完整会话复制**:已完成的 AI 回复可从消息底部复制不含推理、工具日志和引用元数据的 Markdown 原文;完整会话复制复用同一条经过 Preload/Main 校验的剪贴板路径。
- [x] **助手工作栏、多终端与可调布局**:工作栏开关位于明暗主题按钮旁;右侧工作栏使用持久“+”能力目录和应用 Tab,任务中心与工作区为不可关闭的单实例,浏览器可打开多个独立实例。任务中心可切换当前项目、全局任务和全部项目,范围随布局保存。用户可为当前本机或托管 SSH 项目打开多个独立终端,获得有界输出、调整尺寸、结束与显式重连。关闭终端 Tab 会结束 Shell,应用重启只恢复已结束的 Tab 描述,不自动重启 Shell。主侧栏和魔法笔记列表支持鼠标与键盘调宽,魔法笔记列表可隐藏并记住布局,窄宽度下自动堆叠。
- [x] **助手工作栏、多终端与可调布局**:工作栏开关位于明暗主题按钮旁;右侧工作栏使用持久“+”能力目录和应用 Tab,任务中心与工作区为不可关闭的单实例,浏览器可打开多个独立实例。任务中心可切换当前项目和所有项目,范围随布局保存;无项目任务在所有项目中标注“未绑定项目”。用户可为当前本机或托管 SSH 项目打开多个独立终端,获得有界输出、调整尺寸、结束与显式重连。关闭终端 Tab 会结束 Shell,应用重启只恢复已结束的 Tab 描述,不自动重启 Shell。主侧栏和魔法笔记列表支持鼠标与键盘调宽,魔法笔记列表可隐藏并记住布局,窄宽度下自动堆叠。
- [ ] **项目 Agent Space**(规划中):在 Project 中统一角色、知识、Skills/MCP、模型、审批策略、预算和超时,并支持模板复用。
- [ ] **应用导航与系统工具快捷入口**(仅设计):规划固定系统入口、内置应用显隐和打开现有工作栏的底部工具按钮;尚未实现新界面。详见[设计文档](./docs/features/application-tool-navigation/README.md)。
- [ ] **通用助手工作栏与执行空间后续能力**(规划中):在现有工作栏和多终端基础上继续加入监督、统一 Runtime、受管进程、静态安全 HTML 预览、可固定目标的工作区/浏览器/成果实例、底部停靠与独立窗口。Task Center 保持 Task 的单例索引,附件与知识库继续由会话输入区管理,未来的记忆与历史执行上下文归入关联 Task。详见 [Feature PRD](./docs/features/assistant-workbar/prd.md)。
+2 -2
View File
@@ -338,7 +338,7 @@
单实例重复选择时聚焦已有 Tab;浏览器和终端每次选择创建独立实例。主侧栏系统工具快捷
入口按当前上下文聚焦最近使用的匹配实例,额外实例仍从“+”目录创建。
- Task Center 是默认跟随当前项目的单实例索引,不使用多实例模式。用户可在同一 Tab 内切换
“当前项目”“全局任务”和“全部项目”;范围变化不得创建第二个 Task Center。每个 Task 只
“当前项目”和“所有项目”;无项目任务在“所有项目”中标注“未绑定项目”。范围变化不得创建第二个 Task Center。每个 Task 只
关联一条 Conversation,一条 Conversation 可以关联多个 Task;列表不得复制会话内容,也
不得把 Job/Run 提升为可导航 UI 对象。
- 各栏目读取当前会话或项目的对应内容;当前内容不可用时仍保留栏目入口并说明原因。
@@ -687,7 +687,7 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
- 每项显示名称、关联 Conversation、Global 或 Project 范围、Ask/Execute、状态、最近进展、
最近真实活动时间及需要关注信息。
- 点击列表项打开关联 Conversation 并定位该 Task,不显示第二份内容载体。
- 需要关注、进行中、已暂停和已结束使用共享 `SegmentedControl`;窄栏不足时单行滚动。
- 进行中、暂停和已结束使用共享 `SegmentedControl`;窄栏不足时单行滚动。状态归类遵循[任务中心 PRD](docs/features/task-and-job/task-center-prd.md#5-交互)。
- 完整消息留在 Conversation;长错误、工具、审批和成果按 Task 关联到 Runtime、活动记录和
成果查看器,不撑高列表,也不显示 Job/Run 树。
- “新建定制任务”使用共享 Modal,明确选择当前或新 Conversation。聊天入口默认当前
@@ -425,9 +425,8 @@ Renderer 只通过显式 IPC 列出、加入、删除、提升、释放和接收
```text
任务中心
├─ 需要关注
├─ 进行中
├─ 已暂停
├─ 暂停
└─ 已结束
└─ 打开任务自身
+4 -4
View File
@@ -102,7 +102,7 @@ Runtime
### 3.2 当前上下文只提供默认值
任务中心只有一个工作栏实例,默认跟随当前项目,不跟随当前 Conversation。用户可以在同一
实例内显式切换到全局任务或全部项目,但不能为不同项目打开多个任务中心 Tab。其他可绑定
实例内显式切换到所有项目,但不能为不同项目打开多个任务中心 Tab。其他可绑定
目标的能力使用当前会话、项目、Runtime 和主机帮助新面板实例初始定位,但这些上下文不是
使用门槛:
@@ -260,7 +260,7 @@ Runtime
Task Center 保留为工作栏中的稳定入口,并在现有基础上适度完善:
- 只索引 Task;每个 Task 只关联一条 Conversation,一条 Conversation 可以关联多个 Task。
- 显示名称、关联 Conversation、Global 或 Project 范围、Ask/Execute、状态、最近进展、
- 显示名称、关联 Conversation、所属项目(无项目时标注“未绑定项目”)、Ask/Execute、状态、最近进展、
真实活动时间和需要关注信息。
- 点击 Task 打开其关联 Conversation 并定位 Task。
- 完整消息留在 Conversation;工具、Subagent、审批、错误和成果按 Task 关联到 Runtime、
@@ -268,11 +268,11 @@ Task Center 保留为工作栏中的稳定入口,并在现有基础上适度
- 智能心跳的报告、建议、历史和配置不进入任务中心。
任务中心是应用级单例,默认存在且不可关闭。它的数据范围默认跟随当前项目,并在标题或范围
控件中明确显示项目;用户可以切换到“全局任务”或“全部项目”,该选择只改变同一实例的列表
控件中明确显示项目;用户可以切换到“所有项目”,该选择只改变同一实例的列表
范围,不创建第二个任务中心 Tab。切换回“跟随当前项目”后,项目变化立即刷新列表。后台状态
可以更新徽标和排序,但不能抢占用户当前工作。
当前实现已按活动项目过滤 Scheduled Task 和审批,并提供“当前项目”“全局任务”“全部项目”
当前实现已按活动项目过滤 Scheduled Task 和审批,并提供“当前项目”和“所有项目”
范围控件。
主侧栏最近会话为关联 Task 提供轻量入口:父会话行只显示行首展开按钮,Task 子项使用
+12 -1
View File
@@ -1115,13 +1115,24 @@ src/agent-daemon/runtime-composition.test.ts src/main/ipc.test.ts` 为 179 项
测试应用与具名浏览器会话已退出,所属 Electron/Node 进程及 fixture 监听端口均为 0;
临时截图和隔离 profile 保留在本机测试目录,不进入仓库。
## 2026-09-16 任务中心范围简化
- 任务中心仅保留“当前项目”和“所有项目”;无项目任务在后者中标注“未绑定项目”。具体规则见
[Task Center 范围](../task-and-job/task-center-prd.md#4-范围)。
- Renderer 将旧布局中的 `global` 恢复为 `all-projects` 并保存新值;共享解析契约、后端与任务
数据不变。App 仍只向任务中心传入 schedule 来源的顶层任务。
- `npx vitest run src/renderer/src/RightAssistantSidebar.resize.test.tsx src/shared/workbar-contracts.test.ts`
通过 60 项;`npm run typecheck` 和 `npm run lint` 通过。未进行真实 Electron 窗口验收。
- `npm test` 在 200 秒后超时,未取得整库结果;单独复跑 `heartbeat-database.test.ts` 为 6 项通过、
2 项失败,均在未修改的数据库迁移代码中报 `duplicate column name: pinned`。
## 2026-09-10 固定基础页签与浏览器多实例
- 工作栏注册表已分别声明实例策略、默认上下文、默认打开、必须存在、可关闭和排序属性。
任务中心与工作区保持默认存在且不可关闭;浏览器改为当前 Conversation 上下文的多实例。
布局加载按 `required` 补回缺失的基础页签,并修复失效活动实例。
- 任务中心增加“当前项目”“全局任务”“全部项目”范围,默认跟随活动项目;任务和审批按
Conversation 所属项目过滤,范围模式随工作栏布局持久化。
Conversation 所属项目过滤,范围模式随工作栏布局持久化。范围选项已于 2026-09-16 简化,见上方记录。
- 浏览器服务已拆分 Conversation Context 与 Browser Tab。每个 Tab 拥有独立页面、Driver、
操作队列、状态和元素引用空间,同一 Conversation 的 Tab 共享 partition、代理和 Cookie。
- Renderer 使用工作栏实例 UUID 原子创建或恢复 Browser Tab,并以 UUID viewport token 防止
@@ -368,6 +368,8 @@ type WorkbarLayoutPreferences = {
```
- 持久化工作栏打开状态、Tab 顺序、活动实例、应用 ID、自定义标题、公开目标引用和宽度比例。
- `taskScope` 中的 `global` 仅保留旧布局解析兼容;UI 恢复时映射为 `all-projects`,此后只保存
`current-project` 或 `all-projects`。范围规则见 [Task Center PRD](../task-and-job/task-center-prd.md#4-范围)。
- 加载布局时根据 `required` 补回缺失实例,并拒绝重复的单实例应用;应用策略不写入实例
数据,避免持久化状态覆盖注册表。
- `reorderable` 已进入共享契约;当前 Tab 壳层尚未提供拖放或键盘重排,不能把声明属性视为
+12 -8
View File
@@ -19,7 +19,7 @@ Task Center 是工作栏中的单实例 Task 索引,默认跟随当前项目
Conversation 不因为关联 Task 而改变对象类型。
“单实例”和“项目范围”是两个独立属性:应用内只有一个 Task Center Tab,但该 Tab 默认展示
当前项目的 Task。用户可以在同一 Tab 内切换到“全局任务”或“全部项目”,不能为每个项目创建
当前项目的 Task。用户可以在同一 Tab 内切换到“所有项目”,不能为每个项目创建
独立 Task Center 实例。
## 2. 收录边界
@@ -45,7 +45,7 @@ Job/Subjob/Run 树、独立详情或路由。
每条 Task 至少显示:
- 名称和 Global / Project 范围。
- 名称和所属项目;无项目时标注“未绑定项目”。
- 关联 Conversation 标题。
- Task 类型和触发来源。
- Ask / Execute 模式。
@@ -62,17 +62,21 @@ Task 行只显示聚合后的用户状态,不要求用户理解内部 Job/Run
范围控件提供:
- `当前项目`:默认值,跟随活动项目变化,只显示 `projectId` 匹配的 Task。
- `全局任务`:只显示没有 `projectId` 的 Task。
- `全部项目`:显示当前用户可见的项目 Task 和全局 Task,并保留每行的项目或 Global 标识。
- `所有项目`:显示当前用户可见的项目 Task 和没有 `projectId` 的历史 Task,后者标注“未绑定项目”。
没有活动项目时,“当前项目”显示明确空状态,并允许切换到“全局任务”或“全部项目”;系统不
没有活动项目时,“当前项目”显示明确空状态,并允许切换到“所有项目”;系统不
自动改变用户已经选择的范围。范围模式作为工作栏公开偏好持久化,但“当前项目”模式不保存
具体项目 ID;重启后继续跟随届时的活动项目。
具体项目 ID;重启后继续跟随届时的活动项目。旧布局保存的 `global` 范围在 UI 恢复时映射为
`all-projects`,后续保存使用新值;不修改任务数据或后端项目绑定规则。
## 5. 交互
- 点击条目打开关联 Conversation,并定位到该 Task。
- 支持按需要关注、进行中、已暂停、已结束筛选。
- 状态筛选仅保留进行中、暂停、已结束,默认选择进行中。
- 进行中包含 `idle`、`queued`、`running`、`waiting_approval`、`failed` 和 `interrupted`;
暂停对应 `paused`,已结束包含 `completed` 和 `cancelled`。分类只影响列表筛选,
不改变任务状态语义;任务行保留具体状态、错误信息和原有操作。
- 等待审批区域和审批徽标按项目范围展示,不受状态筛选影响,保留拒绝和允许一次操作。
- 支持立即运行、暂停、恢复、取消、编辑和删除。
- 后台变化更新状态和徽标,不自动抢占当前页面。
- 立即运行、重试和恢复在 UI 上都是 Task 操作,内部 Job/Run 不单独显示。
@@ -138,5 +142,5 @@ Task 行只显示聚合后的用户状态,不要求用户理解内部 Job/Run
- [ ] 删除 Task 默认保留 Conversation 和既有消息。
- [x] Smart Heartbeat 不进入 Task Center。
- [x] Task Center 默认只显示当前项目的 Task,切换项目后在同一 Tab 更新列表。
- [x] “全局任务”只显示无项目 Task,“全部项目”显示所有可见 Task 并标注所属范围。
- [x] 仅提供“当前项目”和“所有项目”;后者保留无项目 Task,并标注“未绑定项目”。
- [x] 范围切换不创建第二个 Task Center Tab;任务中心保持默认存在且不可关闭。
@@ -156,6 +156,8 @@ function sidebarElement({
onNavigateBrowser,
onReloadBrowser,
onStopLoadingBrowser,
onOpenTask = vi.fn(),
onRespondApproval = vi.fn(),
onTabChange = vi.fn()
}: {
open?: boolean
@@ -188,6 +190,8 @@ function sidebarElement({
onNavigateBrowser?: (conversationId: string, tabId: BrowserTabId, url: string) => Promise<void>
onReloadBrowser?: (conversationId: string, tabId: BrowserTabId) => Promise<void>
onStopLoadingBrowser?: (conversationId: string, tabId: BrowserTabId) => Promise<void>
onOpenTask?: (task: AssistantTask) => void
onRespondApproval?: React.ComponentProps<typeof RightAssistantSidebar>['onRespondApproval']
onTabChange?: (tab: AssistantSidebarTab) => void
} = {}): React.JSX.Element {
return (
@@ -215,11 +219,11 @@ function sidebarElement({
onRefreshChanges={vi.fn(async () => undefined)}
onReloadBrowser={onReloadBrowser}
onRemoveSchedule={vi.fn(async () => undefined)}
onRespondApproval={vi.fn()}
onRespondApproval={onRespondApproval}
onRunSchedule={vi.fn(async () => undefined)}
onSetScheduleEnabled={vi.fn(async () => undefined)}
onStopLoadingBrowser={onStopLoadingBrowser}
onOpenTask={vi.fn()}
onOpenTask={onOpenTask}
onTabChange={onTabChange}
open={open}
restoreFocusRef={restoreFocusRef}
@@ -568,6 +572,65 @@ describe('RightAssistantSidebar resizing', () => {
expect(screen.queryByText('最近任务')).not.toBeInTheDocument()
})
it('groups attention states as active while preserving task details and approvals across filters', () => {
const statuses: AssistantTask['status'][] = [
'idle', 'queued', 'running', 'waiting_approval', 'failed', 'interrupted',
'paused', 'completed', 'cancelled'
]
const tasks = statuses.map((status): AssistantTask => ({
id: status,
title: `Task ${status}`,
instructions: status,
projectId: currentProject.id,
origin: 'schedule',
status,
error: status === 'failed' ? 'Task execution failed' : undefined,
createdAt: '2026-09-09T00:00:00.000Z'
}))
const approval: PendingSidebarApproval = {
conversationId: 'conversation-1', messageId: 'message-1',
approvalId: 'approval-1', projectId: currentProject.id,
title: '写入工作区', description: '更新 release.md'
}
const onOpenTask = vi.fn()
const onRespondApproval = vi.fn()
renderSidebar({
tasks: [...tasks, { ...tasks[4]!, id: 'child', title: 'Child task', parentTaskId: 'running' }],
approvals: [approval], onOpenTask, onRespondApproval
})
const filters = within(screen.getByRole('group', { name: '筛选任务' }))
expect(filters.getAllByRole('button').map((button) => button.textContent))
.toEqual(['进行中', '暂停', '已结束'])
expect(filters.getByRole('button', { name: '进行中' })).toHaveAttribute('aria-pressed', 'true')
for (const task of tasks.slice(0, 6)) {
const row = screen.getByText(task.title).closest('article')!
expect(within(row).getByText(i18n.t(`task.status.${task.status}`, { ns: 'workspace' }))).toBeVisible()
fireEvent.click(within(row).getByRole('button'))
expect(onOpenTask).toHaveBeenLastCalledWith(task)
}
expect(screen.getByText('Task execution failed')).toBeVisible()
expect(screen.queryByText('Child task')).not.toBeInTheDocument()
for (const [filter, visibleStatuses] of [
['进行中', statuses.slice(0, 6)],
['暂停', ['paused']],
['已结束', ['completed', 'cancelled']]
] as const) {
fireEvent.click(filters.getByRole('button', { name: filter }))
for (const task of tasks) {
expect(Boolean(screen.queryByText(task.title)))
.toBe((visibleStatuses as readonly string[]).includes(task.status))
}
expect(screen.getByLabelText('等待审批: 1')).toBeVisible()
expect(screen.getByLabelText('等待审批: 写入工作区')).toBeVisible()
}
fireEvent.click(screen.getByRole('button', { name: '仅此次允许' }))
expect(onRespondApproval).toHaveBeenLastCalledWith(approval, 'once')
fireEvent.click(screen.getByRole('button', { name: '拒绝' }))
expect(onRespondApproval).toHaveBeenLastCalledWith(approval, 'deny')
})
it('labels approval counts and cards without duplicate live regions', () => {
renderSidebar({
approvals: [
@@ -722,15 +785,15 @@ describe('RightAssistantSidebar resizing', () => {
fireEvent.click(
within(screen.getByRole('group', { name: '任务范围' })).getByRole(
'button',
{ name: '全局' }
{ name: '所有项目' }
)
)
expect(screen.getByText('全局审批')).toBeInTheDocument()
expect(screen.queryByText('当前项目审批')).not.toBeInTheDocument()
expect(screen.getByLabelText('等待审批: 1')).toBeInTheDocument()
expect(screen.getByText('当前项目审批')).toBeInTheDocument()
expect(screen.getByLabelText('等待审批: 2')).toBeInTheDocument()
})
it('scopes tasks to the current project, global tasks, or all projects and persists the choice', () => {
it('scopes tasks to the current or all projects, labels unbound tasks and persists the choice', () => {
const createTask = (
id: string,
title: string,
@@ -758,15 +821,10 @@ describe('RightAssistantSidebar resizing', () => {
expect(screen.getByText('当前项目任务')).toBeInTheDocument()
expect(screen.queryByText('全局任务')).not.toBeInTheDocument()
fireEvent.click(
within(screen.getByRole('group', { name: '任务范围' })).getByRole(
'button',
{ name: '全局' }
)
)
expect(screen.getByText('全局任务')).toBeInTheDocument()
expect(screen.queryByText('当前项目任务')).not.toBeInTheDocument()
expect(screen.queryByText('其他项目任务')).not.toBeInTheDocument()
expect(within(screen.getByRole('group', { name: '任务范围' })).getAllByRole('button'))
.toHaveLength(2)
expect(screen.queryByRole('button', { name: '全局' })).not.toBeInTheDocument()
fireEvent.click(
within(screen.getByRole('group', { name: '任务范围' })).getByRole(
@@ -777,10 +835,33 @@ describe('RightAssistantSidebar resizing', () => {
expect(screen.getByText('当前项目任务')).toBeInTheDocument()
expect(screen.getByText('全局任务')).toBeInTheDocument()
expect(screen.getByText('其他项目任务')).toBeInTheDocument()
expect(screen.getAllByText('未绑定项目')).toHaveLength(1)
expect(screen.getByText('项目:00000000-0000-4000-8000-000000000302')).toBeInTheDocument()
expect(JSON.parse(localStorage.getItem('goodbuddy.workbar-layout.v1')!))
.toEqual(expect.objectContaining({ taskScope: 'all-projects' }))
})
it('restores the legacy global scope as all projects and saves the updated preference', () => {
localStorage.setItem('goodbuddy.workbar-layout.v1', JSON.stringify({
instances: DEFAULT_WORKBAR_INSTANCES,
activeInstanceId: DEFAULT_WORKBAR_INSTANCES[0]!.id,
expanded: true,
dock: 'right',
widthRatio: 0.3,
taskScope: 'global'
}))
renderSidebar({ activeProject: null, tasks: [{
id: 'legacy-unbound-task', title: '历史任务', instructions: '历史任务',
origin: 'schedule', status: 'running', createdAt: '2026-09-09T00:00:00.000Z'
}] })
expect(screen.getByRole('button', { name: '所有项目' })).toHaveAttribute('aria-pressed', 'true')
expect(screen.getByText('历史任务')).toBeInTheDocument()
expect(screen.getByText('未绑定项目')).toBeInTheDocument()
expect(JSON.parse(localStorage.getItem('goodbuddy.workbar-layout.v1')!).taskScope)
.toBe('all-projects')
})
it('shows a clear current-project empty state when no project is active', () => {
renderSidebar({ activeProject: null })
+12 -25
View File
@@ -831,10 +831,12 @@ export function RightAssistantSidebar({
const [workspacePreviewLoadMoreError, setWorkspacePreviewLoadMoreError] =
useState('')
const [taskFilter, setTaskFilter] = useState<
'attention' | 'active' | 'paused' | 'finished'
'active' | 'paused' | 'finished'
>('active')
const [taskScope, setTaskScope] = useState<WorkbarTaskScope>(
initialLayout?.taskScope ?? 'current-project'
const [taskScope, setTaskScope] = useState<Exclude<WorkbarTaskScope, 'global'>>(
initialLayout?.taskScope === 'global'
? 'all-projects'
: initialLayout?.taskScope ?? 'current-project'
)
const [actionErrorState, setActionErrorState] = useState({
scope: { instanceId: activeWorkbarInstanceId, open },
@@ -925,9 +927,6 @@ export function RightAssistantSidebar({
if (taskScope === 'all-projects') {
return true
}
if (taskScope === 'global') {
return !projectId
}
return Boolean(currentProject && projectId === currentProject.id)
},
[currentProject, taskScope]
@@ -946,18 +945,14 @@ export function RightAssistantSidebar({
const filteredTasks = useMemo(
() =>
topLevelTasks.filter((task) => {
if (taskFilter === 'attention') {
return (
task.status === 'waiting_approval' ||
task.status === 'failed' ||
task.status === 'interrupted'
)
}
if (taskFilter === 'active') {
return (
task.status === 'idle' ||
task.status === 'queued' ||
task.status === 'running'
task.status === 'running' ||
task.status === 'waiting_approval' ||
task.status === 'failed' ||
task.status === 'interrupted'
)
}
if (taskFilter === 'paused') {
@@ -1885,10 +1880,6 @@ export function RightAssistantSidebar({
value: 'current-project',
label: t('sidebar.tasks.scope.currentProject')
},
{
value: 'global',
label: t('sidebar.tasks.scope.global')
},
{
value: 'all-projects',
label: t('sidebar.tasks.scope.allProjects')
@@ -1966,10 +1957,6 @@ export function RightAssistantSidebar({
ariaLabel={t('sidebar.tasks.filters.ariaLabel')}
onChange={setTaskFilter}
options={[
{
value: 'attention',
label: t('sidebar.tasks.filters.attention')
},
{
value: 'active',
label: t('sidebar.tasks.filters.active')
@@ -2034,11 +2021,11 @@ export function RightAssistantSidebar({
</button>
<div className="task-center__metadata">
<span>
{projectName
{task.projectId
? t('sidebar.tasks.projectScope', {
project: projectName
project: projectName ?? task.projectId
})
: t('sidebar.tasks.globalScope')}
: t('sidebar.tasks.unboundProject')}
</span>
<span>
{schedule
@@ -327,21 +327,19 @@ export const workspace = {
noFilterResults: 'No tasks match this filter.',
conversationUnavailable: 'Conversation unavailable',
projectScope: 'Project: {{project}}',
globalScope: 'Global',
unboundProject: 'Unbound project',
startedAt: 'Started {{time}}',
nextRunAt: 'Next run: {{time}}',
notStarted: 'Not run yet',
scope: {
ariaLabel: 'Task scope',
currentProject: 'Current project',
global: 'Global',
allProjects: 'All projects',
noCurrentProject:
'No project is active. Choose another task scope or open a project.'
},
filters: {
ariaLabel: 'Filter tasks',
attention: 'Attention',
active: 'Active',
paused: 'Paused',
finished: 'Finished'
@@ -319,23 +319,21 @@ export const workspace = {
noFilterResults: '当前筛选条件下没有任务。',
conversationUnavailable: '关联会话不可用',
projectScope: '项目:{{project}}',
globalScope: '全局',
unboundProject: '未绑定项目',
startedAt: '{{time}} 开始',
nextRunAt: '下次运行:{{time}}',
notStarted: '尚未运行',
scope: {
ariaLabel: '任务范围',
currentProject: '当前项目',
global: '全局',
allProjects: '所有项目',
noCurrentProject: '没有活动项目。请选择其他任务范围或先打开一个项目。'
},
filters: {
ariaLabel: '筛选任务',
attention: '待关注',
active: '进行中',
paused: '已暂停',
finished: '已完成'
paused: '暂停',
finished: '已结束'
},
schedule: {
recurrence: {