DeepSeek Harness: الإضافة الأولى

آخر تحديث: 2026-08-31

كتابة إضافتك الأولى هي الخطوة الأساسية لفهم DSH بعمق — القفز من "استخدام الإطار" إلى "توسيع الإطار." يبدأ هذا الدرس من إنشاء مشروع محلي ويُكمل تدريجياً إضافة Cordis قابلة للتحميل والتشغيل.

💡 نصيحة: البروتوكول الأساسي لإضافة DSH بسيط — فقط صدّر name و apply. عندما يستدعي الإطار apply(ctx)، تُسجّل الإضافة القدرات عبر ctx؛ عند إلغاء تحميل الإضافة، تُستعاد الموارد المُسجّلة على ctx تلقائياً.

📋 المتطلبات المسبقة: أكمل 08-community-plugins.md، ملمّ بنظرة عامة على نظام الإضافات

1. ما ستتعلمه

بنية دليل المكون الإضافي


2. إنشاء مشروع محلي

(1) تهيئة دليل المشروع

كل إضافة DSH هي في الأساس حزمة Node.js. لنُعدّ واحدة من الصفر:

BASH
mkdir -p scratch-plugin/src
cd scratch-plugin
pnpm init

النتيجة package.json:

JSON
{
  "name": "scratch-plugin",
  "version": "0.1.0",
  "main": "src/index.ts"
}

(2) ▶ مثال 2

BASH
pnpm add -D @deepseek-ai/cordis typescript

هيكل المشروع:

TEXT 📖 للعرض فقط
scratch-plugin/
├── src/
│   └── index.ts      ← نقطة الدخول الرئيسية للإضافة
├── package.json
└── node_modules/

(3) إعداد TypeScript

أنشئ tsconfig.json:

JSON
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

3. البروتوكول الأساسي للإضافة

(1) الإضافة الأدنى

إضافة DSH تحتاج فقط إلى استيفاء شرطين:

  1. تصدير سلسلة name — المُعرّف الفريد للإضافة
  2. تصدير دالة apply — نقطة دخول الإضافة
TYPESCRIPT
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

100%
graph LR
    LOAD[الإطار يحمل الإضافة] --> CALL[استدعاء apply<br/>ctx هو "عالم" الإضافة]
    CALL --> RUN[الإضافة تعمل]
    UNLOAD[إلغاء تحميل الإضافة] --> CLEAN[الموارد المسجلة على ctx<br/>تُستعاد تلقائياً]

apply يُستدعى مرة واحدة فقط عند تحميل الإضافة. إذا احتاجت الإضافة العمل بشكل مستمر، سجّل مؤقتات ومستمعين إلخ داخل apply.

(3) غرض name

name هو هوية الإضافة، يُستخدم لـ:

TYPESCRIPT
export const name = 'my-plugin'

⚠️ name يجب أن يكون فريداً عالمياً؛ تكرار اسم إضافة موجودة سيُسبب فشل التحميل.


4. ثلاثة أشكال للإضافات

Cordis يدعم ثلاثة أنماط لكتابة الإضافات. جميعها متكافئة وظيفياً؛ اختر حسب التعقيد:

(1) شكل الدالة (الأبسط)

TYPESCRIPT
import { Context } from '@deepseek-ai/cordis'

export const name = 'hello-fn'

export function apply(ctx: Context) {
  ctx.logger.info('hello from function plugin')
}

حالة الاستخدام: أدوات بسيطة، تسجيل لمرة واحدة.

(2) شكل الكائن

TYPESCRIPT
import { Context } from '@deepseek-ai/cordis'

export default {
  name: 'hello-obj',
  apply(ctx: Context) {
    ctx.logger.info('hello from object plugin')
  }
}

حالة الاستخدام: إضافات متوسطة التعقيد تحتاج تصدير حقول متعددة (مثلاً Config، inject).

(3) شكل الصنف

TYPESCRIPT
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) تسجيل سجل دوري

TYPESCRIPT
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) الاستماع للأحداث

TYPESCRIPT
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) تسجيل أوامر

TYPESCRIPT
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:

YAML
plugins:
  my-plugin:
    $insert: /absolute/path/to/scratch-plugin

$insert يحقن إضافة محلية في قائمة الإضافات. المسار يجب أن يكون مطلقاً.

(2) المسارات المطلقة مقابل النسبية

YAML
plugins:
  my-plugin:
    $insert: /home/alice/plugins/scratch-plugin   # ✅ مسار مطلق
    # $insert: ./scratch-plugin                    # ⚠️ مسار نسبي يعمل لكن غير مُوصى به

أسباب تفضيل المسارات المطلقة:

(3) البدء والتحميل

BASH
pnpm dsh web --patch

المعامل --patch يُخبر DSH بقراءة $insert وعمليات التجاوز الأخرى من cordis.yml، وتركيب الإضافات المحلية فوق الإعدادات الافتراضية.

(4) التحقق من التحميل

بعد البدء، تحقق من سجل الطرفية:

TEXT 📖 للعرض فقط
[my-plugin] loaded!

أو ابحث عن my-plugin في قائمة الإضافات بواجهة الويب.


▶ مثال 7: إضافة الترحيب

اجمع المعرفة أعلاه في مثال كامل:

TYPESCRIPT
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:

YAML
plugins:
  greeter:
    $insert: /home/alice/projects/scratch-plugin

ابدأ وتحقق:

BASH
pnpm dsh web --patch
# [greeter] greeter plugin loaded
# [greeter] session started: abc-123

❓ أسئلة شائعة

س هل يمكن أن يحتوي اسم الإضافة على واصلات؟
ج نعم، my-plugin اسم صالح. نُوصي بأحرف صغيرة وواصلات؛ تجنب camelCase.
س هل يمكن أن تكون دالة apply غير متزامنة؟
ج نعم. async function apply(ctx) صالحة تماماً؛ الإطار سينتظر اكتمال apply غير المتزامنة. ملاحظة: حتى تكتمل apply غير المتزامنة، تكون الإضافة في حالة انتظار والإضافات المعتمدة عليها لن تُحمّل.
س ماذا يحدث إذا كان مسار $insert خاطئاً؟
ج سيُبلغ DSH عن خطأ ويتخطى الإضافة عند البدء؛ لن يُعطّل التطبيق بالكامل. ستعرض الطرفية شيئاً مثل [error] plugin not found: /wrong/path/to/plugin.
س كيف أصدّر Config من إضافة بشكل دالة؟
ج صدّرها بشكل منفصل: typescript export const name = 'my-plugin' export const Config = Schema.object({ ... }) export function apply(ctx: Context) { ... }
س هل يمكن تحميل نفس الإضافة عدة مرات؟
ج ليس افتراضياً — name فريد عالمياً. إذا احتجت نسخاً متعددة، استخدم إعدادات العزل لإنشاء نطاقات معزولة (راجع 20-scope.md).
س هل أحتاج إعادة التشغيل كل مرة أغيّر الكود أثناء التطوير المحلي؟
ج نعم، pnpm dsh web --patch لا يدعم إعادة التحميل السريع. أثناء التطوير، يمكنك استخدام --dump-config للتحقق من الإعدادات، أو راجع 18-hot-reload.md لآليات HMR.

📖 ملخص


📝 تمارين

1. ⭐ أساسي: اتبع خطوات هذا الدرس لإنشاء إضافة hello-world بشكل دالة تُخرج السجل "hello world!" في apply، سجّلها في cordis.yml، وتحقق بالبدء.

2. ⭐⭐ متوسط: أعد كتابة إضافة hello-world بشكلي الكائن والصنف، حمّل كل منهما على حدة، وتأكد أن الثلاثة يُنتجون نفس المخرجات.

3. ⭐⭐⭐ تحدٍ: اكتب إضافة uptime تسجّل وقت تحميل الإضافة وتُخرج "Running for N minutes" كل دقيقة عبر ctx.setInterval. فكّر: هل يجب إعادة ضبط المؤقت إذا أُلغي تحميل الإضافة وأُعيد تحميلها؟ لماذا؟

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%