@openchamber/sdk 1.23.2-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +529 -0
- package/DOCUMENTATION.md +81 -0
- package/GUEST_SERVICES.md +166 -0
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/dist/api-version.d.ts +6 -0
- package/dist/api-version.js +5 -0
- package/dist/contract.d.ts +471 -0
- package/dist/contract.js +256 -0
- package/dist/host-version.d.ts +16 -0
- package/dist/host-version.js +50 -0
- package/dist/host.d.ts +101 -0
- package/dist/host.js +606 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +7 -0
- package/dist/manifest.d.ts +298 -0
- package/dist/manifest.js +224 -0
- package/dist/parse.d.ts +279 -0
- package/dist/parse.js +379 -0
- package/dist/protocol.d.ts +1092 -0
- package/dist/protocol.js +450 -0
- package/dist/schemas.d.ts +4 -0
- package/dist/schemas.js +6 -0
- package/dist/ui/badge.d.ts +10 -0
- package/dist/ui/badge.js +26 -0
- package/dist/ui/banner.d.ts +13 -0
- package/dist/ui/banner.js +48 -0
- package/dist/ui/button.d.ts +14 -0
- package/dist/ui/button.js +49 -0
- package/dist/ui/checkbox.d.ts +12 -0
- package/dist/ui/checkbox.js +45 -0
- package/dist/ui/dom.d.ts +15 -0
- package/dist/ui/dom.js +60 -0
- package/dist/ui/empty.d.ts +11 -0
- package/dist/ui/empty.js +44 -0
- package/dist/ui/field.d.ts +18 -0
- package/dist/ui/field.js +49 -0
- package/dist/ui/icons.d.ts +10 -0
- package/dist/ui/icons.js +23 -0
- package/dist/ui/index.d.ts +35 -0
- package/dist/ui/index.js +17 -0
- package/dist/ui/list.d.ts +26 -0
- package/dist/ui/list.js +90 -0
- package/dist/ui/menu.d.ts +20 -0
- package/dist/ui/menu.js +111 -0
- package/dist/ui/navigation.d.ts +18 -0
- package/dist/ui/navigation.js +38 -0
- package/dist/ui/option.d.ts +16 -0
- package/dist/ui/option.js +35 -0
- package/dist/ui/popup.d.ts +5 -0
- package/dist/ui/popup.js +43 -0
- package/dist/ui/progress.d.ts +11 -0
- package/dist/ui/progress.js +44 -0
- package/dist/ui/search.d.ts +11 -0
- package/dist/ui/search.js +62 -0
- package/dist/ui/select.d.ts +22 -0
- package/dist/ui/select.js +164 -0
- package/dist/ui/separator.d.ts +6 -0
- package/dist/ui/separator.js +26 -0
- package/dist/ui/spinner.d.ts +8 -0
- package/dist/ui/spinner.js +29 -0
- package/dist/ui/style.d.ts +1 -0
- package/dist/ui/style.js +190 -0
- package/dist/ui/tabs.d.ts +15 -0
- package/dist/ui/tabs.js +61 -0
- package/dist/ui/text.d.ts +23 -0
- package/dist/ui/text.js +89 -0
- package/dist/ui/theme.d.ts +22 -0
- package/dist/ui/theme.js +69 -0
- package/dist/workspace-schemas.d.ts +136 -0
- package/dist/workspace-schemas.js +44 -0
- package/dist/workspace.d.ts +109 -0
- package/dist/workspace.js +4 -0
- package/package.json +55 -0
- package/scripts/bundle-guest.ts +44 -0
package/API.md
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
# @openchamber/sdk — developer API reference
|
|
2
|
+
|
|
3
|
+
What third-party guest authors import and call. Source of truth: `[packages/sdk/src](https://github.com/openchamber/openchamber/tree/main/packages/sdk/src)`. Longer guides live in the [product docs](https://github.com/openchamber/openchamber/tree/main/packages/docs/content/docs) (`sdk.mdx`, `sdk/host.mdx`, `sdk/ui.mdx`) and in `[GUEST_SERVICES.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/GUEST_SERVICES.md)` for local services.
|
|
4
|
+
|
|
5
|
+
**Package:** `@openchamber/sdk`
|
|
6
|
+
**API version:** manifest `apiVersion: 1`, wire envelope `v: 1`
|
|
7
|
+
**Runtimes that load guests:** web and desktop. VS Code and mobile mark the catalog `unsupported`.
|
|
8
|
+
|
|
9
|
+
Two entrypoints:
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
| Import | Role |
|
|
13
|
+
| --------------------- | -------------------------------------------------------------- |
|
|
14
|
+
| `@openchamber/sdk` | Manifest parse, iframe protocol, `connectHost` |
|
|
15
|
+
| `@openchamber/sdk/ui` | Optional DOM drawing kit (buttons, fields, lists, popups) |
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Ship checklist (install fails without these)
|
|
21
|
+
|
|
22
|
+
`inspectGuestPackage` (Settings → Extensions install) checks the folder or zip **on disk**. Parse alone is not enough. No OpenChamber runtime compiles TypeScript: packaged desktop, `openchamber serve`, and the dev server all serve the built `.js` files as they sit in the package.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
| Must exist | When | Failure code |
|
|
26
|
+
| -------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------ |
|
|
27
|
+
| Semver `version` on `package.json` (`1.0.0`) | Always on install | `invalid-manifest` |
|
|
28
|
+
| `panel.entry` HTML file | When `panel.entry` is set (a tools-only package may omit it) | `invalid-manifest` |
|
|
29
|
+
| Every relative `<script src="…">` `.js` from that HTML (usually `panel/main.js`) | When `panel.entry` is set | `missing-build` |
|
|
30
|
+
| File named by `panel.icon` | Only when icon ends in `.svg` (e.g. `icon.svg`) | `invalid-manifest` |
|
|
31
|
+
| File named by `service.entry` (e.g. `service/main.js`) | When `contributes.service` is set | `missing-build` |
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
**Icon.** Remixicon kebab name (`window`) needs no file. A package SVG path (`icon.svg`) must sit inside the package. URLs and absolute paths fail parse as `invalid-panel-icon`. Missing SVG on disk fails install as `invalid-manifest`.
|
|
35
|
+
|
|
36
|
+
**Panel JS.** Classic IIFE. The iframe cannot load ESM. Point `panel/index.html` at `./main.js` and ship that file.
|
|
37
|
+
|
|
38
|
+
**Service JS.** Same rule as the panel: `service.entry` must be compiled JS already in the package. `.ts` alone fails as `missing-build`.
|
|
39
|
+
|
|
40
|
+
Bundle with the SDK helper from the guest folder (`--node` targets Node for the service):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
bunx openchamber-guest-bundle panel/main.ts panel/main.js
|
|
44
|
+
bunx openchamber-guest-bundle --node service/main.ts service/main.js
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Zip or folder for install should include at least: `package.json`, `panel/index.html`, `panel/main.js`, and any declared `icon.svg` / `service/main.js`. Skip `node_modules` and TypeScript sources. Zip and git installs land in `{dataDir}/extensions/{id}`. See also `[GUEST_SERVICES.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/GUEST_SERVICES.md)`.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 1. `connectHost` — iframe client
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { connectHost, HostRequestError } from '@openchamber/sdk';
|
|
55
|
+
|
|
56
|
+
const host = connectHost();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Throws `HOST_UNAVAILABLE` when there is no `window`. Outside an iframe (`parent === self`) it still returns a client, but every call rejects with `HOST_UNAVAILABLE`. Call `dispose()` on teardown; in-flight RPCs then reject as `HOST_UNAVAILABLE`. Silent host for 20s → `HOST_TIMEOUT`.
|
|
60
|
+
|
|
61
|
+
### 1.1 Subscriptions (host pushes)
|
|
62
|
+
|
|
63
|
+
Each returns an unsubscribe function. Late subscribers get the last known value (replay from `ready` or the last dedicated push).
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
| Method | Payload | Notes |
|
|
67
|
+
| ------------------------------ | ----------------------- | ------------------------------------------------------------ |
|
|
68
|
+
| `onReady(listener)` | `HostReadyContext` | First snapshot and later full refreshes |
|
|
69
|
+
| `onDirectory(listener)` | `string | null` |
|
|
70
|
+
| `onSession(listener)` | `SessionSnapshot | null` |
|
|
71
|
+
| `onSessionLifecycle(listener)` | `SessionLifecycleEvent` | `{ sessionId, phase }` — `started` / `completed` / `failure` |
|
|
72
|
+
| `onConnection(listener)` | `GuestConnection` | `{ connected, account }` |
|
|
73
|
+
| `onSettings(listener)` | `GuestSettings` | Declared integration fields only (`Record<string, string>`) |
|
|
74
|
+
| `onItem(listener)` | `GuestItem | null` | The item this surface was opened for: the chip (`AttachIssueRequest`), a message (`GuestMessageItem`), or a session (`GuestSessionItem`); `null` from the rail icon or + menu |
|
|
75
|
+
| `onResolve(handler)` | `{ command, args }` → `Promise<AttachIssueRequest \| null>` | Answers a `contributes.commands` slash command. Return the chip to attach, `null` for nothing (the user sees a short notice), or throw (the message reaches the user). One handler at a time |
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
`HostReadyContext`
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
| Field | Type | Meaning |
|
|
82
|
+
| -------------- | ------------------------ | ---------------------------------------------------------------------------------------- |
|
|
83
|
+
| `theme.mode` | `'light' | 'dark'` |
|
|
84
|
+
| `theme.tokens` | surfaces, text, interaction states, `primary`, status colors, `font`, `mono`, `radius` | Pass to `applyHostReady` before mounting UI |
|
|
85
|
+
| `locale` | `string` | Host language tag |
|
|
86
|
+
| `directory` | `string | null` |
|
|
87
|
+
| `session` | snapshot or `null` | Title falls back to `id`. `busy` is live status. `model` is `providerID/id` when present |
|
|
88
|
+
| `surface` | `'panel' | 'dialog'` |
|
|
89
|
+
| `connection` | `{ connected, account }` | Integration link state |
|
|
90
|
+
| `settings` | `Record<string, string>` | Declared keys only |
|
|
91
|
+
| `item` | `GuestItem | null` | Set when the user clicked this guest's chip on the composer, or ran one of this guest's `contributes.actions`. Narrow with `isGuestMessageItem` / `isGuestSessionItem` / `isGuestAttachItem` |
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
`GuestItem` is `AttachIssueRequest | GuestMessageItem | GuestSessionItem`:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
type GuestMessageItem = {
|
|
98
|
+
kind: 'message';
|
|
99
|
+
action: string; // the action id from the manifest
|
|
100
|
+
sessionId: string;
|
|
101
|
+
sessionTitle: string;
|
|
102
|
+
directory: string | null; // the session's project directory
|
|
103
|
+
messageId: string;
|
|
104
|
+
role: 'user' | 'assistant';
|
|
105
|
+
text: string; // what the Markdown export renders for that message, at most 200 000 chars
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
type GuestSessionItem = {
|
|
109
|
+
kind: 'session';
|
|
110
|
+
action: string;
|
|
111
|
+
sessionId: string;
|
|
112
|
+
sessionTitle: string;
|
|
113
|
+
directory: string | null; // the session's project directory
|
|
114
|
+
messages?: Array<{ id: string; role: 'user' | 'assistant'; text: string; createdAt: number }>; // oldest first; only with payload ["messages"] and the conversation grant
|
|
115
|
+
truncated?: boolean; // the oldest messages were dropped so the item stays under 2 000 000 serialized chars
|
|
116
|
+
};
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
Access tokens never appear in `ready` or in request results.
|
|
121
|
+
|
|
122
|
+
**Session lifecycle phases:** live `busy` / `retry` → `started`; `idle` → `completed`; unknown status → `failure` (not abort/crash).
|
|
123
|
+
|
|
124
|
+
### 1.2 Actions (RPC)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
| Method | Arguments | Returns | Behavior |
|
|
128
|
+
| ----------------- | --------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------- |
|
|
129
|
+
| `toast` | `{ kind: 'info' | 'success' | 'error', message }` |
|
|
130
|
+
| `openUrl` | `url: string` | `Promise<void>` | Open URL in the host |
|
|
131
|
+
| `openSurface` | `surfaceId: string` | `Promise<void>` | Switch host chrome to that surface |
|
|
132
|
+
| `writeClipboard` | `text: string` | `Promise<void>` | Copy in the host (1–32000 chars) |
|
|
133
|
+
| `compose` | `{ text, mode?: 'append' | 'replace' }` | `Promise<void>` |
|
|
134
|
+
| `attach` | `AttachIssueRequest` | `Promise<void>` | Composer chip (exclusive with GitHub/Linear) |
|
|
135
|
+
| `startSession` | `StartSessionRequest` | `Promise<{ sessionId, sent }>` | Create session (+ optional worktree), write snapshot. `text` can become first message |
|
|
136
|
+
| `prompt` | `{ text, send?: boolean }` | `Promise<{ sent }>` | Current session: omit/`false` = replace-compose; `send: true` = send |
|
|
137
|
+
| `sessionLink` | `AttachIssueRequest` | `Promise<void>` | Write snapshot on **current** session. Does not create one |
|
|
138
|
+
| `close` | — | `Promise<void>` | Dismiss attach dialog. No-op on the rail |
|
|
139
|
+
| `oauthStart` | — | `Promise<void>` | Open provider authorize URL (or first-party Linear) |
|
|
140
|
+
| `oauthDisconnect` | — | `Promise<void>` | Drop guest tokens / Linear connection |
|
|
141
|
+
| `request` | `{ method, path, query?, body? }` | `Promise<{ status, body }>` | HTTPS call on declared `apiOrigin`. Host attaches auth |
|
|
142
|
+
| `serviceRequest` | same shape as `request` | `Promise<{ status, body }>` | Proxy to this guest's local service on loopback |
|
|
143
|
+
| `serviceStatus` | — | `Promise<{ status }>` | `stopped` |
|
|
144
|
+
| `readFile` | `path: string` | `Promise<{ content }>` | UTF-8 text. Relative = inside the open project (`files`); `/…` or `~/…` = declared `filesystem` pattern |
|
|
145
|
+
| `writeFile` | `path: string, content: string` | `Promise<{ written: true }>` | Atomic (temp + rename), creates parent folders. Same path rules |
|
|
146
|
+
| `listDir` | `path: string` | `Promise<{ entries }>` | `{ name, kind: 'file' \| 'directory' \| 'other' }[]`, sorted, capped at 2 000. Same path rules |
|
|
147
|
+
| `stat` | `path: string` | `Promise<{ kind, size, mtime }>` | `kind` adds `'missing'`; a missing path is not an error. Same path rules |
|
|
148
|
+
| `setBadge` | `count: number \| null` | `Promise<void>` | Number on this guest's rail icon, 0–999 (clamped); `null` clears. Opening the panel clears it too. In memory only |
|
|
149
|
+
| `generate` | `{ prompt, system?, maxOutputTokens? }` | `Promise<{ text }>` | One-off text from the user's Small Model (capability `model`). No session, no history; the host picks the model. Waits up to 90 s |
|
|
150
|
+
| `dispose` | — | `void` | Remove listener, reject pending RPCs |
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
`AttachIssueRequest`
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
{
|
|
157
|
+
providerId: string; // usually panel id
|
|
158
|
+
id: string; // guest identifier, not a GitHub number
|
|
159
|
+
title: string;
|
|
160
|
+
url: string;
|
|
161
|
+
text?: string; // optional model context
|
|
162
|
+
kind?: 'issue' | 'pull'; // default issue
|
|
163
|
+
author?: string;
|
|
164
|
+
branches?: { head: string; base: string }; // for pull
|
|
165
|
+
data?: JsonValue; // opaque, comes back as ready.item.data; not sent to the model
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`data` is plain JSON (`string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }`). It is stored with the chip and the session snapshot and returned unchanged when the user clicks the chip. `JSON.stringify(data).length` must stay within `GUEST_ATTACH_DATA_MAX` (16 000): `clampAttachRequest` silently drops a larger `data`, and the host schema refuses the whole message if it arrives over the limit.
|
|
170
|
+
|
|
171
|
+
`StartSessionRequest` adds `projectId?`, `navigation?: 'preserve' | 'open'`, and `worktree?` to the attach fields. Navigation defaults to `preserve`. Without `projectId`, the current directory is used. With one, creation targets that registered project without switching the app first.
|
|
172
|
+
|
|
173
|
+
`worktree` is `false` or omitted for the target directory, `true` for a generated new worktree, `{ kind: 'existing', directory }` for a known worktree belonging to the project, or `{ kind: 'new', name?, baseBranch? }`. `name` names both the branch and worktree. Omitted name/base use the host's normal defaults. First-message model/agent/variant selection is captured when the call starts.
|
|
174
|
+
|
|
175
|
+
A created session returns `{ sessionId, directory, sent, linked, worktree? }`. `linked: false` means the session exists but saving the attached item failed. If a worktree was created but bootstrap or session creation failed, the result is `{ sessionId: null, sent: 'skipped', directory, worktree, failure }`, where `failure` is `bootstrap-failed` or `session-create-failed`. The worktree is retained. Inspect the result before offering Retry, which would otherwise create another worktree. The call waits up to 180 seconds; a timeout does not prove the server rolled back.
|
|
176
|
+
|
|
177
|
+
### Workspace lists and subscriptions
|
|
178
|
+
|
|
179
|
+
These methods extend the existing `sessions` capability. They expose registered projects on the connected server, session metadata and live state, and known worktrees. They do not grant conversation content or file access.
|
|
180
|
+
|
|
181
|
+
| Method | Result |
|
|
182
|
+
| --- | --- |
|
|
183
|
+
| `listProjects()` | `Promise<GuestProjectsSnapshot>` |
|
|
184
|
+
| `listWorktrees(projectId)` | `Promise<GuestWorktreesSnapshot>` |
|
|
185
|
+
| `listSessions(projectId)` | `Promise<GuestSessionsSnapshot>` including known archived sessions |
|
|
186
|
+
| `onProjects(listener)` | `Promise<() => void>` |
|
|
187
|
+
| `onWorktrees(projectId, listener)` | `Promise<() => void>` |
|
|
188
|
+
| `onSessions(projectId, listener)` | `Promise<() => void>` |
|
|
189
|
+
| `openSession(sessionId)` | `Promise<void>`, explicitly opens the chat and closes the page |
|
|
190
|
+
|
|
191
|
+
Await subscription registration to handle refusal, then retain its returned unsubscribe function. Each subscription sends an initial snapshot, then changes. At most 32 subscriptions per iframe. `dispose()` releases them all. Unmount, disable, uninstall and runtime switch also release host subscriptions.
|
|
192
|
+
|
|
193
|
+
Snapshots carry `state: 'loading' | 'ready' | 'error'`; session snapshots also carry per-directory `coverage`. Loading/error may retain data. Only `ready` establishes complete empty success. Reads use existing shared stores and hydration, never a git scan per extension request.
|
|
194
|
+
|
|
195
|
+
Projects contain `id`, `name`, `directory`. Worktrees contain `directory`, `name`, `branch`, and `status: 'ready' | 'pending' | 'invalid' | 'missing'`. Session records contain `id`, `title`, `projectId`, `directory`, `parentId`, `createdAt`, `updatedAt`, `archivedAt`, `worktree`, `activity`, `outcome`, and `items`. Item references contain only this extension's `id` and optional `data`.
|
|
196
|
+
|
|
197
|
+
`activity` is `unknown`, `idle`, `running`, `retrying`, `waiting-permission`, or `waiting-question`. `outcome` is the last observed `completed` or `failed` turn, or `null` when unknown or working. Outcomes are in memory for the latest 2,000 observed sessions, reset on runtime switch, and are not reconstructed from persisted history. A later idle event preserves an observed failure until another run starts. `completed` never means the extension's task is Done. Blocking-request contents and approve/reply actions are not exposed.
|
|
198
|
+
|
|
199
|
+
### Extension storage
|
|
200
|
+
|
|
201
|
+
`host.storage.get(key)` returns JSON or `undefined` for a missing key. JSON `null` is a stored value. `set(key, value)` and `delete(key)` return `Promise<void>`; `keys()` returns `Promise<string[]>` in sorted order.
|
|
202
|
+
|
|
203
|
+
Storage belongs to the extension on the connected server and needs no extra capability. Keys contain 1 to 128 characters, each serialized value is at most 64 KiB UTF-8, and the complete namespace is at most 2 MiB and 2,000 keys. Use a project ID in your key when data belongs to one project. Concurrent operations serialize on the server, writes are atomic, and read/write failures preserve existing data. Uninstall deletes the namespace, including for folder installs.
|
|
204
|
+
|
|
205
|
+
### Full-screen pages
|
|
206
|
+
|
|
207
|
+
`contributes.page: true` reuses `panel.entry`; `{ entry: 'panel/page.html', title?: 'Board' }` uses separate package HTML. It requires `panel.entry` and the same installed/approved/enabled state as the panel. The sidebar's Extension pages menu is the only page opener; `openSurface` does not open it. `ctx.surface` is `page`, `close()` closes it, and reload or runtime switch returns to chat. Pages use the existing sandbox and capabilities on web/desktop. VS Code and mobile remain unsupported.
|
|
208
|
+
|
|
209
|
+
`sent` **values** (`startSession` / `prompt`): `sent` | `no-model` | `skipped` | `failed`. After `no-model` / `failed` on `startSession`, the session still exists.
|
|
210
|
+
|
|
211
|
+
**File path rules** (`readFile` / `writeFile` / `listDir` / `stat`): a relative path (`README.md`, `src/x.ts`, `.`) is joined to the project that is open when the call runs and needs the `files` capability; no open project is `NO_DIRECTORY`. A path starting with `/` or `~/` is outside the project, must match one of the package's `contributes.filesystem` globs, and needs the `filesystem` capability. Any `..` segment, a backslash, or a symlink that leads out of the allowed tree is `BAD_PATH`. The host compares canonical (realpath) paths, so `/tmp/x` on macOS is checked as `/private/tmp/x` and a pattern's literal prefix is canonicalized the same way. Content over 2 000 000 characters is `FILE_TOO_LARGE` in both directions; an OS permission refusal is `DENIED`.
|
|
212
|
+
|
|
213
|
+
`request` **/** `serviceRequest` **rules:** `method` is `GET` | `POST` | `PUT` | `PATCH` | `DELETE`. `path` must start with `/`, no scheme, stay on the declared origin (cloud API or service loopback). Guest parses `body` as JSON when needed.
|
|
214
|
+
|
|
215
|
+
### 1.3 Error codes (`HostRequestError.code`)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
| Code | When |
|
|
219
|
+
| ------------------ | --------------------------------------------- |
|
|
220
|
+
| `HOST_UNAVAILABLE` | No window, not in iframe, or disposed |
|
|
221
|
+
| `HOST_TIMEOUT` | No answer for 20s |
|
|
222
|
+
| `HOST_REJECTED` | Host refusal, or unknown wire code |
|
|
223
|
+
| `DISCONNECTED` | No token / Linear connection |
|
|
224
|
+
| `DISABLED` | Extension paused in Settings |
|
|
225
|
+
| `BAD_PATH` | Path left origin or malformed |
|
|
226
|
+
| `NO_INTEGRATION` | Manifest has no `integration` |
|
|
227
|
+
| `NO_SESSION` | `prompt` / `sessionLink` with no open session |
|
|
228
|
+
| `SESSION_BUSY` | `prompt({ send: true })` while busy |
|
|
229
|
+
| `NO_SERVICE` | No service, not approved, or not running |
|
|
230
|
+
| `NOT_GRANTED` | The user has not approved this capability |
|
|
231
|
+
| `NO_DIRECTORY` | Relative file path with no open project |
|
|
232
|
+
| `NOT_FOUND` | `readFile` / `listDir` on a path that does not exist |
|
|
233
|
+
| `FILE_TOO_LARGE` | File or content over 2 000 000 characters |
|
|
234
|
+
| `DENIED` | The operating system refused the file access |
|
|
235
|
+
| `NO_MODEL` | `generate` with no usable Small Model |
|
|
236
|
+
| `MODEL_FAILED` | The Small Model returned an error |
|
|
237
|
+
| `SERVICE_FAILED` | Service crashed or never became ready |
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
### 1.4 Field limits (client clamps before send)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
| Field | Max |
|
|
244
|
+
| -------------------------------- | --------- |
|
|
245
|
+
| Clipboard text | 32 000 |
|
|
246
|
+
| Compose / prompt / attach `text` | 16 000 |
|
|
247
|
+
| Attach `data` (serialized) | 16 000 |
|
|
248
|
+
| Attach `id` | 128 |
|
|
249
|
+
| Attach `title` | 200 |
|
|
250
|
+
| Attach `url` | 2 000 |
|
|
251
|
+
| Attach `author` | 80 |
|
|
252
|
+
| Branch name | 200 |
|
|
253
|
+
| Request path | 2 000 |
|
|
254
|
+
| Request body | 64 000 |
|
|
255
|
+
| Request response | 256 000 |
|
|
256
|
+
| Request timeout | 20 000 ms |
|
|
257
|
+
| `resolve` answer (host waits) | 20 000 ms |
|
|
258
|
+
| Badge count | 999 |
|
|
259
|
+
| Message item `text` | 200 000 |
|
|
260
|
+
| Session item (serialized) | 2 000 000 |
|
|
261
|
+
| File path | 1 024 |
|
|
262
|
+
| File content (read and write) | 2 000 000 |
|
|
263
|
+
| `listDir` entries | 2 000 |
|
|
264
|
+
| `generate` prompt / system | 64 000 / 8 000 |
|
|
265
|
+
| `generate` `maxOutputTokens` | 4 000 |
|
|
266
|
+
| `generate` answer | 256 000 |
|
|
267
|
+
| `generate` timeout | 90 000 ms |
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## 2. `@openchamber/sdk/ui` — drawing kit
|
|
273
|
+
|
|
274
|
+
DOM building blocks that use host tokens. They do **not** call the provider or `connectHost`. You compose the screen and wire callbacks yourself.
|
|
275
|
+
|
|
276
|
+
Always call theme first:
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
import { applyHostReady, mountList } from '@openchamber/sdk/ui';
|
|
280
|
+
|
|
281
|
+
host.onReady((ctx) => {
|
|
282
|
+
applyHostReady(ctx, document.documentElement);
|
|
283
|
+
// then mount…
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Every mount returns `{ update(partial), dispose() }`. `update` merges the fields you pass and repaints; `dispose` removes the node and its listeners.
|
|
288
|
+
|
|
289
|
+
### 2.1 Theme
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
| Function | Role |
|
|
293
|
+
| ----------------------------- | --------------------------------------------------------- |
|
|
294
|
+
| `applyHostReady(ctx, root)` | Writes theme tokens + `data-oc-surface` / `data-oc-theme` |
|
|
295
|
+
| `applyHostTheme(theme, root)` | Tokens only |
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
### 2.2 Mount functions
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
| Function | Use for | Main props |
|
|
302
|
+
| --------------------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
303
|
+
| `mountButton(root, props)` | Button | `label`, `onClick`, `variant?` (`default` / `secondary` / `outline` / `ghost` / `destructive`), `size?` (`default` / `sm` / `xs`), `disabled?`, `loading?` |
|
|
304
|
+
| `mountTextField(root, props)` | Input or textarea | `value`, `onChange`, `label?`, `placeholder?`, `password?`, `multiline?`, `rows?`, `disabled?`, `error?`, `helper?`, `mono?` |
|
|
305
|
+
| `mountSearchField(root, props)` | Search box | `value`, `onChange`, `placeholder?`, `label?`, `autofocus?` |
|
|
306
|
+
| `mountSelect(root, props)` | Dropdown | `value`, `options: { id, label, hint? }[]`, `onChange`, `label?`, `placeholder?`, `searchable?`, `searchPlaceholder?`, `disabled?` |
|
|
307
|
+
| `mountCheckbox` / `mountSwitch` | Checkbox / toggle | `label`, `checked`, `onChange`, `disabled?`, `description?` |
|
|
308
|
+
| `mountTabs(root, props)` | Pill tabs | `items: { id, label, count? }[]`, `activeId`, `onChange`, `trackBackground?` |
|
|
309
|
+
| `mountBadge(root, props)` | Pill | `label`, `tone?` (`neutral` / `primary` / `success` / `warning` / `error` / `info`) |
|
|
310
|
+
| `mountList(root, props)` | Keyboard list | `items: { id, title, subtitle?, leading?, meta?, badge?, disabled? }[]`, `onSelect`, `selectedId?`, `emptyText?`, `ariaLabel?` |
|
|
311
|
+
| `mountEmpty(root, props)` | Empty / disconnected state | `title`, `body?`, `action?: { label, onClick }` |
|
|
312
|
+
| `mountSpinner(root, props?)` | Loading ring | `size?` (`sm` / `default`), `label?` |
|
|
313
|
+
| `mountBanner(root, props)` | Notice | `tone` (`info` / `success` / `warning` / `error`), `title`, `body?`, `action?` |
|
|
314
|
+
| `mountSeparator(root, props?)` | Divider | `label?` |
|
|
315
|
+
| `mountProgress(root, props)` | Progress bar | `value` (0..100), `tone?`, `label?` |
|
|
316
|
+
| `mountMenu(root, props)` | Action dropdown | `label`, `items: ({ id, label, destructive?, disabled? } \| { separator: true })[]`, `onSelect`, `variant?`, `size?` |
|
|
317
|
+
| `mountText(root, props)` | Provider text | `text`, `onOpenUrl?` |
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
### 2.3 Helpers
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
| Function | Role |
|
|
324
|
+
| ------------------------------------------ | --------------------------------------------------------------------------- |
|
|
325
|
+
| `filterSelectOptions(options, query)` | Same case-insensitive label / id match `mountSelect` uses |
|
|
326
|
+
| `moveListSelection(items, currentId, key)` | Keyboard step (`next` / `previous` / `first` / `last`) that skips disabled items |
|
|
327
|
+
| `navigationKey(event, axis?)` | Maps arrows, Home, End, Ctrl+N / Ctrl+P to a step |
|
|
328
|
+
| `splitTextMedia(text)` | Splits text into runs, `` images, and `[label](https://…)` links |
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
`mountText` keeps everything as text except `http(s)` markdown images and links. A sandboxed iframe cannot open a link itself, so pass `onOpenUrl` and forward the URL to `host.openUrl`.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## 3. Manifest and host-side parse (`@openchamber/sdk`)
|
|
336
|
+
|
|
337
|
+
Used by the OpenChamber host and by tools that validate packages. Guests rarely call these from the iframe.
|
|
338
|
+
|
|
339
|
+
### 3.1 Manifest block (inside `package.json`)
|
|
340
|
+
|
|
341
|
+
```json
|
|
342
|
+
{
|
|
343
|
+
"name": "@acme/hello-panel",
|
|
344
|
+
"version": "1.0.0",
|
|
345
|
+
"openchamber": {
|
|
346
|
+
"apiVersion": 1,
|
|
347
|
+
"engines": { "openchamber": ">=1.22.0" },
|
|
348
|
+
"contributes": {
|
|
349
|
+
"panel": {
|
|
350
|
+
"id": "acme-hello",
|
|
351
|
+
"name": "Hello",
|
|
352
|
+
"icon": "window",
|
|
353
|
+
"entry": "panel/index.html"
|
|
354
|
+
},
|
|
355
|
+
"attach": "dialog",
|
|
356
|
+
"capabilities": ["prompt", "sessions", "files"],
|
|
357
|
+
"filesystem": ["~/.config/opencode/opencode.json", "/tmp/acme/**"],
|
|
358
|
+
"actions": [
|
|
359
|
+
{ "id": "create-task", "label": "Create task from message", "icon": "add-circle", "where": "message", "roles": ["assistant"] },
|
|
360
|
+
{ "id": "summarize", "label": "Summarize session", "where": "session", "payload": ["messages"] }
|
|
361
|
+
],
|
|
362
|
+
"commands": [{ "name": "task", "description": "Attach a task by id" }],
|
|
363
|
+
"tools": [{ "match": "mcp.tasks.*", "name": "Tasks", "icon": "checkbox-circle", "title": "{input.id}", "output": "table", "columns": ["id", "title", "status"] }],
|
|
364
|
+
"integration": { /* oauth | token | host */ },
|
|
365
|
+
"service": { /* optional local process */ }
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
| Key | Rules |
|
|
373
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
374
|
+
| `version` | Semver required on install (`1.0.0`) |
|
|
375
|
+
| `apiVersion` | Must be `1` |
|
|
376
|
+
| `engines.openchamber` | Optional. Only `1.22.0` or `>=1.22.0`. Older host → `host-too-old` |
|
|
377
|
+
| `panel.id` | kebab-case |
|
|
378
|
+
| `panel.icon` | Remixicon kebab name (`window`) **or** package `.svg` path. Remixicon needs no file. An `.svg` path must exist on disk or install fails (`invalid-manifest`). No URLs/absolute paths |
|
|
379
|
+
| `panel.entry` | Optional. Path inside package. No `..`, absolute, or URL. HTML must exist; its relative `.js` scripts must exist (`missing-build` if not). Omit it for a page-less extension: then only `tools` (plus `engines` and `version`) may be declared; `attach`, `actions`, `commands`, `service`, `integration`, `capabilities`, or `filesystem` without an entry fail parse as `invalid-panel`. A page-less extension has no rail icon, + menu row, or frame; the Extensions card says "No panel". `hasGuestPage(contributes)` tells the two apart |
|
|
380
|
+
| `attach` | `true` / `"panel"` → + menu opens rail; `"dialog"` → host window; omit/`false` → off menus. Object form `{ "mode": "panel" \| "dialog", "entry"?: "panel/attach.html" }`: `entry` (dialog only, same path rules as `panel.entry`, must exist with built scripts) is the page the dialog loads instead of `panel.entry` |
|
|
381
|
+
| `capabilities` | Optional list of `prompt`, `sessions`, `files`, `model`. `files` is read **and** write inside the open project; `model` is `generate`. Approved once at install |
|
|
382
|
+
| `actions` | Optional, 1–8 entries, unique kebab-case `id`, `label` 1–40 chars, optional `icon` (same rules as `panel.icon`, falls back to it), `where: "message" \| "session"`. Message actions may narrow `roles` to `["user"]` / `["assistant"]` (default both); session actions may ask for `payload: ["messages"]`, which adds the `conversation` capability. Bad shape is `invalid-actions`. The entry shows in that message's or session's menu and opens the guest with the item as `ready.item` (the attach window for `attach: "dialog"`, otherwise the rail) |
|
|
383
|
+
| `commands` | Optional, 1–8 entries, unique `name` matching `/^[a-z][a-z0-9-]{0,23}$/`, optional `description` 1–80 chars (`invalid-commands`). `/name args` in the chat box calls `onResolve` instead of the model and attaches what it returns. A name the composer already has (built-in, OpenCode command, skill) is ignored with a console warning |
|
|
384
|
+
| `tools` | Optional, 1–16 entries that say how the extension's tool calls look in the chat. `match` is the full tool name OpenCode reports (`mcp.jira.search`, `jira_search`), 1–128 chars of `[A-Za-z0-9_.:-]`, with `*` allowed once at the end as a suffix wildcard (`mcp.jira.*`). Optional `name` (1–40, the header title when `title` is absent or renders empty), `icon` (Remixicon name or package `.svg` path, same rules as `panel.icon`; the SVG is drawn in the text colour at the glyph size), `title` / `subtitle` templates (1–200, `{input.path}` / `{output.path}` / `{metadata.path}` placeholders, a missing path renders empty, values are cut at 200), `output` `"auto"` (default) \| `"text"` \| `"json"` \| `"markdown"` \| `"code"` \| `"table"`, `language` (code only), `columns` (table only, 1–16 dotted paths; rows are the output array or `output.items`). Bad shape is `invalid-tools`. An exact `match` beats a wildcard from any extension; among equals the first extension wins. Only an enabled, fully approved extension's rules apply |
|
|
385
|
+
| `filesystem` | Optional, 1–16 globs, each 1–256 chars, starting with `/` or `~/`; `**` spans folders, `*` / `?` stay in one segment; no `..`, empty segment, or backslash (`invalid-filesystem`). Declaring it adds the `filesystem` capability and the dialog lists the globs |
|
|
386
|
+
| `integration` | Optional. Exactly one of `oauth`, `token`, or `host` (`provider: "linear"` only) |
|
|
387
|
+
| `service` | Optional. `entry` must be a built `.js` file on disk. See [GUEST_SERVICES.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/GUEST_SERVICES.md) |
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
Extra keys are dropped, not forwarded.
|
|
391
|
+
|
|
392
|
+
### 3.2 Parse / version helpers
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
| Export | Role |
|
|
396
|
+
| -------------------------------------------------- | --------------------------------------------------------- |
|
|
397
|
+
| `parseManifest(document)` (`@openchamber/sdk/schemas`) | Typed document → success/failure (does not throw on junk) |
|
|
398
|
+
| `parseManifestJson(json)` (`@openchamber/sdk/schemas`) | String → same result |
|
|
399
|
+
| `resolveAttachMode(attach)` | Normalize to `'panel' |
|
|
400
|
+
| `resolveAttachEntry(contributes)` | Dialog page from the object form, or `null` when the dialog reuses `panel.entry` |
|
|
401
|
+
| `hasGuestPage(contributes)` | `true` when `panel.entry` is set; a page-less package may only declare `tools` |
|
|
402
|
+
| `resolveIntegrationAuth` / `resolveIntegrationApi` | Auth kind and API origin |
|
|
403
|
+
| `toPublicIntegration` / `toPublicService` | Catalog-safe public slices |
|
|
404
|
+
| `isGuestPackageSvgIcon` | Whether icon is a package SVG path |
|
|
405
|
+
| `compareOpenChamberVersions` | Semver compare |
|
|
406
|
+
| `hostMeetsOpenChamberEngine` | Host vs `engines.openchamber` floor |
|
|
407
|
+
| `openChamberEngineMinimum` | Normalize `>=1.22.0` → floor string |
|
|
408
|
+
| `parseOpenChamberVersion` | Parse `x.y.z` |
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
### 3.3 Protocol helpers (host + guest tooling)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
| Export | Role |
|
|
415
|
+
| ----------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
|
|
416
|
+
| `readHostMessage` (`@openchamber/sdk`) | Guest-side read of a host push; no schema library |
|
|
417
|
+
| `parseHostMessage` / `parseGuestMessage` (`@openchamber/sdk/schemas`) | Host-side typed parse |
|
|
418
|
+
| `hostMessageSchema` / `guestMessageSchema` (`@openchamber/sdk/schemas`) | Zod schemas for `postMessage` data |
|
|
419
|
+
| `clampAttachRequest` / `clampStartSessionRequest` / `clampPromptRequest` | Enforce field max lengths |
|
|
420
|
+
| `isGuestRequestPath` / `isGuestRequestResult` / `isStartSessionResult` / `isPromptResult` / `isServiceStatusResult` | Narrow result payloads |
|
|
421
|
+
| `isGuestAttachItem` / `isGuestMessageItem` / `isGuestSessionItem` | Narrow `ready.item` |
|
|
422
|
+
| `clampBadgeCount` / `guestActionsNeedConversation` | Badge range; whether declared actions need `conversation` |
|
|
423
|
+
| `isHostRequestErrorCode` / `resolveHostRequestErrorCode` | Error code validation |
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
Constants: `OPENCHAMBER_SDK_CHANNEL`, `OPENCHAMBER_SDK_API_VERSION`, `HOST_LINEAR_API_ORIGIN`, `GUEST_*_MAX`, `GUEST_REQUEST_TIMEOUT_MS`, `GUEST_ACTIONS_MAX`, `GUEST_COMMANDS_MAX`, `GUEST_COMMAND_NAME`, `GUEST_TOOLS_MAX`, `GUEST_TOOL_MATCH`, `GUEST_TOOL_OUTPUTS`, `HOST_REQUEST_ERROR_CODES`, `SERVICE_STATUS_VALUES`, `SESSION_LIFECYCLE_PHASES`, `START_SESSION_SENT`.
|
|
427
|
+
|
|
428
|
+
Wire messages added for these: host → guest `resolve` (`{ id, payload: { command, args } }`), guest → host `resolve-result` (`{ id, payload: { item } | { error } }`, no `result` comes back) and `badge` (`{ count }`).
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## 4. Local services (`contributes.service`)
|
|
433
|
+
|
|
434
|
+
For Docker sockets, CLI binaries, kubectl, and similar. The sandboxed iframe cannot dial Unix sockets; the host spawns a package entry and proxies HTTP.
|
|
435
|
+
|
|
436
|
+
Panel → `serviceRequest` → host → `127.0.0.1:port` → service process → socket/CLI.
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
| Panel call | Role |
|
|
440
|
+
| ----------------------------------------------- | -------------------------------- |
|
|
441
|
+
| `serviceRequest({ method, path, query?, body? })` | Proxy to this guest's service only |
|
|
442
|
+
| `serviceStatus()` | Lifecycle state |
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
Manifest sketch: `service.entry` (path to **built** JS, e.g. `service/main.js`), `runtime: "host"`, `permissions.sockets` and/or `permissions.exec`. Install refuses with `missing-build` when that file is absent. Declaring a service adds `service` to the capabilities the user approves at install; until then `serviceRequest` is `NO_SERVICE`.
|
|
446
|
+
|
|
447
|
+
Bundle the service with the Node target:
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
bunx openchamber-guest-bundle --node service/main.ts service/main.js
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Full contract (env vars, `/health`, grants, socket overrides): `[GUEST_SERVICES.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/GUEST_SERVICES.md)`.
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## 5. What this package does not provide
|
|
458
|
+
|
|
459
|
+
Frozen on `apiVersion` 1 — named in docs, no host hole yet:
|
|
460
|
+
|
|
461
|
+
- Host-side `issues.search` / `issues.get` (guest draws the list; chip is `attach`)
|
|
462
|
+
- Public OAuth broker
|
|
463
|
+
- Keyboard shortcuts, raw git remotes, magic prompts
|
|
464
|
+
- Second `host.provider` beyond Linear
|
|
465
|
+
- Arbitrary filesystem access from the page (only the open project with `files`, or declared `contributes.filesystem` globs), terminal, pairing, or host React components. A declared `service` is outside these limits: it is a process with the user's rights and no sandbox
|
|
466
|
+
|
|
467
|
+
Do not go around the guest contract through `RuntimeAPIs`.
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## 6. Minimal panel sketch
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
import { connectHost, HostRequestError } from '@openchamber/sdk';
|
|
475
|
+
import { applyHostReady, mountList, mountEmpty } from '@openchamber/sdk/ui';
|
|
476
|
+
|
|
477
|
+
const host = connectHost();
|
|
478
|
+
const root = document.querySelector('#root')!;
|
|
479
|
+
|
|
480
|
+
host.onReady((ctx) => {
|
|
481
|
+
applyHostReady(ctx, document.documentElement);
|
|
482
|
+
|
|
483
|
+
if (!ctx.connection.connected) {
|
|
484
|
+
mountEmpty(root, {
|
|
485
|
+
title: 'Connect Acme',
|
|
486
|
+
action: { label: 'Sign in', onClick: () => { void host.oauthStart(); } },
|
|
487
|
+
});
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
mountList(root, {
|
|
492
|
+
items: [], // fill from host.request
|
|
493
|
+
onSelect: (id) => {
|
|
494
|
+
void host.attach({
|
|
495
|
+
providerId: 'acme-hello',
|
|
496
|
+
id,
|
|
497
|
+
title: id,
|
|
498
|
+
url: '',
|
|
499
|
+
});
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
host.onConnection(async (connection) => {
|
|
505
|
+
if (!connection.connected) return;
|
|
506
|
+
try {
|
|
507
|
+
const res = await host.request({ method: 'GET', path: '/api/v2/tasks' });
|
|
508
|
+
// parse res.body, then remount / update the list
|
|
509
|
+
} catch (error) {
|
|
510
|
+
if (error instanceof HostRequestError && error.code === 'DISCONNECTED') {
|
|
511
|
+
await host.oauthStart();
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Related files
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
| File | Audience |
|
|
523
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
|
|
524
|
+
| [README.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/README.md) | Package overview and first hole |
|
|
525
|
+
| [DOCUMENTATION.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/DOCUMENTATION.md) | Agent / maintainer invariants |
|
|
526
|
+
| [GUEST_SERVICES.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/GUEST_SERVICES.md) | Local service contract |
|
|
527
|
+
| [src/ui/DOCUMENTATION.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/src/ui/DOCUMENTATION.md) | UI kit invariants |
|
|
528
|
+
| [sdk.mdx](https://github.com/openchamber/openchamber/blob/main/packages/docs/content/docs/sdk.mdx) / [sdk/host.mdx](https://github.com/openchamber/openchamber/blob/main/packages/docs/content/docs/sdk/host.mdx) / [sdk/ui.mdx](https://github.com/openchamber/openchamber/blob/main/packages/docs/content/docs/sdk/ui.mdx) | Author-facing website pages |
|
|
529
|
+
|