loki-mode 6.66.0 → 6.67.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.
@@ -2056,9 +2056,10 @@ async def get_episode(episode_id: str):
2056
2056
  ep_dir = loki_dir / "memory" / "episodic"
2057
2057
  if not ep_dir.exists():
2058
2058
  raise HTTPException(status_code=404, detail="Episode not found")
2059
+ real_loki = os.path.realpath(str(loki_dir))
2059
2060
  for f in ep_dir.glob("*.json"):
2060
2061
  resolved = os.path.realpath(f)
2061
- if not resolved.startswith(os.path.realpath(str(loki_dir))):
2062
+ if not resolved.startswith(real_loki + os.sep) and resolved != real_loki:
2062
2063
  raise HTTPException(status_code=403, detail="Access denied")
2063
2064
  try:
2064
2065
  data = json.loads(f.read_text())
@@ -2144,9 +2145,10 @@ async def get_skill(skill_id: str):
2144
2145
  skills_dir = loki_dir / "memory" / "skills"
2145
2146
  if not skills_dir.exists():
2146
2147
  raise HTTPException(status_code=404, detail="Skill not found")
2148
+ real_loki = os.path.realpath(str(loki_dir))
2147
2149
  for f in skills_dir.glob("*.json"):
2148
2150
  resolved = os.path.realpath(f)
2149
- if not resolved.startswith(os.path.realpath(str(loki_dir))):
2151
+ if not resolved.startswith(real_loki + os.sep) and resolved != real_loki:
2150
2152
  raise HTTPException(status_code=403, detail="Access denied")
2151
2153
  try:
2152
2154
  data = json.loads(f.read_text())
@@ -3547,8 +3549,8 @@ async def get_checkpoint(checkpoint_id: str):
3547
3549
 
3548
3550
  try:
3549
3551
  return json.loads(metadata_file.read_text())
3550
- except (json.JSONDecodeError, IOError) as e:
3551
- raise HTTPException(status_code=500, detail=f"Failed to read checkpoint: {e}")
3552
+ except (json.JSONDecodeError, IOError):
3553
+ raise HTTPException(status_code=500, detail="Failed to read checkpoint data")
3552
3554
 
3553
3555
 
3554
3556
  @app.post("/api/checkpoints", status_code=201, dependencies=[Depends(auth.require_scope("control"))])
@@ -3816,8 +3818,20 @@ async def get_logs(lines: int = 100, token: Optional[dict] = Depends(auth.get_cu
3816
3818
  file_mtime = datetime.fromtimestamp(log_file.stat().st_mtime, tz=timezone.utc).strftime(
3817
3819
  "%Y-%m-%dT%H:%M:%S"
3818
3820
  )
3819
- content = _safe_read_text(log_file)
3820
- for raw_line in content.strip().split("\n")[-lines:]:
3821
+ # Read only the tail to avoid loading huge files into memory
3822
+ tail_lines = []
3823
+ try:
3824
+ with open(log_file, "rb") as lf:
3825
+ # Seek from end to find enough lines
3826
+ lf.seek(0, 2)
3827
+ file_size = lf.tell()
3828
+ # Read at most 1MB from the end (plenty for any reasonable lines count)
3829
+ read_size = min(file_size, 1024 * 1024)
3830
+ lf.seek(max(0, file_size - read_size))
3831
+ tail_lines = lf.read().decode("utf-8", errors="replace").strip().split("\n")[-lines:]
3832
+ except (OSError, UnicodeDecodeError):
3833
+ tail_lines = []
3834
+ for raw_line in tail_lines:
3821
3835
  timestamp = ""
3822
3836
  level = "info"
3823
3837
  message = raw_line
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v6.66.0
5
+ **Version:** v6.67.0
6
6
 
7
7
  ---
8
8
 
package/events/bus.py CHANGED
@@ -6,6 +6,7 @@ This enables CLI, API, VS Code, and MCP to communicate without shared memory.
6
6
  """
7
7
 
8
8
  import json
9
+ import logging
9
10
  import os
10
11
  import time
11
12
  import uuid
@@ -17,6 +18,8 @@ from pathlib import Path
17
18
  from typing import Generator, List, Optional, Callable
18
19
  import threading
19
20
 
21
+ logger = logging.getLogger(__name__)
22
+
20
23
 
21
24
  class EventType(str, Enum):
22
25
  """Event types that can be emitted."""
@@ -374,7 +377,12 @@ class EventBus:
374
377
  try:
375
378
  callback(event)
376
379
  except Exception:
377
- pass # Don't let one callback break others
380
+ logger.warning(
381
+ "Event callback %s failed for event %s",
382
+ getattr(callback, "__name__", callback),
383
+ event.type,
384
+ exc_info=True,
385
+ )
378
386
  self.mark_processed(event)
379
387
  time.sleep(poll_interval)
380
388
 
package/events/emit.sh CHANGED
@@ -41,9 +41,9 @@ else
41
41
  TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
42
42
  fi
43
43
 
44
- # JSON escape helper: handles \, ", and control characters
44
+ # JSON escape helper: handles \, ", and control characters including newlines
45
45
  json_escape() {
46
- printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g; s/\r/\\r/g' | awk '{if(NR>1) printf "\\n"; printf "%s", $0}'
46
+ printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g; s/\r/\\r/g; s//\\b/g; s//\\f/g' | awk '{if(NR>1) printf "\\n"; printf "%s", $0}'
47
47
  }
48
48
 
49
49
  # Build payload JSON
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '6.66.0'
60
+ __version__ = '6.67.0'
@@ -140,20 +140,20 @@ class NamespaceManager:
140
140
  lock_path = Path(str(path) + ".lock")
141
141
  lock_path.parent.mkdir(parents=True, exist_ok=True)
142
142
 
143
- lock_file = None
143
+ lock_file = open(lock_path, "w")
144
144
  try:
145
- lock_file = open(lock_path, "w")
146
145
  lock_type = fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH
147
146
  fcntl.flock(lock_file.fileno(), lock_type)
148
- yield
149
- finally:
150
- if lock_file is not None:
147
+ try:
148
+ yield
149
+ finally:
151
150
  fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
152
- lock_file.close()
153
- try:
154
- os.remove(lock_path)
155
- except OSError:
156
- pass
151
+ finally:
152
+ lock_file.close()
153
+ try:
154
+ os.remove(lock_path)
155
+ except OSError:
156
+ pass
157
157
 
158
158
  def _load_registry(self) -> Dict[str, Any]:
159
159
  """Load the namespace registry with shared file lock."""
@@ -163,7 +163,10 @@ class NamespaceManager:
163
163
 
164
164
  with self._file_lock(registry_path, exclusive=False):
165
165
  with open(registry_path, "r") as f:
166
- return json.load(f)
166
+ try:
167
+ return json.load(f)
168
+ except json.JSONDecodeError:
169
+ return {"version": "1.0.0", "namespaces": {}}
167
170
 
168
171
  def _save_registry(self, registry: Dict[str, Any]) -> None:
169
172
  """Save the namespace registry with exclusive file lock."""
package/memory/schemas.py CHANGED
@@ -782,3 +782,158 @@ class ProceduralSkill:
782
782
  def add_error_fix(self, error: str, fix: str) -> None:
783
783
  """Add a common error and its fix."""
784
784
  self.common_errors.append(ErrorFix(error=error, fix=fix))
785
+
786
+
787
+ # -----------------------------------------------------------------------------
788
+ # Healing Memory Types (v6.67.0)
789
+ # Inspired by Amazon AGI Lab's failure-first learning approach.
790
+ # These types store friction points, failure modes, and institutional knowledge
791
+ # discovered during legacy system healing operations.
792
+ # -----------------------------------------------------------------------------
793
+
794
+
795
+ @dataclass
796
+ class FrictionPoint:
797
+ """
798
+ A friction point discovered during codebase archaeology.
799
+
800
+ Friction points are behaviors that appear to be bugs but may actually
801
+ be undocumented business rules. They must be classified before removal.
802
+
803
+ Attributes:
804
+ id: Unique identifier (e.g., "friction-001")
805
+ location: File path and line number (e.g., "src/billing/invoice.py:234")
806
+ behavior: Description of the observed behavior
807
+ classification: business_rule, true_bug, or unknown
808
+ evidence: Evidence supporting the classification
809
+ discovered_by: How this was discovered (archaeology_scan, manual, test_failure)
810
+ timestamp: When discovered
811
+ safe_to_remove: Whether it's safe to remove this friction
812
+ """
813
+ id: str
814
+ location: str
815
+ behavior: str
816
+ classification: str = "unknown" # business_rule | true_bug | unknown
817
+ evidence: str = ""
818
+ discovered_by: str = "archaeology_scan"
819
+ timestamp: Optional[datetime] = None
820
+ safe_to_remove: bool = False
821
+
822
+ def to_dict(self) -> Dict[str, Any]:
823
+ """Convert to dictionary for JSON serialization."""
824
+ result = {
825
+ "id": self.id,
826
+ "location": self.location,
827
+ "behavior": self.behavior,
828
+ "classification": self.classification,
829
+ "evidence": self.evidence,
830
+ "discovered_by": self.discovered_by,
831
+ "safe_to_remove": self.safe_to_remove,
832
+ }
833
+ if self.timestamp:
834
+ result["timestamp"] = _to_utc_isoformat(self.timestamp)
835
+ return result
836
+
837
+ @classmethod
838
+ def from_dict(cls, data: Dict[str, Any]) -> "FrictionPoint":
839
+ """Create from dictionary."""
840
+ timestamp = None
841
+ if data.get("timestamp"):
842
+ ts = data["timestamp"]
843
+ if isinstance(ts, str):
844
+ timestamp = _parse_utc_datetime(ts)
845
+ return cls(
846
+ id=data.get("id", ""),
847
+ location=data.get("location", ""),
848
+ behavior=data.get("behavior", ""),
849
+ classification=data.get("classification", "unknown"),
850
+ evidence=data.get("evidence", ""),
851
+ discovered_by=data.get("discovered_by", "archaeology_scan"),
852
+ timestamp=timestamp,
853
+ safe_to_remove=data.get("safe_to_remove", False),
854
+ )
855
+
856
+ def validate(self) -> List[str]:
857
+ """Validate the friction point."""
858
+ errors = []
859
+ if not self.id:
860
+ errors.append("FrictionPoint.id is required")
861
+ if not self.location:
862
+ errors.append("FrictionPoint.location is required")
863
+ if not self.behavior:
864
+ errors.append("FrictionPoint.behavior is required")
865
+ if self.classification not in ("business_rule", "true_bug", "unknown"):
866
+ errors.append("FrictionPoint.classification must be business_rule, true_bug, or unknown")
867
+ return errors
868
+
869
+
870
+ @dataclass
871
+ class FailureMode:
872
+ """
873
+ A failure mode discovered during healing operations.
874
+
875
+ Failure-first learning: each failure teaches about the system's
876
+ real behavior. Failures are stored and used to build understanding.
877
+
878
+ Attributes:
879
+ mode_id: Unique identifier
880
+ trigger: What causes the failure
881
+ behavior: What happens when it fails
882
+ recovery: How the system currently recovers
883
+ is_intentional: Whether the failure is by design
884
+ component: System component where failure occurs
885
+ characterization_test_id: Test that reproduces this mode
886
+ """
887
+ mode_id: str
888
+ trigger: str
889
+ behavior: str
890
+ recovery: str = ""
891
+ is_intentional: bool = False
892
+ component: str = ""
893
+ characterization_test_id: str = ""
894
+ timestamp: Optional[datetime] = None
895
+
896
+ def to_dict(self) -> Dict[str, Any]:
897
+ """Convert to dictionary for JSON serialization."""
898
+ result = {
899
+ "mode_id": self.mode_id,
900
+ "trigger": self.trigger,
901
+ "behavior": self.behavior,
902
+ "recovery": self.recovery,
903
+ "is_intentional": self.is_intentional,
904
+ "component": self.component,
905
+ "characterization_test_id": self.characterization_test_id,
906
+ }
907
+ if self.timestamp:
908
+ result["timestamp"] = _to_utc_isoformat(self.timestamp)
909
+ return result
910
+
911
+ @classmethod
912
+ def from_dict(cls, data: Dict[str, Any]) -> "FailureMode":
913
+ """Create from dictionary."""
914
+ timestamp = None
915
+ if data.get("timestamp"):
916
+ ts = data["timestamp"]
917
+ if isinstance(ts, str):
918
+ timestamp = _parse_utc_datetime(ts)
919
+ return cls(
920
+ mode_id=data.get("mode_id", ""),
921
+ trigger=data.get("trigger", ""),
922
+ behavior=data.get("behavior", ""),
923
+ recovery=data.get("recovery", ""),
924
+ is_intentional=data.get("is_intentional", False),
925
+ component=data.get("component", ""),
926
+ characterization_test_id=data.get("characterization_test_id", ""),
927
+ timestamp=timestamp,
928
+ )
929
+
930
+ def validate(self) -> List[str]:
931
+ """Validate the failure mode."""
932
+ errors = []
933
+ if not self.mode_id:
934
+ errors.append("FailureMode.mode_id is required")
935
+ if not self.trigger:
936
+ errors.append("FailureMode.trigger is required")
937
+ if not self.behavior:
938
+ errors.append("FailureMode.behavior is required")
939
+ return errors
package/memory/storage.py CHANGED
@@ -512,7 +512,13 @@ class MemoryStorage:
512
512
  # Load existing patterns
513
513
  if patterns_path.exists():
514
514
  with open(patterns_path, "r") as f:
515
- patterns_file = json.load(f)
515
+ try:
516
+ patterns_file = json.load(f)
517
+ except json.JSONDecodeError:
518
+ patterns_file = {
519
+ "version": self.VERSION,
520
+ "patterns": []
521
+ }
516
522
  else:
517
523
  patterns_file = {
518
524
  "version": self.VERSION,
@@ -625,7 +631,10 @@ class MemoryStorage:
625
631
  return False
626
632
 
627
633
  with open(patterns_path, "r") as f:
628
- patterns_file = json.load(f)
634
+ try:
635
+ patterns_file = json.load(f)
636
+ except json.JSONDecodeError:
637
+ return False
629
638
 
630
639
  # Find and update pattern
631
640
  found = False
@@ -835,7 +844,15 @@ class MemoryStorage:
835
844
  with self._file_lock(timeline_path, exclusive=True):
836
845
  if timeline_path.exists():
837
846
  with open(timeline_path, "r") as f:
838
- timeline = json.load(f)
847
+ try:
848
+ timeline = json.load(f)
849
+ except json.JSONDecodeError:
850
+ timeline = {
851
+ "version": self.VERSION,
852
+ "recent_actions": [],
853
+ "key_decisions": [],
854
+ "active_context": {}
855
+ }
839
856
  else:
840
857
  timeline = {
841
858
  "version": self.VERSION,
@@ -908,7 +925,15 @@ class MemoryStorage:
908
925
  with self._file_lock(timeline_path, exclusive=True):
909
926
  if timeline_path.exists():
910
927
  with open(timeline_path, "r") as f:
911
- timeline = json.load(f)
928
+ try:
929
+ timeline = json.load(f)
930
+ except json.JSONDecodeError:
931
+ timeline = {
932
+ "version": self.VERSION,
933
+ "recent_actions": [],
934
+ "key_decisions": [],
935
+ "active_context": {}
936
+ }
912
937
  else:
913
938
  timeline = {
914
939
  "version": self.VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "6.66.0",
3
+ "version": "6.67.0",
4
4
  "description": "Loki Mode by Autonomi - Multi-agent autonomous startup system for Claude Code, Codex CLI, and Gemini CLI",
5
5
  "keywords": [
6
6
  "agent",