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
2 changes: 1 addition & 1 deletion Agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,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` (799) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (800) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (236: 44 daemon_client + 19 conn-manager + 22 app-commands + 114 renderer smoke + 15 i18n + 7 integration + 3 commands + 5 build-config + 7 gui-state) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
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 上下文)
Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响)
Expand Down
19 changes: 19 additions & 0 deletions emrg/sessions_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ def rebuild_sessions_index(
Scans ``config_root`` recursively (covers ~/.emrg and anything nested under
it) plus each explicit project path (covers projects outside ~/.emrg), and
upserts every discovered session into ``config_root/sessions_index.json``.
Entries whose session directory no longer exists on disk are pruned (e.g.
sessions deleted out-of-band); entries pointing to live directories are
preserved even when the scan did not rediscover them.
Returns the number of distinct sessions indexed.
"""
index_path = config_root / _INDEX_FILENAME
Expand All @@ -177,5 +180,21 @@ def rebuild_sessions_index(

for sid, sdir in found.items():
data[sid] = sdir

# Prune stale entries whose session directory no longer exists on disk
# (removed out-of-band, outside the Session.delete hook). Rebuild stays a
# pure backfill for everything still alive — manual entries that point to a
# live directory are preserved.
stale = []
for sid, sdir in data.items():
try:
alive = Path(sdir).exists()
except (OSError, ValueError):
alive = False
if not alive:
stale.append(sid)
for sid in stale:
del data[sid]

_write(data, index_path)
return len(data)
30 changes: 28 additions & 2 deletions tests/test_sessions_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import json
import shutil
from pathlib import Path

from emrg.session import Session
Expand Down Expand Up @@ -138,16 +139,41 @@ def test_rebuild_skips_meta_without_sid(self, tmp_path):
assert count == 0

def test_rebuild_preserves_manual_entries(self, tmp_path):
"""Manual entries pointing to a live directory survive the rebuild."""
cfg = tmp_path / "cfg"
cfg.mkdir(parents=True, exist_ok=True)
manual_dir = tmp_path / "manual_dir"
manual_dir.mkdir(parents=True, exist_ok=True)
(cfg / "sessions_index.json").write_text(
json.dumps({"s_manual": "/tmp/manual"}), encoding="utf-8"
json.dumps({"s_manual": str(manual_dir)}), encoding="utf-8"
)
self._make_session(cfg, "s_scanned")
count = rebuild_sessions_index(cfg)
assert count == 2
data = _load(cfg / "sessions_index.json")
assert data["s_manual"] == "/tmp/manual"
assert data["s_manual"] == str(manual_dir)
assert "s_scanned" in data

def test_rebuild_prunes_stale_entries(self, tmp_path):
"""Index entries whose session dir was deleted out-of-band are dropped."""
cfg = tmp_path / "cfg"
cfg.mkdir(parents=True, exist_ok=True)
live_dir = tmp_path / "live"
live_dir.mkdir(parents=True, exist_ok=True)
dead_dir = tmp_path / "dead"
dead_dir.mkdir(parents=True, exist_ok=True)
(cfg / "sessions_index.json").write_text(
json.dumps({"s_live": str(live_dir), "s_dead": str(dead_dir)}),
encoding="utf-8",
)
# Delete the dead session's directory out-of-band (no Session.delete hook).
shutil.rmtree(dead_dir)
self._make_session(cfg, "s_scanned")
count = rebuild_sessions_index(cfg)
assert count == 2
data = _load(cfg / "sessions_index.json")
assert "s_dead" not in data
assert data["s_live"] == str(live_dir)
assert "s_scanned" in data


Expand Down
Loading