Tworzenie rozszerzenia
Użyj @openchamber/sdk, aby dodać własny panel do OpenChamber. Rozszerzenie to mała strona internetowa, którą OpenChamber pokazuje na prawym pasku bocznym. Komunikuje się z aplikacją przez connectHost(): może odczytać bieżący projekt i sesję, pokazywać toasty, wstawiać tekst do pola czatu, dołączać zadanie do sesji, a za zgodą użytkownika także uruchamiać sesje i wysyłać prompty.
Rozszerzenia działają w OpenChamber web i desktop. VS Code i wersja mobilna jeszcze ich nie ładują.
Czym jest rozszerzenie
Folder z trzema plikami:
package.jsonz blokiemopenchamber(manifest)panel/index.html, strona, którą pokazuje OpenChamberpanel/main.js, Twój skrypt, zbudowany do jednego klasycznego pliku (IIFE, nie moduł ES, bo strona ładuje się w iframe z sandboxem)
OpenChamber nigdy nie kompiluje Twojego kodu. Dostarczaj gotowy, zbudowany plik .js. SDK zawiera polecenie do bundlowania; zadziała też każdy inny bundler, który wypuszcza IIFE.
npm install @openchamber/sdkbunx openchamber-guest-bundle panel/main.ts panel/main.jsPolecenie bundlowania działa na Bun. Bez Buna użyj esbuild albo dowolnego innego bundlera z --format=iife --platform=browser.
Kompletne, działające rozszerzenie, wszystkie trzy pliki, znajdziesz na stronie Przykład. Skopiuj je i zmień API oraz listę.
Wskaż main.js w index.html zwykłym <script src="main.js"></script>.
Instalacja w trakcie pracy
- Uruchom OpenChamber w wersji web lub desktop.
- Otwórz Ustawienia → Rozszerzenia.
- Wklej bezwzględną ścieżkę do swojego folderu i kliknij Dodaj.
OpenChamber odczytuje manifest i pokazuje okno z listą tego, o co rozszerzenie prosi (zobacz Uprawnienia). Zatwierdź, a Twoja ikona pojawi się na pasku bocznym. Kliknij ją i Twoja strona się załaduje.
Możesz też dodać lokalny plik .zip albo link https do repozytorium git lub pliku zip. Tylko instalacja z git może się aktualizować: podnieś version w package.json, wypchnij zmiany, a OpenChamber zaproponuje aktualizację, gdy użytkownik następnym razem otworzy Ustawienia → Rozszerzenia. #tag albo #branch w adresie URL przypina, co jest śledzone. Te są kopiowane do folderu danych OpenChamber i uruchamiane z tej kopii, więc dostarczaj zbudowane pliki, a nie node_modules ani źródła TypeScript. Usunięcie rozszerzenia usuwa kopię. Instalacja z folderu działa bezpośrednio z Twojego folderu, więc możesz edytować, przebudowywać i przeładowywać.
Manifest
{ "name": "@acme/hello", "version": "1.0.0", "openchamber": { "apiVersion": 1, "engines": { "openchamber": ">=1.24.0" }, "contributes": { "panel": { "id": "acme-hello", "name": "Hello", "icon": "window", "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 jest wymagane i musi być zgodne z semver. Ustawienia → Rozszerzenia pokazuje je na karcie.
apiVersion to 1. OpenChamber odrzuca każdą inną wartość.
engines.openchamber jest opcjonalne. Podaj najstarszą wersję OpenChamber, z którą działa Twoje rozszerzenie, jako 1.24.0 lub >=1.24.0. Starsze wersje odmówią instalacji zamiast zawieść później.
contributes.panel opisuje wpis na pasku bocznym. id jest w kebab-case i musi być unikalne wśród zainstalowanych rozszerzeń. icon to nazwa ikony Remixicon (RiWindowLine staje się window) albo plik SVG w Twoim folderze, na przykład icon.svg. entry to plik HTML w Twoim folderze; pomiń je w rozszerzeniu, które deklaruje tylko tools (zobacz Twoje narzędzia w czacie).
contributes.attach jest opcjonalne. Dodaje Twoje rozszerzenie do menu + obok pola czatu, żeby użytkownik mógł wybrać zadanie i dołączyć je do sesji.
"dialog"otwiera Twoją stronę w oknietruelub"panel"otwiera zamiast tego panel na pasku bocznym{ "mode": "dialog", "entry": "panel/attach.html" }otwiera w oknie osobną Twoją stronę, żeby wybór nie musiał dzielić kodu z panelem na pasku bocznym- bez tego klucza rozszerzenie nie pojawia się w tym menu
ctx.surface ma wartość panel, dialog lub page. Kliknięcie dołączonego elementu przekazuje jego dane w ctx.item; w pozostałych przypadkach jest to null.
contributes.page: true udostępnia panel na pełnym ekranie w menu Strony rozszerzeń nad listą sesji. { "entry": "panel/page.html", "title": "Board" } wskazuje osobny HTML i opcjonalny tytuł. panel.entry jest wymagane, a izolacja i uprawnienia pozostają te same. Stronę otwiera tylko użytkownik. Przeładowanie, wstrzymanie, usunięcie lub zmiana serwera zamyka ją.
Tablica może używać host.storage oraz list projektów, worktree i sesji ze stanami na żywo. startSession może wskazać inny projekt bez zamykania tablicy. Zobacz Host API.
contributes.integration jest opcjonalne. Dodaje kartę w Ustawienia → Integracje, gdzie użytkownik podłącza konto. Zobacz Konta i sieć.
contributes.service jest opcjonalne. Deklaruje usługę lokalną (proces, który OpenChamber uruchamia obok rozszerzenia) do rzeczy, do których strona internetowa nie ma dostępu, na przykład socketu Dockera. Ten proces działa z pełnym dostępem użytkownika i bez piaskownicy, dlatego okno zatwierdzania o nim ostrzega; deklaruj taką usługę tylko wtedy, gdy strona nie jest w stanie wykonać tego zadania. Zobacz plik GUEST_SERVICES.md w pakiecie. Usługa z provides: ["browser"] może też zastąpić przeglądarkę agenta: odpowiada na działania browser.* na serwerze, więc agenci przeglądają bez otwartej aplikacji desktopowej, a użytkownik wybiera ją w Ustawienia → Narzędzia OpenChamber. Taka usługa nie potrzebuje panelu.
Uprawnienia
Rysowanie panelu i odczyt bieżącej sesji nie wymagają żadnej zgody. Wszystko, co działa w imieniu użytkownika, już tak. Wymień te rzeczy w contributes.capabilities:
| Uprawnienie | Na co pozwala |
|---|---|
prompt | wysyłanie wiadomości do sesji użytkownika (prompt({ send: true }), startSession z text) |
sessions | listy projektów, worktree i stanów sesji; tworzenie i otwieranie sesji w zarejestrowanych projektach |
files | odczyt i zapis plików w otwartym projekcie (readFile, writeFile, listDir, stat ze ścieżką względną) |
model | jednorazowe generowanie tekstu za pomocą Small Model użytkownika (generate), poza jakąkolwiek sesją |
Cztery kolejne są dodawane automatycznie: network, gdy deklarujesz integration, service, gdy deklarujesz service, filesystem, gdy deklarujesz wzorce filesystem, oraz conversation, gdy akcja sesji prosi o wiadomości (zobacz Akcje, polecenia i odznaka).
Użytkownik widzi pełną listę raz, przy instalacji rozszerzenia, i albo ją zatwierdza, albo usuwa rozszerzenie. Jeśli nowa wersja prosi o więcej, okno pojawia się ponownie. Wywołanie, które wymaga niezatwierdzonego uprawnienia, kończy się błędem NOT_GRANTED.
Generowanie tekstu
Dzięki uprawnieniu model host.generate prosi Small Model użytkownika o jednorazową odpowiedź: podsumowanie, tytuł, szkic. Nic nie trafia do sesji, historia nie jest zapisywana, a rozszerzenie nigdy nie wybiera dostawcy. OpenChamber używa tego samego modelu, którego używa do własnej pracy w tle, wybranego w Ustawienia → Sesje → Small Model, albo dobieranego automatycznie spośród dostawców, do których użytkownik jest zalogowany.
const { text } = await host.generate({ prompt: task.description, system: "Write a one-line summary. Return only the summary.", maxOutputTokens: 200,});Gdy żaden model nie jest dostępny, wywołanie kończy się błędem NO_MODEL; model, który zwrócił błąd, to MODEL_FAILED. Prompty są ograniczone do 64 000 znaków, a wywołanie czeka maksymalnie 90 sekund. Użytkownik widzi to uprawnienie jako „Użyj swojego Small Model” i kosztuje go to tokeny, więc trzymaj prompty krótkie i wywołuj to przy kliknięciu, a nie przy każdym naciśnięciu klawisza.
Konta i sieć
Twoja strona działa w sandboxie i nie może sama łączyć się z internetem. Zadeklaruj integration, a OpenChamber wykona wywołania za Ciebie przez host.request, dołączając token użytkownika. Token nigdy nie trafia do Twojej strony.
token: użytkownik wkleja token API na karcie w Ustawienia → Integracje.apiOriginto jedyny origin, któryrequestmoże wywołać.accountjest opcjonalne: ścieżka GET i nazwa pola, żeby karta mogła pokazać, kto jest podłączony.schemeokreśla, jak token jest wysyłany; zobacz tabelę poniżej. Sprawdź w dokumentacji API dostawcy, jakiego nagłówka oczekuje.oauth: użytkownik wkleja client id, a OpenChamber przeprowadza proces autoryzacji. WymagaauthorizeUrl,tokenUrliapiOrigin.host: { "provider": "linear" }: używa konta Linear już podłączonego w OpenChamber. Client id nie jest potrzebne.
Co wysyła każdy scheme:
scheme | Nagłówek wysyłany przez OpenChamber | Kiedy używać |
|---|---|---|
| pominięty | Authorization: <token> | API dokumentuje goły token w nagłówku |
"bearer" | Authorization: Bearer <token> | API dokumentuje token bearer lub personal access token |
"basic" | Authorization: Basic base64(username:token) | API dokumentuje HTTP Basic auth z nazwą użytkownika (często e-mail) i tokenem API; karta prosi o oba, a usernameLabel nazywa pierwsze pole |
settings dodaje do karty zwykłe pola tekstowe. Ich wartości trafiają do ctx.settings.
Pliki
Twoja strona nie może sama dotykać dysku. OpenChamber czyta i zapisuje za nią, w granicach zatwierdzonych przez użytkownika.
- Ścieżka względna (
README.md,src/index.ts,.) oznacza otwarty projekt. Wymaga uprawnieniafiles. Brak otwartego projektu toNO_DIRECTORY. - Ścieżka zaczynająca się od
/lub~/oznacza dowolne inne miejsce. Musi pasować do jednego ze wzorców zadeklarowanych wcontributes.filesystem, a użytkownik widzi dokładnie te wzorce w oknie zatwierdzania:
"filesystem": ["~/.config/opencode/opencode.json", "~/notes/**"]Wzorce używają * dla jednego segmentu ścieżki i ** dla dowolnej głębokości. Wszystko poza nimi to BAD_PATH, nawet po zatwierdzeniu. .. nigdy nie jest dozwolone.
const config = await host.readFile("~/.config/opencode/opencode.json");await host.writeFile("~/.config/opencode/opencode.json", nextJson);const { entries } = await host.listDir(".");Zapisy są atomowe: OpenChamber zapisuje plik tymczasowy i zmienia jego nazwę, więc czytający nigdy nie zobaczy pliku zapisanego do połowy. Pliki są czytane i zapisywane jako tekst UTF-8, do 2 MB.
Akcje, polecenia i odznaka
Poza paskiem bocznym i menu + rozszerzenie może pojawić się w trzech kolejnych miejscach. Wszystkie przekazują rozszerzeniu item tak samo jak kliknięcie chipa.
Akcje na wiadomościach i sesjach. Zadeklaruj pozycje menu, a OpenChamber pokaże je obok wbudowanych:
"actions": [ { "id": "create-task", "label": "Create task", "icon": "add-line", "where": "message", "roles": ["assistant"] }, { "id": "summarize", "label": "Summarize session", "where": "session", "payload": ["messages"] }]Akcja na wiadomości otwiera Twoje rozszerzenie z ctx.item o kind: "message": id i tytuł sesji, folder projektu, id wiadomości, jej rola i tekst. Akcja na sesji otwiera je z kind: "session" oraz id, tytułem i folderem sesji. Dodaj "payload": ["messages"], a element niesie też całą rozmowę, od najstarszej wiadomości, ten sam tekst, który daje eksport do Markdown. To wymaga uprawnienia conversation, które użytkownik widzi jako osobny wiersz w oknie zgody. item.action mówi, którą pozycję kliknięto.
Polecenia z ukośnikiem, które dołączają. Zadeklaruj polecenie i obsłuż je na stronie:
"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;});Gdy użytkownik wpisze /task ABC-12 w polu czatu, OpenChamber prosi Twoje rozszerzenie o rozwiązanie go i dołącza to, co zwrócisz, jako chip, niczego nie otwierając. Zwróć null dla “nic nie znaleziono”. Jeśli Twój panel jest zamknięty, OpenChamber ładuje go w tle na potrzeby tego wywołania. Nazwa, której OpenChamber lub OpenCode już używa, jest ignorowana.
Odznaka na ikonie na pasku bocznym. host.setBadge(3) pokazuje liczbę na Twojej ikonie, na przykład otwarte zadania; host.setBadge(null) ją usuwa. Otwarcie panelu też ją usuwa.
Twoje narzędzia w czacie
Gdy Twoja wtyczka OpenCode lub serwer MCP dodaje narzędzie, czat pokazuje jego wywołania z ogólną ikoną i surowym wynikiem. Zamiast tego zadeklaruj, jak mają wyglądać, bez kodu:
"tools": [ { "match": "mcp.jira.*", "name": "Jira", "icon": "task-line", "title": "{input.key}", "subtitle": "{output.status}", "output": "table", "columns": ["key", "summary", "status"] }]match to nazwa narzędzia w takiej postaci, w jakiej zgłasza ją OpenCode; * na końcu dopasowuje prefiks. icon to nazwa ikony Remixicon albo plik .svg w Twoim pakiecie, tak jak panel.icon. title i subtitle to szablony nad input, output i metadata wywołania. output wybiera sposób renderowania treści: text, json (drzewo), markdown, code z language, table z columns (wiersze pochodzą z tablicy wyniku lub z output.items) albo auto dla domyślnego. Dokładny match wygrywa z symbolem wieloznacznym, a przy remisie wygrywa pierwsze zainstalowane rozszerzenie. Żadna zgoda nie jest potrzebna: to zmienia tylko sposób rysowania danych, które już są w czacie.
Rozszerzenie, które tylko stylizuje narzędzia, nie potrzebuje żadnej strony: pomiń panel.entry, a nie będzie miało ikony na pasku bocznym, tylko kartę w Ustawienia → Rozszerzenia. Bez strony może deklarować tools i nic więcej.
Pierwsze linie w panelu
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 działa tylko wewnątrz OpenChamber. Po otwarciu jako zwykły plik każde wywołanie jest odrzucane z HOST_UNAVAILABLE.
Najpierw zbuduj panel z gotowych elementów @openchamber/sdk/ui: przycisków, pól, list rozwijanych, zakładek, list i innych, ostylowanych kolorami i czcionkami aplikacji, żeby panel wyglądał jak część OpenChamber. Własny HTML i CSS pisz tylko dla tego, czego w zestawie brakuje. Zobacz Zestaw UI.
Zobacz też
- Rozszerzenia o instalacji przez SSH i wyborze tożsamości Git serwera
- API hosta z każdą metodą
connectHost, jej limitami i kodami błędów - Zestaw UI z gotowymi elementami
- Przykład z kompletnym rozszerzeniem w trzech plikach do skopiowania