AI Update: روی حالت های تمامی حافظه های موجود پروژه های کاربران رو همر

This commit is contained in:
Antigravity Bot
2026-08-30 10:22:38 +03:30
parent 291a788e1e
commit f67683abdc
9 changed files with 493 additions and 168 deletions
+6
View File
@@ -84,3 +84,9 @@ apt install htop
htop
ذفخح
btop
agyy
agy
ls
cd telegram-agy-bot/
ls
agy
+7
View File
@@ -36,5 +36,12 @@
## Preferred Programming Language & Tech Stack (PHP)
- ALWAYS use **PHP** (e.g. PHP 8.x, modern PHP, Laravel/Symfony/Slim or standalone PHP scripts/APIs) as the primary/default programming language and technology stack for all projects and backend services whenever feasible and applicable, unless the user explicitly requests another language or the nature of the task strictly requires a different technology.
## Memory & Instruction Precedence Hierarchy
- ALWAYS resolve conflicts and prioritize instructions according to the strict 3-tier hierarchy:
1. **Tier 1 (Highest Authority):** Global System Rules & Memories (قوانین و دستورات عمومی سیستم) - Always strictly supersedes all private/user/project instructions.
2. **Tier 2 (Intermediate Authority):** Active Project Memories & Decisions (دستورات و خاطرات اختصاصی هر پروژه) - Supersedes general user profile preferences when operating inside that project.
3. **Tier 3 (Baseline Authority):** General User Personal Preferences (دستورات و ترجیحات کلی کاربر) - Applied when not overridden by project or global rules.
+6 -2
View File
@@ -1326,10 +1326,14 @@ class AGYEngine:
except Exception as ex:
logger.warning(f"Failed to load bot actions instruction: {ex}")
# AI Long-Term Persistent Memory Instruction (Global + User Private)
# AI Long-Term Persistent Hierarchical Memory (Global + User + Active Project)
try:
from memory_manager import memory_manager
memory_instruction = memory_manager.format_memories_for_prompt(user_id=session.chat_id)
active_p_name = session.active_project or (session.current_project.name if session.current_project else None)
memory_instruction = memory_manager.format_memories_for_prompt(
user_id=session.chat_id,
project_name=active_p_name,
)
if memory_instruction:
system_instructions.append(memory_instruction)
except Exception as mex:
+131 -36
View File
@@ -1105,19 +1105,26 @@ def process_ai_schedule_actions(raw_text: str, chat_id: int, project_name: str,
return processed_text, created_tasks
def build_memory_menu(chat_id: int, view_type: str = "private", page: int = 0) -> tuple[str, InlineKeyboardMarkup]:
"""Builds interactive AI Long-Term Memory manager dashboard with tabs (private vs global), pagination, and deletion."""
def build_memory_menu(chat_id: int, view_type: str = "project", page: int = 0) -> tuple[str, InlineKeyboardMarkup]:
"""Builds interactive 3-tier AI Memory manager dashboard with tabs (project, user, global), pagination, and deletion."""
from memory_manager import memory_manager
session = session_manager.get_or_create(chat_id)
curr_proj = session_manager.get_current_project(chat_id)
is_fa = (session.language or "").lower() in ("fa", "farsi", "persian", "🇮🇷 persian / farsi (فارسی)")
is_admin_user = settings.is_admin(chat_id)
view_type = "global" if view_type == "global" else "private"
proj_name = curr_proj.name if curr_proj else "default"
if view_type == "global":
memories = memory_manager.get_global_memories(limit=100)
else:
# Validate view_type
if view_type not in ("project", "user", "global"):
view_type = "project" if curr_proj else "user"
if view_type == "project":
memories = memory_manager.get_project_memories(user_id=chat_id, project_name=proj_name, limit=100)
elif view_type == "user":
memories = memory_manager.get_user_memories(user_id=chat_id, limit=100)
else: # global
memories = memory_manager.get_global_memories(limit=100)
total_cnt = len(memories)
page_size = 5
@@ -1125,19 +1132,29 @@ def build_memory_menu(chat_id: int, view_type: str = "private", page: int = 0) -
page = max(0, min(page, max_pages - 1))
paged_memories = memories[page * page_size : (page + 1) * page_size]
stats = memory_manager.get_stats()
stats = memory_manager.get_stats(user_id=chat_id, project_name=proj_name)
if is_fa:
tab_name = "🌐 خاطرات عمومی (کل سیستم)" if view_type == "global" else "👤 خاطرات خصوصی (اختصاصی شما)"
if view_type == "project":
tab_name = f"📁 خاطرات اختصاصی پروژه (<code>{escape_html(proj_name)}</code>)"
tab_desc = "این خاطرات <b>فقط هنگام فعالیت در همین پروژه</b> به هوش مصنوعی تزریق می‌شوند."
elif view_type == "user":
tab_name = "👤 خاطرات شخصی من (سراسری)"
tab_desc = "این خاطرات مربوط به شخص شماست و در <b>تمام پروژه‌ها</b> اعمال می‌گردد."
else:
tab_name = "🌐 قوانین و خاطرات عمومی (کل سرور و کاربران)"
tab_desc = "این قوانین برای <b>همه کاربران</b> تزریق می‌شوند (ویرایش و حذف فقط توسط مدیر)."
header = (
f"🧠 <b>کنترل‌پنل حافظه هوشمند هوش مصنوعی (AI Long-Term Memory)</b>\n\n"
f"🧠 <b>کنترل‌پنل حافظه هوشمند ۳ لایه (AI Memory Dashboard)</b>\n\n"
f"• 📂 <b>بخش فعال:</b> {tab_name}\n"
f"📊 <b>آمار کل:</b> <code>{stats['total_memories']}</code> خاطره (<code>{stats['global_memories']}</code> عمومی | <code>{stats['private_memories']}</code> خصوصی)\n\n"
f"{tab_desc}\n"
f"• 📊 <b>آمار:</b> <code>{stats['current_project_memories']}</code> پروژه | <code>{stats['current_user_memories']}</code> کاربر | <code>{stats['global_memories']}</code> عمومی\n\n"
)
if not memories:
body = (
f"️ <i>در این بخش هنوز هیچ خاطره‌ای ثبت نشده است.</i>\n\n"
f"💡 <b>یادگیری خودکار:</b> هوش مصنوعی به صورت خودکار در خلال گفتگوها، عادات، نام و نکات مهم شما را استخراج و ذخیره می‌کند."
f"💡 <b>یادگیری خودکار:</b> هوش مصنوعی در حین گفتگوها نکات مهم مربوط به این بخش را خودکار استخراج و ثبت می‌کند."
)
else:
body_lines = [f"<b>لیست خاطرات (صفحه {page + 1} از {max_pages}):</b>\n"]
@@ -1149,16 +1166,26 @@ def build_memory_menu(chat_id: int, view_type: str = "private", page: int = 0) -
)
body = "\n\n".join(body_lines)
else:
tab_name = "🌐 Global System Memory" if view_type == "global" else "👤 Private User Memory"
if view_type == "project":
tab_name = f"📁 Project Memory (<code>{escape_html(proj_name)}</code>)"
tab_desc = "Injected only when working in this project."
elif view_type == "user":
tab_name = "👤 User Profile Memory"
tab_desc = "Applies to you across all your projects."
else:
tab_name = "🌐 Global System Memory"
tab_desc = "Applies to all users (admin-managed)."
header = (
f"🧠 <b>AI Long-Term Memory Dashboard</b>\n\n"
f"🧠 <b>3-Tier AI Memory Dashboard</b>\n\n"
f"• 📂 <b>Active Tab:</b> {tab_name}\n"
f"📊 <b>Stats:</b> <code>{stats['total_memories']}</code> total (<code>{stats['global_memories']}</code> global | <code>{stats['private_memories']}</code> private)\n\n"
f"{tab_desc}\n"
f"• 📊 <b>Stats:</b> <code>{stats['current_project_memories']}</code> project | <code>{stats['current_user_memories']}</code> user | <code>{stats['global_memories']}</code> global\n\n"
)
if not memories:
body = "️ <i>No memories recorded in this section yet.</i>\n\n💡 <i>The AI automatically extracts durable facts and preferences during conversations.</i>"
body = "️ <i>No memories recorded in this section yet.</i>"
else:
body_lines = [f"<b>Memories list (Page {page + 1} of {max_pages}):</b>\n"]
body_lines = [f"<b>Memories (Page {page + 1} of {max_pages}):</b>\n"]
for idx, m in enumerate(paged_memories, start=page * page_size + 1):
cat_badge = f"[{m.category}]" if m.category and m.category != "general" else ""
body_lines.append(
@@ -1172,20 +1199,23 @@ def build_memory_menu(chat_id: int, view_type: str = "private", page: int = 0) -
# Build keyboard
keyboard = []
# 1. Tab buttons
tab_priv_text = "👤 خاطرات من (خصوصی) ✓" if view_type == "private" else "👤 خاطرات من (خصوصی)"
tab_glob_text = "🌐 خاطرات عمومی سیستم" if view_type == "global" else "🌐 خاطرات عمومی سیستم"
# 1. 3 Tab buttons
t_proj = f"📁 پروژه ({proj_name[:10]}) ✓" if view_type == "project" else f"📁 پروژه ({proj_name[:10]})"
t_user = "👤 کاربر (من)" if view_type == "user" else "👤 کاربر (من)"
t_glob = "🌐 عمومی ✓" if view_type == "global" else "🌐 عمومی"
if not is_fa:
tab_priv_text = "👤 My Memory" if view_type == "private" else "👤 My Memory"
tab_glob_text = "🌐 Global Memory" if view_type == "global" else "🌐 Global Memory"
t_proj = f"📁 Project" if view_type == "project" else f"📁 Project"
t_user = "👤 User" if view_type == "user" else "👤 User"
t_glob = "🌐 Global ✓" if view_type == "global" else "🌐 Global"
keyboard.append([
InlineKeyboardButton(tab_priv_text, callback_data="mem_tab:private:0"),
InlineKeyboardButton(tab_glob_text, callback_data="mem_tab:global:0"),
InlineKeyboardButton(t_proj, callback_data="mem_tab:project:0"),
InlineKeyboardButton(t_user, callback_data="mem_tab:user:0"),
InlineKeyboardButton(t_glob, callback_data="mem_tab:global:0"),
])
# 2. Item delete buttons (for paged memories)
can_delete_current = (view_type == "private") or (view_type == "global" and is_admin_user)
# 2. Item delete buttons
can_delete_current = (view_type in ("project", "user")) or (view_type == "global" and is_admin_user)
if can_delete_current and paged_memories:
del_row = []
for idx, m in enumerate(paged_memories, start=page * page_size + 1):
@@ -1206,7 +1236,7 @@ def build_memory_menu(chat_id: int, view_type: str = "private", page: int = 0) -
pag_row.append(InlineKeyboardButton("بعدی ➡️" if is_fa else "Next ➡️", callback_data=f"mem_tab:{view_type}:{page + 1}"))
keyboard.append(pag_row)
# 4. Clear memory button (if applicable)
# 4. Clear memory button
action_row = []
if can_delete_current and total_cnt > 0:
clear_label = "🧹 پاکسازی این بخش" if is_fa else "🧹 Clear this section"
@@ -3385,39 +3415,96 @@ async def usage_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
async def memory_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
chat_id = update.effective_chat.id
session = session_manager.get_or_create(chat_id)
curr_proj = session_manager.get_current_project(chat_id)
is_fa = (session.language or "").lower() in ("fa", "farsi", "persian", "🇮🇷 persian / farsi (فارسی)")
is_admin_user = settings.is_admin(chat_id)
from memory_manager import memory_manager
if not context.args:
text, markup = build_memory_menu(chat_id, view_type="private", page=0)
text, markup = build_memory_menu(chat_id, view_type="project" if curr_proj else "user", page=0)
await update.message.reply_html(text, reply_markup=markup)
return
subcmd = context.args[0].lower().strip()
from memory_manager import memory_manager
if subcmd in ("project", "proj", "p"):
text, markup = build_memory_menu(chat_id, view_type="project", page=0)
await update.message.reply_html(text, reply_markup=markup)
return
if subcmd in ("user", "u", "me", "profile", "private"):
text, markup = build_memory_menu(chat_id, view_type="user", page=0)
await update.message.reply_html(text, reply_markup=markup)
return
if subcmd in ("global", "g", "public", "system"):
text, markup = build_memory_menu(chat_id, view_type="global", page=0)
await update.message.reply_html(text, reply_markup=markup)
return
if subcmd in ("private", "p", "my", "user"):
text, markup = build_memory_menu(chat_id, view_type="private", page=0)
await update.message.reply_html(text, reply_markup=markup)
# Add memory manually: /memory add <global|user|project> <key> | <content>
if subcmd in ("add", "set", "save") and len(context.args) > 2:
target_type = context.args[1].lower().strip()
if target_type not in ("global", "user", "project"):
target_type = "user"
rest = " ".join(context.args[1:]).strip()
else:
rest = " ".join(context.args[2:]).strip()
if target_type == "global" and not is_admin_user:
await update.message.reply_html("🚫 <b>خطای دسترسی: فقط مدیر سیستم می‌تواند خاطره عمومی ثبت کند.</b>" if is_fa else "🚫 <b>Access Denied.</b>")
return
if "|" in rest:
key_part, content_part = rest.split("|", 1)
else:
parts = rest.split(None, 1)
key_part = parts[0]
content_part = parts[1] if len(parts) > 1 else ""
key_clean = key_part.strip().lower()
content_clean = content_part.strip()
if not key_clean or not content_clean:
await update.message.reply_html(
"💡 <i>راهنمای افزودن دستی خاطره:</i>\n<code>/memory add [global|user|project] &lt;key&gt; | &lt;محتوا&gt;</code>\n\n<b>مثال:</b>\n<code>/memory add global default_php | همیشه از PHP مدرن استفاده شود</code>"
if is_fa
else "💡 <i>Usage:</i> <code>/memory add [global|user|project] &lt;key&gt; | &lt;content&gt;</code>"
)
return
p_name = curr_proj.name if (curr_proj and target_type == "project") else "default"
item, is_created = memory_manager.save_or_update(
type_=target_type,
key=key_clean,
content=content_clean,
user_id=chat_id,
project_name=p_name if target_type == "project" else None,
category="rule" if target_type == "global" else "preference",
importance=5 if target_type == "global" else 3,
created_by=chat_id,
)
msg = f"✅ <b>خاطره با موفقیت ثبت شد:</b>\n• 🏷️ بخش: {item.type}\n• 🔑 کلید: <code>{escape_html(item.key)}</code>\n• 📝 محتوا: {escape_html(item.content)}" if is_fa else f"✅ <b>Memory saved:</b> [{item.type}] {item.key}"
text, markup = build_memory_menu(chat_id, view_type=target_type, page=0)
await update.message.reply_html(f"{msg}\n\n{text}", reply_markup=markup)
return
if subcmd in ("clear", "clean", "purge"):
target_type = "global" if len(context.args) > 1 and context.args[1].lower() in ("global", "g") else "private"
target_type = context.args[1].lower() if len(context.args) > 1 else "project"
if target_type not in ("global", "user", "project"):
target_type = "project" if curr_proj else "user"
p_name = curr_proj.name if curr_proj else "default"
try:
cnt = memory_manager.clear_memories(type_=target_type, user_id=chat_id, is_admin=is_admin_user)
type_label = "عمومی" if target_type == "global" else "خصوصی"
cnt = memory_manager.clear_memories(
type_=target_type, user_id=chat_id, project_name=p_name, is_admin=is_admin_user
)
type_label = f"پروژه {p_name}" if target_type == "project" else ("عمومی" if target_type == "global" else "شخصی کاربر")
msg = f"🧹 <b>تعداد {cnt} خاطره {type_label} با موفقیت پاک‌سازی شد.</b>" if is_fa else f"🧹 <b>{cnt} {target_type} memories cleared.</b>"
except PermissionError:
msg = "🚫 <b>خطای دسترسی: فقط ادمین مجاز به پاک‌سازی خاطرات عمومی است.</b>" if is_fa else "🚫 <b>Access denied.</b>"
await update.message.reply_html(msg)
return
text, markup = build_memory_menu(chat_id, view_type="private", page=0)
text, markup = build_memory_menu(chat_id, view_type="project" if curr_proj else "user", page=0)
await update.message.reply_html(text, reply_markup=markup)
@@ -4078,6 +4165,7 @@ async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
session = session_manager.get_or_create(chat_id)
curr_proj = session_manager.get_current_project(chat_id)
is_fa = (session.language or "").lower() in ("fa", "farsi", "persian", "🇮🇷 persian / farsi (فارسی)")
is_admin_user = settings.is_admin(chat_id) or settings.is_admin(user_id)
data = query.data
if data == "btn_dashboard":
@@ -5190,8 +5278,15 @@ async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
elif data.startswith("mem_clear_do:"):
v_type = data.split(":")[1]
from memory_manager import memory_manager
curr_p = session_manager.get_current_project(chat_id)
p_name = curr_p.name if curr_p else "default"
try:
cnt = memory_manager.clear_memories(type_=v_type, user_id=chat_id, is_admin=is_admin_user)
cnt = memory_manager.clear_memories(
type_=v_type,
user_id=chat_id,
project_name=p_name if v_type == "project" else None,
is_admin=is_admin_user,
)
await query.answer(f"🧹 تعداد {cnt} خاطره پاک شد." if is_fa else f"🧹 Cleared {cnt} memories.")
except PermissionError:
await query.answer("🚫 دسترسی غیرمجاز!" if is_fa else "🚫 Permission denied!", show_alert=True)
+76 -29
View File
@@ -1087,11 +1087,13 @@ async def execute_action(
elif act in ("SAVE_MEMORY", "STORE_MEMORY", "SET_MEMORY", "ADD_MEMORY"):
from memory_manager import memory_manager
mem_type = (attrs.get("type") or "private").lower().strip()
raw_type = (attrs.get("type") or "user").lower().strip()
key = attrs.get("key") or attrs.get("slug") or attrs.get("name")
content = attrs.get("content") or attrs.get("text") or attrs.get("value") or attrs.get("_default")
category = (attrs.get("category") or "general").lower().strip()
importance_raw = attrs.get("importance") or "1"
req_project = attrs.get("project") or attrs.get("proj") or current_project_name
try:
importance = int(importance_raw)
except Exception:
@@ -1106,10 +1108,18 @@ async def execute_action(
created_task,
)
# Security check: Non-admins cannot create global memories
if mem_type == "global" and not is_admin:
logger.warning("Non-admin user %s attempted to create global memory %s; downgraded to private.", chat_id, key)
mem_type = "private"
# Map types: 'project' -> project tier, 'user'/'private' -> user tier, 'global' -> rejected/downgraded for AI
if raw_type in ("project", "proj"):
mem_type = "project"
proj_name = req_project
elif raw_type == "global":
# Global memories are strictly bot-managed by Admin. AI cannot auto-save global memories.
logger.info("AI attempted global memory save for user %s; safely converting to user memory.", chat_id)
mem_type = "user"
proj_name = None
else:
mem_type = "user"
proj_name = None
try:
item, is_created = memory_manager.save_or_update(
@@ -1117,21 +1127,28 @@ async def execute_action(
key=key,
content=content,
user_id=chat_id,
project_name=proj_name,
category=category,
importance=importance,
created_by=chat_id,
)
type_label = "🌐 عمومی (سیستم)" if item.is_global else "👤 خصوصی (شما)"
if item.is_project:
type_label = f"📁 پروژه اختصاصی (<code>{escape_html(proj_name or '')}</code>)"
elif item.is_user:
type_label = "👤 کاربر (سراسری برای شما)"
else:
type_label = "🌐 عمومی (سیستم)"
badge = (
f"\n\n🧠 <b>خاطره هوش مصنوعی ثبت شد:</b>\n"
f"• 🏷️ <b>نوع:</b> {type_label}\n"
f"• 🏷️ <b>بخش:</b> {type_label}\n"
f"• 🔑 <b>کلید:</b> <code>{escape_html(item.key)}</code>\n"
f"• 📂 <b>دسته:</b> <code>{escape_html(item.category)}</code>\n"
f"• 📝 <b>محتوا:</b> {escape_html(item.content)}"
if is_fa
else
f"\n\n🧠 <b>AI Memory Saved:</b>\n"
f"• 🏷️ <b>Type:</b> {'Global' if item.is_global else 'Private'}\n"
f"• 🏷️ <b>Scope:</b> {'Project (' + str(proj_name) + ')' if item.is_project else 'User'} \n"
f"• 🔑 <b>Key:</b> <code>{escape_html(item.key)}</code>\n"
f"• 📂 <b>Category:</b> <code>{escape_html(item.category)}</code>\n"
f"• 📝 <b>Content:</b> {escape_html(item.content)}"
@@ -1146,11 +1163,12 @@ async def execute_action(
elif act in ("UPDATE_MEMORY", "EDIT_MEMORY"):
from memory_manager import memory_manager
mem_type = (attrs.get("type") or "private").lower().strip()
raw_type = (attrs.get("type") or "user").lower().strip()
key = attrs.get("key") or attrs.get("slug")
content = attrs.get("content") or attrs.get("text") or attrs.get("_default")
category = attrs.get("category")
importance_raw = attrs.get("importance")
req_project = attrs.get("project") or attrs.get("proj") or current_project_name
if not key or not content:
return (
@@ -1161,11 +1179,15 @@ async def execute_action(
created_task,
)
if mem_type == "global" and not is_admin:
mem_type = "private"
if raw_type in ("project", "proj"):
mem_type = "project"
proj_name = req_project
else:
mem_type = "user"
proj_name = None
try:
existing = memory_manager.get_by_key(type_=mem_type, key=key, user_id=chat_id)
existing = memory_manager.get_by_key(type_=mem_type, key=key, user_id=chat_id, project_name=proj_name)
cat = category or (existing.category if existing else "general")
imp = int(importance_raw) if importance_raw and importance_raw.isdigit() else (existing.importance if existing else 1)
item, is_created = memory_manager.save_or_update(
@@ -1173,20 +1195,21 @@ async def execute_action(
key=key,
content=content,
user_id=chat_id,
project_name=proj_name,
category=cat,
importance=imp,
created_by=chat_id,
)
type_label = "🌐 عمومی (سیستم)" if item.is_global else "👤 خصوصی (شما)"
type_label = f"📁 پروژه (<code>{escape_html(proj_name or '')}</code>)" if item.is_project else "👤 کاربر (سراسری)"
badge = (
f"\n\n🧠 <b>خاطره هوش مصنوعی به‌روزرسانی شد:</b>\n"
f"• 🏷️ <b>نوع:</b> {type_label}\n"
f"• 🏷️ <b>بخش:</b> {type_label}\n"
f"• 🔑 <b>کلید:</b> <code>{escape_html(item.key)}</code>\n"
f"• 📝 <b>محتوای جدید:</b> {escape_html(item.content)}"
if is_fa
else
f"\n\n🧠 <b>AI Memory Updated:</b>\n"
f"• 🏷️ <b>Type:</b> {'Global' if item.is_global else 'Private'}\n"
f"• 🏷️ <b>Scope:</b> {type_label}\n"
f"• 🔑 <b>Key:</b> <code>{escape_html(item.key)}</code>\n"
f"• 📝 <b>New Content:</b> {escape_html(item.content)}"
)
@@ -1202,13 +1225,19 @@ async def execute_action(
mem_id_raw = attrs.get("id")
key = attrs.get("key") or attrs.get("slug") or attrs.get("_default")
mem_type = (attrs.get("type") or "private").lower().strip()
raw_type = (attrs.get("type") or "user").lower().strip()
req_project = attrs.get("project") or attrs.get("proj") or current_project_name
mem_type = "project" if raw_type in ("project", "proj") else "user"
proj_name = req_project if mem_type == "project" else None
try:
if mem_id_raw and mem_id_raw.isdigit():
ok = memory_manager.delete_by_id(int(mem_id_raw), user_id=chat_id, is_admin=is_admin)
elif key:
ok = memory_manager.delete_by_key(type_=mem_type, key=key, user_id=chat_id, is_admin=is_admin)
ok = memory_manager.delete_by_key(
type_=mem_type, key=key, user_id=chat_id, project_name=proj_name, is_admin=is_admin
)
else:
return "\n⚠️ لطفاً شناسه یا کلید خاطره جهت حذف را مشخص کنید." if is_fa else "\n⚠️ Please specify memory id or key to delete.", side_effects, created_task
@@ -1217,7 +1246,7 @@ async def execute_action(
else:
badge = f"\n⚠️ خاطره‌ای با این مشخصات یافت نشد." if is_fa else f"\n⚠️ Memory not found."
except PermissionError:
badge = "\n🚫 <i>خطای دسترسی: ویرایش یا حذف خاطرات عمومی تنها توسط مدیر سیستم امکان‌پذیر است.</i>" if is_fa else "\n🚫 <i>Access Denied: Only administrators can modify global memories.</i>"
badge = "\n🚫 <i>خطای دسترسی: فقط مالک یا مدیر می‌تواند این خاطره را حذف کند.</i>" if is_fa else "\n🚫 <i>Access Denied.</i>"
except Exception as e:
badge = f"\n⚠️ خطا در حذف خاطره: {escape_html(str(e))}"
return badge, side_effects, created_task
@@ -1228,18 +1257,36 @@ async def execute_action(
elif act in ("CLEAR_MEMORY", "PURGE_MEMORY", "RESET_MEMORY"):
from memory_manager import memory_manager
mem_type = (attrs.get("type") or "private").lower().strip()
raw_type = (attrs.get("type") or "user").lower().strip()
req_project = attrs.get("project") or attrs.get("proj") or current_project_name
if raw_type in ("project", "proj"):
mem_type = "project"
proj_name = req_project
type_label = f"پروژه {proj_name}"
elif raw_type == "global":
if not is_admin:
return "\n🚫 <i>خطای دسترسی: پاک‌سازی خاطرات عمومی تنها توسط مدیر امکان‌پذیر است.</i>" if is_fa else "\n🚫 <i>Access Denied: Only admins can clear global memories.</i>", side_effects, created_task
mem_type = "global"
proj_name = None
type_label = "عمومی سیستم"
else:
mem_type = "user"
proj_name = None
type_label = "شخصی کاربر"
try:
count = memory_manager.clear_memories(type_=mem_type, user_id=chat_id, is_admin=is_admin)
type_label = "عمومی" if mem_type == "global" else "خصوصی"
count = memory_manager.clear_memories(
type_=mem_type, user_id=chat_id, project_name=proj_name, is_admin=is_admin
)
badge = (
f"\n\n🧹 <b>حافظه {type_label} پاک‌سازی شد ({count} مورد حذف گردید).</b>"
if is_fa
else
f"\n\n🧹 <b>{mem_type.capitalize()} memory cleared ({count} items purged).</b>"
f"\n\n🧹 <b>{type_label} memory cleared ({count} items purged).</b>"
)
except PermissionError:
badge = "\n🚫 <i>خطای دسترسی: پاک‌سازی خاطرات عمومی تنها توسط مدیر امکان‌پذیر است.</i>" if is_fa else "\n🚫 <i>Access Denied: Only admins can clear global memories.</i>"
badge = "\n🚫 <i>خطای دسترسی: عملیات مجاز نیست.</i>" if is_fa else "\n🚫 <i>Access Denied.</i>"
except Exception as e:
badge = f"\n⚠️ خطا در پاک‌سازی حافظه: {escape_html(str(e))}"
return badge, side_effects, created_task
@@ -1392,11 +1439,11 @@ def get_ai_bot_actions_instruction(lang: str = "fa") -> str:
" [[GIT_INIT: project=\"<project_optional>\"]]\n\n"
"17. Restart Telegram Bot Service:\n"
" [[RESTART_BOT]]\n\n"
"18. Long-Term AI Memory Management:\n"
" [[SAVE_MEMORY: type=\"private|global\", key=\"<unique_slug>\", category=\"<category>\", content=\"<concise_summary>\"]]\n"
" (Saves durable facts/preferences. type defaults to 'private'. Only admin can create 'global').\n"
" [[UPDATE_MEMORY: key=\"<slug>\", content=\"<new_content>\"]]\n"
" [[DELETE_MEMORY: key=\"<slug>\"]]\n"
" [[CLEAR_MEMORY: type=\"private|global\"]]\n\n"
"18. Long-Term AI Memory Management (3-Tier: User & Project):\n"
" [[SAVE_MEMORY: type=\"user|project\", key=\"<unique_slug>\", category=\"<category>\", content=\"<concise_summary>\"]]\n"
" (Use type='user' for personal user profile/habits, or type='project' for current project architecture/decisions. Global memory is read-only for AI).\n"
" [[UPDATE_MEMORY: type=\"user|project\", key=\"<slug>\", content=\"<new_content>\"]]\n"
" [[DELETE_MEMORY: type=\"user|project\", key=\"<slug>\"]]\n"
" [[CLEAR_MEMORY: type=\"user|project\"]]\n\n"
"Always explain to the user in a friendly and professional tone what actions have been performed."
)
Binary file not shown.
+256 -90
View File
@@ -17,11 +17,12 @@ DB_PATH = DATA_DIR / "memory.db"
@dataclass
class MemoryItem:
id: int
type: str # 'global' or 'private'
user_id: Optional[int] # None for global, chat_id for private
key: str
category: str # 'rule', 'preference', 'tech_stack', 'fact', 'workflow', 'general'
content: str
type: str # 'global', 'user', 'project'
user_id: Optional[int] = None # None for global, chat_id for user/project
project_name: Optional[str] = None # None for global/user, project_name for project
key: str = ""
category: str = "general" # 'rule', 'preference', 'tech_stack', 'fact', 'workflow', 'general'
content: str = ""
importance: int = 1
created_by: Optional[int] = None
created_at: float = field(default_factory=time.time)
@@ -35,8 +36,12 @@ class MemoryItem:
return self.type == "global"
@property
def is_private(self) -> bool:
return self.type == "private"
def is_user(self) -> bool:
return self.type == "user"
@property
def is_project(self) -> bool:
return self.type == "project"
class MemoryManager:
@@ -53,41 +58,81 @@ class MemoryManager:
return conn
def _init_db(self):
"""Initializes tables and indexes if they do not exist."""
"""Initializes tables and indexes, migrating from old schema if needed."""
try:
with self._get_connection() as conn:
conn.execute("""
CREATE TABLE IF NOT EXISTS memories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL CHECK(type IN ('global', 'private')),
user_id INTEGER,
key TEXT NOT NULL,
category TEXT NOT NULL DEFAULT 'general',
content TEXT NOT NULL,
importance INTEGER NOT NULL DEFAULT 1,
created_by INTEGER,
created_at REAL NOT NULL,
updated_at REAL NOT NULL
);
""")
# Partial unique indexes to properly handle NULL user_id for global memories
# Check if old memories table exists and needs migration
cur = conn.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='memories';")
table_exists = cur.fetchone() is not None
if table_exists:
# Check columns in existing table
cur = conn.execute("PRAGMA table_info(memories);")
cols = [row["name"] for row in cur.fetchall()]
if "project_name" not in cols:
logger.info("Migrating memories table to support 3-tier hierarchy (adding project_name)...")
# Migrate table
conn.execute("""
CREATE TABLE memories_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL CHECK(type IN ('global', 'user', 'project', 'private')),
user_id INTEGER,
project_name TEXT,
key TEXT NOT NULL,
category TEXT NOT NULL DEFAULT 'general',
content TEXT NOT NULL,
importance INTEGER NOT NULL DEFAULT 1,
created_by INTEGER,
created_at REAL NOT NULL,
updated_at REAL NOT NULL
);
""")
conn.execute("""
INSERT INTO memories_new (id, type, user_id, project_name, key, category, content, importance, created_by, created_at, updated_at)
SELECT id, CASE WHEN type='private' THEN 'user' ELSE type END, user_id, NULL, key, category, content, importance, created_by, created_at, updated_at
FROM memories;
""")
conn.execute("DROP TABLE memories;")
conn.execute("ALTER TABLE memories_new RENAME TO memories;")
else:
conn.execute("""
CREATE TABLE memories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL CHECK(type IN ('global', 'user', 'project', 'private')),
user_id INTEGER,
project_name TEXT,
key TEXT NOT NULL,
category TEXT NOT NULL DEFAULT 'general',
content TEXT NOT NULL,
importance INTEGER NOT NULL DEFAULT 1,
created_by INTEGER,
created_at REAL NOT NULL,
updated_at REAL NOT NULL
);
""")
# Create partial unique indexes for each tier
conn.execute("""
CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_global_key
ON memories(type, key) WHERE type = 'global';
""")
conn.execute("""
CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_private_key
ON memories(type, user_id, key) WHERE type = 'private';
CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_user_key
ON memories(type, user_id, key) WHERE type IN ('user', 'private');
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_memories_user_type
ON memories(type, user_id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_project_key
ON memories(type, user_id, project_name, key) WHERE type = 'project';
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_memories_lookup
ON memories(type, user_id, project_name);
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_memories_category
ON memories(category);
""")
logger.info("Memory database initialized successfully at %s", self.db_path)
logger.info("3-Tier Memory database initialized successfully at %s", self.db_path)
except Exception as e:
logger.error("Failed to initialize memory database: %s", e)
raise
@@ -98,22 +143,32 @@ class MemoryManager:
key: str,
content: str,
user_id: Optional[int] = None,
project_name: Optional[str] = None,
category: str = "general",
importance: int = 1,
created_by: Optional[int] = None,
) -> Tuple[MemoryItem, bool]:
"""
Saves or updates a memory.
Saves or updates a memory in one of the 3 tiers (global, user, project).
Returns: (MemoryItem, is_created: bool)
"""
type_ = type_.lower().strip()
if type_ not in ("global", "private"):
raise ValueError("Memory type must be 'global' or 'private'")
if type_ == "private":
type_ = "user"
if type_ not in ("global", "user", "project"):
raise ValueError("Memory type must be 'global', 'user', or 'project'")
if type_ == "global":
user_id = None
elif user_id is None:
raise ValueError("Private memory requires a valid user_id")
project_name = None
elif type_ == "user":
if user_id is None:
raise ValueError("User memory requires a valid user_id")
project_name = None
elif type_ == "project":
if user_id is None or not project_name:
raise ValueError("Project memory requires both user_id and project_name")
project_name = project_name.strip()
key = key.strip().lower()
if not key:
@@ -128,17 +183,22 @@ class MemoryManager:
now = time.time()
with self._get_connection() as conn:
# Check if exists
# Check if existing item exists
if type_ == "global":
cur = conn.execute(
"SELECT id, created_at, created_by FROM memories WHERE type = 'global' AND key = ?",
(key,),
)
else:
elif type_ == "user":
cur = conn.execute(
"SELECT id, created_at, created_by FROM memories WHERE type = 'private' AND user_id = ? AND key = ?",
"SELECT id, created_at, created_by FROM memories WHERE type IN ('user', 'private') AND user_id = ? AND key = ?",
(user_id, key),
)
else: # project
cur = conn.execute(
"SELECT id, created_at, created_by FROM memories WHERE type = 'project' AND user_id = ? AND project_name = ? AND key = ?",
(user_id, project_name, key),
)
row = cur.fetchone()
if row:
@@ -148,10 +208,10 @@ class MemoryManager:
conn.execute(
"""
UPDATE memories
SET category = ?, content = ?, importance = ?, updated_at = ?
SET type = ?, category = ?, content = ?, importance = ?, updated_at = ?
WHERE id = ?
""",
(category, content, importance, now, mem_id),
(type_, category, content, importance, now, mem_id),
)
is_created = False
created_at = orig_created_at
@@ -159,10 +219,10 @@ class MemoryManager:
else:
cur = conn.execute(
"""
INSERT INTO memories (type, user_id, key, category, content, importance, created_by, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
INSERT INTO memories (type, user_id, project_name, key, category, content, importance, created_by, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(type_, user_id, key, category, content, importance, created_by, now, now),
(type_, user_id, project_name, key, category, content, importance, created_by, now, now),
)
mem_id = cur.lastrowid
is_created = True
@@ -173,6 +233,7 @@ class MemoryManager:
id=mem_id,
type=type_,
user_id=user_id,
project_name=project_name,
key=key,
category=category,
content=content,
@@ -182,12 +243,13 @@ class MemoryManager:
updated_at=now,
)
logger.info(
"%s memory [id=%d, key=%s, type=%s, user=%s]",
"%s memory [id=%d, type=%s, key=%s, user=%s, proj=%s]",
"Created" if is_created else "Updated",
mem_id,
key,
type_,
key,
user_id,
project_name,
)
return item, is_created
@@ -199,17 +261,26 @@ class MemoryManager:
return MemoryItem(**dict(row))
return None
def get_by_key(self, type_: str, key: str, user_id: Optional[int] = None) -> Optional[MemoryItem]:
def get_by_key(
self, type_: str, key: str, user_id: Optional[int] = None, project_name: Optional[str] = None
) -> Optional[MemoryItem]:
type_ = type_.lower().strip()
if type_ == "private":
type_ = "user"
key = key.lower().strip()
with self._get_connection() as conn:
if type_ == "global":
cur = conn.execute("SELECT * FROM memories WHERE type = 'global' AND key = ?", (key,))
else:
elif type_ == "user":
cur = conn.execute(
"SELECT * FROM memories WHERE type = 'private' AND user_id = ? AND key = ?",
"SELECT * FROM memories WHERE type IN ('user', 'private') AND user_id = ? AND key = ?",
(user_id, key),
)
else: # project
cur = conn.execute(
"SELECT * FROM memories WHERE type = 'project' AND user_id = ? AND project_name = ? AND key = ?",
(user_id, project_name, key),
)
row = cur.fetchone()
if row:
return MemoryItem(**dict(row))
@@ -235,16 +306,32 @@ class MemoryManager:
with self._get_connection() as conn:
if category:
cur = conn.execute(
"SELECT * FROM memories WHERE type = 'private' AND user_id = ? AND category = ? ORDER BY importance DESC, updated_at DESC LIMIT ?",
"SELECT * FROM memories WHERE type IN ('user', 'private') AND user_id = ? AND category = ? ORDER BY importance DESC, updated_at DESC LIMIT ?",
(user_id, category.lower().strip(), limit),
)
else:
cur = conn.execute(
"SELECT * FROM memories WHERE type = 'private' AND user_id = ? ORDER BY importance DESC, updated_at DESC LIMIT ?",
"SELECT * FROM memories WHERE type IN ('user', 'private') AND user_id = ? ORDER BY importance DESC, updated_at DESC LIMIT ?",
(user_id, limit),
)
return [MemoryItem(**dict(r)) for r in cur.fetchall()]
def get_project_memories(
self, user_id: int, project_name: str, category: Optional[str] = None, limit: int = 100
) -> List[MemoryItem]:
with self._get_connection() as conn:
if category:
cur = conn.execute(
"SELECT * FROM memories WHERE type = 'project' AND user_id = ? AND project_name = ? AND category = ? ORDER BY importance DESC, updated_at DESC LIMIT ?",
(user_id, project_name, category.lower().strip(), limit),
)
else:
cur = conn.execute(
"SELECT * FROM memories WHERE type = 'project' AND user_id = ? AND project_name = ? ORDER BY importance DESC, updated_at DESC LIMIT ?",
(user_id, project_name, limit),
)
return [MemoryItem(**dict(r)) for r in cur.fetchall()]
def delete_by_id(self, memory_id: int, user_id: Optional[int] = None, is_admin: bool = False) -> bool:
with self._get_connection() as conn:
cur = conn.execute("SELECT * FROM memories WHERE id = ?", (memory_id,))
@@ -254,52 +341,86 @@ class MemoryManager:
mem = MemoryItem(**dict(row))
if mem.type == "global" and not is_admin:
raise PermissionError("Only administrators can delete global memories")
if mem.type == "private" and mem.user_id != user_id and not is_admin:
raise PermissionError("Cannot delete another user's private memory")
if mem.type in ("user", "project", "private") and mem.user_id != user_id and not is_admin:
raise PermissionError("Cannot delete another user's memory")
conn.execute("DELETE FROM memories WHERE id = ?", (memory_id,))
logger.info("Deleted memory [id=%d, key=%s, type=%s]", memory_id, mem.key, mem.type)
return True
def delete_by_key(
self, type_: str, key: str, user_id: Optional[int] = None, is_admin: bool = False
self,
type_: str,
key: str,
user_id: Optional[int] = None,
project_name: Optional[str] = None,
is_admin: bool = False,
) -> bool:
type_ = type_.lower().strip()
if type_ == "private":
type_ = "user"
key = key.lower().strip()
with self._get_connection() as conn:
if type_ == "global":
if not is_admin:
raise PermissionError("Only administrators can delete global memories")
cur = conn.execute("DELETE FROM memories WHERE type = 'global' AND key = ?", (key,))
else:
elif type_ == "user":
if user_id is None:
return False
cur = conn.execute(
"DELETE FROM memories WHERE type = 'private' AND user_id = ? AND key = ?",
"DELETE FROM memories WHERE type IN ('user', 'private') AND user_id = ? AND key = ?",
(user_id, key),
)
else: # project
if user_id is None or not project_name:
return False
cur = conn.execute(
"DELETE FROM memories WHERE type = 'project' AND user_id = ? AND project_name = ? AND key = ?",
(user_id, project_name, key),
)
deleted = cur.rowcount > 0
if deleted:
logger.info("Deleted memory by key [key=%s, type=%s, user=%s]", key, type_, user_id)
logger.info("Deleted memory by key [key=%s, type=%s, user=%s, proj=%s]", key, type_, user_id, project_name)
return deleted
def clear_memories(self, type_: str, user_id: Optional[int] = None, is_admin: bool = False) -> int:
def clear_memories(
self,
type_: str,
user_id: Optional[int] = None,
project_name: Optional[str] = None,
is_admin: bool = False,
) -> int:
type_ = type_.lower().strip()
if type_ == "private":
type_ = "user"
with self._get_connection() as conn:
if type_ == "global":
if not is_admin:
raise PermissionError("Only administrators can clear global memories")
cur = conn.execute("DELETE FROM memories WHERE type = 'global'")
else:
elif type_ == "user":
if user_id is None:
return 0
cur = conn.execute("DELETE FROM memories WHERE type = 'private' AND user_id = ?", (user_id,))
cur = conn.execute("DELETE FROM memories WHERE type IN ('user', 'private') AND user_id = ?", (user_id,))
else: # project
if user_id is None or not project_name:
return 0
cur = conn.execute(
"DELETE FROM memories WHERE type = 'project' AND user_id = ? AND project_name = ?",
(user_id, project_name),
)
count = cur.rowcount
logger.info("Cleared %d %s memories for user %s", count, type_, user_id)
logger.info("Cleared %d %s memories for user %s (proj: %s)", count, type_, user_id, project_name)
return count
def search_memories(
self, query: str, user_id: Optional[int] = None, type_: Optional[str] = None, limit: int = 50
self,
query: str,
user_id: Optional[int] = None,
project_name: Optional[str] = None,
type_: Optional[str] = None,
limit: int = 50,
) -> List[MemoryItem]:
q = f"%{query.strip().lower()}%"
with self._get_connection() as conn:
@@ -312,24 +433,33 @@ class MemoryManager:
""",
(q, q, q, limit),
)
elif type_ == "private" and user_id is not None:
elif type_ == "user" and user_id is not None:
cur = conn.execute(
"""
SELECT * FROM memories
WHERE type = 'private' AND user_id = ? AND (key LIKE ? OR content LIKE ? OR category LIKE ?)
WHERE type IN ('user', 'private') AND user_id = ? AND (key LIKE ? OR content LIKE ? OR category LIKE ?)
ORDER BY importance DESC, updated_at DESC LIMIT ?
""",
(user_id, q, q, q, limit),
)
elif type_ == "project" and user_id is not None and project_name:
cur = conn.execute(
"""
SELECT * FROM memories
WHERE type = 'project' AND user_id = ? AND project_name = ? AND (key LIKE ? OR content LIKE ? OR category LIKE ?)
ORDER BY importance DESC, updated_at DESC LIMIT ?
""",
(user_id, project_name, q, q, q, limit),
)
elif user_id is not None:
cur = conn.execute(
"""
SELECT * FROM memories
WHERE (type = 'global' OR (type = 'private' AND user_id = ?))
WHERE (type = 'global' OR (type IN ('user', 'private') AND user_id = ?) OR (type = 'project' AND user_id = ? AND project_name = ?))
AND (key LIKE ? OR content LIKE ? OR category LIKE ?)
ORDER BY type DESC, importance DESC, updated_at DESC LIMIT ?
ORDER BY importance DESC, updated_at DESC LIMIT ?
""",
(user_id, q, q, q, limit),
(user_id, user_id, project_name or "", q, q, q, limit),
)
else:
cur = conn.execute(
@@ -342,66 +472,102 @@ class MemoryManager:
)
return [MemoryItem(**dict(r)) for r in cur.fetchall()]
def format_memories_for_prompt(self, user_id: Optional[int] = None) -> Optional[str]:
def format_memories_for_prompt(
self, user_id: Optional[int] = None, project_name: Optional[str] = None
) -> Optional[str]:
"""
Formats all applicable memories (Global + User's Private) into a concise, token-efficient
system instruction block for the AI model.
Returns None if no memories are found.
Formats all 3 tiers of memories into a structured, concise instruction block for AI:
1. 🌐 Global System Rules
2. 👤 User Personal Preferences & Profile
3. 📁 Active Project Specific Knowledge
"""
globals_list = self.get_global_memories(limit=50)
privates_list = self.get_user_memories(user_id=user_id, limit=50) if user_id else []
users_list = self.get_user_memories(user_id=user_id, limit=50) if user_id else []
projects_list = (
self.get_project_memories(user_id=user_id, project_name=project_name, limit=50)
if user_id and project_name
else []
)
if not globals_list and not privates_list:
if not globals_list and not users_list and not projects_list:
return None
lines = [
"[SYSTEM INSTRUCTION: AI PERSISTENT MEMORY & CONTINUOUS LEARNING]",
"You have access to persistent long-term memory containing verified system rules and user context.",
"Use this knowledge to maintain continuity and customize all your responses accordingly.\n"
"[SYSTEM INSTRUCTION: AI PERSISTENT HIERARCHICAL MEMORY & CONTINUOUS LEARNING]",
"You have access to persistent 3-tier long-term memory containing verified system rules, project context, and user profile.",
"Use this knowledge to maintain continuity and customize all your responses accordingly.\n",
"⚖️ STRICT PRECEDENCE HIERARCHY (سلسله‌مراتب قطعی اولویت و حل تعارض):",
"1. 🥇 Tier 1 (Highest Authority): Global System Rules ALWAYS strictly supersede all Project and User instructions.",
"2. 🥈 Tier 2 (Intermediate Authority): Active Project Knowledge & Rules ALWAYS supersede general User preferences inside this project.",
"3. 🥉 Tier 3 (Baseline Authority): User Personal Preferences apply across projects as defaults when not overridden by Project or Global rules.\n"
]
if globals_list:
lines.append("🌐 GLOBAL SYSTEM MEMORY (قوانین و اطلاعات عمومی سیستم):")
lines.append("🌐 1. GLOBAL SYSTEM RULES & KNOWLEDGE (قوانین عمومی سیستم - بالاترین اولویت):")
for m in globals_list:
cat = f"[{m.category}]" if m.category and m.category != "general" else ""
lines.append(f"• [key: {m.key}] {cat} {m.content}")
lines.append("")
if privates_list:
lines.append(f"👤 USER PRIVATE MEMORY (خاطرات و ترجیحات اختصاصی این کاربر):")
for m in privates_list:
if projects_list:
lines.append(f"📁 2. ACTIVE PROJECT KNOWLEDGE [پروژه: {project_name}] (دانش و تصمیمات اختصاصی این پروژه - اولویت دوم):")
for m in projects_list:
cat = f"[{m.category}]" if m.category and m.category != "general" else ""
lines.append(f"• [key: {m.key}] {cat} {m.content}")
lines.append("")
if users_list:
lines.append("👤 3. USER PERSONAL PROFILE & PREFERENCES (ترجیحات و مشخصات شخصی کاربر - اولویت پایه):")
for m in users_list:
cat = f"[{m.category}]" if m.category and m.category != "general" else ""
lines.append(f"• [key: {m.key}] {cat} {m.content}")
lines.append("")
lines.extend([
"🧠 AUTONOMOUS MEMORY MANAGEMENT RULES:",
"1. When the user shares important facts, personal preferences, coding conventions, or rules that should persist, output the action tag:",
' [[SAVE_MEMORY: type="private|global", key="<short_slug>", category="preference|rule|tech_stack|fact", content="<concise memory summary>"]]',
"2. Note: Non-admin users can ONLY save 'private' memories. Only system administrators can set 'global' memories.",
"3. If a past memory is superseded or changed, update it with the same key or use [[UPDATE_MEMORY: key=\"<slug>\", content=\"<new>\"]].",
"4. If the user explicitly asks to forget something, use [[DELETE_MEMORY: key=\"<slug>\"]].",
"1. When user shares durable facts, choose the appropriate tier:",
' • User-level (developer habits, preferred tools, personal info): type="user"',
' • Project-level (architecture, database schema, libraries, API designs, fixed bugs in this project): type="project"',
' Action Tag: [[SAVE_MEMORY: type="user|project", key="<short_slug>", category="preference|rule|tech_stack|fact", content="<concise summary>"]]',
"2. Note: Global memory is read-only for AI and managed exclusively by system administrators via the Telegram Bot UI.",
"3. If past memory is superseded, update it: [[UPDATE_MEMORY: type=\"user|project\", key=\"<slug>\", content=\"<new_content>\"]].",
"4. If user asks to forget something: [[DELETE_MEMORY: type=\"user|project\", key=\"<slug>\"]].",
"5. Do NOT store temporary or trivial small-talk. Only store enduring and valuable knowledge."
])
return "\n".join(lines)
def get_stats(self) -> Dict[str, Any]:
def get_stats(self, user_id: Optional[int] = None, project_name: Optional[str] = None) -> Dict[str, Any]:
with self._get_connection() as conn:
cur = conn.execute("SELECT COUNT(*) as cnt FROM memories WHERE type = 'global'")
global_count = cur.fetchone()["cnt"]
cur = conn.execute("SELECT COUNT(*) as cnt FROM memories WHERE type = 'private'")
private_count = cur.fetchone()["cnt"]
cur = conn.execute("SELECT COUNT(DISTINCT user_id) as cnt FROM memories WHERE type = 'private'")
cur = conn.execute("SELECT COUNT(*) as cnt FROM memories WHERE type IN ('user', 'private')")
user_count = cur.fetchone()["cnt"]
cur = conn.execute("SELECT COUNT(*) as cnt FROM memories WHERE type = 'project'")
project_count = cur.fetchone()["cnt"]
cur = conn.execute("SELECT COUNT(DISTINCT user_id) as cnt FROM memories WHERE type IN ('user', 'project', 'private')")
users_with_mem = cur.fetchone()["cnt"]
this_user_count = 0
this_project_count = 0
if user_id:
cur = conn.execute("SELECT COUNT(*) as cnt FROM memories WHERE type IN ('user', 'private') AND user_id = ?", (user_id,))
this_user_count = cur.fetchone()["cnt"]
if project_name:
cur = conn.execute("SELECT COUNT(*) as cnt FROM memories WHERE type = 'project' AND user_id = ? AND project_name = ?", (user_id, project_name))
this_project_count = cur.fetchone()["cnt"]
return {
"total_memories": global_count + private_count,
"total_memories": global_count + user_count + project_count,
"global_memories": global_count,
"private_memories": private_count,
"users_with_memory": user_count,
"user_memories": user_count,
"project_memories": project_count,
"users_with_memory": users_with_mem,
"current_user_memories": this_user_count,
"current_project_memories": this_project_count,
}
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB