Skip to content

Commit 56f62d0

Browse files
authored
feat: merge main to local plugin (#1461)
## Description Please include a summary of the change, the problem it solves, the implementation approach, and relevant context. List any dependencies required for this change. Related Issue (Required): Fixes #issue_number ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Refactor (does not change functionality, e.g. code style improvements, linting) - [ ] Documentation update ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration - [ ] Unit Test - [ ] Test Script Or Test Steps (please provide) - [ ] Pipeline Automated API Test (please provide) ## Checklist - [ ] I have performed a self-review of my own code | 我已自行检查了自己的代码 - [ ] I have commented my code in hard-to-understand areas | 我已在难以理解的地方对代码进行了注释 - [ ] I have added tests that prove my fix is effective or that my feature works | 我已添加测试以证明我的修复有效或功能正常 - [ ] I have created related documentation issue/PR in [MemOS-Docs](https://github.com/MemTensor/MemOS-Docs) (if applicable) | 我已在 [MemOS-Docs](https://github.com/MemTensor/MemOS-Docs) 中创建了相关的文档 issue/PR(如果适用) - [ ] I have linked the issue to this PR (if applicable) | 我已将 issue 链接到此 PR(如果适用) - [ ] I have mentioned the person who will review this PR | 我已提及将审查此 PR 的人 ## Reviewer Checklist - [ ] closes #xxxx (Replace xxxx with the GitHub issue number) - [ ] Made sure Checks passed - [ ] Tests have been provided
2 parents 2d85c73 + 96a1dd6 commit 56f62d0

20 files changed

Lines changed: 426 additions & 193 deletions

File tree

apps/memos-local-plugin/adapters/hermes/__init__.py

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,25 @@
88

99
from __future__ import annotations
1010

11+
import contextlib
1112
import json
1213
import logging
13-
import os
1414
import re
1515
import sys
1616
import threading
17+
1718
from pathlib import Path
18-
from typing import Any, Dict, List
19+
from typing import Any
20+
1921

2022
# Add this directory to sys.path so sibling modules (config, bridge_client, …) resolve
2123
_PLUGIN_DIR = Path(__file__).resolve().parent
2224
if str(_PLUGIN_DIR) not in sys.path:
2325
sys.path.insert(0, str(_PLUGIN_DIR))
2426

25-
from agent.memory_provider import MemoryProvider
26-
from tools.registry import tool_error
27+
from agent.memory_provider import MemoryProvider # noqa: E402
28+
from tools.registry import tool_error # noqa: E402
29+
2730

2831
logger = logging.getLogger(__name__)
2932

@@ -51,10 +54,10 @@
5154
re.compile(r'^\s*\{["\']?ok["\']?\s*:\s*true\s*\}\s*$', re.IGNORECASE),
5255
re.compile(r'^\s*\{["\']?success["\']?\s*:\s*true\s*\}\s*$', re.IGNORECASE),
5356
re.compile(r'^\s*\{["\']?status["\']?\s*:\s*["\']?ok["\']?\s*\}\s*$', re.IGNORECASE),
54-
re.compile(r'^Operation interrupted:', re.IGNORECASE),
55-
re.compile(r'^Error:', re.IGNORECASE),
56-
re.compile(r'waiting for model response.*elapsed', re.IGNORECASE),
57-
re.compile(r'^\s*$'),
57+
re.compile(r"^Operation interrupted:", re.IGNORECASE),
58+
re.compile(r"^Error:", re.IGNORECASE),
59+
re.compile(r"waiting for model response.*elapsed", re.IGNORECASE),
60+
re.compile(r"^\s*$"),
5861
]
5962

6063
_MIN_CONTENT_LENGTH = 6
@@ -74,7 +77,10 @@ def _is_trivial(text: str) -> bool:
7477
keys = {k.lower() for k in obj}
7578
if keys <= {"ok", "success", "status", "result", "error", "message"}:
7679
vals = list(obj.values())
77-
if all(isinstance(v, (bool, type(None))) or (isinstance(v, str) and len(v) < 20) for v in vals):
80+
if all(
81+
isinstance(v, bool | type(None)) or (isinstance(v, str) and len(v) < 20)
82+
for v in vals
83+
):
7884
return True
7985
except (json.JSONDecodeError, TypeError):
8086
pass
@@ -100,6 +106,7 @@ def name(self) -> str:
100106
def is_available(self) -> bool:
101107
try:
102108
from config import find_bridge_script
109+
103110
find_bridge_script()
104111
return True
105112
except Exception:
@@ -109,6 +116,7 @@ def initialize(self, session_id: str, **kwargs) -> None:
109116
self._session_id = session_id
110117

111118
from daemon_manager import ensure_daemon
119+
112120
try:
113121
info = ensure_daemon()
114122
logger.info(
@@ -121,6 +129,7 @@ def initialize(self, session_id: str, **kwargs) -> None:
121129
logger.warning("Failed to start MemTensor daemon: %s", e)
122130

123131
from bridge_client import MemosCoreBridge
132+
124133
try:
125134
self._bridge = MemosCoreBridge()
126135
logger.info("MemTensor bridge connected")
@@ -175,6 +184,7 @@ def _run():
175184
if pending and self._bridge:
176185
try:
177186
from config import OWNER
187+
178188
user_content, assistant_content, sid = pending
179189
messages = []
180190
if user_content:
@@ -201,9 +211,7 @@ def _do_recall(self, query: str) -> str:
201211
parts: list[str] = []
202212

203213
try:
204-
search_resp = self._bridge.search(
205-
query, max_results=5, min_score=0.4, owner=OWNER
206-
)
214+
search_resp = self._bridge.search(query, max_results=5, min_score=0.4, owner=OWNER)
207215
hits = search_resp.get("hits") or search_resp.get("memories") or []
208216
for h in hits:
209217
text = h.get("original_excerpt") or h.get("content") or h.get("summary", "")
@@ -225,9 +233,7 @@ def _do_recall(self, query: str) -> str:
225233

226234
return "\n".join(parts)
227235

228-
def sync_turn(
229-
self, user_content: str, assistant_content: str, *, session_id: str = ""
230-
) -> None:
236+
def sync_turn(self, user_content: str, assistant_content: str, *, session_id: str = "") -> None:
231237
"""Queue turn data for deferred ingest.
232238
233239
Hermes calls sync_all() BEFORE queue_prefetch_all(), so ingesting
@@ -239,8 +245,11 @@ def sync_turn(
239245
if not self._bridge:
240246
return
241247
if _is_trivial(user_content) and _is_trivial(assistant_content):
242-
logger.debug("sync_turn: skipping trivial turn (user=%r, assistant=%r)",
243-
user_content[:80] if user_content else "", assistant_content[:80] if assistant_content else "")
248+
logger.debug(
249+
"sync_turn: skipping trivial turn (user=%r, assistant=%r)",
250+
user_content[:80] if user_content else "",
251+
assistant_content[:80] if assistant_content else "",
252+
)
244253
return
245254
if _is_trivial(user_content):
246255
user_content = ""
@@ -249,10 +258,10 @@ def sync_turn(
249258
sid = session_id or self._session_id or "default"
250259
self._pending_ingest = (user_content, assistant_content, sid)
251260

252-
def get_tool_schemas(self) -> List[Dict[str, Any]]:
261+
def get_tool_schemas(self) -> list[dict[str, Any]]:
253262
return [MEMORY_SEARCH_SCHEMA]
254263

255-
def handle_tool_call(self, tool_name: str, args: Dict[str, Any], **kwargs) -> str:
264+
def handle_tool_call(self, tool_name: str, args: dict[str, Any], **kwargs) -> str:
256265
if tool_name != "memory_search":
257266
return tool_error(f"Unknown tool: {tool_name}")
258267

@@ -265,6 +274,7 @@ def handle_tool_call(self, tool_name: str, args: Dict[str, Any], **kwargs) -> st
265274

266275
try:
267276
from config import OWNER
277+
268278
resp = self._bridge.search(query, max_results=8, owner=OWNER)
269279
hits = resp.get("hits") or resp.get("memories") or []
270280
if not hits:
@@ -285,6 +295,7 @@ def on_memory_write(self, action: str, target: str, content: str) -> None:
285295
return
286296

287297
from config import OWNER
298+
288299
label = "user_profile" if target == "user" else "memory"
289300
messages = [
290301
{"role": "system", "content": f"[{label}] {content}"},
@@ -298,14 +309,16 @@ def _write():
298309
owner=OWNER,
299310
)
300311
self._bridge.flush()
301-
logger.info("MemTensor on_memory_write: %s %s (%d chars)", action, target, len(content))
312+
logger.info(
313+
"MemTensor on_memory_write: %s %s (%d chars)", action, target, len(content)
314+
)
302315
except Exception as e:
303316
logger.warning("MemTensor on_memory_write failed: %s", e)
304317

305318
t = threading.Thread(target=_write, daemon=True, name="memtensor-memory-write")
306319
t.start()
307320

308-
def on_session_end(self, messages: List[Dict[str, Any]]) -> None:
321+
def on_session_end(self, messages: list[dict[str, Any]]) -> None:
309322
if not self._bridge:
310323
return
311324
# Flush any deferred ingest that hasn't been picked up by queue_prefetch
@@ -314,6 +327,7 @@ def on_session_end(self, messages: List[Dict[str, Any]]) -> None:
314327
if pending:
315328
try:
316329
from config import OWNER
330+
317331
user_content, assistant_content, sid = pending
318332
msgs = []
319333
if user_content:
@@ -334,10 +348,8 @@ def shutdown(self) -> None:
334348
if t and t.is_alive():
335349
t.join(timeout=5.0)
336350
if self._bridge:
337-
try:
351+
with contextlib.suppress(Exception):
338352
self._bridge.shutdown()
339-
except Exception:
340-
pass
341353
self._bridge = None
342354

343355

apps/memos-local-plugin/adapters/hermes/bridge_client.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@
77

88
from __future__ import annotations
99

10+
import contextlib
1011
import json
1112
import logging
1213
import os
1314
import socket
1415
import subprocess
1516
import threading
17+
1618
from typing import Any
1719

18-
from config import find_bridge_script, get_bridge_config, get_daemon_port, OWNER, _get_plugin_root
20+
from config import OWNER, _get_plugin_root, find_bridge_script, get_bridge_config, get_daemon_port
21+
1922

2023
logger = logging.getLogger(__name__)
2124

@@ -45,10 +48,8 @@ def send(self, data: str) -> str:
4548

4649
def close(self) -> None:
4750
if self._sock:
48-
try:
51+
with contextlib.suppress(OSError):
4952
self._sock.close()
50-
except OSError:
51-
pass
5253
self._sock = None
5354

5455

@@ -133,20 +134,27 @@ def call(self, method: str, params: dict[str, Any] | None = None) -> Any:
133134
raise RuntimeError(f"Bridge error: {resp['error']}")
134135
return resp.get("result", {})
135136

136-
def search(self, query: str, max_results: int = 6, min_score: float = 0.45, owner: str = OWNER) -> dict:
137-
return self.call("search", {
138-
"query": query,
139-
"maxResults": max_results,
140-
"minScore": min_score,
141-
"owner": owner,
142-
})
137+
def search(
138+
self, query: str, max_results: int = 6, min_score: float = 0.45, owner: str = OWNER
139+
) -> dict:
140+
return self.call(
141+
"search",
142+
{
143+
"query": query,
144+
"maxResults": max_results,
145+
"minScore": min_score,
146+
"owner": owner,
147+
},
148+
)
143149

144150
def ingest(self, messages: list[dict], session_id: str = "default", owner: str = OWNER) -> None:
145151
params: dict[str, Any] = {"messages": messages, "sessionId": session_id, "owner": owner}
146152
self.call("ingest", params)
147153

148154
def build_prompt(self, query: str, max_results: int = 6, owner: str = OWNER) -> dict:
149-
return self.call("build_prompt", {"query": query, "maxResults": max_results, "owner": owner})
155+
return self.call(
156+
"build_prompt", {"query": query, "maxResults": max_results, "owner": owner}
157+
)
150158

151159
def flush(self) -> None:
152160
self.call("flush")

apps/memos-local-plugin/adapters/hermes/config.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
import json
66
import os
7+
78
from pathlib import Path
89

10+
911
DAEMON_PORT = 18992
1012
VIEWER_PORT = 18901
1113
OWNER = "hermes"
@@ -139,6 +141,7 @@ def _resolve_tsx(plugin_root: Path) -> str:
139141
if local_tsx.exists():
140142
return str(local_tsx)
141143
import shutil
144+
142145
global_tsx = shutil.which("tsx")
143146
if global_tsx:
144147
return global_tsx
@@ -170,7 +173,7 @@ def find_bridge_script() -> list[str]:
170173
return ["node", str(candidate)]
171174
tsx = _resolve_tsx(candidate.parent)
172175
if " " in tsx:
173-
return tsx.split() + [str(candidate)]
176+
return [*tsx.split(), str(candidate)]
174177
return [tsx, str(candidate)]
175178

176179
raise FileNotFoundError(

0 commit comments

Comments
 (0)