ocerebro 0.4.3 → 0.4.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ocerebro",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "OCerebro - Sistema de Memoria para Agentes (Claude Code/MCP)",
5
5
  "main": "bin/ocerebro.js",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ocerebro"
7
- version = "0.4.3"
7
+ version = "0.4.5"
8
8
  description = "OCerebro - Sistema de Memoria para Agentes (Claude Code/MCP)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -6,6 +6,7 @@ from typing import Any, Dict, List, Optional
6
6
  import sqlite3
7
7
  import json
8
8
  from datetime import datetime
9
+ from src.forgetting.gc import calculate_rfms_score
9
10
 
10
11
 
11
12
  def create_router(
@@ -24,6 +25,11 @@ def create_router(
24
25
  router.entities_db = entities_db
25
26
  router.cerebro_path = cerebro_path
26
27
 
28
+ @router.get("/ping")
29
+ async def get_ping():
30
+ """Retorna o cerebro_path atual do servidor"""
31
+ return {"cerebro_path": str(router.cerebro_path)}
32
+
27
33
  @router.get("/status")
28
34
  async def get_status():
29
35
  """Retorna status geral do sistema"""
@@ -242,7 +248,6 @@ def create_router(
242
248
  memories = []
243
249
  for row in rows:
244
250
  # Calcula GC risk
245
- from src.forgetting.gc import calculate_rfms_score
246
251
  memory_dict = dict(row)
247
252
  gc_risk = 1.0 - calculate_rfms_score(memory_dict)
248
253
 
@@ -0,0 +1,47 @@
1
+ """Servidor standalone do Dashboard do OCerebro - roda como processo separado"""
2
+
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ # Adiciona project root ao path
8
+ project_root = Path(__file__).parent.parent.parent
9
+ sys.path.insert(0, str(project_root))
10
+
11
+ from src.dashboard.server import DashboardServer
12
+ from src.mcp.server import CerebroMCP
13
+
14
+ def main():
15
+ """Inicia o servidor standalone"""
16
+ port = int(sys.argv[1]) if len(sys.argv) > 1 else 7999
17
+ cerebro_path = Path(sys.argv[2]) if len(sys.argv) > 2 else None
18
+
19
+ # Inicializa componentes com o path correto
20
+ mcp = CerebroMCP(cerebro_path=cerebro_path)
21
+
22
+ dashboard = DashboardServer(
23
+ cerebro_path=mcp.cerebro_path,
24
+ metadata_db=mcp.metadata_db,
25
+ embeddings_db=mcp.embeddings_db,
26
+ entities_db=mcp.entities_db
27
+ )
28
+
29
+ if dashboard.start(port):
30
+ # Salva PID para poder ser morto quando precisar trocar de contexto
31
+ pid_file = Path.home() / ".ocerebro_dashboard.pid"
32
+ pid_file.write_text(str(os.getpid()), encoding="utf-8")
33
+
34
+ print(f"Dashboard rodando em http://localhost:{port}")
35
+ print(f"Cerebro path: {mcp.cerebro_path}")
36
+ print(f"PID: {os.getpid()}")
37
+
38
+ # Mantém vivo
39
+ import time
40
+ while True:
41
+ time.sleep(3600) # Dorme por 1 hora, loop infinito
42
+ else:
43
+ print(f"Falha ao iniciar dashboard na porta {port}")
44
+ sys.exit(1)
45
+
46
+ if __name__ == "__main__":
47
+ main()
package/src/mcp/server.py CHANGED
@@ -791,6 +791,22 @@ Uma chamada por memória. O sistema salva e indexa automaticamente.
791
791
  except Exception:
792
792
  pass # Falha silenciosa se frontmatter inválido
793
793
 
794
+ # Indexar no metadata_db para aparecer no dashboard
795
+ if self.metadata_db:
796
+ tags_str = tags if isinstance(tags, str) else ",".join(tags) if isinstance(tags, list) else ""
797
+ self.metadata_db.insert({
798
+ "id": mem_name,
799
+ "type": m_type,
800
+ "project": project,
801
+ "title": frontmatter.get("title", mem_name) if frontmatter else mem_name,
802
+ "content": body_content,
803
+ "tags": tags_str,
804
+ "created_at": datetime.now().isoformat(),
805
+ "updated_at": datetime.now().isoformat(),
806
+ "layer": "auto",
807
+ "path": str(file_path),
808
+ })
809
+
794
810
  return f"✅ Memória '{mem_name}' salva em {file_path}"
795
811
 
796
812
  def _remember(self, args: Dict[str, Any]) -> str:
@@ -898,26 +914,77 @@ Uma chamada por memória. O sistema salva e indexa automaticamente.
898
914
  def _cerebro_dashboard(self, args: Dict[str, Any]) -> str:
899
915
  """Abre o dashboard visual do OCerebro no browser"""
900
916
  try:
901
- from src.dashboard.server import DashboardServer
917
+ import subprocess
918
+ import sys
919
+ import requests
902
920
 
903
921
  port = args.get("port", 7999)
904
922
 
905
- # Instancia servidor
906
- dashboard_server = DashboardServer(
907
- cerebro_path=self.cerebro_path,
908
- metadata_db=self.metadata_db,
909
- embeddings_db=self.embeddings_db,
910
- entities_db=self.entities_db
911
- )
923
+ # Verifica se já está rodando
924
+ import socket
925
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
926
+ sock.settimeout(1)
927
+ result = sock.connect_ex(('127.0.0.1', port))
928
+ sock.close()
929
+ is_running = result == 0
930
+
931
+ if is_running:
932
+ # Verifica se o servidor está usando o cerebro_path correto
933
+ try:
934
+ resp = requests.get(f"http://127.0.0.1:{port}/api/ping", timeout=2)
935
+ if resp.status_code == 200:
936
+ data = resp.json()
937
+ running_path = data.get("cerebro_path", "")
938
+ current_path = str(self.cerebro_path.absolute())
939
+
940
+ if running_path != current_path:
941
+ # Path diferente - precisa reiniciar o servidor
942
+ # Lê o PID e mata o processo antigo
943
+ pid_file = Path.home() / ".ocerebro_dashboard.pid"
944
+ if pid_file.exists():
945
+ try:
946
+ old_pid = int(pid_file.read_text(encoding="utf-8").strip())
947
+ import os
948
+ os.kill(old_pid, 9) # SIGKILL
949
+ pid_file.unlink() # Remove o arquivo PID
950
+ except Exception:
951
+ pass # Processo já morreu ou erro ao matar
952
+
953
+ # Agora is_running será False e vamos reiniciar abaixo
954
+ is_running = False
955
+ except Exception:
956
+ pass # Se falhar o ping, tenta reiniciar mesmo assim
957
+
958
+ if not is_running:
959
+ # Inicia como processo separado (persiste após o MCP terminar)
960
+ server_script = Path(__file__).parent.parent / "dashboard" / "standalone_server.py"
961
+ if not server_script.exists():
962
+ return "⚠️ Erro: standalone_server.py não encontrado."
963
+
964
+ # Inicia processo em background
965
+ subprocess.Popen(
966
+ [sys.executable, str(server_script), str(port), str(self.cerebro_path.absolute())],
967
+ stdout=subprocess.DEVNULL,
968
+ stderr=subprocess.DEVNULL,
969
+ start_new_session=True # Desacoplado do processo pai
970
+ )
912
971
 
913
- # Inicia se não estiver rodando
914
- if not dashboard_server.is_running(port):
915
- started = dashboard_server.start(port)
916
- if not started:
917
- return "⚠️ Não foi possível iniciar o servidor do dashboard. Verifique se a porta está disponível."
972
+ # Aguarda servidor estar pronto (até 5s)
973
+ import time
974
+ for _ in range(50):
975
+ time.sleep(0.1)
976
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
977
+ sock.settimeout(1)
978
+ result = sock.connect_ex(('127.0.0.1', port))
979
+ sock.close()
980
+ if result == 0:
981
+ break
982
+ else:
983
+ return "⚠️ Servidor não iniciou em 5 segundos."
918
984
 
919
985
  # Abre browser
920
- dashboard_server.open_browser(port)
986
+ import webbrowser
987
+ webbrowser.open(f"http://localhost:{port}")
921
988
 
922
989
  return f"✅ Dashboard aberto em http://localhost:{port}"
923
990
  except ImportError as e: