확장 만들기
@openchamber/sdk를 사용하면 OpenChamber에 나만의 패널을 추가할 수 있습니다. 확장은 OpenChamber가 오른쪽 레일에 표시하는 작은 웹 페이지입니다. 앱과는 connectHost()로 대화합니다. 현재 프로젝트와 세션 읽기, 토스트 표시, 채팅창에 텍스트 넣기, 작업을 세션에 첨부하기, 그리고 사용자가 승인하면 세션 시작과 프롬프트 전송까지 할 수 있습니다.
확장은 OpenChamber 웹과 데스크톱에서 실행됩니다. VS Code와 모바일은 아직 확장을 로드하지 않습니다.
확장이란
파일 세 개가 들어 있는 폴더입니다.
openchamber블록(매니페스트)이 있는package.jsonpanel/index.html, OpenChamber가 표시하는 페이지panel/main.js, 여러분의 스크립트. 단일 classic 파일(ES 모듈이 아닌 IIFE)로 빌드한 것입니다. 페이지가 샌드박스 iframe에서 로드되기 때문입니다
OpenChamber는 여러분의 코드를 컴파일하지 않습니다. 빌드된 .js 파일을 함께 배포하세요. SDK에는 번들러 명령이 포함되어 있습니다. IIFE를 출력하는 번들러라면 어떤 것이든 됩니다.
npm install @openchamber/sdkbunx openchamber-guest-bundle panel/main.ts panel/main.js이 번들러 명령은 Bun에서 실행됩니다. Bun이 없다면 esbuild나 다른 번들러를 --format=iife --platform=browser 옵션으로 사용하세요.
파일 세 개가 모두 들어 있는 완전히 동작하는 확장은 예제 페이지에 있습니다. 복사한 다음 API와 목록만 바꾸세요.
index.html에서는 일반적인 <script src="main.js"></script>로 main.js를 불러옵니다.
작업하면서 설치하기
- OpenChamber를 웹이나 데스크톱에서 실행합니다.
- 설정 → 확장 을 엽니다.
- 폴더의 절대 경로를 붙여넣고 추가를 클릭합니다.
OpenChamber가 매니페스트를 읽고, 확장이 요구하는 항목을 나열한 대화상자를 보여줍니다(권한 참고). 승인하면 레일에 아이콘이 나타납니다. 클릭하면 페이지가 로드됩니다.
로컬 .zip 파일이나 git 저장소 또는 zip 파일의 https 링크도 추가할 수 있습니다. 업데이트할 수 있는 것은 git 설치뿐입니다. package.json의 version을 올리고 푸시하면, OpenChamber가 실행 직후와 몇 시간마다 확인하고, 사용자에게 한 번 알린 뒤 설정 → 확장 에서 업데이트를 제안합니다. 어느 페이지에서든 openchamber://extensions/install?url=<encoded repository URL> 링크를 열면 해당 저장소가 추가 대화상자에 들어간 상태로 데스크톱 앱이 열리므로, 설치 버튼을 제공할 수 있습니다. URL의 #tag나 #branch로 무엇을 따라갈지 고정할 수 있습니다. 이들은 OpenChamber 데이터 폴더로 복사되어 그 복사본에서 실행되므로, node_modules나 TypeScript 소스가 아니라 빌드된 파일을 배포하세요. 확장을 제거하면 복사본도 삭제됩니다. 폴더 설치는 여러분의 폴더에서 직접 실행되므로 수정, 재빌드, 새로고침이 가능합니다.
매니페스트
{ "name": "@acme/hello", "version": "1.0.0", "openchamber": { "apiVersion": 1, "engines": { "openchamber": ">=1.24.0" }, "id": "acme-hello", "name": "Hello", "icon": "window", "contributes": { "panel": { "entry": "panel/index.html" }, "attach": "dialog", "page": true, "capabilities": ["prompt", "sessions"], "integration": { "name": "Acme", "description": "Tasks from Acme", "token": { "apiOrigin": "https://api.acme.example", "account": { "path": "/me", "name": "login" }, "scheme": "bearer" }, "settings": [{ "id": "list-id", "label": "List ID" }] } } }}version은 필수이며 semver여야 합니다. 설정 → 확장 의 카드에 표시됩니다.
apiVersion은 1입니다. OpenChamber는 다른 값을 거부합니다.
engines.openchamber는 선택 사항입니다. 확장이 동작하는 가장 오래된 OpenChamber 버전을 1.24.0 또는 >=1.24.0 형태로 지정하세요. 더 오래된 빌드는 나중에 실패하는 대신 설치 자체를 거부합니다.
id, name, icon은 확장을 식별합니다. id는 kebab-case이며 설치된 확장 중에서 고유해야 합니다. icon은 Remixicon 이름(RiWindowLine이면 window)이거나 폴더 안의 SVG 파일(예: icon.svg)입니다. contributes.panel은 레일에 표시되는 확장의 패널이며, 그 entry는 폴더 안의 HTML 파일입니다. 레일 패널이 없는 확장은 panel을 생략합니다. 오래된 패키지는 id, name, icon을 panel 안에 두며, 이 방식도 계속 작동합니다.
contributes.attach는 선택 사항입니다. 채팅창 옆의 + 메뉴에 확장을 추가해서, 사용자가 작업을 골라 세션에 첨부할 수 있게 합니다.
"dialog"는 페이지를 창으로 엽니다true또는"panel"은 대신 레일 패널을 엽니다{ "mode": "dialog", "entry": "panel/attach.html" }은 여러분의 별도 페이지를 창으로 열어서, 선택기가 레일 패널과 코드를 공유하지 않아도 되게 합니다- 생략하면 확장은 그 메뉴에 나타나지 않습니다
ctx.surface는 panel, dialog, page, status, block 중 하나입니다. 첨부 항목을 클릭하면 해당 데이터가 ctx.item에 들어가며, 그 외에는 null입니다.
contributes.page: true를 지정하면 세션 목록 위의 확장 프로그램 페이지 메뉴에서 전체 화면으로 열 수 있습니다. { "entry": "panel/page.html", "title": "Board" }로 별도의 HTML과 선택적 제목을 지정합니다. panel.entry는 필수이며 샌드박스와 권한은 같습니다. 사용자만 페이지를 열 수 있습니다. 새로고침, 일시 중지, 제거, 서버 전환 시 닫힙니다.
보드에는 host.storage와 프로젝트, worktree, 세션 목록 및 실시간 상태를 사용할 수 있습니다. startSession은 보드를 닫지 않고 다른 프로젝트를 지정합니다. Host API를 참고하세요.
contributes.integration은 선택 사항입니다. 설정 → 통합 에 카드를 추가하고, 사용자는 거기서 계정을 연결합니다. 계정과 네트워크를 참고하세요.
contributes.service는 선택 사항입니다. Docker 소켓처럼 웹 페이지가 닿을 수 없는 것을 위해, OpenChamber가 확장 옆에서 시작하는 로컬 프로세스를 선언합니다. 그 프로세스는 사용자의 전체 권한으로, 샌드박스 없이 실행되므로 승인 대화상자가 이를 경고합니다. 페이지로 처리할 수 없을 때만 선언하세요. 패키지 파일 GUEST_SERVICES.md를 참고하세요. provides: ["browser"]를 가진 서비스는 에이전트의 브라우저를 대신할 수도 있습니다. 서버에서 browser.* 동작에 응답하므로 데스크톱 앱을 열지 않아도 에이전트가 탐색할 수 있고, 사용자는 설정 → OpenChamber 도구에서 선택합니다. 이런 서비스에는 패널이 필요 없습니다. 서비스는 사용자의 세션에서 일어나는 일을 전달받을 수도 있습니다. 그 옆에 contributes.sessionEvents를 선언하면 OpenChamber가 끝나거나 실패한 각 턴과 에이전트가 요청한 각 권한이나 질문을 모든 프로젝트에서, 창을 열지 않아도 서비스로 보냅니다. 세션 이벤트를 참고하세요. 그리고 provides: ["notifications"]를 가진 서비스는 데스크톱과 휴대폰에 더해 Slack, Matrix 또는 직접 운영하는 서버로 사용자의 알림을 전달합니다. 사용자는 설정 → 통합에서 계정을 연결하고 토큰은 OpenChamber에 남습니다. 알림 채널을 참고하세요. provides: ["tracker"]를 가진 서비스는 Linear 옆의 작업 항목 트래커이며, 계정 카드는 확장 계정 아래에 놓입니다. 트래커를 참고하세요. 그리고 provides: ["quota"]를 가진 서비스는 매니페스트에 나열한 제공자에 대해 OpenCode가 보관하는 자격 증명을 읽어 내장 제공자 옆에서 사용량을 보고합니다. 사용량 제공자를 참고하세요.
권한
패널을 그리는 것과 현재 세션을 읽는 것에는 권한이 필요 없습니다. 사용자를 대신해 무언가를 하는 것에는 필요합니다. 그런 것들을 contributes.capabilities에 나열하세요.
| 권한 | 허용되는 것 |
|---|---|
prompt | 사용자의 세션에 메시지 보내기(prompt({ send: true }), text가 있는 startSession) |
sessions | 프로젝트, worktree, 세션 상태 조회 및 등록된 프로젝트에서 세션 생성과 열기 |
files | 열린 프로젝트 안의 파일 읽고 쓰기(상대 경로를 쓰는 readFile, writeFile, listDir, stat) |
model | 세션 밖에서 사용자의 Small Model로 일회성 텍스트 생성(generate) |
열한 가지가 자동으로 추가됩니다. integration을 선언하면 network, service를 선언하면 service, filesystem 패턴을 선언하면 filesystem, origins를 선언하면 origins, 세션 작업이 메시지를 요청하면 conversation(작업, 명령, 배지 참고), sessionEvents나 sessionControl이 content를 요청하면 transcript(세션 이벤트 참고), 서비스가 세션을 다루면 sessionControl(OpenChamber 호출 참고), 서비스가 사용자 대신 에이전트의 권한 요청과 폼에 응답하면 sessionAnswers(sessionControl: { answers: true }), 에이전트가 읽는 내용에 추가하면 instructions, contributes.skills를 선언하면 skills입니다. contributes.skills는 패키지 안의 스킬 폴더로, 확장이 켜져 있는 동안 모든 에이전트에게 전달됩니다. contributes.opencodePlugin을 선언하면 opencodePlugin도 추가됩니다(아래 참고).
사용자는 확장을 설치할 때 전체 목록을 한 번 보고, 승인하거나 확장을 제거합니다. 새 버전이 더 많은 권한을 요구하면 대화상자가 다시 나타납니다. 사용자가 승인하지 않은 권한이 필요한 호출은 NOT_GRANTED로 실패합니다.
패키지에 OpenCode 플러그인 포함하기
확장은 OpenCode 플러그인을 함께 제공할 수 있어서 사용자가 두 가지 대신 하나만 설치하면 됩니다. "opencodePlugin": "plugin"을 선언하세요. 패키지 안의 폴더이며, 그 루트에 index.js 또는 server.js가 있어야 합니다. OpenCode는 main을 읽지 않으므로 진입 파일을 다른 곳에 두려면 폴더의 package.json에 name과 exports를 지정하세요. 진입 파일은 OpenCode가 불러오는 형태인 { id, setup }을 기본 내보내기 합니다. OpenCode는 아무것도 설치되지 않은 당신의 폴더에서 단순한 import()로 불러오므로 진입 파일은 아무것도 가져오지 않습니다. 도구의 input은 순수 JSON Schema이고, execute는 { content }를 답합니다.
{ "contributes": { "panel": { "id": "teapot-tools", "name": "Teapot Tools", "icon": "cup" }, "opencodePlugin": "plugin" }}승인 대화상자에 OpenCode에 플러그인 추가 행이 하나 더 생기며, 플러그인 코드가 사용자의 모든 권한으로 OpenCode 안에서 실행된다고 분명히 말합니다. 허용되면 OpenChamber는 자신이 관리하는 OpenCode 설정, 즉 contributes.skills를 싣는 것과 같은 파일에 당신의 폴더를 넣습니다. OpenChamber가 실행하는 OpenCode는 그 파일을 감시하고 몇 초 안에 플러그인을 불러옵니다. 확장을 끄거나 제거하면 같은 방식으로 빠집니다. 사용자의 opencode.json에는 아무것도 쓰지 않습니다. 그 파일은 사용자의 것이며, 거기 있는 플러그인은 확장보다 오래 남아 이 컴퓨터의 모든 OpenCode에서 실행될 것이기 때문입니다.
그 파일이 OpenCode에 닿지 않는 경우 확장의 화면이 그렇게 말하고 전역 설정에 추가를 제안합니다. 이는 당신의 폴더를 사용자의 전역 OpenCode 설정에, 그때부터 사용자 소유인 항목으로 씁니다. OpenChamber가 대화 상대인 OpenCode를 시작하지 않았을 때, 또는 서버 환경이 자체 OPENCODE_CONFIG를 설정했을 때(이때 플러그인은 다음 OpenCode 시작 시 불러와집니다) 해당합니다. 다른 컴퓨터의 OpenCode는 이 컴퓨터의 폴더를 불러올 수 없습니다. 화면은 그렇게 말하고 아무것도 제안하지 않습니다. 불러오기에 실패한 플러그인은 항목이 있을 때 설정 → Plugins에서 OpenCode가 보고합니다. 어느 경우든 확장은 계속 동작합니다. 플러그인 폴더가 없거나 그 package.json에 진입 파일이 없는 패키지는 설치가 거부됩니다.
VS Code와 모바일 앱은 확장을 불러오지 않으므로 플러그인도 전달되지 않습니다. 격리된 공간 안의 세션은 자체 OpenCode를 실행하며 호스트의 설정을 읽지 않습니다. 엔터프라이즈 모드는 플러그인을 로컬 서비스처럼 다룹니다. 관리자가 등록한 저장소에서만 설치됩니다. SDK에는 동작하는 예제 examples/opencode-plugin이 있습니다.
텍스트 생성
model 권한이 있으면 host.generate는 사용자의 Small Model에 요약, 제목, 초안 같은 일회성 답변을 요청합니다. 세션에는 아무것도 들어가지 않고 기록도 남지 않으며, 확장이 프로바이더를 고르는 일도 없습니다. OpenChamber는 자신의 백그라운드 작업에 쓰는 것과 같은 모델을 사용합니다. 설정 → 세션 → Small Model 에서 고른 모델, 사용자가 작업 중인 프로바이더의 small model, 또는 사용자의 OpenCode 설정에 있는 기본 모델입니다.
const { text } = await host.generate({ prompt: task.description, system: "Write a one-line summary. Return only the summary.", maxOutputTokens: 200,});사용할 수 있는 모델이 없으면 호출은 NO_MODEL로 실패하고, 모델이 오류를 내면 MODEL_FAILED입니다. 프롬프트는 최대 64,000자이며, 호출은 최대 90초까지 기다립니다. 사용자에게는 이 권한이 “Small Model 사용”으로 표시되고 토큰을 사용하므로, 프롬프트는 짧게 유지하고 키 입력마다가 아니라 클릭할 때 호출하세요.
계정과 네트워크
페이지는 샌드박스에서 실행되며 스스로 네트워크에 접근할 수 없습니다. 스크립트, 스타일, 이미지, 글꼴, 미디어는 패키지에서만 불러오고, fetch도 패키지 안의 파일에만 닿습니다. 글꼴과 이미지는 CDN에서 불러오지 말고 패키지에 포함하세요.
외부 서비스와 통신하려면 integration을 선언하세요. OpenChamber가 host.request를 통해 사용자의 토큰을 붙여 대신 호출합니다. 토큰은 절대 페이지에 도달하지 않습니다.
CDN의 글꼴이나 이미지, 공개 API처럼 페이지가 직접 필요로 하는 그 밖의 것에는 https origin을 최대 8개까지 나열하세요.
"origins": ["https://fonts.example.com"]사용자는 승인 대화상자에서 이를 확장이 데이터를 보내고 받을 수 있는 주소로 보게 되며, 업데이트로 origin이 추가되면 다시 승인합니다. 승인된 origin은 fetch, 이미지, 글꼴, 스타일, 미디어에는 열리지만 스크립트에는 절대 열리지 않습니다. 코드는 패키지에 포함되어야 합니다. 페이지의 origin은 null이므로 fetch 응답을 읽으려면 서버가 그 origin에 대해 CORS를 허용해야 합니다.
엔터프라이즈 모드에서는 origins, integration, service 중 하나라도 있는 확장은 관리자가 목록에 넣은 Git 저장소에서만 설치됩니다. 이것들 없이도 동작하는 확장이라면 그런 팀에서도 설치할 수 있습니다. 그런 컴퓨터에서 확장을 개발하려면 관리자에게 allowLocalExtensions를 요청하세요. 그러면 확장을 폴더에서 바로 설치할 수 있습니다.
token: 사용자가 설정 → 통합 카드에 API 토큰을 붙여넣습니다.apiOrigin은request가 호출할 수 있는 유일한 origin입니다.account는 선택 사항으로, GET 경로와 필드 이름을 지정하면 카드에 누가 연결되어 있는지 표시할 수 있습니다.scheme은 토큰을 보내는 방식을 지정합니다. 아래 표를 참고하세요. 어떤 헤더를 기대하는지는 프로바이더의 API 문서에서 확인하세요.oauth: 사용자가 클라이언트 ID를 붙여넣고, OpenChamber가 인증 흐름을 실행합니다.authorizeUrl,tokenUrl,apiOrigin이 필요합니다.host: { "provider": "linear" }: OpenChamber에 이미 연결된 Linear 계정을 재사용합니다. 클라이언트 ID가 필요 없습니다.
각 scheme이 보내는 것:
scheme | OpenChamber가 보내는 헤더 | 사용 시점 |
|---|---|---|
| 생략 | Authorization: <token> | API가 헤더에 접두어 없는 토큰을 문서화한 경우 |
"bearer" | Authorization: Bearer <token> | API가 bearer 토큰 또는 personal access token을 문서화한 경우 |
"basic" | Authorization: Basic base64(username:token) | API가 사용자 이름(흔히 이메일)과 API 토큰을 쓰는 HTTP Basic 인증을 문서화한 경우. 카드는 둘 다 묻고, usernameLabel이 첫 번째 입력란의 이름이 됩니다 |
settings는 카드에 일반 텍스트 입력란을 추가합니다. 그 값은 ctx.settings로 전달됩니다. "secret": true인 입력란은 카드에서 가려지고 페이지에는 전달되지 않습니다. 서비스는 readSettings()로 읽습니다.
파일
페이지 자체는 디스크를 건드릴 수 없습니다. OpenChamber가 사용자가 승인한 범위 안에서 대신 읽고 씁니다.
- 상대 경로(
README.md,src/index.ts,.)는 열린 프로젝트를 뜻합니다.files권한이 필요합니다. 열린 프로젝트가 없으면NO_DIRECTORY입니다. /또는~/로 시작하는 경로는 그 밖의 어디든 뜻합니다.contributes.filesystem에 선언한 패턴 중 하나와 일치해야 하며, 사용자는 승인 대화상자에서 그 패턴을 그대로 봅니다:
"filesystem": ["~/.config/opencode/opencode.json", "~/notes/**"]패턴에서 *는 경로 세그먼트 하나, **는 임의의 깊이를 뜻합니다. 패턴 밖의 것은 승인 후에도 BAD_PATH입니다. ..는 절대 허용되지 않습니다.
const config = await host.readFile("~/.config/opencode/opencode.json");await host.writeFile("~/.config/opencode/opencode.json", nextJson);const { entries } = await host.listDir(".");쓰기는 원자적입니다. OpenChamber가 임시 파일에 쓴 뒤 이름을 바꾸므로, 읽는 쪽이 반쯤 쓰인 파일을 보는 일은 없습니다. 파일은 UTF-8 텍스트로 읽고 쓰며, 최대 2 MB입니다.
Work Status의 섹션
contributes.statusSection은 채팅 옆 Work Status 패널에 직접 만든 섹션을 추가합니다. 사용자는 기본 섹션처럼 그 패널의 섹션 목록에서 켜고 끄거나 순서를 바꿀 수 있습니다.
"statusSection": { "entry": "status/index.html", "title": "Recent commits", "height": 160 }true는panel.entry를 그대로 씁니다.entry는 폴더 안의 별도 HTML 페이지입니다. 이것이 있으면panel.entry가 필요 없으므로, 레일 아이콘 없이 이 섹션만 있는 확장도 됩니다.title은 섹션 제목에서 패널 이름을 대신하며, 최대 60자입니다.height는 픽셀 단위 시작 높이로 24~320이며 기본값은 120입니다. 콘텐츠 크기가 바뀌면host.setHeight()를 호출하세요.
페이지에서 ctx.surface는 status입니다. OpenChamber는 Work Status 패널이 보이고 섹션이 펼쳐져 있는 동안에만 페이지를 불러오고, 사용자가 섹션을 접으면 내립니다. 그러니 다시 만들 수 없는 상태는 두지 마세요. 섹션만 있는 확장은 권한, 서비스, 통합, 파일을 쓸 수 있지만 page, attach, actions, commands는 쓸 수 없습니다. 웹과 데스크톱 전용입니다. git-graph-status 예제가 완성된 섹션입니다.
내 파일 형식용 편집기
contributes.fileEditors는 파일을 텍스트 편집기 대신 내 페이지에서 엽니다. 사용자가 Files에서 일치하는 파일을 열면 OpenChamber가 그 자리에 편집기를 보여 주며, 기본 제공 뷰어와 같은 소스 전환, 전체 화면 버튼, 저장 버튼이 함께 표시됩니다.
"fileEditors": [ { "id": "canvas", "title": "Excalidraw", "match": ["*.excalidraw", "*.excalidraw.md"], "entry": "editor/index.html" }]match는 대소문자를 구분하지 않고 파일 이름만 비교합니다.*는 임의 길이의 문자열과,?는 한 글자와 일치합니다. 패턴에는/를 넣을 수 없고 와일드카드만으로 이루어질 수도 없습니다.entry는 폴더 안의 HTML 페이지입니다.panel.entry가 필요 없으므로, 레일 아이콘 없이 편집기만 있는 확장도 됩니다.content는"text"(기본값) 또는"binary"입니다. 아래를 참고하세요.- 확장당 편집기는 최대 8개, 편집기마다 패턴은 최대 16개입니다.
파일은 OpenChamber가 관리합니다. 페이지는 파일을 직접 읽거나 쓰지 않으므로 그에 대한 권한도 필요 없습니다. OpenChamber가 텍스트를 넘겨주고, 저장할 때 편집된 텍스트를 요청하며, Cmd/Ctrl+S, 자동 저장, 저장하지 않은 변경 확인, 줄 끝 문자, 디스크에서 일어난 변경을 처리합니다:
import { connectHost, createFileSaveTracker } from "@openchamber/sdk";
const host = connectHost();let tracker = createFileSaveTracker(null);
host.onFileOpen((file) => { if (file.encoding !== "text") return; // 텍스트 편집기는 항상 텍스트를 받습니다 load(file.content); // file.path, file.name, file.readOnly도 있습니다 tracker = createFileSaveTracker(versionOf(currentText()));});
// 편집할 때마다 호출하세요. `edited`는 사용자가 멈출 때까지 자동 저장을 미룹니다.const changed = () => host.reportFileChange(tracker.observe(versionOf(currentText())));
host.onFileSnapshot(() => ({ content: currentText(), version: versionOf(currentText()) }));
// OpenChamber가 쓰는 동안 생긴 편집은 파일을 저장되지 않은 상태로 남깁니다.host.onFileSaved((version) => host.reportFileChange({ dirty: tracker.markSaved(version), edited: false }));version은 문서가 바뀌면 함께 바뀌는 짧은 문자열이면 무엇이든 됩니다(예: 해시). 편집기가 열 수 없는 파일이면 host.reportFileUnsupported()를 호출하세요. OpenChamber가 안내 문구와 함께 소스를 보여 줍니다. 20 MB가 넘는 파일은 소스 보기에 머뭅니다.
바이너리 파일
스프레드시트, 문서, 이미지처럼 텍스트가 아닌 파일을 열려면 "content": "binary"를 설정하세요. 편집기는 파일의 바이트를 받고 바이트를 돌려줍니다. 나머지는 모두 똑같이 동작합니다.
host.onFileOpen((file) => { if (file.encoding !== "binary") return; open(file.bytes); // 디스크에 있는 그대로의 Uint8Array});
host.onFileSnapshot(() => ({ bytes: currentBytes(), version: versionOf(currentBytes()) }));바이너리 편집기는 패턴이 일치하는 파일이라면 무엇이든 맡을 수 있으며, 이미지나 PDF처럼 OpenChamber가 직접 미리 보여 주는 파일도 포함됩니다. 소스 보기는 없고, 저장하지 않은 변경이 있는 상태에서 사용자가 편집기를 전체 화면으로 전환하거나 전체 화면에서 나오면 OpenChamber가 먼저 저장합니다. 형식을 읽고 쓰는 일은 편집기의 몫입니다. OpenChamber는 반환한 바이트를 그대로 다시 씁니다.
페이지에서 ctx.surface는 file입니다. 디스크에서 파일이 바뀌거나, 사용자가 변경을 버리거나, 편집기가 전체 화면으로 들어가거나 나올 때 OpenChamber는 페이지를 다시 불러오고 파일을 다시 넘겨줍니다. 패턴이 일치하는 활성화된 확장 중 첫 번째가 선택되며, OpenChamber 자체 미리 보기보다 우선합니다. 편집기만 있는 확장은 권한, 서비스, 통합, 파일을 쓸 수 있지만 page, attach, actions, commands는 쓸 수 없습니다. 웹과 데스크톱 전용입니다. checklist-editor 예제가 완성된 편집기입니다.
작업, 명령, 배지
레일과 + 메뉴 외에도 확장은 세 곳에 더 나타날 수 있습니다. 모두 칩을 클릭했을 때와 같은 방식으로 확장에 item을 넘깁니다.
메시지와 세션의 작업. 메뉴 항목을 선언하면 OpenChamber가 기본 항목 옆에 보여줍니다.
"actions": [ { "id": "create-task", "label": "Create task", "icon": "add-line", "where": "message", "roles": ["assistant"] }, { "id": "summarize", "label": "Summarize session", "where": "session", "payload": ["messages"] }]where에는 selection(메시지에서 선택한 텍스트), file(Files의 행), diff(파일이나 diff에서 선택한 줄)도 쓸 수 있습니다. 이들의 항목에는 같은 이름의 kind가 붙습니다. selection에는 텍스트, file에는 경로, diff에는 경로와 줄 범위가 들어 있습니다. 선택 영역에서는 display가 작업의 표시 방식을 정합니다. label(기본값), 레이블을 툴팁으로 보여 주는 icon, 또는 both입니다.
메시지 작업은 kind: "message"인 ctx.item과 함께 확장을 엽니다. 세션 id와 제목, 프로젝트 폴더, 메시지 id, 역할, 텍스트가 들어 있습니다. 세션 작업은 kind: "session"과 세션 id, 제목, 폴더와 함께 엽니다. "payload": ["messages"]를 추가하면 항목에 대화 전체가 오래된 순서로 담기며, Markdown 내보내기가 만드는 것과 같은 텍스트입니다. 여기에는 conversation 권한이 필요하고, 사용자는 승인 대화 상자에서 이를 별도 줄로 봅니다. item.action은 어떤 항목을 클릭했는지 알려줍니다.
첨부하는 슬래시 명령. 명령을 선언하고 페이지에서 처리합니다.
"commands": [{ "name": "task", "description": "Attach a task by id" }]host.onResolve(async ({ command, args }) => { const task = await findTask(args.trim()); return task ? { providerId: "acme-tasks", id: task.id, title: task.title, url: task.url } : null;});사용자가 채팅창에 /task ABC-12를 입력하면 OpenChamber는 확장에 해석을 요청하고, 돌려준 것을 아무것도 열지 않은 채 칩으로 첨부합니다. 찾은 것이 없으면 null을 반환하세요. 패널이 닫혀 있으면 OpenChamber가 이 호출을 위해 백그라운드에서 패널을 불러옵니다. OpenChamber나 OpenCode가 이미 쓰는 이름은 무시됩니다.
레일 아이콘의 배지. host.setBadge(3)은 아이콘에 점을 표시하고 숫자(예: 열린 작업 수)를 툴팁에 보여 주며, host.setBadge(null)은 지웁니다. 패널을 열어도 지워집니다.
채팅에 표시되는 내 도구
OpenCode 플러그인이나 MCP 서버가 도구를 추가하면 채팅은 그 호출을 일반 아이콘과 원본 출력으로 보여줍니다. 대신 코드 없이 어떻게 보일지 선언하세요.
"tools": [ { "match": "mcp.jira.*", "name": "Jira", "icon": "task-line", "title": "{input.key}", "subtitle": "{output.status}", "output": "table", "columns": ["key", "summary", "status"] }]match는 OpenCode가 보고하는 도구 이름이며, 끝의 *는 접두사와 일치합니다. icon은 Remixicon 이름이거나 패키지 안의 .svg 파일이며, panel.icon과 같습니다. title과 subtitle은 호출의 input, output, metadata를 쓰는 템플릿입니다. output은 본문을 그리는 방식을 고릅니다. text, json(트리), markdown, language가 있는 code, columns가 있는 table(행은 출력 배열이나 output.items에서 가져옵니다), 또는 기본값인 auto입니다. 정확한 match가 와일드카드보다 우선하고, 동점이면 먼저 설치된 확장이 이깁니다. 권한은 필요 없습니다. 이미 채팅에 있는 데이터를 그리는 방식만 바꾸기 때문입니다.
도구 모양만 꾸미는 확장에는 페이지가 전혀 필요 없습니다. panel.entry를 생략하면 레일에 아이콘이 없고, 설정 → 확장 에 카드만 표시됩니다. 페이지가 없으면 tools만 선언하고 다른 것은 선언하지 않아도 됩니다. 또 다른 예외는 자체 entry가 있는 Work Status 섹션이나 파일 편집기입니다(위 참고).
패널의 첫 줄
import { connectHost } from "@openchamber/sdk";
const host = connectHost();
host.onReady((ctx) => { document.body.dataset.theme = ctx.theme.mode; document.body.dataset.surface = ctx.surface;});connectHost는 OpenChamber 안에서만 동작합니다. 일반 파일로 열면 모든 호출이 HOST_UNAVAILABLE로 reject됩니다.
패널은 먼저 @openchamber/sdk/ui의 구성 요소로 만드세요. 버튼, 입력란, 드롭다운, 탭, 목록 등이 앱의 색상과 글꼴로 스타일링되어 있어 패널이 OpenChamber의 일부처럼 느껴집니다. 직접 HTML과 CSS를 작성하는 것은 키트에 없는 것에만 한정하세요. UI 키트를 참고하세요.