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

+31
View File
@@ -0,0 +1,31 @@
# auto-summarizer
Maintains a running conversation summary to help agents handle long conversations without losing context. Uses extractive summarization (no ML or external dependencies) to identify the most important parts of a conversation.
## How it works
After each conversation turn, the plugin scans all messages and extracts:
- **Topic opener** -- the first user message that started the conversation
- **Questions** -- any messages containing questions (detected via `?`)
- **Decisions** -- messages with conclusion/decision language ("let's", "decided", "the plan is", etc.)
- **Recent context** -- the last 2 exchanges to preserve immediate context
These are combined into a compact summary (max 500 characters) and persisted to disk. On the next ingest, the summary is returned as a memory fragment so the agent retains awareness of the full conversation.
Summarization only activates when the conversation exceeds 6 messages -- shorter conversations are passed through as-is.
## Hooks
| Hook | Script | Description |
|------|--------|-------------|
| ingest | `hooks/ingest.py` | Returns the stored conversation summary as a memory fragment |
| after_turn | `hooks/after_turn.py` | Builds and persists an extractive summary of the conversation |
## Storage
Summaries are stored at `~/.librefang/plugins/auto-summarizer/{agent_id}.summary`.
## Usage
Installed automatically when enabled in agent configuration.
+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()
+41
View File
@@ -0,0 +1,41 @@
name = "auto-summarizer"
version = "0.1.0"
description = "Maintains a running conversation summary to help agents handle long conversations without losing context"
author = "librefang"
[hooks]
ingest = "hooks/ingest.py"
after_turn = "hooks/after_turn.py"
[i18n.zh]
name = "自动摘要"
description = "持续维护会话摘要,帮助 Agent 在长对话中不丢失上下文。"
[i18n.zh-TW]
name = "自動摘要"
description = "持續維護會話摘要,幫助 Agent 在長對話中不遺失上下文。"
[i18n.ja]
name = "自動要約"
description = "会話の要約を逐次更新し、長い会話でもコンテキストを失わないよう支援。"
[i18n.ko]
name = "자동 요약"
description = "진행 중인 대화 요약을 유지하여 긴 대화에서 컨텍스트 손실을 방지."
[i18n.de]
name = "Auto-Zusammenfassung"
description = "Pflegt eine laufende Konversations-Zusammenfassung, damit Agenten bei langen Gesprächen den Kontext behalten."
[i18n.es]
name = "Auto-resumen"
description = "Mantiene un resumen continuo de la conversación para que los agentes no pierdan contexto en diálogos largos."
[i18n.fr]
name = "Auto-résumé"
description = "Maintient un résumé continu de la conversation pour que les agents ne perdent pas de contexte dans les longs échanges."
[integrity]
"hooks/after_turn.py" = "fe24d5f8dd85348f855f7e2c27e607e57c4fad549d50d814101be14fa5ab34f1"
"hooks/ingest.py" = "0581dd415457bffc3160580acf3036a2fe9e211e86b49bbc51cc30118dc67e0e"
+1
View File
@@ -0,0 +1 @@
# No external dependencies — uses only Python stdlib