All plugins are stdlib-only Python with no external dependencies. - auto-summarizer: extractive conversation summary for long context compression, persists per-agent summaries to disk - conversation-logger: JSONL audit logs per agent with ISO 8601 timestamps, auto-creates log directory tree - guardrails: safety filter detecting PII (email, phone, SSN, CC), prompt injection patterns, and credential exposure via regex - keyword-memory: extracts entities (emails, URLs, dates, technical terms like camelCase/snake_case/dotted identifiers) as memories - sentiment-tracker: keyword-based sentiment scoring with intensifiers and negation handling, only injects context for non-neutral sentiment - todo-tracker: detects action items via 7 task patterns, tracks completion, deduplicates, persists per-agent with 20-item FIFO limit
69 lines
2.1 KiB
Python
69 lines
2.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Conversation logger after_turn hook.
|
|
|
|
Appends a JSON line to a per-agent log file after each conversation turn.
|
|
Log files are stored under ~/.librefang/logs/conversations/{agent_id}.jsonl.
|
|
|
|
Receives via stdin:
|
|
{"type": "after_turn", "agent_id": "...", "messages": [...]}
|
|
|
|
Prints to stdout:
|
|
{"type": "ok"}
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
|
|
def _log_dir() -> Path:
|
|
"""Return the conversations log directory, creating it if needed."""
|
|
home = Path.home()
|
|
log_path = home / ".librefang" / "logs" / "conversations"
|
|
log_path.mkdir(parents=True, exist_ok=True)
|
|
return log_path
|
|
|
|
|
|
def _last_message_by_role(messages: list, role: str) -> str:
|
|
"""Find the last message with the given role and return its content truncated to 200 chars."""
|
|
for msg in reversed(messages):
|
|
if msg.get("role") == role:
|
|
content = msg.get("content", "")
|
|
if len(content) > 200:
|
|
return content[:200] + "..."
|
|
return content
|
|
return ""
|
|
|
|
|
|
def main():
|
|
request = json.loads(sys.stdin.read())
|
|
agent_id = request.get("agent_id", "unknown")
|
|
messages = request.get("messages", [])
|
|
|
|
entry = {
|
|
"timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
"agent_id": agent_id,
|
|
"turn_number": max(
|
|
sum(1 for m in messages if m.get("role") == "assistant"), 1
|
|
),
|
|
"message_count": len(messages),
|
|
"last_user_message": _last_message_by_role(messages, "user"),
|
|
"last_assistant_message": _last_message_by_role(messages, "assistant"),
|
|
}
|
|
|
|
try:
|
|
log_file = _log_dir() / f"{agent_id}.jsonl"
|
|
with open(log_file, "a", encoding="utf-8") as f:
|
|
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
except OSError:
|
|
# Filesystem issues should not crash the agent. The hook still
|
|
# responds with "ok" so the conversation continues normally.
|
|
pass
|
|
|
|
print(json.dumps({"type": "ok"}))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|