본문으로 건너뛰기
피드

DOM을 직접 조작하는 오픈소스 웹 에이전트 SDK, dotdotduck 공개

frontend 약 14분
vote
0
댓글
북마크

dotdotduck은 기존 웹사이트 안에 삽입돼 DOM을 직접 보고 조작하는 AI 에이전트 SDK다. 챗봇을 구석에 붙이는 방식이 아니라, Ctrl+K 팔레트, 인라인 AI, 음성, Dwell 제스처, 번역, 분석까지 한 패키지로 제공하려는 쪽에 가깝다.

  • 1

    고객지원, 페이지 탐색, 추천, 번역, 폼 조작 같은 작업을 사이트 내부 DOM 기반 에이전트가 처리

  • 2

    v0.2.0에서 TaskAgent, WebAgent 멀티 인스턴스, 액션 번들 opt-in, 스트리밍 JSON 파서, 자체 분석 레이어가 추가

  • 3

    기본 설치 액션이 전체 12개에서 coreActions 5개로 바뀌는 breaking change가 있음

  • dotdotduck은 기존 사이트를 “AI-native site”로 바꾸겠다는 오픈소스 웹 에이전트 SDK임

    • 페이지 구석에 챗봇 하나 붙이는 방식이 아니라, 페이지 안에 들어와 DOM을 보고 조작하는 에이전트를 지향함
    • 사용자는 Ctrl/Command+K 팔레트, 인라인 UI, 음성, Dwell 제스처 같은 여러 진입점으로 맥락을 넘길 수 있음
  • 제일 큰 주장 중 하나는 고객지원 티켓의 상당수가 페이지 안에서 해결 가능하다는 것임

    • “주문 추적 어디서 해?”, “요금제 변경 어떻게 해?”, “이 기능 어디 있어?” 같은 질문은 이미 사이트 안에 답이 있는 경우가 많음
    • dotdotduck은 DOM 기반 에이전트가 그 페이지를 실제로 탐색하고 안내해서 쉬운 티켓 70%를 사람에게 가기 전에 deflect하겠다고 말함
  • 팔레트는 검색창이 아니라 작업 표면으로 설계돼 있음

    • 각 row의 detail pane과 PanelSkills는 차트, 테이블, 폼, 미니 대시보드 같은 Pieces tree를 렌더링할 수 있음
    • 금융 사이트에서는 AAPL을 입력하면 가격 카드와 sparkline을 보여줄 수 있음
    • 고객지원에서는 FAQ 링크만 던지는 대신 포맷된 답변을 바로 인라인으로 보여줄 수 있음
    • SaaS에서는 regex tester, JSON formatter, unit converter, 내부 lookup 같은 도구를 팔레트에 넣을 수 있음
  • Dwell 제스처는 “스크린샷 찍고 설명하기”를 줄이려는 기능임

    • 사용자가 요소를 길게 누르면 에이전트가 selector와 자동 스크린샷을 같이 받음
    • 차트, 대시보드 패널, 테이블 row처럼 말로 설명하기 귀찮은 대상을 바로 가리킬 수 있음
    • 의도가 손가락에서 LLM으로 바로 흐른다는 식의 UX를 노림

중요

> dotdotduck의 핵심은 답변 생성보다 “현재 페이지에서 실제 액션을 수행하는 AI 레이어”에 있음. 그래서 DOM 스냅샷, selector, action registry, session 처리가 제품의 중심임.

  • 번역 기능도 내장 모듈로 들어감

    • immersive translate는 현재 페이지의 모든 문단을 양쪽 언어로 나란히 렌더링함
    • 200문단짜리 글은 페이지당 약 7번의 LLM call로 batch 처리할 수 있다고 설명함
    • 영어 문서나 제품 카피를 한국어, 중국어, 일본어, 스페인어 사용자에게 보여줘야 하는 SaaS에는 꽤 솔깃한 포인트임
  • “여섯 벤더를 붙이지 말자”는 포지셔닝도 강함

    • 검색은 Algolia, 채팅은 Intercom, 분석은 Mixpanel, 음성은 Whisper 식으로 붙이는 대신 하나의 SDK로 묶겠다는 주장임
    • 팔레트, 에이전트, 인라인 AI, 음성, Dwell, proactive, analytics, immersive translate가 한 설치 단위로 제공됨
    • yes/no/multi-choice 응답은 사용자 의도가 명확한 강화학습(RL) 라벨로 기록할 수 있다고 봄

v0.2.0에서 바뀐 것들

  • 이번 릴리스는 webagent core의 구조 개편이 큼

    • breaking change로 기본 설치 액션이 전체 builtinActions가 아니라 coreActions가 됨
    • 기본 5개는 narrate, navigate, click, border, scroll_to임
    • formActions, flowActions, extraActions는 opt-in으로 넣어야 함
  • TaskAgent가 WebAgent, InlineAgent에 이어 세 번째 agent kind로 추가됨

    • DOM 없이 대화와 host-defined tool calling을 처리하는 plain protocol 에이전트임
    • ask()streamAsk()를 제공함
    • 같은 AgentSession shape을 써서 같은 세션에 연결된 여러 TaskAgent가 대화 히스토리를 공유할 수 있음
  • WebAgent는 멀티 인스턴스와 공유 세션을 지원함

    • dddk.sessions named-session registry와 dddk.agents named-instance registry가 생김
    • route별 persona가 다른 WebAgent를 두고, route change 때 dddk.agents.setActive(name)으로 전환하는 식의 구성이 가능함
  • 액션과 커서 관련 API도 많이 늘었음

    • hold_key, double_click, long_press, drag가 새로 들어옴
    • press_key는 modifiers를 지원함
    • narrate는 CoT 전용 primitive에서 action registry의 first-class action으로 승격됨
    • cursorTrail: true는 click, border, highlight, fill_input, scroll_to, narrate-with-about까지 커버함
  • planner가 DOM을 직접 보게 된 것도 큰 변화임

    • planning call마다 현재 페이지 snapshot이 hostContext로 들어감
    • briefed sitemap에 빠진 route나 link도 planner가 현재 화면에서 찾을 수 있음
    • 기본 cap은 plannerDomMaxLength 8000임
  • navigate에는 경로 검증이 붙음

    • sitemap에 없는 path는 reject함
    • 유효한 path list를 LLM에 돌려줘서 재시도하게 함
    • 환각 경로를 따라 404로 빠지는 루프를 줄이려는 장치임
  • 스트리밍 envelope parser는 반응성 개선 포인트임

    • scanner 기반 incremental JSON parser가 tool args의 { } 균형이 맞는 순간 action을 dispatch함
    • 바깥 envelope가 완전히 닫힐 때까지 기다리지 않아도 됨
    • enableStreamingEnvelope: true로 opt-in함
sequenceDiagram
    participant 사용자
    participant 팔레트
    participant 웹에이전트
    participant 현재페이지
    participant 엘엘엠
    사용자->>팔레트: 명령 입력 또는 요소 Dwell
    팔레트->>웹에이전트: 선택자와 페이지 맥락 전달
    웹에이전트->>현재페이지: DOM 스냅샷 수집
    웹에이전트->>엘엘엠: hostContext와 도구 목록 전달
    엘엘엠-->>웹에이전트: 도구 호출 JSON 스트리밍
    웹에이전트->>현재페이지: click/scroll/navigate 실행
    웹에이전트-->>사용자: subtitle 또는 feedback UI 표시

분석, 세션, 운영 쪽도 손봤다

  • live registry로 도구와 context provider를 런타임 등록/해제할 수 있음

    • webagent.registerTool(def)는 ToolHandle을 반환함
    • webagent.registerContextProvider(role, fn)도 handle을 반환하고, remove()로 제거할 수 있음
    • context provider를 제거하면 빈 슬롯이 아니라 SDK 기본값으로 복구됨
  • context provider는 6개 슬롯으로 분리됨

    • url, page_summary, dom, screenshot, history, selection이 기본 슬롯임
    • WebAgent constructor에서 기본 provider가 설치됨
    • 다만 dom은 selector resolution의 currentIndexMap과 엮여 있어 아직 완전 migration은 뒤로 미뤄짐
  • 자체 분석 레이어도 포함됨

    • @perhapxin/dddk/analytics는 IndexedDB 기반 EventStore를 제공함
    • toCSV, toNDJSON, toSQL exporter가 있음
    • SQLite, Postgres, MySQL용 canonical dddk_events DDL도 제공함
    • 미니 대시보드는 vanilla SVG 차트 6개와 영어/번체중문 라벨을 지원함
  • 세션 lifecycle은 더 보수적으로 바뀜

    • F5, Ctrl+R, Ctrl+Shift+R 같은 hard reload는 sessionContinuityMs와 무관하게 세션을 항상 지움
    • 기본 sessionContinuityMs는 5분에서 0으로 바뀜
    • 즉 host가 opt-in하지 않으면 각 ask가 자기 세션을 갖는 방식임

⚠️주의

> v0.2.0은 기본 action bundle이 바뀌는 breaking change가 있음. 기존에 전체 builtin action이 자동 설치된다고 가정한 코드는 migration guide를 봐야 함.

도입 전에 봐야 할 현실적인 부분

  • 프로젝트는 active development 상태라 rough edge를 예상하라고 직접 말함

    • 문서는 길지만, 작성·유지에 Claude Code를 사용한다고 밝힘
    • 뭔가 이상하면 문서보다 repo를 grep해서 실제 구현을 확인하라는 태도임
    • 공식 랜딩 페이지인 dddk.perhapxin.com도 실제 테스트베드로 사용됨
  • 모델 선택은 SDK에 고정돼 있지 않음

    • 데모는 webagent와 planner에 gpt-5.4-nano, InlineAgent와 voice cleanup에 gpt-5.4-mini를 쓴다고 설명함
    • 패키지는 OpenAI, Google, proxy, OpenAI-compatible vendor adapter를 제공함
    • DeepSeek, Qwen, OpenRouter 같은 baseURL 호환 벤더도 붙일 수 있음
    • 음성 인식은 기본적으로 브라우저 Web Speech API지만, 프로덕션은 Whisper나 Deepgram 같은 transcribe(audio) 확장을 연결하는 구조임
  • 설치와 사용은 프론트엔드 SDK답게 단순한 편임

    • pnpm add @perhapxin/dddk 또는 npm i @perhapxin/dddk로 설치함
    • DotDotDuckOpenAIProvider를 만들고 dddk.mount()를 호출함
    • CSS custom properties로 --dddk-bg, --dddk-accent, --dddk-radius, --dddk-font 등을 오버라이드할 수 있음
    • 다크 모드는 [data-theme="dark"]prefers-color-scheme: dark를 따라감
  • 라이선스는 AGPL-3.0-or-later임

    • 내부 서비스에 붙이는 SDK라면 라이선스 영향 범위를 반드시 검토해야 함
    • “오픈소스니까 바로 붙이자”가 아니라, 배포 형태와 수정 여부까지 따져야 하는 타입임

기술 맥락

  • dotdotduck의 기술적 선택은 챗봇을 제품 밖에 두지 않고, 제품 UI 안에 에이전트 런타임을 심는 쪽이에요. 그래서 자연어 답변보다 DOM snapshot, selector, action registry, session, feedback event가 더 중요해져요. 사용자가 원하는 건 설명이 아니라 현재 페이지에서 일이 끝나는 것이기 때문이에요.

  • planner가 DOM을 보게 한 것도 같은 이유예요. 사전에 준 sitemap만 믿으면 LLM이 없는 경로를 상상하거나, 현재 화면에 보이는 링크를 놓칠 수 있거든요. DOM을 hostContext로 넣으면 토큰 비용은 늘지만, 실제 페이지 상태에 근거한 계획을 세울 수 있어요.

  • 기본 액션을 전체 12개가 아니라 coreActions 5개로 줄인 건 보안과 예측 가능성 쪽 선택으로 보여요. 폼 입력, 드래그, 복잡한 플로우 액션을 기본으로 다 열어두면 데모는 화려하지만 호스트 입장에서는 제어면이 넓어지거든요. 필요한 bundle만 opt-in하는 구조가 운영에는 더 낫습니다.

  • 스트리밍 JSON 파서는 UX를 위한 구현 디테일이에요. LLM이 도구 호출 전체를 끝까지 출력할 때까지 기다리면 웹 액션이 굼떠 보이니까, tool args가 완성되는 순간 바로 dispatch하려는 거예요. 웹 에이전트는 사용자가 화면에서 기다리기 때문에 이런 수백 밀리초 단위의 반응성도 체감이 커요.

  • 자체 analytics를 넣은 건 단순 이벤트 로깅 이상의 의미가 있어요. yes/no, accept/reject 같은 명시적 사용자 피드백은 나중에 에이전트 평가나 파인튜닝 데이터가 될 수 있거든요. 외부 SaaS에 흩어지는 clickstream보다 “어떤 의도였고 어떤 액션을 받아들였는지”를 한 이벤트 모델로 모으려는 설계예요.

웹 에이전트가 별도 채팅창에서 답변만 하는 단계를 넘어, 실제 제품 UI 안에서 액션을 실행하는 방향으로 가는 사례임. 다만 AGPL 라이선스, 빠르게 바뀌는 API, AI로 작성된 문서라는 점 때문에 도입 전 코드를 직접 읽고 검증해야 함.

댓글

댓글

댓글을 불러오는 중...

frontend

개인 웹사이트에 JSON-LD 넣는 법, 검색엔진과 크롤러가 내 사이트를 제대로 읽게 만들기

개인 웹사이트에 JSON-LD 구조화 데이터를 추가해 검색엔진과 크롤러가 사이트, 사람, 글, 프로젝트를 더 정확히 이해하게 만드는 실전 가이드야. WebSite, Person, ProfilePage, BlogPosting 같은 노드를 어떻게 연결하고 어느 페이지에 넣어야 하는지 예시 중심으로 설명해.

frontend

Deno, 웹 프로젝트를 데스크톱 앱으로 묶는 `deno desktop` 공개

Deno가 TypeScript 파일 하나부터 Next.js 앱까지 데스크톱 앱으로 패키징하는 `deno desktop`을 공개했다. 아직 안정 릴리스는 아니고 Deno v2.9.0 canary에서만 쓸 수 있지만, 운영체제 WebView 기반의 작은 바이너리, 프레임워크 자동 감지, 내장 자동 업데이트까지 한 번에 노린다.

frontend

파비콘 안에 웹사이트를 숨겨 넣은 개발자, 진짜 됨

한 개발자가 웹사이트의 파비콘 이미지를 작은 저장소처럼 사용해 HTML을 픽셀 RGB 값 안에 넣고, 브라우저에서 다시 읽어 렌더링하는 실험을 했다. 208바이트짜리 HTML payload에 4바이트 길이 헤더를 붙여 총 212바이트를 만들었고, 이를 9x9 픽셀 PNG 안에 87% 사용률로 저장했다.

frontend

스크린이 절대 못 보여주는 색은 어디에 있을까

이 글은 우리가 화면에서 보는 색이 인간이 볼 수 있는 색 전체가 아니라, sRGB와 Display-P3 같은 색역 안에 갇힌 일부라는 점을 파고든다. 특히 숲, 바닷속, 새와 나비의 구조색, 생물발광, 교통신호 LED 같은 실제 세계에는 모니터와 카메라가 제대로 담지 못하는 청록색과 녹색 계열이 꽤 많다는 얘기다. 디스플레이, 카메라, 조명, 렌더링을 다루는 개발자라면 “색상값 하나”가 생각보다 물리와 표준의 타협이라는 걸 체감하게 된다.

frontend

크롬, 매니페스트 버전 2 우회로까지 닫는다

구글 크롬이 매니페스트 버전 2 확장 지원을 사실상 최종 종료 단계로 밀어넣고 있다. 기존에는 플래그나 레지스트리 설정으로 유블록 오리진 같은 확장을 살리는 우회가 있었지만, 크로미움 150과 151을 거치며 그 우회 코드까지 제거되는 흐름이다.