diff --git a/agent/context_engine.py b/agent/context_engine.py index 3acfdb5c..9154d813 100644 --- a/agent/context_engine.py +++ b/agent/context_engine.py @@ -118,11 +118,14 @@ class ContextEngine(ABC): """ return [] - def handle_tool_call(self, name: str, args: Dict[str, Any]) -> str: + def handle_tool_call(self, name: str, args: Dict[str, Any], **kwargs) -> str: """Handle a tool call from the agent. Only called for tool names returned by get_tool_schemas(). Must return a JSON string. + + kwargs may include: + messages: the current in-memory message list (for live ingestion) """ import json return json.dumps({"error": f"Unknown context engine tool: {name}"}) diff --git a/run_agent.py b/run_agent.py index 2af911af..98ec4ec3 100644 --- a/run_agent.py +++ b/run_agent.py @@ -1295,6 +1295,31 @@ class AIAgent: provider=self.provider, ) self.compression_enabled = compression_enabled + + # Inject context engine tool schemas (e.g. lcm_grep, lcm_describe, lcm_expand) + self._context_engine_tool_names: set = set() + if hasattr(self, "context_compressor") and self.context_compressor and self.tools is not None: + for _schema in self.context_compressor.get_tool_schemas(): + _wrapped = {"type": "function", "function": _schema} + self.tools.append(_wrapped) + _tname = _schema.get("name", "") + if _tname: + self.valid_tool_names.add(_tname) + self._context_engine_tool_names.add(_tname) + + # Notify context engine of session start + if hasattr(self, "context_compressor") and self.context_compressor: + try: + self.context_compressor.on_session_start( + self.session_id, + hermes_home=str(get_hermes_home()), + platform=self.platform or "cli", + model=self.model, + context_length=getattr(self.context_compressor, "context_length", 0), + ) + except Exception as _ce_err: + logger.debug("Context engine on_session_start: %s", _ce_err) + self._subdirectory_hints = SubdirectoryHintTracker( working_dir=os.getenv("TERMINAL_CWD") or None, ) @@ -6885,6 +6910,29 @@ class AIAgent: spinner.stop(cute_msg) elif self._should_emit_quiet_tool_messages(): self._vprint(f" {cute_msg}") + elif self._context_engine_tool_names and function_name in self._context_engine_tool_names: + # Context engine tools (lcm_grep, lcm_describe, lcm_expand, etc.) + spinner = None + if self.quiet_mode and not self.tool_progress_callback: + face = random.choice(KawaiiSpinner.KAWAII_WAITING) + emoji = _get_tool_emoji(function_name) + preview = _build_tool_preview(function_name, function_args) or function_name + spinner = KawaiiSpinner(f"{face} {emoji} {preview}", spinner_type='dots', print_fn=self._print_fn) + spinner.start() + _ce_result = None + try: + function_result = self.context_compressor.handle_tool_call(function_name, function_args, messages=messages) + _ce_result = function_result + except Exception as tool_error: + function_result = json.dumps({"error": f"Context engine tool '{function_name}' failed: {tool_error}"}) + logger.error("context_engine.handle_tool_call raised for %s: %s", function_name, tool_error, exc_info=True) + finally: + tool_duration = time.time() - tool_start_time + cute_msg = _get_cute_tool_message_impl(function_name, function_args, tool_duration, result=_ce_result) + if spinner: + spinner.stop(cute_msg) + elif self.quiet_mode: + self._vprint(f" {cute_msg}") elif self._memory_manager and self._memory_manager.has_tool(function_name): # Memory provider tools (hindsight_retain, honcho_search, etc.) # These are not in the tool registry — route through MemoryManager.