feat: add 6 production-ready plugins

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
This commit is contained in:
Evan Hu committed 2026-03-21 02:51:58 +09:00
1 parent 994b60c0d9
commit afcb260554
25 files changed
+1396 -4

No files matched your search

@@ -0,0 +1,68 @@
#!/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()