SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor

DeepSeek Harness dsh 플러그인 직접 개발 가이드

DeepSeek Harness에서 사용할 dsh 플러그인을 처음부터 직접 개발하는 방법을 설명합니다. package.json 필수 필드 설정부터 패치 파일 구성, 실제 도구 구현 및 두 가지 핵심 훅 사용법까지 0.1.0-rc.7 버전 기준으로 상세히 안내합니다.

dsh 플러그인의 정의

dsh 플러그인은 apply 함수를 내보내고 DeepSeek Harness에 로드를 지시하는 작은 YAML 파일 하나를 포함하는 npm 패키지입니다. 별도로 학습해야 할 플러그인 SDK는 없습니다. dsh는 Cordis 애플리케이션이며, "모든 것이 플러그인"이라는 원칙은 문자 그대로 적용됩니다. 도구 레지스트리, 에이전트 루프, 세션 저장소, 웹 서버는 모두 귀하의 패키지가 합류하게 될 동일한 플러그인 트리의 구성 요소입니다.

Cordis는 독립적으로 구축되어 수년간 Koishi 챗봇 프레임워크의 기반으로 사용된 범용 구성 프레임워크입니다. Cordis는 플러그인의 로드와 언로드를 처리하며 플러그인 간의 의존성을 해결합니다. Cordis 자체는 에이전트에 대해 알지 못합니다. 에이전트와 관련된 모든 기능은 그 위에 계층화된 harness 패키지에서 제공되므로, 아래에 설명된 플러그인 구조가 매우 단순해 보이는 이유입니다. 제공되는 기능 대부분은 상속받은 것입니다.

플러그인은 두 부분으로 나뉩니다. 호스트 측은 Node에서 실행되며 도구와 이벤트 리스너를 등록하고 자체 서비스를 제공할 수 있습니다. 브라우저 측은 Web UI 내부에서 실행되며 인터페이스 슬롯을 등록합니다. 첫 번째 플러그인은 거의 항상 호스트 전용이므로, 필요하기 전까지 브라우저 측은 선택 사항으로 간주하십시오.

이 가이드는 2026년 8월 19일 기준 npm latest 태그인 @deepseek-ai/dsh 버전 0.1.0-rc.7을 기준으로 작성되었습니다. dsh는 개발자 프리뷰 단계이며 README에 명시된 대로 호환성을 깨뜨리는 변경 사항이 발생할 수 있습니다. 아래의 모든 키 이름은 해당 날짜의 업스트림 문서와 저장소에서 확인한 내용입니다. 2026년 10월 12일에 이 가이드를 재검토했을 때 npm latest 태그는 0.2.0-rc.2였으며, 아래 섹션에서는 해당 릴리스에서 동작이 달라지는 부분을 지적합니다. 프리뷰 API는 릴리스 후보 간에 필드 이름을 변경하므로, 특정 필드에 의존하기 전에 키 이름을 다시 확인하십시오. harness가 아직 실행 중이 아니라면 VPS에서 DeepSeek Harness 실행하기와 dsh API 키 및 모델 설정을 먼저 완료한 후 다시 돌아오십시오.

패키징 전에 스크래치 파일 하나를 먼저 로드하십시오

먼저 패키징하는 것은 이 과정을 배우는 느린 방법입니다. 파일 하나를 로드하여 런타임이 코드를 호출하는지 확인한 다음 패키징하십시오.

하네스 체크아웃 외부의 폴더를 생성하고 그 안에 파일 하나를 넣으십시오.

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는 이를 한 번 호출하고 플러그인 범위로 지정된 컨텍스트를 전달합니다. 해당 컨텍스트에 등록한 모든 항목은 플러그인이 삭제될 때 자동으로 정리됩니다.

그 옆에 cordis.yml을 작성하십시오.

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

이제 해당 파일이 위에 계층화된 프로파일을 부팅하십시오.

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

npx @deepseek-ai/dsh web --patch ./scratch-plugin/cordis.yml이 PATH에 없다면 npx @deepseek-ai/dsh web --patch ./scratch-plugin/cordis.yml도 동일한 역할을 수행합니다. npx 경로는 이 가이드에서 설명하는 버전 대신 캐시된 이전 릴리스 후보를 제공할 수 있습니다. 따라서 하네스가 문서화된 플래그를 완전히 거부한다면, 자신의 파일을 의심하기 전에 dsh 설치 및 버전 오류 해결 방법을 먼저 확인하십시오. dsh를 시작한 터미널에서 [hello-plugin] plugin loaded이 표시되어야 합니다. 아무것도 나타나지 않으면 행이 확인되지 않은 것입니다.

name 필드는 npm 패키지 이름이나 파일 시스템 경로를 사용하며, 업스트림 문서에 따르면 경로는 절대 경로여야 합니다. 스크래치 플러그인에서 출력이 생성되지 않을 때 가장 먼저 확인해야 할 것은 상대 경로인 ./hello.ts입니다. 두 번째는 파일 확장자입니다. 문서화된 루프는 하네스 저장소의 복제본에서 pnpm dsh web --patch ...로 실행되며, 여기서 TypeScript 항목은 tsx를 통해 로드됩니다. dsh를 npm에서 가져온 경우 행을 일반 JavaScript로 지정하거나 파일을 먼저 빌드하십시오.

--patch은 런처 플래그이며, 해당 오버레이는 모든 번들과 사용자 고유의 프로파일 패치 이후 마지막에 적용됩니다. 따라서 스크래치 오버레이는 항상 우선하며, 이는 반복 작업을 수행할 때 정확히 필요한 동작입니다.

유용한 기능을 수행하는 최소한의 도구 작성하기

로그 한 줄은 플러그인이 로드되었음을 증명합니다. 도구는 플러그인이 에이전트의 일부임을 증명합니다.

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는 ctx.tools이 존재할 때까지 Cordis가 대기하도록 지시한 뒤 apply을 호출하게 합니다. 이 설정이 없으면 레지스트리가 등록을 받을 준비가 되지 않은 시점에 코드가 실행될 수 있습니다.

객체의 나머지 부분은 모델이 인식하는 계약입니다. parameters은 인자 스키마이며, execute는 이미 해당 스키마에 따라 파싱된 인자를 전달받습니다. output.schema은 execute이 반환하는 값을 설명하며, render는 해당 값을 모델이 읽을 수 있는 콘텐츠 블록으로 변환합니다. 이 두 가지를 분리해 두어야 인터페이스에는 하나를 보여주면서 모델은 다른 것을 읽게 할 수 있습니다.

프로필을 시작하고 어시스턴트에게 이름을 불러 인사를 하도록 요청하십시오. 응답은 execute을 통해 돌아옵니다. ctx를 통한 등록은 되돌릴 수 있으므로, 플러그인을 폐기하면 도구도 자동으로 등록 해제됩니다. 소켓이나 파일 핸들처럼 Cordis가 알 수 없는 항목의 경우, ctx.effect()를 호출하여 폐기자(disposer)를 전달하십시오.

첫 번째 플러그인이 주로 다루는 두 가지 확장 지점

확장 지점의 전체 목록은 길지만, 그중 두 가지가 거의 모든 초기 플러그인에서 사용됩니다.

대화 이벤트(Conversation events)는 영구적으로 기록되는 스트림입니다. 0.1.0-rc.7 버전에서는 session/event, turn/start, turn/end, step/start, step/end, user/message, assistant/message, assistant/chunk, tool/call, tool/result가 사용되었습니다. 0.2.0-rc.2 릴리스에는 assistant/chunk이 없습니다. 실시간 스트리밍은 agent/assistant-stream로 이동했으며, 해당 프레임은 프로세스 내에만 머물고 기록되지 않습니다. 새로 추가된 영구적인 assistant/attempt 이벤트는 실패, 재시도, 취소된 시도를 보관합니다. 여기에 일반적인 리스너를 연결하면 됩니다.

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

페이로드를 한 번 출력하여 내용을 확인하십시오. 이 가이드를 포함한 어떤 문서에서도 페이로드 필드 이름을 그대로 복사하지 마십시오. 페이로드 구조는 프리뷰 API에서 가장 자주 변경되는 부분이기 때문입니다.

두 번째 확장 지점은 워터폴(waterfall)입니다. agent/pre-step, agent/request, agent/request-error, llm/stream 및 tools/* 이벤트는 워터폴 방식이며, 워터폴 리스너는 다른 시그니처를 가집니다. 이 리스너는 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() 호출을 잊으면 훅이 추가되지 않습니다. 모델 호출을 아무것도 없는 상태로 대체하게 되어 에이전트가 그 지점에서 멈춥니다. 이는 의도적으로 요청을 거부하는 게이트웨이 플러그인을 위해 설계된 단락(short circuiting) 동작입니다. 이 차이점이 초기 플러그인 개발 시 가장 많은 혼란을 야기합니다. 다른 코드를 작성하기 전에 next() 호출부터 작성하십시오.

agent/request는 모델 호출 자체를 감쌉니다. 이 페이로드에는 호출을 수행하는 에이전트, 현재 열려 있는 턴 번호, 요청이 속한 단계, 그리고 해당 턴의 중단 신호가 포함됩니다. 이러한 특성 덕분에 요청 로거(request logger)나 속도 제한기(rate limiter)를 구현하기에 적합한 지점입니다. tools/* 워터폴은 한 단계 아래에서 동일한 구조를 가집니다. tools/pre-execute는 디스패치 전 요청을 허용, 거부하거나 승인을 요청할 수 있게 합니다. tools/execute는 디스패치를 감쌉니다. tools/post-execute은 정규화된 결과를 대체하거나 차단할 수 있습니다. tools/result은 최종적으로 확정된 결과만 관찰합니다.

다른 사용자가 설치할 수 있도록 번들로 패키징하기

번들은 package.json에 패치 파일을 가리키는 dsh.bundle 필드가 선언된 npm 패키지입니다. 이 선언이 스크래치 파일과 설치 가능한 패키지를 구분하는 유일한 차이점입니다.

{
  "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 행은 사용자가 설정을 재정의할 때 이후 레이어가 참조하는 대상이므로, 안정적인 이름을 선택하고 다른 플러그인에 재사용하지 마십시오.

files에는 반드시 cordis.patch.yml를 나열해야 합니다. 이를 생략하면 게시된 tarball에 포함된 dsh.bundle.patch가 패키징되지 않은 파일을 가리키게 되어, 패키지는 설치되지만 트리에는 아무것도 기여하지 못하게 됩니다.

플러그인 폴더가 포함된 디렉터리에서 프로필로 설치하십시오.

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, sdk, sdk-minimal 및 acp 프로필은 처음 사용할 때 템플릿에서 자동으로 생성됩니다. 0.2.0-rc.2 버전에서 다른 이름은 두 가지 방식 중 하나로 생성됩니다. dsh plugin는 기본 번들만으로 시작합니다. dsh --profile <name> --from-default-profile <template>은 제공된 템플릿의 번들 목록을 복사합니다. 아직 존재하지 않는 사용자 지정 프로필로 부팅하면 dsh plugin을 실행하라는 안내와 함께 실패합니다.

구성된 트리에서 행이 누락되는 이유

구성은 빈 항목 목록에서 시작하여 고정된 순서로 계층을 쌓습니다. 프로필의 dsh.profile.bundles에 명명된 각 번들이 나열된 순서대로 추가됩니다. 그다음 프로필 자체의 cordis.patch.yml이 추가됩니다. 이어서 $DSH_HOME/cordis.patch.yml이 추가됩니다. 마지막으로 명령줄에서 지정한 --patch 오버레이가 추가됩니다. 이후 계층은 id를 기준으로 이전 행을 대체합니다.

프로필은 $DSH_HOME/profiles/<name> 아래에 위치합니다. 프로필 디렉터리에는 정렬된 bundles 목록이 포함된 dsh.profile 매니페스트를 담은 package.json 파일과 사용자의 패치 파일이 들어 있습니다. 번들 이름은 dsh 설치 경로에서 먼저 확인하고, 그다음 프로필의 node_modules에서 확인합니다. pnpm은 트리에 포함되지 않은 플러그인을 바로 이곳에 배치합니다.

dsh --profile demo --dump-config는 아무것도 부팅하지 않고 완전히 구성된 트리를 출력하며, 이 출력 결과가 디버깅의 기준점이 됩니다. 행 id가 보이지 않는다면 구성 과정의 문제입니다. 이름을 해석하지 못했거나 패치 파일이 패키징되지 않은 경우입니다. 행은 존재하는데 아무런 동작이 일어나지 않는다면, 그것은 코드의 문제입니다. 이 질문에 먼저 답하면 대부분의 추측 과정을 생략할 수 있습니다.

로딩 오류가 실제로 나타나는 곳

apply 내부에서 발생하는 오류는 명확하게 드러납니다. 프로세스는 해당 예외와 함께 종료되며, 사용자의 코드 라인을 가리키는 스택 트레이스를 확인할 수 있습니다.

해결(resolution) 실패는 조용하게 발생합니다. 로더는 충돌하는 대신 Cordis 로거를 통해 해결할 수 없는 모듈을 보고합니다. 상위 튜토리얼에서는 이러한 메시지가 콘솔 익스포터가 연결되기 전에 출력되므로 시작 시점에 누락된 것처럼 보일 수 있다고 경고합니다. 따라서 경로 오타는 로드되었지만 아무 작업도 수행하지 않는 플러그인과 똑같이 보입니다. 이것이 바로 코드를 읽기 전에 위에서 언급한 --dump-config 검사를 실행해야 하는 이유입니다.

개발 중에는 apply의 첫 번째 문장으로 console.log을 유지하십시오. 이 문장이 없으면 문제의 절반이 어디에 있는지 알 수 없으며, 나중에 삭제하는 데 비용이 들지 않습니다. 서버에서는 서비스 관리자 대신 포그라운드에서 하네스를 실행하여 반복 작업을 수행하십시오. 그래야 로더 출력이 직접 확인해야 하는 저널이 아닌 터미널로 전달됩니다.

전체 재시작 없이 반복 작업하기

0.1.0-rc.7 버전에서 호스트 측의 해결책은 간단했습니다. 바로 재시작하는 것입니다. 해당 릴리스의 웹 애플리케이션 번들은 공유 핫 모듈 리로드(hot module reload) 행이 비활성화된 채로 배포되었으며, 리로드 생명주기 테스트가 완료되면 다시 활성화될 것이라는 주석이 달려 있었습니다. 0.2.0-rc.2 버전에서는 해당 오버라이드가 제거되었습니다. 기본 번들은 "프로필 설정은 기본적으로 리로드되며, 모듈 루트는 선택 사항입니다"라는 주석과 함께 모든 프로필에서 @deepseek-ai/dsh-hmr을 root: []로 마운트합니다. 이제 프로필 및 홈 패치 파일에 대한 유효한 편집 내용은 재시작 없이 즉시 다시 적용됩니다. 플러그인 자체 소스는 해당 행의 root에 디렉터리를 추가하지 않는 한 감시되지 않습니다. 번들을 추가, 제거 또는 업데이트하려면 여전히 재시작이 필요합니다. 클라이언트 측 리로드 체인은 항상 마운트되어 있지만, 리빌드 감시자가 클라이언트 번들을 다시 작성하기 전까지는 유휴 상태로 유지되므로 Node 측 작업에는 아무런 영향을 주지 않습니다.

선택적으로 사용해야 하는 리로드 기능에 의존하기보다 재시작 비용을 낮추십시오. 플러그인을 하나의 파일로 유지하십시오. 프로필에 설치하는 대신 --patch로 로드하면 편집과 실행 사이에 빌드 단계나 pnpm 단계가 개입하지 않습니다. 모든 항목을 ctx를 통해 등록하여 재시작 시 중복된 도구나 오래된 리스너가 남지 않도록 하십시오. 직접 할당하는 모든 항목은 실제 해제자(disposer)가 포함된 ctx.effect()으로 감싸십시오. 해제자가 누락되면 첫 번째 실행에서 점유한 포트를 두 번째 실행에서 확보하지 못해 실패하는 현상이 흔히 발생하기 때문입니다.

노트북이 아닌 서버에서 실행되는 하네스(harness)를 대상으로 개발하더라도 위의 내용은 동일하게 적용되지만, 웹 UI 바인딩은 중요합니다. 로컬에서 실행하면 웹 UI가 http://127.0.0.1:3080에서 서비스되고 기본 브라우저에서 열리며, --no-open를 전달하면 브라우저 실행을 건너뜁니다. SSH를 통한 실행은 호스트 URL만 출력하므로 로컬 머신에서 아무것도 열리지 않습니다. 포트 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 내보내기가 없으면 스캐너가 오류를 발생시킵니다. 따라서 내보내기 맵은 편의 기능이 아니라 매니페스트의 일부입니다. 클라이언트 진입점은 클라이언트 런타임 유형으로 확장된 Cordis Context를 수신하며, 모든 등록은 ctx.slots.register을 통해 apply 내부에서 이루어집니다. 해당 위치에서는 모듈 수준의 부작용(side effect)이 허용되지 않습니다.

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은 기준선 외부의 모듈 요청을 선언하지만, 현재 클라이언트 노트에 따르면 이는 기능 플러그인을 위한 의존성 메커니즘이 아니며 인프라, 전송 및 생성된 어셈블리 코드를 위해 예약되어 있습니다. 기능 플러그인에서는 이 항목을 비워 두십시오. 이 영역은 프리뷰 버전에서 가장 빠르게 변화하는 부분이므로, 가이드를 읽는 날이 아니라 코드를 작성하는 당일에 하니스(harness) 저장소의 packages/client/AGENTS.md를 확인하십시오.

플러그인 게시 및 접근 권한 명시

GitHub 저장소에 dsh-plugin 토픽을 추가하면 사용자가 플러그인을 검색할 때 해당 목록에 노출됩니다. 이는 타인의 신뢰를 전제로 하는 행위이며, 그에 따른 의무가 따릅니다. 이러한 의무는 dsh 플러그인 설치 전 검토 가이드에서 독자들에게 확인을 권장하는 항목들과 정확히 일치하므로, 해당 체크리스트에 맞춰 문서를 작성하는 것이 검토를 통과하는 가장 쉬운 방법입니다.

  • 의존성을 고정하십시오. 전이적 의존성에 캐럿(caret) 범위를 사용하면 지난주까지 안전했던 패키지가 이번 주에는 다른 코드를 실행하게 될 수 있으며, 이는 서버를 대상으로 하는 npm 공급망 공격의 핵심 메커니즘입니다.
  • 매니페스트에 접근 권한을 명시하십시오. inject 목록은 귀하가 사용하는 하니스(harness) 서비스가 무엇인지 기계가 읽을 수 있는 형태로 정직하게 요약한 것입니다. 검토자는 이를 수 초 내에 읽고 판단을 내립니다.
  • 암묵적인 네트워크 호출을 금지하십시오. 도구가 API를 호출한다면 README에 호스트 이름을 명시하고 엔드포인트를 설정 가능하게 만드십시오. 언급되지 않은 서버와 통신하는 플러그인은 감사 담당자에 의해 목록에서 삭제됩니다.
  • files을 엄격하게 관리하십시오. 작업 폴더 전체를 게시하면 실수로 포함된 자격 증명 파일이 레지스트리에 유출될 수 있습니다.
  • git 설치 사용자에게 개발 전용 가정이 없는 prepare 빌드 스크립트를 제공하고, README에 프로필의 pnpm-workspace.yaml에서 해당 빌드를 허용 목록(allowlist)에 추가해야 함을 명시하십시오.
  • 빌드 및 테스트를 완료한 릴리스 후보 버전을 기준으로 README에 날짜를 기록하십시오. 미리 보기 API를 사용하는 독자들은 귀하가 어떤 버전을 기준으로 작업했는지 알아야 합니다.

완성된 플러그인이 외부에서 어떻게 보이는지 확인하려면 설치할 가치가 있는 dsh 플러그인을 읽고 각 README가 설치 전 사용자에게 무엇을 알리는지 확인하십시오. 다른 에이전트용 확장을 작성해 본 경험이 있다면 Claude Code 플러그인 구성 방식이 유용한 대조 자료가 될 것입니다. 하니스는 라이브 객체 그래프와 가역적 등록 기능을 제공하는데, 이는 단순한 파일 매니페스트보다 강력한 권한을 부여하는 만큼 그에 상응하는 책임이 따릅니다.

FAQ

dsh 플러그인을 작성하려면 npm에 게시해야 합니까?

아니요. cordis.yml 오버레이에 있는 파일 시스템 경로를 dsh web --patch ./scratch-plugin/cordis.yml으로 로드하는 것만으로도 하네스 내부에서 직접 코드를 실행할 수 있습니다. 경로는 반드시 절대 경로여야 합니다. 패키징은 다른 사람이 플러그인을 설치할 때만 중요하며, 그 경우에도 dsh plugin --profile demo add ./my-plugin을 사용하여 로컬 폴더를 설치하면 레지스트리를 거치지 않고도 패키지 형태를 테스트할 수 있습니다.

플러그인은 로드되는데 왜 도구가 나타나지 않습니까?

먼저 dsh --profile demo --dump-config을 실행하십시오. 해당 출력에 행 ID가 없다면 플러그인이 마운트되지 않은 것이며, 원인은 코드가 아닌 구성 문제입니다. 행이 존재한다면 export const inject = ['tools']를 확인하십시오. Cordis 구성의 항목들은 동시에 시작되므로 파일 순서가 로드 순서를 결정하지 않습니다. 해당 선언이 없으면 Cordis는 도구 레지스트리를 기다리지 않으며, ctx.tools이 등록을 위해 준비되지 않은 시점에 apply이 실행될 수 있습니다.

cordis.yml과 cordis.patch.yml의 차이점은 무엇입니까?

cordis.yml는 전체 항목 목록입니다. cordis.patch.yml은 기존 구성에 새로운 항목을 삽입하거나 교체하기 위해 ID별로 행을 지정하여 적용하는 레이어입니다. 번들은 package.json 내의 dsh.bundle.patch를 통해 자체 패치 파일을 가리킵니다. 레이어는 고정된 순서로 적용됩니다. 프로필에 나열된 순서대로 모든 번들이 적용된 후, 프로필의 패치 파일, $DSH_HOME/cordis.patch.yml, 그리고 모든 --patch 오버레이가 순차적으로 적용됩니다. 나중에 적용된 레이어가 우선합니다.

에이전트가 실행 중일 때 dsh 플러그인을 핫 리로드할 수 있습니까?

0.1.0-rc.7 버전에서는 불가능합니다. 해당 버전의 웹 번들은 공유 핫 모듈 리로드 행이 비활성화된 상태로 출시되었습니다. 0.2.0-rc.2 버전에서는 해당 행이 모든 프로필에 마운트되지만, 기본적으로는 프로필 구성만 리로드합니다. 모듈 루트는 root 목록을 통해 선택적으로 활성화해야 하므로, 추가 설정을 하지 않는 한 플러그인 코드는 여전히 재시작이 필요합니다. 어떤 경우든 빠른 재시작이 가능하도록 설계하십시오. 빌드 단계 없이 --patch를 통해 로드되는 단일 파일을 사용하고, 모든 등록은 ctx을 통해 수행하여 이전 실행의 데이터가 다음 실행으로 누출되지 않도록 하십시오. Cordis가 스스로 정리할 수 없는 리소스는 디스포저(disposer)와 함께 ctx.effect()을 사용하십시오.