84 lines
3.3 KiB
Python
84 lines
3.3 KiB
Python
"""Exercise bundled subagent hooks as separate host processes, without Mem0 calls."""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT / "python"))
|
|
|
|
from memory_core import EvidenceStore # noqa: E402
|
|
|
|
|
|
@pytest.mark.parametrize("host", ["claude-code", "codex", "kimi", "cursor"])
|
|
def test_sidekick_hooks_preserve_parent_scope_and_correlate_completion(tmp_path, host):
|
|
parent = tmp_path / "parent"
|
|
child = tmp_path / "child-worktree"
|
|
parent.mkdir()
|
|
child.mkdir()
|
|
database = tmp_path / "data" / "evidence.sqlite3"
|
|
store = EvidenceStore(database)
|
|
repo = store.repo_for_session("parent-session", str(parent))
|
|
store.mark_injected("parent-session", repo.identity, [{"id": "one", "memory": "Parent memory marker."}])
|
|
store.mark_injected("other-session", repo.identity, [{"id": "two", "memory": "Foreign memory marker."}])
|
|
store.close()
|
|
|
|
plugin = ROOT.parent / f"{host}-plugin"
|
|
adapter = plugin / ("adapters/claude/hook.py" if host == "claude-code" else "hooks/adapter.py")
|
|
start, stop = "sidekick-start", "sidekick-stop"
|
|
payload = {"session_id": "parent-session", "cwd": str(child), "agent_id": "worker", "agent_type": "sidekick"}
|
|
response_key = "last_assistant_message"
|
|
if host == "kimi":
|
|
start, stop = "SubagentStart", "SubagentStop"
|
|
payload["agent_name"] = payload.pop("agent_type")
|
|
response_key = "response"
|
|
elif host == "cursor":
|
|
start, stop = "subagentStart", "subagentStop"
|
|
payload = {
|
|
"conversation_id": "parent-session",
|
|
"workspace_roots": [str(child)],
|
|
"subagent_id": "worker",
|
|
"subagent_type": "sidekick",
|
|
}
|
|
response_key = "summary"
|
|
|
|
env = {key: value for key, value in os.environ.items() if not key.startswith(("MEM0_", "CLAUDE_PLUGIN_"))}
|
|
env.update(MEM0_CODE_DATA_DIR=str(database.parent), MEM0_TELEMETRY="false", MEM0_API_URL="http://127.0.0.1:1")
|
|
|
|
def invoke(event, body):
|
|
result = subprocess.run(
|
|
[sys.executable, str(adapter), event],
|
|
input=json.dumps({**body, "hook_event_name": event}),
|
|
text=True,
|
|
capture_output=True,
|
|
env=env,
|
|
timeout=15,
|
|
check=True,
|
|
)
|
|
return result.stdout
|
|
|
|
output = invoke(start, payload)
|
|
if host == "cursor":
|
|
assert json.loads(output) == {"permission": "allow"}
|
|
else:
|
|
context = output if host == "kimi" else json.loads(output)["hookSpecificOutput"]["additionalContext"]
|
|
assert "Parent memory marker." in context
|
|
assert "Foreign memory marker." not in output
|
|
assert "Parent memory marker." not in invoke(start, payload)
|
|
invoke(stop, {**payload, response_key: "Finished the delegated task."})
|
|
|
|
store = EvidenceStore(database)
|
|
runs = store.conn.execute("SELECT * FROM sidekick_runs").fetchall()
|
|
store.close()
|
|
assert len(runs) == 1
|
|
assert (runs[0]["repo_id"], runs[0]["session_id"], runs[0]["agent_id"]) == (
|
|
repo.identity, "parent-session", "worker"
|
|
)
|
|
assert runs[0]["stopped_at"]
|
|
assert runs[0]["final_message"] == "Finished the delegated task."
|
|
assert (runs[0]["context_chars"] > 0) == (host != "cursor")
|