Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions emrg/gui/renderer/js/dialogs.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -719,17 +719,17 @@ const Dialogs = (() => {
for (const r of runs) {
const row = el("div", { class: "task-run-row" });
row.appendChild(el("span", { class: "task-run-time" }, formatRelativeTime(r.timestamp)));
let done = (typeof r.summary === "string" && r.summary) ? r.summary : "";
// rant 2026-08-20T10:58:55:字段统一 work(原 summary/done)
let done = (typeof r.work === "string" && r.work) ? r.work : "";
row.appendChild(el("span", { class: "task-run-done" }, done || "-"));
const flagCell = el("span", { class: "task-run-flag" });
if (r.recommend_slowdown) {
flagCell.appendChild(el("span", { class: "task-badge task-run-badge-warn" }, _t("app.taskRunThrottle")));
} else if (r.meaningful === false) {
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 : "";
// rant 2026-08-19T18:25:14:原因列 —— 降频判断的自然语言理由(vibe check
// slowdown_reason,rant 2026-08-20T10:58:55 改名)。
let reason = (typeof r.slowdown_reason === "string" && r.slowdown_reason) ? r.slowdown_reason : "";
row.appendChild(el("span", { class: "task-run-reason" }, reason || "-"));
wrap.appendChild(row);
}
Expand DownExpand Up@@ -787,9 +787,11 @@ const Dialogs = (() => {
} else {
meta.appendChild(el("span", { class: "task-meta-item" }, _t("app.taskNoRunYet")));
}
// rant 2026-08-20T10:58:55:降频徽标改为只认 heartbeat_active(不再用
// empty_cycles)—— 文案「已降频 · heartbeat {m}s」(app.taskThrottled)。
const sat = (t.saturation && typeof t.saturation === "object") ? t.saturation : null;
if (sat && sat.empty_cycles > 0 && sat.heartbeat_active) {
meta.appendChild(el("span", { class: "task-badge task-saturation-badge" }, _t("app.taskSaturation", { n: sat.empty_cycles, m: formatCountdown(sat.heartbeat_interval) })));
if (sat && sat.heartbeat_active) {
meta.appendChild(el("span", { class: "task-badge task-saturation-badge" }, _t("app.taskThrottled", { m: formatCountdown(sat.heartbeat_interval) })));
}
row.appendChild(meta);
const actions = el("span", { class: "task-actions" });
Expand Down
6 changes: 2 additions & 4 deletions emrg/gui/renderer/js/i18n.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -410,14 +410,13 @@ const I18N = (() => {
"app.taskRunningBadge": "运行中",
"app.taskLastRun": "上次运行:{n}",
"app.taskNoRunYet": "尚未运行",
"app.taskSaturation": "空转 {n} 轮,降频至 {m}s heartbeat",
"app.taskThrottled": "已降频 · heartbeat {m}s",
"app.taskRunsEmpty": "暂无运行记录",
"app.taskRunsColTime": "时间",
"app.taskRunsColDone": "干了什么",
"app.taskRunsColThrottle": "降频",
"app.taskRunsColReason": "原因",
"app.taskRunThrottle": "建议降频",
"app.taskRunIdle": "空转",
"app.deletedSwitch": "这个对话已被删除,已帮你切到最近的对话。",
"app.switched": "已切换对话。",
"app.sessionDisconnected": "该会话连接已断开,正在自动重连…",
Expand DownExpand Up@@ -840,14 +839,13 @@ const I18N = (() => {
"app.taskRunningBadge": "running",
"app.taskLastRun": "last run: {n}",
"app.taskNoRunYet": "never ran",
"app.taskSaturation": "{n} empty cycles, throttled to {m}s heartbeat",
"app.taskThrottled": "throttled · {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.",
"app.switched": "Conversation switched.",
"app.sessionDisconnected": "This session's connection is lost — reconnecting automatically…",
Expand Down
54 changes: 29 additions & 25 deletions emrg/gui/test/renderer.smoke.test.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -2651,17 +2651,17 @@ test("rant 21:32:32:任务卡点击展开最近运行子表(时间/干了什
{
name: "emrg-task", type: "evolution", config: { project: "emrg" },
interval: 60, enabled: true, last_run_at: "2026-08-18T10:00:00",
last_cycle_summary: "修了双实例根因",
saturation: { heartbeat_interval: 480, heartbeat_active: true },
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, 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, reason: "" },
{ timestamp: "2026-08-18T08:00:00", summary: "NTE",
impact: ["cycle-ts-complete"], meaningful: false,
recommend_slowdown: true, tool_count: 0, reason: "empty cycles, no value" },
{ timestamp: "2026-08-18T10:00:00", work: "修了双实例根因,提交 PR #854",
impact: ["cycle-ts-complete", "tools-executed=26"],
recommend_slowdown: false, tool_count: 26, slowdown_reason: "" },
{ timestamp: "2026-08-18T09:00:00", work: "",
impact: ["cycle-ts-complete", "tools-executed=3"],
recommend_slowdown: false, tool_count: 3, slowdown_reason: "" },
{ timestamp: "2026-08-18T08:00:00", work: "NTE",
impact: ["cycle-ts-complete"], recommend_slowdown: true, tool_count: 0,
slowdown_reason: "长期无产出" },
],
},
{ name: "fresh-task", type: "sync", config: { project: "docs" }, interval: 3600, enabled: true },
Expand All@@ -2680,22 +2680,25 @@ test("rant 21:32:32:任务卡点击展开最近运行子表(时间/干了什
await tick();
const detail = vm.runInContext(`document.getElementById("task-list").children[0].querySelector(".task-run-detail")`, ctx);
assert.strictEqual(detail.classList.contains("hidden"), false, "点击后子表应展开");
// 第一行 run:Agent 总结展示(自然语言)
// 第一行 run:Agent 总结展示(自然语言 work,rant 2026-08-20T10:58:55 字段统一
const doneTxt = vm.runInContext(`document.getElementById("task-list").children[0].querySelectorAll(".task-run-done")[0].textContent`, ctx);
assert.strictEqual(doneTxt, "修了双实例根因,提交 PR #854", `子表应显示 Agent 总结,实际: ${doneTxt}`);
// 降频徽章:recommend_slowdown=true → 建议降频;meaningful=false → 空转
// 降频徽章:recommend_slowdown=true → 建议降频(idle 空转徽章已删除,rant 10:58:55)
const warnCount = vm.runInContext(`document.getElementById("task-list").children[0].querySelectorAll(".task-run-badge-warn").length`, ctx);
assert.ok(warnCount >= 1, "recommend_slowdown=true 应显示建议降频徽章");
const idleCount = vm.runInContext(`document.getElementById("task-list").children[0].querySelectorAll(".task-run-badge-idle").length`, ctx);
assert.ok(idleCount >= 1, "meaningful=false 应显示空转徽章");
// 无 summary → 显示 "-"(rant 2026-08-19T07:06:45 宿主定稿:不再 fallback impact 机器串)
assert.strictEqual(idleCount, 0, "meaningful 已删除,不应再出现空转徽章");
// 一级卡片:heartbeat_active → 已降频徽章(app.taskThrottled,rant 10:58:55)
const satBadge = vm.runInContext(`document.getElementById("task-list").children[0].querySelectorAll(".task-saturation-badge").length`, ctx);
assert.strictEqual(satBadge, 1, "saturation.heartbeat_active=true 应显示已降频徽章");
// 无 work → 显示 "-"(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 显示 "-"
assert.strictEqual(doneTexts[1], "-", `无 work 应显示 "-",实际: ${doneTexts[1]}`);
// rant 2026-08-19T18:25:14:原因列 —— 有 slowdown_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]}`);
assert.strictEqual(reasonTexts[0], "-", `无 slowdown_reason 应显示 "-",实际: ${reasonTexts[0]}`);
assert.strictEqual(reasonTexts[1], "-", `无 slowdown_reason 应显示 "-",实际: ${reasonTexts[1]}`);
assert.strictEqual(reasonTexts[2], "长期无产出", `原因列应显示降频原因,实际: ${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}`);
Expand DownExpand Up@@ -2921,11 +2924,10 @@ test("rant 10:45:52:任务行显示上次执行元信息 + 降频标识(rant
{ name: "with-run", type: "evolution", running: false, interval: 1800,
next_run_in_seconds: 43,
last_run_at: new Date(nowMs - 5 * 60_000).toISOString(),
last_cycle_summary: "tools-executed=24, cycle-complete",
saturation: { empty_cycles: 3, threshold: 5, heartbeat_interval: 7200, heartbeat_active: true } },
saturation: { heartbeat_interval: 7200, heartbeat_active: true } },
{ name: "never-run", type: "evolution", running: false, interval: 3600,
next_run_in_seconds: null, enabled: true, last_run_at: null, last_cycle_summary: null,
saturation: { empty_cycles: 0, threshold: 5, heartbeat_interval: 7200, heartbeat_active: false } },
next_run_in_seconds: null, enabled: true, last_run_at: null,
saturation: { heartbeat_interval: 7200, heartbeat_active: false } },
],
listProjects: async () => [],
});
Expand All@@ -2943,9 +2945,11 @@ test("rant 10:45:52:任务行显示上次执行元信息 + 降频标识(rant
assert.ok(rows[0].includes("上次运行:5m ago"), `运行时间相对显示:${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]}`);
// rant 2026-08-20T10:58:55:降频徽标只认 heartbeat_active —— 「已降频 · heartbeat {m}s」
// (7200s → 2h00m;不再有 empty_cycles "空转 N 轮" 文案)
assert.ok(rows[0].includes("已降频 · heartbeat 2h00m"), `降频徽标:${rows[0]}`);
assert.ok(rows[1].includes("尚未运行"), `未运行提示:${rows[1]}`);
assert.ok(!rows[1].includes("空转"), `无降频时无徽标:${rows[1]}`);
assert.ok(!rows[1].includes("已降频"), `无降频时无徽标:${rows[1]}`);
});

test("rant 2026-08-14T15:41:52:快速点击添加任务 —— 元数据未加载完也填充下拉 + 保存成功", async () => {
Expand Down
14 changes: 6 additions & 8 deletions emrg/protocol.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -128,15 +128,13 @@ class EvolutionLog:
operations: list[str] = field(default_factory=list)
upstream_contribution: Optional[dict] = None
# rant 2026-08-18T21:32:32: the agent's own natural-language summary of
# what meaningful work was done this cycle (from the vibe check "done"
# field) + the vibe result flags, surfaced in GUI task recent-runs.
summary: str = ""
meaningful: Optional[bool] = None
# what was done this cycle, surfaced in GUI task recent-runs.
# Rant 2026-08-20T10:58:55 (host design-finalized): `summary` → `work`;
# `meaningful` deleted (agent no longer self-rates value);
# `reason` → `slowdown_reason` (only populated when recommend_slowdown).
work: str = ""
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 = ""
slowdown_reason: str = ""
tool_count: int = 0


Expand Down
22 changes: 12 additions & 10 deletions emrg/server/daemon.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -1218,13 +1218,16 @@ async def _task_vibe_check(self, task_name: str, session_id: str, cwd: str,
prompt: str = "", completion_summary: str = "") -> dict:
"""Structured LLM ask (Ask mode, no tools) about a finished task cycle.

Asks whether a just-finished scheduled task produced meaningful value.
The agent must answer in strict JSON:
``{"meaningful": bool, "recommend_slowdown": bool, "reason": str, "done": str}``.
The agent must answer in strict JSON (rant 2026-08-20T10:58:55,
host design-finalized — fields unified to 3, meaningful deleted):
``{"work": str, "recommend_slowdown": bool, "slowdown_reason": str}``.

``done`` (rant 2026-08-18T21:32:32) is a natural-language summary of
what meaningful work was done this cycle, for humans to read in the
GUI task recent-runs table. Old models / old parsing omit it → "".
``work`` (rant 2026-08-18T21:32:32 → renamed from ``done``) is a
natural-language summary of what was done this cycle, for humans to
read in the GUI task recent-runs table. ``recommend_slowdown`` is now
the ONLY slowdown switch: true → next run at heartbeat interval,
false → normal interval. ``slowdown_reason`` is required only when
recommend_slowdown is true. Old models / old parsing omit fields → "".

Rant 2026-08-19T07:10:40 (root cause): the done frame used to carry an
empty ``content``, so ``completion_summary`` here was empty and the
Expand All@@ -1242,7 +1245,7 @@ async def _task_vibe_check(self, task_name: str, session_id: str, cwd: str,
``prompts/vibe_check.j2`` (same live-reload mechanism as system.j2).

Raises on any failure (caller sends ``ok: false``); the scheduler
conservatively leaves its empty-cycle counter unchanged then.
conservatively leaves its slowdown state unchanged then.
"""
template = _get_jinja_env().get_template("vibe_check.j2")
system = template.render(
Expand DownExpand Up@@ -1285,10 +1288,9 @@ async def _task_vibe_check(self, task_name: str, session_id: str, cwd: str,
if not isinstance(data, dict):
raise ValueError("vibe check response is not a JSON object")
return {
"meaningful": bool(data.get("meaningful")),
"work": str(data.get("work", ""))[:500],
"recommend_slowdown": bool(data.get("recommend_slowdown")),
"reason": str(data.get("reason", ""))[:200],
"done": str(data.get("done", ""))[:500],
"slowdown_reason": str(data.get("slowdown_reason", ""))[:300],
}

def _build_system_prompt(self, session: Session | None = None) -> str:
Expand Down
29 changes: 14 additions & 15 deletions emrg/server/prompts/vibe_check.j2
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,27 +5,26 @@
Rant 2026-08-19T10:15:43 (host-finalized): the PRIMARY evidence is the
task's own session history — the daemon loads it by the task's fixed
session_id and passes it as the chat messages that follow this system
message. prompt/completion_summary below are only auxiliary context. #}
message. prompt/completion_summary below are only auxiliary context.
Rant 2026-08-20T10:58:55 (host design-finalized): fields unified to
work / recommend_slowdown / slowdown_reason — the agent no longer
self-rates "meaningful"; slowdown is decided solely by recommend_slowdown. #}
你是 EMRG 定时任务调度助手。刚完成一次定时任务「{{ task_name }}」。以下是该定时任务的
**完整会话记录**(任务要求 + Agent 的实际执行过程:工具调用、分析、memory 写入、最终回复)。
请基于这些**真实记忆**做总结与判断:只有会话记录显示确实没做任何事(无回复、无动作)
才算空转;不要因为"没有 commit/PR"就武断判为无产出——工具执行、分析、排查、写 memory、
决策等具体动作都是实际工作。
请基于这些**真实记忆**做总结与判断:工具执行、分析、排查、写 memory、决策等具体动作都是实际工作;
不要因为"没有 commit/PR"就武断判为无产出。

请用 JSON 严格回答(不要任何其他文字),格式:
{"meaningful": true|false, "recommend_slowdown": true|false, "reason": "一句话原因", "done": "这次干了哪些有意义有价值的事"}
{"work": "本次具体做了什么(自然语言,可为空串)", "recommend_slowdown": true|false, "slowdown_reason": "一句话原因"}

字段要求:
- meaningful:这轮是否对项目产生了有意义的价值。只要会话记录显示执行了
具体动作(工具调用、分析、排查、写 memory、决策、产出等)即为 true;只有确实
空转(无回复、无任何动作)才为 false。
- recommend_slowdown:若本任务长期无有意义产出,是否建议降频省 token
(true=建议降低检查频率)。
- reason:简短中文原因(给降频判断用),引用实际信息说明依据。
- done:从会话记录中**提炼**这次具体做了什么(工具动作、产出、分析,
自然语言列举,如:执行了 N 次工具调用,git fetch 检查了 X、分析了 Y、写了
memory Z)。**只要会话记录有实质内容就必须写具体动作**;会话记录确实为空、
纯空转才写空字符串 ""。
- work:如实汇报本次具体做了什么(工具动作、产出、分析,自然语言列举,如:执行了 N 次
工具调用,git fetch 检查了 X、分析了 Y、写了 memory Z)。**只要会话记录有实质内容就必须写
具体动作**;会话记录确实为空、确实没干才写空字符串 "" 或"没干",诚实即可。
- recommend_slowdown:是否建议降频省 token(true=下次以慢速 heartbeat 间隔运行,
false=保持正常频率)。由你根据本任务长期是否值得高频运行判断。
- slowdown_reason:仅 recommend_slowdown=true 时必填;否则写空字符串 ""。简短中文原因,
引用实际信息说明依据。

任务名称:{{ task_name }}
(辅助上下文)任务要求:{{ prompt }}
Expand Down
Loading
Loading