diff --git a/emrg/gui/renderer/css/components.css b/emrg/gui/renderer/css/components.css index be9f671a..e37ed069 100644 --- a/emrg/gui/renderer/css/components.css +++ b/emrg/gui/renderer/css/components.css @@ -1050,7 +1050,7 @@ dialog::backdrop { .task-run-head, .task-run-row { display: grid; - grid-template-columns: 72px 1fr 92px; + grid-template-columns: 72px 1fr 92px 1fr; gap: 6px; font-size: var(--fs-aux); min-width: 0; @@ -1072,6 +1072,12 @@ dialog::backdrop { white-space: nowrap; color: var(--text-2); } +.task-run-reason { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--text-3); +} .task-run-flag { display: flex; gap: 4px; diff --git a/emrg/gui/renderer/js/dialogs.js b/emrg/gui/renderer/js/dialogs.js index 4cecffab..1a5fe2e4 100644 --- a/emrg/gui/renderer/js/dialogs.js +++ b/emrg/gui/renderer/js/dialogs.js @@ -714,6 +714,7 @@ const Dialogs = (() => { head.appendChild(el("span", {}, _t("app.taskRunsColTime"))); head.appendChild(el("span", {}, _t("app.taskRunsColDone"))); head.appendChild(el("span", {}, _t("app.taskRunsColThrottle"))); + head.appendChild(el("span", {}, _t("app.taskRunsColReason"))); wrap.appendChild(head); for (const r of runs) { const row = el("div", { class: "task-run-row" }); @@ -727,6 +728,9 @@ const Dialogs = (() => { flagCell.appendChild(el("span", { class: "task-badge task-run-badge-idle" }, _t("app.taskRunIdle"))); } row.appendChild(flagCell); + // rant 2026-08-19T18:25:14:原因列 —— 降频/空转判断的自然语言理由(vibe check reason)。 + let reason = (typeof r.reason === "string" && r.reason) ? r.reason : ""; + row.appendChild(el("span", { class: "task-run-reason" }, reason || "-")); wrap.appendChild(row); } return wrap; @@ -772,15 +776,14 @@ const Dialogs = (() => { if (t.interval != null) hints.push(_t("app.taskInterval", { n: t.interval ?? "-" })); if (t.enabled === false) hints.push(_t("app.taskDisabled")); row.appendChild(el("span", { class: "task-hint" }, hints.join(" · "))); - // rant 2026-08-18T10:45:52:上次执行维度 —— 最近一次运行时间 + 摘要 + 降频(saturation)标识。 - // 数据来自 daemon list_tasks → handler.status()(last_run_at/last_cycle_summary/saturation)。 + // rant 2026-08-18T10:45:52:上次执行维度 —— 最近一次运行时间 + 降频(saturation)标识。 + // rant 2026-08-19T18:25:14:一级列表不再显示"干了什么"(last_cycle_summary)—— + // 执行记录(时间/工作/降频建议/原因)统一由点击展开的二级列表(buildTaskRunDetail)呈现。 + // 数据来自 daemon list_tasks → handler.status()(last_run_at/saturation/recent_runs)。 const meta = el("div", { class: "task-meta" }); if (t.last_run_at) { const runTxt = el("span", { class: "task-meta-item" }, _t("app.taskLastRun", { n: formatRelativeTime(t.last_run_at) })); meta.appendChild(runTxt); - if (t.last_cycle_summary) { - meta.appendChild(el("span", { class: "task-meta-item task-meta-summary" }, _t("app.taskLastRunSummary", { n: t.last_cycle_summary }))); - } } else { meta.appendChild(el("span", { class: "task-meta-item" }, _t("app.taskNoRunYet"))); } diff --git a/emrg/gui/renderer/js/i18n.js b/emrg/gui/renderer/js/i18n.js index d9950757..97e24a7a 100644 --- a/emrg/gui/renderer/js/i18n.js +++ b/emrg/gui/renderer/js/i18n.js @@ -409,13 +409,13 @@ const I18N = (() => { "app.taskRunning": "任务 {n} 正在运行中(无法重复触发)", "app.taskRunningBadge": "运行中", "app.taskLastRun": "上次运行:{n}", - "app.taskLastRunSummary": "干了:{n}", "app.taskNoRunYet": "尚未运行", "app.taskSaturation": "空转 {n} 轮,降频至 {m}s heartbeat", "app.taskRunsEmpty": "暂无运行记录", "app.taskRunsColTime": "时间", "app.taskRunsColDone": "干了什么", "app.taskRunsColThrottle": "降频", + "app.taskRunsColReason": "原因", "app.taskRunThrottle": "建议降频", "app.taskRunIdle": "空转", "app.deletedSwitch": "这个对话已被删除,已帮你切到最近的对话。", @@ -839,13 +839,13 @@ const I18N = (() => { "app.taskRunning": "Task {n} is running (cannot re-trigger)", "app.taskRunningBadge": "running", "app.taskLastRun": "last run: {n}", - "app.taskLastRunSummary": "did: {n}", "app.taskNoRunYet": "never ran", "app.taskSaturation": "{n} empty cycles, throttled to {m}s heartbeat", "app.taskRunsEmpty": "No run records yet", "app.taskRunsColTime": "Time", "app.taskRunsColDone": "What was done", "app.taskRunsColThrottle": "Throttle", + "app.taskRunsColReason": "Reason", "app.taskRunThrottle": "slowdown advised", "app.taskRunIdle": "idle", "app.deletedSwitch": "This conversation was deleted — switched to the most recent one.", diff --git a/emrg/gui/test/renderer.smoke.test.js b/emrg/gui/test/renderer.smoke.test.js index e5891446..9948a358 100644 --- a/emrg/gui/test/renderer.smoke.test.js +++ b/emrg/gui/test/renderer.smoke.test.js @@ -2655,13 +2655,13 @@ test("rant 21:32:32:任务卡点击展开最近运行子表(时间/干了什 recent_runs: [ { timestamp: "2026-08-18T10:00:00", summary: "修了双实例根因,提交 PR #854", impact: ["cycle-ts-complete", "tools-executed=26"], meaningful: true, - recommend_slowdown: false, tool_count: 26 }, + recommend_slowdown: false, tool_count: 26, reason: "meaningful work" }, { timestamp: "2026-08-18T09:00:00", summary: "", impact: ["cycle-ts-complete", "tools-executed=3"], meaningful: false, - recommend_slowdown: false, tool_count: 3 }, + recommend_slowdown: false, tool_count: 3, reason: "" }, { timestamp: "2026-08-18T08:00:00", summary: "NTE", impact: ["cycle-ts-complete"], meaningful: false, - recommend_slowdown: true, tool_count: 0 }, + recommend_slowdown: true, tool_count: 0, reason: "empty cycles, no value" }, ], }, { name: "fresh-task", type: "sync", config: { project: "docs" }, interval: 3600, enabled: true }, @@ -2691,6 +2691,14 @@ test("rant 21:32:32:任务卡点击展开最近运行子表(时间/干了什 // 无 summary → 显示 "-"(rant 2026-08-19T07:06:45 宿主定稿:不再 fallback impact 机器串) const doneTexts = vm.runInContext(`Array.from(document.getElementById("task-list").children[0].querySelectorAll(".task-run-done")).map((n) => n.textContent)`, ctx); assert.strictEqual(doneTexts[1], "-", `无 summary 应显示 "-",实际: ${doneTexts[1]}`); + // rant 2026-08-19T18:25:14:原因列 —— 有 reason 显示原文,无 reason 显示 "-" + const reasonTexts = vm.runInContext(`Array.from(document.getElementById("task-list").children[0].querySelectorAll(".task-run-reason")).map((n) => n.textContent)`, ctx); + assert.strictEqual(reasonTexts[0], "meaningful work", `原因列应显示 reason,实际: ${reasonTexts[0]}`); + assert.strictEqual(reasonTexts[1], "-", `无 reason 应显示 "-",实际: ${reasonTexts[1]}`); + assert.strictEqual(reasonTexts[2], "empty cycles, no value", `原因列应显示降频原因,实际: ${reasonTexts[2]}`); + // rant 2026-08-19T18:25:14:一级列表不再显示"干了什么"(last_cycle_summary) + const primarySummary = vm.runInContext(`document.getElementById("task-list").children[0].querySelectorAll(".task-meta-summary").length`, ctx); + assert.strictEqual(primarySummary, 0, `一级列表不应显示 last_cycle_summary,实际数量: ${primarySummary}`); // 再次点击 → 折叠 await vm.runInContext(`document.getElementById("task-list").children[0].click()`, ctx); await tick(); @@ -2894,7 +2902,7 @@ test("rant 10:36:39:倒计时生命周期 —— 渲染启动 interval、离 assert.strictEqual(win2._intervalCount || 0, s2, "全 running 无倒计时 → 不启动 interval"); }); -test("rant 10:45:52:任务行显示上次执行元信息 + 降频标识", async () => { +test("rant 10:45:52:任务行显示上次执行元信息 + 降频标识(rant 18:25:14:不再显示摘要)", async () => { const nowMs = 1_700_000_000_000; const { ctx, win } = makeSandbox({ listTasks: async () => [ @@ -2921,7 +2929,8 @@ test("rant 10:45:52:任务行显示上次执行元信息 + 降频标识", asyn return name + " => " + metaText + " ## " + satText; })`, ctx); assert.ok(rows[0].includes("上次运行:5m ago"), `运行时间相对显示:${rows[0]}`); - assert.ok(rows[0].includes("干了:tools-executed=24, cycle-complete"), `执行摘要:${rows[0]}`); + // rant 2026-08-19T18:25:14:一级列表不再显示"干了什么"(last_cycle_summary),移入点击展开的二级列表 + assert.ok(!rows[0].includes("干了:"), `一级列表不应显示摘要:${rows[0]}`); assert.ok(rows[0].includes("空转 3 轮"), `降频徽标:${rows[0]}`); assert.ok(rows[1].includes("尚未运行"), `未运行提示:${rows[1]}`); assert.ok(!rows[1].includes("空转"), `无降频时无徽标:${rows[1]}`); diff --git a/emrg/protocol.py b/emrg/protocol.py index e0357535..f026df3d 100644 --- a/emrg/protocol.py +++ b/emrg/protocol.py @@ -133,6 +133,10 @@ class EvolutionLog: summary: str = "" meaningful: Optional[bool] = None recommend_slowdown: bool = False + # rant 2026-08-19T18:25:14: the vibe check's natural-language reason for + # the meaningful/slowdown judgment, surfaced in the GUI task recent-runs + # secondary list (时间/工作/降频建议/原因). + reason: str = "" tool_count: int = 0 diff --git a/emrg/server/scheduler.py b/emrg/server/scheduler.py index c29d24e1..53ebdd43 100644 --- a/emrg/server/scheduler.py +++ b/emrg/server/scheduler.py @@ -454,6 +454,7 @@ def status(self) -> dict: "impact": list(log.impact), "meaningful": log.meaningful, "recommend_slowdown": log.recommend_slowdown, + "reason": log.reason, "tool_count": log.tool_count, }) saturation = { @@ -775,10 +776,15 @@ async def _run_evolution_cycle(self) -> None: summary = "" meaningful = None recommend = False + reason = "" if vibe_result is not None: summary = str(vibe_result.get("done") or "")[:500] meaningful = vibe_result.get("meaningful") recommend = bool(vibe_result.get("recommend_slowdown")) + # rant 2026-08-19T18:25:14: keep the vibe check's reason for the + # meaningful/slowdown judgment so the GUI secondary list can show + # 原因 (time/work/throttle/reason). + reason = str(vibe_result.get("reason") or "")[:300] log = EvolutionLog( timestamp=cycle_ts, @@ -788,6 +794,7 @@ async def _run_evolution_cycle(self) -> None: summary=summary, meaningful=meaningful, recommend_slowdown=recommend, + reason=reason, tool_count=tool_count, ) # Rant 2026-08-19T14:18:40 — no disk archival: the evolution log lives diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py index a0b5f86d..1a0feb6e 100644 --- a/tests/test_scheduler.py +++ b/tests/test_scheduler.py @@ -570,6 +570,7 @@ def test_evolution_handler_status_last_run_fields(): assert r0["impact"] == ["tools-executed=24", "cycle-complete"] assert r0["meaningful"] is None assert r0["recommend_slowdown"] is False + assert r0["reason"] == "" assert r0["tool_count"] == 0 # agent summary preferred over machine impact tags handler.evolutions.append(EvolutionLog( @@ -580,6 +581,7 @@ def test_evolution_handler_status_last_run_fields(): summary="修了 stop_all 双实例根因,提交 PR #854", meaningful=True, recommend_slowdown=False, + reason="meaningful work done", tool_count=5, )) st = handler.status() @@ -587,6 +589,7 @@ def test_evolution_handler_status_last_run_fields(): assert len(st["recent_runs"]) == 2, "recent_runs holds last 5 runs" assert st["recent_runs"][1]["summary"] == "修了 stop_all 双实例根因,提交 PR #854" assert st["recent_runs"][1]["meaningful"] is True + assert st["recent_runs"][1]["reason"] == "meaningful work done" assert st["recent_runs"][1]["tool_count"] == 5 assert st["recent_runs"][0]["meaningful"] is None