DeepSeek Harness: الإضافة الأولى
آخر تحديث: 2026-08-31
كتابة إضافتك الأولى هي الخطوة الأساسية لفهم DSH بعمق — القفز من "استخدام الإطار" إلى "توسيع الإطار." يبدأ هذا الدرس من إنشاء مشروع محلي ويُكمل تدريجياً إضافة Cordis قابلة للتحميل والتشغيل.
name و apply. عندما يستدعي الإطار apply(ctx)، تُسجّل الإضافة القدرات عبر ctx؛ عند إلغاء تحميل الإضافة، تُستعاد الموارد المُسجّلة على ctx تلقائياً.
📋 المتطلبات المسبقة: أكمل 08-community-plugins.md، ملمّ بنظرة عامة على نظام الإضافات
1. ما ستتعلمه
- إنشاء هيكل مشروع إضافة محلي
- جوهر الإضافة: وحدة TypeScript تصدّر دالة apply
- معنى
export const nameوexport function apply(ctx) - ثلاثة أشكال للإضافات: دالة، كائن، صنف
- التسجيل في cordis.yml والتحميل
- البدء والتحقق باستخدام
pnpm dsh web --patch
2. إنشاء مشروع محلي
(1) تهيئة دليل المشروع
كل إضافة DSH هي في الأساس حزمة Node.js. لنُعدّ واحدة من الصفر:
mkdir -p scratch-plugin/src
cd scratch-plugin
pnpm init
النتيجة package.json:
{
"name": "scratch-plugin",
"version": "0.1.0",
"main": "src/index.ts"
}
(2) ▶ مثال 2
pnpm add -D @deepseek-ai/cordis typescript
هيكل المشروع:
scratch-plugin/
├── src/
│ └── index.ts ← نقطة الدخول الرئيسية للإضافة
├── package.json
└── node_modules/
(3) إعداد TypeScript
أنشئ tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}
3. البروتوكول الأساسي للإضافة
(1) الإضافة الأدنى
إضافة DSH تحتاج فقط إلى استيفاء شرطين:
- تصدير سلسلة
name— المُعرّف الفريد للإضافة - تصدير دالة
apply— نقطة دخول الإضافة
import { Context } from '@deepseek-ai/cordis'
export const name = 'my-plugin'
export function apply(ctx: Context) {
ctx.logger.info('my-plugin loaded!')
}
هذه إضافة كاملة. بعد تحميل الإطار لها، يستدعي apply(ctx)، و ctx.logger.info() يُخرج سجلاً.
(2) ▶ مثال 2
graph LR
LOAD[الإطار يحمل الإضافة] --> CALL[استدعاء apply<br/>ctx هو "عالم" الإضافة]
CALL --> RUN[الإضافة تعمل]
UNLOAD[إلغاء تحميل الإضافة] --> CLEAN[الموارد المسجلة على ctx<br/>تُستعاد تلقائياً]
apply يُستدعى مرة واحدة فقط عند تحميل الإضافة. إذا احتاجت الإضافة العمل بشكل مستمر، سجّل مؤقتات ومستمعين إلخ داخل apply.
(3) غرض name
name هو هوية الإضافة، يُستخدم لـ:
- بادئة السجل:
[my-plugin] loaded! - نطاق الإعدادات:
plugins.my-plugin.config - تصريح الاعتماديات: الإضافات الأخرى تشير بالاسم
export const name = 'my-plugin'
⚠️ name يجب أن يكون فريداً عالمياً؛ تكرار اسم إضافة موجودة سيُسبب فشل التحميل.
4. ثلاثة أشكال للإضافات
Cordis يدعم ثلاثة أنماط لكتابة الإضافات. جميعها متكافئة وظيفياً؛ اختر حسب التعقيد:
(1) شكل الدالة (الأبسط)
import { Context } from '@deepseek-ai/cordis'
export const name = 'hello-fn'
export function apply(ctx: Context) {
ctx.logger.info('hello from function plugin')
}
حالة الاستخدام: أدوات بسيطة، تسجيل لمرة واحدة.
(2) شكل الكائن
import { Context } from '@deepseek-ai/cordis'
export default {
name: 'hello-obj',
apply(ctx: Context) {
ctx.logger.info('hello from object plugin')
}
}
حالة الاستخدام: إضافات متوسطة التعقيد تحتاج تصدير حقول متعددة (مثلاً Config، inject).
(3) شكل الصنف
import { Context } from '@deepseek-ai/cordis'
export default class HelloClass {
static name = 'hello-class'
constructor(private ctx: Context) {
ctx.logger.info('hello from class plugin')
}
}
حالة الاستخدام: إضافات معقدة تحتاج إدارة حالة داخلية أو تنفيذ أصناف خدمة أساسية.
(4) مقارنة الأشكال الثلاثة
| البُعد | الدالة | الكائن | الصنف |
|---|---|---|---|
| التعقيد | منخفض | متوسط | عالٍ |
| إدارة الحالة | عمليات الإغلاق | عمليات الإغلاق | خصائص النسخة |
| تصدير Config | تصدير منفصل | حقل الكائن | خاصية ساكنة |
| التوريث | غير مدعوم | غير مدعوم | مدعوم |
| الأفضل لـ | إضافات الأدوات | إضافات قياسية | إضافات الخدمات |
5. جعل الإضافة "تفعل شيئاً"
(1) تسجيل سجل دوري
import { Context } from '@deepseek-ai/cordis'
export const name = 'heartbeat'
export function apply(ctx: Context) {
ctx.setInterval(() => {
ctx.logger.info('heartbeat tick')
}, 60000)
}
المؤقتات المُسجّلة بـ ctx.setInterval تُزال تلقائياً عند إلغاء تحميل الإضافة — هذه هي الميزة الأساسية للتنظيف التلقائي في Cordis.
(2) الاستماع للأحداث
import { Context } from '@deepseek-ai/cordis'
export const name = 'welcome'
export function apply(ctx: Context) {
ctx.on('session/created', (session) => {
ctx.logger.info(`new session: ${session.id}`)
})
}
المستمعون المُسجّلون بـ ctx.on يُزالون أيضاً تلقائياً عند إلغاء التحميل.
(3) تسجيل أوامر
import { Context } from '@deepseek-ai/cordis'
export const name = 'hello-cmd'
export function apply(ctx: Context) {
ctx.command('hello <name:text>')
.action(({ session }, name) => {
return `Hello, ${name}!`
})
}
6. التسجيل في cordis.yml والتحميل
(1) إعدادات cordis.yml
سجّل الإضافة المحلية في cordis.yml في جذر مشروع DSH:
plugins:
my-plugin:
$insert: /absolute/path/to/scratch-plugin
$insert يحقن إضافة محلية في قائمة الإضافات. المسار يجب أن يكون مطلقاً.
(2) المسارات المطلقة مقابل النسبية
plugins:
my-plugin:
$insert: /home/alice/plugins/scratch-plugin # ✅ مسار مطلق
# $insert: ./scratch-plugin # ⚠️ مسار نسبي يعمل لكن غير مُوصى به
أسباب تفضيل المسارات المطلقة:
- حل المسار لا يتأثر بدليل العمل
- سلوك متسق عبر طرق البدء المختلفة
- واضح وغير لبس أثناء التصحيح
(3) البدء والتحميل
pnpm dsh web --patch
المعامل --patch يُخبر DSH بقراءة $insert وعمليات التجاوز الأخرى من cordis.yml، وتركيب الإضافات المحلية فوق الإعدادات الافتراضية.
(4) التحقق من التحميل
بعد البدء، تحقق من سجل الطرفية:
[my-plugin] loaded!
أو ابحث عن my-plugin في قائمة الإضافات بواجهة الويب.
▶ مثال 7: إضافة الترحيب
اجمع المعرفة أعلاه في مثال كامل:
import { Context } from '@deepseek-ai/cordis'
export const name = 'greeter'
export function apply(ctx: Context) {
ctx.logger.info('greeter plugin loaded')
ctx.on('session/created', (session) => {
ctx.logger.info(`session started: ${session.id}`)
})
ctx.setInterval(() => {
ctx.logger.info('greeter heartbeat')
}, 300000)
}
إعدادات cordis.yml:
plugins:
greeter:
$insert: /home/alice/projects/scratch-plugin
ابدأ وتحقق:
pnpm dsh web --patch
# [greeter] greeter plugin loaded
# [greeter] session started: abc-123
❓ أسئلة شائعة
my-plugin اسم صالح. نُوصي بأحرف صغيرة وواصلات؛ تجنب camelCase.async function apply(ctx) صالحة تماماً؛ الإطار سينتظر اكتمال apply غير المتزامنة. ملاحظة: حتى تكتمل apply غير المتزامنة، تكون الإضافة في حالة انتظار والإضافات المعتمدة عليها لن تُحمّل.[error] plugin not found: /wrong/path/to/plugin.typescript export const name = 'my-plugin' export const Config = Schema.object({ ... }) export function apply(ctx: Context) { ... } pnpm dsh web --patch لا يدعم إعادة التحميل السريع. أثناء التطوير، يمكنك استخدام --dump-config للتحقق من الإعدادات، أو راجع 18-hot-reload.md لآليات HMR.📖 ملخص
- الإضافة هي وحدة TypeScript تصدّر
name+apply(ctx)؛ الإطار يستدعي apply عند التحميل - عبر
ctx، سجّل مؤقتات ومستمعي أحداث وأوامر إلخ؛ جميعها تُستعاد تلقائياً عند إلغاء التحميل - ثلاثة أشكال للإضافات: دالة (الأبسط)، كائن (قياسي)، صنف (معقد/يحتاج توريث)
- استخدم
$insert+ مسار مطلق فيcordis.ymlلتسجيل الإضافات المحلية pnpm dsh web --patchيبدأ ويحمّل إعدادات التراكب- name يجب أن يكون فريداً عالمياً؛ الأسماء المكررة تُسبب فشل التحميل
📝 تمارين
1. ⭐ أساسي: اتبع خطوات هذا الدرس لإنشاء إضافة hello-world بشكل دالة تُخرج السجل "hello world!" في apply، سجّلها في cordis.yml، وتحقق بالبدء.
2. ⭐⭐ متوسط: أعد كتابة إضافة hello-world بشكلي الكائن والصنف، حمّل كل منهما على حدة، وتأكد أن الثلاثة يُنتجون نفس المخرجات.
3. ⭐⭐⭐ تحدٍ: اكتب إضافة uptime تسجّل وقت تحميل الإضافة وتُخرج "Running for N minutes" كل دقيقة عبر ctx.setInterval. فكّر: هل يجب إعادة ضبط المؤقت إذا أُلغي تحميل الإضافة وأُعيد تحميلها؟ لماذا؟