fix(mem0-agent): non-blocking stdin, clean session_state, lint

Found by running the CLI live against a scratch project:
- hook_input() blocked forever on an open stdin pipe with no data. A real hook
  writes JSON and closes the pipe, but any manual invocation hung; now polled
  with select() and never blocks.
- the open-thread snapshot was filling with progress lines and file-modification
  lists -- v1's exact failure -- so it now filters turns through the same
  hard-drop rules the capture gate uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Deshraj Yadav
2026-07-28 02:12:23 -07:00
co-authored by Claude Fable 5
parent 4d5f2e653c
commit ba1de5f87b
6 changed files with 76 additions and 9 deletions
+24 -4
View File
@@ -24,12 +24,32 @@ from .settings import CAPTURE_LEVELS, MEMORY_MODES, RETRIEVAL_LEVELS, Settings
# --------------------------------------------------------------------------
# hook plumbing
# --------------------------------------------------------------------------
def hook_input() -> dict:
"""Editors hand hooks a JSON payload on stdin. Absent or malformed is fine."""
if sys.stdin is None or sys.stdin.isatty():
def hook_input(timeout: float = 0.25) -> dict:
"""Editors hand hooks a JSON payload on stdin. Absent or malformed is fine.
Never blocks. A hook's stdin is written and closed, but a manual invocation inherits
an open pipe with nothing in it -- reading that would hang the command forever.
"""
stream = sys.stdin
if stream is None or not hasattr(stream, "read"):
return {}
try:
raw = sys.stdin.read()
if stream.isatty():
return {}
except Exception:
return {}
# StringIO and friends (tests) are readable immediately; real pipes get a poll.
if hasattr(stream, "fileno"):
try:
import select
if not select.select([stream], [], [], timeout)[0]:
return {}
except Exception:
pass # not selectable (e.g. StringIO) -- fall through and read
try:
raw = stream.read()
return json.loads(raw) if raw.strip() else {}
except Exception:
return {}