拡張機能を作る
@openchamber/sdk を使うと、OpenChamber に自分のパネルを追加できます。拡張機能とは、OpenChamber が右側のレールに表示する小さなウェブページです。アプリとは connectHost() で話します。現在のプロジェクトとセッションを読む、トーストを出す、チャット欄にテキストを入れる、タスクをセッションに添付する、そしてユーザーの承認があればセッションを開始してプロンプトを送る、といったことができます。
拡張機能は OpenChamber のウェブ版とデスクトップ版で動きます。VS Code とモバイルはまだ読み込みません。
拡張機能とは何か
3 つのファイルが入ったフォルダです。
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 で使ってください。
3 つのファイルすべてを含む、動作する完全な拡張機能は 例 ページにあります。コピーして、API と一覧を差し替えてください。
index.html からは通常の <script src="main.js"></script> で main.js を読み込みます。
作業中にインストールする
- OpenChamber をウェブかデスクトップで起動します。
- 設定 → 拡張機能 を開きます。
- フォルダの絶対パスを貼り付けて、追加をクリックします。
OpenChamber がマニフェストを読み、拡張機能が求めているものの一覧をダイアログで表示します(権限 を参照)。承認すると、レールにアイコンが現れます。クリックするとページが読み込まれます。
ローカルの .zip や、git リポジトリまたは zip ファイルへの https リンクも追加できます。更新できるのは git からのインストールだけです。package.json の version を上げてプッシュすると、ユーザーが次に 設定 → 拡張機能 を開いたときに OpenChamber が更新を提案します。URL の #tag や #branch で、どれを追従するかを固定できます。これらは 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 は任意です。Docker ソケットのようにウェブページからは届かないもののために、OpenChamber が拡張機能の隣で起動するローカルプロセスを宣言します。そのプロセスはユーザーのフルアクセス権限で、サンドボックスなしに動くため、承認ダイアログはそれについて警告します。ページでは対応できない場合にのみ宣言してください。パッケージ内のファイル GUEST_SERVICES.md を参照してください。 provides: ["browser"] を持つサービスはエージェントのブラウザーの代わりも務められます。サーバー上で browser.* 操作に応答するので、デスクトップアプリを開かなくてもエージェントがブラウズでき、ユーザーは設定 → OpenChamber ツールで選択します。このようなサービスにパネルは不要です。
権限
パネルを描くことと現在のセッションを読むことに権限は要りません。ユーザーの代わりに何かを行うものには要ります。それらを contributes.capabilities に列挙します。
| 権限 | 許可されること |
|---|---|
prompt | ユーザーのセッションにメッセージを送る(prompt({ send: true })、text 付きの startSession) |
sessions | プロジェクト、worktree、セッション状態を一覧表示し、登録済みプロジェクトでセッションを作成して開く |
files | 開いているプロジェクト内のファイルを読み書きする(相対パス付きの readFile、writeFile、listDir、stat) |
model | セッションの外で、ユーザーの Small Model による一回限りのテキスト生成を行う(generate) |
さらに 4 つが自動で追加されます。integration を宣言すると network、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: ユーザーがクライアント 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 に届きます。
ファイル
ページ自体はディスクに触れません。OpenChamber がユーザーの承認した範囲内で、代わりに読み書きします。
- 相対パス(
README.md、src/index.ts、.)は開いているプロジェクトを指します。files権限が必要です。開いているプロジェクトがない場合はNO_DIRECTORYです。 /または~/で始まるパスはそれ以外の場所を指します。contributes.filesystemで宣言したパターンのいずれかに一致する必要があり、ユーザーは承認ダイアログでそのパターンをそのまま目にします:
"filesystem": ["~/.config/opencode/opencode.json", "~/notes/**"]パターンでは * がパスの 1 セグメント、** が任意の深さを表します。パターンの外にあるものは、承認後であっても 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 です。
アクション、コマンド、バッジ
レールと + メニュー以外にも、拡張機能はさらに 3 か所に現れることができます。いずれも、チップのクリックと同じ形で拡張機能に 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"] }]メッセージのアクションは、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 だけを宣言して他は何も宣言しなくて構いません。
パネルの最初の数行
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 キット を参照してください。