refactor(integrations): shared agent plugin runtimes and native adapters (#7203)
This commit is contained in:
@@ -1,7 +1,20 @@
|
||||
"""Keep the test suite from sending usage telemetry to the live PostHog project."""
|
||||
"""Configure harness identity and disable live telemetry for the test suite."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
os.environ["MEM0_TELEMETRY"] = "false"
|
||||
|
||||
HOST_ROOT = Path(__file__).resolve().parents[1]
|
||||
_core = HOST_ROOT / "core"
|
||||
sys.path.insert(0, str(_core))
|
||||
|
||||
import hook_runner # noqa: E402,F401
|
||||
from memory_core import configure_harness # noqa: E402
|
||||
import telemetry # noqa: E402
|
||||
|
||||
configure_harness("claude-code", data_dir_name="claude-code-plugin", source_tag="claude_code_plugin")
|
||||
telemetry.init(harness="claude-code", source_tag="CLAUDE_CODE_PLUGIN")
|
||||
|
||||
@@ -13,10 +13,11 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
PLUGIN_ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(PLUGIN_ROOT / "core"))
|
||||
sys.path.insert(0, str(PLUGIN_ROOT / "adapters" / "claude"))
|
||||
_core_dir = PLUGIN_ROOT / "core"
|
||||
sys.path.insert(0, str(_core_dir))
|
||||
sys.path.insert(0, str(PLUGIN_ROOT))
|
||||
|
||||
import hook # noqa: E402
|
||||
import hook_runner # noqa: E402
|
||||
import memory_core # noqa: E402
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
@@ -177,15 +178,17 @@ def test_shared_project_memory_reaches_a_teammate_who_never_wrote(ns):
|
||||
def test_repo_scope_spans_every_subdirectory(ns):
|
||||
found = ns.search("erin", ns.root, "how many times are Stripe webhooks retried", scope="repo")
|
||||
assert "stripe" in _text(found)
|
||||
app_ids = {m.get("app_id") for m in found}
|
||||
assert any(app.endswith("/services/billing") for app in app_ids)
|
||||
assert any("services/billing" in (memory.get("metadata") or {}).get("dirs", []) for memory in found)
|
||||
|
||||
|
||||
def test_dir_scope_narrows_shared_memory_to_the_directory(ns):
|
||||
billing = ns.root / "services" / "billing"
|
||||
found = ns.search("erin", billing, "how do I run the tests here", scope="dir")
|
||||
assert "billing-test" in _text(found)
|
||||
assert {m.get("app_id") for m in found if m.get("agent_id")} == {memory_core.directory_app_id(memory_core.resolve_repo(str(billing)))}
|
||||
shared = [memory for memory in found if memory.get("agent_id")]
|
||||
assert shared
|
||||
assert {memory.get("app_id") for memory in shared} == {memory_core.resolve_repo(str(billing)).app_id}
|
||||
assert all("services/billing" in (memory.get("metadata") or {}).get("dirs", []) for memory in shared)
|
||||
|
||||
web = ns.root / "apps" / "web"
|
||||
elsewhere = ns.search("erin", web, "Stripe webhook retries exponential backoff", scope="dir", tries=1)
|
||||
@@ -232,7 +235,7 @@ def test_a_pending_packet_is_recovered_and_delivered_by_the_worker(ns):
|
||||
stale.write_text(json.dumps({"hook_input": {"session_id": sid, "cwd": str(ns.root)}, "reason": "session-end"}))
|
||||
os.utime(stale, (time.time() - 3600, time.time() - 3600))
|
||||
|
||||
assert hook.recover_pending_handoffs() == 1
|
||||
assert hook_runner.recover_pending_handoffs() == 1
|
||||
deadline = time.time() + 240
|
||||
while time.time() < deadline and list(pending.iterdir()):
|
||||
time.sleep(3)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HOST = Path(__file__).resolve().parents[1]
|
||||
CORE_ROOT = HOST.parent / "agent-plugin-core"
|
||||
sys.path.insert(0, str(CORE_ROOT))
|
||||
|
||||
from build.build import SHARED_SKILLS, build, render_template # noqa: E402
|
||||
|
||||
|
||||
def test_native_claude_bundle_preserves_working_contract(tmp_path: Path) -> None:
|
||||
root = build("claude-code", "native", tmp_path / "claude-code")
|
||||
|
||||
assert (root / ".claude-plugin" / "plugin.json").read_bytes() == (
|
||||
HOST / ".claude-plugin" / "plugin.json"
|
||||
).read_bytes()
|
||||
assert (root / ".mcp.json").read_bytes() == (HOST / ".mcp.json").read_bytes()
|
||||
assert (root / "hooks" / "hooks.json").read_bytes() == (HOST / "hooks" / "hooks.json").read_bytes()
|
||||
assert (root / "agents" / "sidekick.md").read_bytes() == (HOST / "agents" / "sidekick.md").read_bytes()
|
||||
values = {
|
||||
"PLUGIN_ROOT": "${CLAUDE_PLUGIN_ROOT}",
|
||||
"PLUGIN_DATA": "${PLUGIN_DATA}",
|
||||
"PLUGIN_DATA_ARG": '--plugin-data-dir "${CLAUDE_PLUGIN_DATA}"',
|
||||
"COMMAND_PREFIX": "mem0",
|
||||
"HARNESS_ID": "claude-code",
|
||||
"HARNESS_NAME": "Claude Code",
|
||||
}
|
||||
for skill in SHARED_SKILLS.glob("*/SKILL.md.tmpl"):
|
||||
rendered = render_template(skill.read_text(encoding="utf-8"), values)
|
||||
assert (root / "skills" / skill.parent.name / "SKILL.md").read_text(encoding="utf-8") == rendered
|
||||
assert (root / "adapters" / "claude" / "hook.py").is_file()
|
||||
assert (root / "core" / "mcp_server.py").is_file()
|
||||
assert not any(path.is_symlink() for path in root.rglob("*"))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,8 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
PLUGIN_ROOT = Path(__file__).resolve().parents[1]
|
||||
CORE = PLUGIN_ROOT / "core"
|
||||
HOST_ROOT = Path(__file__).resolve().parents[1]
|
||||
CORE = HOST_ROOT / "core"
|
||||
sys.path.insert(0, str(CORE))
|
||||
|
||||
import memory_core # noqa: E402
|
||||
@@ -77,6 +76,46 @@ def test_record_hashes_identifiers_and_keeps_no_content(isolated_env):
|
||||
assert "session-abcdef" not in serialized
|
||||
|
||||
|
||||
def test_record_rejects_sensitive_properties_at_the_shared_boundary(isolated_env):
|
||||
secret = "sk-eval-12345678901234567890"
|
||||
telemetry.record(
|
||||
"search",
|
||||
prompt=f"remember {secret}",
|
||||
query=secret,
|
||||
api_key=secret,
|
||||
user_id="private-user",
|
||||
note=f"failure contained {secret}",
|
||||
memory_count=2,
|
||||
)
|
||||
|
||||
(event,) = spool_lines()
|
||||
assert event["properties"]["memory_count"] == 2
|
||||
serialized = json.dumps(event)
|
||||
assert secret not in serialized
|
||||
assert "private-user" not in serialized
|
||||
assert not {"prompt", "query", "api_key", "user_id"} & event["properties"].keys()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key", ["password", "token", "secret", "authorization"])
|
||||
def test_record_removes_sensitive_keys_from_nested_lists(isolated_env, key):
|
||||
secret = "sk-eval-12345678901234567890"
|
||||
telemetry.record(
|
||||
"search",
|
||||
details=[
|
||||
{key: "plain-value", "count": 2},
|
||||
{"nested": {key.upper(): "plain-value", "ok": True}},
|
||||
[f"failure contained {secret}"],
|
||||
],
|
||||
)
|
||||
|
||||
(event,) = spool_lines()
|
||||
assert event["properties"]["details"] == [
|
||||
{"count": 2},
|
||||
{"nested": {"ok": True}},
|
||||
["failure contained [REDACTED]"],
|
||||
]
|
||||
|
||||
|
||||
def test_record_stops_appending_past_the_spool_cap(isolated_env):
|
||||
spool = memory_core.data_dir() / "telemetry.jsonl"
|
||||
spool.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
Reference in New Issue
Block a user