البوابات والمنصات
كيف يتحدث عقل كاظمه الواحد مع قنوات عديدة: طبقة المُكيّفات، عزل المنصات، مخزن الجلسات، أوامر الشرطة المائلة، ومصفوفة تكافؤ الميزات — كلها موثّقة مقابل
kazma-gateway/.
1. البنية
Section titled “1. البنية”أُعيد هيكلة حزمة البوابة لكاظمه (kazma-gateway/kazma_gateway/) من ملف agent_handler.py واحد إلى حزمة agent_handler/ (التفكيك مُوثَّق في agent_handler/__init__.py:4). الشكل كالتالي:
flowchart LR
subgraph Platform Adapters
TG[TelegramAdapter]
DC[DiscordAdapter]
SL[SlackAdapter]
end
subgraph agent_handler package
ST[store.py<br/>SessionStore + isolation]
GH[graph.py<br/>graph bridge]
CM[commands.py<br/>interactive cmds]
end
SS[(SessionStore<br/>SQLite / in-memory)]
GB[Supervisor Graph]
TG & DC & SL -->|IncomingMessage| GH
GH --> ST
ST <--> SS
GH -->|graph.ainvoke| GB
GB -->|final state| GH
GH -->|OutboundMessage| TG & DC & SL
- المُكيّفات (
adapters/) — واحد لكل منصة؛ جميعها تمدّBaseAdapter(gateway.py:239). يُترجِم كلٌّ منها الأحداث الأصلية للمنصة إلىIncomingMessageالخاصة بكاظمه ويُعيد تصييرOutboundMessage. - حزمة agent_handler — الجسر بين المُكيّفات ورسم المُشرف.
- مُكيّفات الناقل (
adapters/*_bus.py) — مجموعة منفصلة من المُكيّفات لموافقات HITL الخاصة بالسرب (Telegram/Discord/Slack). راجع الأمن والسلامة.
2. مُكيّفات المنصات
Section titled “2. مُكيّفات المنصات”2.1 Telegram (adapters/telegram.py)
Section titled “2.1 Telegram (adapters/telegram.py)”| الجانب | التفصيل |
|---|---|
| الصنف | TelegramAdapter (telegram.py:70)، name = "telegram". |
| الاستقبال | getUpdates يدوي بالاقتراع الطويل (HTTP)، مع تفاوت 1–3 ثوانٍ؛ موجّه webhook اختياري عبر telegram_webhook_router() مُركَّب عند /api/webhooks/telegram، يحدّد المحوّل العامل عند كل طلب. |
| الإرسال | POST /sendMessage مع parse_mode بصيغة HTML، إعادة محاولة عند 429، وتقسيم الرسائل. |
| مفاتيح السياق | chat_id (int)، user_id (int)، username (str)، message_id (int)، chat_type (str). |
| إضافات | الصوت/STT (telegram_stt.py)، لوحات المفاتيح المضمّنة (telegram_keyboards.py)، التفاعلات (✅/🎯/❌)، مؤشر الكتابة، قائمة أوامر مُسجَّلة عبر setMyCommands، التقاط الصور/المستندات/الفيديو/الصور المتحركة الواردة، الإرسال الصادر عبر sendPhoto/sendDocument/sendVideo/sendAudio. |
| وحدات مساعدة | telegram_callbacks.py، telegram_keyboards.py، telegram_parse.py، telegram_send.py، telegram_stt.py. |
Telegram هو المُكيّف الأكثر اكتمالًا في الميزات.
2.2 Discord (adapters/discord.py)
Section titled “2.2 Discord (adapters/discord.py)”| الجانب | التفصيل |
|---|---|
| الصنف | DiscordAdapter (discord.py:50)، name = "discord". |
| الاستقبال | بوابة WebSocket لديسكورد (wss://gateway.discord.gg/?v=10&encoding=json). |
| الإرسال | REST عبر POST /channels/\{channel_id\}/messages، إعادة محاولة عند 429، تقسيم عند 2000 محرف. |
| مفاتيح السياق | channel_id، guild_id، user_id، message_id، username، guild_name. |
| Markdown | نص عادي (لا يُولَّد Markdown الخاص بـ Discord). |
أوامر الشرطة المائلة: يحجز Discord الأوامر ذات البادئة
/لنظام تفاعلاته الخاص. تستقبلها كاظمه كنص عادي وتُحلِّلها داخليًا.
2.3 Slack (adapters/slack.py)
Section titled “2.3 Slack (adapters/slack.py)”| الجانب | التفصيل |
|---|---|
| الصنف | SlackAdapter (slack.py:47). |
| الاستقبال | وضع Socket (عند وجود xapp- app_token) أو اقتراع احتياطي لـ conversations.history. |
| الإرسال | REST عبر واجهة Slack API POST. |
| مفاتيح السياق | channel_id، user_id، team_id، thread_ts، message_ts، username. |
أوامر الشرطة المائلة: يحظر Slack أوامر الشرطة المائلة الصادرة عن البوت، لذا يُصدَر طلب موافقة HITL بصيغة
hitl approve|deny <id>بدون بادئة/(graph.py:184).
المستندات: كل المنصات التي تصل إلى معالِج الوكيل تدعم /documents (الاسم البديل /docs) لأوامر list/status/read/convert/pdf-info/redact/search/health مقابل خدمة ذكاء المستندات المشتركة. وكل محادثة تعمل بصفتها مشغّل التثبيت، كما تعمل صفحة المستندات في واجهة الويب وأدوات المستندات لدى الوكيل، فالمستند المرفوع في الصفحة يُسرَد ويُقرأ هنا والعكس كذلك (منذ 2026-10-13؛ كانت الدردشة تستخدم هوية agent لا ترى أيًّا من مستندات الصفحة). ومع مستخدمي المنصات (وضع تعدد المستخدمين) ترفض الأوامر: فتحديد أيّ مستخدم تمثّله رسالة الدردشة لم يُبنَ بعد. والمعرّف الذي يملكه مستأجر آخر يُجاب عنه تمامًا كالمعرّف غير المعروف. وقد تُحلِّل مرفقات الدردشة أنواع المستندات تلقائيًا إلى مقتطفات مسيَّجة: قراءة عابرة في العامل المعزول لا تخزّن شيئًا في المنصة، ولا تُنشئ document_id، ولا تذهب أبدًا إلى محلِّل بعيد. انظر أوامر الشرطة المائلة — المستندات وذكاء المستندات.
الجداول: تُعاد كتابة جداول markdown في الإجابة أسطراً قبل أن تذهب إلى تيليجرام أو Discord أو Slack (kazma_gateway/chat_tables.py)، لتُقرأ بسهولة على الهاتف. وتبقى كتل الشيفرة كما هي.
2.4 كل رسالة محسوبة، واختبار يشخّص
Section titled “2.4 كل رسالة محسوبة، واختبار يشخّص”يحتفظ كل محوّل بسجل لما استقبله اتصاله (kazma_gateway/receive_log.py): حالة الاتصال وآخر مشكلة واجهته، وما آلت إليه كل رسالة — سُلّمت إلى كاظمه، أو تُركت لسبب (المرسل ليس من المستخدمين المسموح لهم، أو الرسالة من بوت، أو لا تحمل شيئاً يستطيع كاظمه قراءته، أو الطابور ممتلئ). الرسالة المتروكة لسبب تستطيع إصلاحه، مثل قائمة مستخدمين مسموح لهم فارغة، تُسجَّل WARNING في السجل، مرة كل عشر دقائق على الأكثر لكل سبب؛ ورسالة البوت تُسجَّل DEBUG، والبقية INFO.
الإعدادات ← المزوّدون والموصلات ← موصلات المنصات ← اختبار يسأل المنصة ويجمع جوابها مع ذلك السجل:
| المنصة | ما يفحصه الاختبار |
|---|---|
| Telegram | توكن البوت؛ وwebhook يوقف الاقتراع، والتحديثات المنتظرة؛ وخصوصية المجموعات؛ ومحادثة التسليم (هل ضغط الشخص Start، وهل حظر البوت)؛ ومسار المجموعة؛ وآخر رسالة أرسلها شخص وما آلت إليه؛ والمستخدمون المسموح لهم؛ والاتصال |
| Discord | توكن البوت؛ وصلاحية Message Content؛ والخوادم التي فيها البوت؛ وقناة التسليم؛ وأحدث رسالة كتبها شخص فيها؛ والرسائل المباشرة من كل مستخدم مسموح له إلى البوت، مع رابط يفتح المحادثة؛ والمستخدمون المسموح لهم؛ والاتصال |
| Slack | توكن البوت؛ والتوكن على مستوى التطبيق (Socket Mode)؛ والصلاحيات التي يحملها التوكن؛ وقناة التسليم وهل البوت فيها؛ وأحدث رسالة هناك وفي الرسائل المباشرة لكل مستخدم مسموح له، مع رابط؛ والمستخدمون المسموح لهم؛ والاتصال |
لا يكتب الاختبار شيئاً، إلا أنه على Discord وSlack يفتح قناة الرسائل المباشرة مع كل مستخدم مسموح له، وهذا لا يرسل أي رسالة. وتحفظ البطاقة حقولها قبل أن تختبر، ويفحص الاختبار الاتصال الذي قام على ما حُفظ: فإن كان المحوّل الجاري مبنياً من إعدادات أخرى أُعيد بناؤه أولاً، وانتظر الاختبار حتى عشر ثوانٍ أول محاولة للاتصال الجديد.
اتصال Socket Mode في Slack. يطلب Slack اتصالاً جديداً من حين لآخر: refresh_requested كل بضع ساعات، وwarning قبل نحو عشر ثوانٍ من توقف أحد خوادمه، وlink_disabled حين يُطفأ Socket Mode في إعدادات التطبيق أو يُلغى التوكن الذي استعمله الاتصال على مستوى التطبيق. يسجّل كاظمه كل طلب مع سببه وخادم Slack، ويعيد الاتصال فوراً. أما link_disabled فيُسجَّل تحذيراً (WARNING)، ويعرضه الاختبار مع طريقة إصلاحه. وحين يُفتح الاتصال يذكر Slack عدد الاتصالات المفتوحة للتطبيق، وهو يسلّم كل حدث إلى اتصال واحد منها فقط؛ فإذا اتصل برنامج آخر بتطبيق Slack نفسه (نسخة ثانية من كاظمه، أو بوت اختبار قديم) أخذ بصمت جزءاً من رسائل كاظمه. لكن العدد يشمل أيضاً اتصالاً انتهى دون أن يُغلَق، إلى أن ينتبه Slack، وقد يستغرق ذلك ساعات؛ فأكثر من اتصال واحد ليس دليلاً على برنامج ثانٍ. فإن عدّ Slack أكثر من اتصال واحد سجّل كاظمه تحذيراً (WARNING) يذكر الاحتمالين، وقال فحص الاتصال في الاختبار أيّهما هو بحسب الرسائل التي فحصها: فإن لم تصل إحداها إلى كاظمه فهناك برنامج آخر يأخذ رسائل، وإن وصلت كلها فالعدد قديم. والعدد المأخوذ خلال 15 ثانية من إعادة كاظمه اتصاله قد يشمل اتصاله السابق، فلا يُعتدّ به إلا إذا أظهر اتصالاً غير هذين الاثنين. ويسمح Slack للتطبيق بعشرة اتصالات في وقت واحد، ويرفض اتصالاً زائداً برسالة too_many_websockets، فلا يكون كاظمه متصلاً حتى يفرغ مكان. ويعيد المحاولة كل بضع ثوانٍ؛ ويُسجَّل أول رفض في كل سلسلة تحذيراً (WARNING) ويظهر مشكلةً في الاختبار، ويذكر الاتصال الذي يليها عدد مرات الرفض قبله.
الرسائل التي يرسلها شخص واحد بسرعة كبيرة. بعد gateway.rate_limits (30 رسالة في الدقيقة لكل شخص على كل منصة) يجيب كاظمه «تمهّل»، مرة كل 30 ثانية على الأكثر، ويترك الباقي بلا رد. وتُسجَّل كل رسالة يتركها في سجل المنصة على أنها rate_limited، فيقول السجل والاختبار ما صار إليها. وحتى 2026-10-01 لم تكن هذه الرسائل تترك أي أثر، وكان حدّ Slack رسالة واحدة في الدقيقة.
2.5 الإعداد المحفوظ يصل إلى المحوّل الجاري
Section titled “2.5 الإعداد المحفوظ يصل إلى المحوّل الجاري”تسري إعدادات تطبيق المحادثة حين تُحفظ، أينما حُفظت: بطاقة محوّلات المنصات، أو استعادة الإعدادات، أو التطبيق الطرفي، أو الوكيل. وفي غضون ثانية يفعل كاظمه ما يلي:
- يعيد بناء محوّل تلك المنصة إذا تغيّر توكنها، أو التوكن على مستوى التطبيق (Slack)، أو سر الويب هوك (Telegram)، أو مفتاح التشغيل، ويترك المنصات الأخرى متصلة؛
- ويطبّق قائمة السماح المتغيّرة على المحوّل الجاري في مكانه؛
- ويعيد بناء مُرسِل السرب لمنصة إذا تغيّر توكن البوت أو مفتاح التشغيل أو قناة الوجهة. أما المُرسِل الذي لم يتغيّر فيبقى، ومعه الموافقات التي ينتظرها.
والمنصة المطفأة في الإعدادات تبقى مطفأة عند الإقلاع وعند كل إعادة بناء. أما Refresh Gateway (وkazma gateway refresh) فيعيد بناء كل المحوّلات بالشيفرة نفسها التي يستعملها الإقلاع، حين تريد أن يعيد كل شيء الاتصال. وحتى 2026-10-01 لم يكن التوكن المحفوظ يصل إلى أي محوّل جارٍ قبل Refresh. وكان ذلك الـRefresh يستعمل نسخته الخاصة من شيفرة الإقلاع، فكان يشغّل منصة مطفأة، ويُسقط سر الويب هوك في Telegram، ويترك المحوّلات التي بلا قائمة سماح لا تستقبل أي رسالة. والشيفرة في kazma_gateway/chat_adapters.py، ويخبرها مخزن الإعدادات بكل كتابة.
3. عزل المنصات (الثابت الجوهري)
Section titled “3. عزل المنصات (الثابت الجوهري)”هذا هو العقد الأهم في البوابة على الإطلاق. يجب ألا تحتوي حالة LangGraph على chat_id أو user_id أو message_id. كسر هذا الثابت يُسرّب مُعرّفات المنصة إلى الرسم ويفسد الجلسات.
3.1 المفاتيح المُحرَّمة
Section titled “3.1 المفاتيح المُحرَّمة”_PLATFORM_KEYS (agent_handler/store.py:16-31) هي المجموعة المجمّدة (frozen) الموثوقة:
_PLATFORM_KEYS = frozenset({ # Telegram "chat_id", "user_id", "message_id", "update_id", "chat_type", # Discord / Slack "channel_id", "guild_id", "team_id", "thread_ts", "message_ts",})3.2 كيف يُفرَض العزل
Section titled “3.2 كيف يُفرَض العزل”_build_initial_state(msg, store) (store.py:95):
- يحلّ
thread_idعبر_resolve_thread(store.py:34):thread_idموجود في context_metadata، وإلّا- مُحدَّد بشكل حتمي من مُعرّف المُرسِل (مثلًا
"telegram:12345"→"gw-telegram-12345")، وإلّا UUID4جديد.
- يحفظ سياق المنصة الكامل في SessionStore:
await store.put(thread_id, dict(ctx)). - يبني حالة رسم مُصغَّرة تحتوي كتلة
_gatewayفقط:
state["_gateway"] = { "thread_id": thread_id, "display_name": ctx.get("username") or "unknown", "platform": msg.platform,}- تمريرة دفاعية معمّقة تنتزع أي مفتاح مُسرّب من
_PLATFORM_KEYS(store.py:139-141).
3.3 كيف تعود الردود إلى مسارها
Section titled “3.3 كيف تعود الردود إلى مسارها”بعد اكتمال graph.ainvoke() (agent_handler/graph.py:316)، يُنفِّذ المعالِج ctx = await _store.get(thread_id) لإعادة تملء مُعرّفات المنصة، ثم يوجّهها عبر _build_target_id(platform, ctx) (store.py:146):
- Telegram →
chat_id - Discord/Slack →
channel_id - يُنتج مُعرّفات مسبوقة بالمنصة مثل
"telegram:12345".
الاستمرارية بالتصميم: مدخلات الجلسة لا تُحذَف بعد الرد (
graph.py:312-315) كي يتمكن توجيه الاستعادة بعد الانهيار من إعادة تملء السياق. تُطرَد المدخلات المعلّقة بتكاسل عبر TTL مدته 300 ثانية (graph.py:98،_session_ttl_seconds).
4. SessionStore
Section titled “4. SessionStore”يحتفظ SessionStore بتعيينات مُعرّفات المنصات ↔ thread_id — لا الرسم.
| التنفيذ | الموقع | الاستخدام |
|---|---|---|
SessionStore (مجرّد) | gateway.py:185 | get، put، delete، evict_older_than اختياري. |
_InMemoryStore | agent_handler/store.py:63 | للاختبار/الاحتياطي؛ يتتبع طوابع زمنية رتيبة للـ TTL. |
SQLite SessionStore | stores/sqlite.py | دائم. |
| LangGraph checkpointer | stores/checkpoint.py | يُستخدَم من واجهة المستخدم لحالة المحادثة (app.py:724). |
تذكر وثائق IncomingMessage (gateway.py:62-66) ذلك بوضوح: “لا يلمس العقل الحقول الخاصة بالمنصة. تعيش مُعرّفات المنصة الخام داخل context_metadata.”
5. ناقل واجهة الويب (SSE)
Section titled “5. ناقل واجهة الويب (SSE)”تستخدم واجهة الويب أحداث الخادم المُرسَلة (SSE) ناقلًا للرسم.
- نقطة النهاية:
POST /api/chat/stream(sse_chat/) — تقبل{message, session_id, model}وتُعيدStreamingResponse(text/event-stream). - WebSocket للتلمترية:
/ws/chat/{session_id}هو ناقل المؤشّر/الإطارات الحيّة لـ Turn Delivery V2. وهو لا يشغّل الرسم ولا يقبل التحكم في الأدوار. - توصيل العميل:
chat.jsيستدعي دائمًاKS.sse('/api/chat/stream', …)للأدوار وPOST /api/approve/{thread_id}لـ HITL.
عقد أحداث SSE الكامل (token، tool_call، tool_result، approval_required، done، error) موثَّق في واجهة API ونقاط الامتداد.
6. سطح موافقة HITL لكل منصة
Section titled “6. سطح موافقة HITL لكل منصة”| المنصة | الآلية | نمط مُعرّف الاستدعاء | المُعالِج |
|---|---|---|---|
| Telegram | أزرار لوحة المفاتيح المضمّنة | hitl:approve:\{id\} / hitl:deny:\{id\} | telegram.py:733 |
| Discord | أزرار Components v2 | swarm_approve_\{task_id\} / swarm_reject_\{task_id\} | discord.py:312 |
| Slack | استدعاء تفاعلي | كتلة موافقة السرب | slack.py:401 |
| واجهة الويب | زر → POST /api/approve/\{thread_id\} + حدث SSE approval_required | — | routes_direct/misc.py |
صفّا Discord وSlack هما موافقات ناقل السرب. أما بطاقات الموافقة في الدردشة
فتستخدم الإجراءات نفسها hitl:approve:<id> / hitl:deny:<id> / hitl:approve_task:<id>
على المنصات الثلاث. وبطاقة X تسمّي إجراءها وليس فيها
“الموافقة للمهمة”. والبطاقة المنتهية صلاحيتها يُعلَن عنها في الدردشة، ويُسلَّم
الدور هناك.
أولوية النسخة المفردة لمُكيّف الناقل هي Telegram > Discord > Slack (واحد نشط فقط في كل مرة، موصول في
app.py:506-556). راجع الأمن والسلامة.
7. مصفوفة تكافؤ الميزات
Section titled “7. مصفوفة تكافؤ الميزات”| الميزة | Telegram | Discord | Slack | واجهة الويب | TUI |
|---|---|---|---|---|---|
| آلية الاستقبال | اقتراع طويل HTTP | بوابة WS | وضع Socket / اقتراع | HTTP POST (SSE) | محلي |
| أزرار موافقة HITL | ✅ | ✅ | ✅ | ✅ | نافذة منبثقة |
| تصيير Markdown | → Telegram HTML | نص عادي | نص عادي | من جهة العميل | عادي |
| أوامر الشرطة المائلة | مجموعة كاملة (مُسجَّلة) | تحليل نص عادي | hitl بدون / | أزرار واجهة | — |
| الصوت / STT (تحويل الكلام الوارد إلى نص) | ✅ | ✅ | ✅ | ✅ /api/voice/stt | — |
| الصوت / TTS (الرد الصوتي الصادر) | ✅ | ✅ | ✅ | ✅ /api/voice/tts | — |
| الوسائط دخول/خروج (صورة/مستند/فيديو) | ✅ | ✅ | ✅ | ✅ /api/chat/upload | — |
| مؤشر الكتابة | ✅ | ✅ | ✅ | لا ينطبق | لا ينطبق |
| التفاعلات | ✅ ✅/🎯/❌ | ❌ | ❌ | لا ينطبق | لا ينطبق |
| واجهة مُحدِّد النموذج | ✅ لوحة مفاتيح مضمّنة | ❌ | ❌ | ✅ إعدادات | للقراءة فقط |
8. واجهة Textual الطرفية (TUI) (kazma-tui)
Section titled “8. واجهة Textual الطرفية (TUI) (kazma-tui)”لوحة تحكم للمراقبة في الغالب للقراءة، فوق نفس النسخ المفردة (singletons) الأساسية.
| الجانب | التفصيل |
|---|---|
| نقطة الدخول | kazma_tui.app:main → KazmaTUI().run(). |
| التبويبات | لوحة التحكم (MetricsDashboard)، الذاكرة (MemoryTab)، الدردشة (ChatPanel)، الملفات (FilesPanel)، التتبعات (TracesPanel)، السرب (SwarmPanel)، الإعدادات (SettingsPanel)، المستندات (DocumentsPanel). |
| تهيئة النسخة المفردة | _initialize_core() يُهيّئ ModelRegistry وSwarmEngine إذا أُطلق مستقلًا. |
| HITL | نافذة الموافقة المنبثقة (widgets/hitl_modal.py)؛ _check_pending_approvals، _submit_hitl_decision. |
| RTL | update_localization() يُبدِّل صف CSS باسم rtl-mode ويُترجِم تسميات التبويبات، بكلمات واجهة الويب (TAB_LABELS). |
9. تسجيل أوامر Telegram
Section titled “9. تسجيل أوامر Telegram”يستدعي _register_bot_commands (telegram.py:961) دالة setMyCommands في Telegram عبر ثلاثة نطاقات (default، all_private_chats، all_group_chats). القائمة المُسجَّلة تتضمّن أوامر الشرطة المائلة القياسية بالإضافة إلى /swarm. وهي لا تُسجّل /hitl (لأن Slack يحتاج الصيغة المجردة hitl، والتسجيل منطق مشترك).
ملاحظات تدقيق التوثيق
Section titled “ملاحظات تدقيق التوثيق”agent_handler.pyلم يعد موجودًا كملف واحد — بل أصبح الحزمةagent_handler/. التوثيقات الأقدم التي تشير إلى الملف قديمة.- ناقل الرسم هو SSE.
/ws/chat/{session_id}حيٌّ كناقل تلمترية/مؤشّر؛ ولا يشغّل الرسم.

