Зробити розширення
Використовуйте @openchamber/sdk, щоб додати власну панель в OpenChamber. Розширення - це невелика вебсторінка, яку OpenChamber показує на правій рейці. Із застосунком вона спілкується через connectHost(): може читати поточний проєкт і сесію, показувати тости, вставляти текст у поле чату, прикріплювати задачу до сесії, а зі згоди користувача ще й створювати сесії та надсилати повідомлення.
Розширення працюють в OpenChamber у вебі та на десктопі. VS Code і мобільний застосунок їх поки не завантажують.
Що таке розширення
Тека з трьома файлами:
package.jsonз блокомopenchamber(маніфест)panel/index.html, сторінка, яку показує OpenChamberpanel/main.js, ваш скрипт, зібраний в один класичний файл (IIFE, не ES-модуль, бо сторінка завантажується в ізольованому 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 та список.
Підключіть main.js в index.html звичайним <script src="main.js"></script>.
Встановити під час роботи
- Запустіть OpenChamber у вебі або на десктопі.
- Відкрийте Налаштування → Розширення.
- Вставте абсолютний шлях до своєї теки і натисніть Додати.
OpenChamber читає маніфест і показує діалог зі списком того, що просить розширення (див. Дозволи). Підтвердьте, і ваша іконка з’явиться на рейці. Натисніть її, і сторінка завантажиться.
Можна також додати локальний .zip або https-посилання на git-репозиторій чи zip-файл. Оновлюватися вміє лише встановлення з git: підніміть version у package.json і запуште, і OpenChamber запропонує оновлення, коли користувач наступного разу відкриє Налаштування → Розширення. #tag чи #branch в URL закріплює, за чим саме воно стежить. Їх OpenChamber копіює у свою теку даних і запускає з тієї копії, тому віддавайте зібрані файли, а не node_modules чи TypeScript-джерела. Видалення розширення видаляє копію. Розширення з теки працює прямо з вашої теки, тож можна редагувати, перезбирати й перезавантажувати.
Маніфест
{ "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 обов’язкова і має бути semver. Налаштування → Розширення показує її на картці.
apiVersion дорівнює 1. Будь-яке інше значення OpenChamber відхиляє.
engines.openchamber необов’язкове. Вкажіть найстарішу версію OpenChamber, з якою працює ваше розширення, як 1.24.0 або >=1.24.0. Старіші збірки відмовляться його встановлювати замість того, щоб ламатися пізніше.
contributes.panel описує пункт на рейці. id пишеться в kebab-case і має бути унікальним серед встановлених розширень. icon - це назва іконки Remixicon (RiWindowLine стає window) або SVG-файл у вашій теці, наприклад icon.svg. entry - HTML-файл у вашій теці; пропустіть його для розширення, яке оголошує лише tools (див. Ваші інструменти в чаті).
contributes.attach необов’язкове. Воно додає ваше розширення в меню + біля поля чату, щоб користувач міг вибрати задачу і прикріпити її до сесії.
"dialog"відкриває вашу сторінку у вікніtrueабо"panel"замість цього відкриває панель на рейці{ "mode": "dialog", "entry": "panel/attach.html" }відкриває у вікні окрему вашу сторінку, тож вибір не мусить ділити код із панеллю на рейці- без цього поля розширення в тому меню не з’являється
ctx.surface має значення panel, dialog або page. Натискання прикріпленого елемента передає його дані в 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 необов’язкове. Воно оголошує локальний сервіс (процес, який OpenChamber запускає поруч із розширенням) для речей, до яких вебсторінка не дотягнеться, наприклад сокета Docker. Цей процес працює з повним доступом користувача і без пісочниці, тому діалог підтвердження попереджає про нього; оголошуйте його лише тоді, коли сторінка не може впоратися із завданням. Див. файл пакета GUEST_SERVICES.md. Сервіс із provides: ["browser"] може також замінити браузер агента: він відповідає на дії browser.* на сервері, тож агенти працюють з вебом без відкритого десктопного застосунку, а користувач обирає його в Налаштування → Інструменти OpenChamber. Такому сервісу панель не потрібна.
Дозволи
Щоб намалювати панель і прочитати поточну сесію, дозвіл не потрібен. Він потрібен для всього, що діє від імені користувача. Перелічіть такі речі в contributes.capabilities:
| Дозвіл | Що дозволяє |
|---|---|
prompt | надсилати повідомлення в сесію користувача (prompt({ send: true }), startSession з text) |
sessions | переглядати проєкти, worktree та стани сесій; створювати й відкривати сесії в доданих проєктах |
files | читати й писати файли всередині відкритого проєкту (readFile, writeFile, listDir, stat з відносним шляхом) |
model | одноразова генерація тексту через Small Model користувача (generate), поза будь-якою сесією |
Ще чотири додаються автоматично: network, коли ви оголошуєте integration, service, коли оголошуєте service, filesystem, коли оголошуєте шаблони filesystem, і conversation, коли дія сесії просить повідомлення (див. Дії, команди та бейдж).
Користувач бачить повний список один раз, під час встановлення розширення, і або підтверджує його, або видаляє розширення. Якщо нова версія просить більше, діалог з’являється знову. Виклик, якому потрібен непідтверджений дозвіл, завершується помилкою NOT_GRANTED.
Генерація тексту
З дозволом model host.generate просить Small Model користувача дати одноразову відповідь: резюме, заголовок, чернетку. Нічого не потрапляє в сесію, історія не зберігається, і розширення ніколи не обирає провайдера. OpenChamber використовує ту саму модель, яку використовує для власної фонової роботи, обрану в Налаштування → Сесії → Small Model, або підібрану автоматично серед провайдерів, до яких увійшов користувач.
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», і це коштує йому токенів, тож тримайте промпти короткими й викликайте це по кліку, а не на кожне натискання клавіші.
Облікові записи і мережа
Ваша сторінка працює в пісочниці і не може ходити в інтернет напряму. Оголосіть integration, і OpenChamber робитиме запити за вас через host.request, додаючи токен користувача. Токен ніколи не потрапляє на вашу сторінку.
token: користувач вставляє API-токен на картці в Налаштування → Інтеграції.apiOrigin- єдине джерело, яке може викликатиrequest.accountнеобов’язкове: GET-шлях і назва поля, щоб картка могла показати, хто підключений.schemeкаже, як надсилати токен; див. таблицю нижче. Перевірте в документації API провайдера, який заголовок він очікує.oauth: користувач вставляє client id, а OpenChamber проводить авторизацію. ПотрібніauthorizeUrl,tokenUrlіapiOrigin.host: { "provider": "linear" }: використати обліковий запис Linear, який уже підключено в OpenChamber. Client id не потрібен.
Що надсилає кожен scheme:
scheme | Заголовок, який надсилає OpenChamber | Коли використовувати |
|---|---|---|
| не вказано | Authorization: <token> | API документує голий токен у заголовку |
"bearer" | Authorization: Bearer <token> | API документує bearer або personal access token |
"basic" | Authorization: Basic base64(username:token) | API документує HTTP Basic auth з іменем користувача (часто це email) і API-токеном; картка запитує обидва, а usernameLabel задає підпис першого поля |
settings додає на картку прості текстові поля. Їхні значення приходять у ctx.settings.
Файли
Ваша сторінка не може торкатися диска сама. 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 МБ.
Дії, команди та бейдж
Крім рейки і меню +, розширення може з’являтися ще в трьох місцях. Усі вони передають розширенню 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"] }]Дія на повідомленні відкриває ваше розширення з ctx.item виду kind: "message": 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, code з language, table з columns (рядки беруться з масиву виводу або з output.items) або auto для типового вигляду. Точний match перемагає шаблон із зірочкою, а за нічиєї виграє перше встановлене розширення. Дозвіл не потрібен: це змінює лише те, як малюються дані, що вже є в чаті.
Розширенню, яке лише оформлює інструменти, сторінка взагалі не потрібна: пропустіть panel.entry, і воно не матиме іконки на рейці, лише картку в Налаштування → Розширення. Без сторінки воно може оголошувати tools і більше нічого.
Перші рядки в панелі
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.
Спершу збирайте панель з готових елементів @openchamber/sdk/ui: кнопки, поля, випадні списки, вкладки, списки тощо, оформлені кольорами і шрифтами застосунку, щоб панель відчувалася частиною OpenChamber. Власний HTML і CSS пишіть лише для того, чого в наборі немає. Див. UI-набір.
Дивіться також
- Розширення про встановлення через SSH і вибір Git identity сервера
- Host API: кожен метод
connectHost, його обмеження і коди помилок - UI-набір: готові елементи
- Приклад: повне розширення з трьох файлів, яке можна скопіювати