diff --git a/Agent.md b/Agent.md index 6c8c0ce..b38454f 100644 --- a/Agent.md +++ b/Agent.md @@ -93,7 +93,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design: pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg ``` -Python: `uv run pytest tests/ -v` (572) — import check: `uv run python -c "from emrg.client.app import run_client"` +Python: `uv run pytest tests/ -v` (573) — import check: `uv run python -c "from emrg.client.app import run_client"` GUI: `cd emrg/gui && npm test` (93: 22 daemon_client + 22 app-commands + 24 renderer smoke + 15 i18n + 7 integration + 3 commands) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js` CI: `uv run pytest` + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文) Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响) diff --git a/README.md b/README.md index 7c40dc4..e845322 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ EMRG doesn't just keep up — it catches up on its own. git clone https://github.com/argszero/emrg.git cd emrg uv sync # install deps -uv run pytest tests/ -v # run tests (currently 572 items) +uv run pytest tests/ -v # run tests (currently 573 items) uv run python -m emrg # launch TUI # CI includes actionlint workflow gate (#444): workflow parse errors fail PR CI diff --git a/emrg/server/daemon.py b/emrg/server/daemon.py index 64dfa57..099a5f2 100644 --- a/emrg/server/daemon.py +++ b/emrg/server/daemon.py @@ -286,6 +286,25 @@ async def serve(self) -> None: except OSError: pass + def _evolution_count(self) -> int: + """Total completed evolution cycles across scheduler handlers. + + The daemon's own ``self.evolutions`` list is a legacy from the + pre-scheduler BackgroundThread design (#95) and is never appended; + the scheduler's handlers own the real per-cycle logs. Aggregate from + the scheduler, falling back to the legacy list only when the + scheduler is unavailable (e.g. test harnesses mock it away). + """ + sched = getattr(self, "_scheduler", None) + if sched is not None: + try: + total = sched.total_evolutions() + if isinstance(total, int): + return total + except Exception: + pass + return len(self.evolutions) + async def _handle_client(self, ws) -> None: """Handle a single WebSocket client connection. @@ -907,7 +926,7 @@ async def _process_message( "branch_id": self.identity.branch_id, }, uptime_seconds=max(0, elapsed), - evolution_count=len(self.evolutions), + evolution_count=self._evolution_count(), ) await self._send(ws, { "type": "pong", @@ -1165,13 +1184,13 @@ async def _process_message( continue await self._send(ws, { "type": "evolution_summary", - "count": len(self.evolutions), + "count": self._evolution_count(), "recent": recent, }) except OSError: await self._send(ws, { "type": "evolution_summary", - "count": len(self.evolutions), + "count": self._evolution_count(), "recent": [], }) diff --git a/emrg/server/scheduler.py b/emrg/server/scheduler.py index 409a694..1e851a0 100644 --- a/emrg/server/scheduler.py +++ b/emrg/server/scheduler.py @@ -874,6 +874,10 @@ def list_tasks(self) -> list[dict]: """Return status for all running handlers.""" return [handler.status() for handler in self._handlers] + def total_evolutions(self) -> int: + """Total completed evolution cycles across all running handlers.""" + return sum(len(handler.evolutions) for handler in self._handlers) + async def wait_all(self) -> None: """Wait for all handler coroutines to finish (after cancel).""" for coro in self._coros: diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py index 11109c6..c39bd11 100644 --- a/tests/test_scheduler.py +++ b/tests/test_scheduler.py @@ -452,6 +452,23 @@ def test_evolution_handler_default_owner(): assert handler._repo_url == "https://github.com/argszero/emrg.git" +def test_task_scheduler_total_evolutions(): + """total_evolutions sums per-handler evolution log counts.""" + from emrg.protocol import EvolutionLog + + sched = TaskScheduler(InstanceIdentity()) + h1 = EvolutionHandler(name="a", config={}, interval=60, identity=InstanceIdentity()) + h2 = EvolutionHandler(name="b", config={}, interval=60, identity=InstanceIdentity()) + sched._handlers = [h1, h2] + + assert sched.total_evolutions() == 0 + h1.evolutions.append(EvolutionLog(timestamp="t1")) + h1.evolutions.append(EvolutionLog(timestamp="t2")) + assert sched.total_evolutions() == 2 + h2.evolutions.append(EvolutionLog(timestamp="t3")) + assert sched.total_evolutions() == 3 + + def test_paper_template_renders_with_context(): """paper_prompt.md renders without Jinja2 errors (seq/uptime placeholders).""" import jinja2