fix(memos-local-plugin): prevent orphan episode scan from closing active sessions; add episode delete API#1546
fix(memos-local-plugin): prevent orphan episode scan from closing active sessions; add episode delete API#1546Starfie1d1272 wants to merge 6 commits intoMemTensor:mainfrom
Conversation
…ive sessions; add episode delete API
Two related fixes:
1. Orphan episode protection (2 files):
- core/pipeline/memory-core.ts: init() now checks session.meta.closedAt
before treating open episodes as orphans. Episodes from sessions
that haven't been explicitly closed are no longer abandoned on
bridge restart.
- core/session/manager.ts: closeSession() now stamps session.meta.closedAt
so future init() calls can distinguish 'explicitly closed' from
'crashed and might reconnect'.
2. WebUI Tasks page bulk delete (3 files):
- core/storage/repos/episodes.ts: added deleteById() method
- core/pipeline/memory-core.ts: added deleteEpisode() / deleteEpisodes()
- agent-contract/memory-core.ts: added interface signatures
- server/routes/session.ts: DELETE /api/v1/episodes now calls
deleteEpisode (actually removes the row + cascading traces)
instead of closeEpisode (no-op on already-closed episodes).
Added POST /api/v1/episodes/delete for bulk operations.
…rmes session restart Three fixes for the Hermes bridge adapter: 1. Use tsx runtime instead of node --experimental-strip-types 2. PID file to prevent duplicate bridge processes 3. Bridge lifetime tracking via register_bridge()
…d of hardcoding Previously the bridge reported '2.0.0-alpha.1' regardless of the actual package version. Now it reads from package.json at startup.
There was a problem hiding this comment.
Pull request overview
Improves Hermes memos-local-plugin session stability by refining startup orphan-episode handling, and adds hard-delete support for episodes (including a bulk-delete HTTP endpoint) to fix Tasks page deletions.
Changes:
- Refine
init()orphan-episode scan to only close open episodes when the owning session is explicitly closed (or missing). - Add episode hard-delete capabilities end-to-end (repo → core → HTTP routes), including a bulk delete route.
- Stamp
session.meta.closedAton session close so restarts can distinguish “explicitly closed” vs “may reconnect”.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/memos-local-plugin/server/routes/session.ts | Switch single-episode delete to hard-delete and add bulk delete endpoint. |
| apps/memos-local-plugin/core/storage/repos/episodes.ts | Add deleteById() to physically remove episodes (FK cascade expected). |
| apps/memos-local-plugin/core/session/manager.ts | Stamp meta.closedAt on closeSession() to support orphan detection. |
| apps/memos-local-plugin/core/pipeline/memory-core.ts | Update orphan filtering on init; implement deleteEpisode(s) in the core. |
| apps/memos-local-plugin/bridge.cts | Update bridge version sourcing and daemon/stdin lifetime handling. |
| apps/memos-local-plugin/agent-contract/memory-core.ts | Extend MemoryCore contract with deleteEpisode / deleteEpisodes. |
| apps/memos-local-plugin/adapters/hermes/memos_provider/daemon_manager.py | Add PID-file-based singleton bridge management helpers. |
| apps/memos-local-plugin/adapters/hermes/memos_provider/bridge_client.py | Use PID-file singleton management when spawning/closing the bridge. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const openEpisodes = handle.repos.episodes.list({ status: "open", limit: 500 }); | ||
| // Only treat an open episode as an orphan if its session has been | ||
| // explicitly closed (meta.closedAt is set) or no longer exists. | ||
| // Otherwise the session might reconnect — leave it alone. | ||
| const orphans = openEpisodes.filter((ep) => { |
There was a problem hiding this comment.
The new orphan-episode filter changes boot-time behavior in a subtle but reliability-critical way (preserving open episodes when the owning session wasn’t explicitly closed). There are existing MemoryCore tests (e.g. tests/unit/pipeline/memory-core.test.ts); please add coverage for this init-path so regressions don’t reintroduce accidental orphan-closing on restart.
| async function deleteEpisode(episodeId: EpisodeId): Promise<{ deleted: boolean }> { | ||
| ensureLive(); | ||
| return { deleted: handle.repos.episodes.deleteById(episodeId) }; | ||
| } |
There was a problem hiding this comment.
New delete APIs/endpoints (deleteEpisode + bulk delete) are user-facing data-management operations; adding at least a small unit test around the delete behavior (and cascade expectations) would align with the existing MemoryCore test coverage and guard against regressions like the earlier no-op deletion bug.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Prevent deleting episodes that are currently open/in-flight, which could cause FK violations and corrupt in-memory pipeline state. Co-Authored-By: Copilot <copilot-pull-request-reviewer[bot]@users.noreply.github.com>
Description
This PR introduces two related fixes and feature enhancements for the
memos-local-plugin(Hermes adapter) to improve session stability and data management.1. Orphan Episode Protection (Reliability Fix)
Problem: Previously, the bridge
init()process treated allstatus='open'episodes as orphans upon startup, auto-closing them with the reason "插件上次未正常退出". This caused episodes from active sessions to be prematurely abandoned during bridge restarts, gateway loops, or session switches.Fix:
init()logic to checksession.meta.closedAtbefore closing open episodes.closeSession()in the session manager to stampclosedAtviatouchLastSeen(), enabling the system to distinguish between a "properly closed session" and a "bridge crash/reconnect" scenario.2. WebUI Tasks Page Deletion Fix & Bulk Delete
Problem: The "Delete selected" button on the Tasks page was calling
closeEpisode(), which is a no-op for already-closed episodes. This resulted in a successful UI confirmation while the data remained in the database.Fix:
deleteById()to the episodes SQLite repo (supports cascading trace deletion via Foreign Keys).deleteEpisodeanddeleteEpisodesmethods to theMemoryCoreinterface and implementation.DELETE /api/v1/episodesroute to usedeleteEpisodeinstead ofcloseEpisode.POST /api/v1/episodes/deletefor bulk deletion operations.Type of change
Files Changed
apps/memos-local-plugin/agent-contract/memory-core.ts: Added interface methods.apps/memos-local-plugin/core/pipeline/memory-core.ts: Improved orphan filter & implemented delete logic.apps/memos-local-plugin/core/session/manager.ts: AddedclosedAttimestamping.apps/memos-local-plugin/core/storage/repos/episodes.ts: ImplementeddeleteById.apps/memos-local-plugin/server/routes/session.ts: Fixed delete route and added bulk endpoint.How Has This Been Tested?
Tested on Hermes agent with a live bridge:
init()scan and remained open in SQLite.Checklist