جسور الدردشة
جسر الدردشة يفتح مساحة عمل Control Center من منتج دردشة خارجي، والمتاح اليوم هو Slack. أشر إلى البوت فيستيقظ وكيل، وأجب في خيط Slack فتستمر المحادثة، واكتب أمر شرطة مائلة فتُفتح تذكرة. وتحتها المساحات نفسها، والنسخ النصية، والتذاكر، والإسناد الذي يستخدمه تطبيق الحاسوب. الدردشة سطح على مساحة العمل، لا نسخة عنها.
لا يُشحن من المزوّدين إلا Slack. لا تنفيذ لـ Discord ولا لـ Telegram ولا لـ Teams ولا لـ Matrix في أي موضع من المنتج. كُتبت الميزة عمدًا بحيث لا تعرف المزوّد: إضافة منتج ثانٍ قيمة تعداد واحدة، ومحوّل واحد، ومكوّن إضافي واحد. لكن «الدردشة» اليوم تعني Slack.
يغطيها سطحان، ولكل منهما صاحبه. ربط مساحة العمل بـ Slack وتخصيص البوت إجراءان يقوم بهما مسؤول، من الإعدادات ← مساحة العمل ← عام. وربط حساب دردشتك بهويتك في Control Center إجراء يقوم به عضو، من الإعدادات ← مساحة العمل ← الملف الشخصي والهوية.
ثلاثة قرارات تشكّل الميزة كلها: أي اتجاه يسلكه الاتصال، وما الذي يفوّض رسالة تصل من الخارج، وأين يُسمح للمعرفة الخاصة بالمنتج أن تعيش.
الاتصال صادر بالتصميم
قسم بعنوان “الاتصال صادر بالتصميم”الجسر لا يستمع إلى المزوّد. يُوصَل Slack عبر Socket Mode: يطلب الخادم من Slack عنوان مقبس ثم يتصل نحو الخارج، ويستقبل عبر هذا الاتصال الإشارات والرسائل المباشرة وأوامر الشرطة المائلة.
والمقصود هذه النتيجة نفسها. حاسوب محمول خلف NAT، وخادم منزلي بلا نطاق، وجهاز ينتقل بين الشبكات: كلها تشغّل الجسر بلا منفذ وارد، وبلا نفق، وبلا شهادة TLS، وبلا عنوان عام يُبدَّل كلما تغيّر. لا نقطة نهاية webhook لتأمينها، لأنه لا توجد نقطة نهاية webhook. حين يؤلف Control Center تعريف التطبيق يفرض Socket Mode وينزع أي «request URL»، لأن التطبيق الذي يُهيأ للوضعين معًا لا يؤدي إلا نصف عمله.
الكلفة اتصال يجب إبقاؤه حيًا، وهذا عمل الخادم: إعادة الاتصال تراجع أسي مع ارتجاف، بسقف 30 ثانية، وتدوير المقبس الذي يبدأه المزوّد يعيد الاتصال من غير رفع ذلك التراجع، و_بيانات اعتماد مرفوضة_ حالة نهائية. إعادة محاولة رمز مُلغى بلا نهاية لا تُقابل إلا بحد معدّل، فيتوقف الجسر وينتظر رموزًا جديدة، ويعرض في الإعدادات خطأ المزوّد نفسه بحروفه.
مساحة عمل بلا تطبيق دردشة لا تكلف شيئًا: لا مقبس، ولا مؤقتات، ولا حركة. ويبدأ هذا كله بعد لافتة جاهزية الخادم، لأن تطبيق دردشة بطيء الرد يجب ألا يؤخر الإقلاع.
العضوية تفوّض، لا حيازة الرمز
قسم بعنوان “العضوية تفوّض، لا حيازة الرمز”من ثبّت التطبيق ليس من يرسل الرسالة. لذلك يُحَل كل حدث وارد إلى Principal قبل أي شيء، ويفشل مغلقًا مع تفسير:
- حساب دردشة غير مربوط يُخبَر كيف يربط نفسه، في خيط Slack الذي كتب فيه.
- حساب مربوط وصاحبه ليس عضوًا في مساحة العمل هذه يُطلَب منه أن يطلب دعوة من مسؤول.
- عضو بدور للقراءة فقط (
viewer،guest) يُخبَر أن دوره لا يستطيع بدء العمل.
لا يمضي أي من هذه الحالات. لا تُنسب رسالة دردشة إلى من لم يرسلها، وهذا ما يُبقي سجل التدقيق وحدود المعدّل لكل Principal ومُبلّغي التذاكر صادقين. انظر تعدد اللاعبين — الهوية والعضوية والحضور لنموذج العضوية الذي يستند إليه هذا.
الربط حل للهوية، لا منحة وصول. تُفحص العضوية من جديد عند كل حدث، فربط يخص من أُزيل بعد ذلك يُحَل إلى رفض لا إلى امتيازاته السابقة. مساران ينشئان الربط: رمز قصير العمر يُستخدم مرة واحدة يكتبه العضو في الدردشة، أو مطابقة تلقائية على البريد الموثَّق لدى المزوّد. ومسار البريد يشترط أن يكون المستخدم المطابق عضوًا أصلًا، فغريب يتصادف وجود عنوانه على الخادم لا يُربَط بشيء.
تُبقى الرموز في الذاكرة عمدًا، بمدة صلاحية 15 دقيقة. حفظ أحدها ينشئ صفًا دائمًا على شكل بيانات اعتماد لشيء يُفترض أن يزول، وإبطال كل رمز قائم عند إعادة تشغيل الخادم هو السلوك المقصود. يضغط العضو «ربط» مرة أخرى.
يُجاب الرفض في خيط Slack الذي فتحته الرسالة، كرد حقيقي. الرد داخل الخيط يضع شارة على الرسالة التي يجيب عنها، ويبلغ الشخص المعني، ويبقي «لست مربوطًا» خارج القناة نفسها. نشر رسالة هو القدرة الوحيدة التي يملكها كل مزوّد، فالتفسير لا يتوقف على ميزة قد لا يحملها التطبيق.
أصل الرسالة ومشكلة الصدى
قسم بعنوان “أصل الرسالة ومشكلة الصدى”جسر يعكس الاتجاهين يدور في حلقة ما لم يفصل شيء بينهما. كل رسالة يجلبها الجسر
تُختَم بـ metadata['chat']: المزوّد، والفريق، والمحادثة، والخيط، والرسالة،
والمرسل. المرآة الصادرة تتجاوز أي رسالة تحمل هذا الختم.
هذه القاعدة وحدها تجعل العكس في الاتجاهين آمنًا: رسالة إنسان كُتبت في Control Center تُرحَّل إلى الخيط المربوط مسبوقة باسم المؤلف، ورسالة أتت من الدردشة تتوقف حيث دخلت.
ما يعبر إلى الخارج ضيّق عمدًا. جواب الوكيل يُرحَّل كنص دردشة عادي. فكرة
قصيرة قد تظهر في صف Thinking… على البطاقة. مخرجات الأداة جدار من الفروق،
وتبقى في Control Center، وهو الموضع الذي يشير إليه الرد المقطوع.
البطاقة تبلّغ عن العمل، والنص يبقى الرسالة
قسم بعنوان “البطاقة تبلّغ عن العمل، والنص يبقى الرسالة”حيث يعرضها المزوّد، تحمل الجولة بطاقة مهمة واحدة، تُحرَّر في موضعها ما دامت الجولة جارية، وتقف فوق الجواب الذي تفسّره.
البطاقة خطة واحدة مجمّعة، لا فقرة مختلطة. يلصق Slack الحقلين details وoutput
على المهمة نفسها، فتُرسَل تفاصيل الصف مرة واحدة (الفكرة على Thinking…) ولا
يُبَث output أبدًا. الإعداد صف واحد يُستبدَل عنوانه (Working on it…،
Cloning …). الاستدلال يضيف صف Thinking…. كل أداة صفها (Read lib/main.dart).
الجواب نص عادي تحت البطاقة. عرض الخطة في Slack يقرأه من إلحاق Markdown لاحق، لا
من قطعة مختلطة داخل المهام. ولا شيء غير ذلك: لا مخرجات أداة، ومن وسائط الأداة ما
يقع في قائمة سماح قصيرة فقط، لأن وسائط أداة مجهولة قد تكون متن ملف بأكمله،
والصف سطر واحد.
تغطي البطاقة أيضًا الانتظار قبل الجولة. أول إشارة تنشئ المحادثة، وإنشاؤها
يستنسخ كل مستودع سيعمل فيه الوكيل. على مستودع حقيقي هذه دقائق لم يُسأل الوكيل
فيها عن شيء بعد، فيبدو الخيط كأن البوت تجاهل الطلب. لذلك تُفتح البطاقة لحظة قبول
الإشارة (Working on it…)، ثم يتحدّث صف الإعداد هذا بالألفاظ نفسها التي يعرضها
الحاسوب: Cloning acme/widgets…، ثم Setting up Reviewer…، ثم Starting the agent…،
ثم صف Thinking… ما إن يبدأ الوكيل الاستدلال، ثم صف لكل أداة. بطاقة واحدة
ورسالة واحدة، من «سمعتك» حتى الجواب كنص عادي تحتها. لا يُبلَّغ عن الإعداد إلا
حيث ينتظره أحد في الدردشة: محادثة تُجهَّز في التطبيق، أو محادثة عالقة تُستأنف
عند إقلاع الخادم، تُجهَّز بلا بطاقة أصلًا.
تحمل ما لا تستطيع الدردشة إعطاءه للقارئ: رابط عرض في Control Center إلى
المحادثة التي خلف الرد. منتجات الدردشة لا تقبل في الرابط إلا http(s)، فالزر
يشير إلى صفحة صغيرة يقدّمها هذا الخادم، وهي تحوّل المتصفح إلى رابط
control-center:// في تطبيق الحاسوب. تلك الصفحة لا تقرأ شيئًا ولا تحلّ شيئًا:
المعرّفات في عنوانها تُنسَخ إلى رابط عميق ولا تذهب إلى موضع آخر. والخادم الذي لا
عنوان عام له يمكن بلوغه ينتج بطاقة بلا زر، لا زرًا معطوبًا.
البطاقات قدرة كغيرها. مزوّد بلا بطاقات يبقى كما كان: نص فقط. ومزوّد يرفض البطاقة
وقت الاستدعاء يُبقي بثه، فالرد يعلو دائمًا على البطاقة التي تصفه. حين يُطفأ البث
بالكامل، تُحمَل البطاقة المكتملة مع الرسالة الوحيدة المنشورة. والتذكرة التي
تُفتح تُعامل المعاملة نفسها: تأكيد /cc ticket بطاقة مكتملة، وسبب وجودها الرابط
إلى التذكرة التي أُنشئت للتو.
تفاوض على القدرات ثم حفظها
قسم بعنوان “تفاوض على القدرات ثم حفظها”تختلف منتجات الدردشة فيما تستطيع عرضه، فكُتب الجسر على أغنى سطح: رد ينمو لحظة بلحظة داخل خيط، وحالة عابرة «جارٍ التفكير…»، وخيط له عنوان، وجواب مؤقت على أمر شرطة مائلة. ثم يتخلى عن قدرة واحدة في كل مرة. مزوّد لا يملك شيئًا من ذلك يبقى عاملًا، وتصل الردود رسالة واحدة.
الإعلان عن قدرة ليس وعدًا بها. ردود Slack المتدفقة تحتاج ميزة في خطة مدفوعة، وSlack لا يرفض إلا وقت الاستدعاء. الرفض الموسوم بالدوام يُحفظ طوال عمر الاتصال: يتوقف الجسر عن الطلب وينشر بعدها ردودًا كاملة، وتقوله الإعدادات بوضوح، حتى يُجاب «لماذا توقف بث ردودي» من غير قراءة سجل. وتُسقَط بطاقات المهام بالطريقة نفسها إن رفض Slack إحداها.
كل حد يخص المزوّد لا المرحّل: سقف الرسالة، وسقف قطعة البث، وهل يحتاج البث إلى مرساة خيط. يصرّح المحوّل بها كلها، والرد الطويل يُقطَع عند حد فقرة أو سطر أو كلمة، لا في منتصف كلمة.
جسر واحد، ومحوّل واحد لكل منتج
قسم بعنوان “جسر واحد، ومحوّل واحد لكل منتج”كل ما يأخذ شكل المنتج يعيش خلف منفذ. تعرف النواة العامة Markdown، ومحادثات
الدردشة، وخيوط الدردشة، والأعضاء، ولا تعرف شيئًا عن مظاريف Slack، ولا عن
mrkdwn، ولا عن gateway intents. ثلاثة أجزاء تجعل ذلك يعمل:
- المحوّل يوحّد شكل منتج واحد: نقله، واستدعاءات واجهته، ومرمّز نصه، وخصوصيات مظروفه. خصوصيات Slack كاشفة: إشارة القناة تصل مرتين، بصيغة
app_mentionو بصيغةmessage، ومعظم الأنواع الفرعية للرسائل زخرفة لا طلبات جديدة. يُحَل الاثنان داخل المحوّل، فلا تتعلم النواة أن Slack يسلّم مرتين. - الواصف هو الشكل العلني للمزوّد: أي بيانات اعتماد يحتاج، وما صيغها، وأي عناوين تساعد المستخدم على إيجادها، وما يستطيع فعله، وهل يمكن إنشاء تطبيقه أو تحريره من هنا. بطاقة الإعدادات، وحوار الاتصال، والتحقق من الوسائط، كلها تُولَّد منه. يرسل العميل في المقابل مزوّدًا وخريطة مفاتيحها معرّفات الحقول.
- المكوّن الإضافي هو التركيب على جهة الخادم: كيف تصير بيانات الاعتماد اتصالًا موثَّقًا، وكيف يُبنى المحوّل، وكيف يُدار التطبيق لدى المزوّد.
فإضافة منتج هي قيمة تعداد واحدة، ومحوّل واحد، ومكوّن إضافي واحد. لا عملية RPC جديدة، ولا تغيير في المخطط، ولا تغيير في العميل. ويبقى العميل أمينًا، لأن حقلًا لم يسمع به من قبل يُعرَض بالتسمية التي أرسلها الخادم.
تفصيلان يمنعان تسرّب التجريد إلى الأعلى. خطوات إدارة التطبيق التي لا واجهة للمزوّد لها (توليد رمز، وتثبيت التطبيق، وإعادة التثبيت بعد تغيير الأذونات) تُعاد خطوات مسمّاة بروابطها، فتعرضها شاشة إنهاء الإعداد من غير أن تعرف أي مزوّد هذا. وكل تعليمة يكتبها الجسر تسمّي المزوّد والأمر الذي يستخدمه التطبيق فعلًا، فيُقرأ أمر أُعيدت تسميته أو مزوّد ثانٍ قراءة صحيحة من غير نص جديد.
أين تعيش بيانات الاعتماد
قسم بعنوان “أين تعيش بيانات الاعتماد”رموز الدردشة لمساحة العمل تستقر في دليلها الخاص، إلى جانب قاعدتها، بصيغة
JSON عادية في
<dataDir>/<workspaceId>/chat_credentials/<provider>.json،
لا في القاعدة، وعمدًا لا في سلسلة مفاتيح نظام التشغيل. نتيجتان يسهل إغفالهما
لاحقًا: حذف مساحة العمل يفصل الدليل فتذهب الرموز معه، وتصدير مساحة العمل أو
نسخها احتياطيًا ينسخ workspace.db، فمساحة عمل تُسلَّم إلى شخص آخر لا تحمل في
داخلها رموز بوت حيّة. يرث الملف حد ثقة نظام ملفات مضيف الخادم: من يملك قراءة
دليل البيانات يستطيع قراءة رمز البوت، وهو الحد نفسه الذي تقف خلفه القواعد.
ملف واحد لكل مزوّد، يُكتب كتابة ذرية وللمالك فقط. التطبيق المُلغى يُحذف بفصل ملف واحد، والملف الفاسد يكلف مزوّدًا واحدًا لا المزوّدين كلهم. بيانات الاعتماد تسافر إلى الداخل عبر عملية الاتصال ولا تعود إلى الخارج، وقراءات الحالة تجيب ببيانات تعريف الاتصال فقط.
ما لا يُقدَّم عمدًا
قسم بعنوان “ما لا يُقدَّم عمدًا”- إعدادات بنية التطبيق. النقل والنطاقات واشتراكات الأحداث تُفرَض ولا تُعرَض. تطبيق يمكن إسكاته من شاشة إعدادات تذكرة دعم، لا ميزة.
- المحادثة وأوامر الشرطة المائلة. سطح Slack هو الرسائل، وردود الخيوط، وأوامر الشرطة المائلة.
- مصدر حقيقة ثانٍ. الدردشة ليست موضعًا يُخزَّن فيه العمل. خيط Slack مساحة، والرد نسخة نصية، والتذكرة تذكرة، وكلها في مساحة العمل، ويمكن الوصول إليها من كل عميل.
انظر أيضًا
قسم بعنوان “انظر أيضًا”- تعدد اللاعبين — الهوية والعضوية والحضور: عناصر Principal والأدوار التي يحلّ الجسر من خلالها
- مساحات العمل والعزل: لماذا يكون الربط وبيانات الاعتماد لكل مساحة عمل
- نموذج الوكيل: ما الذي يستيقظ حين تصل إشارة
- التذاكر والتفويض: ما الذي يفتحه
/cc ticket - البنية: أين يقف الجسر بين الخادم والعملاء
- اربط Slack بمساحة عملك
- إعداد تكامل Slack
- ربط حساب Slack
- تخصيص بوت الدردشة
- محادثات طلبات السحب: القرارات الثلاثة نفسها، اتُّخذت لمضيف شفرة