Initial Arka plugin registry: Official plugins mirror + Arka-signed index

11 plugins from github.com/librefang/librefang-registry plugins/.
index.json / index.json.sig are signed with Arka's Ed25519 key
(not upstream stats.librefang.ai). Private key is not in this repo.
This commit is contained in:
ixoblakp committed 2026-09-01 10:22:07 +03:00
commit 8dec8f6038
62 files changed
+5189

No files matched your search

+153
View File
@@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""Auto-summarizer after_turn hook.
Generates a compact extractive summary of the conversation after each turn.
Keeps agents aware of conversation context even in long exchanges.
Receives via stdin:
{"type": "after_turn", "agent_id": "...", "messages": [...]}
Prints to stdout:
{"type": "ok"}
"""
import json
import os
import sys
# Only summarize when conversation exceeds this many messages
MIN_MESSAGES_FOR_SUMMARY = 6
# Maximum length of the generated summary
MAX_SUMMARY_CHARS = 500
# Keywords that signal decisions or conclusions
DECISION_KEYWORDS = (
"let's", "i'll", "we should", "decided", "agreed",
"the plan is", "we'll", "going to", "conclusion",
"in summary", "to summarize", "final answer",
)
def get_storage_dir():
"""Return the plugin storage directory, creating it if needed."""
home = os.path.expanduser("~")
path = os.path.join(home, ".librefang", "plugins", "auto-summarizer")
os.makedirs(path, exist_ok=True)
return path
def get_content(msg):
"""Extract text content from a message object."""
if isinstance(msg, dict):
return msg.get("content", "") or ""
return str(msg)
def get_role(msg):
"""Extract the role from a message object."""
if isinstance(msg, dict):
return msg.get("role", "unknown")
return "unknown"
def contains_question(text):
"""Check if text contains a question."""
return "?" in text
def contains_decision(text):
"""Check if text contains decision/conclusion language."""
lower = text.lower()
return any(kw in lower for kw in DECISION_KEYWORDS)
def truncate(text, max_len):
"""Truncate text to max_len, adding ellipsis if needed."""
if len(text) <= max_len:
return text
return text[:max_len - 3] + "..."
def build_summary(messages):
"""Build an extractive summary from the conversation messages.
Strategy:
- First user message (topic opener)
- Messages containing questions
- Messages containing decisions/conclusions
- Last 2 exchanges (most recent context)
Deduplicates and truncates to MAX_SUMMARY_CHARS.
"""
if len(messages) <= MIN_MESSAGES_FOR_SUMMARY:
return ""
selected = []
seen_indices = set()
# 1. First user message (topic opener)
for i, msg in enumerate(messages):
if get_role(msg) == "user":
content = get_content(msg).strip()
if content:
selected.append(f"Topic: {truncate(content, 120)}")
seen_indices.add(i)
break
# 2. Messages containing questions
for i, msg in enumerate(messages):
if i in seen_indices:
continue
content = get_content(msg).strip()
if content and contains_question(content):
role = get_role(msg)
prefix = "Q" if role == "user" else "Agent-Q"
selected.append(f"{prefix}: {truncate(content, 100)}")
seen_indices.add(i)
# 3. Messages containing decisions/conclusions
for i, msg in enumerate(messages):
if i in seen_indices:
continue
content = get_content(msg).strip()
if content and contains_decision(content):
selected.append(f"Decision: {truncate(content, 100)}")
seen_indices.add(i)
# 4. Last 2 exchanges (up to 4 messages: user+assistant pairs)
tail_start = max(0, len(messages) - 4)
for i in range(tail_start, len(messages)):
if i in seen_indices:
continue
content = get_content(messages[i]).strip()
if content:
role = get_role(messages[i])
label = "User" if role == "user" else "Agent"
selected.append(f"Recent({label}): {truncate(content, 100)}")
seen_indices.add(i)
if not selected:
return ""
summary = " | ".join(selected)
return truncate(summary, MAX_SUMMARY_CHARS)
def main():
request = json.loads(sys.stdin.read())
agent_id = request.get("agent_id", "unknown")
messages = request.get("messages", [])
summary = build_summary(messages)
if summary:
storage_dir = get_storage_dir()
summary_path = os.path.join(storage_dir, f"{agent_id}.summary")
with open(summary_path, "w", encoding="utf-8") as f:
f.write(summary)
print(json.dumps({"type": "ok"}), flush=True)
if __name__ == "__main__":
main()
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""Auto-summarizer ingest hook.
Returns the stored conversation summary as a memory fragment so agents
maintain awareness of prior conversation context.
Receives via stdin:
{"type": "ingest", "agent_id": "...", "message": "user message text"}
Prints to stdout:
{"type": "ingest_result", "memories": [{"content": "..."}]}
"""
import json
import os
import sys
def get_summary_path(agent_id):
"""Return the path to the summary file for the given agent."""
home = os.path.expanduser("~")
return os.path.join(
home, ".librefang", "plugins", "auto-summarizer", f"{agent_id}.summary"
)
def main():
request = json.loads(sys.stdin.read())
agent_id = request.get("agent_id", "unknown")
memories = []
summary_path = get_summary_path(agent_id)
if os.path.isfile(summary_path):
try:
with open(summary_path, "r", encoding="utf-8") as f:
summary = f.read().strip()
if summary:
memories.append(
{"content": f"[summary] Conversation so far: {summary}"}
)
except (OSError, IOError):
# If we cannot read the file, return no memories silently.
pass
print(json.dumps({"type": "ingest_result", "memories": memories}))
if __name__ == "__main__":
main()