loki-mode 10.10.5 → 10.11.2
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 +2 -2
- package/VERSION +1 -1
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-REFERENCE.md +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/v10/BOARD.md +6 -4
- package/docs/v10/CP-ASK-PLAN.md +85 -0
- package/docs/v10/DEPS.md +3 -0
- package/docs/v10/FAILURE-CLASSES.md +32 -0
- package/docs/v10/FC21B-FC22-PLAN.md +107 -0
- package/docs/v10/FOUNDER-QUEUE.md +2 -0
- package/docs/v10/METRICS.md +1 -0
- package/docs/v10/PROGRESS.md +21 -0
- package/loki-ts/dist/loki.js +683 -678
- package/mcp/__init__.py +1 -1
- package/mcp/server.py +86 -3
- package/mcp/tests/_read_only_wall_probe.py +156 -0
- package/mcp/tests/test_read_only.py +289 -0
- package/memory/storage.py +41 -3
- package/package.json +1 -1
- package/packages/control-plane/dist/server.js +289 -210
- package/packages/control-plane/drizzle/0005_ask.sql +39 -0
- package/packages/control-plane/drizzle/meta/0005_snapshot.json +800 -0
- package/packages/control-plane/drizzle/meta/_journal.json +7 -0
- package/packages/control-plane/package.json +3 -1
- package/packages/control-plane/src/ask/parse.ts +120 -0
- package/packages/control-plane/src/ask/tools.ts +111 -0
- package/packages/control-plane/src/ask/tools_server.ts +46 -0
- package/packages/control-plane/src/db/schema.ts +43 -1
- package/packages/control-plane/src/server/routes/artifacts.ts +3 -3
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/web-app/dist/assets/{AdminPage-qj6cyTT6.js → AdminPage-CmgIgBFY.js} +1 -1
- package/web-app/dist/assets/{Avatar-Dg4WrYYd.js → Avatar-Dm3ypJzh.js} +1 -1
- package/web-app/dist/assets/{Badge-DHiwSg-p.js → Badge-0siYUwAA.js} +1 -1
- package/web-app/dist/assets/{Button-gi8XEzIU.js → Button-EMKQXvZe.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-B1QNjPBn.js → CockpitPage-Z60eDvJ9.js} +1 -1
- package/web-app/dist/assets/{ComparePage-DW-oUaDu.js → ComparePage-D4L7nViy.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-BK5zjqn-.js → ErrorBoundary--v9DugrJ.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-F58cuFm9.js → EvidenceReceiptPanel-CA866XsM.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-QVyvF19M.js → GitHubIssuesPanel-Bi6FJmR2.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-BRFHMzwy.js → GitHubPRsPanel-CgSfcWOg.js} +1 -1
- package/web-app/dist/assets/{HomePage-Cpskd4yM.js → HomePage-BxQtk9Ec.js} +2 -2
- package/web-app/dist/assets/{LoginPage-CteYWi5M.js → LoginPage-CCSxWlFA.js} +1 -1
- package/web-app/dist/assets/{MagicPage-CCLor0AU.js → MagicPage-B4eYm0jJ.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-WgsOaLoq.js → MetricsPage-Bp4Hh0p2.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-DXSCr9P6.js → NotFoundPage-DTXk_JK4.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-sR0kaaXV.js → ProjectPage-6S1v0GHS.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-J3TNS42-.js → ProjectsPage-9foqEwG2.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-Cil5837P.js → SettingsPage-CWOG_hhD.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-C3_oWYJa.js → ShowcasePage-Du9LYlao.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-Sddw1fFS.js → SystemSettingsPage-BQconaDX.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-B0poySCt.js → TeamsPage-_NsqtVmC.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-pcYSxHam.js → TemplatesPage-DyTEGBpR.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-CRWTZM7F.js → TerminalOutput-XHOYTpky.js} +1 -1
- package/web-app/dist/assets/{activity-D3aR95zO.js → activity-B9KiMdEs.js} +1 -1
- package/web-app/dist/assets/{bell-WdWaPIBo.js → bell-Oe4lG6kK.js} +1 -1
- package/web-app/dist/assets/{bot-CzD3xokc.js → bot-D86Qmxfy.js} +1 -1
- package/web-app/dist/assets/{check-DMYJiYcm.js → check-BvW6_ni0.js} +1 -1
- package/web-app/dist/assets/{chevron-left-DY0FSKvI.js → chevron-left-DYA62-3g.js} +1 -1
- package/web-app/dist/assets/{circle-alert-DVPtG3Pe.js → circle-alert-CHW8ye4p.js} +1 -1
- package/web-app/dist/assets/{clock-BjjLL6uI.js → clock-QbQ2XfbM.js} +1 -1
- package/web-app/dist/assets/{cloud-CnUCVZhm.js → cloud-ylzrY7eL.js} +1 -1
- package/web-app/dist/assets/{code-xml-BPoxcFNK.js → code-xml-DdKKw2Y9.js} +1 -1
- package/web-app/dist/assets/{database-ClVgDBYt.js → database-BLY_OWs3.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-Ui4BIml8.js → dollar-sign-CsFIa1e2.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-BNYl7J1Z.js → file-code-corner-Df4nyrCW.js} +1 -1
- package/web-app/dist/assets/{file-plus-BxkSyRVn.js → file-plus-_MWEY2WY.js} +1 -1
- package/web-app/dist/assets/{globe-b2poyDKT.js → globe-Dpc7QU9m.js} +1 -1
- package/web-app/dist/assets/{hammer-D_0BUpBG.js → hammer-B-PldT0c.js} +1 -1
- package/web-app/dist/assets/{index-D8TYtITk.js → index-gq0R3j-o.js} +3 -3
- package/web-app/dist/assets/{layers-Co1szEIf.js → layers-BjcNwHLZ.js} +1 -1
- package/web-app/dist/assets/{loader-circle-DylW-nwq.js → loader-circle-CoTgcqkV.js} +1 -1
- package/web-app/dist/assets/{lock-D7aFdKw7.js → lock-JEI8O6TS.js} +1 -1
- package/web-app/dist/assets/{package-ElSsRKiN.js → package-CXWkNS40.js} +1 -1
- package/web-app/dist/assets/{plus-CVWgRxDX.js → plus-CRK4U5b0.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-B2azSZHT.js → refresh-cw-CrzWr4cw.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-BdQHOrf0.js → rotate-ccw-DV7UZyUw.js} +1 -1
- package/web-app/dist/assets/{scroll-text-9m-YKf83.js → scroll-text-CXEqRrXU.js} +1 -1
- package/web-app/dist/assets/{server-BYq99Fe1.js → server-CDFfFP10.js} +1 -1
- package/web-app/dist/assets/{shield-alert-BNCiHXhR.js → shield-alert-B5EC_m6_.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-BB_EJkat.js → thumbs-up-BeLDemd2.js} +1 -1
- package/web-app/dist/assets/{trash-2-BMF4kFvy.js → trash-2-B7kS86-0.js} +1 -1
- package/web-app/dist/assets/{trending-down-BiqiTPXx.js → trending-down-BGvuLayo.js} +1 -1
- package/web-app/dist/assets/{trending-up-Bq6qyKub.js → trending-up-BkPM2tiK.js} +1 -1
- package/web-app/dist/assets/{upload-1HzIJHHR.js → upload-CXVI0GRU.js} +1 -1
- package/web-app/dist/assets/{usePolling-FNwpjURt.js → usePolling-CMGMVsMA.js} +1 -1
- package/web-app/dist/assets/{user-DrORfQv3.js → user-DPcXNtfj.js} +1 -1
- package/web-app/dist/index.html +1 -1
package/memory/storage.py
CHANGED
|
@@ -71,6 +71,19 @@ class MemoryStorage:
|
|
|
71
71
|
Supports namespace-based project isolation for memory separation.
|
|
72
72
|
"""
|
|
73
73
|
|
|
74
|
+
# Process-wide read-only switch, set by mcp/server.py apply_read_only_mode.
|
|
75
|
+
# When True, constructing a store creates nothing (no directories, no
|
|
76
|
+
# index/timeline init, no cleanup), reads take no lock file, and any write
|
|
77
|
+
# raises (every public mutator calls _check_writable). A missing store
|
|
78
|
+
# simply reads as empty.
|
|
79
|
+
READ_ONLY = False
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def _check_writable() -> None:
|
|
83
|
+
"""Raise PermissionError when the process-wide READ_ONLY flag is set."""
|
|
84
|
+
if MemoryStorage.READ_ONLY:
|
|
85
|
+
raise PermissionError("memory store is read-only in this process")
|
|
86
|
+
|
|
74
87
|
VERSION = "1.1.0"
|
|
75
88
|
|
|
76
89
|
def __init__(
|
|
@@ -112,9 +125,10 @@ class MemoryStorage:
|
|
|
112
125
|
# called on the same path from nested operations in the same thread.
|
|
113
126
|
self._held_locks: threading.local = threading.local()
|
|
114
127
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
128
|
+
if not MemoryStorage.READ_ONLY:
|
|
129
|
+
self._ensure_directories()
|
|
130
|
+
self._ensure_index()
|
|
131
|
+
self._ensure_timeline()
|
|
118
132
|
|
|
119
133
|
@property
|
|
120
134
|
def namespace(self) -> Optional[str]:
|
|
@@ -316,6 +330,11 @@ class MemoryStorage:
|
|
|
316
330
|
Yields:
|
|
317
331
|
File handle with lock held
|
|
318
332
|
"""
|
|
333
|
+
if MemoryStorage.READ_ONLY:
|
|
334
|
+
# No lock file may be created; reads proceed unlocked.
|
|
335
|
+
yield
|
|
336
|
+
return
|
|
337
|
+
|
|
319
338
|
lock_path = path.with_suffix(path.suffix + ".lock")
|
|
320
339
|
lock_key = str(lock_path)
|
|
321
340
|
|
|
@@ -364,6 +383,8 @@ class MemoryStorage:
|
|
|
364
383
|
path: Target file path
|
|
365
384
|
data: Dictionary to serialize as JSON
|
|
366
385
|
"""
|
|
386
|
+
if MemoryStorage.READ_ONLY:
|
|
387
|
+
raise PermissionError("memory store is read-only in this process")
|
|
367
388
|
path = Path(path)
|
|
368
389
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
369
390
|
|
|
@@ -482,6 +503,7 @@ class MemoryStorage:
|
|
|
482
503
|
Returns:
|
|
483
504
|
Episode ID
|
|
484
505
|
"""
|
|
506
|
+
self._check_writable()
|
|
485
507
|
# Handle both dict and object
|
|
486
508
|
if hasattr(episode, "to_dict"):
|
|
487
509
|
episode_data = episode.to_dict()
|
|
@@ -620,6 +642,7 @@ class MemoryStorage:
|
|
|
620
642
|
Returns:
|
|
621
643
|
True if deleted, False if not found
|
|
622
644
|
"""
|
|
645
|
+
self._check_writable()
|
|
623
646
|
episodic_dir = self.base_path / "episodic"
|
|
624
647
|
if not episodic_dir.exists():
|
|
625
648
|
return False
|
|
@@ -690,6 +713,7 @@ class MemoryStorage:
|
|
|
690
713
|
Returns:
|
|
691
714
|
Pattern ID
|
|
692
715
|
"""
|
|
716
|
+
self._check_writable()
|
|
693
717
|
# Handle both dict and object
|
|
694
718
|
if hasattr(pattern, "to_dict"):
|
|
695
719
|
pattern_data = pattern.to_dict()
|
|
@@ -826,6 +850,7 @@ class MemoryStorage:
|
|
|
826
850
|
Returns:
|
|
827
851
|
True if updated, False if not found
|
|
828
852
|
"""
|
|
853
|
+
self._check_writable()
|
|
829
854
|
# Handle both dict and object
|
|
830
855
|
if hasattr(pattern, "to_dict"):
|
|
831
856
|
pattern_data = pattern.to_dict()
|
|
@@ -906,6 +931,7 @@ class MemoryStorage:
|
|
|
906
931
|
True if the pattern was found and the merged record written, False if
|
|
907
932
|
the pattern id was not present (caller should fall back to a create).
|
|
908
933
|
"""
|
|
934
|
+
self._check_writable()
|
|
909
935
|
if not pattern_id:
|
|
910
936
|
return False
|
|
911
937
|
|
|
@@ -968,6 +994,7 @@ class MemoryStorage:
|
|
|
968
994
|
Returns:
|
|
969
995
|
Skill ID
|
|
970
996
|
"""
|
|
997
|
+
self._check_writable()
|
|
971
998
|
# Handle both dict and object
|
|
972
999
|
if hasattr(skill, "to_dict"):
|
|
973
1000
|
skill_data = skill.to_dict()
|
|
@@ -1053,6 +1080,7 @@ class MemoryStorage:
|
|
|
1053
1080
|
|
|
1054
1081
|
Scans all memory stores and builds a topic index for efficient lookup.
|
|
1055
1082
|
"""
|
|
1083
|
+
self._check_writable()
|
|
1056
1084
|
index = {
|
|
1057
1085
|
"version": self.VERSION,
|
|
1058
1086
|
"last_updated": datetime.now(timezone.utc).isoformat(),
|
|
@@ -1126,6 +1154,7 @@ class MemoryStorage:
|
|
|
1126
1154
|
Args:
|
|
1127
1155
|
action: Action dictionary with type, description, timestamp, etc.
|
|
1128
1156
|
"""
|
|
1157
|
+
self._check_writable()
|
|
1129
1158
|
timeline_path = self.base_path / "timeline.json"
|
|
1130
1159
|
|
|
1131
1160
|
with self._file_lock(timeline_path, exclusive=True):
|
|
@@ -1207,6 +1236,7 @@ class MemoryStorage:
|
|
|
1207
1236
|
Args:
|
|
1208
1237
|
context: Dictionary of current context variables
|
|
1209
1238
|
"""
|
|
1239
|
+
self._check_writable()
|
|
1210
1240
|
timeline_path = self.base_path / "timeline.json"
|
|
1211
1241
|
|
|
1212
1242
|
with self._file_lock(timeline_path, exclusive=True):
|
|
@@ -1263,6 +1293,7 @@ class MemoryStorage:
|
|
|
1263
1293
|
|
|
1264
1294
|
def ensure_directory(self, subpath: str) -> None:
|
|
1265
1295
|
"""Create directory if it doesn't exist."""
|
|
1296
|
+
self._check_writable()
|
|
1266
1297
|
path = os.path.join(self.base_path, subpath)
|
|
1267
1298
|
os.makedirs(path, exist_ok=True)
|
|
1268
1299
|
|
|
@@ -1286,6 +1317,7 @@ class MemoryStorage:
|
|
|
1286
1317
|
|
|
1287
1318
|
def write_json(self, filepath: str, data: dict) -> None:
|
|
1288
1319
|
"""Write JSON file atomically."""
|
|
1320
|
+
self._check_writable()
|
|
1289
1321
|
full_path = self._resolve_path(filepath)
|
|
1290
1322
|
self._atomic_write(full_path, data)
|
|
1291
1323
|
|
|
@@ -1298,6 +1330,7 @@ class MemoryStorage:
|
|
|
1298
1330
|
|
|
1299
1331
|
def delete_file(self, filepath: str) -> bool:
|
|
1300
1332
|
"""Delete file, return True if deleted."""
|
|
1333
|
+
self._check_writable()
|
|
1301
1334
|
full_path = self._resolve_path(filepath)
|
|
1302
1335
|
try:
|
|
1303
1336
|
os.remove(full_path)
|
|
@@ -1545,6 +1578,7 @@ class MemoryStorage:
|
|
|
1545
1578
|
Returns:
|
|
1546
1579
|
True if a record was found and persisted, False otherwise.
|
|
1547
1580
|
"""
|
|
1581
|
+
self._check_writable()
|
|
1548
1582
|
memory_id = memory.get("id")
|
|
1549
1583
|
if not memory_id:
|
|
1550
1584
|
return False
|
|
@@ -1656,6 +1690,7 @@ class MemoryStorage:
|
|
|
1656
1690
|
Returns:
|
|
1657
1691
|
True if the pattern was found and incremented, False otherwise.
|
|
1658
1692
|
"""
|
|
1693
|
+
self._check_writable()
|
|
1659
1694
|
patterns_path = self.base_path / "semantic" / "patterns.json"
|
|
1660
1695
|
if not patterns_path.exists():
|
|
1661
1696
|
return False
|
|
@@ -1706,6 +1741,7 @@ class MemoryStorage:
|
|
|
1706
1741
|
Returns:
|
|
1707
1742
|
Number of memories updated
|
|
1708
1743
|
"""
|
|
1744
|
+
self._check_writable()
|
|
1709
1745
|
updated_count = 0
|
|
1710
1746
|
|
|
1711
1747
|
collections_to_process = []
|
|
@@ -1926,6 +1962,7 @@ class MemoryStorage:
|
|
|
1926
1962
|
Returns:
|
|
1927
1963
|
Dictionary with counts of copied items
|
|
1928
1964
|
"""
|
|
1965
|
+
self._check_writable()
|
|
1929
1966
|
target = self.with_namespace(target_namespace)
|
|
1930
1967
|
copied = {"episodes": 0, "patterns": 0, "skills": 0}
|
|
1931
1968
|
|
|
@@ -1970,6 +2007,7 @@ class MemoryStorage:
|
|
|
1970
2007
|
Returns:
|
|
1971
2008
|
Dictionary with counts of merged items
|
|
1972
2009
|
"""
|
|
2010
|
+
self._check_writable()
|
|
1973
2011
|
source = self.with_namespace(source_namespace)
|
|
1974
2012
|
merged = {"episodes": 0, "patterns": 0, "skills": 0}
|
|
1975
2013
|
|
package/package.json
CHANGED