loki-mode 7.0.1 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +4 -4
- package/VERSION +1 -1
- package/autonomy/run.sh +51 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/static/index.html +464 -73
- package/docs/INSTALLATION.md +1 -1
- package/mcp/__init__.py +1 -1
- package/mcp/managed_tools.py +11 -0
- package/memory/managed_memory/client.py +30 -5
- package/memory/managed_memory/events.py +48 -1
- package/package.json +1 -1
- package/providers/managed.py +1 -1
- package/skills/memory.md +6 -4
package/docs/INSTALLATION.md
CHANGED
package/mcp/__init__.py
CHANGED
package/mcp/managed_tools.py
CHANGED
|
@@ -98,6 +98,17 @@ def redact_memory_versions(
|
|
|
98
98
|
"scanned": 0,
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
# v7.0.2: bound pattern length to mitigate ReDoS. Catastrophic-backtracking
|
|
102
|
+
# patterns like (a+)+$ can hang the MCP server. 512 chars is generous for
|
|
103
|
+
# legitimate compliance/PII patterns.
|
|
104
|
+
if not isinstance(pattern, str) or len(pattern) > 512:
|
|
105
|
+
return {
|
|
106
|
+
"error": "pattern must be a string of <=512 characters (ReDoS guard)",
|
|
107
|
+
"redacted_count": 0,
|
|
108
|
+
"errors": [],
|
|
109
|
+
"scanned": 0,
|
|
110
|
+
}
|
|
111
|
+
|
|
101
112
|
try:
|
|
102
113
|
compiled = re.compile(pattern)
|
|
103
114
|
except re.error as e:
|
|
@@ -101,7 +101,12 @@ class ManagedClient:
|
|
|
101
101
|
stores = getattr(beta, "memory_stores", None) or getattr(beta, "stores", None)
|
|
102
102
|
if stores is None or not hasattr(stores, "list"):
|
|
103
103
|
raise ManagedDisabled("memory_stores API not available in SDK")
|
|
104
|
-
|
|
104
|
+
try:
|
|
105
|
+
result = stores.list()
|
|
106
|
+
except ManagedDisabled:
|
|
107
|
+
raise
|
|
108
|
+
except Exception as e:
|
|
109
|
+
raise ManagedDisabled(f"stores_list failed: {e!s}") from e
|
|
105
110
|
# SDK returns a pydantic model; normalize to list of dicts.
|
|
106
111
|
data = getattr(result, "data", result)
|
|
107
112
|
return [self._to_dict(x) for x in (data or [])]
|
|
@@ -117,7 +122,12 @@ class ManagedClient:
|
|
|
117
122
|
stores = getattr(beta, "memory_stores", None) or getattr(beta, "stores", None)
|
|
118
123
|
if stores is None or not hasattr(stores, "create"):
|
|
119
124
|
raise ManagedDisabled("memory_stores.create not available in SDK")
|
|
120
|
-
|
|
125
|
+
try:
|
|
126
|
+
created = stores.create(name=name, description=description, scope=scope)
|
|
127
|
+
except ManagedDisabled:
|
|
128
|
+
raise
|
|
129
|
+
except Exception as e:
|
|
130
|
+
raise ManagedDisabled(f"stores_get_or_create failed: {e!s}") from e
|
|
121
131
|
return self._to_dict(created)
|
|
122
132
|
|
|
123
133
|
# ---------- memories ------------------------------------------------
|
|
@@ -148,7 +158,12 @@ class ManagedClient:
|
|
|
148
158
|
}
|
|
149
159
|
if sha256_precondition:
|
|
150
160
|
kwargs["if_match_sha256"] = sha256_precondition
|
|
151
|
-
|
|
161
|
+
try:
|
|
162
|
+
created = memories.create(**kwargs)
|
|
163
|
+
except ManagedDisabled:
|
|
164
|
+
raise
|
|
165
|
+
except Exception as e:
|
|
166
|
+
raise ManagedDisabled(f"memory_create failed: {e!s}") from e
|
|
152
167
|
return self._to_dict(created)
|
|
153
168
|
|
|
154
169
|
def memory_read(self, store_id: str, memory_id: str) -> Dict[str, Any]:
|
|
@@ -156,7 +171,12 @@ class ManagedClient:
|
|
|
156
171
|
memories = getattr(beta, "memories", None)
|
|
157
172
|
if memories is None or not hasattr(memories, "retrieve"):
|
|
158
173
|
raise ManagedDisabled("memories.retrieve not available in SDK")
|
|
159
|
-
|
|
174
|
+
try:
|
|
175
|
+
got = memories.retrieve(store_id=store_id, memory_id=memory_id)
|
|
176
|
+
except ManagedDisabled:
|
|
177
|
+
raise
|
|
178
|
+
except Exception as e:
|
|
179
|
+
raise ManagedDisabled(f"memory_read failed: {e!s}") from e
|
|
160
180
|
return self._to_dict(got)
|
|
161
181
|
|
|
162
182
|
def memories_list(
|
|
@@ -169,7 +189,12 @@ class ManagedClient:
|
|
|
169
189
|
kwargs: Dict[str, Any] = {"store_id": store_id}
|
|
170
190
|
if path_prefix:
|
|
171
191
|
kwargs["path_prefix"] = path_prefix
|
|
172
|
-
|
|
192
|
+
try:
|
|
193
|
+
result = memories.list(**kwargs)
|
|
194
|
+
except ManagedDisabled:
|
|
195
|
+
raise
|
|
196
|
+
except Exception as e:
|
|
197
|
+
raise ManagedDisabled(f"memories_list failed: {e!s}") from e
|
|
173
198
|
data = getattr(result, "data", result)
|
|
174
199
|
return [self._to_dict(x) for x in (data or [])]
|
|
175
200
|
|
|
@@ -21,6 +21,45 @@ from typing import Any, Dict, Optional
|
|
|
21
21
|
_ROTATE_BYTES = 10 * 1024 * 1024
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
def _load_loki_version() -> str:
|
|
25
|
+
"""
|
|
26
|
+
Read the repo VERSION file once at module import.
|
|
27
|
+
|
|
28
|
+
Resolves to <repo_root>/VERSION assuming this file lives at
|
|
29
|
+
<repo_root>/memory/managed_memory/events.py. If the file is missing
|
|
30
|
+
or unreadable for any reason, returns the literal string "unknown"
|
|
31
|
+
so the correlation field is ALWAYS present on emitted events.
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
version_path = Path(__file__).resolve().parents[2] / "VERSION"
|
|
35
|
+
return version_path.read_text(encoding="utf-8").strip() or "unknown"
|
|
36
|
+
except Exception:
|
|
37
|
+
return "unknown"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Cached at module load: never re-read per-event. The repo version does not
|
|
41
|
+
# change during a single Python process lifetime.
|
|
42
|
+
_LOKI_VERSION: str = _load_loki_version()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _correlation_stamp() -> Dict[str, Any]:
|
|
46
|
+
"""
|
|
47
|
+
Build the correlation fields to merge into every payload.
|
|
48
|
+
|
|
49
|
+
Reads LOKI_ITERATION_COUNT and LOKI_SESSION_ID from the environment.
|
|
50
|
+
Missing env vars are OMITTED (not set to None) so callers can tell
|
|
51
|
+
"unset" apart from "explicitly null". loki_version is always present.
|
|
52
|
+
"""
|
|
53
|
+
stamp: Dict[str, Any] = {"loki_version": _LOKI_VERSION}
|
|
54
|
+
iteration = os.environ.get("LOKI_ITERATION_COUNT")
|
|
55
|
+
if iteration is not None and iteration != "":
|
|
56
|
+
stamp["iteration_id"] = iteration
|
|
57
|
+
session = os.environ.get("LOKI_SESSION_ID")
|
|
58
|
+
if session is not None and session != "":
|
|
59
|
+
stamp["session_id"] = session
|
|
60
|
+
return stamp
|
|
61
|
+
|
|
62
|
+
|
|
24
63
|
def _events_dir(target_dir: Optional[str] = None) -> Path:
|
|
25
64
|
base = target_dir or os.environ.get("LOKI_TARGET_DIR") or os.getcwd()
|
|
26
65
|
return Path(base) / ".loki" / "managed"
|
|
@@ -66,10 +105,18 @@ def emit_managed_event(
|
|
|
66
105
|
path = dir_path / "events.ndjson"
|
|
67
106
|
_maybe_rotate(path)
|
|
68
107
|
|
|
108
|
+
# Stamp correlation fields (loki_version always; iteration_id and
|
|
109
|
+
# session_id when env vars are set). Caller-supplied keys win: if
|
|
110
|
+
# the payload already carries iteration_id / session_id / loki_version
|
|
111
|
+
# the explicit caller value is preserved. Single-writer invariant
|
|
112
|
+
# is preserved because we only mutate the in-memory dict here; the
|
|
113
|
+
# write path (file handle, lock-free append) is unchanged.
|
|
114
|
+
merged_payload = {**_correlation_stamp(), **(payload or {})}
|
|
115
|
+
|
|
69
116
|
record = {
|
|
70
117
|
"ts": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
|
71
118
|
"type": event_type,
|
|
72
|
-
"payload":
|
|
119
|
+
"payload": merged_payload,
|
|
73
120
|
}
|
|
74
121
|
# Line-buffered append; JSONL.
|
|
75
122
|
with open(path, "a", encoding="utf-8") as f:
|
package/package.json
CHANGED
package/providers/managed.py
CHANGED
package/skills/memory.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Memory Integration (v7.
|
|
1
|
+
# Memory Integration (v7.1.0+)
|
|
2
2
|
|
|
3
3
|
Loki Mode ships two memory layers:
|
|
4
4
|
|
|
@@ -109,8 +109,10 @@ With managed memory on, a new project gets access to:
|
|
|
109
109
|
- `.loki/memory/skills/*.json` (org store, RO)
|
|
110
110
|
- `.loki/memory/anti_patterns/*.json` (org store, RO)
|
|
111
111
|
|
|
112
|
-
Promotions from user-RW to org-RO
|
|
113
|
-
|
|
112
|
+
Promotions from user-RW to org-RO are MANUAL only at v7.0.x. Use the
|
|
113
|
+
Managed Agents API directly (`memory_stores.memories.create` against the
|
|
114
|
+
org store) with human review. An MCP `loki_memory_promote` tool is on
|
|
115
|
+
the roadmap but NOT shipped in v7.0.x; do not depend on it. Never
|
|
114
116
|
auto-promote.
|
|
115
117
|
|
|
116
118
|
## PII redaction
|
|
@@ -182,4 +184,4 @@ Dashboard endpoints (read-only, view-layer merge):
|
|
|
182
184
|
|
|
183
185
|
---
|
|
184
186
|
|
|
185
|
-
**v7.
|
|
187
|
+
**v7.1.0** | Opt-in, additive, rollback-safe. Default behavior unchanged.
|