SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-08-21

آموزش ساخت پلاگین اختصاصی برای DeepSeek Harness

با دنبال کردن این راهنما از یک پوشه خالی، پلاگین dsh خود را بسازید. فیلدهای ضروری در package.json، نحوه تنظیم patch فایل و دو hook اصلی برای اتصال ابزارها را بیاموزید.

پلاگین dsh دقیقاً چیست

یک پلاگین dsh یک پکیج npm است که یک تابع apply را export می‌کند و یک فایل YAML کوچک به همراه دارد که به DeepSeek Harness می‌گوید آن را بارگذاری کند. هیچ SDK جداگانه‌ای برای یادگیری پیش‌نیاز وجود ندارد. dsh یک اپلیکیشن Cordis است و عبارت «همه چیز یک پلاگین است» به معنای واقعی کلمه درست است: رجیستری ابزارها، حلقه عامل (agent loop)، ذخیره‌ساز نشست (session store) و وب‌سرور، همگی ردیف‌هایی در همان درخت پلاگینی هستند که پکیج شما به آن می‌پیوندد.

Cordis یک چارچوب ترکیب کلی است که به‌طور مستقل ساخته شده و سال‌ها به عنوان پایه چارچوب چت‌بات Koishi استفاده شده است. این چارچوب بارگذاری و تخلیه (unloading) را مدیریت کرده و وابستگی‌های بین پلاگین‌ها را حل می‌کند. Cordis هیچ دانشی درباره عامل‌ها (agents) ندارد. هر چیزی که ماهیت عامل دارد، از پکیج‌های harness که روی آن قرار گرفته‌اند ناشی می‌شود؛ به همین دلیل است که ساختار پلاگین در ادامه بسیار کوچک به نظر می‌رسد. بیشتر آنچه دریافت می‌کنید، به ارث رسیده است.

یک پلاگین دو نیمه دارد. نیمه میزبان (host) در Node اجرا می‌شود، ابزارها و شنونده‌های رویداد (event listeners) را ثبت می‌کند و می‌تواند سرویس‌های خاص خود را ارائه دهد. نیمه مرورگر در داخل Web UI اجرا شده و اسلات‌های رابط کاربری را ثبت می‌کند. اولین پلاگین تقریباً همیشه فقط شامل بخش میزبان است، بنابراین تا زمانی که به نیمه مرورگر نیاز پیدا نکرده‌اید، آن را اختیاری در نظر بگیرید.

این راهنما بر اساس نسخه @deepseek-ai/dsh 0.1.0-rc.7 نوشته شده است که تگ npm latest در تاریخ 19 اوت 2026 است. dsh یک نسخه پیش‌نمایش توسعه‌دهنده است و فایل README آن اعلام کرده که تغییرات ناسازگار (breaking changes) در آینده وجود خواهد داشت. تمام نام‌های کلیدی در ادامه، از مستندات بالادستی و مخزن در آن تاریخ خوانده شده‌اند. پیش از آنکه به هر کدام وابسته شوید، دوباره آن‌ها را بررسی کنید، زیرا API پیش‌نمایش، فیلدها را بین release candidateها تغییر نام می‌دهد. اگر harness هنوز در حال اجرا نیست، ابتدا آن را با DeepSeek Harness روی یک VPS و کلید API و پیکربندی مدل dsh راه‌اندازی کنید و سپس به اینجا بازگردید.

پیش از بسته‌بندی، یک فایل آزمایشی را بارگذاری کنید

بسته‌بندی در همان ابتدا، روشی کند برای یادگیری این فرآیند است. یک فایل تکی بارگذاری کنید، اطمینان حاصل کنید که runtime کد شما را فراخوانی می‌کند، و سپس آن را بسته‌بندی کنید.

یک پوشه خارج از مسیر checkout ابزار harness ایجاد کرده و یک فایل در آن قرار دهید.

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

export const name = 'hello-plugin'

export function apply(ctx: Context) {
  console.log('[hello-plugin] plugin loaded')
}

export const name متادیتایی است که برای برچسب‌گذاری پلاگین در عیب‌یابی استفاده می‌شود. apply کل قرارداد است: Cordis آن را یک‌بار فراخوانی کرده و یک context محدود به پلاگین شما را ارسال می‌کند. هر چیزی که در این context ثبت کنید، هنگام dispose شدن پلاگین، به‌طور خودکار برای شما پاکسازی می‌شود.

در کنار آن، cordis.yml را بنویسید.

- insert:
    - id: hello
      name: '/absolute/path/to/scratch-plugin/hello.ts'

اکنون یک profile را با لایه‌گذاری آن فایل در بالاترین سطح اجرا کنید.

dsh web --patch ./scratch-plugin/cordis.yml

اگر dsh در PATH شما نیست، npx @deepseek-ai/dsh web --patch ./scratch-plugin/cordis.yml همان کار را انجام می‌دهد. مسیر npx ممکن است یک نسخه قدیمی‌تر (release candidate) کش‌شده را به جای نسخه‌ای که در این راهنما توصیف شده است به شما بدهد؛ بنابراین اگر harness یک فلگ مستند را نپذیرفت، پیش از آنکه به فایل خود شک کنید، اصلاحات مربوط به نصب و خطاهای نسخه dsh را بررسی کنید. شما باید [hello-plugin] plugin loaded را در ترمینالی که dsh را اجرا کرده است ببینید. اگر چیزی ظاهر نشد، آن ردیف resolve نشده است.

فیلد name یک نام بسته npm یا یک مسیر فایل‌سیستم می‌پذیرد و مستندات upstream بیان می‌کنند که مسیر باید مطلق (absolute) باشد. یک ./hello.ts نسبی، اولین چیزی است که باید هنگام عدم خروجی پلاگین آزمایشی بررسی کنید. مورد دوم، پسوند فایل است. حلقه مستندشده به صورت pnpm dsh web --patch ... از یک clone مخزن harness اجرا می‌شود، جایی که ورودی‌های TypeScript از طریق tsx بارگذاری می‌شوند. اگر dsh خود را از npm دریافت کرده‌اید، ردیف را به یک فایل JavaScript ساده اشاره دهید یا ابتدا فایل را build کنید.

--patch یک فلگ راه‌انداز (launcher flag) است و overlay آن در آخرین مرحله، پس از تمام bundleها و پس از patch پروفایل خودتان اعمال می‌شود. بنابراین یک overlay آزمایشی همیشه اولویت دارد، که دقیقاً همان چیزی است که هنگام توسعه و تکرار به آن نیاز دارید.

نوشتن کوچک‌ترین ابزاری که کار مفیدی انجام می‌دهد

یک خط لاگ ثابت می‌کند که پلاگین بارگذاری شده است. یک ابزار ثابت می‌کند که پلاگین بخشی از agent است.

import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'greet-tool'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'greet',
    description: 'Greet someone by name.',
    parameters: {
      name: { type: 'string', required: true, description: 'The name to greet' },
    },
    output: {
      schema: { type: 'string' },
      render: (_args, value) => [{ type: 'text', text: value }],
    },
    async execute(args) {
      return `Hello, ${args.name}!`
    },
  }))
}

export const inject = ['tools'] خطی است که افراد از قلم می‌اندازند. ورودی‌ها در پیکربندی Cordis به‌طور هم‌زمان شروع می‌شوند، بنابراین موقعیت یک ردیف در فایل، هیچ تضمینی برای ترتیب بارگذاری ایجاد نمی‌کند. ترتیب‌بندی از وابستگی‌های اعلام‌شده ناشی می‌شود. inject به Cordis می‌گوید تا زمانی که ctx.tools وجود نداشته باشد، منتظر بماند و سپس apply شما را فراخوانی کند؛ بدون این دستور، کد شما ممکن است در لحظه‌ای اجرا شود که registry برای ثبت در دسترس نیست.

باقی شیء، قراردادی است که مدل مشاهده می‌کند. parameters طرحوارهٔ آرگومان‌ها است و execute آرگومان‌هایی را دریافت می‌کند که قبلاً بر اساس آن طرحواره تجزیه شده‌اند. output.schema مقداری را که execute بازمی‌گرداند توصیف می‌کند، در حالی که render آن مقدار را به بلوک‌های محتوایی که مدل می‌خواند تبدیل می‌کند. جدا نگه‌داشتن این دو مورد باعث می‌شود رابط کاربری یک چیز را نمایش دهد در حالی که مدل چیز دیگری را می‌خواند.

پروفایل را شروع کنید و از دستیار بخواهید به کسی با نام سلام کند. پاسخ از طریق execute شما بازمی‌گردد. ثبت از طریق ctx قابل‌برگشت است، بنابراین دور انداختن (dispose) پلاگین، ابزار را برای شما لغو ثبت می‌کند. برای هر چیزی که Cordis نمی‌تواند دربارهٔ آن بداند، مانند یک socket یا یک file handle، ctx.effect() را فراخوانی کنید و یک disposer به آن بدهید.

دو نقطه توسعه که اولین پلاگین واقعاً با آن‌ها درگیر می‌شود

فهرست کامل نقاط اتصال (seams) طولانی است. دو مورد از آن‌ها تقریباً هر پلاگین اولیه‌ای را پوشش می‌دهند.

رویدادهای گفتگو (Conversation events) جریانی پایدار و ثبت‌شده هستند. نام‌های آن‌ها session/event، turn/start، turn/end، step/start، step/end، user/message، assistant/message، assistant/chunk، tool/call و tool/result است. شما یک شنونده (listener) معمولی به آن‌ها متصل می‌کنید.

ctx.on('tool/call', (payload) => {
  console.log('[my-plugin] tool/call', JSON.stringify(payload))
})

محتوای payload را یک‌بار چاپ کرده و آن را بخوانید. نام فیلدهای payload را از هیچ راهنمایی، از جمله همین راهنما، کپی نکنید؛ زیرا ساختار payload بخشی از یک API پیش‌نمایش است که بیشترین تغییرات را دارد.

دومین نقطه توسعه، آبشار (waterfall) است. رویدادهای agent/pre-step، agent/request، agent/request-error، llm/stream و tools/* از نوع آبشار هستند و شنوندهٔ آبشار امضای متفاوتی دارد. این شنونده یک callback به نام next دریافت می‌کند و زنجیره تنها در صورتی ادامه می‌یابد که آن را فراخوانی کنید.

ctx.on('agent/request', async (payload, next) => {
  const startedAt = Date.now()
  const downstream = await next()
  console.log('[my-plugin] model request took', Date.now() - startedAt, 'ms')
  return downstream
})

اگر await next() را فراموش کنید، هیچ هوکی (hook) اضافه نکرده‌اید. شما فراخوانی مدل را با «هیچ» جایگزین کرده‌اید و عامل (agent) در همان‌جا متوقف می‌شود، زیرا اتصال کوتاه (short circuiting) رفتار طراحی‌شده برای یک پلاگین دروازه (gateway) است که عمداً یک درخواست را رد می‌کند. همین یک تفاوت، عامل اصلی سردرگمی در اولین پلاگین‌هاست. فراخوانی next() را پیش از نوشتن هر کد دیگری در اطراف آن، بنویسید.

agent/request خودِ فراخوانی مدل را در بر می‌گیرد. payload آن شامل عاملی است که فراخوانی را انجام می‌دهد، شماره نوبت باز، مرحله‌ای که درخواست به آن تعلق دارد و سیگنال توقف (abort signal) همان نوبت؛ که همین ویژگی، آن را به نقطه اتصال مناسبی برای ثبت‌کننده درخواست (request logger) یا محدودکننده نرخ (rate limiter) تبدیل می‌کند. آبشارهای tools/* در یک لایه پایین‌تر، ساختار مشابهی دارند. tools/pre-execute اجازه می‌دهد، رد می‌کند یا پیش از ارسال، درخواست تأیید می‌کند. tools/execute عمل ارسال را در بر می‌گیرد. tools/post-execute می‌تواند نتیجه نرمال‌شده را جایگزین یا مسدود کند. tools/result تنها نتیجه نهایی و تثبیت‌شده را مشاهده می‌کند.

بسته‌بندی آن به عنوان یک پکیج قابل نصب برای دیگران

یک بسته (bundle)، یک پکیج npm است که در package.json آن یک فیلد dsh.bundle تعریف شده که به فایل patch شما اشاره می‌کند. همین تعریف، تمام تفاوت بین یک فایل پیش‌نویس (scratch file) و چیزی است که قابلیت نصب دارد.

{
  "name": "dsh-plugin-hello",
  "version": "0.1.0",
  "type": "module",
  "main": "lib/index.js",
  "files": ["lib", "cordis.patch.yml", "README.md", "LICENSE"],
  "engines": { "node": "^22.19 || >=24", "dsh": ">=0.1.0-rc.6" },
  "dsh": { "bundle": { "patch": "./cordis.patch.yml" } },
  "keywords": ["dsh-plugin", "deepseek-harness"],
  "scripts": { "build": "tsdown", "prepare": "pnpm run build" },
  "exports": {
    ".": { "types": "./lib/index.d.ts", "default": "./lib/index.js" },
    "./cordis.patch.yml": "./cordis.patch.yml",
    "./package.json": "./package.json"
  }
}

فایل cordis.patch.yml که در کنار آن قرار دارد، کوتاه است.

- insert:
    - id: dsh-plugin-hello
      name: dsh-plugin-hello

ردیف name نام پکیج است، بنابراین این دو رشته باید با هم مطابقت داشته باشند. ردیف id مقصدی است که لایه‌های بعدی هنگام override کردن پیکربندی شما توسط کاربر، آن را هدف قرار می‌دهند؛ پس نامی پایدار انتخاب کنید و هرگز آن را برای پلاگین دیگری مجدداً به کار نبرید.

در files حتماً باید cordis.patch.yml ذکر شود. اگر آن را حذف کنید، tarball منتشرشده حاوی یک dsh.bundle.patch خواهد بود که به فایلی اشاره می‌کند که هرگز بسته‌بندی نشده است؛ در نتیجه پکیج نصب می‌شود اما هیچ تغییری در درخت (tree) ایجاد نمی‌کند.

آن را از دایرکتوری حاوی پوشه پلاگین خود، در یک پروفایل نصب کنید.

dsh plugin --profile demo add ./dsh-plugin-hello
dsh --profile demo --dump-config
dsh --profile demo

دستور dsh plugin --profile <name> باقی آرگومان‌های خود را به pnpm در داخل آن دایرکتوری پروفایل ارسال می‌کند، بنابراین add و remove دقیقاً مشابه رفتار pnpm عمل می‌کنند. برای حذف نصب از dsh plugin --profile demo remove dsh-plugin-hello استفاده کنید. پروفایل‌های web و headless در اولین استفاده از روی قالب‌های پیش‌فرض (shipped templates) ساخته می‌شوند و هر نام پروفایل دیگری باید از طریق dsh plugin ایجاد شود.

چرا ردیف شما در درخت ترکیب‌شده وجود ندارد

فرآیند ترکیب (Composition) با یک لیست ورودی خالی شروع می‌شود و لایه‌ها را به ترتیب مشخصی روی هم قرار می‌دهد. هر بسته (bundle) که در dsh.profile.bundles پروفایل نام برده شده، به همان ترتیبی که ذکر شده است، اضافه می‌شود. سپس cordis.patch.yml خودِ پروفایل، بعد از آن $DSH_HOME/cordis.patch.yml و در نهایت هرگونه overlay از نوع --patch که از طریق خط فرمان اعمال شده باشد. لایه‌های بعدی، ردیف‌های قبلی را بر اساس id جایگزین می‌کنند.

پروفایل‌ها در مسیر $DSH_HOME/profiles/<name> قرار دارند. هر دایرکتوری پروفایل شامل یک package.json است که مانیفست dsh.profile را به همراه لیست مرتب‌شده bundles و همچنین فایل patch کاربر در خود جای داده است. نام بسته‌ها ابتدا از محل نصب dsh و سپس از node_modules پروفایل حل (resolve) می‌شوند؛ این همان محلی است که pnpm یک پلاگین خارج از درخت (out of tree) را در آن قرار می‌دهد.

دستور dsh --profile demo --dump-config درخت کاملاً ترکیب‌شده را بدون اجرای هیچ چیزی چاپ می‌کند و خروجی آن، مرز تفکیک برای عیب‌یابی است. اگر id ردیف شما در خروجی وجود ندارد، مشکل از ترکیب است: نامی که حل نمی‌شود یا فایل patchای که هرگز بسته‌بندی نشده است. اگر ردیف موجود است اما اتفاقی نمی‌افتد، مشکل از کد شماست. ابتدا به این پرسش پاسخ دهید تا از بخش بزرگی از حدس و گمان‌ها عبور کنید.

محل واقعی بروز خطاهای بارگذاری

خطایی که در داخل apply رخ می‌دهد، آشکار است. پردازش با همان استثنا متوقف می‌شود و شما یک stack trace دریافت می‌کنید که دقیقاً به خط کد شما اشاره دارد.

خطاهای مربوط به resolution (حل وابستگی‌ها) بی‌سروصدا هستند. لودر به جای کرش کردن، ماژولی را که قادر به حل آن نیست از طریق Cordis logger گزارش می‌کند. آموزش‌های بالادستی هشدار می‌دهند که این پیام‌ها ممکن است در زمان راه‌اندازی گم شوند، زیرا پیش از متصل شدن console exporterها صادر می‌شوند. بنابراین، یک غلط تایپی در مسیر (path) دقیقاً مشابه پلاگینی به نظر می‌رسد که بارگذاری شده اما کاری انجام نمی‌دهد؛ به همین دلیل است که اجرای بررسی --dump-config در بالا، پیش از خواندن هرگونه کد، ارزشمند است.

در حین توسعه، یک console.log به عنوان اولین دستور در apply نگه دارید. نبود آن به شما می‌گوید که با کدام نیمه از مشکل مواجه هستید و حذف آن در آینده هیچ هزینه‌ای ندارد. روی سرور، به جای اجرای harness تحت یک service manager، آن را در foreground اجرا کنید تا خروجی لودر به جای ژورنالی که باید برای خواندنش به سراغ آن بروید، مستقیماً به ترمینال شما برسد.

تکرار بدون راه‌اندازی مجدد کل سیستم

پاسخ صادقانه برای بخش سمت سرور در حال حاضر این است که باید آن را مجدداً راه‌اندازی کنید. بسته نرم‌افزاری وب‌اپلیکیشن با قابلیت hot module reload غیرفعال عرضه می‌شود و در فایل مربوطه یادداشتی وجود دارد که پس از تست چرخه حیات reload، این قابلیت دوباره فعال خواهد شد. زنجیره reload سمت کلاینت همیشه mount است اما تا زمانی که یک watcher برای بازسازی، بسته‌های کلاینت را بازنویسی نکند، غیرفعال می‌ماند؛ بنابراین برای بخش Node شما نیز کاری انجام نمی‌دهد.

به‌جای جستجو برای قابلیتی که هنوز وجود ندارد، راه‌اندازی مجدد را کم‌هزینه کنید. پلاگین را در یک فایل نگه دارید. آن را با --patch بارگذاری کنید و از نصب آن در یک profile خودداری کنید تا هیچ مرحله build یا pnpm بین ویرایش و اجرا قرار نگیرد. همه چیز را از طریق ctx ثبت کنید تا راه‌اندازی مجدد باعث باقی ماندن ابزارهای تکراری یا listenerهای قدیمی نشود. هر چیزی را که خودتان تخصیص می‌دهید در ctx.effect() با یک disposer واقعی محصور کنید، زیرا نشانه معمول نبود disposer، شکست خوردن اجرای دوم به دلیل اشغال بودن پورت توسط اجرای اول است.

اگر به‌جای لپ‌تاپ خود، روی یک harness در حال اجرا در سرور توسعه می‌دهید، هیچ‌کدام از موارد بالا تغییر نمی‌کند، اما binding رابط کاربری وب اهمیت پیدا می‌کند. اتصال loopback روی پورت 3080 توضیح می‌دهد که چرا صفحه به‌طور خودکار باز نمی‌شود و در این مورد چه باید کرد.

بخش مرورگر و میزان اعتماد به آن

این بخش را تنها زمانی اضافه کنید که پلاگین شما به رابط کاربری اختصاصی نیاز دارد. این مورد در همان فیلد dsh که باندل در آن تعریف شده، اعلام می‌شود.

{
  "dsh": {
    "client": {
      "platform": "web",
      "inject": [],
      "external": [],
      "immediately": false
    }
  },
  "exports": {
    ".": "./src/index.ts",
    "./client": "./src/client/apply.ts",
    "./package.json": "./package.json"
  }
}

فیلد "platform": "web" الزامی است و اگر بسته فاقد خروجی ./client باشد، اسکنر خطا می‌دهد؛ بنابراین نقشه خروجی (export map) بخشی از مانیفست است و نه یک قابلیت جانبی. ورودی کلاینت، Cordis Context را که با نوع runtime کلاینت گسترش یافته دریافت می‌کند و تمام ثبت‌ها در داخل apply و از طریق ctx.slots.register انجام می‌شوند. اثرات جانبی در سطح ماژول در آنجا مجاز نیستند.

import type { Context } from 'cordis'
import type { DshClientContext } from '@deepseek-ai/dsh-client-runtime'

export async function apply(ctx: Context & DshClientContext) {
  ctx.slots.register({ name: 'domain.entry.slot' }, MyComponent)
}

پیش از شروع، دانستن دو نکته ضروری است. فیلد inject در مانیفست کلاینت، جنبه مستنداتی دارد و نه زمان‌بندی: این فیلد یال‌های وابستگی در سطح بسته را ثبت می‌کند و ترتیب فعال‌سازی را کنترل نمی‌کند. external جایی است که درخواست‌های ماژول خارج از خط پایه (baseline) را اعلام می‌کنید تا پیش از درخواست پلاگین شما، آماده‌سازی (materialise) شوند. این بخش از پیش‌نمایش، سریع‌ترین تغییرات را دارد؛ بنابراین در روزی که کد را می‌نویسید، نه روزی که راهنما را می‌خوانید، packages/client/AGENTS.md را در مخزن harness مطالعه کنید.

انتشار و مشخص کردن دسترسی‌های پلاگین

افزودن موضوع dsh-plugin به یک مخزن GitHub، آن را در فهرستی قرار می‌دهد که کاربران هنگام جستجو برای پلاگین‌ها مشاهده می‌کنند. این کار به معنای جلب اعتماد کاربران ناشناس است و تعهداتی را به همراه دارد. این تعهدات دقیقاً بازتابی از مواردی است که در راهنمای بررسی پلاگین‌های dsh پیش از نصب به کاربران توصیه شده است؛ بنابراین، نوشتن مستندات بر اساس این چک‌لیست، ساده‌ترین راه برای تأیید شدن است.

  • وابستگی‌های خود را Pin کنید. استفاده از caret range برای وابستگی‌های غیرمستقیم باعث می‌شود بسته‌ای که هفته گذشته ایمن بود، این هفته کد متفاوتی را اجرا کند؛ این دقیقاً همان مکانیزمی است که پشت حملات زنجیره تأمین npm روی سرور قرار دارد.
  • در فایل manifest مشخص کنید که به چه بخش‌هایی دسترسی دارید. لیست inject شما یک خلاصه صادقانه و قابل‌خواندن توسط ماشین از سرویس‌های harness است که استفاده می‌کنید. یک بازبین در چند ثانیه آن را می‌خواند و بر اساس آن قضاوت می‌کند.
  • هیچ تماس شبکه مخفیانه‌ای برقرار نکنید. اگر ابزاری با یک API تماس می‌گیرد، نام میزبان (host) را در README ذکر کنید و endpoint را قابل‌تنظیم قرار دهید. پلاگینی که با سروری تماس می‌گیرد که هرگز نامی از آن نبرده است، توسط افرادی که این موارد را ممیزی می‌کنند، از لیست حذف خواهد شد.
  • فایل files را محدود نگه دارید. انتشار کل پوشه کاری باعث می‌شود فایل‌های حاوی اعتبارنامه‌های حساس به اشتباه وارد رجیستری شوند.
  • برای نصب‌کننده‌های git، یک اسکریپت prepare ارائه دهید که بدون پیش‌فرض‌های مخصوص محیط توسعه (dev-only) ساخته شود و در README به آن‌ها اطلاع دهید که باید این build را در pnpm-workspace.yaml پروفایل خود در لیست سفید (allowlist) قرار دهند.
  • در README تاریخ انتشار را بر اساس release candidate که با آن تست کرده‌اید، درج کنید. خوانندگان یک API پیش‌نمایش باید بدانند شما با کدام نسخه کار کرده‌اید.

برای مشاهده ظاهر یک پلاگین تکمیل‌شده از دید کاربر، پلاگین‌های dsh که ارزش نصب دارند را بخوانید و توجه کنید که هر README پیش از نصب چه اطلاعاتی به شما می‌دهد. اگر برای عامل دیگری افزونه نوشته‌اید، نحوه ساختار پلاگین‌های Claude Code مقایسه مفیدی است. این harness یک گراف شیء زنده و قابلیت ثبت‌نام برگشت‌پذیر (reversible registration) در اختیار شما می‌گذارد که قدرت بیشتری نسبت به یک لیست ساده از فایل‌ها دارد و به همان نسبت مسئولیت بیشتری نیز به همراه می‌آورد.

FAQ

آیا برای نوشتن یک پلاگین dsh حتماً باید آن را در npm منتشر کنم؟

خیر. یک مسیر فایل‌سیستم در یک cordis.yml overlay که با dsh web --patch ./scratch-plugin/cordis.yml بارگذاری شده باشد، برای اجرای کد شما در محیط harness کافی است. این مسیر باید مطلق (absolute) باشد. بسته‌بندی (Packaging) تنها زمانی اهمیت پیدا می‌کند که شخص دیگری بخواهد پلاگین را نصب کند؛ حتی در آن حالت هم می‌توانید با استفاده از dsh plugin --profile demo add ./my-plugin یک پوشه محلی را نصب کنید تا فرم بسته‌بندی‌شده را بدون نیاز به رجیستری تست کنید.

چرا پلاگین من بارگذاری می‌شود اما ابزار ظاهر نمی‌شود؟

ابتدا dsh --profile demo --dump-config را اجرا کنید. اگر شناسه ردیف (row id) شما در خروجی وجود ندارد، پلاگین هرگز mount نشده و علت آن مربوط به ترکیب (composition) است، نه کد. اگر ردیف وجود دارد، export const inject = ['tools'] را بررسی کنید. ورودی‌ها در پیکربندی Cordis به‌طور هم‌زمان شروع می‌شوند، بنابراین ترتیب فایل‌ها تعیین‌کننده ترتیب بارگذاری نیست. بدون آن اعلان، Cordis منتظر رجیستری ابزار نمی‌ماند و apply شما ممکن است در لحظه‌ای اجرا شود که ctx.tools هنوز برای ثبت‌نام در دسترس نیست.

تفاوت cordis.yml و cordis.patch.yml چیست؟

cordis.yml یک لیست کامل از ورودی‌ها است. cordis.patch.yml لایه‌ای است که روی یک لیست اعمال می‌شود و ردیف‌ها را بر اساس شناسه هدف قرار می‌دهد تا ورودی‌های جدید درج یا پیکربندی موجود جایگزین شود. یک bundle از طریق dsh.bundle.patch در package.json به فایل patch خود اشاره می‌کند. لایه‌ها به ترتیب مشخصی اعمال می‌شوند: هر bundle به ترتیبی که در پروفایل لیست شده، سپس فایل patch پروفایل، سپس $DSH_HOME/cordis.patch.yml و در نهایت هر --patch overlay. لایه‌های بعدی اولویت دارند.

آیا می‌توانم یک پلاگین dsh را در حین اجرای agent به صورت hot reload بارگذاری کنم؟

تا نسخه 0.1.0-rc.7، این امکان برای بخش host در پروفایل وب وجود ندارد. آن bundle قابلیت hot module reload را به‌صورت غیرفعال عرضه می‌کند و در فایل یادداشتی وجود دارد که می‌گوید پس از تست چرخه حیات reload، این قابلیت بازخواهد گشت. برای restart سریع طراحی کنید: یک فایل که از طریق --patch و بدون مرحله build بارگذاری می‌شود، و هر ثبت‌نامی که از طریق ctx انجام می‌گیرد تا هیچ‌چیز از یک اجرا به اجرای بعدی نشت نکند. برای منابعی که Cordis نمی‌تواند به‌تنهایی پاکسازی کند، از ctx.effect() به همراه یک disposer استفاده کنید.