DeepSeek Harness: التحميل المبني على الاعتماديات وإعادة…
آخر تحديث: 2026-08-31
من "التحكم يدوياً بترتيب التحميل" إلى "صرّح بالاعتماديات، الإطار يتولى الباقي" — التحميل المبني على الاعتماديات يعني أن المطورين يصرّحون بالعلاقات فقط. مُقترناً بإعادة تحميل الوحدات السريعة (HMR)، تغييرات الكود تسري بدون إعادة تشغيل. تجربة التطوير تتحسن بشكل كبير.
📋 المتطلبات المسبقة: أكمل 14-inject.md، تفهم تصريحات اعتماديات inject
1. ما ستتعلمه
- رسم الاعتماديات والفرز الطوبولوجي
- التحميل التلقائي عند جاهزية الاعتماديات
- آلية إعادة تحميل الوحدات السريعة (HMR)
- السياق المتداخل (Nested Context)
- إعادة التحميل المُحفّزة بتغيير الإعدادات
- أفضل ممارسات HMR للتطوير
2. رسم الاعتماديات والفرز الطوبولوجي
(1) بناء رسم الاعتماديات
عند البدء، يجتاز الإطار تصريحات inject لجميع الإضافات ويبني رسماً أكليلاً موجهاً غير دوري (DAG):
function buildDependencyGraph(plugins: Plugin[]) {
const graph = new DAG()
for (const plugin of plugins) {
graph.addNode(plugin.name)
for (const dep of plugin.inject) {
graph.addEdge(dep, plugin.name)
}
}
return graph
}
(2) الفرز الطوبولوجي
الفرز الطوبولوجي يُحدد ترتيب التحميل:
تصريحات الإضافات:
core: inject = []
tools: inject = ['core']
llm: inject = ['core']
my-tool: inject = ['tools', 'llm']
رسم الاعتماديات:
core → tools → my-tool
core → llm → my-tool
نتيجة الفرز الطوبولوجي: [core, tools, llm, my-tool]
(3) التحميل المتوازي
الإضافات بدون علاقات اعتماد تُحمّل بالتوازي:
graph TB
subgraph Phase1[المرحلة 1]
CORE[core]
end
subgraph Phase2[المرحلة 2 - متوازي]
TOOLS[tools]
LLM[llm]
end
subgraph Phase3[المرحلة 3]
MYTOOL[my-tool]
end
CORE --> TOOLS
CORE --> LLM
TOOLS --> MYTOOL
LLM --> MYTOOL
(4) ▶ مثال 4
graph TB
CORE[core] --> TOOLS[tools]
CORE --> SESSIONS[sessions]
CORE --> LOGGER[logger]
TOOLS --> MY_TOOL[my-tool]
SESSIONS --> MY_TOOL
LOGGER --> TRAJECTORY[trajectory]
SESSIONS --> TRAJECTORY
3. التحميل التلقائي عند جاهزية الاعتماديات
(1) إشباع الاعتماديات الديناميكي
الإضافات لا تحتاج جميع اعتمادياتها مُشبعة عند البدء. عند تسجيل خدمة اعتمادية لاحقاً، تُفعّل الإضافات المنتظرة تلقائياً:
// Plugin A: inject = ['tools'] — tools لم تُسجّل بعد
// → حالة Fiber: pending
// لاحقاً، إضافة tools تُحمّل وتُسجّل الخدمة
// → Fiber الخاص بـ Plugin A ينتقل تلقائياً لـ active، يستدعي apply
(2) ▶ مثال 2
export function apply(ctx: Context) {
if (someCondition) {
ctx.provide('optional-service', impl)
// الإضافات المنتظرة المعتمدة على optional-service تُفعّل تلقائياً الآن
}
}
(3) ▶ مثال 3
sequenceDiagram
participant F as الإطار
participant A as Plugin A (inject: tools)
participant T as Tools Plugin
F->>A: تسجيل → pending (tools غير جاهز)
F->>T: تسجيل → active
T->>F: تسجيل خدمة tools
F->>A: الاعتمادية جاهزة → active
A->>F: apply() يُنفّذ
(4) اعتماديات غير قابلة للإشباع بشكل دائم
إذا كانت اعتمادية مطلوبة مُصرّح بها لا يمكن إشباعها أبداً:
[warn] plugin my-plugin has unsatisfied dependency: nonexistent-service
[warn] my-plugin will remain in pending state
الإضافة لا تُخطئ — تبقى في حالة pending للأبد. الاعتماديات الاختيارية (مع ?) لا تُنتج تحذيراً عند عدم الإشباع.
4. آلية إعادة تحميل الوحدات السريعة (HMR)
(1) مفهوم HMR
إعادة تحميل الوحدات السريعة تتيح استبدال كود الإضافة وقت التشغيل بدون إعادة تشغيل DSH بالكامل:
graph LR
CHANGE[تغيير الكود] --> DETECT[اكتشاف تغيير الملف]
DETECT --> DISPOSE[Fiber القديم disposing]
DISPOSE --> LOAD[الكود الجديد حُمّل]
LOAD --> ACTIVE[Fiber الجديد active]
(2) تفعيل HMR
pnpm dsh web --patch --watch
علامة --watch تُفعّل مراقبة الملفات؛ عند تغيير كود مصدر الإضافة، تُحفّز إعادة التحميل تلقائياً.
(3) سير HMR الكامل
- راقب نظام الملفات يكتشف تغيير
src/index.ts - Fiber الإضافة القديمة يدخل حالة disposing
- جميع دوال التنظيف تُنفّذ (ctx.effect، التنظيف التلقائي)
- الكود الجديد يُترجَم ويُحمّل
- Fiber جديد يُنشأ، يدخل pending/active
- الإضافات المعتمدة تُعاد تحميلها حسب الحاجة
(4) قيود HMR
| السيناريو | دعم HMR | ملاحظات |
|---|---|---|
| تعديل دالة execute | ✅ | منطق الأداة يُحدّث سريعاً |
| تعديل Config | ✅ | الإعدادات يُعاد التحقق منها |
| تعديل inject | ⚠️ | قد يُحفّز إعادة تحميل متسلسلة |
| تعديل name | ❌ | يتطلب إعادة تشغيل يدوية |
| تعديل إصدارات الاعتماديات | ❌ | يتطلب إعادة تشغيل يدوية |
(5) إعادة التحميل المتسلسلة
عندما تُعاد تحميل إضافة يعتمد عليها آخرون، تُعاد تحميل الإضافات المعتمدة أيضاً:
إعادة تحميل HMR لإضافة tools → my-tool (يعتمد على tools) يُعاد تحميله أيضاً
هذا يضمن تناسق الاعتماديات، لكن يمكن أن يُسبب "عواصف إعادة تحميل":
core إعادة تحميل → tools إعادة تحميل → my-tool إعادة تحميل → ... (سلسلة الاعتماديات بأكملها تُعاد تحميلها)
5. السياق المتداخل
(1) هرمية السياق
Cordis يدعم سياقات متداخلة — السياقات الأبناء ترث خدمات السياق الأب لكن يمكنها تجاوزها:
export function apply(ctx: Context) {
const childCtx = ctx.extend({
// تجاوز أو إضافة خدمات
})
childCtx.plugin({
name: 'child-plugin',
apply(innerCtx) {
// innerCtx يرث خدمات ctx
}
})
}
(2) قواعد توريث السياق
السياق الأب: { tools, llm, sessions }
السياق الابن: { tools(مُتجاوز), cache(مُضاف) }
السياق الابن يرى: { tools(النسخة المُتجاوزة), llm, sessions, cache }
- البحث عن الخدمات: افحص السياق الابن أولاً، ثم السياق الأب (نمط سلسلة النموذج الأولي)
- انتشار الأحداث: أحداث السياق الابن تصعد للسياق الأب
- تنظيف الموارد: تدمير السياق الابن لا يؤثر على السياق الأب
(3) حالات استخدام السياق المتداخل
| السيناريو | الوصف |
|---|---|
| عزل الجلسات | كل جلسة لها ctx مستقل |
| نطاق الطلب | كل طلب يُنشئ ctx مؤقت |
| الاختبار | إنشاء سياقات اختبار معزولة |
| متعدد الوكلاء | كل وكيل له مجموعة أدوات مستقلة |
(4) عمق التداخل
نظرياً غير محدود، لكن التداخل المفرط يؤثر على الأداء:
// ❌ عميق جداً
ctx.extend().extend().extend().extend()
// ✅ تداخل معتدل
const sessionCtx = ctx.extend({ session })
6. إعادة التحميل المُحفّزة بتغيير الإعدادات
(1) إعادة التحميل التلقائية
عندما يُعدّل المستخدمون إعدادات الإضافة في واجهة الويب، يُحفّز الإطار إعادة التحميل تلقائياً:
graph LR
UI[واجهة الويب تُعدّل الإعدادات] --> VALID[تحقق Schema]
VALID --> OLD[Fiber القديم disposing]
OLD --> NEW[Config جديد + Fiber جديد]
NEW --> ACTIVE[Fiber active]
(2) تحديث ساخن جزئي للإعدادات
بعض تغييرات الإعدادات لا تتطلب إعادة تحميل كاملة:
export function apply(ctx: Context) {
ctx.on('config/updated', (newConfig) => {
if (newConfig.debug !== ctx.config.debug) {
ctx.logger.level = newConfig.debug ? 'debug' : 'info'
}
})
}
(3) إعدادات تتطلب إعادة تحميل كاملة
تغييرات الإعدادات هذه تتطلب إعادة تحميل كاملة:
- تغيير قائمة inject
- تغيير رقم المنفذ
- تغيير مُعاملات تسجيل الخدمة
(4) إعادة التحميل والاستمرارية
تغييرات الإعدادات تُحفظ في cordis.yml بعد إعادة التحميل:
plugins:
my-plugin:
config:
debug: true # عُدّل بواسطة المستخدم عبر واجهة الويب، حُفظ تلقائياً
7. أفضل ممارسات HMR للتطوير
(1) أبقِ apply مُساواة
دالة apply يجب أن تكون مُساواة — استدعاءات متعددة تُنتج نتائج متسقة:
// ✅ مُساواة: كل apply يُسجّل نفس الأداة
export function apply(ctx: Context) {
ctx.tools.register(fileCountTool)
}
// ❌ غير مُساواة: apply يُراكم آثاراً جانبية
let counter = 0
export function apply(ctx: Context) {
counter++ // العداد يزداد بعد إعادة التحميل
}
(2) تجنب الحالة العامة
// ❌ حالة عامة: الحالة القديمة تبقى بعد إعادة تحميل HMR
const globalCache = new Map()
// ✅ حالة الإغلاق: كل apply يُنشئ حالة جديدة
export function apply(ctx: Context) {
const cache = new Map()
ctx.effect(() => () => cache.clear())
}
(3) أكمل دوال التنظيف
أثناء إعادة تحميل HMR، دوال تنظيف Fiber القديم يجب أن تنظّف جميع الموارد بالكامل:
export function apply(ctx: Context) {
const ws = new WebSocket('ws://localhost:8080')
// ✅ تسجيل التنظيف
ctx.effect(() => () => ws.close())
// ❌ نسيان التنظيف → الاتصال القديم يتسرب بعد إعادة التحميل
}
(4) سير عمل التطوير
حلقة تطوير HMR المُوصى بها من Alice:
# 1. بدء وضع التطوير مع HMR
pnpm dsh web --patch --watch
# 2. اكتب الكود بشكل طبيعي، يُعاد التحميل تلقائياً عند الحفظ
# مخرجات الطرفية:
# [hmr] file changed: src/index.ts
# [hmr] disposing my-plugin (old)
# [hmr] loading my-plugin (new)
# [my-plugin] plugin reloaded
# 3. تحقق من سجلات إعادة التحميل، أكد عدم أخطاء تنظيف
(5) نصائح تصحيح HMR
// أضف سجل تصحيح في بداية apply
export function apply(ctx: Context) {
ctx.logger.info('apply called at', new Date().toISOString())
// ...
}
// إذا رأيت apply استُدعي عدة مرات بشكل غير متوقع، فهذا يعني إعادة تحميل HMR متسلسلة
❓ أسئلة شائعة
[hmr] loading xxx (new) و [xxx] plugin reloaded. الفشل يُخرج رسائل خطأ.--watch للتطوير فقط.📖 ملخص
- رسم الاعتماديات يُبنى كـ DAG من تصريحات inject؛ الفرز الطوبولوجي يُحدد ترتيب التحميل
- إشباع الاعتماديات الديناميكي: الإضافات المنتظرة تُفعّل تلقائياً عند جاهزية الاعتماديات
- HMR يُفعّل عبر
--watch؛ تغييرات الكود تُعيد تحميل الإضافات تلقائياً - السياقات المتداخلة ترث خدمات الأب، تدعم التجاوز والعزل
- تغييرات الإعدادات تُحفّز إعادة التحميل تلقائياً؛ بعض التغييرات يمكن تحديثها ساخناً
- أفضل ممارسات HMR: apply مُساواة، تجنب الحالة العامة، أكمل دوال التنظيف
📝 تمارين
1. ⭐ أساسي: ابدأ pnpm dsh web --patch --watch، عدّل دالة apply لإضافة مُحمّلة (أضف سطر سجل)، احفظ ولاحظ رسائل إعادة تحميل HMR في الطرفية.
2. ⭐⭐ متوسط: أنشئ إضافتين بعلاقة اعتماد (A inject B). ابدأ HMR، عدّل كود B، ولاحظ إن كان A يُعاد تحميله بشكل متسلسل. ثم عدّل كود A فقط وتأكد أن B غير متأثر.
3. ⭐⭐⭐ تحدٍ: اكتب إضافة تستخدم متغيرات عامة (غير مُساواة). بعد إعادة تحميل HMR، لاحظ تغيرات قيم المتغيرات. ثم أعد البناء لحالة إغلاق (مُساواة) وتحقق من سلوك متسق بعد إعادة التحميل. سجّل مقارنة مخرجات الطرفية قبل وبعد إعادة البناء.