ابدأ

إعادة هيكلة دورة حياة ACP

تعمل دورة حياة ACP حاليًا، لكن يُستنتج جزء كبير جدًا منها بعد وقوع الحدث. تعيد عملية تنظيف العمليات بناء الملكية من معرّفات PID، وسلاسل الأوامر، ومسارات الأغلفة، وجدول العمليات النشط. وتعيد إمكانية رؤية الجلسات بناء الملكية من سلاسل مفاتيح الجلسات، إلى جانب عمليات البحث الثانوية sessions.list({ spawnedBy }). يتيح ذلك إجراء إصلاحات محدودة النطاق، لكنه يجعل أيضًا إغفال الحالات الحدّية أمرًا سهلًا: إعادة استخدام PID، والأوامر المقتبسة، والعمليات التابعة من الجيل الثاني للمهايئ، وجذور الحالة متعددة الـ Gateway، والفرق بين cancel وclose، والفرق بين إمكانية رؤية tree وall؛ كلها تصبح مواضع منفصلة لإعادة اكتشاف قواعد الملكية نفسها.

تجعل إعادة الهيكلة هذه الملكية مفهومًا من الدرجة الأولى. وليس الهدف إنشاء واجهة منتج جديدة لـ ACP؛ بل توفير عقد داخلي أكثر أمانًا لسلوك ACP وACPX الحالي.

الأهداف

  • ألّا يرسل التنظيف إشارة إلى أي عملية إلا إذا تطابقت الأدلة الحية الحالية مع عقد إيجار مملوك لـ OpenClaw.
  • أن تكون لكل من cancel وclose وعملية الحصاد عند بدء التشغيل نوايا متميزة لدورة الحياة.
  • أن تستخدم sessions_list وsessions_history وsessions_send وعمليات التحقق من الحالة نموذج الجلسات المملوكة لمقدّم الطلب نفسه.
  • ألّا تتمكن عمليات التثبيت متعددة الـ Gateway من حصاد أغلفة ACPX التابعة لبعضها.
  • أن تستمر سجلات جلسات ACPX القديمة في العمل أثناء الترحيل.
  • أن يظل وقت التشغيل مملوكًا للـ Plugin؛ وألّا تتعرّف النواة على تفاصيل حزمة ACPX.

ما لا نستهدفه

  • استبدال ACPX أو تغيير واجهة الأمر العامة /acp.
  • نقل سلوك مهايئ ACP الخاص بالمورّد إلى النواة.
  • مطالبة المستخدمين بتنظيف الحالة يدويًا قبل الترقية.
  • جعل cancel يغلق جلسات ACP القابلة لإعادة الاستخدام.

النموذج المستهدف

هوية نسخة Gateway

يجب أن تكون لكل عملية Gateway معرّف ثابت لنسخة وقت التشغيل:

ts
type GatewayInstanceId = string;

يمكن إنشاؤه عند بدء تشغيل Gateway وحفظه في الحالة طوال عمر ذلك التثبيت. وهو ليس سرًا أمنيًا؛ بل عامل تمييز للملكية يُستخدم لتجنّب الخلط بين عمليات ACP التابعة لأحد الـ Gateway وعمليات Gateway آخر.

ملكية جلسة ACP

يجب أن تتضمن كل جلسة ACP منشأة بيانات وصفية موحّدة للملكية:

ts
type AcpSessionOwner = {  sessionKey: string;  spawnedBy?: string;  parentSessionKey?: string;  ownerSessionKey: string;  agentId: string;  backend: "acpx";  gatewayInstanceId: GatewayInstanceId;  createdAt: number;};

يجب أن يعيد Gateway هذه الحقول في صفوف الجلسات حيثما كانت معروفة. ويجب أن تكون تصفية إمكانية الرؤية تحققًا خالصًا يعتمد على البيانات الوصفية للصف:

ts
canSeeSessionRow({  row,  requesterSessionKey,  visibility,  a2aPolicy,});

يؤدي ذلك إلى إزالة استدعاءات sessions.list({ spawnedBy }) الثانوية المخفية من عمليات التحقق من إمكانية الرؤية. يكون العنصر الفرعي المنشأ لـ ACP عبر وكيل آخر مملوكًا لمقدّم الطلب لأن الصف ينص على ذلك، لا لأن استعلامًا ثانيًا صادف أن وجده.

عقود إيجار عمليات ACPX

يجب أن ينشئ كل تشغيل لغلاف مولّد سجل عقد إيجار:

ts
type AcpxProcessLease = {  leaseId: string;  gatewayInstanceId: GatewayInstanceId;  sessionKey: string;  wrapperRoot: string;  wrapperPath: string;  rootPid: number;  processGroupId?: number;  commandHash: string;  startedAt: number;  state: "open" | "closing" | "closed" | "lost";};

يجب أن تتلقى عملية الغلاف معرّف عقد الإيجار ومعرّف نسخة Gateway ضمن بيئتها:

sh
OPENCLAW_ACPX_LEASE_ID=...OPENCLAW_GATEWAY_INSTANCE_ID=...

عندما تسمح المنصة بذلك، يجب أن يفضّل التحقق البيانات الوصفية الحية للعملية التي لا يمكن أن يربكها اقتباس الأوامر:

  • استمرار وجود PID الجذر
  • وجود مسار الغلاف الحي تحت wrapperRoot
  • تطابق مجموعة العمليات مع عقد الإيجار عند توفرها
  • احتواء البيئة على معرّف عقد الإيجار المتوقع عندما تكون قابلة للقراءة
  • تطابق تجزئة الأمر أو مسار الملف التنفيذي مع عقد الإيجار

إذا تعذر التحقق من العملية الحية، يفشل التنظيف بحالة مغلقة.

متحكم دورة الحياة

أدخل متحكمًا واحدًا لدورة حياة ACPX يمتلك عقود إيجار العمليات وسياسة التنظيف:

ts
interface AcpxLifecycleController {  ensureSession(input: AcpRuntimeEnsureInput): Promise&lt;AcpRuntimeHandle&gt;;  cancelTurn(handle: AcpRuntimeHandle): Promise<void>;  closeSession(input: {    handle: AcpRuntimeHandle;    discardPersistentState?: boolean;    reason?: string;  }): Promise<void>;  reapStartupOrphans(): Promise<void>;  verifyOwnedTree(lease: AcpxProcessLease): Promise&lt;OwnedProcessTree | null&gt;;}

يطلب cancelTurn إلغاء الدور فقط. ويجب ألّا يحصد عمليات الغلاف أو المهايئ القابلة لإعادة الاستخدام.

يُسمح لـ closeSession بالحصاد، ولكن فقط بعد تحميل سجل الجلسة، وتحميل عقد الإيجار، والتحقق من أن شجرة العمليات الحية ما تزال تابعة لعقد الإيجار ذاك.

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

عقد الغلاف

يجب أن تظل الأغلفة المولّدة صغيرة. وينبغي لها:

  • تشغيل المهايئ ضمن مجموعة عمليات حيثما كان ذلك مدعومًا
  • تمرير إشارات الإنهاء العادية إلى مجموعة العمليات
  • اكتشاف وفاة العملية الأم
  • عند وفاة العملية الأم، إرسال SIGTERM، ثم إبقاء الغلاف حيًا حتى تشغيل مسار SIGKILL الاحتياطي
  • إبلاغ متحكم دورة الحياة بمعرّف PID الجذر ومعرّف مجموعة العمليات عند توفر ذلك

يجب ألّا تقرر الأغلفة سياسة الجلسة. فهي لا تفعل سوى فرض تنظيف محلي لشجرة العمليات الخاصة بمجموعة المهايئ التابعة لها.

عقد إمكانية رؤية الجلسات

يجب أن تستخدم إمكانية الرؤية ملكية الصفوف الموحّدة:

ts
type SessionVisibilityInput = {  requesterSessionKey: string;  row: {    key: string;    agentId: string;    ownerSessionKey?: string;    spawnedBy?: string;    parentSessionKey?: string;  };  visibility: "self" | "tree" | "agent" | "all";  a2aPolicy: AgentToAgentPolicy;};

القواعد:

  • self: جلسة مقدّم الطلب فقط.
  • tree: جلسة مقدّم الطلب، إضافة إلى الصفوف المملوكة له أو المنشأة منها.
  • all: جميع صفوف الوكيل نفسه، وصفوف الوكلاء الآخرين المسموح بها وفق a2a، وصفوف الوكلاء الآخرين المنشأة والمملوكة لمقدّم الطلب حتى عندما يكون a2a العام معطّلًا.
  • agent: الوكيل نفسه فقط، ما لم تنص علاقة ملكية صريحة على أن الصف تابع لمقدّم الطلب.

يجعل هذا tree وall رتيبتين: يجب ألّا يخفي all عنصرًا فرعيًا مملوكًا كان tree سيعرضه.

خطة الترحيل

المرحلة 1: إضافة الهوية وعقود الإيجار

  • إضافة gatewayInstanceId إلى حالة Gateway.
  • إضافة مخزن لعقود إيجار ACPX ضمن دليل حالة ACPX.
  • كتابة عقد إيجار قبل إنشاء غلاف مولّد.
  • تخزين leaseId في سجلات جلسات ACPX الجديدة.
  • الاحتفاظ بحقول PID والأوامر الحالية للسجلات القديمة.

المرحلة 2: التنظيف المعتمد أولًا على عقود الإيجار

  • تغيير تنظيف الإغلاق لتحميل leaseId أولًا.
  • التحقق من ملكية العملية الحية مقابل عقد الإيجار قبل إرسال الإشارات.
  • الاحتفاظ بمسار PID الجذر وجذر الغلاف الاحتياطي الحاليين للسجلات القديمة فقط.
  • وضع علامة closed على عقود الإيجار بعد التنظيف المتحقق منه.
  • وضع علامة lost على عقود الإيجار عندما تكون العملية قد اختفت قبل التنظيف.

المرحلة 3: الحصاد عند بدء التشغيل المعتمد أولًا على عقود الإيجار

  • يفحص الحصاد عند بدء التشغيل عقود الإيجار المفتوحة.
  • يتحقق لكل عقد إيجار من العملية الجذرية ويجمع العمليات التابعة.
  • يحصد الأشجار المتحقق منها بدءًا من العناصر الفرعية.
  • ينهي صلاحية عقود الإيجار القديمة ذات الحالة closed وlost ضمن نافذة احتفاظ محدودة.
  • يحتفظ بفحص علامات الأوامر بوصفه مسارًا احتياطيًا مؤقتًا للسجلات القديمة فقط، مع تقييده بجذر الغلاف ونسخة Gateway حيثما أمكن.

المرحلة 4: صفوف ملكية الجلسات

  • إضافة بيانات الملكية الوصفية إلى صفوف جلسات Gateway.
  • تعليم كتّاب ACPX والوكلاء الفرعيين والمهام الخلفية ومخزن الجلسات تعبئة ownerSessionKey أو spawnedBy.
  • تحويل عمليات التحقق من إمكانية رؤية الجلسات إلى استخدام البيانات الوصفية للصفوف.
  • إزالة عمليات البحث الثانوية sessions.list({ spawnedBy }) أثناء التحقق من إمكانية الرؤية.

المرحلة 5: إزالة الاستدلالات القديمة

بعد نافذة إصدار واحدة:

  • التوقف عن الاعتماد على سلاسل أوامر الجذر المخزنة لتنظيف ACPX غير القديم
  • إزالة عمليات فحص علامات الأوامر عند بدء التشغيل
  • إزالة عمليات البحث الاحتياطية في القوائم للتحقق من إمكانية الرؤية
  • الإبقاء على سلوك دفاعي يفشل بحالة مغلقة عند فقدان عقود الإيجار أو تعذر التحقق منها

الاختبارات

أضف مجموعتي اختبارات معتمدتين على الجداول.

محاكي دورة حياة العمليات:

  • إعادة استخدام PID بواسطة عملية غير مرتبطة
  • إعادة استخدام PID بواسطة جذر غلاف تابع لـ Gateway آخر
  • أمر الغلاف المخزن مقتبس بصيغة shell، بينما أمر ps الحي غير مقتبس
  • خروج عملية المهايئ الفرعية مع بقاء عملية من الجيل التالي ضمن مجموعة العمليات
  • وصول المسار الاحتياطي SIGTERM عند وفاة العملية الأم إلى SIGKILL
  • عدم توفر قائمة العمليات
  • عقد إيجار قديم مع عملية مفقودة
  • عملية يتيمة عند بدء التشغيل تتضمن الغلاف، وعملية المهايئ الفرعية، وعملية من الجيل التالي

مصفوفة إمكانية رؤية الجلسات:

  • self وtree وagent وall
  • تمكين a2a وتعطيله
  • صف للوكيل نفسه
  • صف لوكيل آخر
  • صف ACP لوكيل آخر منشأ ومملوك لمقدّم الطلب
  • تقييد مقدّم طلب معزول إلى tree
  • إجراءات القائمة والسجل والإرسال والحالة

الثابت المهم: يكون العنصر الفرعي المنشأ والمملوك لمقدّم الطلب مرئيًا حيثما تشمل إمكانية الرؤية المضبوطة شجرة جلسة مقدّم الطلب، ولا يكون all أقل قدرة من tree.

ملاحظات التوافق

قد لا تحتوي سجلات الجلسات القديمة على leaseId. وينبغي أن تستخدم مسار التنظيف القديم الذي يفشل بحالة مغلقة:

  • اشتراط وجود عملية جذر حية
  • اشتراط ملكية جذر الغلاف عندما يكون متوقعًا وجود غلاف مولّد
  • اشتراط توافق الأوامر للجذور التي لا تستخدم غلافًا
  • عدم إرسال إشارة أبدًا اعتمادًا فقط على بيانات PID الوصفية القديمة المخزنة

إذا تعذر التحقق من سجل قديم، فاتركه دون تغيير. ومن المفترض أن يؤدي تنظيف عقود الإيجار عند بدء التشغيل ونافذة الإصدار التالية في النهاية إلى إيقاف المسار الاحتياطي.

معايير النجاح

  • ألّا يؤدي إغلاق جلسة ACPX قديمة أو بالية إلى إنهاء عملية تابعة لـ Gateway آخر.
  • ألّا تترك وفاة العملية الأم عمليات عنيدة من الأجيال التالية للمهايئ قيد التشغيل.
  • أن يُجهض cancel الدور النشط دون إغلاق الجلسات القابلة لإعادة الاستخدام.
  • أن تتمكن sessions_list من عرض العناصر الفرعية لـ ACP التابعة لوكلاء آخرين والمملوكة لمقدّم الطلب تحت كل من tree وall.
  • أن يعتمد التنظيف عند بدء التشغيل على عقود الإيجار، لا على عمليات فحص واسعة لسلاسل الأوامر.
  • أن تغطي اختبارات مصفوفة العمليات وإمكانية الرؤية المركّزة كل حالة حدّية كانت تتطلب سابقًا إصلاحات مراجعة منفردة.
Was this useful?
On this page

On this page