DeepSeek Harness: نظام الأحداث
آخر تحديث: 2026-08-31
الأحداث هي آلية Cordis الأساسية للتواصل слаб الاقتران بين الإضافات — الإضافات لا تستدعي بعضها مباشرة، بل تتعاون عبر بث الأحداث والاشتراك. أربعة أنماط أحداث تغطي جميع السيناريوهات من الإشعارات البسيطة إلى خطوط الأنابيب المعقدة.
📋 المتطلبات المسبقة: أكمل 14-inject.md، تفهم حقن الاعتماديات
1. ما ستتعلمه
- emit: بث أحداث عام
- bail: أحداث قابلة للمقاطعة
- serial: أحداث متسلسلة
- waterfall: أحداث تمرير متسلسل
- نطاقات الأحداث: session/agent/capability
- أحداث مخصصة وأمان الأنواع
2. emit: بث أحداث عام
(1) الاستخدام الأساسي
emit هو أبسط نمط حدث — بث إشعار؛ جميع المستمعين يتلقونه، قيم الإرجاع تُتجاهل:
// إصدار حدث
ctx.emit('session/created', { id: 'abc-123', user: 'Alice' })
// الاستماع للحدث
ctx.on('session/created', (data) => {
ctx.logger.info(`new session: ${data.id}`)
})
(2) الخصائص
| الخاصية | الوصف |
|---|---|
| بث | جميع المستمعين يُستدعون |
| بدون قيمة إرجاع | قيم إرجاع المستمعين تُتجاهل |
| بدون مقاطعة | المستمعون لا يمكنهم منع تنفيذ المستمعين اللاحقين |
| متسلسل | المستمعون يُنفذون بترتيب التسجيل |
(3) السيناريوهات النموذجية
- إشعارات:
session/created،plugin/loaded - تسجيل:
tool/executed،llm/request - إحصائيات:
request/completed،error/occurred
(4) ▶ مثال 4
ctx.on('session/created', (data) => {
ctx.logger.info(`logger: ${data.id}`)
})
ctx.on('session/created', (data) => {
ctx.metrics.inc('session_count')
})
ctx.on('session/created', (data) => {
ctx.cache.set(`session:${data.id}`, data)
})
ctx.emit('session/created', { id: 'abc', user: 'Alice' })
// جميع المستمعين الثلاثة يُنفذون
3. bail: أحداث قابلة للمقاطعة
(1) الاستخدام الأساسي
bail حدث قابل للمقاطعة — عندما يعيد مستمع قيمة غير undefined، المستمعون اللاحقون لا يُنفذون:
// المستمع يمكنه "اعتراض" الحدث
ctx.on('tool/beforeExecute', (data) => {
if (data.tool === 'shell' && data.params.command.includes('rm')) {
return { denied: true, reason: 'dangerous command' }
}
})
ctx.bail('tool/beforeExecute', { tool: 'shell', params: { command: 'rm -rf /' } })
// → يُعيد { denied: true, reason: 'dangerous command' }
// المستمعون اللاحقون لا يُنفذون
(2) الخصائص
| الخاصية | الوصف |
|---|---|
| قابل للمقاطعة | إعادة قيمة غير undefined تقطع الانتشار |
| دائرة قصيرة | أول مستمع يعيد قيمة يُنهي الانتشار |
| له قيمة إرجاع | استدعاء bail يُعيد القيمة المُعترضة |
| حساس للترتيب | المستمعون المُسجّلون أسبق يعترضون أولاً |
(3) السيناريوهات النموذجية
- فحوصات الصلاحيات:
tool/beforeExecute(رفض العمليات الخطرة) - تصفية المحتوى:
message/beforeSend(تصفية المحتوى الحساس) - التخطي الشرطي:
task/beforeRun(تخطي المهام غير المُطبقة)
(4) ▶ مثال 4
// إضافة سياسة الموافقة
ctx.on('tool/beforeExecute', (data) => {
const policy = getApprovalPolicy(data.tool)
if (policy === 'deny') {
return { denied: true, reason: `${data.tool} is denied by policy` }
}
if (policy === 'ask') {
return { pending: true, requiresApproval: true }
}
// إعادة undefined → لا تعترض، استمر في الانتشار
})
// إضافة Sandbox (بعد الموافقة)
ctx.on('tool/beforeExecute', (data) => {
if (!isInSandbox(data.params.cwd)) {
return { denied: true, reason: 'execution outside sandbox' }
}
})
(5) ▶ مثال 5
const result = ctx.bail('tool/beforeExecute', data)
if (result) {
// عُترض
ctx.logger.warn('tool execution denied:', result.reason)
} else {
// لم يُعترض، يمكن التنفيذ
await executeTool(data)
}
4. serial: أحداث متسلسلة
(1) الاستخدام الأساسي
serial يُنفذ المستمعين غير المتزامنين بالترتيب، كل واحد ينتظر اكتمال السابق:
ctx.on('session/initialized', async (data) => {
await loadUserPreferences(data.userId)
})
ctx.on('session/initialized', async (data) => {
await setupWorkspace(data.workspaceId)
})
ctx.on('session/initialized', async (data) => {
await warmCache(data.projectPath)
})
// ثلاثة مستمعين يُنفذون بالتسلسل
await ctx.serial('session/initialized', { userId: 'alice', workspaceId: 'ws-1' })
(2) الخصائص
| الخاصية | الوصف |
|---|---|
| متسلسل | المستمعون يُنفذون واحداً تلو الآخر بالترتيب |
| غير متزامن | يدعم مستمعين غير متزامنين |
| ينتظر الاكتمال | استدعاء serial ينتظر اكتمال جميع المستمعين |
| بدون مقاطعة | المستمعون لا يمكنهم منع التنفيذ اللاحق |
(3) السيناريوهات النموذجية
- تدفقات التهيئة:
session/initialized(تحميل إعدادات → إنشاء اتصالات → تسخين ذاكرة مؤقتة بالترتيب) - تدفقات التنظيف:
session/closing(حفظ بيانات → قطع اتصال → تنظيف ملفات مؤقتة بالترتيب) - خطوط أنابيب البيانات:
data/transform(تحويل البيانات خطوة بخطوة)
(4) الفرق عن emit
// emit: متوازي (لا ينتظر)
ctx.emit('session/created', data) // لا ينتظر اكتمال المستمعين
// serial: متسلسل (ينتظر)
await ctx.serial('session/created', data) // ينتظر اكتمال جميع المستمعين
5. waterfall: أحداث تمرير متسلسل
(1) الاستخدام الأساسي
waterfall يمرر قيمة إرجاع المستمع السابق للتالي، مُشكّلاً سلسلة:
ctx.on('message/format', async (data, next) => {
data.text = data.text.trim()
return next(data)
})
ctx.on('message/format', async (data, next) => {
data.text = data.text.replace(/\s+/g, ' ')
return next(data)
})
ctx.on('message/format', async (data, next) => {
data.text = data.text.substring(0, 4096)
return next(data)
})
const result = await ctx.waterfall('message/format', { text: ' hello world ' })
// result.text === 'hello world' (trim → collapse → truncate)
(2) الخصائص
| الخاصية | الوصف |
|---|---|
| تمرير متسلسل | مخرج السابق هو مدخل التالي |
| استدعاء next() | المستمعون يجب أن يستدعوا next() للتمرير للتالي |
| قابل للتعديل | كل مستمع يمكنه تعديل البيانات |
| قابل للمقاطعة | عدم استدعاء next() يقطع السلسلة |
(3) دالة next()
كل مستمع waterfall يستقبل مُعامل next:
ctx.on('event/name', async (data, next) => {
// تعديل البيانات
data.field = newValue
// استدعاء next للتمرير للمستمع التالي
return next(data)
// عدم استدعاء next → السلسلة مُقطعة، البيانات لا تُمرر أبعد
})
(4) السيناريوهات النموذجية
- معالجة الرسائل:
message/format(تنسيق → تصفية → اقتطاع) - خط أنابيب الطلبات:
request/process(مصادقة → تخويل → معالجة → تسجيل) - تحويل البيانات:
data/transform(تحليل → تحقق → توحيد → إخراج)
(5) مقاطعة السلسلة
ctx.on('request/process', async (data, next) => {
if (!data.authenticated) {
return { error: 'unauthenticated' } // لا تستدعي next، السلسلة مُقطعة
}
return next(data)
})
6. نطاقات الأحداث
(1) تقسيم النطاقات
أحداث Cordis مُقسّمة حسب النطاق، مفصولة بـ /:
session/created → نطاق الجلسة
session/destroyed → نطاق الجلسة
tool/beforeExecute → نطاق الأدوات (نطاق فرعي للقدرات)
tool/afterExecute → نطاق الأدوات
agent/initialized → نطاق الوكيل
llm/request → نطاق LLM
(2) نطاقات الأحداث الأساسية
| النطاق | البادئة | أحداث نموذجية |
|---|---|---|
| session | session/ |
created, destroyed, forked |
| agent | agent/ |
initialized, stopped, error |
| tool | tool/ |
beforeExecute, afterExecute, error |
| llm | llm/ |
request, response, stream, error |
| fiber | fiber/ |
created, active, disposing, disposed, errored |
| config | config/ |
updated, validated |
(3) غرض النطاقات
نطاقات الأحداث ليست سكراً نحوياً — الإطار يُحسّن بناءً على النطاقات:
- تصفية الأحداث: اشترك فقط في أحداث نطاق مُحدد
- عزل النطاق: أحداث نطاق الجلسة تنتشر داخل سياقات الجلسة
- تجميع التدقيق: اجمع سجلات الأحداث حسب النطاق
(4) الاشتراك في نطاق مُحدد
// اشترك في جميع أحداث نطاق الأدوات
ctx.on('tool/*', (eventName, data) => {
ctx.logger.info(`tool event: ${eventName}`)
})
7. أحداث مخصصة وأمان الأنواع
(1) التصريح بأحداث مخصصة
// events.ts
interface MyPluginEvents {
'my-plugin/data-loaded': { source: string; count: number }
'my-plugin/data-error': { source: string; error: Error }
}
declare module '@deepseek-ai/cordis' {
interface Events extends MyPluginEvents {}
}
(2) إصدار أحداث آمن الأنواع
ctx.emit('my-plugin/data-loaded', { source: 'api', count: 42 }) // ✅ نوع صحيح
ctx.emit('my-plugin/data-loaded', { wrong: true }) // ❌ خطأ نوع
(3) استماع آمن الأنواع
ctx.on('my-plugin/data-loaded', (data) => {
// data يُستنتج تلقائياً كـ { source: string; count: number }
ctx.logger.info(`loaded ${data.count} items from ${data.source}`)
})
(4) نمط تعريف أنواع الأحداث
// أحداث داخلية للإضافة
interface InternalEvents {
'cache/hit': { key: string; age: number }
'cache/miss': { key: string }
'cache/evicted': { key: string; reason: string }
}
// توسيع واجهة Events العامة
declare module '@deepseek-ai/cordis' {
interface Events extends InternalEvents {}
}
// تصدير لاستخدامه بإضافات أخرى
export type CacheEvents = InternalEvents
(5) اصطلاحات تسمية الأحداث
{نطاق}/{فعل-ماضي} ✅ session/created
{نطاق}/{فعل-مضارع} ✅ tool/execute (قيد التنفيذ)
{نطاق}/before{فعل} ✅ tool/beforeExecute (خطاف قبلي)
{نطاق}/after{فعل} ✅ tool/afterExecute (خطاف بعدي)
{نطاق}/{اسم-حالة} ✅ fiber/errored (حالة)
❓ أسئلة شائعة
ctx.off() للإزالة — يتطلب نفس مرجع الدالة.typescript ctx.logger.info('listeners:', ctx.listenerCount('tool/beforeExecute')) 📖 ملخص
- أربعة أنماط أحداث: emit (بث)، bail (قابل للمقاطعة)، serial (غير متزامن متسلسل)، waterfall (تمرير متسلسل)
- emit يناسب الإشعارات، bail يناسب الاعتراض، serial يناسب التهيئة المرتبة، waterfall يناسب خطوط أنابيب البيانات
- نطاقات الأحداث مفصولة بـ
/: session/agent/tool/llm/fiber/config - الأحداث المخصصة تحقق أمان الأنواع عبر
declare moduleتوسيع واجهة Events - استدعاء next() في waterfall أساسي لانتشار السلسلة؛ نسيان الاستدعاء يقطع السلسلة
📝 تمارين
1. ⭐ أساسي: اكتب إضافة تبث حدث my-plugin/loaded عبر emit، واستمع له في إضافة أخرى مع إخراج سجل.
2. ⭐⭐ متوسط: نفّذ معترض موافقة باستخدام bail على tool/beforeExecute لاعتراض أوامر shell التي تحتوي rm. الاختبار: ls يُنفذ بشكل طبيعي، rm -rf / يُعترض.
3. ⭐⭐⭐ تحدٍ: استخدم waterfall لتنفيذ خط أنابيب معالجة رسائل: trim → إزالة كلمات حساسة → اقتطاع نص مُفرط. كل خطوة مستمع مستقل؛ الخطوات الوسيطة يمكنها تعديل البيانات، والخطوة الأخيرة تُعيد النتيجة النهائية. اكتب اختبارات للتحقق من سلوك خط الأنابيب.