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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v5.34.0
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 `CORS_ALLOWED_ORIGINS` environment variable:
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
- CORS_ALLOWED_ORIGINS="http://localhost:3000,https://my-dashboard.example.com" loki dashboard start
607
+ LOKI_DASHBOARD_CORS="http://localhost:3000,https://my-dashboard.example.com" loki dashboard start
608
608
 
609
- # Default: localhost only (secure by default)
609
+ # Default: http://localhost:57374,http://127.0.0.1:57374 (localhost-only, secure by default)
610
610
  ```
611
611
 
612
612
  ---
@@ -163,7 +163,7 @@ class UnifiedMemoryAccess:
163
163
  self.storage.append_action(
164
164
  source=source,
165
165
  action=action,
166
- timestamp=utcnow()
166
+ timestamp=datetime.now(timezone.utc)
167
167
  )
168
168
  ```
169
169
 
@@ -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
- - Current Node.js API: `/Users/lokesh/git/claudeskill-loki-mode/autonomy/api-server.js`
877
- - Current Deno API: `/Users/lokesh/git/claudeskill-loki-mode/api/server.ts`
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
 
@@ -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 `CORS_ALLOWED_ORIGINS` environment variable (default: localhost only) |
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.utcnow().isoformat() + "Z",
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.utcnow().isoformat() + "Z"
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.utcnow().isoformat() + "Z",
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.utcnow().isoformat() + "Z",
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.utcnow().isoformat() + "Z"
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.utcnow().isoformat() + "Z",
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.utcnow().isoformat() + "Z",
135
+ "timestamp": datetime.now(timezone.utc).isoformat(),
136
136
  "decision": decision,
137
137
  "rationale": rationale,
138
138
  }
@@ -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() + "Z"
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.utcnow()
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() + "Z",
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.utcnow()
617
+ self.started_at = datetime.now(timezone.utc)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "5.34.0",
3
+ "version": "5.35.0",
4
4
  "description": "Multi-agent autonomous startup system for Claude Code, Codex CLI, and Gemini CLI",
5
5
  "keywords": [
6
6
  "claude",