From ba65cee9f233f222b91be954dd689b3a505d7450 Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Mon, 31 Aug 2026 14:57:00 +0800 Subject: [PATCH] =?UTF-8?q?emrg:=20session=20=E2=80=94=20recompute=20messa?= =?UTF-8?q?ge=5Fcount=20on=20compact,=20count=20message=20records=20only?= =?UTF-8?q?=20(rant=202026-08-31T14:18:14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Agent.md | 2 +- emrg/session.py | 16 ++++++++++++++- tests/test_session.py | 45 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/Agent.md b/Agent.md index 0f9e8f63..b81071e8 100644 --- a/Agent.md +++ b/Agent.md @@ -119,7 +119,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design: pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg ``` -Python: `uv run pytest tests/ -v` (1180) — import check: `uv run python -c "from emrg.client.app import run_client"` +Python: `uv run pytest tests/ -v` (1183) — import check: `uv run python -c "from emrg.client.app import run_client"` GUI: `cd emrg/gui && npm test` (95: 45 daemon_client + 20 conn-manager + 8 integration + 6 build-config + 7 gui-state + 3 preload-api + 4 boot-contract + 2 theme-guard) — syntax: `node --check main.js preload.js daemon_client.js` Renderer: `cd emrg/gui/renderer && npm run typecheck && npm test` (480: 5 snapshot-store + 9 utils + 3 ErrorBoundary + 2 App smoke + 11 commands + 4 copywriting + 11 i18n + 11 markdown + 17 transcript + 10 TranscriptView + 15 history + 22 composer + 34 Composer + 6 LinkDialog + 12 sidebar + 17 Sidebar + 9 fileTree + 9 FileTree + 16 resultPanel + 8 ResultPanel + 27 workspaceView + 10 WorkspaceView + 10 dialog + 6 Dialog + 9 ConfirmDialog + 9 RenameDialog + 10 dialogLists + 3 HelpDialog + 9 MemoryDialog + 6 SkillsDialog + 9 openSession + 6 WelcomeDialog + 8 OpenSessionDialog + 7 NewSessionDialog + 7 rewind + 8 RewindDialog + 7 GithubDeviceDialog + 15 daemonBridge + 7 DaemonBridgeProvider + 26 Shell + 15 DialogHost + 20 SettingsPanel + 6 TaskFormDialog + 5 RantDialog + 4 vendorMarkdown) + `npm run build` → `renderer/dist/` CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文) diff --git a/emrg/session.py b/emrg/session.py index 0510b25c..788c7086 100644 --- a/emrg/session.py +++ b/emrg/session.py @@ -240,7 +240,12 @@ def append_message(self, record: dict) -> None: with open(self._daily_history_path(), "a", encoding="utf-8") as f: f.write(line) - self._message_count += 1 + # message_count counts message-type records only — tool_result / summary + # records are persisted but do not inflate the user-facing message count + # (rant 2026-08-31T14:18:14: count previously grew for every record, + # including tool_results, and compact never decremented it). + if record.get("type", "message") == "message": + self._message_count += 1 self._updated_at = datetime.now().isoformat() self._save_meta() @@ -450,6 +455,15 @@ def compact(self, summary: str, keep_recent: int = 5) -> int: new_history = [summary_record] + recent self._write_history(new_history) + # Recompute message_count from the surviving records — compact replaces + # the compacted messages with one summary, so the count must shrink with + # them (rant 2026-08-31T14:18:14: previously the count was never + # decremented, inflating the TUI/GUI "msgs" display). Same semantics as + # the rewind handler (daemon.py): count message-type records only. + self._message_count = sum( + 1 for r in new_history if r.get("type", "message") == "message" + ) + self._compact_count += 1 self._last_compact_at = datetime.now().isoformat() self._updated_at = self._last_compact_at diff --git a/tests/test_session.py b/tests/test_session.py index 35cabcaf..02edd4e8 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -400,6 +400,16 @@ def test_append_message_preserves_existing_timestamp(self, tmp_path): records = session._read_history() assert records[0]["timestamp"] == ts + def test_append_tool_result_does_not_increment_count(self, tmp_path): + """tool_result records persist but do not inflate message_count.""" + session = Session.create(tmp_path) + session.append_message({"type": "message", "role": "user", "content": "hi"}) + session.append_message({"type": "tool_result", "tool_call_id": "c1", "content": "ok"}) + session.append_message({"type": "message", "role": "assistant", "content": "done"}) + + assert session.message_count == 2 # user + assistant, tool_result excluded + assert len(session._read_history()) == 3 # all three persisted + def test_append_llm_writes_to_llm_file(self, tmp_path): """append_llm() writes records to llm.jsonl.""" session = Session.create(tmp_path) @@ -484,6 +494,41 @@ def test_compact_updates_meta(self, tmp_path): assert meta["compact_count"] == 1 assert meta["last_compact_at"] is not None + def test_compact_recomputes_message_count(self, tmp_path): + """compact() decrements message_count to match surviving message records. + + Regression for rant 2026-08-31T14:18:14 — the count previously only grew + (append_message incremented for every record incl. tool_results) and + compact never decremented it, inflating the TUI/GUI "msgs" display. + """ + session = Session.create(tmp_path) + for i in range(8): + session.append_message({"type": "message", "role": "user", "content": f"msg {i}"}) + session.append_message({"type": "tool_result", "tool_call_id": f"c{i}", "content": "ok"}) + # 8 user messages + 8 tool_results → count is message-only + assert session.message_count == 8 + + session.compact("summary of first 5", keep_recent=3) + records = session._read_history() + # new_history = [summary] + 3 recent records ([c6, msg7, c7] — keep_recent + # slices raw records, not message-only records) + assert records[0]["type"] == "summary" + surviving_messages = sum(1 for r in records if r.get("type", "message") == "message") + assert session.message_count == surviving_messages + assert session.message_count == 1 # only the last user message survives + assert len(records) == 4 # summary + 3 recent + + def test_compact_count_persists_after_reload(self, tmp_path): + """compact() recomputed message_count survives a reload from meta.""" + session = Session.create(tmp_path) + for i in range(6): + session.append_message({"type": "message", "role": "user", "content": f"msg {i}"}) + session.append_message({"type": "tool_result", "tool_call_id": f"c{i}", "content": "ok"}) + + session.compact("summary", keep_recent=2) + loaded = Session.load(session.session_id, tmp_path) + assert loaded.message_count == 1 # only the last user message survives + def test_compact_keeps_exact_keep_recent(self, tmp_path): """compact() with keep_recent=1 replaces all but the last message.""" session = Session.create(tmp_path)