fix(plugins): report the plugin that produced the event, not the one that sent it (#7323)
This commit is contained in:
@@ -81,6 +81,28 @@ def replace_output(staged: Path, output: Path) -> Path:
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def _render_harness_id(host: str) -> str:
|
||||||
|
"""Emit core/_harness_id.py for one host.
|
||||||
|
|
||||||
|
Carries both vocabularies from a single definition: the PostHog `source` tag
|
||||||
|
and the platform's X-Mem0-Source / X-Application pair. Keeping them together
|
||||||
|
is what stops the two from drifting into separate vocabularies for the same
|
||||||
|
thing.
|
||||||
|
"""
|
||||||
|
tag = host.upper().replace("-", "_") + "_PLUGIN"
|
||||||
|
return (
|
||||||
|
'"""Generated by integrations/agent-plugin-core/build/build.py. Do not edit."""\n'
|
||||||
|
"\n"
|
||||||
|
f'HARNESS_ID = "{host}"\n'
|
||||||
|
f'SOURCE_TAG = "{tag}"\n'
|
||||||
|
"\n"
|
||||||
|
"# Platform-side vocabulary (mem0_event.source + X-Application). The whole\n"
|
||||||
|
"# plugin family is one source; which editor it runs in is the application.\n"
|
||||||
|
'PLATFORM_SOURCE = "MEM0_PLUGIN"\n'
|
||||||
|
f'PLATFORM_APPLICATION = "{host}"\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _bundle_python(
|
def _bundle_python(
|
||||||
staged: Path,
|
staged: Path,
|
||||||
host: str,
|
host: str,
|
||||||
@@ -96,6 +118,12 @@ def _bundle_python(
|
|||||||
continue
|
continue
|
||||||
shutil.copy2(source, core / source.name)
|
shutil.copy2(source, core / source.name)
|
||||||
|
|
||||||
|
# Generated per host so identity does not depend on an entrypoint remembering
|
||||||
|
# to call telemetry.init(). mcp_server.py and the detached telemetry.py sender
|
||||||
|
# never did, which is how MCP searches reported harness=generic and every
|
||||||
|
# batch they drained was labelled MEM0_PLUGIN regardless of the real host.
|
||||||
|
(core / "_harness_id.py").write_text(_render_harness_id(host), encoding="utf-8")
|
||||||
|
|
||||||
values = {
|
values = {
|
||||||
"PLUGIN_ROOT": plugin_root,
|
"PLUGIN_ROOT": plugin_root,
|
||||||
"PLUGIN_DATA": "${PLUGIN_DATA}",
|
"PLUGIN_DATA": "${PLUGIN_DATA}",
|
||||||
|
|||||||
@@ -34,9 +34,24 @@ from typing import Any
|
|||||||
|
|
||||||
import memory_core
|
import memory_core
|
||||||
|
|
||||||
|
# Seeded from the per-host module the build generates into core/. Two processes
|
||||||
|
# in this pipeline never call init() — mcp_server.py, and the detached
|
||||||
|
# `python3 telemetry.py` sender that spawn_flush() starts — so a module default
|
||||||
|
# was what every one of their events got labelled with.
|
||||||
|
try: # pragma: no cover - absent only in the un-built shared source tree
|
||||||
|
from _harness_id import HARNESS_ID as _DEFAULT_HARNESS
|
||||||
|
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
|
||||||
|
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
|
||||||
|
from _harness_id import SOURCE_TAG as _DEFAULT_SOURCE_TAG
|
||||||
|
except ImportError:
|
||||||
|
_DEFAULT_HARNESS = "generic"
|
||||||
|
_DEFAULT_SOURCE_TAG = "MEM0_PLUGIN"
|
||||||
|
_PLATFORM_SOURCE = "MEM0_PLUGIN"
|
||||||
|
_PLATFORM_APPLICATION = ""
|
||||||
|
|
||||||
_salt_cache: str = ""
|
_salt_cache: str = ""
|
||||||
_harness: str = "generic"
|
_harness: str = _DEFAULT_HARNESS
|
||||||
_source_tag: str = "MEM0_PLUGIN"
|
_source_tag: str = _DEFAULT_SOURCE_TAG
|
||||||
_PRIVATE_KEYS = {
|
_PRIVATE_KEYS = {
|
||||||
"apikey",
|
"apikey",
|
||||||
"authorization",
|
"authorization",
|
||||||
@@ -62,10 +77,19 @@ _PRIVATE_KEYS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def init(harness: str = "generic", source_tag: str = "") -> None:
|
def init(harness: str = "", source_tag: str = "") -> None:
|
||||||
|
"""Override the generated identity. Optional — core/_harness_id.py is the default.
|
||||||
|
|
||||||
|
The fallback shape matches memory_core.configure_harness's (``<HOST>_PLUGIN``).
|
||||||
|
It used to be ``MEM0_<HOST>_PLUGIN`` here and ``<host>_plugin`` there, which
|
||||||
|
meant one plugin could emit three different source values depending on which
|
||||||
|
process happened to send the batch.
|
||||||
|
"""
|
||||||
global _harness, _source_tag
|
global _harness, _source_tag
|
||||||
_harness = harness
|
_harness = harness or _DEFAULT_HARNESS
|
||||||
_source_tag = source_tag or f"MEM0_{harness.upper().replace('-', '_')}_PLUGIN"
|
_source_tag = source_tag or (
|
||||||
|
f"{_harness.upper().replace('-', '_')}_PLUGIN" if harness else _DEFAULT_SOURCE_TAG
|
||||||
|
)
|
||||||
|
|
||||||
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
||||||
POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/i/v0/e/"
|
POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/i/v0/e/"
|
||||||
@@ -291,8 +315,13 @@ def record(
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
properties = _safe_value(properties)
|
properties = _safe_value(properties)
|
||||||
|
# Stamped in the RECORDING process, beside harness. `source` used to be
|
||||||
|
# read in the sending process from a module global, so whichever process
|
||||||
|
# drained the spool named every event in it. flush() spreads per-event
|
||||||
|
# properties last, so this now wins over any sender's default.
|
||||||
properties.update(
|
properties.update(
|
||||||
harness=_harness,
|
harness=_harness,
|
||||||
|
source=_source_tag,
|
||||||
plugin_version=memory_core.PLUGIN_VERSION,
|
plugin_version=memory_core.PLUGIN_VERSION,
|
||||||
os=sys.platform,
|
os=sys.platform,
|
||||||
python_version=platform.python_version(),
|
python_version=platform.python_version(),
|
||||||
@@ -311,6 +340,7 @@ def record(
|
|||||||
line = json.dumps(
|
line = json.dumps(
|
||||||
{
|
{
|
||||||
"event": f"{EVENT_PREFIX}.{event}",
|
"event": f"{EVENT_PREFIX}.{event}",
|
||||||
|
"uuid": str(uuid.uuid4()),
|
||||||
"timestamp": memory_core.utc_now(),
|
"timestamp": memory_core.utc_now(),
|
||||||
"properties": {
|
"properties": {
|
||||||
key: value for key, value in properties.items() if value is not None
|
key: value for key, value in properties.items() if value is not None
|
||||||
@@ -496,6 +526,8 @@ def flush() -> int:
|
|||||||
"distinct_id": distinct_id,
|
"distinct_id": distinct_id,
|
||||||
"timestamp": event.get("timestamp"),
|
"timestamp": event.get("timestamp"),
|
||||||
"properties": {
|
"properties": {
|
||||||
|
# Fallback only: events recorded by a build before source
|
||||||
|
# moved into record() have none of their own.
|
||||||
"source": _source_tag,
|
"source": _source_tag,
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"$process_person_profile": False,
|
"$process_person_profile": False,
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
"""Core telemetry behaviour with NO telemetry.init(), in a real subprocess.
|
||||||
|
|
||||||
|
Why this file exists
|
||||||
|
--------------------
|
||||||
|
``telemetry.py`` lives in ``agent-plugin-core/python/`` but its only tests lived
|
||||||
|
under ``claude-code-plugin/tests/``, behind a ``conftest.py`` that calls
|
||||||
|
``configure_harness()`` and ``telemetry.init()`` at import. Core behaviour was
|
||||||
|
therefore only ever exercised inside an already-configured module.
|
||||||
|
|
||||||
|
Two processes in the real pipeline never call ``init()``:
|
||||||
|
|
||||||
|
- ``mcp_server.py``, which records every manual search;
|
||||||
|
- the detached ``python3 telemetry.py`` sender that ``spawn_flush()`` starts at
|
||||||
|
session start, after every skill command, and when the MCP server exits.
|
||||||
|
|
||||||
|
Both fell back to module defaults, so MCP searches reported ``harness=generic``
|
||||||
|
and everything that sender delivered was labelled ``MEM0_PLUGIN`` regardless of
|
||||||
|
which of the six plugins produced it. The suite stayed green throughout.
|
||||||
|
|
||||||
|
These tests run in a fresh interpreter with no conftest, against a built host
|
||||||
|
bundle, which is the only arrangement that can catch that class of bug.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
CORE_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
REPOSITORY_ROOT = CORE_ROOT.parents[1]
|
||||||
|
HOSTS = {
|
||||||
|
"claude-code": ("claude-code-plugin", "CLAUDE_CODE_PLUGIN"),
|
||||||
|
"cursor": ("cursor-plugin", "CURSOR_PLUGIN"),
|
||||||
|
"codex": ("codex-plugin", "CODEX_PLUGIN"),
|
||||||
|
"kimi": ("kimi-plugin", "KIMI_PLUGIN"),
|
||||||
|
"antigravity": ("antigravity-plugin", "ANTIGRAVITY_PLUGIN"),
|
||||||
|
# Portable: no flush_worker and no hook_runner, so its ONLY sender is the
|
||||||
|
# uninitialised telemetry.py. A native-only test passes here vacuously.
|
||||||
|
"coding-agent": ("mem0-agent-plugin", "CODING_AGENT_PLUGIN"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _core_dir(directory: str) -> Path:
|
||||||
|
return REPOSITORY_ROOT / "integrations" / directory / "core"
|
||||||
|
|
||||||
|
|
||||||
|
def _run(core: Path, data_dir: Path, body: str) -> str:
|
||||||
|
"""Execute `body` in a fresh interpreter with only the host's core on sys.path."""
|
||||||
|
script = f"import sys; sys.path.insert(0, {str(core)!r})\n{body}"
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, "-c", script],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
env={
|
||||||
|
"MEM0_CODE_DATA_DIR": str(data_dir),
|
||||||
|
"PATH": "/usr/bin:/bin",
|
||||||
|
"HOME": str(data_dir),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result.returncode == 0, result.stderr
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("harness,spec", sorted(HOSTS.items()))
|
||||||
|
def test_identity_resolves_without_init(harness, spec):
|
||||||
|
"""Every built host knows what it is with no configuration call at all."""
|
||||||
|
directory, source_tag = spec
|
||||||
|
core = _core_dir(directory)
|
||||||
|
if not core.exists():
|
||||||
|
pytest.skip(f"{directory} is not built in this tree")
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
out = _run(
|
||||||
|
core,
|
||||||
|
Path(tmp),
|
||||||
|
"import telemetry; print(telemetry._harness, telemetry._source_tag)",
|
||||||
|
)
|
||||||
|
assert out == f"{harness} {source_tag}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_mcp_server_records_the_real_harness():
|
||||||
|
"""mcp_server imports telemetry and never initialises it (server.py has no init).
|
||||||
|
|
||||||
|
Its recorded events used to carry harness=generic for every plugin.
|
||||||
|
"""
|
||||||
|
core = _core_dir("claude-code-plugin")
|
||||||
|
if not core.exists():
|
||||||
|
pytest.skip("claude-code-plugin is not built in this tree")
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
data_dir = Path(tmp)
|
||||||
|
_run(
|
||||||
|
core,
|
||||||
|
data_dir,
|
||||||
|
"import mcp_server, telemetry; telemetry.record('search', trigger='mcp-search')",
|
||||||
|
)
|
||||||
|
spooled = (data_dir / "telemetry.jsonl").read_text(encoding="utf-8").strip()
|
||||||
|
|
||||||
|
event = json.loads(spooled)
|
||||||
|
assert event["properties"]["harness"] == "claude-code"
|
||||||
|
assert event["properties"]["source"] == "CLAUDE_CODE_PLUGIN"
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_detached_sender_does_not_relabel_events():
|
||||||
|
"""`python3 telemetry.py` is the sender spawn_flush() starts, and never inits.
|
||||||
|
|
||||||
|
source is stamped at record time now, so which process sends is irrelevant.
|
||||||
|
"""
|
||||||
|
core = _core_dir("claude-code-plugin")
|
||||||
|
if not core.exists():
|
||||||
|
pytest.skip("claude-code-plugin is not built in this tree")
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
data_dir = Path(tmp)
|
||||||
|
_run(core, data_dir, "import telemetry; telemetry.record('search')")
|
||||||
|
|
||||||
|
captured = data_dir / "captured.json"
|
||||||
|
# Drain with a fresh, unconfigured interpreter, capturing the payload
|
||||||
|
# instead of posting it.
|
||||||
|
_run(
|
||||||
|
core,
|
||||||
|
data_dir,
|
||||||
|
"import json, telemetry\n"
|
||||||
|
"sent = []\n"
|
||||||
|
"telemetry._post = lambda payload, url: sent.append(payload) or True\n"
|
||||||
|
"telemetry.flush()\n"
|
||||||
|
f"open({str(captured)!r}, 'w').write(json.dumps(sent))",
|
||||||
|
)
|
||||||
|
payloads = json.loads(captured.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
batches = [p for p in payloads if "batch" in p]
|
||||||
|
assert batches, "nothing was sent"
|
||||||
|
properties = batches[0]["batch"][0]["properties"]
|
||||||
|
assert properties["source"] == "CLAUDE_CODE_PLUGIN"
|
||||||
|
assert properties["harness"] == "claude-code"
|
||||||
|
|
||||||
|
|
||||||
|
def test_every_event_carries_a_uuid_for_dedupe():
|
||||||
|
core = _core_dir("claude-code-plugin")
|
||||||
|
if not core.exists():
|
||||||
|
pytest.skip("claude-code-plugin is not built in this tree")
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
data_dir = Path(tmp)
|
||||||
|
_run(core, data_dir, "import telemetry; telemetry.record('search'); telemetry.record('flush')")
|
||||||
|
lines = (data_dir / "telemetry.jsonl").read_text(encoding="utf-8").strip().splitlines()
|
||||||
|
|
||||||
|
ids = [json.loads(line)["uuid"] for line in lines]
|
||||||
|
assert len(ids) == 2
|
||||||
|
assert len(set(ids)) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_source_tag_defaults_agree_between_the_two_modules():
|
||||||
|
"""configure_harness and telemetry.init must derive the same tag.
|
||||||
|
|
||||||
|
They disagreed: `<host>_plugin` in one and `MEM0_<HOST>_PLUGIN` in the other,
|
||||||
|
so one plugin could emit three different source values depending on which
|
||||||
|
process sent the batch.
|
||||||
|
"""
|
||||||
|
core = _core_dir("claude-code-plugin")
|
||||||
|
if not core.exists():
|
||||||
|
pytest.skip("claude-code-plugin is not built in this tree")
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
out = _run(
|
||||||
|
core,
|
||||||
|
Path(tmp),
|
||||||
|
"import memory_core, telemetry\n"
|
||||||
|
"memory_core.configure_harness('kimi')\n"
|
||||||
|
"telemetry.init(harness='kimi')\n"
|
||||||
|
"print(memory_core.harness_config()['source_tag'].upper(), telemetry._source_tag)",
|
||||||
|
)
|
||||||
|
left, right = out.split()
|
||||||
|
assert left == right == "KIMI_PLUGIN"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
"""Generated by integrations/agent-plugin-core/build/build.py. Do not edit."""
|
||||||
|
|
||||||
|
HARNESS_ID = "antigravity"
|
||||||
|
SOURCE_TAG = "ANTIGRAVITY_PLUGIN"
|
||||||
|
|
||||||
|
# Platform-side vocabulary (mem0_event.source + X-Application). The whole
|
||||||
|
# plugin family is one source; which editor it runs in is the application.
|
||||||
|
PLATFORM_SOURCE = "MEM0_PLUGIN"
|
||||||
|
PLATFORM_APPLICATION = "antigravity"
|
||||||
@@ -34,9 +34,24 @@ from typing import Any
|
|||||||
|
|
||||||
import memory_core
|
import memory_core
|
||||||
|
|
||||||
|
# Seeded from the per-host module the build generates into core/. Two processes
|
||||||
|
# in this pipeline never call init() — mcp_server.py, and the detached
|
||||||
|
# `python3 telemetry.py` sender that spawn_flush() starts — so a module default
|
||||||
|
# was what every one of their events got labelled with.
|
||||||
|
try: # pragma: no cover - absent only in the un-built shared source tree
|
||||||
|
from _harness_id import HARNESS_ID as _DEFAULT_HARNESS
|
||||||
|
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
|
||||||
|
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
|
||||||
|
from _harness_id import SOURCE_TAG as _DEFAULT_SOURCE_TAG
|
||||||
|
except ImportError:
|
||||||
|
_DEFAULT_HARNESS = "generic"
|
||||||
|
_DEFAULT_SOURCE_TAG = "MEM0_PLUGIN"
|
||||||
|
_PLATFORM_SOURCE = "MEM0_PLUGIN"
|
||||||
|
_PLATFORM_APPLICATION = ""
|
||||||
|
|
||||||
_salt_cache: str = ""
|
_salt_cache: str = ""
|
||||||
_harness: str = "generic"
|
_harness: str = _DEFAULT_HARNESS
|
||||||
_source_tag: str = "MEM0_PLUGIN"
|
_source_tag: str = _DEFAULT_SOURCE_TAG
|
||||||
_PRIVATE_KEYS = {
|
_PRIVATE_KEYS = {
|
||||||
"apikey",
|
"apikey",
|
||||||
"authorization",
|
"authorization",
|
||||||
@@ -62,10 +77,19 @@ _PRIVATE_KEYS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def init(harness: str = "generic", source_tag: str = "") -> None:
|
def init(harness: str = "", source_tag: str = "") -> None:
|
||||||
|
"""Override the generated identity. Optional — core/_harness_id.py is the default.
|
||||||
|
|
||||||
|
The fallback shape matches memory_core.configure_harness's (``<HOST>_PLUGIN``).
|
||||||
|
It used to be ``MEM0_<HOST>_PLUGIN`` here and ``<host>_plugin`` there, which
|
||||||
|
meant one plugin could emit three different source values depending on which
|
||||||
|
process happened to send the batch.
|
||||||
|
"""
|
||||||
global _harness, _source_tag
|
global _harness, _source_tag
|
||||||
_harness = harness
|
_harness = harness or _DEFAULT_HARNESS
|
||||||
_source_tag = source_tag or f"MEM0_{harness.upper().replace('-', '_')}_PLUGIN"
|
_source_tag = source_tag or (
|
||||||
|
f"{_harness.upper().replace('-', '_')}_PLUGIN" if harness else _DEFAULT_SOURCE_TAG
|
||||||
|
)
|
||||||
|
|
||||||
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
||||||
POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/i/v0/e/"
|
POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/i/v0/e/"
|
||||||
@@ -291,8 +315,13 @@ def record(
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
properties = _safe_value(properties)
|
properties = _safe_value(properties)
|
||||||
|
# Stamped in the RECORDING process, beside harness. `source` used to be
|
||||||
|
# read in the sending process from a module global, so whichever process
|
||||||
|
# drained the spool named every event in it. flush() spreads per-event
|
||||||
|
# properties last, so this now wins over any sender's default.
|
||||||
properties.update(
|
properties.update(
|
||||||
harness=_harness,
|
harness=_harness,
|
||||||
|
source=_source_tag,
|
||||||
plugin_version=memory_core.PLUGIN_VERSION,
|
plugin_version=memory_core.PLUGIN_VERSION,
|
||||||
os=sys.platform,
|
os=sys.platform,
|
||||||
python_version=platform.python_version(),
|
python_version=platform.python_version(),
|
||||||
@@ -311,6 +340,7 @@ def record(
|
|||||||
line = json.dumps(
|
line = json.dumps(
|
||||||
{
|
{
|
||||||
"event": f"{EVENT_PREFIX}.{event}",
|
"event": f"{EVENT_PREFIX}.{event}",
|
||||||
|
"uuid": str(uuid.uuid4()),
|
||||||
"timestamp": memory_core.utc_now(),
|
"timestamp": memory_core.utc_now(),
|
||||||
"properties": {
|
"properties": {
|
||||||
key: value for key, value in properties.items() if value is not None
|
key: value for key, value in properties.items() if value is not None
|
||||||
@@ -496,6 +526,8 @@ def flush() -> int:
|
|||||||
"distinct_id": distinct_id,
|
"distinct_id": distinct_id,
|
||||||
"timestamp": event.get("timestamp"),
|
"timestamp": event.get("timestamp"),
|
||||||
"properties": {
|
"properties": {
|
||||||
|
# Fallback only: events recorded by a build before source
|
||||||
|
# moved into record() have none of their own.
|
||||||
"source": _source_tag,
|
"source": _source_tag,
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"$process_person_profile": False,
|
"$process_person_profile": False,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
"""Generated by integrations/agent-plugin-core/build/build.py. Do not edit."""
|
||||||
|
|
||||||
|
HARNESS_ID = "claude-code"
|
||||||
|
SOURCE_TAG = "CLAUDE_CODE_PLUGIN"
|
||||||
|
|
||||||
|
# Platform-side vocabulary (mem0_event.source + X-Application). The whole
|
||||||
|
# plugin family is one source; which editor it runs in is the application.
|
||||||
|
PLATFORM_SOURCE = "MEM0_PLUGIN"
|
||||||
|
PLATFORM_APPLICATION = "claude-code"
|
||||||
@@ -34,9 +34,24 @@ from typing import Any
|
|||||||
|
|
||||||
import memory_core
|
import memory_core
|
||||||
|
|
||||||
|
# Seeded from the per-host module the build generates into core/. Two processes
|
||||||
|
# in this pipeline never call init() — mcp_server.py, and the detached
|
||||||
|
# `python3 telemetry.py` sender that spawn_flush() starts — so a module default
|
||||||
|
# was what every one of their events got labelled with.
|
||||||
|
try: # pragma: no cover - absent only in the un-built shared source tree
|
||||||
|
from _harness_id import HARNESS_ID as _DEFAULT_HARNESS
|
||||||
|
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
|
||||||
|
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
|
||||||
|
from _harness_id import SOURCE_TAG as _DEFAULT_SOURCE_TAG
|
||||||
|
except ImportError:
|
||||||
|
_DEFAULT_HARNESS = "generic"
|
||||||
|
_DEFAULT_SOURCE_TAG = "MEM0_PLUGIN"
|
||||||
|
_PLATFORM_SOURCE = "MEM0_PLUGIN"
|
||||||
|
_PLATFORM_APPLICATION = ""
|
||||||
|
|
||||||
_salt_cache: str = ""
|
_salt_cache: str = ""
|
||||||
_harness: str = "generic"
|
_harness: str = _DEFAULT_HARNESS
|
||||||
_source_tag: str = "MEM0_PLUGIN"
|
_source_tag: str = _DEFAULT_SOURCE_TAG
|
||||||
_PRIVATE_KEYS = {
|
_PRIVATE_KEYS = {
|
||||||
"apikey",
|
"apikey",
|
||||||
"authorization",
|
"authorization",
|
||||||
@@ -62,10 +77,19 @@ _PRIVATE_KEYS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def init(harness: str = "generic", source_tag: str = "") -> None:
|
def init(harness: str = "", source_tag: str = "") -> None:
|
||||||
|
"""Override the generated identity. Optional — core/_harness_id.py is the default.
|
||||||
|
|
||||||
|
The fallback shape matches memory_core.configure_harness's (``<HOST>_PLUGIN``).
|
||||||
|
It used to be ``MEM0_<HOST>_PLUGIN`` here and ``<host>_plugin`` there, which
|
||||||
|
meant one plugin could emit three different source values depending on which
|
||||||
|
process happened to send the batch.
|
||||||
|
"""
|
||||||
global _harness, _source_tag
|
global _harness, _source_tag
|
||||||
_harness = harness
|
_harness = harness or _DEFAULT_HARNESS
|
||||||
_source_tag = source_tag or f"MEM0_{harness.upper().replace('-', '_')}_PLUGIN"
|
_source_tag = source_tag or (
|
||||||
|
f"{_harness.upper().replace('-', '_')}_PLUGIN" if harness else _DEFAULT_SOURCE_TAG
|
||||||
|
)
|
||||||
|
|
||||||
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
||||||
POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/i/v0/e/"
|
POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/i/v0/e/"
|
||||||
@@ -291,8 +315,13 @@ def record(
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
properties = _safe_value(properties)
|
properties = _safe_value(properties)
|
||||||
|
# Stamped in the RECORDING process, beside harness. `source` used to be
|
||||||
|
# read in the sending process from a module global, so whichever process
|
||||||
|
# drained the spool named every event in it. flush() spreads per-event
|
||||||
|
# properties last, so this now wins over any sender's default.
|
||||||
properties.update(
|
properties.update(
|
||||||
harness=_harness,
|
harness=_harness,
|
||||||
|
source=_source_tag,
|
||||||
plugin_version=memory_core.PLUGIN_VERSION,
|
plugin_version=memory_core.PLUGIN_VERSION,
|
||||||
os=sys.platform,
|
os=sys.platform,
|
||||||
python_version=platform.python_version(),
|
python_version=platform.python_version(),
|
||||||
@@ -311,6 +340,7 @@ def record(
|
|||||||
line = json.dumps(
|
line = json.dumps(
|
||||||
{
|
{
|
||||||
"event": f"{EVENT_PREFIX}.{event}",
|
"event": f"{EVENT_PREFIX}.{event}",
|
||||||
|
"uuid": str(uuid.uuid4()),
|
||||||
"timestamp": memory_core.utc_now(),
|
"timestamp": memory_core.utc_now(),
|
||||||
"properties": {
|
"properties": {
|
||||||
key: value for key, value in properties.items() if value is not None
|
key: value for key, value in properties.items() if value is not None
|
||||||
@@ -496,6 +526,8 @@ def flush() -> int:
|
|||||||
"distinct_id": distinct_id,
|
"distinct_id": distinct_id,
|
||||||
"timestamp": event.get("timestamp"),
|
"timestamp": event.get("timestamp"),
|
||||||
"properties": {
|
"properties": {
|
||||||
|
# Fallback only: events recorded by a build before source
|
||||||
|
# moved into record() have none of their own.
|
||||||
"source": _source_tag,
|
"source": _source_tag,
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"$process_person_profile": False,
|
"$process_person_profile": False,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
"""Generated by integrations/agent-plugin-core/build/build.py. Do not edit."""
|
||||||
|
|
||||||
|
HARNESS_ID = "codex"
|
||||||
|
SOURCE_TAG = "CODEX_PLUGIN"
|
||||||
|
|
||||||
|
# Platform-side vocabulary (mem0_event.source + X-Application). The whole
|
||||||
|
# plugin family is one source; which editor it runs in is the application.
|
||||||
|
PLATFORM_SOURCE = "MEM0_PLUGIN"
|
||||||
|
PLATFORM_APPLICATION = "codex"
|
||||||
@@ -34,9 +34,24 @@ from typing import Any
|
|||||||
|
|
||||||
import memory_core
|
import memory_core
|
||||||
|
|
||||||
|
# Seeded from the per-host module the build generates into core/. Two processes
|
||||||
|
# in this pipeline never call init() — mcp_server.py, and the detached
|
||||||
|
# `python3 telemetry.py` sender that spawn_flush() starts — so a module default
|
||||||
|
# was what every one of their events got labelled with.
|
||||||
|
try: # pragma: no cover - absent only in the un-built shared source tree
|
||||||
|
from _harness_id import HARNESS_ID as _DEFAULT_HARNESS
|
||||||
|
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
|
||||||
|
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
|
||||||
|
from _harness_id import SOURCE_TAG as _DEFAULT_SOURCE_TAG
|
||||||
|
except ImportError:
|
||||||
|
_DEFAULT_HARNESS = "generic"
|
||||||
|
_DEFAULT_SOURCE_TAG = "MEM0_PLUGIN"
|
||||||
|
_PLATFORM_SOURCE = "MEM0_PLUGIN"
|
||||||
|
_PLATFORM_APPLICATION = ""
|
||||||
|
|
||||||
_salt_cache: str = ""
|
_salt_cache: str = ""
|
||||||
_harness: str = "generic"
|
_harness: str = _DEFAULT_HARNESS
|
||||||
_source_tag: str = "MEM0_PLUGIN"
|
_source_tag: str = _DEFAULT_SOURCE_TAG
|
||||||
_PRIVATE_KEYS = {
|
_PRIVATE_KEYS = {
|
||||||
"apikey",
|
"apikey",
|
||||||
"authorization",
|
"authorization",
|
||||||
@@ -62,10 +77,19 @@ _PRIVATE_KEYS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def init(harness: str = "generic", source_tag: str = "") -> None:
|
def init(harness: str = "", source_tag: str = "") -> None:
|
||||||
|
"""Override the generated identity. Optional — core/_harness_id.py is the default.
|
||||||
|
|
||||||
|
The fallback shape matches memory_core.configure_harness's (``<HOST>_PLUGIN``).
|
||||||
|
It used to be ``MEM0_<HOST>_PLUGIN`` here and ``<host>_plugin`` there, which
|
||||||
|
meant one plugin could emit three different source values depending on which
|
||||||
|
process happened to send the batch.
|
||||||
|
"""
|
||||||
global _harness, _source_tag
|
global _harness, _source_tag
|
||||||
_harness = harness
|
_harness = harness or _DEFAULT_HARNESS
|
||||||
_source_tag = source_tag or f"MEM0_{harness.upper().replace('-', '_')}_PLUGIN"
|
_source_tag = source_tag or (
|
||||||
|
f"{_harness.upper().replace('-', '_')}_PLUGIN" if harness else _DEFAULT_SOURCE_TAG
|
||||||
|
)
|
||||||
|
|
||||||
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
||||||
POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/i/v0/e/"
|
POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/i/v0/e/"
|
||||||
@@ -291,8 +315,13 @@ def record(
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
properties = _safe_value(properties)
|
properties = _safe_value(properties)
|
||||||
|
# Stamped in the RECORDING process, beside harness. `source` used to be
|
||||||
|
# read in the sending process from a module global, so whichever process
|
||||||
|
# drained the spool named every event in it. flush() spreads per-event
|
||||||
|
# properties last, so this now wins over any sender's default.
|
||||||
properties.update(
|
properties.update(
|
||||||
harness=_harness,
|
harness=_harness,
|
||||||
|
source=_source_tag,
|
||||||
plugin_version=memory_core.PLUGIN_VERSION,
|
plugin_version=memory_core.PLUGIN_VERSION,
|
||||||
os=sys.platform,
|
os=sys.platform,
|
||||||
python_version=platform.python_version(),
|
python_version=platform.python_version(),
|
||||||
@@ -311,6 +340,7 @@ def record(
|
|||||||
line = json.dumps(
|
line = json.dumps(
|
||||||
{
|
{
|
||||||
"event": f"{EVENT_PREFIX}.{event}",
|
"event": f"{EVENT_PREFIX}.{event}",
|
||||||
|
"uuid": str(uuid.uuid4()),
|
||||||
"timestamp": memory_core.utc_now(),
|
"timestamp": memory_core.utc_now(),
|
||||||
"properties": {
|
"properties": {
|
||||||
key: value for key, value in properties.items() if value is not None
|
key: value for key, value in properties.items() if value is not None
|
||||||
@@ -496,6 +526,8 @@ def flush() -> int:
|
|||||||
"distinct_id": distinct_id,
|
"distinct_id": distinct_id,
|
||||||
"timestamp": event.get("timestamp"),
|
"timestamp": event.get("timestamp"),
|
||||||
"properties": {
|
"properties": {
|
||||||
|
# Fallback only: events recorded by a build before source
|
||||||
|
# moved into record() have none of their own.
|
||||||
"source": _source_tag,
|
"source": _source_tag,
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"$process_person_profile": False,
|
"$process_person_profile": False,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
"""Generated by integrations/agent-plugin-core/build/build.py. Do not edit."""
|
||||||
|
|
||||||
|
HARNESS_ID = "cursor"
|
||||||
|
SOURCE_TAG = "CURSOR_PLUGIN"
|
||||||
|
|
||||||
|
# Platform-side vocabulary (mem0_event.source + X-Application). The whole
|
||||||
|
# plugin family is one source; which editor it runs in is the application.
|
||||||
|
PLATFORM_SOURCE = "MEM0_PLUGIN"
|
||||||
|
PLATFORM_APPLICATION = "cursor"
|
||||||
@@ -34,9 +34,24 @@ from typing import Any
|
|||||||
|
|
||||||
import memory_core
|
import memory_core
|
||||||
|
|
||||||
|
# Seeded from the per-host module the build generates into core/. Two processes
|
||||||
|
# in this pipeline never call init() — mcp_server.py, and the detached
|
||||||
|
# `python3 telemetry.py` sender that spawn_flush() starts — so a module default
|
||||||
|
# was what every one of their events got labelled with.
|
||||||
|
try: # pragma: no cover - absent only in the un-built shared source tree
|
||||||
|
from _harness_id import HARNESS_ID as _DEFAULT_HARNESS
|
||||||
|
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
|
||||||
|
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
|
||||||
|
from _harness_id import SOURCE_TAG as _DEFAULT_SOURCE_TAG
|
||||||
|
except ImportError:
|
||||||
|
_DEFAULT_HARNESS = "generic"
|
||||||
|
_DEFAULT_SOURCE_TAG = "MEM0_PLUGIN"
|
||||||
|
_PLATFORM_SOURCE = "MEM0_PLUGIN"
|
||||||
|
_PLATFORM_APPLICATION = ""
|
||||||
|
|
||||||
_salt_cache: str = ""
|
_salt_cache: str = ""
|
||||||
_harness: str = "generic"
|
_harness: str = _DEFAULT_HARNESS
|
||||||
_source_tag: str = "MEM0_PLUGIN"
|
_source_tag: str = _DEFAULT_SOURCE_TAG
|
||||||
_PRIVATE_KEYS = {
|
_PRIVATE_KEYS = {
|
||||||
"apikey",
|
"apikey",
|
||||||
"authorization",
|
"authorization",
|
||||||
@@ -62,10 +77,19 @@ _PRIVATE_KEYS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def init(harness: str = "generic", source_tag: str = "") -> None:
|
def init(harness: str = "", source_tag: str = "") -> None:
|
||||||
|
"""Override the generated identity. Optional — core/_harness_id.py is the default.
|
||||||
|
|
||||||
|
The fallback shape matches memory_core.configure_harness's (``<HOST>_PLUGIN``).
|
||||||
|
It used to be ``MEM0_<HOST>_PLUGIN`` here and ``<host>_plugin`` there, which
|
||||||
|
meant one plugin could emit three different source values depending on which
|
||||||
|
process happened to send the batch.
|
||||||
|
"""
|
||||||
global _harness, _source_tag
|
global _harness, _source_tag
|
||||||
_harness = harness
|
_harness = harness or _DEFAULT_HARNESS
|
||||||
_source_tag = source_tag or f"MEM0_{harness.upper().replace('-', '_')}_PLUGIN"
|
_source_tag = source_tag or (
|
||||||
|
f"{_harness.upper().replace('-', '_')}_PLUGIN" if harness else _DEFAULT_SOURCE_TAG
|
||||||
|
)
|
||||||
|
|
||||||
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
||||||
POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/i/v0/e/"
|
POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/i/v0/e/"
|
||||||
@@ -291,8 +315,13 @@ def record(
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
properties = _safe_value(properties)
|
properties = _safe_value(properties)
|
||||||
|
# Stamped in the RECORDING process, beside harness. `source` used to be
|
||||||
|
# read in the sending process from a module global, so whichever process
|
||||||
|
# drained the spool named every event in it. flush() spreads per-event
|
||||||
|
# properties last, so this now wins over any sender's default.
|
||||||
properties.update(
|
properties.update(
|
||||||
harness=_harness,
|
harness=_harness,
|
||||||
|
source=_source_tag,
|
||||||
plugin_version=memory_core.PLUGIN_VERSION,
|
plugin_version=memory_core.PLUGIN_VERSION,
|
||||||
os=sys.platform,
|
os=sys.platform,
|
||||||
python_version=platform.python_version(),
|
python_version=platform.python_version(),
|
||||||
@@ -311,6 +340,7 @@ def record(
|
|||||||
line = json.dumps(
|
line = json.dumps(
|
||||||
{
|
{
|
||||||
"event": f"{EVENT_PREFIX}.{event}",
|
"event": f"{EVENT_PREFIX}.{event}",
|
||||||
|
"uuid": str(uuid.uuid4()),
|
||||||
"timestamp": memory_core.utc_now(),
|
"timestamp": memory_core.utc_now(),
|
||||||
"properties": {
|
"properties": {
|
||||||
key: value for key, value in properties.items() if value is not None
|
key: value for key, value in properties.items() if value is not None
|
||||||
@@ -496,6 +526,8 @@ def flush() -> int:
|
|||||||
"distinct_id": distinct_id,
|
"distinct_id": distinct_id,
|
||||||
"timestamp": event.get("timestamp"),
|
"timestamp": event.get("timestamp"),
|
||||||
"properties": {
|
"properties": {
|
||||||
|
# Fallback only: events recorded by a build before source
|
||||||
|
# moved into record() have none of their own.
|
||||||
"source": _source_tag,
|
"source": _source_tag,
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"$process_person_profile": False,
|
"$process_person_profile": False,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
"""Generated by integrations/agent-plugin-core/build/build.py. Do not edit."""
|
||||||
|
|
||||||
|
HARNESS_ID = "kimi"
|
||||||
|
SOURCE_TAG = "KIMI_PLUGIN"
|
||||||
|
|
||||||
|
# Platform-side vocabulary (mem0_event.source + X-Application). The whole
|
||||||
|
# plugin family is one source; which editor it runs in is the application.
|
||||||
|
PLATFORM_SOURCE = "MEM0_PLUGIN"
|
||||||
|
PLATFORM_APPLICATION = "kimi"
|
||||||
@@ -34,9 +34,24 @@ from typing import Any
|
|||||||
|
|
||||||
import memory_core
|
import memory_core
|
||||||
|
|
||||||
|
# Seeded from the per-host module the build generates into core/. Two processes
|
||||||
|
# in this pipeline never call init() — mcp_server.py, and the detached
|
||||||
|
# `python3 telemetry.py` sender that spawn_flush() starts — so a module default
|
||||||
|
# was what every one of their events got labelled with.
|
||||||
|
try: # pragma: no cover - absent only in the un-built shared source tree
|
||||||
|
from _harness_id import HARNESS_ID as _DEFAULT_HARNESS
|
||||||
|
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
|
||||||
|
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
|
||||||
|
from _harness_id import SOURCE_TAG as _DEFAULT_SOURCE_TAG
|
||||||
|
except ImportError:
|
||||||
|
_DEFAULT_HARNESS = "generic"
|
||||||
|
_DEFAULT_SOURCE_TAG = "MEM0_PLUGIN"
|
||||||
|
_PLATFORM_SOURCE = "MEM0_PLUGIN"
|
||||||
|
_PLATFORM_APPLICATION = ""
|
||||||
|
|
||||||
_salt_cache: str = ""
|
_salt_cache: str = ""
|
||||||
_harness: str = "generic"
|
_harness: str = _DEFAULT_HARNESS
|
||||||
_source_tag: str = "MEM0_PLUGIN"
|
_source_tag: str = _DEFAULT_SOURCE_TAG
|
||||||
_PRIVATE_KEYS = {
|
_PRIVATE_KEYS = {
|
||||||
"apikey",
|
"apikey",
|
||||||
"authorization",
|
"authorization",
|
||||||
@@ -62,10 +77,19 @@ _PRIVATE_KEYS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def init(harness: str = "generic", source_tag: str = "") -> None:
|
def init(harness: str = "", source_tag: str = "") -> None:
|
||||||
|
"""Override the generated identity. Optional — core/_harness_id.py is the default.
|
||||||
|
|
||||||
|
The fallback shape matches memory_core.configure_harness's (``<HOST>_PLUGIN``).
|
||||||
|
It used to be ``MEM0_<HOST>_PLUGIN`` here and ``<host>_plugin`` there, which
|
||||||
|
meant one plugin could emit three different source values depending on which
|
||||||
|
process happened to send the batch.
|
||||||
|
"""
|
||||||
global _harness, _source_tag
|
global _harness, _source_tag
|
||||||
_harness = harness
|
_harness = harness or _DEFAULT_HARNESS
|
||||||
_source_tag = source_tag or f"MEM0_{harness.upper().replace('-', '_')}_PLUGIN"
|
_source_tag = source_tag or (
|
||||||
|
f"{_harness.upper().replace('-', '_')}_PLUGIN" if harness else _DEFAULT_SOURCE_TAG
|
||||||
|
)
|
||||||
|
|
||||||
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
||||||
POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/i/v0/e/"
|
POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/i/v0/e/"
|
||||||
@@ -291,8 +315,13 @@ def record(
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
properties = _safe_value(properties)
|
properties = _safe_value(properties)
|
||||||
|
# Stamped in the RECORDING process, beside harness. `source` used to be
|
||||||
|
# read in the sending process from a module global, so whichever process
|
||||||
|
# drained the spool named every event in it. flush() spreads per-event
|
||||||
|
# properties last, so this now wins over any sender's default.
|
||||||
properties.update(
|
properties.update(
|
||||||
harness=_harness,
|
harness=_harness,
|
||||||
|
source=_source_tag,
|
||||||
plugin_version=memory_core.PLUGIN_VERSION,
|
plugin_version=memory_core.PLUGIN_VERSION,
|
||||||
os=sys.platform,
|
os=sys.platform,
|
||||||
python_version=platform.python_version(),
|
python_version=platform.python_version(),
|
||||||
@@ -311,6 +340,7 @@ def record(
|
|||||||
line = json.dumps(
|
line = json.dumps(
|
||||||
{
|
{
|
||||||
"event": f"{EVENT_PREFIX}.{event}",
|
"event": f"{EVENT_PREFIX}.{event}",
|
||||||
|
"uuid": str(uuid.uuid4()),
|
||||||
"timestamp": memory_core.utc_now(),
|
"timestamp": memory_core.utc_now(),
|
||||||
"properties": {
|
"properties": {
|
||||||
key: value for key, value in properties.items() if value is not None
|
key: value for key, value in properties.items() if value is not None
|
||||||
@@ -496,6 +526,8 @@ def flush() -> int:
|
|||||||
"distinct_id": distinct_id,
|
"distinct_id": distinct_id,
|
||||||
"timestamp": event.get("timestamp"),
|
"timestamp": event.get("timestamp"),
|
||||||
"properties": {
|
"properties": {
|
||||||
|
# Fallback only: events recorded by a build before source
|
||||||
|
# moved into record() have none of their own.
|
||||||
"source": _source_tag,
|
"source": _source_tag,
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"$process_person_profile": False,
|
"$process_person_profile": False,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
"""Generated by integrations/agent-plugin-core/build/build.py. Do not edit."""
|
||||||
|
|
||||||
|
HARNESS_ID = "coding-agent"
|
||||||
|
SOURCE_TAG = "CODING_AGENT_PLUGIN"
|
||||||
|
|
||||||
|
# Platform-side vocabulary (mem0_event.source + X-Application). The whole
|
||||||
|
# plugin family is one source; which editor it runs in is the application.
|
||||||
|
PLATFORM_SOURCE = "MEM0_PLUGIN"
|
||||||
|
PLATFORM_APPLICATION = "coding-agent"
|
||||||
@@ -34,9 +34,24 @@ from typing import Any
|
|||||||
|
|
||||||
import memory_core
|
import memory_core
|
||||||
|
|
||||||
|
# Seeded from the per-host module the build generates into core/. Two processes
|
||||||
|
# in this pipeline never call init() — mcp_server.py, and the detached
|
||||||
|
# `python3 telemetry.py` sender that spawn_flush() starts — so a module default
|
||||||
|
# was what every one of their events got labelled with.
|
||||||
|
try: # pragma: no cover - absent only in the un-built shared source tree
|
||||||
|
from _harness_id import HARNESS_ID as _DEFAULT_HARNESS
|
||||||
|
from _harness_id import PLATFORM_APPLICATION as _PLATFORM_APPLICATION
|
||||||
|
from _harness_id import PLATFORM_SOURCE as _PLATFORM_SOURCE
|
||||||
|
from _harness_id import SOURCE_TAG as _DEFAULT_SOURCE_TAG
|
||||||
|
except ImportError:
|
||||||
|
_DEFAULT_HARNESS = "generic"
|
||||||
|
_DEFAULT_SOURCE_TAG = "MEM0_PLUGIN"
|
||||||
|
_PLATFORM_SOURCE = "MEM0_PLUGIN"
|
||||||
|
_PLATFORM_APPLICATION = ""
|
||||||
|
|
||||||
_salt_cache: str = ""
|
_salt_cache: str = ""
|
||||||
_harness: str = "generic"
|
_harness: str = _DEFAULT_HARNESS
|
||||||
_source_tag: str = "MEM0_PLUGIN"
|
_source_tag: str = _DEFAULT_SOURCE_TAG
|
||||||
_PRIVATE_KEYS = {
|
_PRIVATE_KEYS = {
|
||||||
"apikey",
|
"apikey",
|
||||||
"authorization",
|
"authorization",
|
||||||
@@ -62,10 +77,19 @@ _PRIVATE_KEYS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def init(harness: str = "generic", source_tag: str = "") -> None:
|
def init(harness: str = "", source_tag: str = "") -> None:
|
||||||
|
"""Override the generated identity. Optional — core/_harness_id.py is the default.
|
||||||
|
|
||||||
|
The fallback shape matches memory_core.configure_harness's (``<HOST>_PLUGIN``).
|
||||||
|
It used to be ``MEM0_<HOST>_PLUGIN`` here and ``<host>_plugin`` there, which
|
||||||
|
meant one plugin could emit three different source values depending on which
|
||||||
|
process happened to send the batch.
|
||||||
|
"""
|
||||||
global _harness, _source_tag
|
global _harness, _source_tag
|
||||||
_harness = harness
|
_harness = harness or _DEFAULT_HARNESS
|
||||||
_source_tag = source_tag or f"MEM0_{harness.upper().replace('-', '_')}_PLUGIN"
|
_source_tag = source_tag or (
|
||||||
|
f"{_harness.upper().replace('-', '_')}_PLUGIN" if harness else _DEFAULT_SOURCE_TAG
|
||||||
|
)
|
||||||
|
|
||||||
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
||||||
POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/i/v0/e/"
|
POSTHOG_CAPTURE_URL = "https://us.i.posthog.com/i/v0/e/"
|
||||||
@@ -291,8 +315,13 @@ def record(
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
properties = _safe_value(properties)
|
properties = _safe_value(properties)
|
||||||
|
# Stamped in the RECORDING process, beside harness. `source` used to be
|
||||||
|
# read in the sending process from a module global, so whichever process
|
||||||
|
# drained the spool named every event in it. flush() spreads per-event
|
||||||
|
# properties last, so this now wins over any sender's default.
|
||||||
properties.update(
|
properties.update(
|
||||||
harness=_harness,
|
harness=_harness,
|
||||||
|
source=_source_tag,
|
||||||
plugin_version=memory_core.PLUGIN_VERSION,
|
plugin_version=memory_core.PLUGIN_VERSION,
|
||||||
os=sys.platform,
|
os=sys.platform,
|
||||||
python_version=platform.python_version(),
|
python_version=platform.python_version(),
|
||||||
@@ -311,6 +340,7 @@ def record(
|
|||||||
line = json.dumps(
|
line = json.dumps(
|
||||||
{
|
{
|
||||||
"event": f"{EVENT_PREFIX}.{event}",
|
"event": f"{EVENT_PREFIX}.{event}",
|
||||||
|
"uuid": str(uuid.uuid4()),
|
||||||
"timestamp": memory_core.utc_now(),
|
"timestamp": memory_core.utc_now(),
|
||||||
"properties": {
|
"properties": {
|
||||||
key: value for key, value in properties.items() if value is not None
|
key: value for key, value in properties.items() if value is not None
|
||||||
@@ -496,6 +526,8 @@ def flush() -> int:
|
|||||||
"distinct_id": distinct_id,
|
"distinct_id": distinct_id,
|
||||||
"timestamp": event.get("timestamp"),
|
"timestamp": event.get("timestamp"),
|
||||||
"properties": {
|
"properties": {
|
||||||
|
# Fallback only: events recorded by a build before source
|
||||||
|
# moved into record() have none of their own.
|
||||||
"source": _source_tag,
|
"source": _source_tag,
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"$process_person_profile": False,
|
"$process_person_profile": False,
|
||||||
|
|||||||
Reference in New Issue
Block a user