teleo-infrastructure/hermes-agent/patches/apply_response_transform_hook.py
2026-07-15 22:49:20 +02:00

190 lines
7.3 KiB
Python

#!/usr/bin/env python3
"""Install the managed Hermes response and Telegram name-provenance patches."""
from __future__ import annotations
import argparse
import ast
import hashlib
import importlib.util
import json
import os
import tempfile
from pathlib import Path
PATCH_VERSION = "teleo-response-transform-hook-v1"
RUNTIME_PATCH_VERSION = "teleo-hermes-runtime-patches-v2"
PATCH_MARKER = " # TELEO_RESPONSE_TRANSFORM_HOOK_V1\n"
START_ANCHOR = " # Save trajectory if enabled\n"
END_ANCHOR = " # Extract reasoning from the last assistant message (if any)\n"
REPLACEMENT = ''' # TELEO_RESPONSE_TRANSFORM_HOOK_V1
# Run response validators before trajectory/session persistence. A plugin
# may return {"assistant_response": "..."}; the last non-empty value wins.
# This keeps delivered text and durable conversation memory identical.
if final_response and not interrupted:
try:
from hermes_cli.plugins import invoke_hook as _invoke_hook
_post_results = _invoke_hook(
"post_llm_call",
session_id=self.session_id,
user_message=original_user_message,
assistant_response=final_response,
conversation_history=list(messages),
model=self.model,
platform=getattr(self, "platform", None) or "",
)
_original_final_response = final_response
for _post_result in _post_results:
if not isinstance(_post_result, dict):
continue
_candidate = _post_result.get("assistant_response")
if isinstance(_candidate, str) and _candidate.strip():
final_response = _candidate.strip()
if final_response != _original_final_response:
_updated_assistant_message = False
for _message in reversed(messages):
if _message.get("role") == "assistant" and not _message.get("tool_calls"):
_message["content"] = final_response
_updated_assistant_message = True
break
if not _updated_assistant_message:
messages.append({"role": "assistant", "content": final_response})
except Exception as exc:
logger.warning("post_llm_call hook failed: %s", exc)
# Save trajectory if enabled
self._save_trajectory(messages, user_message, completed)
# Clean up VM and browser for this task after conversation completes
self._cleanup_task_resources(effective_task_id)
# Persist session to both JSON log and SQLite
self._persist_session(messages, conversation_history)
'''
def sha256_text(value: str) -> str:
return hashlib.sha256(value.encode("utf-8")).hexdigest()
def patch_source(source: str) -> tuple[str, bool]:
if PATCH_MARKER in source:
required = (
'_candidate = _post_result.get("assistant_response")',
"self._save_trajectory(messages, user_message, completed)",
"self._persist_session(messages, conversation_history)",
)
missing = [item for item in required if item not in source]
if missing:
raise ValueError(f"installed patch marker is incomplete: {missing}")
return source, False
if source.count(START_ANCHOR) != 1 or source.count(END_ANCHOR) != 1:
raise ValueError("Hermes response lifecycle anchors changed; refusing an unreviewed patch")
start = source.index(START_ANCHOR)
end = source.index(END_ANCHOR, start)
original_block = source[start:end]
required_original = (
'"post_llm_call"',
"self._save_trajectory(messages, user_message, completed)",
"self._cleanup_task_resources(effective_task_id)",
"self._persist_session(messages, conversation_history)",
)
missing = [item for item in required_original if item not in original_block]
if missing:
raise ValueError(f"Hermes response lifecycle changed; missing expected statements: {missing}")
patched = source[:start] + REPLACEMENT + source[end:]
ast.parse(patched)
return patched, True
def install(path: Path, *, check: bool) -> tuple[dict[str, str | bool], int]:
source = path.read_text(encoding="utf-8")
patched, changed = patch_source(source)
before_sha = sha256_text(source)
after_sha = sha256_text(patched)
if check:
status = "patch_required" if changed else "installed"
return {
"patch_version": PATCH_VERSION,
"status": status,
"target": str(path),
"sha256": before_sha,
}, 1 if changed else 0
if changed:
mode = path.stat().st_mode & 0o777
with tempfile.NamedTemporaryFile(
"w", encoding="utf-8", dir=path.parent, prefix=f".{path.name}.", delete=False
) as handle:
handle.write(patched)
temp_path = Path(handle.name)
try:
temp_path.chmod(mode)
os.replace(temp_path, path)
finally:
temp_path.unlink(missing_ok=True)
ast.parse(path.read_text(encoding="utf-8"))
return {
"patch_version": PATCH_VERSION,
"status": "installed_now" if changed else "already_installed",
"target": str(path),
"before_sha256": before_sha,
"after_sha256": after_sha,
}, 0
def _load_telegram_name_policy_patcher():
path = Path(__file__).with_name("apply_telegram_sender_name_policy.py")
spec = importlib.util.spec_from_file_location("teleo_telegram_sender_name_policy", path)
if not spec or not spec.loader:
raise RuntimeError(f"could not load Telegram name-policy patcher: {path}")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def install_runtime(run_agent_path: Path, *, check: bool) -> tuple[dict[str, object], int]:
response_result, response_code = install(run_agent_path, check=check)
name_policy = _load_telegram_name_policy_patcher()
telegram_path = run_agent_path.parent / "gateway" / "platforms" / "telegram.py"
name_result, name_code = name_policy.install(telegram_path, check=check)
if check:
changed = bool(response_code or name_code)
status = "patch_required" if changed else "installed"
code = 1 if changed else 0
else:
changed = any(
result.get("status") == "installed_now"
for result in (response_result, name_result)
)
status = "installed_now" if changed else "already_installed"
code = 0
return {
"patch_version": RUNTIME_PATCH_VERSION,
"status": status,
"agent_root": str(run_agent_path.parent),
"response_transform": response_result,
"telegram_sender_name_policy": name_result,
}, code
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("target", type=Path)
parser.add_argument("--check", action="store_true")
args = parser.parse_args()
result, code = install_runtime(args.target, check=args.check)
print(json.dumps(result, sort_keys=True))
return code
if __name__ == "__main__":
raise SystemExit(main())