تخطَّ إلى المحتوى

النشر والعملاء

Control Center ليس برنامجًا واحدًا، بل بنية عميل خفيف: خادم بلا واجهة يملك بياناتك، وعملاء يرسمون واجهة فوق اتصال RPC واحد به. فهم هذا الفصل يفسّر لماذا يعمل بعض الأشياء حيث تعمل، وما يبلغه كل عميل وما لا يبلغه.

عملية اسمها cc_server تحتفظ بقواعد البيانات: ملف global.db صغير لحالة الخادم كله، وملف SQLite واحد لكل مساحة عمل، يُفتح عند الحاجة. وتشغّل العمل المستمر: محرّكات خطوط الأنابيب، والمُوفِّقات، وسطح أدوات MCP، ومستمع التنسيق. كل ما ينبغي أن يبقى بعد إغلاق النافذة، أو أن يراه عميلان بالصورة نفسها، يعيش هناك.

لا يفتح أي عميل ملف قاعدة بنفسه. كل عميل يحمل RemoteRpcClient واحدًا، ويُصدر عبره عمليات المستودع والاشتراكات. يصادق الخادم الاتصال بمفتاح اقتران مشترك مسبقًا لجهاز مقترن، ثم لا يعنيه الاتصال بعد ذلك. الجلسة غير مربوطة بمساحة عمل. الموزّع بلا حالة: كل استدعاء خاص بمساحة عمل يحمل workspace_id في وسائطه، ويُرفض الاستدعاء الذي يسقطه.

هذا مقصود، وهو ما يتيح لعميلين على خادم واحد أن يعملا في مساحتي عمل مختلفتين، من غير أن يحتفظ الخادم بـ«مساحة عمل حالية» يختلفان عليها. يُفرض العزل على كل استدعاء، في أربع خطوات:

  1. تصرّح العملية إن كانت خاصة بمساحة عمل. إن كانت كذلك ولم يصل workspace_id، يُرفض الاستدعاء.
  2. يجب أن يكون المعرّف المذكور لمساحة عمل مسجّلة، ويُتحقق من ذلك قبل أن تُفتح أي قاعدة. وإلا أنشأ معرّف مجهول ملف workspace.db شبحًا فارغًا مع كل طلب. إن غاب، فالنتيجة غير موجود.
  3. يجب أن يبلغ WorkspaceRole لدى المستدعي حد العملية الأدنى، ويُشتق من نوعها: القراءة تحتاج ضيفًا، والتعديل يحتاج عضوًا، والعمل المدمّر يحتاج مسؤولًا.
  4. العملية التي تكشف محتوى مستودع تشترط أيضًا منحة لذلك المستودع بالمستوى الذي تصرّح به، none / read / review / write، حتى لا تعطي عضوية مساحة العمل في صمت صلاحيات أوسع من مضيف الشفرة.

الجملة التي تهم: حيازة مفتاح الاقتران ليست حد الوصول، والعضوية هي الحد.

ثلاثة ضغوط دفعت إلى خادم:

  1. مصدر واحد للحقيقة. حاسوب وهاتف، أو محمول وحاسوب مكتبي، ينبغي أن يريا حالة الأسطول نفسها. لو ملك كل واحد قاعدته لتباعدت الصور. الخادم الواحد يعني أن كل عميل يقرأ البيانات الحية نفسها.
  2. الويب لا يكتفي بنفسه. المتصفح لا يطلق عملية فرعية ولا يفتح ملفًا محليًا، فنسخة الويب لا تملك القاعدة ولا تشغّل وكيلًا. لا بد أن تكلم خادمًا يفعل ذلك. عوض بناء مسار بيانات ثانٍ للويب وحده، أُدخل الحاسوب في المسار نفسه.
  3. التشغيل بلا واجهة. خادم تشغّله على جهاز صغير، تبلغه من هاتف في القطار أو من متصفح في أي مكان، أنفع من واجهة رسومية لا بد أن تبقى مفتوحة.

المقابل أن الخادم صار اعتمادًا: إن لم يكن شغالًا فلا شيء يعمل. جواب الحاسوب الافتراضي أن يشغّل خادمًا بنفسه.

خمسة عملاء تبلغ الخادم، ولكل منها شكل ودرجة ثقة.

تطبيق Flutter للحاسوب (macOS وWindows وLinux) هو العميل الكامل. عند أول تشغيل يسأل «كيف ينبغي أن يعمل Control Center؟»، ثم يحفظ الاختيار:

  • محلي: يشغّل cc_server ويشرف عليه على هذا الجهاز. هذا هو الافتراضي، إعداد مكتفٍ بنفسه لمستخدم واحد. يطلق الحاسوب الخادم، فيملك القاعدة تحت جذر دعم التطبيق ويكلمه عبر loopback.
  • بعيد: يتصل بـ cc_server في مكان آخر عبر WebSocket آمن. البيانات تعيش على ذلك الخادم، وهذا الحاسوب عارض فقط.

في الحالتين يحل عميل RPC الناتج محل موفّر بيانات التطبيق، فتقرأ الواجهة كلها وتكتب عبر الخادم: كل شاشة وكل ميزة. الحاسوب لا يمس ملف القاعدة مباشرة.

نسخة الويب هي تطبيق Flutter نفسه، مُترجَمًا ليعمل في المتصفح، ومستضافة على app.usectrl.dev، فلا شيء يُثبَّت. المتصفح لا يطلق عملية فرعية، فعميل الويب بعيد دائمًا: يتصل بـ cc_server عبر wss:// ويرسم واجهة الحاسوب كاملة. متى اتصل أظهر الشاشات نفسها على الأسطول نفسه، فالشفرة واحدة وRPC واحد. الفجوات قليلة، وهي ما يحتاج قدرة أصيلة لا يملكها المتصفح. طبقة جودة التقاط الاجتماع، أدناه، هي الأهم، وإشعارات نظام التشغيل تليها.

لهذا يختلف قول «بلا تنزيل» عن قول «بلا خادم». عميل الويب يلغي التثبيت، ولا يلغي الخادم. قد يتصل بـ cc_server على الحاسوب المحمول نفسه، على ws://localhost:9030، بلا شهادة، لأن المتصفحات تعد loopback موثوقًا. أو بخادم على جهاز آخر، ولا بد أن يُبلغ عبر wss:// بشهادة يثق بها المتصفح. يرفض cc_server ربط عنوان غير loopback من غير TLS، فالخادم البعيد يعني شهادته هو، أو بروكسي ينهي TLS، أو نفقًا أو VPN يوفّر الشهادة.

الحزمة نفسها تُستضاف بمعزل عن الخادم الذي تكلمه. cc_server المستقل يقدّم نقطة RPC وسطح MCP وبروكسياته، لا حزمة ويب. مسار الملفات الثابتة موجود في الخادم، لكن الثنائي المستقل بلا جذر ويب مُعد، فلا شيء يُقدَّم عند /. صورة الحاوية المنشورة cc-webapp هي التي تستضيف الحزمة، وscripts/build_web.sh هو الذي يبنيها. لا يقبل الخادم من أصول المتصفح إلا ما في قائمة --allowed-origins، وافتراضها https://app.usectrl.dev. استضافة الحزمة بنفسك تعني إضافة أصلها.

الوسائط (الصور الرمزية، وصور الموجز، ولقطات طلبات السحب) تمر بنقطة البروكسي في الخادم بدل أن تطلب المضيفين المصدر مباشرة، فلا يجلب المتصفح أصلًا اعتباطيًا. يختم العميل المنشور سياسة Content-Security Policy لكل طلب، من كوكي غير حسّاس، فلا يُسمح إلا بأصل الخادم المتصل.

تطبيق Remote عميل منفصل وأخف، تطبيق ويب تقدّمي بـ Flutter على remote.usectrl.dev. هو عمدًا ليس التطبيق الكامل. يقترن عبر مرحّل WebSocket بوساطة، مغلق من طرف إلى طرف، ويتكلم شريحة من سطح الأدوات تميل إلى القراءة: قراءة التذاكر والرسائل وموجز الأخبار، وإرسال رد أو تحديث تذكرة.

الهاتف فاعل بصلاحيات أدنى من وكيل محلي. سياسة أدوات ترفض افتراضيًا، ومفتاح اقتران يختم كل إطار، وقدرة جلسة تُخرج العمليات المميّزة (الاقتران مثلًا) من عميل الرفيق، وفحوص الدور ومنحة المستودع على كل استدعاء مما سبق، وتحديد المعدّل: هذا كله يمنع اقترانًا مقبولًا، أو مسرّبًا، من أن يصير تحكمًا عن بُعد كاملًا. انظر التحكم عن بُعد والجوال لنموذج الأمان كاملًا.

الأدوات الخارجية، من محررات ووكلاء آخرين وواجهات سطر أوامر، التي تتكلم Model Context Protocol، تتصل بسطح MCP في الخادم وتستهلك سجل الأدوات المُنمَّط. هذا مسار التكامل البرمجي، لا واجهة رسومية. انظر استخدام خادم MCP ومرجع أدوات MCP.

cc_worker ثنائي Dart خالص بلا واجهة، يقترن بـ cc_server، ويصرّح بقدرات مضيفه، ويسحب مهامًا مؤجَّرة لينفّذها. يبث أحداث العملية عائدة عبر RPC نفسه، ولا يحتفظ بحالة دائمة: لا قاعدة، ولا مصادقة، ولا موافقات، ولا ميزانيات. تلك لا تغادر cc_server. العامل ذراع لا دماغ ثانٍ. خادم واحد صاحب القرار، وN أذرع لا تفكّر، بلا إجماع وبلا حركة من عامل إلى عامل.

التنفيذ المؤجَّر ما زال سباكة للإيجار. agentRun هو النوع الوحيد الذي يشغّل أمرًا حقيقيًا (CC_JOB_COMMAND، أو مطالبة تُعاد بالصدى إن لم يُقدَّم أمر). pipelineStep وcodeIndex وgoldenRender وbenchmark وevalBatch تسبر حاليًا بـ git --version / rev-parse HEAD، حتى يُمارَس مسار البث من غير ادّعاء أن تلك المهام قد جرت.

ما يعلنه: نظام التشغيل، ومعمارية المعالج، وعدد الأنوية، وحجم الذاكرة، وهل يمكن بلوغ Flutter SDK، وأي خلفيات للبيئة المعزولة يملكها المضيف (لا شيء على Windows). العامل المخصّص يعلن دائمًا alwaysOn وacceptsParallel، وهما ما يعتمد عليه مجدول التثبيت ← التفضيل ← الفيض في التوزيع. لا سبر لـ GPU ولا لـ ML.

الحاسوب المنفرد لا يلاحظ شيئًا من هذا، فالإرسال العادي لا يمر بالأسطول أصلًا. يوجد عامل ضمني داخل العملية، مسجّل لمضيف الخادم، لكنه مفصل تسجيل بلا منفّذات مربوطة به. المهمة التي تُوضع فعلًا على العامل المحلي تفشل بدل أن تُنفَّذ. اليوم لا ينفّذ المهام المؤجَّرة إلا cc_worker حقيقي.

الجانب الخادم (cc_server) منفّذ الأسطول (cc_worker) عميل الحاسوب عميل الويب / الهاتف
قواعد البيانات (Drift/SQLite) يملكها — — —
المصادقة والموافقات والميزانيات والهوية يملكها — — —
خطوط الأنابيب والمُوفِّقات ومستمع التنسيق يشغّلها — — —
سطح أدوات MCP يقدّمه — — —
تنفيذ الوكيل يشغّله (أو يؤجّره) ينفّذ المهام المؤجَّرة — —
رسم الواجهة — — كاملة كاملة (الويب) / تميل إلى القراءة (الهاتف)
التقاط الاجتماع (الميكروفون + صوت الاجتماع) — — التقاط أصيل الويب فقط، عبر صوت مشاركة الشاشة
OAuth التقويم (Google) يخزّن الرموز — — —

الطبقتان لا تعزلان بالأسلوب نفسه. يغلّف cc_server التشغيلات التي ينفّذها بنفسه ببيئة نظام التشغيل المعزولة حيث توجد خلفية: Seatbelt على macOS، وbubblewrap على Linux وWSL2. وعلى Windows، أو من غير bwrap وsocat، يعود إلى الحدود الأخرى. cc_worker لا يعزل أصلًا. يعلن قدرة sandbox حتى يضع الخادم المهام حسب المنصة، لكن العامل لا يطبّق عزلة: المهمة المؤجَّرة تُطلَق مباشرة، ويحدّها بيئة الإيجار وشجرة العمل التي أُعطيت إياها. تأجير العمل يخرج التنفيذ من البيئة المعزولة، حتى على مضيف يملكها. انظر البيئة المعزولة والأمان لما يحدّ التشغيل في كل حالة.

التقاط الاجتماع يجري على العميل لأنه يحتاج ميكروفونًا وصوت الاجتماع. خادم في خزانة لا يسجّل مكالمتك. ثم يلخّص خط أنابيب الخادم التسجيل، وتُحفظ الملاحظات حيث يقرأها كل عميل.

عميل الويب يسجّل أيضًا، وقد يخفي شكل الجدول ذلك: يلتقط الميكروفون مع مسار صوت لمشاركة الشاشة في المتصفح، ويبث PCM بتردد 16 kHz إلى الخادم. وهو يشترط مسار الصوت ذاك، فيفشل صراحة حيث لا يوفّره المتصفح. ما يخص الحاسوب فعلًا هو طبقة الجودة حول الالتقاط: إلغاء الصدى على مستوى الإشارة عبر المعالج الأصيل، ومقياس مستوى الدخل، وتحذير الميكروفون الصامت، وإعادة تشغيل الملخص، وإلغاء المعالجة، وإجراء التقويم «تسجيل وربط».

كل عميل يثبت هويته بـمفتاح اقتران مشترك مسبقًا يُجهَّز مرة واحدة:

  • الحاسوب في الوضع المحلي يولّد مفتاحًا جديدًا عند كل إقلاع، ويسلّمه إلى الخادم الذي أطلقه عبر متغيرات البيئة، فلا يُحفظ سر على الحاسوب.
  • العملاء البعيدون (الحاسوب البعيد، والويب، والهاتف) يستخدمون مفتاحًا يصكّه أمر pair في الخادم، ويُحفظ في سلسلة مفاتيح النظام (أو محليًا على الهاتف).

الإبطال فوري لا مؤجّل. يراقب الخادم جدول الأجهزة المقترنة، ويُسقط خلال ثوانٍ أي جلسة مفتوحة خرج جهازها من المجموعة النشطة، على مسار WebSocket المباشر ومسار المرحّل سواء. لا تنتظر إعادة اتصال الجهاز حتى تقطعه.

المفتاح لا يمر في عنوان يستطيع مضيف ثابت أن يراه. حين يصل عبر رابط، على الهاتف أو في المتصفح، يركب جزء الرابط، وهو ما بعد #، والمتصفح لا يرسل ذلك إلى الخادم الذي يستضيف الصفحة. هذا هو الثابت الذي يُحفظ، وينطبق على الاثنين. ولعميل الويب مدخلان آخران: نموذج الاتصال، حيث تلصق خادمًا ومفتاحًا مباشرة، ورمز دعوة يصرفه مقابل بيانات اعتماد مصكوكة لذلك المتصفح وحده.