@openchamber/sdk 1.24.2 → 2.0.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 CHANGED
@@ -116,7 +116,7 @@ type GuestSessionItem = {
116
116
  sessionId: string;
117
117
  sessionTitle: string;
118
118
  directory: string | null; // the session's project directory
119
- messages?: Array<{ id: string; role: 'user' | 'assistant'; text: string; createdAt: number }>; // oldest first; only with payload ["messages"] and the conversation grant
119
+ messages?: Array<{ id: string; role: 'user' | 'assistant'; text: string; createdAt: number }>; // oldest first; only with payload ["messages"] and the conversation grant. Same messages the Markdown export writes: the conversation plus context the user attached (`user`); OpenCode's own plumbing messages are left out
120
120
  truncated?: boolean; // the oldest messages were dropped so the item stays under 2 000 000 serialized chars
121
121
  };
122
122
  ```
@@ -151,6 +151,8 @@ Access tokens never appear in `ready` or in request results.
151
151
  | `listDir` | `path: string` | `Promise<{ entries }>` | `{ name, kind: 'file' \| 'directory' \| 'other' }[]`, sorted, capped at 2 000. Same path rules |
152
152
  | `stat` | `path: string` | `Promise<{ kind, size, mtime }>` | `kind` adds `'missing'`; a missing path is not an error. Same path rules |
153
153
  | `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 |
154
+ | `openCommit` | `sha: string` | `Promise<void>` | Show that commit of the open project in the host's Diff view (commit scope). 7–64 hex characters; the host reads the commit itself. `NO_DIRECTORY` without a project, `NOT_FOUND` for an unknown commit, `UNSUPPORTED` where the host has no Diff view |
155
+ | `setHeight` | `height: number` | `Promise<void>` | Content height in CSS px. The Work Status section sizes its frame to it, clamped to 24–320; taller content scrolls inside. A page docked to a shared surface grows or shrinks its dock to it (a width for a `left`/`right` dock), from 24 px up to half the panel. Other surfaces ignore it |
154
156
  | `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 |
155
157
  | `dispose` | — | `void` | Remove listener, reject pending RPCs |
156
158
 
@@ -199,7 +201,7 @@ Snapshots carry `state: 'loading' | 'ready' | 'error'`; session snapshots also c
199
201
 
200
202
  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`.
201
203
 
202
- `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.
204
+ `activity` is `unknown`, `idle`, `running`, `retrying`, `waiting-permission`, or `waiting-question` (the agent put a form to the user and is waiting on the answer). `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.
203
205
 
204
206
  ### Extension storage
205
207
 
@@ -211,6 +213,10 @@ Storage belongs to the extension on the connected server and needs no extra capa
211
213
 
212
214
  `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.
213
215
 
216
+ ### Work Status sections
217
+
218
+ `contributes.statusSection: true` reuses `panel.entry`; `{ entry: 'status/index.html', title?: 'Recent commits', height?: 160 }` uses separate package HTML (`.html`, inside the package, built scripts checked at install). The object form needs no `panel.entry`, so an extension can ship only a section and no rail icon. `title` is 1 to 60 characters and replaces `panel.name` on the section header; the icon is `panel.icon`. `height` (24 to 320, default 120) is the frame height before your page calls `setHeight`. The section appears in the chat's Work Status panel and in its section chooser, where the user can hide it or move it. `ctx.surface` is `status`. The frame runs only while the panel is shown and the section is expanded, so keep no state in it that you cannot rebuild. It gets the same sandbox, directory, session, theme, grants, and service as a panel. A status-only package may declare `capabilities`, `service`, `integration`, and `filesystem`; `page`, `attach`, `actions`, and `commands` still need `panel.entry` or `background.entry`. Web and desktop only.
219
+
214
220
  `sent` **values** (`startSession` / `prompt`): `sent` | `no-model` | `skipped` | `failed`. After `no-model` / `failed` on `startSession`, the session still exists.
215
221
 
216
222
  **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`.
@@ -263,6 +269,7 @@ See [Actions without opening a panel](./README.md#actions-without-opening-a-pane
263
269
  | `DENIED` | The operating system refused the file access |
264
270
  | `NO_MODEL` | `generate` with no usable Small Model |
265
271
  | `MODEL_FAILED` | The Small Model returned an error |
272
+ | `UNSUPPORTED` | This host surface cannot do that (for example `openCommit` without a Diff view) |
266
273
  | `SERVICE_FAILED` | Service crashed or never became ready |
267
274
 
268
275
 
@@ -435,7 +442,7 @@ Used by the OpenChamber host and by tools that validate packages. Guests rarely
435
442
  | `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 |
436
443
  | `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 |
437
444
  | `integration` | Optional. Exactly one of `oauth`, `token`, or `host` (`provider: "linear"` only) |
438
- | `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) |
445
+ | `service` | Optional. `entry` must be a built `.js` file on disk. `provides: ["browser"]` makes it the agent's browser when the user selects it; `surface: true` gives it a host-drawn live panel the user can take over (no `panel.entry` then). Neither needs a panel or background entry. See [GUEST_SERVICES.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/GUEST_SERVICES.md) |
439
446
 
440
447
 
441
448
  Extra keys are dropped, not forwarded.
@@ -495,6 +502,10 @@ Panel → `serviceRequest` → host → `127.0.0.1:port` → service process →
495
502
 
496
503
  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`.
497
504
 
505
+ A service with `provides: ["browser"]` answers the agent's `browser.*` actions at `POST /browser-control` instead of the desktop app's browser panel, once the user selects it in Settings → OpenChamber Tools. The host starts it on the first action and stops it when idle. Parse the body with `readBrowserProviderRequest`; request and answer types (`BrowserProviderRequest`, `BrowserProviderResult`, per-action `Browser*Parameters` / `Browser*Data`) and the limits (`BROWSER_PROVIDER_*`) are exported from `@openchamber/sdk`. Full contract in [GUEST_SERVICES.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/GUEST_SERVICES.md#browser-provider-provides-browser).
506
+
507
+ A service with `surface: true` shows a live picture in the extension's rail panel: the host pulls frames from `GET /surface/frame`, draws them, sends the user's input to `POST /surface/input`, and owns who is in control (nobody, the agent, the user). Paths, event types, and the `readSurface*` parsers are exported from `@openchamber/sdk`; contract in [GUEST_SERVICES.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/GUEST_SERVICES.md#shared-surface-surface-true).
508
+
498
509
  Bundle the service with the Node target:
499
510
 
500
511
  ```bash
package/DOCUMENTATION.md CHANGED
@@ -16,6 +16,8 @@ Public author pages live in `packages/docs/content/docs/sdk.mdx`, `sdk/host.mdx`
16
16
  - `scripts/bundle-guest.ts`: official IIFE build, exposed as the `openchamber-guest-bundle` bin. Run `bunx openchamber-guest-bundle panel/main.ts panel/main.js` from the guest folder; paths resolve against the current directory. Needs Bun. Any bundler with an IIFE target is an equivalent substitute.
17
17
  - `dist/`: the published output (`bun run --cwd packages/sdk build`, `tsc -p tsconfig.build.json`). `package.json` `exports` point here so Node loads the SDK from `node_modules` (packaged Electron, `openchamber serve`). Inside the monorepo, Vite aliases and tsconfig `paths` resolve `@openchamber/sdk` to `src/` so builds and type-checks never depend on a stale `dist`. The server under Bun and Node uses `dist`; the root `postinstall` builds it. The package ships to npm from the release workflow with `bun publish`, right before `@openchamber/web`; `scripts/bump-version.mjs` bumps it with the app, so the SDK version always equals the OpenChamber version it was released with.
18
18
  - `GUEST_SERVICES.md`: local service contract (`contributes.service`, `serviceRequest`, grant, loopback).
19
+ - `src/service-providers.ts`: contracts a service fulfils for a host role (`contributes.service.provides`). Today only `browser`: the ten `browser.*` actions, their parameter and answer types, the limits, `BrowserProviderContext` (the calling project and chat, filled by the host from the tool call; `null` fields mean unknown), and `readBrowserProviderRequest` (a zod-free boundary parse for the service side). `src/service-provider-schemas.ts` is the host's zod parse of the answer, exported from `@openchamber/sdk/schemas`. A providing service needs no panel or background entry (`runtimeContributions` in `parse.ts`). The host side is `packages/web/server/lib/browser-control/provider.js`.
20
+ - `src/service-surface.ts`: the shared-surface contract (`contributes.service.surface`): loopback paths and headers, input event types, limits, and the zod-free `readSurfaceInputBatch` / `readSurfaceControlNotice` / `readSurfaceResizeRequest` for the service side. `src/service-surface-schemas.ts` (host entry) holds the viewer↔host socket messages (`surfaceViewerMessageSchema`, `surfaceHostMessageSchema`) shared by `packages/ui/src/lib/guests/surface-client.ts` and `packages/web/server/lib/guests/surface.js`, plus the service answer schemas. A surface service needs no entry of its own; with `panel.entry` that page is docked to one edge of the picture, `panel.dock` (`GUEST_SURFACE_DOCKS`) and `panel.size` px (`GUEST_SURFACE_DOCK_SIZE_*`), only valid with both. The rail panel is `packages/ui/src/components/layout/GuestSurfacePane.tsx` (host-drawn canvas, not an iframe); `ContextPanel` lays the page iframe (kept mounted) beside the pane (mounted only while shown) per `DOCK_LAYOUT`, and `lib/guests/surfaces.ts#guestSurfaceDocking` decides. Viewer identity is host-set: input carries `x-surface-viewer` and `x-surface-frame-seq` (the service's own number of the frame that viewer last drew; a 409 refuses it as stale), control notices carry `viewer`, and page `serviceRequest`s from a window with a live viewer (`packages/ui/src/lib/guests/surface-viewers.ts`) also carry `x-surface-viewer-controls`.
19
21
  - `src/ui/`: guest drawing kit. Building blocks only (`mountButton`, `mountTextField`, `mountSearchField`, `mountSelect`, `mountCheckbox`, `mountSwitch`, `mountTabs`, `mountBadge`, `mountList`, `mountEmpty`, `mountSpinner`, `mountBanner`, `mountSeparator`, `mountProgress`, `mountMenu`, `mountText`); the guest composes screens. See `src/ui/DOCUMENTATION.md`. Import `@openchamber/sdk/ui`. Do not import this from `packages/ui`. Kit changes follow package semver, not `apiVersion`.
20
22
 
21
23
  ## Invariants
@@ -24,6 +26,9 @@ Public author pages live in `packages/docs/content/docs/sdk.mdx`, `sdk/host.mdx`
24
26
 
25
27
  - Extension activity uses `useConfigStore.isConnected` plus live status records and directory `sessionStatusReady` authority. `useUIStore.eventStreamStatus` is an unmaintained diagnostic field, not a connection gate; its default `idle` must not hide known running or idle sessions as `unknown`.
26
28
 
29
+ - `contributes.statusSection` is `true` or `{ entry, title?, height? }` (`StatusSectionContribution`). `true` reuses `panel.entry` and fails as `invalid-status-section` without one; the object form's `entry` is package-local `.html`, `title` 1 to `GUEST_STATUS_SECTION_TITLE_MAX` (60), `height` `GUEST_STATUS_SECTION_HEIGHT_MIN`..`MAX` (24..320). `resolveStatusSectionEntry` resolves shorthand. The object form is a frame of its own: without `panel.entry` or `background.entry` it permits `capabilities`, `service`, `integration`, and `filesystem`, and refuses `page`, `attach`, `actions`, and `commands` as `invalid-panel`, because those are opened or invoked elsewhere. The server validates the HTML/scripts, exposes `statusEntry`/`statusTitle`/`statusHeight`, and counts `statusEntry` as a runtime entry for serving and storage. The host mounts `PluginPane surface="status"` inside the Work Status panel (`components/chat/work-status/WorkStatusExtensionSection.tsx`, see that folder's `DOCUMENTATION.md`): the same sandbox, guest-scoped token, context, grants, approval, and pause gates as a panel. Guest `resize` (`host.setHeight`, `ResizeRequest { height }`, integer 0..`GUEST_FRAME_HEIGHT_MAX`) is a request answered with an empty result on every surface; only `status` sizes its frame from it, through `clampStatusSectionHeight`, and a page docked to a shared surface sizes its dock from it (`DockedGuestPage` in `ContextPanel`: thickness across the dock edge, at least `GUEST_SURFACE_DOCK_SIZE_MIN`, at most half the panel through CSS). Each frame's `setHeight` reaches only its own pane. A status frame never registers a command resolver, clears a badge, or closes rail tabs.
30
+ - `openCommit(sha)` posts `open-commit { sha }`. `sha` is 7 to 64 hex characters (`GUEST_COMMIT_SHA`); the client refuses anything else as `HOST_REJECTED` before posting and the host schema drops it. The host (`lib/guests/open-commit.ts`) reads the commit itself with `git.getGitLog(directory, { maxCount: 1, to: sha })` in the frame's current directory, checks the returned hash starts with the requested one (`NOT_FOUND` otherwise), selects it in `useCommitSelectionStore` under the Diff view's key (directory, current branch, git runtime key), and opens the context panel's Diff tab in `commit` scope. No project is `NO_DIRECTORY`. VS Code and mobile answer `UNSUPPORTED` explicitly; they load no guests today, so this is the contract for when they do. No capability is needed: it only shows the user their own history.
31
+ - Scrollbars in guest documents follow the app's overlay behavior: hidden at rest, shown on hover and for one second after the element last scrolled. `GUEST_SCROLLBAR_CSS` does the hiding and hover; `installGuestScrollbarActivity` (one capturing passive `scroll` listener per document) marks the scrolling element with `data-oc-scrolling`. The server appends both to every served guest HTML as `GUEST_SCROLLBAR_SCRIPT` (the function's own source), so already installed bundles get it without a rebuild; the UI kit installs it too for previews. An authored CSP that blocks inline scripts keeps the hover-only behavior. Forced-colors mode keeps system colors.
27
32
  - `contributes.page` is `true` or `{ entry, title? }`, requires `panel.entry`, and adds a user-opened full-screen page. `resolvePageEntry` resolves shorthand; the server validates its HTML/scripts and exposes `pageEntry`/`pageTitle`. `PluginPane surface="page"` uses the same sandbox. `useUIStore.openGuestPageId` is transient and mutually exclusive with other main pages. The sidebar menu opens it; `close()` or explicit session navigation closes it. Pages are not context-rail surfaces.
28
33
  - `workspace.ts` owns project/worktree/session snapshots and storage types; `workspace-schemas.ts` validates their wire contracts. The existing `sessions` grant covers lists, subscriptions, opening sessions, and targeted creation. `lib/guests/workspace.ts` projects shared project, topology, session, and live-status stores; reads perform no network or git discovery. A registered observer is shared per runtime/extension/query and stops with its last listener. `PluginPane` limits each frame to 32 subscriptions and clears them on unmount, grant withdrawal, and runtime switch. Initial snapshots and updates have explicit loading/error coverage. `HostClient.onProjects/onWorktrees/onSessions` return promises of unsubscribe functions, so approval failures reach the caller.
29
34
  - `storage` is instance-owned JSON under the guest's namespace, without a new capability. `storage.js` serializes authorization, read-modify-write, and removal per namespace; writes use unique temporary files and rename. Missing keys differ from stored null; corrupt files and failed writes fail without clearing data. Limits and public result shapes live in `API.md`. For project data, extensions namespace keys with project IDs.
@@ -34,7 +39,7 @@ Public author pages live in `packages/docs/content/docs/sdk.mdx`, `sdk/host.mdx`
34
39
  - Extra manifest keys are dropped, not forwarded.
35
40
  - Theme text fields are required on `apiVersion: 1`: `primaryText`, `successText`, `warningText`, `errorText`, `infoText`. Host calculation and UI-kit application are documented in `src/ui/DOCUMENTATION.md`. Keep the wire schema, TypeScript contract and iframe mapping in sync.
36
41
  - `connectHost` rejects when `parent === self`. A silent host rejects in-flight RPCs as `HOST_TIMEOUT` after 20s. Dispose rejects the rest as `HOST_UNAVAILABLE`.
37
- - Failed `result` may carry `code`. Known guest codes are `HOST_UNAVAILABLE`, `HOST_TIMEOUT`, `HOST_REJECTED`, `DISCONNECTED`, `DISABLED`, `BAD_PATH`, `NO_INTEGRATION`, `NO_SESSION`, `SESSION_BUSY`, `NO_SERVICE`, `SERVICE_FAILED`, `NOT_GRANTED`, `NO_DIRECTORY`, `NOT_FOUND`, `FILE_TOO_LARGE`, `DENIED`, `NO_MODEL`, `MODEL_FAILED`. Anything else becomes `HOST_REJECTED`. The `error` string stays.
42
+ - Failed `result` may carry `code`. Known guest codes are `HOST_UNAVAILABLE`, `HOST_TIMEOUT`, `HOST_REJECTED`, `DISCONNECTED`, `DISABLED`, `BAD_PATH`, `NO_INTEGRATION`, `NO_SESSION`, `SESSION_BUSY`, `NO_SERVICE`, `SERVICE_FAILED`, `NOT_GRANTED`, `NO_DIRECTORY`, `NOT_FOUND`, `FILE_TOO_LARGE`, `DENIED`, `NO_MODEL`, `MODEL_FAILED`, `UNSUPPORTED`. Anything else becomes `HOST_REJECTED`. The `error` string stays.
38
43
  - `parseManifest` takes a typed document. File or network bytes go through `parseManifestJson`. Both return a result and do not throw on junk.
39
44
  - The host parses iframe `event.data` with `guestMessageSchema` (`@openchamber/sdk/schemas`). The guest reads host pushes with `readHostMessage` in `contract.ts`: envelope and discriminant only, because the sender is the verified parent frame. `protocol.ts` asserts the hand-written `HostMessage` / `GuestMessage` types against the zod schemas at compile time, so the two cannot drift.
40
45
  - `ready.item` is `GuestItem | null`: the composer chip (`AttachIssueRequest`, unchanged, `kind` still `issue` / `pull`), or a `GuestMessageItem` / `GuestSessionItem` from a declared action, discriminated by `kind: 'message' | 'session'` and carrying the manifest `action` id. A message item's `text` is what `formatMessageRecordText` renders for the export, capped at `GUEST_ITEM_MESSAGE_TEXT_MAX` (200 000). A session item carries `messages` only when the action declared `payload: ["messages"]` and `conversation` is granted; `buildGuestSessionItem` in `packages/ui/src/lib/guests/actions.ts` drops the oldest messages until the serialized item is under `GUEST_ITEM_SESSION_MAX` (2 000 000) and sets `truncated`. The host schema refuses a larger item. `isGuestAttachItem` / `isGuestMessageItem` / `isGuestSessionItem` narrow it.
@@ -42,7 +47,7 @@ Public author pages live in `packages/docs/content/docs/sdk.mdx`, `sdk/host.mdx`
42
47
  - `contributes.commands` is 1 to 8 entries (`GUEST_COMMANDS_MAX`) with a unique `name` matching `GUEST_COMMAND_NAME` (`/^[a-z][a-z0-9-]{0,23}$/`) and an optional `description` of 1 to `GUEST_COMMAND_DESCRIPTION_MAX` (80) characters; else `invalid-commands`. The host owns collisions: a name the composer already has (built-in, OpenCode command, skill, or an earlier guest) is dropped with a console warning. The public catalog row carries `commands` as declared.
43
48
  - `contributes.tools` is 1 to 16 entries (`GUEST_TOOLS_MAX`) that say how the extension's tool calls look in the chat, without code. `match` is the full tool name as OpenCode reports it, un-normalized (`mcp.jira.search`, `jira_search`), 1 to `GUEST_TOOL_MATCH_MAX` (128) characters of `[A-Za-z0-9_.:-]`, with one `*` allowed at the end as a suffix wildcard (`mcp.jira.*`; `*` alone is refused). Optional `name` (1 to `GUEST_TOOL_NAME_MAX`, 40) is the header title when `title` is absent or renders empty; `icon` is a Remixicon name or a package `.svg` path with the `panel.icon` rules (the chat draws the SVG as a `currentColor` mask at the glyph size through `resolveGuestToolIcon`, fetched as an authenticated asset under `/api/guests/<id>/<path>`; a Remixicon the sprite lacks leaves the host's own tool icon); `title` and `subtitle` are templates of up to `GUEST_TOOL_TEMPLATE_MAX` (200) characters with `{input.path}`, `{output.path}`, and `{metadata.path}` placeholders (dotted paths, array indexes allowed; a missing path is an empty string; a value is stringified and cut at `GUEST_TOOL_TEMPLATE_VALUE_MAX`, 200). `output` is `auto` (the host's own detection, the default), `text`, `json` (the JSON summary/tree/raw views), `markdown` (the assistant markdown renderer), `code` with `language` (1 to 32), or `table` with `columns` (1 to 16 dotted paths; rows are the output array or `output.items`). `language` outside `code` and `columns` outside `table` (or a `table` without `columns`) fail closed; anything else wrong is `invalid-tools`. The public catalog row carries `tools` as declared; the host applies them only for an active guest, an exact `match` beats a wildcard from any extension, and among equals the first extension in the catalog wins. The registry lives in `packages/ui/src/lib/guests/tool-presentation.ts` and is consulted by the existing chat tool renderers; VS Code and mobile keep the catalog empty, so nothing changes there.
44
49
  - Host → guest `resolve` is `{ id, payload: { command, args } }`; the guest answers `resolve-result` with the same `id` and `{ item }` (a clamped `AttachIssueRequest` or `null`) or `{ error }` (1 to `GUEST_RESOLVE_ERROR_MAX` characters). The host waits `GUEST_REQUEST_TIMEOUT_MS`, then treats silence as a failure and never sends a `result` for a `resolve-result`. `badge` is `{ count }` with `count` an integer 0..999 or `null`; the host schema drops anything else.
45
- - `ready` always carries `session` (`{ id, title, busy }` or `null`), `surface` (`panel` on the rail, `dialog` in the attach window), `connection` (`{ connected, account }`), and `settings` (declared keys only). Title falls back to `id` when the host record has no title. `busy` is live `session_status` `busy` or `retry`. Missing status is `busy: false`, not an empty success that clears a previous snapshot. `model` is `providerID/id` when the host session record has both. `agent` is the session agent name when present. `surface` is which host chrome mounted the iframe. It is not `openSurface`. `item` is the attached item the surface was opened for (the user clicked that item's chip on the composer; the same `AttachIssueRequest` shape the guest handed to `attach`, with `text` filled from the chip's context) or `null` from the rail icon and the + menu. The host also pushes `item` when it changes while the frame is up; `onItem` replays the latest value. `ready.theme.tokens` is the host colour bag: surfaces (`background`, `elevated`, `mutedSurface`, `subtle`), text (`foreground`, `elevatedForeground`, `muted`), interaction (`border`, `hover`, `active`, `selection`, `selectionForeground`, `focus`), `primary` / `primaryForeground`, the four status colours, `font`, `mono`, and `radius`. `applyHostReady` writes it onto the iframe. The host also pushes `connection` and `settings` the same way as `session`. Access tokens never appear in `ready` or a result.
50
+ - `ready` always carries `session` (`{ id, title, busy }` or `null`), `surface` (`panel` on the rail, `dialog` in the attach window, `page` full screen, `background` hidden, `status` in the Work Status panel), `connection` (`{ connected, account }`), and `settings` (declared keys only). Title falls back to `id` when the host record has no title. `busy` is live `session_status` `busy` or `retry`. Missing status is `busy: false`, not an empty success that clears a previous snapshot. `model` is `providerID/id` when the host session record has both. `agent` is the session agent name when present. `surface` is which host chrome mounted the iframe. It is not `openSurface`. `item` is the attached item the surface was opened for (the user clicked that item's chip on the composer; the same `AttachIssueRequest` shape the guest handed to `attach`, with `text` filled from the chip's context) or `null` from the rail icon and the + menu. The host also pushes `item` when it changes while the frame is up; `onItem` replays the latest value. `ready.theme.tokens` is the host colour bag: surfaces (`background`, `elevated`, `mutedSurface`, `subtle`), text (`foreground`, `elevatedForeground`, `muted`), interaction (`border`, `hover`, `active`, `selection`, `selectionForeground`, `focus`), `primary` / `primaryForeground`, the four status colours, `font`, `mono`, and `radius`. `applyHostReady` writes it onto the iframe. The host also pushes `connection` and `settings` the same way as `session`. Access tokens never appear in `ready` or a result.
46
51
  - `writeClipboard` text is 1–32000 characters. `compose` text is 1–16000 after trim. Compose default mode is `append`. Compose writes the chat composer. It does not send.
47
52
  - `contributes.capabilities` is a list of `prompt`, `sessions`, `files`, and/or `model` (`DECLARED_GUEST_CAPABILITIES`). `model` gates `generate`: `{ prompt, system?, maxOutputTokens? }` → `{ text }`, posted with a `GUEST_GENERATE_TIMEOUT_MS` (90s) timeout instead of the usual 20s because the model answers slowly; the host answers `NO_MODEL` when the small-model service resolves nothing (or only Claude Code) and `MODEL_FAILED` when the provider call fails. `requestedGuestCapabilities` adds `conversation`, `service`, `network`, and `filesystem` from a session action with `payload: ["messages"]`, `contributes.service`, `contributes.integration`, and `contributes.filesystem`; the user approves that whole list once at install (`PUT /api/guests/:id/capabilities`), and a package that later asks for more is re-approved. `hasGuestCapability` gates `prompt({ send })`, `startSession`, `request`, `serviceRequest`, and the four file calls; a refusal is `NOT_GRANTED` (`NO_SERVICE` for the service).
48
53
  - `contributes.filesystem` is 1 to 16 globs, each 1 to 256 characters, starting with `/` or `~/`, with no `..`, empty segment, NUL, or backslash (`isGuestFilesystemPattern`; parse failure is `invalid-filesystem`). `**` spans directories, `*` and `?` stay inside one segment, and a pattern ending in `/**` also matches the directory itself. The public catalog row carries the list as `filesystem` so the approval dialog can show it.
package/GUEST_SERVICES.md CHANGED
@@ -88,6 +88,8 @@ Parse rules:
88
88
  - `contributes.service` is optional. A guest without `service` is HTML-only.
89
89
  - `service.entry` is a relative path inside the package. Ship compiled JS; the host never compiles TypeScript.
90
90
  - `service.runtime` phase 1 accepts only `"host"`.
91
+ - `service.provides` is optional: roles the service stands in for on the host. Only `"browser"` exists today (below). A service that provides a role needs no `panel.entry` or `background.entry`; the host starts it itself.
92
+ - `service.surface` is optional (`true`): the service shows a live picture the host draws in the extension's rail panel and takes the user's input back (below). With `panel.entry` too, that page is docked to one edge of the picture (`panel.dock`: `top`, `bottom`, `left`, or `right`, default `top`; `panel.size` in CSS px across that edge, 24 to 480, default 40); without it, the picture is the whole panel.
91
93
  - `service.permissions.sockets` and `service.permissions.exec` are shown to the user in the approval dialog. They describe intent and do not confine the process: a service runs with the user's full access. Declaring `contributes.service` adds the `service` capability to the package's request list; the user approves the whole list once when the package is installed (Settings → Extensions), and the first `serviceRequest` is refused with `NO_SERVICE` until then.
92
94
  - The catalog adds `service.socketBindings`: `{ id, candidates, resolved, override }` for this host. The user can override a path in Extensions. Override empty clears it and the next spawn re-resolves.
93
95
  - `contributes.integration` remains valid next to `service`. Cloud `request` and `serviceRequest` may both exist on one guest.
@@ -107,7 +109,8 @@ Error codes:
107
109
 
108
110
  - `NO_SERVICE` — no service declared, not granted, not started, or already torn down
109
111
  - `DISABLED` — extension paused in Settings → Extensions (service stopped; tokens/grants stay)
110
- - `SERVICE_FAILED` — process crashed or never became ready
112
+ - `SERVICE_FAILED` — process crashed or never became ready; nothing was sent to it
113
+ - `REQUEST_FAILED` — the request was sent but got no usable answer (timeout, dropped connection); the service may have acted on it
111
114
  - Existing: `HOST_TIMEOUT`, `HOST_REJECTED`, `HOST_UNAVAILABLE`, `BAD_PATH`
112
115
 
113
116
  Server routes (authenticated UI session):
@@ -164,3 +167,87 @@ Ship `service/main.js` already built. Same packaging rule as `panel/main.js`.
164
167
  ## Example
165
168
 
166
169
  `examples/service-echo` is a checked-in service extension: a Node HTTP server on loopback that the panel calls through `serviceRequest` and whose status it shows. Bundle the service with `--node`, install the folder from Settings → Extensions, allow the local service in the approval dialog, then open the rail panel. Streaming from a service to the panel (shell into a container, log tails) is deferred; it needs a streaming call on the SDK first.
170
+
171
+ ## Browser provider (`provides: ["browser"]`)
172
+
173
+ The host's `openchamber_web` tool gives agents ten `browser.*` actions. By default a connected desktop app answers them with its browser panel; on a server with no desktop client the agent gets nothing. A service that declares `provides: ["browser"]` can answer instead, from a browser it runs itself (headless Chrome over CDP, for instance). The agent sees one tool either way.
174
+
175
+ The user picks the provider in Settings → General → OpenChamber Tools → Browser provider. The dropdown lists installed, enabled, fully approved extensions with the role; with none it shows only "OpenChamber Web", disabled. The choice is `browserProvider` in the instance's `settings.json` (`builtin` or the extension id) and is read on every action, so it applies to the next action without a restart. Pausing, removing, or withdrawing approval from the selected extension puts `builtin` back and every open client shows a toast saying so; the same happens on the next action if the extension became unusable any other way.
176
+
177
+ Lifecycle differs from a panel-driven service in two ways. The host starts the service on the first action, so the agent can browse with no panel and no viewer; the grant is the same one the user gave at install. After `BROWSER_PROVIDER_IDLE_MS` (ten minutes) without an action the host stops the process; the next action starts it again. A `browser.open` may take `BROWSER_PROVIDER_OPEN_TIMEOUT_MS` (45 s), every other action `BROWSER_PROVIDER_ACTION_TIMEOUT_MS` (20 s). Answers are read up to `BROWSER_PROVIDER_RESPONSE_MAX` (12 MB), enough for a screenshot.
178
+
179
+ ### Wire
180
+
181
+ `POST /browser-control` on the service loopback, same bearer as every request, JSON body:
182
+
183
+ ```json
184
+ {
185
+ "requestId": "browser-…",
186
+ "action": "browser.click",
187
+ "parameters": { "selector": "#save" },
188
+ "context": { "directory": "/Users/me/app", "sessionId": "ses_…" }
189
+ }
190
+ ```
191
+
192
+ `readBrowserProviderRequest(body)` from `@openchamber/sdk` parses it (`null` → answer HTTP 400). The host validated `parameters` for the action before posting, so the service can trust the shape.
193
+
194
+ `context` says where the action came from: the project the agent works in and the chat it runs in. The host fills it from the tool call; the model never types it. A provider that keeps one browser per project or chat keys its targets on these; one that keeps a single browser ignores them. Either field is `null` when the host had none (an action sent from the CLI, for example); treat that as "unknown", not as a scope of its own. The shared surface is still one per service: if you keep several targets, choose which one the picture shows.
195
+
196
+ Answer HTTP 200 with one of:
197
+
198
+ ```json
199
+ { "ok": true, "data": { … } }
200
+ { "ok": false, "error": "No element matches #save" }
201
+ ```
202
+
203
+ `error` goes to the agent as the tool's error, so write it as what to do differently, not what broke inside. Any other status or shape is reported to the agent as "unknown page state".
204
+
205
+ ### Actions
206
+
207
+ Parameters the host sends and `data` the service answers; types are exported from `@openchamber/sdk` (`BrowserOpenParameters`, `BrowserOpenData`, …). Every shape is what the desktop app's own panel answers, so an agent's prompt written against one works against the other.
208
+
209
+ | Action | Parameters | `data` |
210
+ |---|---|---|
211
+ | `browser.open` | `url` (absolute http/https), `viewport?` | `url`, `title`, `opened: true`, `settled` (`false` = still loading, not a failure), `viewport` |
212
+ | `browser.snapshot` | `selector?` (scope) | `url`, `title`, `scope`, `scrollY`, `maxScrollY`, `text`, `elements[]`, `viewport`, optional `textTruncated`/`textTotalChars`, `elementsTruncated`/`interactiveElementsOnPage`, `consoleProblems[]` |
213
+ | `browser.click` | `selector?` or `text?` (visible label) | `clicked` (selector), `label`, `url` |
214
+ | `browser.type` | `selector`, `value`, `submit` | `selector`, `url` |
215
+ | `browser.scroll` | `direction?` (`up`/`down`/`top`/`bottom`) or `selector?` | `scrollY`, `maxScrollY`, `atTop`, `atBottom`, optional `scrolledTo`/`direction` |
216
+ | `browser.back` / `browser.forward` | none | `url`, `title` |
217
+ | `browser.inspect` | `selector` | `selector`, `tag`, `label`, `bounds`, `inViewport`, `styles` (computed, as strings) |
218
+ | `browser.capture` | `label?` | `base64`, `mime`, `width`, `height`, `url`, `title`, `viewport`; the host writes the file into the project and returns its path |
219
+ | `browser.resize` | `viewport` (`mobile`/`tablet`/`desktop`/`fill`) | `viewport` |
220
+
221
+ Every action may carry `tabId` (`BrowserTabTarget`): an id from the `tabs` your snapshot listed (`[{ id, title, url, active }]`, `active` being the tab the user sees), passed through from the agent untouched. Without it, act on the tab the user sees, except `browser.open`: without `tabId` it opens a new background tab and answers its id as `tabId` (`BrowserOpenData`), so the agent never replaces the user's page. Refuse an id you did not issue with `ok: false`; never act on another tab instead. A provider with one page lists no tabs and refuses every id.
222
+
223
+ `viewport` in answers is `{ mode, width, height }` (`mode` may be `custom`; `fill` has `null` sizes). Snapshot `elements` carry `selector`, `tag`, `bounds`, and only the fields that apply (`inViewport`, `type`, `role`, `label`, `disabled`, `missingAccessibleName`). Keep `text` and `elements` bounded yourself; report what was dropped with the truncation fields.
224
+
225
+ `examples/browser-provider-stub` is a checked-in provider with no browser: one in-memory page that answers every action. Install it to see the dropdown, the routing, and the idle stop before writing a real one.
226
+
227
+ ## Shared surface (`surface: true`)
228
+
229
+ A surface lets a person watch what an agent is working in and step in for a manual step. It is not about browsers: a service that drives a browser, a simulator, or a desktop app can show one. The host draws it in the extension's rail panel (a canvas; the extension runs no code there), sends the user's pointer, keyboard, and paste back, and decides who is in control.
230
+
231
+ Control has three states. Nobody, the agent (for `SURFACE_AGENT_HOLD_MS`, 30 s, after each action the host ran against this extension, such as a browser provider action), or the user. The user takes control by acting: the first click, wheel, key, or paste while nobody or the agent holds it. Moving the pointer over the picture is looking, not acting; it takes nothing and is not even sent until the user holds control. While the user holds it, the host refuses the browser provider's actions with a message that tells the agent to wait or ask, and the panel shows a "Hand back to agent" button. A second device sees who holds control and cannot take it. Every change is posted to the service so its own automation can pause. The service is kept running while a viewer is attached, and returns to its idle window when the last one leaves.
232
+
233
+ ### Wire
234
+
235
+ Plain HTTP on the service loopback, same bearer as everything else:
236
+
237
+ | Call | Body / answer |
238
+ |---|---|
239
+ | `GET /surface/frame?after=<seq>&wait=<ms>` | 200 with `image/jpeg` or `image/png` bytes and headers `x-surface-seq`, `x-surface-width`, `x-surface-height`, optional `x-surface-title`, optional `x-surface-agent-active: 1` while your own automation is driving; 204 when nothing newer than `seq` arrived within `wait` ms. Sequence numbers start at 1, so `after=0` is "the current picture, now". The host asks one frame at a time per viewer; a viewer that draws slowly skips frames rather than queueing them. Frames up to `SURFACE_FRAME_MAX_BYTES` (8 MB). |
240
+ | `POST /surface/input` | `{ events: SurfaceInputEvent[] }`: `pointer` (`down`/`up`/`move`, `x`, `y` in frame pixels, `button`, `buttons`, `modifiers`), `wheel` (`x`, `y`, `deltaX`, `deltaY`, `modifiers`), `key` (`down`/`up`, `key`, `code`, `modifiers`), `text` (pasted or composed text). Parse with `readSurfaceInputBatch`; `null` → 400. Sent only while the user holds control. Headers `x-surface-viewer` (the viewer's id) and `x-surface-frame-seq` (your own sequence number of the frame that viewer last drew, `0` before its first) say where the input came from; answer 409 to refuse input made on a picture you no longer show, and the host tells the viewer it was not applied. |
241
+ | `POST /surface/control` | `{ controller: "none" \| "agent" \| "user", viewer? }` whenever control changes; `viewer` is the controlling viewer's id when `controller` is `user`. Parse with `readSurfaceControlNotice`. Advisory. |
242
+ | `POST /surface/resize` | `{ width, height }` the panel can show, in device pixels. Answer `{ width, height }` you settled on, or 400 to keep your size. Parse with `readSurfaceResizeRequest`. |
243
+ | `GET /surface/clipboard` | `{ text }`: what the user copied inside the surface. The host asks after a copy chord and puts it on the user's clipboard. |
244
+
245
+ Input, control notices, resizes, and clipboard reads reach the service one at a time, in the order the viewer sent them, so a batch never overtakes the one before it. Copy and paste: the host sends `Ctrl/Cmd+C` as a `key` event and then, behind it, reads `/surface/clipboard`; it never sends the paste chord, it sends a `text` event with the pasted text instead. Every other key reaches you as pressed, including the host's own shortcuts, which stand down while the surface has focus.
246
+
247
+ ### Your own controls beside the picture
248
+
249
+ Declare `panel.entry` as well and the host docks that page to one edge of the surface: `panel.dock` picks the edge (`top` by default; `bottom` for an inspector, `left` or `right` for a tool column) and `panel.size` its thickness in CSS pixels across that edge (default 40). It is an ordinary sandboxed panel page: it talks to your service through `host.serviceRequest`, gets the theme and the current session like any panel, and stays mounted while the tab is hidden. Call `host.setHeight(px)` to grow or shrink the dock, for example to open a console under the address field and close it again: the value is the thickness across the dock's edge (a width for `left` and `right`), at least 24 px and at most half the panel, so the picture stays in view. Put an address field, tabs, a device picker, or a console toggle there; the picture, the input, and who is in control stay with the host. The page and the surface are independent: the page does not see frames, and the host does not route input through it.
250
+
251
+ While a viewer is open in the same window, every `serviceRequest` from your pages (the docked one included) reaches the service with three headers the host sets itself: `x-surface-viewer`, `x-surface-viewer-controls` (`1` while that viewer holds control, else `0`), and `x-surface-frame-seq` (the frame it last drew). Compare the viewer with the one from `/surface/control` to let the person in control use the dock while others only watch, and compare the frame with the view it belongs to so a toolbar command made on an old picture cannot change a view selected since. Without a viewer in that window the headers are absent.
252
+
253
+ The types and paths are exported from `@openchamber/sdk` (`SURFACE_*`, `SurfaceInputEvent`, …). `examples/browser-provider-stub` also declares `surface: true` and paints its fake page with rectangles, so the viewer, the hand-off, and the input path can be seen working without a browser; its `panel/` is a one-line address bar docked above the picture.
package/README.md CHANGED
@@ -96,7 +96,7 @@ Git installs also accept SSH addresses such as `git@github.com:owner/extension.g
96
96
  - `tools` is optional: how your tool calls look in the chat, up to 16, no code. `match` is the tool name OpenCode reports (`mcp.tasks.*` matches every tool under that prefix); `name` and `icon` (a Remixicon name or an SVG inside the folder, like `panel.icon`) set the header, `title` and `subtitle` are templates like `{input.id}` or `{output.total} open`, and `output` picks the body: `text`, `json`, `markdown`, `code` (with `language`), or `table` (with `columns`, rows from the output array or `output.items`). Leave `output` out to keep the app's own detection.
97
97
  - `capabilities` lists what needs the user's approval: `prompt` to send messages, `sessions` to create sessions and worktrees, `files` to read and write inside the open project, `model` for one-off text generation with the user's Small Model (`host.generate`, no session involved). An `integration` adds `network`, a `service` adds `service`, `filesystem` patterns (like `["~/.config/opencode/opencode.json"]`) add `filesystem`, which lets `readFile`, `writeFile`, `listDir`, and `stat` reach those paths outside the project, and a session action with `payload: ["messages"]` adds `conversation`. The user approves the whole list once at install. Calls outside it fail with `NOT_GRANTED`.
98
98
  - `integration` is optional. It adds a card at Settings → Integrations. `token` takes a pasted API token (`scheme: "bearer"` for `Authorization: Bearer`, `"basic"` for a username and token pair as Jira Cloud wants), `oauth` runs an authorize flow with a pasted client id, and `host: { "provider": "linear" }` reuses the Linear account already connected in OpenChamber. The page never sees the token; OpenChamber makes the calls through `host.request`.
99
- - `service` is optional. It declares a local process OpenChamber starts next to the extension. It runs with the user's full access and no sandbox, so declare one only when the page cannot do the job. See [GUEST_SERVICES.md](./GUEST_SERVICES.md).
99
+ - `service` is optional. It declares a local process OpenChamber starts next to the extension. It runs with the user's full access and no sandbox, so declare one only when the page cannot do the job. See [GUEST_SERVICES.md](./GUEST_SERVICES.md). With `provides: ["browser"]` the service can stand in for the agent's browser, so agents browse on the server with no desktop app open; the user picks it in Settings → OpenChamber Tools. With `surface: true` it shows a live picture in the rail that the user can watch and take over, and hand back to the agent; add `panel.entry` (with `panel.dock` and `panel.size`) for your own controls docked beside it.
100
100
 
101
101
  ## Actions without opening a panel
102
102
 
@@ -157,6 +157,8 @@ Copy keeps the toast open and shows Copied on success. A failed copy shows an er
157
157
 
158
158
  For a full-screen board, add `"page": true` under `contributes`, or `"page": { "entry": "panel/page.html", "title": "Board" }` for separate HTML. Users open it from the Extension pages menu above the session list. `ctx.surface` is `"page"`. The extension cannot open the page itself.
159
159
 
160
+ For a small readout in the chat's Work Status panel, add `"statusSection": { "entry": "status/index.html", "title": "Recent commits" }`. It needs no `panel.entry`, so a section-only extension has no rail icon. `ctx.surface` is `"status"`. Call `host.setHeight(px)` when your content changes size; the host stops growing the frame at 320 px and your page scrolls after that. `examples/git-graph-status` is a complete section with a local service.
161
+
160
162
  With `sessions` approved, use `listProjects()`, `listWorktrees(projectId)`, and `listSessions(projectId)`. Subscribe through `await onProjects(listener)`, `await onWorktrees(projectId, listener)`, or `await onSessions(projectId, listener)` and retain the returned unsubscribe function. Snapshots distinguish loading, ready, and error; session activity and observed turn outcomes are separate from your task status.
161
163
 
162
164
  `startSession` accepts `projectId` and `worktree: { kind: "new", name: "fix-login", baseBranch: "main" }` or `{ kind: "existing", directory }`. It preserves the current screen by default. `openSession(sessionId)` explicitly opens the chat. `host.storage.get/set/delete/keys` stores your own JSON on the connected server without a file-access grant. See [API.md](./API.md) for limits and partial results. The `tasks-demo` page exercises these methods together.
@@ -200,6 +202,8 @@ await host.startSession({
200
202
  });
201
203
  await host.prompt({ text: 'Fix the login', send: true });
202
204
  await host.setBadge(3); // number on the rail icon; null clears it
205
+ await host.openCommit(sha); // show a commit of the open project in the Diff view
206
+ await host.setHeight(document.body.scrollHeight); // Work Status section: frame height, clamped by the host
203
207
  const { text } = await host.generate({ prompt: task.description, system: 'One-line summary only.' }); // capability model
204
208
 
205
209
  host.onResolve(({ command, args }) => {
@@ -238,7 +242,7 @@ host.onReady((ctx) => {
238
242
  });
239
243
  ```
240
244
 
241
- OpenChamber supplies thin, theme-aware native scrollbars inside extension documents, including nested lists, tabs, and textareas. The UI kit includes the same defaults for development previews. Existing installed bundles get the host stylesheet without rebuilding. Custom rendering hosts can use `GUEST_SCROLLBAR_CSS` from `@openchamber/sdk`. Authors can override these default rules; an extension's CSP still applies.
245
+ OpenChamber supplies thin, theme-aware native scrollbars inside extension documents, including nested lists, tabs, and textareas. Like the app's own, they stay hidden until you hover or scroll. The UI kit includes the same defaults for development previews. Existing installed bundles get the host stylesheet without rebuilding. Custom rendering hosts can use `GUEST_SCROLLBAR_CSS` from `@openchamber/sdk`. Authors can override these default rules; an extension's CSP still applies.
242
246
 
243
247
  ## Schemas
244
248
 
@@ -50,8 +50,11 @@ export type SessionSnapshot = {
50
50
  model?: string;
51
51
  agent?: string;
52
52
  };
53
- /** Which host chrome mounted this iframe. Not `openSurface`. */
54
- export type GuestHostSurface = 'panel' | 'dialog' | 'page' | 'background';
53
+ /**
54
+ * Which host chrome mounted this iframe. Not `openSurface`. `status` is the
55
+ * extension's section in the chat's Work Status panel.
56
+ */
57
+ export type GuestHostSurface = 'panel' | 'dialog' | 'page' | 'background' | 'status';
55
58
  export type GuestConnection = {
56
59
  connected: boolean;
57
60
  account: string;
@@ -235,6 +238,17 @@ export type BadgeRequest = {
235
238
  /** 0 to `GUEST_BADGE_MAX`; `null` clears the badge. */
236
239
  count: number | null;
237
240
  };
241
+ /** A commit the guest asks the host to show in its Diff view. */
242
+ export type OpenCommitRequest = {
243
+ sha: string;
244
+ };
245
+ /** Abbreviated or full hex commit id: 7 to 64 characters. The host resolves it in the open project. */
246
+ export declare const GUEST_COMMIT_SHA: RegExp;
247
+ export declare const isGuestCommitSha: (value: string) => boolean;
248
+ /** The content height the guest would like, in CSS px. Only the `status` surface sizes its frame from it. */
249
+ export type ResizeRequest = {
250
+ height: number;
251
+ };
238
252
  export type ToastKind = 'info' | 'success' | 'error';
239
253
  export type ToastRequest = {
240
254
  kind: ToastKind;
@@ -325,9 +339,11 @@ export declare const GUEST_ITEM_MESSAGE_TEXT_MAX = 200000;
325
339
  export declare const GUEST_ITEM_SESSION_MAX = 2000000;
326
340
  /** Largest count a rail badge shows. */
327
341
  export declare const GUEST_BADGE_MAX = 999;
342
+ /** Largest height a `resize` message may carry; the host clamps further per surface. */
343
+ export declare const GUEST_FRAME_HEIGHT_MAX = 10000;
328
344
  /** Characters in a `resolve-result` error string. */
329
345
  export declare const GUEST_RESOLVE_ERROR_MAX = 500;
330
- export declare const HOST_REQUEST_ERROR_CODES: readonly ["HOST_UNAVAILABLE", "HOST_TIMEOUT", "HOST_REJECTED", "DISCONNECTED", "DISABLED", "BAD_PATH", "NO_INTEGRATION", "NO_SERVICE", "SERVICE_FAILED", "NO_SESSION", "SESSION_BUSY", "NOT_GRANTED", "NO_DIRECTORY", "NOT_FOUND", "FILE_TOO_LARGE", "DENIED", "NO_MODEL", "MODEL_FAILED"];
346
+ export declare const HOST_REQUEST_ERROR_CODES: readonly ["HOST_UNAVAILABLE", "HOST_TIMEOUT", "HOST_REJECTED", "DISCONNECTED", "DISABLED", "BAD_PATH", "NO_INTEGRATION", "NO_SERVICE", "SERVICE_FAILED", "NO_SESSION", "SESSION_BUSY", "NOT_GRANTED", "NO_DIRECTORY", "NOT_FOUND", "FILE_TOO_LARGE", "DENIED", "NO_MODEL", "MODEL_FAILED", "UNSUPPORTED"];
331
347
  export declare const SERVICE_STATUS_VALUES: readonly ["stopped", "starting", "ready", "failed"];
332
348
  export type ServiceStatus = (typeof SERVICE_STATUS_VALUES)[number];
333
349
  export type ServiceStatusResult = {
@@ -348,6 +364,8 @@ export declare const clampStartSessionRequest: (request: StartSessionRequest) =>
348
364
  export declare const clampPromptRequest: (request: PromptRequest) => PromptRequest;
349
365
  /** Badge counts are whole numbers from 0 to `GUEST_BADGE_MAX`; anything else clears. */
350
366
  export declare const clampBadgeCount: (count: number | null) => number | null;
367
+ /** Resize heights are whole CSS pixels from 0 to `GUEST_FRAME_HEIGHT_MAX`; a non-number asks for 0. */
368
+ export declare const clampFrameHeight: (height: number) => number;
351
369
  /**
352
370
  * Which grant a file path needs. `/…` and `~/…` are outside the project and
353
371
  * go through the declared `filesystem` patterns; anything else is joined to
@@ -469,6 +487,8 @@ export type GuestFileListMessage = GuestCall<'file-list', FileListRequest>;
469
487
  export type GuestFileStatMessage = GuestCall<'file-stat', FileStatRequest>;
470
488
  export type GuestGenerateMessage = GuestCall<'generate', GenerateRequest>;
471
489
  export type GuestBadgeMessage = GuestCall<'badge', BadgeRequest>;
490
+ export type GuestResizeMessage = GuestCall<'resize', ResizeRequest>;
491
+ export type GuestOpenCommitMessage = GuestCall<'open-commit', OpenCommitRequest>;
472
492
  /** Answers a host `resolve` by `id`. The host sends no `result` back for it. */
473
493
  export type GuestResolveResultMessage = Envelope & {
474
494
  type: 'resolve-result';
@@ -485,7 +505,7 @@ export type GuestMessage = GuestCall<'workspace-read', GuestWorkspaceQuery> | Gu
485
505
  subscriptionId: string;
486
506
  }> | GuestCall<'storage', GuestStorageRequest> | GuestCall<'open-session', {
487
507
  sessionId: string;
488
- }> | GuestHelloMessage | GuestToastMessage | GuestOpenUrlMessage | GuestOpenSurfaceMessage | GuestClipboardWriteMessage | GuestComposeMessage | GuestAttachMessage | GuestStartSessionMessage | GuestPromptMessage | GuestSessionLinkMessage | GuestCloseMessage | GuestOauthStartMessage | GuestOauthDisconnectMessage | GuestRequestMessage | GuestServiceRequestMessage | GuestServiceStatusMessage | GuestFileReadMessage | GuestFileWriteMessage | GuestFileListMessage | GuestFileStatMessage | GuestGenerateMessage | GuestBadgeMessage | GuestActionResultMessage | GuestResolveResultMessage;
508
+ }> | GuestHelloMessage | GuestToastMessage | GuestOpenUrlMessage | GuestOpenSurfaceMessage | GuestClipboardWriteMessage | GuestComposeMessage | GuestAttachMessage | GuestStartSessionMessage | GuestPromptMessage | GuestSessionLinkMessage | GuestCloseMessage | GuestOauthStartMessage | GuestOauthDisconnectMessage | GuestRequestMessage | GuestServiceRequestMessage | GuestServiceStatusMessage | GuestFileReadMessage | GuestFileWriteMessage | GuestFileListMessage | GuestFileStatMessage | GuestGenerateMessage | GuestBadgeMessage | GuestResizeMessage | GuestOpenCommitMessage | GuestActionResultMessage | GuestResolveResultMessage;
489
509
  export declare const isServiceStatusResult: (value: HostResultPayload | undefined) => value is ServiceStatusResult;
490
510
  export declare const isGuestRequestResult: (value: HostResultPayload | undefined) => value is GuestRequestResult;
491
511
  export declare const isFileReadResult: (value: HostResultPayload | undefined) => value is FileReadResult;
package/dist/contract.js CHANGED
@@ -13,6 +13,9 @@ export const isGuestMessageItem = (item) => (item !== null && item.kind === 'mes
13
13
  export const isGuestSessionItem = (item) => (item !== null && item.kind === 'session');
14
14
  /** The composer chip: what the guest handed to `attach`, with `text` filled from the chip. */
15
15
  export const isGuestAttachItem = (item) => (item !== null && item.kind !== 'message' && item.kind !== 'session');
16
+ /** Abbreviated or full hex commit id: 7 to 64 characters. The host resolves it in the open project. */
17
+ export const GUEST_COMMIT_SHA = /^[0-9a-f]{7,64}$/i;
18
+ export const isGuestCommitSha = (value) => GUEST_COMMIT_SHA.test(value);
16
19
  export const GUEST_TOAST_MAX = 500;
17
20
  export const GUEST_CLIPBOARD_TEXT_MAX = 32_000;
18
21
  export const GUEST_COMPOSE_TEXT_MAX = 16_000;
@@ -54,6 +57,8 @@ export const GUEST_ITEM_MESSAGE_TEXT_MAX = 200_000;
54
57
  export const GUEST_ITEM_SESSION_MAX = 2_000_000;
55
58
  /** Largest count a rail badge shows. */
56
59
  export const GUEST_BADGE_MAX = 999;
60
+ /** Largest height a `resize` message may carry; the host clamps further per surface. */
61
+ export const GUEST_FRAME_HEIGHT_MAX = 10_000;
57
62
  /** Characters in a `resolve-result` error string. */
58
63
  export const GUEST_RESOLVE_ERROR_MAX = 500;
59
64
  export const HOST_REQUEST_ERROR_CODES = [
@@ -75,6 +80,7 @@ export const HOST_REQUEST_ERROR_CODES = [
75
80
  'DENIED',
76
81
  'NO_MODEL',
77
82
  'MODEL_FAILED',
83
+ 'UNSUPPORTED',
78
84
  ];
79
85
  export const SERVICE_STATUS_VALUES = ['stopped', 'starting', 'ready', 'failed'];
80
86
  const hostRequestErrorCodeSet = new Set(HOST_REQUEST_ERROR_CODES);
@@ -160,6 +166,12 @@ export const clampBadgeCount = (count) => {
160
166
  return null;
161
167
  return Math.min(GUEST_BADGE_MAX, Math.max(0, Math.round(count)));
162
168
  };
169
+ /** Resize heights are whole CSS pixels from 0 to `GUEST_FRAME_HEIGHT_MAX`; a non-number asks for 0. */
170
+ export const clampFrameHeight = (height) => {
171
+ if (!Number.isFinite(height))
172
+ return 0;
173
+ return Math.min(GUEST_FRAME_HEIGHT_MAX, Math.max(0, Math.ceil(height)));
174
+ };
163
175
  /**
164
176
  * Which grant a file path needs. `/…` and `~/…` are outside the project and
165
177
  * go through the declared `filesystem` patterns; anything else is joined to
package/dist/host.d.ts CHANGED
@@ -57,6 +57,13 @@ export type HostClient = {
57
57
  onAction: (handler: (item: GuestActionItem) => void | Promise<void>) => () => void;
58
58
  toast: (request: ToastRequest) => Promise<void>;
59
59
  openUrl: (url: string) => Promise<void>;
60
+ /**
61
+ * Show a commit of the open project in the host's Diff view (commit scope).
62
+ * `sha` is 7 to 64 hex characters; the host reads the commit itself. No open
63
+ * project is `NO_DIRECTORY`, an unknown commit `NOT_FOUND`, a host without a
64
+ * Diff view `UNSUPPORTED`.
65
+ */
66
+ openCommit: (sha: string) => Promise<void>;
60
67
  openSurface: (surfaceId: string) => Promise<void>;
61
68
  writeClipboard: (text: string) => Promise<void>;
62
69
  compose: (request: ComposeRequest) => Promise<void>;
@@ -98,6 +105,14 @@ export type HostClient = {
98
105
  generate: (request: GenerateRequest) => Promise<GenerateResult>;
99
106
  /** Number on this guest's rail icon (0 to `GUEST_BADGE_MAX`); `null` clears it. Opening the panel clears it too. */
100
107
  setBadge: (count: number | null) => Promise<void>;
108
+ /**
109
+ * The height the guest's content needs, in CSS px. On the Work Status
110
+ * `status` surface the host sizes the frame to it, clamped to
111
+ * `GUEST_STATUS_SECTION_HEIGHT_MIN`..`GUEST_STATUS_SECTION_HEIGHT_MAX`;
112
+ * taller content scrolls inside the frame. Other surfaces fill their host
113
+ * chrome and ignore it.
114
+ */
115
+ setHeight: (height: number) => Promise<void>;
101
116
  dispose: () => void;
102
117
  };
103
118
  export declare class HostRequestError extends Error {
package/dist/host.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL } from "./api-version.js";
2
2
  import { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_VALUE_BYTES } from "./workspace.js";
3
- import { GUEST_FILE_CONTENT_MAX, GUEST_CLIPBOARD_TEXT_MAX, GUEST_TOAST_MAX, GUEST_FILE_PATH_MAX, GUEST_GENERATE_OUTPUT_TOKENS_MAX, GUEST_GENERATE_PROMPT_MAX, GUEST_GENERATE_SYSTEM_MAX, GUEST_GENERATE_TIMEOUT_MS, GUEST_REQUEST_TIMEOUT_MS, GUEST_RESOLVE_ERROR_MAX, isGuestFilePath, isGuestRequestPath, clampAttachRequest, clampBadgeCount, clampPromptRequest, clampStartSessionRequest, readHostMessage, isGenerateResult, isFileListResult, isFileReadResult, isFileStatResult, isJsonValue, isFileWriteResult, isServiceStatusResult, isGuestRequestResult, isPromptResult, isStartSessionResult, } from "./contract.js";
3
+ import { GUEST_FILE_CONTENT_MAX, GUEST_CLIPBOARD_TEXT_MAX, GUEST_TOAST_MAX, GUEST_FILE_PATH_MAX, GUEST_GENERATE_OUTPUT_TOKENS_MAX, GUEST_GENERATE_PROMPT_MAX, GUEST_GENERATE_SYSTEM_MAX, GUEST_GENERATE_TIMEOUT_MS, GUEST_REQUEST_TIMEOUT_MS, GUEST_RESOLVE_ERROR_MAX, isGuestFilePath, isGuestRequestPath, clampAttachRequest, clampBadgeCount, clampFrameHeight, isGuestCommitSha, clampPromptRequest, clampStartSessionRequest, readHostMessage, isGenerateResult, isFileListResult, isFileReadResult, isFileStatResult, isJsonValue, isFileWriteResult, isServiceStatusResult, isGuestRequestResult, isPromptResult, isStartSessionResult, } from "./contract.js";
4
4
  export class HostRequestError extends Error {
5
5
  code;
6
6
  constructor(code, message) {
@@ -390,6 +390,13 @@ export const connectHost = (options = {}) => {
390
390
  id: nextId(ids),
391
391
  payload: { url },
392
392
  }),
393
+ openCommit: (sha) => (isGuestCommitSha(sha) ? request({
394
+ channel: OPENCHAMBER_SDK_CHANNEL,
395
+ v: OPENCHAMBER_SDK_API_VERSION,
396
+ type: 'open-commit',
397
+ id: nextId(ids),
398
+ payload: { sha },
399
+ }) : Promise.reject(new HostRequestError('HOST_REJECTED', 'Commit id must be 7 to 64 hex characters.'))),
393
400
  openSurface: (surfaceId) => request({
394
401
  channel: OPENCHAMBER_SDK_CHANNEL,
395
402
  v: OPENCHAMBER_SDK_API_VERSION,
@@ -613,6 +620,13 @@ export const connectHost = (options = {}) => {
613
620
  id: nextId(ids),
614
621
  payload: { count: clampBadgeCount(count) },
615
622
  }),
623
+ setHeight: (height) => request({
624
+ channel: OPENCHAMBER_SDK_CHANNEL,
625
+ v: OPENCHAMBER_SDK_API_VERSION,
626
+ type: 'resize',
627
+ id: nextId(ids),
628
+ payload: { height: clampFrameHeight(height) },
629
+ }),
616
630
  dispose: () => {
617
631
  for (const subscriptionId of workspaceListeners.keys()) {
618
632
  post({ ...envelope, type: 'workspace-unsubscribe', id: nextId(ids), payload: { subscriptionId } });
package/dist/index.d.ts CHANGED
@@ -1,13 +1,17 @@
1
1
  export { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL, OPENCHAMBER_SDK_MANIFEST_API_VERSIONS } from './api-version.ts';
2
- export { GUEST_SCROLLBAR_CSS } from './scrollbar-style.ts';
2
+ export { GUEST_SCROLLBAR_CSS, GUEST_SCROLLBAR_SCRIPT } from './scrollbar-style.ts';
3
3
  export type { GuestLoadState, GuestProject, GuestWorktree, GuestSessionActivity, GuestSessionRecord, GuestDirectoryCoverage, GuestProjectsSnapshot, GuestWorktreesSnapshot, GuestSessionsSnapshot, GuestWorkspaceSnapshot, GuestWorkspaceQuery, GuestWorkspaceSubscription, GuestWorkspaceUpdate, GuestStorageRequest, GuestStorageResult, GuestSessionWorktree } from './workspace.ts';
4
4
  export { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_KEYS_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_TOTAL_BYTES } from './workspace.ts';
5
5
  export type { OpenChamberManifestApiVersion } from './api-version.ts';
6
6
  export { compareOpenChamberVersions, hostMeetsOpenChamberEngine, openChamberEngineMinimum, OPENCHAMBER_ENGINE_PATTERN, parseOpenChamberVersion, } from './host-version.ts';
7
- export { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_CAPABILITIES, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, GUEST_TOOL_TEMPLATE_VALUE_MAX, guestActionsNeedConversation, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_FILESYSTEM_PATTERNS_MAX, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, resolveAttachMode, resolveIntegrationApi, resolveIntegrationAuth, toPublicService, toPublicIntegration, } from './manifest.ts';
8
- export type { ServiceContribution, ServicePermissions, GuestActionContribution, GuestActionPayload, GuestActionRole, GuestActionWhere, GuestCommandContribution, GuestToolContribution, GuestToolOutput, AttachContribution, AttachContributionObject, AttachMode, DeclaredGuestCapability, GuestAuthorization, GuestCapability, IntegrationAuth, IntegrationContribution, IntegrationHost, IntegrationHostProvider, IntegrationOAuth, IntegrationOAuthAccount, IntegrationSettingField, IntegrationToken, IntegrationTokenScheme, OpenChamberContributes, OpenChamberEngines, OpenChamberManifest, PanelContribution, BackgroundContribution, PageContribution, ParseManifestErrorCode, ParseManifestFailure, ParseManifestResult, ParseManifestSuccess, PublicService, PublicServicePermissions, PublicGuestCapabilities, PublicIntegration, PublicIntegrationToken, PublicSocketBinding, ResolvedGuestApi, SocketBinding, SocketPlatform, } from './manifest.ts';
7
+ export { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_CAPABILITIES, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, GUEST_TOOL_TEMPLATE_VALUE_MAX, guestActionsNeedConversation, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_SERVICE_PROVIDES, GUEST_SURFACE_DOCKS, GUEST_SURFACE_DOCK_DEFAULT, GUEST_SURFACE_DOCK_SIZE_DEFAULT, GUEST_SURFACE_DOCK_SIZE_MAX, GUEST_SURFACE_DOCK_SIZE_MIN, GUEST_STATUS_SECTION_HEIGHT_DEFAULT, GUEST_STATUS_SECTION_HEIGHT_MAX, GUEST_STATUS_SECTION_HEIGHT_MIN, GUEST_STATUS_SECTION_TITLE_MAX, clampStatusSectionHeight, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, resolveStatusSectionEntry, resolveAttachMode, resolveIntegrationApi, resolveIntegrationAuth, serviceProvides, toPublicService, toPublicIntegration, } from './manifest.ts';
8
+ export type { GuestServiceProvides, GuestSurfaceDock, ServiceContribution, ServicePermissions, GuestActionContribution, GuestActionPayload, GuestActionRole, GuestActionWhere, GuestCommandContribution, GuestToolContribution, GuestToolOutput, AttachContribution, AttachContributionObject, AttachMode, DeclaredGuestCapability, GuestAuthorization, GuestCapability, IntegrationAuth, IntegrationContribution, IntegrationHost, IntegrationHostProvider, IntegrationOAuth, IntegrationOAuthAccount, IntegrationSettingField, IntegrationToken, IntegrationTokenScheme, OpenChamberContributes, OpenChamberEngines, OpenChamberManifest, PanelContribution, BackgroundContribution, PageContribution, StatusSectionContribution, ParseManifestErrorCode, ParseManifestFailure, ParseManifestResult, ParseManifestSuccess, PublicService, PublicServicePermissions, PublicGuestCapabilities, PublicIntegration, PublicIntegrationToken, PublicSocketBinding, ResolvedGuestApi, SocketBinding, SocketPlatform, } from './manifest.ts';
9
9
  export { connectHost, HostRequestError } from './host.ts';
10
10
  export type { HostClient, HostClientOptions, HostFrame } from './host.ts';
11
- export { clampAttachRequest, clampBadgeCount, clampPromptRequest, clampStartSessionRequest, guestFileScope, isAttachData, isJsonValue, isFileListResult, isFileReadResult, isFileStatResult, isGenerateResult, isFileWriteResult, isGuestFilePath, isGuestAttachItem, isGuestMessageItem, isGuestSessionItem, isServiceStatusResult, isGuestRequestPath, isGuestRequestResult, isHostRequestErrorCode, isPromptResult, isStartSessionResult, readHostMessage, resolveHostRequestErrorCode, } from './contract.ts';
12
- export type { BadgeRequest, ActionResultPayload, GuestActionItem, GuestActionResultMessage, HostActionMessage, GuestBadgeMessage, GuestItem, GuestItemRole, GuestMessageItem, GuestResolveResultMessage, GuestSessionItem, GuestSessionItemMessage, HostResolveMessage, ResolveRequest, ResolveResultPayload, FileListEntry, FileListRequest, FileListResult, FileReadRequest, FileReadResult, FileStatRequest, FileStatResult, FileWriteRequest, FileWriteResult, GenerateRequest, GenerateResult, GuestFileEntryKind, GuestFileListMessage, GuestFileReadMessage, GuestFileScope, GuestFileStatKind, GuestFileStatMessage, GuestGenerateMessage, GuestFileWriteMessage, ServiceStatus, ServiceStatusResult, HostRequestErrorCode, AttachBranches, AttachIssueRequest, AttachThreadKind, JsonValue, HostResultPayload, PromptRequest, PromptResult, SessionLifecycleEvent, SessionLifecyclePhase, StartSessionRequest, StartSessionResult, StartSessionSent, ComposeRequest, GuestServiceRequestMessage, GuestServiceStatusMessage, GuestAttachMessage, GuestClipboardWriteMessage, GuestCloseMessage, GuestComposeMessage, GuestConnection, GuestHelloMessage, GuestMessage, GuestOauthDisconnectMessage, GuestOauthStartMessage, GuestOpenSurfaceMessage, GuestOpenUrlMessage, GuestRequest, GuestRequestMessage, GuestRequestMethod, GuestPromptMessage, GuestSessionLinkMessage, GuestStartSessionMessage, GuestRequestResult, GuestSettings, GuestToastMessage, GuestHostSurface, HostConnectionMessage, HostDirectoryMessage, HostItemMessage, HostMessage, HostReadyContext, HostReadyMessage, HostResultMessage, HostSessionLifecycleMessage, HostSessionMessage, HostSettingsMessage, HostTheme, HostThemeMode, HostThemeTokens, SessionSnapshot, ToastKind, ToastRequest, } from './contract.ts';
13
- export { EMPTY_GUEST_CONNECTION, GUEST_BADGE_MAX, GUEST_ITEM_MESSAGE_TEXT_MAX, GUEST_ITEM_SESSION_MAX, GUEST_RESOLVE_ERROR_MAX, GUEST_FILE_CONTENT_MAX, GUEST_FILE_ENTRY_KINDS, GUEST_FILE_LIST_MAX, GUEST_FILE_PATH_MAX, GUEST_GENERATE_OUTPUT_TOKENS_MAX, GUEST_GENERATE_PROMPT_MAX, GUEST_GENERATE_SYSTEM_MAX, GUEST_GENERATE_TEXT_MAX, GUEST_GENERATE_TIMEOUT_MS, GUEST_FILE_STAT_KINDS, SERVICE_STATUS_VALUES, HOST_REQUEST_ERROR_CODES, SESSION_LIFECYCLE_PHASES, START_SESSION_SENT, GUEST_ACCOUNT_MAX, GUEST_SESSION_AGENT_MAX, GUEST_SESSION_MODEL_MAX, GUEST_ATTACH_AUTHOR_MAX, GUEST_ATTACH_BRANCH_MAX, GUEST_ATTACH_DATA_MAX, GUEST_ATTACH_ID_MAX, GUEST_ATTACH_TEXT_MAX, GUEST_ATTACH_TITLE_MAX, GUEST_ATTACH_URL_MAX, GUEST_CLIPBOARD_TEXT_MAX, GUEST_TOAST_MAX, GUEST_COMPOSE_TEXT_MAX, GUEST_REQUEST_BODY_MAX, GUEST_REQUEST_PATH_MAX, GUEST_REQUEST_RESPONSE_MAX, GUEST_REQUEST_TIMEOUT_MS, GUEST_SETTING_VALUE_MAX, } from './contract.ts';
11
+ export { clampAttachRequest, clampBadgeCount, clampFrameHeight, isGuestCommitSha, GUEST_COMMIT_SHA, clampPromptRequest, clampStartSessionRequest, guestFileScope, isAttachData, isJsonValue, isFileListResult, isFileReadResult, isFileStatResult, isGenerateResult, isFileWriteResult, isGuestFilePath, isGuestAttachItem, isGuestMessageItem, isGuestSessionItem, isServiceStatusResult, isGuestRequestPath, isGuestRequestResult, isHostRequestErrorCode, isPromptResult, isStartSessionResult, readHostMessage, resolveHostRequestErrorCode, } from './contract.ts';
12
+ export type { BadgeRequest, ActionResultPayload, GuestActionItem, GuestActionResultMessage, HostActionMessage, GuestBadgeMessage, GuestResizeMessage, GuestOpenCommitMessage, OpenCommitRequest, ResizeRequest, GuestItem, GuestItemRole, GuestMessageItem, GuestResolveResultMessage, GuestSessionItem, GuestSessionItemMessage, HostResolveMessage, ResolveRequest, ResolveResultPayload, FileListEntry, FileListRequest, FileListResult, FileReadRequest, FileReadResult, FileStatRequest, FileStatResult, FileWriteRequest, FileWriteResult, GenerateRequest, GenerateResult, GuestFileEntryKind, GuestFileListMessage, GuestFileReadMessage, GuestFileScope, GuestFileStatKind, GuestFileStatMessage, GuestGenerateMessage, GuestFileWriteMessage, ServiceStatus, ServiceStatusResult, HostRequestErrorCode, AttachBranches, AttachIssueRequest, AttachThreadKind, JsonValue, HostResultPayload, PromptRequest, PromptResult, SessionLifecycleEvent, SessionLifecyclePhase, StartSessionRequest, StartSessionResult, StartSessionSent, ComposeRequest, GuestServiceRequestMessage, GuestServiceStatusMessage, GuestAttachMessage, GuestClipboardWriteMessage, GuestCloseMessage, GuestComposeMessage, GuestConnection, GuestHelloMessage, GuestMessage, GuestOauthDisconnectMessage, GuestOauthStartMessage, GuestOpenSurfaceMessage, GuestOpenUrlMessage, GuestRequest, GuestRequestMessage, GuestRequestMethod, GuestPromptMessage, GuestSessionLinkMessage, GuestStartSessionMessage, GuestRequestResult, GuestSettings, GuestToastMessage, GuestHostSurface, HostConnectionMessage, HostDirectoryMessage, HostItemMessage, HostMessage, HostReadyContext, HostReadyMessage, HostResultMessage, HostSessionLifecycleMessage, HostSessionMessage, HostSettingsMessage, HostTheme, HostThemeMode, HostThemeTokens, SessionSnapshot, ToastKind, ToastRequest, } from './contract.ts';
13
+ export { EMPTY_GUEST_CONNECTION, GUEST_BADGE_MAX, GUEST_FRAME_HEIGHT_MAX, GUEST_ITEM_MESSAGE_TEXT_MAX, GUEST_ITEM_SESSION_MAX, GUEST_RESOLVE_ERROR_MAX, GUEST_FILE_CONTENT_MAX, GUEST_FILE_ENTRY_KINDS, GUEST_FILE_LIST_MAX, GUEST_FILE_PATH_MAX, GUEST_GENERATE_OUTPUT_TOKENS_MAX, GUEST_GENERATE_PROMPT_MAX, GUEST_GENERATE_SYSTEM_MAX, GUEST_GENERATE_TEXT_MAX, GUEST_GENERATE_TIMEOUT_MS, GUEST_FILE_STAT_KINDS, SERVICE_STATUS_VALUES, HOST_REQUEST_ERROR_CODES, SESSION_LIFECYCLE_PHASES, START_SESSION_SENT, GUEST_ACCOUNT_MAX, GUEST_SESSION_AGENT_MAX, GUEST_SESSION_MODEL_MAX, GUEST_ATTACH_AUTHOR_MAX, GUEST_ATTACH_BRANCH_MAX, GUEST_ATTACH_DATA_MAX, GUEST_ATTACH_ID_MAX, GUEST_ATTACH_TEXT_MAX, GUEST_ATTACH_TITLE_MAX, GUEST_ATTACH_URL_MAX, GUEST_CLIPBOARD_TEXT_MAX, GUEST_TOAST_MAX, GUEST_COMPOSE_TEXT_MAX, GUEST_REQUEST_BODY_MAX, GUEST_REQUEST_PATH_MAX, GUEST_REQUEST_RESPONSE_MAX, GUEST_REQUEST_TIMEOUT_MS, GUEST_SETTING_VALUE_MAX, } from './contract.ts';
14
+ export { BROWSER_CONTROL_ACTIONS, BROWSER_PROVIDER_ACTION_TIMEOUT_MS, BROWSER_PROVIDER_IDLE_MS, BROWSER_PROVIDER_OPEN_TIMEOUT_MS, BROWSER_PROVIDER_PATH, BROWSER_PROVIDER_RESPONSE_MAX, BROWSER_SCROLL_DIRECTIONS, BROWSER_VIEWPORT_MODES, isBrowserControlAction, readBrowserProviderRequest, } from './service-providers.ts';
15
+ export type { BrowserCaptureData, BrowserCaptureParameters, BrowserClickData, BrowserClickParameters, BrowserConsoleProblem, BrowserControlAction, BrowserElementBounds, BrowserInspectData, BrowserInspectParameters, BrowserNavigationData, BrowserOpenData, BrowserOpenParameters, BrowserProviderContext, BrowserProviderData, BrowserProviderRequest, BrowserProviderResult, BrowserResizeData, BrowserResizeParameters, BrowserScrollData, BrowserScrollDirection, BrowserScrollParameters, BrowserSnapshotData, BrowserSnapshotElement, BrowserSnapshotParameters, BrowserTab, BrowserTabTarget, BrowserTypeData, BrowserTypeParameters, BrowserViewportMode, BrowserViewportSummary, } from './service-providers.ts';
16
+ export { SURFACE_AGENT_ACTIVE_HEADER, SURFACE_AGENT_HOLD_MS, SURFACE_CLIPBOARD_PATH, SURFACE_CONTROL_PATH, SURFACE_CONTROLLERS, SURFACE_DIMENSION_MAX, SURFACE_FRAME_MAX_BYTES, SURFACE_FRAME_MIMES, SURFACE_FRAME_PATH, SURFACE_FRAME_SEQ_HEADER, SURFACE_FRAME_WAIT_MS, SURFACE_HEIGHT_HEADER, SURFACE_INPUT_BATCH_MAX, SURFACE_INPUT_PATH, SURFACE_RESIZE_PATH, SURFACE_SEQ_HEADER, SURFACE_TEXT_MAX, SURFACE_TITLE_HEADER, SURFACE_TITLE_MAX, SURFACE_VIEWER_CONTROLS_HEADER, SURFACE_VIEWER_HEADER, SURFACE_WIDTH_HEADER, readSurfaceControlNotice, readSurfaceInputBatch, readSurfaceResizeRequest, } from './service-surface.ts';
17
+ export type { SurfaceClipboardAnswer, SurfaceControlNotice, SurfaceController, SurfaceFrameMime, SurfaceInputBatch, SurfaceInputEvent, SurfaceKeyEvent, SurfaceModifiers, SurfacePointerEvent, SurfaceResizeAnswer, SurfaceResizeRequest, SurfaceTextEvent, SurfaceWheelEvent, } from './service-surface.ts';
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  export { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL, OPENCHAMBER_SDK_MANIFEST_API_VERSIONS } from "./api-version.js";
2
- export { GUEST_SCROLLBAR_CSS } from "./scrollbar-style.js";
2
+ export { GUEST_SCROLLBAR_CSS, GUEST_SCROLLBAR_SCRIPT } from "./scrollbar-style.js";
3
3
  export { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_KEYS_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_TOTAL_BYTES } from "./workspace.js";
4
4
  export { compareOpenChamberVersions, hostMeetsOpenChamberEngine, openChamberEngineMinimum, OPENCHAMBER_ENGINE_PATTERN, parseOpenChamberVersion, } from "./host-version.js";
5
- export { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_CAPABILITIES, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, GUEST_TOOL_TEMPLATE_VALUE_MAX, guestActionsNeedConversation, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_FILESYSTEM_PATTERNS_MAX, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, resolveAttachMode, resolveIntegrationApi, resolveIntegrationAuth, toPublicService, toPublicIntegration, } from "./manifest.js";
5
+ export { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_CAPABILITIES, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, GUEST_TOOL_TEMPLATE_VALUE_MAX, guestActionsNeedConversation, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_SERVICE_PROVIDES, GUEST_SURFACE_DOCKS, GUEST_SURFACE_DOCK_DEFAULT, GUEST_SURFACE_DOCK_SIZE_DEFAULT, GUEST_SURFACE_DOCK_SIZE_MAX, GUEST_SURFACE_DOCK_SIZE_MIN, GUEST_STATUS_SECTION_HEIGHT_DEFAULT, GUEST_STATUS_SECTION_HEIGHT_MAX, GUEST_STATUS_SECTION_HEIGHT_MIN, GUEST_STATUS_SECTION_TITLE_MAX, clampStatusSectionHeight, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, resolveStatusSectionEntry, resolveAttachMode, resolveIntegrationApi, resolveIntegrationAuth, serviceProvides, toPublicService, toPublicIntegration, } from "./manifest.js";
6
6
  export { connectHost, HostRequestError } from "./host.js";
7
- export { clampAttachRequest, clampBadgeCount, clampPromptRequest, clampStartSessionRequest, guestFileScope, isAttachData, isJsonValue, isFileListResult, isFileReadResult, isFileStatResult, isGenerateResult, isFileWriteResult, isGuestFilePath, isGuestAttachItem, isGuestMessageItem, isGuestSessionItem, isServiceStatusResult, isGuestRequestPath, isGuestRequestResult, isHostRequestErrorCode, isPromptResult, isStartSessionResult, readHostMessage, resolveHostRequestErrorCode, } from "./contract.js";
8
- export { EMPTY_GUEST_CONNECTION, GUEST_BADGE_MAX, GUEST_ITEM_MESSAGE_TEXT_MAX, GUEST_ITEM_SESSION_MAX, GUEST_RESOLVE_ERROR_MAX, GUEST_FILE_CONTENT_MAX, GUEST_FILE_ENTRY_KINDS, GUEST_FILE_LIST_MAX, GUEST_FILE_PATH_MAX, GUEST_GENERATE_OUTPUT_TOKENS_MAX, GUEST_GENERATE_PROMPT_MAX, GUEST_GENERATE_SYSTEM_MAX, GUEST_GENERATE_TEXT_MAX, GUEST_GENERATE_TIMEOUT_MS, GUEST_FILE_STAT_KINDS, SERVICE_STATUS_VALUES, HOST_REQUEST_ERROR_CODES, SESSION_LIFECYCLE_PHASES, START_SESSION_SENT, GUEST_ACCOUNT_MAX, GUEST_SESSION_AGENT_MAX, GUEST_SESSION_MODEL_MAX, GUEST_ATTACH_AUTHOR_MAX, GUEST_ATTACH_BRANCH_MAX, GUEST_ATTACH_DATA_MAX, GUEST_ATTACH_ID_MAX, GUEST_ATTACH_TEXT_MAX, GUEST_ATTACH_TITLE_MAX, GUEST_ATTACH_URL_MAX, GUEST_CLIPBOARD_TEXT_MAX, GUEST_TOAST_MAX, GUEST_COMPOSE_TEXT_MAX, GUEST_REQUEST_BODY_MAX, GUEST_REQUEST_PATH_MAX, GUEST_REQUEST_RESPONSE_MAX, GUEST_REQUEST_TIMEOUT_MS, GUEST_SETTING_VALUE_MAX, } from "./contract.js";
7
+ export { clampAttachRequest, clampBadgeCount, clampFrameHeight, isGuestCommitSha, GUEST_COMMIT_SHA, clampPromptRequest, clampStartSessionRequest, guestFileScope, isAttachData, isJsonValue, isFileListResult, isFileReadResult, isFileStatResult, isGenerateResult, isFileWriteResult, isGuestFilePath, isGuestAttachItem, isGuestMessageItem, isGuestSessionItem, isServiceStatusResult, isGuestRequestPath, isGuestRequestResult, isHostRequestErrorCode, isPromptResult, isStartSessionResult, readHostMessage, resolveHostRequestErrorCode, } from "./contract.js";
8
+ export { EMPTY_GUEST_CONNECTION, GUEST_BADGE_MAX, GUEST_FRAME_HEIGHT_MAX, GUEST_ITEM_MESSAGE_TEXT_MAX, GUEST_ITEM_SESSION_MAX, GUEST_RESOLVE_ERROR_MAX, GUEST_FILE_CONTENT_MAX, GUEST_FILE_ENTRY_KINDS, GUEST_FILE_LIST_MAX, GUEST_FILE_PATH_MAX, GUEST_GENERATE_OUTPUT_TOKENS_MAX, GUEST_GENERATE_PROMPT_MAX, GUEST_GENERATE_SYSTEM_MAX, GUEST_GENERATE_TEXT_MAX, GUEST_GENERATE_TIMEOUT_MS, GUEST_FILE_STAT_KINDS, SERVICE_STATUS_VALUES, HOST_REQUEST_ERROR_CODES, SESSION_LIFECYCLE_PHASES, START_SESSION_SENT, GUEST_ACCOUNT_MAX, GUEST_SESSION_AGENT_MAX, GUEST_SESSION_MODEL_MAX, GUEST_ATTACH_AUTHOR_MAX, GUEST_ATTACH_BRANCH_MAX, GUEST_ATTACH_DATA_MAX, GUEST_ATTACH_ID_MAX, GUEST_ATTACH_TEXT_MAX, GUEST_ATTACH_TITLE_MAX, GUEST_ATTACH_URL_MAX, GUEST_CLIPBOARD_TEXT_MAX, GUEST_TOAST_MAX, GUEST_COMPOSE_TEXT_MAX, GUEST_REQUEST_BODY_MAX, GUEST_REQUEST_PATH_MAX, GUEST_REQUEST_RESPONSE_MAX, GUEST_REQUEST_TIMEOUT_MS, GUEST_SETTING_VALUE_MAX, } from "./contract.js";
9
+ export { BROWSER_CONTROL_ACTIONS, BROWSER_PROVIDER_ACTION_TIMEOUT_MS, BROWSER_PROVIDER_IDLE_MS, BROWSER_PROVIDER_OPEN_TIMEOUT_MS, BROWSER_PROVIDER_PATH, BROWSER_PROVIDER_RESPONSE_MAX, BROWSER_SCROLL_DIRECTIONS, BROWSER_VIEWPORT_MODES, isBrowserControlAction, readBrowserProviderRequest, } from "./service-providers.js";
10
+ export { SURFACE_AGENT_ACTIVE_HEADER, SURFACE_AGENT_HOLD_MS, SURFACE_CLIPBOARD_PATH, SURFACE_CONTROL_PATH, SURFACE_CONTROLLERS, SURFACE_DIMENSION_MAX, SURFACE_FRAME_MAX_BYTES, SURFACE_FRAME_MIMES, SURFACE_FRAME_PATH, SURFACE_FRAME_SEQ_HEADER, SURFACE_FRAME_WAIT_MS, SURFACE_HEIGHT_HEADER, SURFACE_INPUT_BATCH_MAX, SURFACE_INPUT_PATH, SURFACE_RESIZE_PATH, SURFACE_SEQ_HEADER, SURFACE_TEXT_MAX, SURFACE_TITLE_HEADER, SURFACE_TITLE_MAX, SURFACE_VIEWER_CONTROLS_HEADER, SURFACE_VIEWER_HEADER, SURFACE_WIDTH_HEADER, readSurfaceControlNotice, readSurfaceInputBatch, readSurfaceResizeRequest, } from "./service-surface.js";