loki-mode 5.34.0 → 5.35.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/README.md +5 -5
- package/SKILL.md +4 -4
- package/VERSION +1 -1
- package/api/README.md +1 -1
- package/api/server.js +1 -1
- package/api/server.ts +5 -5
- package/api/test.js +1 -1
- package/autonomy/api-server.js +6 -4
- package/autonomy/completion-council.sh +4 -2
- package/autonomy/hooks/store-episode.sh +2 -2
- package/autonomy/loki +84 -54
- package/autonomy/run.sh +579 -35
- package/autonomy/sandbox.sh +3 -9
- package/autonomy/serve.sh +10 -10
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +16 -16
- package/dashboard/static/index.html +359 -58
- package/docs/INSTALLATION.md +4 -4
- package/docs/SYNERGY-ROADMAP.md +1 -1
- package/docs/architecture/DASHBOARD_V2_ARCHITECTURE.md +4 -3
- package/docs/dashboard-guide.md +1 -1
- package/memory/layers/index_layer.py +4 -4
- package/memory/layers/timeline_layer.py +5 -5
- package/memory/retrieval.py +10 -2
- package/memory/storage.py +1 -1
- package/memory/token_economics.py +12 -8
- package/package.json +1 -1
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v5.
|
|
5
|
+
**Version:** v5.35.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -600,13 +600,13 @@ loki serve --port 57374
|
|
|
600
600
|
|
|
601
601
|
### CORS Configuration
|
|
602
602
|
|
|
603
|
-
For remote or cross-origin access to the dashboard, configure allowed origins via the `
|
|
603
|
+
For remote or cross-origin access to the dashboard, configure allowed origins via the `LOKI_DASHBOARD_CORS` environment variable:
|
|
604
604
|
|
|
605
605
|
```bash
|
|
606
606
|
# Allow specific origins
|
|
607
|
-
|
|
607
|
+
LOKI_DASHBOARD_CORS="http://localhost:3000,https://my-dashboard.example.com" loki dashboard start
|
|
608
608
|
|
|
609
|
-
# Default: localhost only
|
|
609
|
+
# Default: http://localhost:57374,http://127.0.0.1:57374 (localhost-only, secure by default)
|
|
610
610
|
```
|
|
611
611
|
|
|
612
612
|
---
|
package/docs/SYNERGY-ROADMAP.md
CHANGED
|
@@ -20,7 +20,7 @@ This document outlines the architecture for a new enterprise-grade Loki Mode Das
|
|
|
20
20
|
| Component | Location | Description | Limitations |
|
|
21
21
|
|-----------|----------|-------------|-------------|
|
|
22
22
|
| Static Dashboard | `autonomy/.loki/dashboard/index.html` | Single-file HTML/CSS/JS (~2000 lines) | No persistence, file-polling for updates |
|
|
23
|
-
| Node.js API | `autonomy/api-server.js` | Simple HTTP server (zero deps) | SSE polling, no database, single project |
|
|
23
|
+
| Node.js API (legacy) | `autonomy/api-server.js` | Simple HTTP server (zero deps) - replaced by FastAPI | SSE polling, no database, single project |
|
|
24
24
|
| Deno API | `api/server.ts` | TypeScript HTTP/SSE server | File-based state, no cross-project |
|
|
25
25
|
| State Files | `.loki/dashboard-state.json` | JSON state written by run.sh | 2-second polling, no real-time |
|
|
26
26
|
|
|
@@ -873,8 +873,9 @@ dashboard/
|
|
|
873
873
|
### B. Related Files
|
|
874
874
|
|
|
875
875
|
- Current static dashboard: `/Users/lokesh/git/claudeskill-loki-mode/autonomy/.loki/dashboard/index.html`
|
|
876
|
-
-
|
|
877
|
-
-
|
|
876
|
+
- Production API: `/Users/lokesh/git/claudeskill-loki-mode/dashboard/server.py` (unified FastAPI, port 57374)
|
|
877
|
+
- Legacy Node.js API: `/Users/lokesh/git/claudeskill-loki-mode/autonomy/api-server.js`
|
|
878
|
+
- Legacy Deno API: `/Users/lokesh/git/claudeskill-loki-mode/api/server.ts`
|
|
878
879
|
- OpenAPI spec: `/Users/lokesh/git/claudeskill-loki-mode/api/openapi.yaml`
|
|
879
880
|
- Run script: `/Users/lokesh/git/claudeskill-loki-mode/autonomy/run.sh`
|
|
880
881
|
|
package/docs/dashboard-guide.md
CHANGED
|
@@ -254,7 +254,7 @@ The dashboard includes several security measures:
|
|
|
254
254
|
| **XSS protection** | Log stream output is sanitized to prevent cross-site scripting |
|
|
255
255
|
| **Memory leak fix** | Session control properly cleans up resources on disconnect |
|
|
256
256
|
| **Python injection fix** | `completion-council.sh` sanitizes inputs to prevent code injection |
|
|
257
|
-
| **CORS configuration** | Configurable via `
|
|
257
|
+
| **CORS configuration** | Configurable via `LOKI_DASHBOARD_CORS` environment variable (default: localhost only) |
|
|
258
258
|
|
|
259
259
|
---
|
|
260
260
|
|
|
@@ -11,7 +11,7 @@ loading full memory content.
|
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
13
|
from dataclasses import dataclass, field, asdict
|
|
14
|
-
from datetime import datetime
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
from typing import List, Dict, Any, Optional
|
|
17
17
|
|
|
@@ -101,7 +101,7 @@ class IndexLayer:
|
|
|
101
101
|
"""Create an empty index structure."""
|
|
102
102
|
return {
|
|
103
103
|
"version": self.VERSION,
|
|
104
|
-
"last_updated": datetime.
|
|
104
|
+
"last_updated": datetime.now(timezone.utc).isoformat(),
|
|
105
105
|
"topics": [],
|
|
106
106
|
"total_memories": 0,
|
|
107
107
|
"total_tokens_available": 0,
|
|
@@ -115,7 +115,7 @@ class IndexLayer:
|
|
|
115
115
|
index: Index dictionary to save
|
|
116
116
|
"""
|
|
117
117
|
self.base_path.mkdir(parents=True, exist_ok=True)
|
|
118
|
-
index["last_updated"] = datetime.
|
|
118
|
+
index["last_updated"] = datetime.now(timezone.utc).isoformat()
|
|
119
119
|
|
|
120
120
|
with open(self.index_path, "w") as f:
|
|
121
121
|
json.dump(index, f, indent=2)
|
|
@@ -145,7 +145,7 @@ class IndexLayer:
|
|
|
145
145
|
|
|
146
146
|
index = {
|
|
147
147
|
"version": self.VERSION,
|
|
148
|
-
"last_updated": datetime.
|
|
148
|
+
"last_updated": datetime.now(timezone.utc).isoformat(),
|
|
149
149
|
"topics": topics,
|
|
150
150
|
"total_memories": len(topics),
|
|
151
151
|
"total_tokens_available": total_tokens,
|
|
@@ -9,7 +9,7 @@ providing temporal context before loading full memories.
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import json
|
|
12
|
-
from datetime import datetime
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from typing import List, Dict, Any, Optional
|
|
15
15
|
|
|
@@ -58,7 +58,7 @@ class TimelineLayer:
|
|
|
58
58
|
"""Create an empty timeline structure."""
|
|
59
59
|
return {
|
|
60
60
|
"version": self.VERSION,
|
|
61
|
-
"last_updated": datetime.
|
|
61
|
+
"last_updated": datetime.now(timezone.utc).isoformat(),
|
|
62
62
|
"recent_actions": [],
|
|
63
63
|
"key_decisions": [],
|
|
64
64
|
"active_context": {
|
|
@@ -76,7 +76,7 @@ class TimelineLayer:
|
|
|
76
76
|
timeline: Timeline dictionary to save
|
|
77
77
|
"""
|
|
78
78
|
self.base_path.mkdir(parents=True, exist_ok=True)
|
|
79
|
-
timeline["last_updated"] = datetime.
|
|
79
|
+
timeline["last_updated"] = datetime.now(timezone.utc).isoformat()
|
|
80
80
|
|
|
81
81
|
with open(self.timeline_path, "w") as f:
|
|
82
82
|
json.dump(timeline, f, indent=2)
|
|
@@ -100,7 +100,7 @@ class TimelineLayer:
|
|
|
100
100
|
timeline = self.load()
|
|
101
101
|
|
|
102
102
|
action_entry = {
|
|
103
|
-
"timestamp": datetime.
|
|
103
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
104
104
|
"action": action,
|
|
105
105
|
"outcome": outcome,
|
|
106
106
|
}
|
|
@@ -132,7 +132,7 @@ class TimelineLayer:
|
|
|
132
132
|
timeline = self.load()
|
|
133
133
|
|
|
134
134
|
decision_entry = {
|
|
135
|
-
"timestamp": datetime.
|
|
135
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
136
136
|
"decision": decision,
|
|
137
137
|
"rationale": rationale,
|
|
138
138
|
}
|
package/memory/retrieval.py
CHANGED
|
@@ -783,11 +783,15 @@ class MemoryRetrieval:
|
|
|
783
783
|
try:
|
|
784
784
|
if isinstance(last_used, str):
|
|
785
785
|
if last_used.endswith("Z"):
|
|
786
|
-
last_used = last_used[:-1]
|
|
786
|
+
last_used = last_used[:-1] + "+00:00"
|
|
787
787
|
last_used_dt = datetime.fromisoformat(last_used)
|
|
788
788
|
else:
|
|
789
789
|
last_used_dt = last_used
|
|
790
790
|
|
|
791
|
+
# Ensure timezone-aware for comparison
|
|
792
|
+
if last_used_dt.tzinfo is None:
|
|
793
|
+
last_used_dt = last_used_dt.replace(tzinfo=timezone.utc)
|
|
794
|
+
|
|
791
795
|
if since <= last_used_dt <= until:
|
|
792
796
|
pattern["_source"] = "semantic"
|
|
793
797
|
results.append(pattern)
|
|
@@ -948,11 +952,15 @@ class MemoryRetrieval:
|
|
|
948
952
|
try:
|
|
949
953
|
if isinstance(timestamp, str):
|
|
950
954
|
if timestamp.endswith("Z"):
|
|
951
|
-
timestamp = timestamp[:-1]
|
|
955
|
+
timestamp = timestamp[:-1] + "+00:00"
|
|
952
956
|
item_time = datetime.fromisoformat(timestamp)
|
|
953
957
|
else:
|
|
954
958
|
item_time = timestamp
|
|
955
959
|
|
|
960
|
+
# Ensure timezone-aware for comparison with UTC now
|
|
961
|
+
if item_time.tzinfo is None:
|
|
962
|
+
item_time = item_time.replace(tzinfo=timezone.utc)
|
|
963
|
+
|
|
956
964
|
# Calculate age in days
|
|
957
965
|
age_days = (now - item_time).days
|
|
958
966
|
|
package/memory/storage.py
CHANGED
|
@@ -1091,7 +1091,7 @@ class MemoryStorage:
|
|
|
1091
1091
|
now = datetime.now(timezone.utc)
|
|
1092
1092
|
|
|
1093
1093
|
# Update access tracking
|
|
1094
|
-
memory["last_accessed"] = now.isoformat()
|
|
1094
|
+
memory["last_accessed"] = now.isoformat()
|
|
1095
1095
|
memory["access_count"] = memory.get("access_count", 0) + 1
|
|
1096
1096
|
|
|
1097
1097
|
# Boost importance (with diminishing returns for high importance)
|
|
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
import json
|
|
18
18
|
import os
|
|
19
19
|
from dataclasses import dataclass, field, asdict
|
|
20
|
-
from datetime import datetime
|
|
20
|
+
from datetime import datetime, timezone
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from typing import Any, Dict, List, Optional
|
|
23
23
|
|
|
@@ -180,7 +180,7 @@ def optimize_context(
|
|
|
180
180
|
List of memories that fit within the token budget, sorted by
|
|
181
181
|
combined score.
|
|
182
182
|
"""
|
|
183
|
-
from datetime import datetime
|
|
183
|
+
from datetime import datetime, timezone
|
|
184
184
|
|
|
185
185
|
if not memories:
|
|
186
186
|
return []
|
|
@@ -189,7 +189,7 @@ def optimize_context(
|
|
|
189
189
|
return []
|
|
190
190
|
|
|
191
191
|
scored_memories = []
|
|
192
|
-
now = datetime.now()
|
|
192
|
+
now = datetime.now(timezone.utc)
|
|
193
193
|
|
|
194
194
|
for memory in memories:
|
|
195
195
|
# Calculate importance score (0-1)
|
|
@@ -206,10 +206,12 @@ def optimize_context(
|
|
|
206
206
|
try:
|
|
207
207
|
if isinstance(timestamp, str):
|
|
208
208
|
if timestamp.endswith("Z"):
|
|
209
|
-
timestamp = timestamp[:-1]
|
|
209
|
+
timestamp = timestamp[:-1] + "+00:00"
|
|
210
210
|
item_time = datetime.fromisoformat(timestamp)
|
|
211
211
|
else:
|
|
212
212
|
item_time = timestamp
|
|
213
|
+
if item_time.tzinfo is None:
|
|
214
|
+
item_time = item_time.replace(tzinfo=timezone.utc)
|
|
213
215
|
|
|
214
216
|
# Calculate age in days
|
|
215
217
|
age_days = (now - item_time).days
|
|
@@ -445,7 +447,7 @@ class TokenEconomics:
|
|
|
445
447
|
"""
|
|
446
448
|
self.session_id = session_id
|
|
447
449
|
self.base_path = base_path
|
|
448
|
-
self.started_at = datetime.
|
|
450
|
+
self.started_at = datetime.now(timezone.utc)
|
|
449
451
|
|
|
450
452
|
self.metrics: Dict[str, int] = {
|
|
451
453
|
"discovery_tokens": 0,
|
|
@@ -553,7 +555,7 @@ class TokenEconomics:
|
|
|
553
555
|
"""
|
|
554
556
|
return {
|
|
555
557
|
"session_id": self.session_id,
|
|
556
|
-
"started_at": self.started_at.isoformat()
|
|
558
|
+
"started_at": self.started_at.isoformat(),
|
|
557
559
|
"metrics": dict(self.metrics),
|
|
558
560
|
"ratio": self.get_ratio(),
|
|
559
561
|
"savings_percent": self.get_savings_percent(),
|
|
@@ -594,8 +596,10 @@ class TokenEconomics:
|
|
|
594
596
|
started_at_str = data.get("started_at", "")
|
|
595
597
|
if started_at_str:
|
|
596
598
|
if started_at_str.endswith("Z"):
|
|
597
|
-
started_at_str = started_at_str[:-1]
|
|
599
|
+
started_at_str = started_at_str[:-1] + "+00:00"
|
|
598
600
|
self.started_at = datetime.fromisoformat(started_at_str)
|
|
601
|
+
if self.started_at.tzinfo is None:
|
|
602
|
+
self.started_at = self.started_at.replace(tzinfo=timezone.utc)
|
|
599
603
|
|
|
600
604
|
loaded_metrics = data.get("metrics", {})
|
|
601
605
|
for key in self.metrics:
|
|
@@ -610,4 +614,4 @@ class TokenEconomics:
|
|
|
610
614
|
for key in self.metrics:
|
|
611
615
|
self.metrics[key] = 0
|
|
612
616
|
self._full_load_baseline = None
|
|
613
|
-
self.started_at = datetime.
|
|
617
|
+
self.started_at = datetime.now(timezone.utc)
|