DSH 플러그인 개발 표준
본 스킬은 DeepSeek Harness(DSH) 플러그인 개발의 유일한 표준입니다. DSH 공식 문서의 튜토리얼, 참조 매뉴얼, 생성 디렉토리에 분산된 규칙을 실행 가능한 워크플로우, 하드 룰, 체크리스트로 통합합니다. DSH의 모든 것은 플러그인입니다(모델 어댑터, 도구 레지스트리, 세션 로그, 에이전트 루프 자체까지). 따라서 이 스킬을 따르는 것은 DSH 자체의 아키텍처 방식으로 DSH를 확장하는 것입니다.
적용 범위
- 레포지토리 내 파일 기반 DSH 플러그인 개발: 플러그인 패키지 작성, cordis.yml 행 등록, 패치 오버레이, 도구 개발, 모델 연결, 패키징 및 설치.
- DSH 모노레포 내 워크스페이스 패키지 개발(packages//)도 본 스킬에 포함됩니다(references/workspace-package.md 참조).
- 본 스킬은 다음을 포함하지 않습니다: 세션 내 동적 플러그인(cordis_define/cordis_run 플로우) 및 에이전트 프리셋 조합 편집 – 이 두 가지는 각 배포 전용 스킬 또는 공식 도구가 담당합니다.
하드 룰(모든 시나리오에서 반드시 준수)
- 인터페이스는 생성된 참조를 기준으로 합니다. 서비스명, 공개 메서드, 이벤트 시그니처, ctx 키 모두 레포지토리가 자동 생성한 서브시스템 페이지와 TypeScript 인터페이스를 기준으로 합니다. 서비스명, 예제, 이전 코드로 완전한 API를 추론하지 말고, 별도의 정적 목록을 유지하지 마십시오.
- 모든 기여는 부수 효과(side effect)입니다. ctx를 통해 수행하는 모든 등록(이벤트 리스너, 도구, 어댑터, ctx.effect)은 플러그인 언로드 시 자동으로 취소됩니다. 모듈 스코프에서 프로세스/페이지 레벨 부수 효과를 생성하지 마십시오. disposer를 반환하지 않는 타사 구독은 정리 메커니즘을 적극적으로 확인해야 합니다.
- 워터폴(waterfall) 리스너는 반드시 next()를 호출합니다. next()를 호출하지 않으면 의도적으로 하위를 단락(short-circuit)시키는 것입니다(인터셉터/게이트웨이용). 선택 사항이 아닙니다.
- 실패는 명확하게. apply에서 예외가 발생하면 프로세스가 종료됩니다. 구성 유효성 검사 실패는 명확하게 오류를 보고해야 합니다. schema는 완전한 제약 조건을 표현해야 하며, 런타임에 조용히 오류를 삼키면 안 됩니다.
- 필수 의존성은 inject로 선언하고, 선택적 의존성은 ctx.get()으로 null 검사를 합니다. undefined 검사를 회피하기 위해 inject를 사용하지 마십시오. 또한 선언되지 않은 inject 없이 ctx.xxx에 직접 접근하지 마십시오. 선언되지 않은 서비스는 서비스 리졸버 평가 시 undefined가 될 수 있습니다.
- 구성은 모두 Schemastery를 사용합니다. interface Config와 동일한 이름의 Schema를 내보내고, 기본값은 schema에 작성합니다. 일반 객체를 Config로 내보내지 마십시오. 배포에 따라 값이 변경될 수 있는 모든 매개변수는 구성에 포함되어야 합니다.
- 도구 execute는 표준 JSON 값을 반환하며, 콘텐츠 블록을 반환하지 않습니다. 사람이 읽을 수 있는 텍스트는 output.render에 넣습니다. 배포 전략/훅을 도구 본체에 내장하지 마십시오.
- 모델이 볼 수 있으면 이미 기록된 것입니다. 새 모델 가시 입력은 세션 로그가 재구성할 수 있는 메커니즘(새 지속 이벤트 또는 agent.inject() 통해)에 포함되어야 하며, 런타임 불변 조건 단언이 있어야 합니다.
- npm 설정 패널은
ctx.connection.rpc를 우선합니다. 브라우저 절반 설정 패널은 메인 프로세스 메서드 호출 시 Connection RPC를 우선합니다(ctx.connection.rpc.handle+ctx.connection.rpc.call, 보일러플레이트는 references/connection-rpc.md 참조). 원시@Remote, 수동 typert manifest,createRequire를 통한 harness 소스 연결은 금지합니다. 완전한 Typert generator 출력을package.json의./typert+./remote로 내보내고ctx.remote.$mount()가 소비하는 경로는 여전히 유효합니다.
표준 워크플로우
시나리오 A: 새 플러그인 생성
- 플러그인이 무엇을 제공할지 결정합니다(서비스? 도구? 리스너?). references/seams.md의 "새 동작의 귀속 위치" 표에서 메커니즘을 선택합니다.
- src/.ts를 생성하고, name, inject(선택), apply(ctx, config?)를 내보냅니다. 먼저 함수 형태로 작성하고, 외부에 서비스를 제공해야 할 때 Service 클래스 형태로 전환합니다.
- 로컬 개발 루프(소스 체크아웃):
mkdir -p scratch-plugin/src
# scratch-plugin/cordis.yml —— 패치 오버레이; 플러그인 경로는 절대 경로여야 함
- insert:
- id: hello
name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'
pnpm dsh web --patch ./scratch-plugin/cordis.yml # http://127.0.0.1:3080 열기
- 로드 및 언로드 확인: 시작 로그에 나타나고, 중지 시 리소스가 실제로 정리되는지 확인합니다(references/plugin-anatomy.md 참조).
시나리오 B: 모델에 도구 추가
- inject: ['tools']로 설정하고, defineTool로 정의합니다(name/description/parameters/output/execute).
- references/tools.md의 execute 규칙에 따라 구현합니다: schema DSL로 매개변수와 출력 선언, execute는 표준 값 반환, exec.signal 준수, 선택적 presentationMeta/UI 카드.
- 전략이 필요한 경우 tools/* 이벤트(pre-execute/guard/execute/post-execute/result)에 연결하고, 전략을 도구 본체에 하드코딩하지 마십시오(권한 게이트 예제는 references/plugin-forms.md 참조).
- 재시작 후 모델이 실제로 호출하는지 확인합니다.
시나리오 C: 교체 가능한 기능(여러 제공자)
- references/three-roles.md에 따라 Definition/Provider/Consumer 세 개의 패키지로 분할합니다(필요에 따라 역할 병합, 예방적 분할 금지).
- Definition은 Request/Result 타입과 추상 Service를 보유합니다. Provider가 구현합니다. Consumer는 도구나 서비스 소비자로 노출됩니다.
- cordis.yml에는 Provider와 Consumer 행만 나열합니다. 제공자를 변경하려면 한 행만 변경합니다.
시나리오 D: 새 모델 제공자 연결
- LlmAdapter를 상속하여 stream()을 구현하고, StreamChunk 프로토콜에 따라 청크를 출력합니다. 오류는 안정적인 code를 가진 LlmError를 사용합니다(또는 인밴드로 finish { kind: 'error' | 'aborted' }로 종료). 각 HTTP 요청은 attributionHeaders()를 병합하고 signal을 전달합니다.
- ctx.llm.registerAdapter([''], adapter)를 호출합니다. 선택적으로 resolveModel()/listModels()를 재정의할 수 있습니다.
- 조합에서 agent-loop의 provider/model을 확인합니다. 자세한 내용은 references/llm-adapter.md(7개 프로토콜 의무 포함)를 참조하세요.
시나리오 E: 패키징 및 설치 배포
- 조합 패키지 생성: package.json에 dsh.bundle(cordis.patch.yml 가리킴)을 선언하고, 패치 행은 패키지명으로 플러그인을 참조합니다. import 전용 라이브러리 패키지는 dsh.bundle을 선언하지 않습니다.
- 설치: dsh plugin --profile add 를 실행합니다. 먼저 dsh --profile --dump-config로 레이어를 확인한 후, 시작합니다.
- 레이어 순서와 전체 행 교체 의미를 기억합니다(references/packaging.md 참조).
시나리오 F: DSH 모노레포 내 새 패키지 생성
references/workspace-package.md의 파일별 체크리스트를 따릅니다: 패키지 생성(package.json 불변 조건/tsconfig/src/README) → 루트 구성 등록 → 패키지 토폴로지 및 명명(역할 어휘표) → README Model Experience 구조 → 검증 명령어.
결정 퀵 레퍼런스
| 원하는 것 | 메커니즘 | 상세 정보 |
|---|---|---|
| 다른 플러그인에 기능 공개 | Service(클래스 형태) | references/services.md |
| 기존 기능 사용 | inject 또는 ctx.get() | references/services.md |
| 플러그인 간 통신 / 확장 포인트 | ctx.on / ctx.emit 등 이벤트(5가지 전파 모드) | references/events.md |
| 사용자 설정 가능하도록 | Config + Schemastery | references/config.md |
| 모델에 기능 추가 | defineTool → ctx.tools.register(또는 원시 JSON Schema) | references/tools.md |
| 새 모델 제공자 연결 | LlmAdapter + ctx.llm.registerAdapter | references/llm-adapter.md |
| 교체 가능한 기능 분리 | 세 가지 역할(Definition/Provider/Consumer) | references/three-roles.md |
| 플러그인 배포 | 조합 패키지 + profile | references/packaging.md |
| 내장 서비스 / 귀속 위치 확인 | seam 디렉토리, 아키텍처 맵 | references/seams.md |
| 스코프/서비스 저장소/Fiber API | ctx.extend/isolate/provide/mixin, fiber.* | references/context-api.md |
| 훅 / UI / 패널 / 프로토콜 브리지 플러그인 | 5가지 확장 형태 + 기능→메커니즘 맵 | references/plugin-forms.md |
| 브라우저 절반 패널에서 메인 프로세스 서비스 호출 | ctx.connection.rpc.handle/call, endpoint + envelope | references/connection-rpc.md |
| 레포지토리 내 새 패키지 생성 / 명명 | 워크스페이스 패키지 체크리스트 + 역할 어휘표 | references/workspace-package.md |
완료 전 체크리스트
- 인터페이스는 생성된 참조를 기준으로 확인(서비스/이벤트/ctx 키 모두 생성 기준), 이름으로 API 추측 금지.
- 모든 등록은 ctx를 통해(이벤트/도구/어댑터/effect), 언로드 시 정리 가능; 순서에 민감한 정리는 동일한 ctx.effect에 배치.
- 필수 의존성 주입 선언 정확; 선택적 의존성은 undefined 처리.
- 구성이 있는 플러그인: Config는 Schemastery 사용, 기본값은 schema에, 잘못된 구성은 로드 시 명확하게 실패.
- 도구가 있는 경우: execute는 표준 JSON 값 반환; render는 사람이 읽을 수 있도록 처리; 전략은 tools/* 이벤트를 통해; exec.signal 준수; 동시성 안전은 isConcurrencySafe 선언(미선언 → exclusive).
- 워터폴 리스너: next() 호출 및 반환(의도적 단축이 아닌 경우).
- LLM 어댑터: StreamChunk 프로토콜 완전함(청크 쌍, index는 첫 등장 기준, usage는 finish 전, arguments는 전체 원시 JSON 문자열); 오류는 두 가지 합법적 경로 중 하나; 지원되지 않는 필드는 UNSUPPORTED throw; 기본 재생 필요 시 finish.replayState 전송; attributionHeaders와 signal 전달.
- 조합 행과 레이어 순서 정확; 새 행이 --dump-config에서 보임; HMR/재시작 후 잔여 등록 없음.
- 모델 가시 콘텐츠 변경 시: 로그 재구성 가능 메커니즘 내에 포함.
- 브라우저 절반 설정 패널:
ctx.connection.rpc.handle/call우선; 원시@Remote, 수동 typert manifest,createRequireharness 소스 연결 금지; 완전한 Typert generator +./typert+./remote산물은 여전히 유효. - 새 워크스페이스 패키지: package.json 불변 조건, 정확히 하나의 aggregate, 역할 어휘표에 따른 명명, README에 Model Experience 구조, constraints/typecheck/lint/build/hygiene 모두 통과.
참조 파일(필요 시 로드, 한 번에 모두 읽지 않음)
전체 인덱스와 "언제 읽을지" 대응표는 references/README.md 참조.
- references/plugin-anatomy.md —— 플러그인 작성/수정, 생명주기 처리 또는 HMR 시 읽기
- references/services.md —— 서비스 정의 또는 소비, 의존성 주입 시 읽기
- references/events.md —— 이벤트 통신, 리스너 확장 포인트 시 읽기
- references/config.md —— 플러그인 설정 가능하게 할 때 읽기
- references/context-api.md —— 스코프, 서비스 저장소 또는 Fiber API 사용 시 읽기
- references/three-roles.md —— 교체 가능한 기능 분리 시 읽기
- references/tools.md —— 모델 도구 개발 시 읽기
- references/llm-adapter.md —— 새 모델 제공자 연결 시 읽기
- references/plugin-forms.md —— 훅/UI/프로토콜 브리지 플러그인 작성 시 읽기
- references/connection-rpc.md —— npm 설정 패널에서 메인 프로세스 메서드 호출 시 읽기; 원시
@Remote/ 수동 manifest /createRequire소스 브리지 발견 시에도 읽기 - references/packaging.md —— 패키징 설치, 플러그인 배포 시 읽기
- references/workspace-package.md —— 모노레포 내 새 패키지 생성 시 읽기
- references/seams.md —— 내장 서비스, 귀속 위치 또는 아키텍처 맵 확인 시 읽기
검증
- 형식 자체 점검: name은 kebab-case이며 디렉토리명과 일치; description은 비어 있지 않고 간결(본 스킬은 500자 이내); 본문은 표준 Markdown 사용; 참조된 모든 references 파일이 실제로 존재.
- 디렉토리 명명: 본 스킬명(name)은 dsh-plugin-dev이며, 호스팅 레포지토리명 dsh-plugin-dev-skills와 다름——클론/압축 해제 후 폴더명을 dsh-plugin-dev(name과 일치)로 지정해야 함, 그렇지 않으면 디렉토리명으로 주소 지정하는 로더가 본 스킬을 발견하지 못할 수 있음.
- skills-ref CLI 환경에서 실행 가능: skills-ref validate ./dsh-plugin-dev
- 회귀 트리거: description 수정 후 evals/trigger-queries.json 평가 세트 실행(방법은 레포지토리 evals/README.md 참조).
- 동작 검증: 시나리오 A에 따라 최소 플러그인(로드 로그 + 언로드 정리)을 한 번 통과하고, 시나리오 B에 따라 greet 도구를 한 번 통과.