Skip to content

CH 22 · UI 플러그인: 스킨 변경, 패널 추가, dsh에 콘텐츠 주입

전체 글자 수약 2,420 자예상 소요 시간약 20분선행CH 17 (플러그인 설치)난이도재현 가능

본 장 목표

이전 장들의 플러그인은 "보이지 않는 곳"에서 일했습니다: 도구를 등록하고, 호출을 가로채고, 의존성을 제공했습니다. 이 장에서는 보이는 층으로 전환합니다 — 플러그인이 Web UI의 외형과 능력을 직접 바꿀 수 있습니다. 이 장은 UI 플러그인이 무엇을 할 수 있는지, 어떻게 동작하는지 정리하고, 실습으로 실제 이벤트 스트림을 확인합니다.

이미 UI 플러그인을 쓰고 계십니다

CH 17을 돌아보면, 우리는 세 개의 플러그인을 설치했고 그것이 모두 UI 플러그인이었습니다:

플러그인하는 일
dsh-themeWeb UI 스킨 변경(설정 → 외관에 테마 카드 표시)
dsh-oil-sticky-prompt스크롤 중 최신 사용자 메시지를 상단에 고정
dsh-better-sidebar사이드바 워크벤치, 파일 패널에서 디렉터리를 직접 확인

그때는 "플러그인을 설치했다" 정도였지만, 이제 이해가 됩니다: 이들 모두 UI를 수정합니다. UI 플러그인의 첫 번째 이해는 이겁니다 — 이들이 "보이는" 모든 사용자 정의를 담당합니다.

UI 플러그인이 할 수 있는 일

크게 세 가지 범주의 능력이 있습니다:

  • 스킨 변경(reskinning): 색상, 폰트, 레이아웃 스타일을 변경. 가장 가벼운 범주로, dsh-theme이 이에 해당합니다.
  • 레이아웃 수정 / 패널 추가 / 기능 추가: 사이드바, 상단 바, 입력 영역의 이동, 또한 인터페이스에 완전한 기능 모듈이나 시각화 패널을 직접 추가. dsh-better-sidebar, dsh-oil-sticky-prompt가 이 범주이며, CH 12에서 설치한 DSH Skill & MCP Panel도 여기에 해당합니다 — 설정에 "MCP 관리" 항목을 직접 추가하여 각 서버의 상태와 도구 수를 한눈에 볼 수 있게 하며, Web UI에 가시화된 운영 패널을 추가하는 셈입니다.
  • 대화에 콘텐츠 기여: 세션에서 커스텀 행을 렌더링(예: 특정 종류의 도구 결과에 카드 표시, 특수 노드 삽입). 가장 깊은 범주로, 공식 명칭은 "Web Client에 비즈니스 행을 기여".

앞 두 범주는 대부분 순수 프런트엔드 작업이며, 세 번째는 실제로 대화 렌더링 파이프라인에 "연결"되어야 합니다.

UI 플러그인은 어떻게 동작하는가: 모든 것은 이벤트 스트림에서 시작

UI 플러그인은 페이지의 DOM을 직접 조작하지 않으며, 이벤트 스트림을 구독합니다. UI 플러그인을 이해하는 데 가장 핵심적인 점입니다.

세션 내내, 모든 이벤트는 표준 이벤트가 됩니다: 사용자가 메시지를 보냈고, 모델이 스트리밍 출력을 시작했고, step이 시작되었으며, 도구 호출이 발생했습니다 — 이 이벤트들은 session/event 채널을 따라 물처럼 흐릅니다. 내장 인터페이스는 이로부터 대화 버블, trajectory, 상태를 렌더링합니다. UI 플러그인도 마찬가지로, ctx.on('session/event', ...)를 통해 동일한 스트림을 구독합니다.

UI 플러그인을 위한 공식 최소 예제가 정확히 이 작업을 수행합니다:

js
export const name = 'my-ui'
export const inject = ['agents']

export function apply(ctx) {
  ctx.on('session/event', (_session, event) => {
    if (event.type === 'assistant/chunk' && event.data.chunk.type === 'text-delta') {
      console.log(event.data.chunk.text)   // 모델 스트리밍 출력의 각 조각
    }
  })
}

전체 메커니즘의 골격:

사용자가 입력하고, 모델이 응답하면, 세션 컨트롤러가 이를 모두 이벤트 스트림으로 변환합니다. UI 플러그인과 내장 인터페이스는 동일한 스트림을 구독하며, 각자 필요한 부분을 가져갑니다. 인터페이스는 이벤트 스트림의 "소비자"이며, DOM의 직접 조작자가 아닙니다 — 이것이 "모든 것이 플러그인"이 UI까지 포괄할 수 있는 이유를 설명합니다. 인터페이스조차도 플러그인이 이벤트 스트림에서 "청취"해 만들어지는 결과물입니다.

실습: "이벤트 관찰자"를 설치해 이벤트 스트림을 지켜보기

메커니즘만 봐서는 충분하지 않으니, 모든 세션 이벤트를 출력하는 미니멀 플러그인을 작성합니다 — 한 번의 대화 뒤에서 실제로 얼마나 많은 일이 일어나는지 직접 봅니다.

1단계: 디렉터리 생성, 플러그인 작성

플러그인을 둘 워크스페이스에서:

powershell
New-Item -ItemType Directory -Path "ui-demo\src" -Force

ui-demo\src\event-watch.js를 만듭니다:

js
export const name = 'event-watch'

export function apply(ctx) {
  ctx.on('session/event', (_session, event) => {
    console.log(`[event] ${event.type}${event.data?.type ? ' / ' + event.data.type : ''}`)
  })
}

그저 session/event를 구독해 각 이벤트의 타입을 출력할 뿐입니다.

2단계: 플러그인 선언

ui-demo\cordis.yml을 만듭니다(경로는 본인 것으로 바꾸고, 공백은 %20으로 적어야 함을 기억):

yaml
- insert:
    - id: event-watch
      name: 'file:///E:/your-workspace/ui-demo/src/event-watch.js'

3단계: headless로 한 번 실행

powershell
cd your-workspace-directory
dsh --profile headless --patch "./ui-demo/cordis.yml" "Just reply: hi"

제 실제 terminal 출력:

"hi"라고만 답하는 한 마디 답변에, 이면에서는 22개 이벤트가 지나갔습니다: 세션 초기화(권한, 샌드박스, 승인), turn/start, step/start, 세 개의 user/message, 요청 헤더, assistant/chunk이 텍스트를 하나씩 스트리밍, assistant/message이 정리, step/end, turn/end ...

Web UI에서 보는 대화는 이 이벤트들의 연쇄가 소비된 뒤 렌더링된 결과입니다. 이벤트 관찰자가 UI 뒤의 원석을 처음으로 "보여주었습니다".

dsh에게 맡기기: 가시적인 UI 플러그인 개발하기

위 이벤트 관찰자는 단지 "이벤트를 지켜보기"만 했지 인터페이스를 실제로 바꾸지는 않았습니다. dsh가 직접 가시 효과가 있는 — 설정 페이지에 새 탭을 추가하는 — UI 플러그인을 개발하도록 합시다. dsh가 이를 해낼 수 있습니다.

Web UI의 입력창에 다음 프롬프트를 그대로 보냅니다:

text
현재 워크스페이스에서 Web UI에 가시적인 간단한 UI 플러그인을 개발해 줘: 설정 페이지에 커스텀 탭을 하나 추가해 (예: "My Plugins"라는 이름, 페이지 안에는 자리표시용 콘텐츠만 있으면 되며 복잡한 기능은 불필요). 먼저 dsh 공식 문서를 읽어 UI 플러그인 메커니즘과 설정 페이지에 탭을 추가하는 방법을 파악한 다음, 공식 명세에 따라 구현하고, 어떤 profile에 설치해야 하는지, 인터페이스에서 어떻게 봐야 하는지(그리고 dsh 재시작 또는 설정 변경이 필요한지) 알려 줘.

dsh가 문서를 직접 읽고, 기술 스택을 판단하며(그러한 인터페이스 플러그인은 보통 TypeScript와 프런트엔드 빌드가 필요한데, 이를 스스로 처리), 구현하고, 어떻게 검증할지 알려줍니다. 안내에 따라 dsh를 재시작하면, 설정 페이지에 새 탭이 보입니다.

이는 실제로 한 번 실행해 본 모습입니다 — 문서 읽기부터 플러그인 작성, profile 설치, 인터페이스에서 어떻게 보는지, 어떻게 종료하는지까지 모두 스스로 수행했으며, 핵심 경로까지 짚어주었습니다:

재시작 후, 설정 → 플러그인으로 가면, 상단에 새로운 "My Plugins" 탭이 나타납니다:

한 가지 디테일: 이 플러그인은 순수 UI이며 Host 측 동작이 없어, settings.yaml / cordis.yml에 변경이 생기지 않습니다 — 모든 것이 profile의 bundles를 통해 적재됩니다. 롤백도 간단합니다. bundles 배열에서 제거하면 됩니다. 이것이 "모든 것이 플러그인"의 또 다른 구현입니다. 인터페이스에 탭을 추가하는 일조차 "플러그인 설치"입니다.

자주 겪는 함정

문제현상대응
이벤트 관찰자가 출력되지 않음session/event 구독이 잘못되었거나, 플러그인이 적재되지 않음cordis.yml 경로, ctx.on 문법 확인
이벤트가 너무 많아 읽기 어려움한 번의 대화에 자연스럽게 수십 개의 이벤트가 발생먼저 turn/startturn/end 사이를 살펴보면, 그것이 메인 트렁크
대화 안에 직접 콘텐츠를 그리고 싶음React 렌더러가 필요, 고급먼저 이벤트 스트림과 설정에 익숙해진 뒤, 이후에 고급으로
Cannot find package 보고플러그인이 외부 의존성을 사용하지만 설치하지 않음플러그인 디렉터리에서 해당 패키지를 npm install

이 장에서 배운 것

아래 항목들을 직접 해낼 수 있으면 합격입니다:

  • [ ] UI 플러그인의 세 가지 능력 범주를 말할 수 있다: 스킨 변경 / 레이아웃 & 패널 / 세션 콘텐츠 기여
  • [ ] "인터페이스는 이벤트 스트림의 소비자"라는 핵심 메커니즘을 이해한다
  • [ ] ctx.on('session/event', ...)로 세션 이벤트를 구독할 수 있다
  • [ ] 이벤트 관찰자를 사용해 한 번의 대화 뒤의 이벤트 스트림을 확인한다
  • [ ] UI 플러그인이 설정 페이지에 커스텀 탭을 추가할 수 있음을 안다

Open Source · MIT · Community Driven