File size: 6,517 Bytes
891669b d715ed0 891669b 6186ca4 cce30c5 891669b e9073c0 891669b 6186ca4 adbf39e 6186ca4 cce30c5 acf2df3 adbf39e 891669b e9073c0 891669b 859c2fd 891669b cce30c5 acf2df3 891669b cce30c5 edeb6b2 cce30c5 891669b edeb6b2 e9073c0 d4d4456 e9073c0 d4d4456 edeb6b2 e9073c0 d4d4456 891669b edeb6b2 891669b 68bf032 e9073c0 edeb6b2 e9073c0 891669b 68bf032 891669b e9073c0 891669b e9073c0 d715ed0 891669b edeb6b2 891669b cce30c5 891669b cce30c5 891669b cce30c5 891669b cce30c5 891669b cce30c5 891669b d4d4456 891669b d4d4456 891669b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | # ---- Changelog ----
# [2026-04-06] Josh + Claude — Add edit_file facade method
# What: Delegate edit_file to FilesystemTool
# Why: New edit_file tool needs facade wiring like read_file/write_file
# How: One-line delegation to self._fs.edit_file()
# [2026-03-29] Chisel/TQB — Block C: Rewrite as thin facade
# What: RecursiveContextManager is now a facade delegating to tools/ classes
# Why: PRD Block C — split 267-line god-class into focused single-responsibility tools
# How: Constructor wires tool instances; methods delegate; cross-cutting sync stays here
# -------------------
import logging
import os
from pathlib import Path
from typing import List, Dict
from huggingface_hub import HfApi, hf_hub_download
from openclaw_hook import NeuroGraphMemory
from tools import FilesystemTool, GitTool, NotebookTool, NeurographTool, ShellTool, WorkspaceTool
logger = logging.getLogger("recursive_context")
class RecursiveContextManager:
def __init__(self, repo_path: str, ng=None):
self.repo_path = Path(repo_path)
self.memory_path = self.repo_path / "memory"
self.notebook_file = self.memory_path / "notebook.json"
self.token = os.getenv("HF_TOKEN")
self.dataset_id = os.getenv("DATASET_ID", "Executor-Tyrant-Framework/clawdbot-memory")
# Use the passed-in NG instance (owned by worker_ng) or fall back to creating one
if ng is not None:
self.ng = ng
else:
neurograph_workspace = os.getenv(
"NEUROGRAPH_WORKSPACE_DIR",
str(self.repo_path / "data" / "neurograph_worker")
)
self.ng = NeuroGraphMemory.get_instance(workspace_dir=neurograph_workspace)
logger.info("NeuroGraph Memory Loaded.")
self._saves_since_ng_backup = 0
self.NG_BACKUP_EVERY_N = 10
# --- Tool instances ---
import policy_engine as pe # Cricket-shaped enforcement
self._fs = FilesystemTool(self.repo_path, pe)
self._git = GitTool(self.repo_path, pe)
self._notebook = NotebookTool(self.repo_path, pe,
notebook_file=self.notebook_file,
save_callback=self._save_notebook)
self._ng_tool = NeurographTool(self.repo_path, self.ng, pe)
self._shell = ShellTool(self.repo_path, pe)
self._workspace = WorkspaceTool(self.repo_path, self.ng, pe)
self._init_memory()
# === Cross-cutting sync (stays in facade) ===
def _init_memory(self):
self.memory_path.mkdir(parents=True, exist_ok=True)
if self.token:
try:
hf_hub_download(
repo_id=self.dataset_id, filename="notebook.json", repo_type="dataset",
token=self.token, local_dir=self.memory_path, local_dir_use_symlinks=False
)
except (OSError, ValueError) as e:
logger.warning("Failed to download notebook from HF: %s", e)
self._notebook._save_local([])
def _save_notebook(self, notes: List[Dict]):
if self.token and self.dataset_id:
try:
api = HfApi(token=self.token)
api.upload_file(
path_or_fileobj=self.notebook_file, path_in_repo="notebook.json",
repo_id=self.dataset_id, repo_type="dataset",
commit_message=f"Notebook Update: {len(notes)}"
)
except (OSError, ConnectionError) as e:
logger.warning("HF notebook sync failed: %s", e)
def _backup_ng_checkpoint_to_dataset(self):
if not self.token:
return
checkpoint_path = Path(self.ng.save())
if not checkpoint_path.exists():
return
try:
api = HfApi(token=self.token)
api.upload_file(
path_or_fileobj=checkpoint_path,
path_in_repo="neurograph/main.msgpack",
repo_id=self.dataset_id,
repo_type="dataset",
commit_message=f"NeuroGraph checkpoint ({self.ng.stats()['nodes']} nodes)"
)
logger.info("NeuroGraph checkpoint uploaded.")
except (OSError, ConnectionError) as e:
logger.warning("NeuroGraph checkpoint upload failed: %s", e)
def save_conversation_turn(self, user_msg: str, assist_msg: str, turn_id: int):
from openclaw_hook import NeuroGraphMemory
from universal_ingestor import SourceType
combined = f"USER: {user_msg}\n\nASSISTANT: {assist_msg}"
self.ng.on_message(combined, source_type=SourceType.TEXT)
self.ng.step(5)
self._saves_since_ng_backup += 1
if self._saves_since_ng_backup >= self.NG_BACKUP_EVERY_N:
self._backup_ng_checkpoint_to_dataset()
self._saves_since_ng_backup = 0
# === Delegated methods ===
def read_file(self, path, start_line=None, end_line=None):
return self._fs.read_file(path, start_line, end_line)
def write_file(self, path, content):
return self._fs.write_file(path, content)
def edit_file(self, path, old_text, new_text):
return self._fs.edit_file(path, old_text, new_text)
def list_files(self, path=".", max_depth=3):
return self._fs.list_files(path, max_depth)
def push_to_github(self, message):
return self._git.push_to_github(message)
def pull_from_github(self, branch):
return self._git.pull_from_github(branch)
def create_shadow_branch(self):
return self._git.create_shadow_branch()
def notebook_read(self):
return self._notebook.notebook_read()
def notebook_add(self, content):
return self._notebook.notebook_add(content)
def notebook_delete(self, index):
return self._notebook.notebook_delete(index)
def search_conversations(self, query, n=5):
return self._ng_tool.search_conversations(query, n)
def search_code(self, query, n=5):
return self._ng_tool.search_code(query, n)
def search_testament(self, query, n=5):
return self._ng_tool.search_testament(query, n)
def ingest_workspace(self):
return self._ng_tool.ingest_workspace()
def shell_execute(self, command):
return self._shell.shell_execute(command)
def map_repository_structure(self):
return self._workspace.map_repository_structure()
def get_stats(self):
return self._workspace.get_stats()
|