diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 58b2efe..f50886d 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -1,5 +1,23 @@ # Progress +## Phase 143 (Tasks page now defaults to schedule and cron execution) — Completed +- Scope: + - Stop making the tasks page look empty or contradictory when staff are active through cron/ad-hoc sessions but there are no tracked task rows. + - Make schedule and cron execution the primary view, with tracked-task boards demoted behind a secondary foldout. +- Changed files: + - `src/ui/server.ts` + - `test/ui-render-smoke.test.ts` + - `docs/PROGRESS.md` +- Implementation: + - Reframed the tasks-page lead copy so it explains that staff activity can come from cron or ad-hoc runtime sessions before anything lands in the tracked task store. + - Promoted `Today and next schedule` and `Cron execution board` to the always-visible primary view. + - Moved tracked tasks, follow-up items, execution chains, project lanes, live feed, and raw tables behind a `Tracked tasks and follow-up` detail block. + - Updated current-task navigation anchors so overview and readiness links land on the new tracked-task summary instead of the old lane anchor. +- Verification: + - `npm run build` + - `npm test` + - `npm run smoke:ui` + ## Phase 142 (Documents workbench now includes identity and soul markdown files) — Completed - Scope: - Fix the document workbench so Main and active agents expose their `IDENTITY.md` and `SOUL.md` files instead of hiding them behind an overly narrow core-doc whitelist. diff --git a/src/ui/server.ts b/src/ui/server.ts index e1a3c47..1cbc573 100644 --- a/src/ui/server.ts +++ b/src/ui/server.ts @@ -1962,7 +1962,7 @@ function buildGlobalVisibilityDetailHref(taskType: GlobalVisibilityTaskRow["task return `${buildHomeHref({ quick: "all" }, true, "overview", language)}#heartbeat-health`; } if (taskType === "current_task") { - return `${buildHomeHref({ quick: "all" }, true, "projects-tasks", language)}#task-lane`; + return `${buildHomeHref({ quick: "all" }, true, "projects-tasks", language)}#tracked-task-view`; } return `${buildHomeHref({ quick: "all" }, true, "overview", language)}#tool-activity`; } @@ -3473,8 +3473,8 @@ async function renderHtml( ) : activeSection === "projects-tasks" ? t( - "One task hub for schedule, review queue, runtime evidence, and cron execution.", - "一个任务中枢,把排程、审阅队列、运行证据和 Cron 执行放在同一页。", + "Start with schedule and cron execution. Staff can be active from cron or ad-hoc sessions even when there is no tracked task row yet.", + "先看排程和 Cron 执行。员工显示在工作,可能只是 Cron 或临时会话在跑,不一定已经落成可跟踪的任务条目。", ) : sectionMeta.blurb; const needsSessionPreview = activeSection === "projects-tasks" || activeSection === "overview"; @@ -3640,8 +3640,8 @@ async function renderHtml( `${t("Runtime issues", "运行异常")} ${runtimeIssueCount}`, `${t("Budget risks", "预算风险")} ${budgetRiskCount}`, ].join(" · "); - const focusHref = `${buildHomeHref({ quick: "all" }, options.compactStatusStrip, "projects-tasks", options.language, options.usageView)}#task-decision-center`; - const currentTaskHealthHref = `${buildHomeHref({ quick: "all" }, true, "projects-tasks", options.language, options.usageView)}#task-lane`; + const focusHref = `${buildHomeHref({ quick: "all" }, options.compactStatusStrip, "projects-tasks", options.language, options.usageView)}#tracked-task-view`; + const currentTaskHealthHref = `${buildHomeHref({ quick: "all" }, true, "projects-tasks", options.language, options.usageView)}#tracked-task-view`; const runtimeCronById = new Map(cronOverview.jobs.map((job) => [job.jobId, job])); const catalogMatchedRuntimeIds = new Set(); const catalogCronRows = openclawCronJobs.map((job) => { @@ -4932,128 +4932,162 @@ async function renderHtml( `; const teamUnifiedSection = teamSection; + const hasTrackedTaskPanels = tasks.length > 0 || pendingDecisionCount > 0 || taskCertaintyCards.length > 0; + const trackedTaskDetailsOpen = pendingDecisionCount > 0 || taskCertaintyCards.length > 0; + const trackedTaskSummaryText = hasTrackedTaskPanels + ? t( + `Tracked tasks ${taskCertaintyCards.length} · Follow-up ${pendingDecisionCount}`, + `跟踪任务 ${taskCertaintyCards.length} · 待处理 ${pendingDecisionCount}`, + ) + : t("No tracked task rows yet", "还没有跟踪任务条目"); + const trackedTaskExplanation = hasTrackedTaskPanels + ? t( + "This lower-priority area is only for tracked task rows, decisions, and runtime evidence.", + "这块低优先级区域只看可跟踪任务条目、待处理事项和运行证据。", + ) + : liveSessionCount > 0 + ? t( + "Staff status comes from live sessions. Cron jobs, heartbeat, and ad-hoc sessions can keep agents busy before anything becomes a tracked task row.", + "员工状态来自实时会话。Cron、心跳和临时会话可能已经让智能体在工作,但还没有形成可跟踪的任务条目。", + ) + : t( + "There is no tracked task row visible right now. Start here only when you actually use the task store.", + "当前还没有可见的跟踪任务条目。只有真正使用任务库时,这里才会出现内容。", + ); + const trackedTaskDetailsBody = hasTrackedTaskPanels + ? ` +
+
+
+
+

${escapeHtml(t("Task hub", "任务中枢"))}

+
${escapeHtml(t("One place for tracked tasks, follow-up items, and runtime evidence.", "把可跟踪任务、待处理事项和运行证据放在一起。"))}
+
+
${overviewPrimaryStatus}
+
+ ${taskHubStatCardsHtml} +
+
+
${escapeHtml(t("Current focus", "当前关注"))}
+
${badge(currentTaskHealth)} ${escapeHtml(t("Confirmed live", "已确认在跑"))} ${taskCertaintyStrongCount} · ${escapeHtml(t("Need follow-up", "需跟进"))} ${taskCertaintyFollowupCount} · ${escapeHtml(t("Needs inspection", "需排查"))} ${taskCertaintyWeakCount}
+ ${mappingTaskHint ? `
${escapeHtml(mappingTaskHint)}
` : ""} +
+ +
+
+
+
+

${escapeHtml(t("Waiting for your decision", "等待你决策"))}

+
${badge(pendingDecisionCount > 0 ? "warn" : "ok", pendingDecisionCount > 0 ? t("Queue active", "队列活跃") : t("Clear", "已清空"))}
+
+
${escapeHtml(t("Pending decisions", "待处理事项"))} ${pendingDecisionCount} · ${escapeHtml(t("Approvals", "审批"))} ${pendingApprovalsCount} · ${escapeHtml(t("Unacked alerts", "未确认告警"))} ${actionQueue.counts.unacked}
+ ${taskDecisionPreviewHtml} +
+
+ ${taskExecutionChainSection} +
+
+

${escapeHtml(t("Task lanes", "任务泳道"))}

+
${escapeHtml(t("Current focus", "当前关注"))}:${escapeHtml(quickFilterLabel(effectiveQuick, options.language))}
+ ${ + controlCenterMappingTasks.length > 0 + ? `
${escapeHtml(t(`${controlCenterMappingTasks.length} board-only mapping examples are hidden because they are not real execution tasks.`, `已隐藏 ${controlCenterMappingTasks.length} 个看板映射样例(非真实执行任务)。`))}
` + : "" + } +
${quickFilters}
+
+ + + + + +
+ + +
+
+ + +
+
+ + +
+
+ + ${escapeHtml(t("Clear filters", "清空筛选"))} +
+
+ ${taskBoard} +
+

${escapeHtml(t("Task groups (native view)", "任务分组列表(原生视图)"))}

+ ${taskGroupedListHtml} + ${ + controlCenterMappingTasks.length === 0 + ? "" + : `
+ ${escapeHtml(t("Open board mapping examples (non-executing)", "查看看板映射样例(不执行任务)"))} +
+ + + ${mappingTaskRows} +
${escapeHtml(t("Example task", "样例任务"))}${escapeHtml(t("Label", "标签"))}${escapeHtml(t("Status", "状态"))}
+
+
` + } +
+
+
+

${escapeHtml(t("Project lanes", "项目泳道"))}

+ ${projectBoard} +
+
+

${escapeHtml(t("Live activity feed", "实时活动流"))}

+
${escapeHtml(t("Use this to confirm what OpenClaw and its sub-agents are doing right now.", "用于确认 OpenClaw 与子智能体当前正在执行什么。"))}
+
    ${replayMomentsRows}
+
+
+
+
+ ${escapeHtml(t(`Task table (raw detail, ${tasks.length}/${allTasks.length})`, `任务表格(原始明细,${tasks.length}/${allTasks.length})`))} +
+ + + ${taskRows} +
${escapeHtml(t("Project", "项目"))}${escapeHtml(t("Task", "任务"))}${escapeHtml(t("Title", "标题"))}${escapeHtml(t("Status", "状态"))}${escapeHtml(t("Agent", "智能体"))}${escapeHtml(t("Due", "截止"))}${escapeHtml(t("Updated", "更新时间"))}
+
+
+ ` + : `
${escapeHtml(trackedTaskExplanation)}
`; const projectsSection = ` -
-
-
-
-

${escapeHtml(t("Task hub", "任务中枢"))}

-
${escapeHtml(t("One place for schedule, current tasks, follow-up items, and cron execution.", "把排程、当前任务、待处理事项和 Cron 执行放在同一页。"))}
-
-
${overviewPrimaryStatus}
-
- ${taskHubStatCardsHtml} -
-
-
${escapeHtml(t("Current focus", "当前关注"))}
-
${badge(currentTaskHealth)} ${escapeHtml(t("Confirmed live", "已确认在跑"))} ${taskCertaintyStrongCount} · ${escapeHtml(t("Need follow-up", "需跟进"))} ${taskCertaintyFollowupCount} · ${escapeHtml(t("Needs inspection", "需排查"))} ${taskCertaintyWeakCount}
- ${mappingTaskHint ? `
${escapeHtml(mappingTaskHint)}
` : ""} -
- -
-
-
-
-

${escapeHtml(t("Waiting for your decision", "等待你决策"))}

-
${badge(pendingDecisionCount > 0 ? "warn" : "ok", pendingDecisionCount > 0 ? t("Queue active", "队列活跃") : t("Clear", "已清空"))}
-
-
${escapeHtml(t("Pending decisions", "待处理事项"))} ${pendingDecisionCount} · ${escapeHtml(t("Approvals", "审批"))} ${pendingApprovalsCount} · ${escapeHtml(t("Unacked alerts", "未确认告警"))} ${actionQueue.counts.unacked}
- ${taskDecisionPreviewHtml} -
-
- ${taskExecutionChainSection}
${calendarSection} ${cronExecutionSection}
-
-
-

${escapeHtml(t("Task lanes", "任务泳道"))}

-
${escapeHtml(t("Current focus", "当前关注"))}:${escapeHtml(quickFilterLabel(effectiveQuick, options.language))}
- ${ - controlCenterMappingTasks.length > 0 - ? `
${escapeHtml(t(`${controlCenterMappingTasks.length} board-only mapping examples are hidden because they are not real execution tasks.`, `已隐藏 ${controlCenterMappingTasks.length} 个看板映射样例(非真实执行任务)。`))}
` - : "" - } -
${quickFilters}
-
- - - - - -
- - -
-
- - -
-
- - -
-
- - ${escapeHtml(t("Clear filters", "清空筛选"))} -
-
- ${taskBoard} -
-

${escapeHtml(t("Task groups (native view)", "任务分组列表(原生视图)"))}

- ${taskGroupedListHtml} - ${ - controlCenterMappingTasks.length === 0 - ? "" - : `
- ${escapeHtml(t("Open board mapping examples (non-executing)", "查看看板映射样例(不执行任务)"))} -
- - - ${mappingTaskRows} -
${escapeHtml(t("Example task", "样例任务"))}${escapeHtml(t("Label", "标签"))}${escapeHtml(t("Status", "状态"))}
-
-
` - } -
-
-
-

${escapeHtml(t("Project lanes", "项目泳道"))}

- ${projectBoard} -
-
-

${escapeHtml(t("Live activity feed", "实时活动流"))}

-
${escapeHtml(t("Use this to confirm what OpenClaw and its sub-agents are doing right now.", "用于确认 OpenClaw 与子智能体当前正在执行什么。"))}
-
    ${replayMomentsRows}
-
-
-
-
- ${escapeHtml(t(`Task table (raw detail, ${tasks.length}/${allTasks.length})`, `任务表格(原始明细,${tasks.length}/${allTasks.length})`))} +
+ ${escapeHtml(t("Tracked tasks and follow-up", "跟踪任务与跟进"))}
- - - ${taskRows} -
${escapeHtml(t("Project", "项目"))}${escapeHtml(t("Task", "任务"))}${escapeHtml(t("Title", "标题"))}${escapeHtml(t("Status", "状态"))}${escapeHtml(t("Agent", "智能体"))}${escapeHtml(t("Due", "截止"))}${escapeHtml(t("Updated", "更新时间"))}
+
${escapeHtml(trackedTaskSummaryText)}
+
${escapeHtml(trackedTaskExplanation)}
+ ${trackedTaskDetailsBody}
`; @@ -11471,7 +11505,7 @@ function buildParitySurfaceRows(input: { { id: "approvals", name: "审批与决策队列", - route: "/?section=projects-tasks&quick=attention#task-lane", + route: "/?section=projects-tasks&quick=attention#tracked-task-view", status: "enabled", detail: `待审批 ${input.pendingApprovals} 条,决策队列可用。`, }, @@ -12585,7 +12619,7 @@ function renderTaskDetailPage(input: { : `
${escapeHtml(pickUiText(language, "No session evidence is visible yet.", "当前还没有可显示的会话证据。"))}
` } - + `; diff --git a/test/ui-render-smoke.test.ts b/test/ui-render-smoke.test.ts index d2276ba..9b08e1d 100644 --- a/test/ui-render-smoke.test.ts +++ b/test/ui-render-smoke.test.ts @@ -162,16 +162,21 @@ test("legacy mission-control routes resolve to dashboard sections", async () => assert.equal(resolveDashboardSection(new URLSearchParams("section=replay-audit")), "overview"); }); -test("tasks section includes merged schedule and board content", async () => { +test("tasks section prioritizes schedule and cron before tracked task detail", async () => { const source = await readFile("src/ui/server.ts", "utf8"); assert(source.includes('
')); - assert(source.includes('id="task-execution-chain"')); assert(source.includes('id="task-timeline"')); assert(source.includes('t("Today and next schedule", "今日与下一批排程")')); + assert(source.includes('
')); + assert(source.includes('id="tracked-task-view"')); + assert(source.includes('const hasTrackedTaskPanels = tasks.length > 0 || pendingDecisionCount > 0 || taskCertaintyCards.length > 0;')); + assert(source.includes('t("Tracked tasks and follow-up", "跟踪任务与跟进")')); + assert(source.includes("Start with schedule and cron execution. Staff can be active from cron or ad-hoc sessions even when there is no tracked task row yet.")); + assert(source.includes("先看排程和 Cron 执行。员工显示在工作,可能只是 Cron 或临时会话在跑,不一定已经落成可跟踪的任务条目。")); assert(source.includes('
')); assert(source.includes('id="task-decision-center"')); - assert(source.includes('
')); assert(source.includes('
')); + assert(source.includes('id="task-execution-chain"')); assert(source.includes('t("Execution chain", "执行链")')); assert(source.includes('Accepted and spawned child sessions')); assert(source.includes('if (options.section === "calendar") sectionBody = projectsSection;')); @@ -193,7 +198,7 @@ test("global visibility card keeps plain-language EN/ZH copy for four key signal assert(en.includes("Active heartbeat checks: 1.")); assert(en.includes('/?compact=1&section=overview&lang=en&quick=all#cron-health')); assert(en.includes('/?compact=1&section=overview&lang=en&quick=all#heartbeat-health')); - assert(en.includes('/?compact=1&section=projects-tasks&lang=en&quick=all#task-lane')); + assert(en.includes('/?compact=1&section=projects-tasks&lang=en&quick=all#tracked-task-view')); assert(en.includes('/?compact=1&section=overview&lang=en&quick=all#tool-activity')); assert(!en.includes('href="/cron"')); assert(!en.includes('href="/sessions"')); @@ -215,7 +220,7 @@ test("global visibility card keeps plain-language EN/ZH copy for four key signal assert(!zh.includes("Tasks in progress:")); assert(zh.includes('/?compact=1&section=overview&lang=zh&quick=all#cron-health')); assert(zh.includes('/?compact=1&section=overview&lang=zh&quick=all#heartbeat-health')); - assert(zh.includes('/?compact=1&section=projects-tasks&lang=zh&quick=all#task-lane')); + assert(zh.includes('/?compact=1&section=projects-tasks&lang=zh&quick=all#tracked-task-view')); assert(zh.includes('/?compact=1&section=overview&lang=zh&quick=all#tool-activity')); assert(!zh.includes('href="/cron"')); assert(!zh.includes('href="/sessions"')); @@ -323,7 +328,7 @@ test("dashboard keeps global visibility as overview-only block", async () => { assert(source.includes("replayPreview.stats.timeline.total")); assert(source.includes('t("Replay activity", "活动回放")')); assert(source.includes('t("Approval requests", "审批请求")')); - assert(source.includes('route: "/?section=projects-tasks&quick=attention#task-lane"')); + assert(source.includes('route: "/?section=projects-tasks&quick=attention#tracked-task-view"')); assert(source.includes('route: "/audit"')); assert(source.includes('route: "/digest/latest"')); assert(source.includes("void primeUiRenderCaches(toolClient);"));