@openchamber/sdk 2.0.0 → 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
@@ -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
 
@@ -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
 
package/DOCUMENTATION.md CHANGED
@@ -17,7 +17,7 @@ Public author pages live in `packages/docs/content/docs/sdk.mdx`, `sdk/host.mdx`
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
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.
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`.
21
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`.
22
22
 
23
23
  ## Invariants
@@ -26,6 +26,9 @@ Public author pages live in `packages/docs/content/docs/sdk.mdx`, `sdk/host.mdx`
26
26
 
27
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`.
28
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.
29
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.
30
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.
31
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.
@@ -36,7 +39,7 @@ Public author pages live in `packages/docs/content/docs/sdk.mdx`, `sdk/host.mdx`
36
39
  - Extra manifest keys are dropped, not forwarded.
37
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.
38
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`.
39
- - 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.
40
43
  - `parseManifest` takes a typed document. File or network bytes go through `parseManifestJson`. Both return a result and do not throw on junk.
41
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.
42
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.
@@ -44,7 +47,7 @@ Public author pages live in `packages/docs/content/docs/sdk.mdx`, `sdk/host.mdx`
44
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.
45
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.
46
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.
47
- - `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.
48
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.
49
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).
50
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
@@ -218,6 +218,8 @@ Parameters the host sends and `data` the service answers; types are exported fro
218
218
  | `browser.capture` | `label?` | `base64`, `mime`, `width`, `height`, `url`, `title`, `viewport`; the host writes the file into the project and returns its path |
219
219
  | `browser.resize` | `viewport` (`mobile`/`tablet`/`desktop`/`fill`) | `viewport` |
220
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
+
221
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.
222
224
 
223
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.
@@ -235,8 +237,8 @@ Plain HTTP on the service loopback, same bearer as everything else:
235
237
  | Call | Body / answer |
236
238
  |---|---|
237
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). |
238
- | `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. |
239
- | `POST /surface/control` | `{ controller: "none" \| "agent" \| "user" }` whenever control changes. Parse with `readSurfaceControlNotice`. Advisory. |
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. |
240
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`. |
241
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. |
242
244
 
@@ -244,6 +246,8 @@ Input, control notices, resizes, and clipboard reads reach the service one at a
244
246
 
245
247
  ### Your own controls beside the picture
246
248
 
247
- 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. 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.
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.
248
252
 
249
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
@@ -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,17 +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, 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, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, 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, 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
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, 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_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_WIDTH_HEADER, readSurfaceControlNotice, readSurfaceInputBatch, readSurfaceResizeRequest, } from './service-surface.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
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,10 +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, 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, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, resolveAttachMode, resolveIntegrationApi, resolveIntegrationAuth, serviceProvides, 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
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_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_WIDTH_HEADER, readSurfaceControlNotice, readSurfaceInputBatch, readSurfaceResizeRequest, } from "./service-surface.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";
@@ -48,6 +48,26 @@ export type PageContribution = true | {
48
48
  entry: string;
49
49
  title?: string;
50
50
  };
51
+ /**
52
+ * A section inside the chat's Work Status panel. `true` reuses `panel.entry`;
53
+ * the object form names its own package HTML, so an extension can ship only
54
+ * this section and no rail panel. `title` replaces `panel.name` on the section
55
+ * header; `height` is the starting frame height in CSS px before the guest
56
+ * reports its own through `setHeight`.
57
+ */
58
+ export type StatusSectionContribution = true | {
59
+ entry: string;
60
+ title?: string;
61
+ height?: number;
62
+ };
63
+ /** Characters in a status section title. */
64
+ export declare const GUEST_STATUS_SECTION_TITLE_MAX = 60;
65
+ /** Smallest frame height the host gives a status section, in CSS px. */
66
+ export declare const GUEST_STATUS_SECTION_HEIGHT_MIN = 24;
67
+ /** Tallest frame height the host gives a status section; taller content scrolls inside the frame. */
68
+ export declare const GUEST_STATUS_SECTION_HEIGHT_MAX = 320;
69
+ /** Frame height before the manifest or the guest says otherwise. */
70
+ export declare const GUEST_STATUS_SECTION_HEIGHT_DEFAULT = 120;
51
71
  export type GuestActionWhere = 'message' | 'session';
52
72
  export type GuestActionRole = 'user' | 'assistant';
53
73
  /** What a session action wants alongside the session id and title. */
@@ -276,6 +296,8 @@ export type OpenChamberContributes = {
276
296
  background?: BackgroundContribution;
277
297
  attach?: AttachContribution;
278
298
  page?: PageContribution;
299
+ /** A section in the chat's Work Status panel. */
300
+ statusSection?: StatusSectionContribution;
279
301
  capabilities?: DeclaredGuestCapability[];
280
302
  integration?: IntegrationContribution;
281
303
  service?: ServiceContribution;
@@ -310,6 +332,14 @@ export declare const resolveAttachMode: (attach: AttachContribution | undefined)
310
332
  */
311
333
  export declare const resolveAttachEntry: (contributes: Pick<OpenChamberContributes, "attach">) => string | null;
312
334
  export declare const resolvePageEntry: (contributes: Pick<OpenChamberContributes, "panel" | "page">) => string | null;
335
+ /**
336
+ * The HTML the Work Status section loads: `panel.entry` for `true`, the
337
+ * object's own `entry` otherwise, `null` when nothing is declared or `true`
338
+ * has no panel page to reuse.
339
+ */
340
+ export declare const resolveStatusSectionEntry: (contributes: Pick<OpenChamberContributes, "panel" | "statusSection">) => string | null;
341
+ /** Clamp a requested status section height to what the host allows. */
342
+ export declare const clampStatusSectionHeight: (height: number) => number;
313
343
  export type OpenChamberEngines = {
314
344
  openchamber: string;
315
345
  };
@@ -318,7 +348,7 @@ export type OpenChamberManifest = {
318
348
  engines?: OpenChamberEngines;
319
349
  contributes: OpenChamberContributes;
320
350
  };
321
- export type ParseManifestErrorCode = 'not-object' | 'missing-openchamber' | 'unsupported-api-version' | 'invalid-engines' | 'invalid-version' | 'missing-panel' | 'invalid-panel' | 'invalid-panel-id' | 'invalid-panel-name' | 'invalid-panel-icon' | 'invalid-panel-entry' | 'invalid-background' | 'invalid-attach' | 'invalid-page' | 'invalid-capabilities' | 'invalid-integration' | 'invalid-service' | 'invalid-filesystem' | 'invalid-actions' | 'invalid-commands' | 'invalid-tools';
351
+ export type ParseManifestErrorCode = 'not-object' | 'missing-openchamber' | 'unsupported-api-version' | 'invalid-engines' | 'invalid-version' | 'missing-panel' | 'invalid-panel' | 'invalid-panel-id' | 'invalid-panel-name' | 'invalid-panel-icon' | 'invalid-panel-entry' | 'invalid-background' | 'invalid-attach' | 'invalid-page' | 'invalid-status-section' | 'invalid-capabilities' | 'invalid-integration' | 'invalid-service' | 'invalid-filesystem' | 'invalid-actions' | 'invalid-commands' | 'invalid-tools';
322
352
  export type ParseManifestFailure = {
323
353
  ok: false;
324
354
  code: ParseManifestErrorCode;
package/dist/manifest.js CHANGED
@@ -5,6 +5,14 @@ export const GUEST_SURFACE_DOCK_DEFAULT = 'top';
5
5
  export const GUEST_SURFACE_DOCK_SIZE_DEFAULT = 40;
6
6
  export const GUEST_SURFACE_DOCK_SIZE_MIN = 24;
7
7
  export const GUEST_SURFACE_DOCK_SIZE_MAX = 480;
8
+ /** Characters in a status section title. */
9
+ export const GUEST_STATUS_SECTION_TITLE_MAX = 60;
10
+ /** Smallest frame height the host gives a status section, in CSS px. */
11
+ export const GUEST_STATUS_SECTION_HEIGHT_MIN = 24;
12
+ /** Tallest frame height the host gives a status section; taller content scrolls inside the frame. */
13
+ export const GUEST_STATUS_SECTION_HEIGHT_MAX = 320;
14
+ /** Frame height before the manifest or the guest says otherwise. */
15
+ export const GUEST_STATUS_SECTION_HEIGHT_DEFAULT = 120;
8
16
  /** How many `contributes.actions` entries a package may declare. */
9
17
  export const GUEST_ACTIONS_MAX = 8;
10
18
  /** Characters in an action label. */
@@ -166,6 +174,23 @@ export const resolvePageEntry = (contributes) => {
166
174
  return null;
167
175
  return contributes.page === true ? contributes.panel.entry : contributes.page.entry;
168
176
  };
177
+ /**
178
+ * The HTML the Work Status section loads: `panel.entry` for `true`, the
179
+ * object's own `entry` otherwise, `null` when nothing is declared or `true`
180
+ * has no panel page to reuse.
181
+ */
182
+ export const resolveStatusSectionEntry = (contributes) => {
183
+ const section = contributes.statusSection;
184
+ if (!section)
185
+ return null;
186
+ return section === true ? contributes.panel.entry ?? null : section.entry;
187
+ };
188
+ /** Clamp a requested status section height to what the host allows. */
189
+ export const clampStatusSectionHeight = (height) => {
190
+ if (!Number.isFinite(height))
191
+ return GUEST_STATUS_SECTION_HEIGHT_DEFAULT;
192
+ return Math.min(GUEST_STATUS_SECTION_HEIGHT_MAX, Math.max(GUEST_STATUS_SECTION_HEIGHT_MIN, Math.round(height)));
193
+ };
169
194
  export const isSafeAssetPath = (value) => {
170
195
  if (value.includes('\0') || value.includes('\\') || value.startsWith('/') || value.includes('://')) {
171
196
  return false;
package/dist/parse.d.ts CHANGED
@@ -36,6 +36,11 @@ export declare const openChamberManifestSchema: z.ZodObject<{
36
36
  entry: z.ZodString;
37
37
  title: z.ZodOptional<z.ZodString>;
38
38
  }, z.core.$strip>]>>;
39
+ statusSection: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodObject<{
40
+ entry: z.ZodString;
41
+ title: z.ZodOptional<z.ZodString>;
42
+ height: z.ZodOptional<z.ZodNumber>;
43
+ }, z.core.$strip>]>>;
39
44
  capabilities: z.ZodOptional<z.ZodArray<z.ZodEnum<{
40
45
  sessions: "sessions";
41
46
  prompt: "prompt";
@@ -192,6 +197,11 @@ export declare const packageManifestSchema: z.ZodObject<{
192
197
  entry: z.ZodString;
193
198
  title: z.ZodOptional<z.ZodString>;
194
199
  }, z.core.$strip>]>>;
200
+ statusSection: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodObject<{
201
+ entry: z.ZodString;
202
+ title: z.ZodOptional<z.ZodString>;
203
+ height: z.ZodOptional<z.ZodNumber>;
204
+ }, z.core.$strip>]>>;
195
205
  capabilities: z.ZodOptional<z.ZodArray<z.ZodEnum<{
196
206
  sessions: "sessions";
197
207
  prompt: "prompt";
package/dist/parse.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { OPENCHAMBER_SDK_MANIFEST_API_VERSIONS } from "./api-version.js";
3
3
  import { OPENCHAMBER_ENGINE_PATTERN } from "./host-version.js";
4
- import { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_SERVICE_PROVIDES, 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, PANEL_ID, hasGuestPage, isGuestFilesystemPattern, isGuestPackageSvgIcon, isSafeAssetPath, resolveIntegrationAuth, GUEST_SURFACE_DOCKS, GUEST_SURFACE_DOCK_SIZE_MAX, GUEST_SURFACE_DOCK_SIZE_MIN, } from "./manifest.js";
4
+ import { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_SERVICE_PROVIDES, 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, PANEL_ID, hasGuestPage, isGuestFilesystemPattern, isGuestPackageSvgIcon, isSafeAssetPath, resolveIntegrationAuth, GUEST_SURFACE_DOCKS, GUEST_SURFACE_DOCK_SIZE_MAX, GUEST_SURFACE_DOCK_SIZE_MIN, GUEST_STATUS_SECTION_HEIGHT_MAX, GUEST_STATUS_SECTION_HEIGHT_MIN, GUEST_STATUS_SECTION_TITLE_MAX, } from "./manifest.js";
5
5
  const isPanelIcon = (value) => (PANEL_ID.test(value) || isGuestPackageSvgIcon(value));
6
6
  const isHttpsUrl = (value) => {
7
7
  try {
@@ -193,6 +193,11 @@ const contributesSchema = z.object({
193
193
  entry: z.string().trim().refine(isSafeAssetPath),
194
194
  title: z.string().trim().min(1).max(200).optional(),
195
195
  })]).optional(),
196
+ statusSection: z.union([z.literal(true), z.object({
197
+ entry: z.string().trim().refine((value) => isSafeAssetPath(value) && value.toLowerCase().endsWith('.html')),
198
+ title: z.string().trim().min(1).max(GUEST_STATUS_SECTION_TITLE_MAX).optional(),
199
+ height: z.number().int().min(GUEST_STATUS_SECTION_HEIGHT_MIN).max(GUEST_STATUS_SECTION_HEIGHT_MAX).optional(),
200
+ })]).optional(),
196
201
  capabilities: z.array(z.enum(DECLARED_GUEST_CAPABILITIES)).max(8).optional(),
197
202
  integration: integrationSchema.optional(),
198
203
  service: serviceSchema.optional(),
@@ -242,6 +247,14 @@ export const openChamberManifestSchema = z.object({
242
247
  });
243
248
  return;
244
249
  }
250
+ // `true` reuses the panel page; without one the section has nothing to load.
251
+ if (contributes.statusSection === true && !hasGuestPage(contributes)) {
252
+ ctx.addIssue({
253
+ code: 'custom', path: ['statusSection'],
254
+ message: 'contributes.statusSection: true reuses panel.entry; without a panel page, name its own { "entry": "<package .html>" }.',
255
+ });
256
+ return;
257
+ }
245
258
  if (hasGuestPage(contributes))
246
259
  return;
247
260
  if (contributes.background) {
@@ -259,6 +272,26 @@ export const openChamberManifestSchema = z.object({
259
272
  });
260
273
  return;
261
274
  }
275
+ if (contributes.statusSection) {
276
+ // The section frame runs code and may use what the package is granted,
277
+ // but it only hosts itself: things the host opens or invokes elsewhere
278
+ // need a panel or background entry.
279
+ const needsFrame = [];
280
+ if (contributes.page !== undefined)
281
+ needsFrame.push('page');
282
+ if (contributes.attach !== undefined && contributes.attach !== false)
283
+ needsFrame.push('attach');
284
+ if (contributes.actions !== undefined)
285
+ needsFrame.push('actions');
286
+ if (contributes.commands !== undefined)
287
+ needsFrame.push('commands');
288
+ if (needsFrame.length > 0)
289
+ ctx.addIssue({
290
+ code: 'custom', path: ['panel'],
291
+ message: `${needsFrame.map((key) => `contributes.${key}`).join(', ')} needs panel.entry or background.entry; a status section only hosts itself.`,
292
+ });
293
+ return;
294
+ }
262
295
  const needsRuntime = runtimeContributions(contributes);
263
296
  if (needsRuntime.length === 0)
264
297
  return;
@@ -333,6 +366,11 @@ const failureFromIssue = (issue) => {
333
366
  if (path === 'contributes.page' || path.startsWith('contributes.page.')) {
334
367
  return fail('invalid-page', 'contributes.page must be true or { entry: "<package HTML>", title?: "Page title" }.');
335
368
  }
369
+ if (path === 'contributes.statusSection' || path.startsWith('contributes.statusSection.')) {
370
+ return fail('invalid-status-section', issue.code === 'custom'
371
+ ? issue.message
372
+ : `contributes.statusSection must be true or { entry: "<package .html>", title?: "1 to ${GUEST_STATUS_SECTION_TITLE_MAX} characters", height?: ${GUEST_STATUS_SECTION_HEIGHT_MIN} to ${GUEST_STATUS_SECTION_HEIGHT_MAX} }.`);
373
+ }
336
374
  if (path === 'contributes.background' || path.startsWith('contributes.background.')) {
337
375
  return fail('invalid-background', 'contributes.background needs an entry ending in .html inside the package.');
338
376
  }
@@ -144,6 +144,7 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
144
144
  dialog: "dialog";
145
145
  page: "page";
146
146
  background: "background";
147
+ status: "status";
147
148
  }>;
148
149
  connection: z.ZodObject<{
149
150
  connected: z.ZodBoolean;
@@ -633,7 +634,7 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
633
634
  id: string;
634
635
  ok: false;
635
636
  error: string;
636
- code: "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";
637
+ code: "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";
637
638
  payload?: undefined;
638
639
  }, {
639
640
  type: "result";
@@ -1114,6 +1115,22 @@ export declare const guestMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1114
1115
  }, z.core.$strip>;
1115
1116
  channel: z.ZodLiteral<"openchamber.sdk">;
1116
1117
  v: z.ZodLiteral<1>;
1118
+ }, z.core.$strip>, z.ZodObject<{
1119
+ type: z.ZodLiteral<"open-commit">;
1120
+ id: z.ZodString;
1121
+ payload: z.ZodObject<{
1122
+ sha: z.ZodString;
1123
+ }, z.core.$strip>;
1124
+ channel: z.ZodLiteral<"openchamber.sdk">;
1125
+ v: z.ZodLiteral<1>;
1126
+ }, z.core.$strip>, z.ZodObject<{
1127
+ type: z.ZodLiteral<"resize">;
1128
+ id: z.ZodString;
1129
+ payload: z.ZodObject<{
1130
+ height: z.ZodNumber;
1131
+ }, z.core.$strip>;
1132
+ channel: z.ZodLiteral<"openchamber.sdk">;
1133
+ v: z.ZodLiteral<1>;
1117
1134
  }, z.core.$strip>, z.ZodObject<{
1118
1135
  type: z.ZodLiteral<"resolve-result">;
1119
1136
  id: z.ZodString;
package/dist/protocol.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { guestSessionWorktreeSchema, guestStorageRequestSchema, guestStorageResultSchema, guestWorkspaceQuerySchema, guestWorkspaceSnapshotSchema } from "./workspace-schemas.js";
3
3
  import { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL } from "./api-version.js";
4
- import { SERVICE_STATUS_VALUES, ATTACH_PROVIDER_ID, GUEST_ACCOUNT_MAX, GUEST_ATTACH_AUTHOR_MAX, GUEST_BADGE_MAX, GUEST_ITEM_MESSAGE_TEXT_MAX, GUEST_ITEM_SESSION_MAX, GUEST_RESOLVE_ERROR_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_COMPOSE_TEXT_MAX, GUEST_FILE_CONTENT_MAX, GUEST_FILE_ENTRY_KINDS, GUEST_FILE_LIST_MAX, GUEST_GENERATE_OUTPUT_TOKENS_MAX, GUEST_GENERATE_PROMPT_MAX, GUEST_GENERATE_SYSTEM_MAX, GUEST_GENERATE_TEXT_MAX, GUEST_FILE_PATH_MAX, GUEST_FILE_STAT_KINDS, GUEST_REQUEST_BODY_MAX, GUEST_REQUEST_PATH_MAX, GUEST_REQUEST_RESPONSE_MAX, GUEST_SESSION_AGENT_MAX, GUEST_SESSION_MODEL_MAX, GUEST_SETTING_VALUE_MAX, GUEST_TOAST_MAX, SESSION_LIFECYCLE_PHASES, SETTING_KEY, START_SESSION_SENT, isGuestFilePath, isGuestRequestPath, resolveHostRequestErrorCode, } from "./contract.js";
4
+ import { SERVICE_STATUS_VALUES, ATTACH_PROVIDER_ID, GUEST_ACCOUNT_MAX, GUEST_ATTACH_AUTHOR_MAX, GUEST_BADGE_MAX, GUEST_FRAME_HEIGHT_MAX, GUEST_COMMIT_SHA, GUEST_ITEM_MESSAGE_TEXT_MAX, GUEST_ITEM_SESSION_MAX, GUEST_RESOLVE_ERROR_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_COMPOSE_TEXT_MAX, GUEST_FILE_CONTENT_MAX, GUEST_FILE_ENTRY_KINDS, GUEST_FILE_LIST_MAX, GUEST_GENERATE_OUTPUT_TOKENS_MAX, GUEST_GENERATE_PROMPT_MAX, GUEST_GENERATE_SYSTEM_MAX, GUEST_GENERATE_TEXT_MAX, GUEST_FILE_PATH_MAX, GUEST_FILE_STAT_KINDS, GUEST_REQUEST_BODY_MAX, GUEST_REQUEST_PATH_MAX, GUEST_REQUEST_RESPONSE_MAX, GUEST_SESSION_AGENT_MAX, GUEST_SESSION_MODEL_MAX, GUEST_SETTING_VALUE_MAX, GUEST_TOAST_MAX, SESSION_LIFECYCLE_PHASES, SETTING_KEY, START_SESSION_SENT, isGuestFilePath, isGuestRequestPath, resolveHostRequestErrorCode, } from "./contract.js";
5
5
  const envelope = {
6
6
  channel: z.literal(OPENCHAMBER_SDK_CHANNEL),
7
7
  v: z.literal(OPENCHAMBER_SDK_API_VERSION),
@@ -146,7 +146,7 @@ const readyPayloadSchema = z.object({
146
146
  locale: z.string().min(1),
147
147
  directory: z.string().nullable(),
148
148
  session: sessionSnapshotSchema,
149
- surface: z.enum(['panel', 'dialog', 'page', 'background']),
149
+ surface: z.enum(['panel', 'dialog', 'page', 'background', 'status']),
150
150
  connection: guestConnectionSchema,
151
151
  settings: guestSettingsSchema,
152
152
  item: guestItemSchema,
@@ -443,6 +443,22 @@ export const guestMessageSchema = z.discriminatedUnion('type', [
443
443
  count: z.number().int().min(0).max(GUEST_BADGE_MAX).nullable(),
444
444
  }),
445
445
  }),
446
+ z.object({
447
+ ...envelope,
448
+ type: z.literal('open-commit'),
449
+ id: z.string().min(1),
450
+ payload: z.object({
451
+ sha: z.string().regex(GUEST_COMMIT_SHA),
452
+ }),
453
+ }),
454
+ z.object({
455
+ ...envelope,
456
+ type: z.literal('resize'),
457
+ id: z.string().min(1),
458
+ payload: z.object({
459
+ height: z.number().int().min(0).max(GUEST_FRAME_HEIGHT_MAX),
460
+ }),
461
+ }),
446
462
  z.object({
447
463
  ...envelope,
448
464
  type: z.literal('resolve-result'),
@@ -1,2 +1,10 @@
1
- /** Shared by served extension documents and the UI kit, including older guest bundles. */
2
- export declare const GUEST_SCROLLBAR_CSS = "\n:root {\n --oc-scrollbar-thumb: color-mix(in srgb, var(--oc-muted, currentColor) 40%, transparent);\n --oc-scrollbar-thumb-hover: color-mix(in srgb, var(--oc-muted, currentColor) 65%, transparent);\n scrollbar-gutter: stable;\n}\n* {\n scrollbar-width: thin;\n scrollbar-color: var(--oc-scrollbar-thumb) transparent;\n}\n/* Chromium's standard scrollbar properties otherwise override its pseudo-elements. */\n@supports selector(::-webkit-scrollbar) {\n * { scrollbar-width: auto; scrollbar-color: auto; }\n ::-webkit-scrollbar { width: 6px; height: 6px; background: transparent; }\n :root::-webkit-scrollbar, body::-webkit-scrollbar { background: var(--oc-bg, inherit); }\n ::-webkit-scrollbar-track { background: transparent; }\n ::-webkit-scrollbar-thumb {\n background: var(--oc-scrollbar-thumb);\n border-radius: 999px;\n min-width: 24px;\n min-height: 24px;\n }\n ::-webkit-scrollbar-thumb:hover { background: var(--oc-scrollbar-thumb-hover); }\n ::-webkit-scrollbar-corner { background: transparent; }\n ::-webkit-scrollbar-button { display: none; width: 0; height: 0; }\n}\n@media (forced-colors: active) {\n * { scrollbar-color: auto; }\n ::-webkit-scrollbar-thumb, ::-webkit-scrollbar-thumb:hover { background: CanvasText; }\n}\n";
1
+ export declare const GUEST_SCROLLBAR_CSS = "\n:root {\n --oc-scrollbar-thumb: color-mix(in srgb, var(--oc-muted, currentColor) 40%, transparent);\n --oc-scrollbar-thumb-hover: color-mix(in srgb, var(--oc-muted, currentColor) 65%, transparent);\n scrollbar-gutter: stable;\n}\n* {\n scrollbar-width: thin;\n scrollbar-color: transparent transparent;\n}\n:hover, [data-oc-scrolling] {\n scrollbar-color: var(--oc-scrollbar-thumb) transparent;\n}\n/* Chromium's standard scrollbar properties otherwise override its pseudo-elements. */\n@supports selector(::-webkit-scrollbar) {\n *, :hover, [data-oc-scrolling] { scrollbar-width: auto; scrollbar-color: auto; }\n ::-webkit-scrollbar { width: 6px; height: 6px; background: transparent; }\n ::-webkit-scrollbar-track { background: transparent; }\n ::-webkit-scrollbar-thumb {\n background: transparent;\n border-radius: 999px;\n min-width: 24px;\n min-height: 24px;\n }\n :hover::-webkit-scrollbar-thumb, [data-oc-scrolling]::-webkit-scrollbar-thumb { background: var(--oc-scrollbar-thumb); }\n ::-webkit-scrollbar-thumb:hover { background: var(--oc-scrollbar-thumb-hover); }\n ::-webkit-scrollbar-corner { background: transparent; }\n ::-webkit-scrollbar-button { display: none; width: 0; height: 0; }\n}\n@media (forced-colors: active) {\n *, :hover, [data-oc-scrolling] { scrollbar-color: auto; }\n ::-webkit-scrollbar-thumb, ::-webkit-scrollbar-thumb:hover { background: CanvasText; }\n}\n";
2
+ /**
3
+ * Marks whichever element scrolls (the root for document scrolling) with
4
+ * `GUEST_SCROLLING_ATTRIBUTE` and clears it once scrolling has been quiet for
5
+ * the hide delay. One capturing, passive listener per document; installing
6
+ * twice is a no-op. Self-contained on purpose: the server inlines its source.
7
+ */
8
+ export declare function installGuestScrollbarActivity(doc: Document): void;
9
+ /** Inline script form of `installGuestScrollbarActivity`, appended by the host to served guest HTML. */
10
+ export declare const GUEST_SCROLLBAR_SCRIPT: string;
@@ -1,4 +1,17 @@
1
- /** Shared by served extension documents and the UI kit, including older guest bundles. */
1
+ /**
2
+ * Scrollbars in extension documents behave like the app's own overlay
3
+ * scrollbars: invisible at rest, shown while the pointer is over the
4
+ * scrolling element or while it scrolls, then hidden again after
5
+ * one second (the app's `OverlayScrollbar` default).
6
+ *
7
+ * Hover works from CSS alone. "While it scrolls" needs the attribute set by
8
+ * `installGuestScrollbarActivity`; without it (an authored CSP that blocks the
9
+ * inline script), scrollbars still appear on hover.
10
+ *
11
+ * Shared by served extension documents and the UI kit, including older guest bundles.
12
+ */
13
+ /** Attribute present on an element for a moment after it scrolled. */
14
+ const GUEST_SCROLLING_ATTRIBUTE = 'data-oc-scrolling';
2
15
  export const GUEST_SCROLLBAR_CSS = `
3
16
  :root {
4
17
  --oc-scrollbar-thumb: color-mix(in srgb, var(--oc-muted, currentColor) 40%, transparent);
@@ -7,26 +20,58 @@ export const GUEST_SCROLLBAR_CSS = `
7
20
  }
8
21
  * {
9
22
  scrollbar-width: thin;
23
+ scrollbar-color: transparent transparent;
24
+ }
25
+ :hover, [${GUEST_SCROLLING_ATTRIBUTE}] {
10
26
  scrollbar-color: var(--oc-scrollbar-thumb) transparent;
11
27
  }
12
28
  /* Chromium's standard scrollbar properties otherwise override its pseudo-elements. */
13
29
  @supports selector(::-webkit-scrollbar) {
14
- * { scrollbar-width: auto; scrollbar-color: auto; }
30
+ *, :hover, [${GUEST_SCROLLING_ATTRIBUTE}] { scrollbar-width: auto; scrollbar-color: auto; }
15
31
  ::-webkit-scrollbar { width: 6px; height: 6px; background: transparent; }
16
- :root::-webkit-scrollbar, body::-webkit-scrollbar { background: var(--oc-bg, inherit); }
17
32
  ::-webkit-scrollbar-track { background: transparent; }
18
33
  ::-webkit-scrollbar-thumb {
19
- background: var(--oc-scrollbar-thumb);
34
+ background: transparent;
20
35
  border-radius: 999px;
21
36
  min-width: 24px;
22
37
  min-height: 24px;
23
38
  }
39
+ :hover::-webkit-scrollbar-thumb, [${GUEST_SCROLLING_ATTRIBUTE}]::-webkit-scrollbar-thumb { background: var(--oc-scrollbar-thumb); }
24
40
  ::-webkit-scrollbar-thumb:hover { background: var(--oc-scrollbar-thumb-hover); }
25
41
  ::-webkit-scrollbar-corner { background: transparent; }
26
42
  ::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
27
43
  }
28
44
  @media (forced-colors: active) {
29
- * { scrollbar-color: auto; }
45
+ *, :hover, [${GUEST_SCROLLING_ATTRIBUTE}] { scrollbar-color: auto; }
30
46
  ::-webkit-scrollbar-thumb, ::-webkit-scrollbar-thumb:hover { background: CanvasText; }
31
47
  }
32
48
  `;
49
+ /**
50
+ * Marks whichever element scrolls (the root for document scrolling) with
51
+ * `GUEST_SCROLLING_ATTRIBUTE` and clears it once scrolling has been quiet for
52
+ * the hide delay. One capturing, passive listener per document; installing
53
+ * twice is a no-op. Self-contained on purpose: the server inlines its source.
54
+ */
55
+ export function installGuestScrollbarActivity(doc) {
56
+ const root = doc.documentElement;
57
+ if (root.hasAttribute('data-oc-scrollbar-activity'))
58
+ return;
59
+ root.setAttribute('data-oc-scrollbar-activity', '');
60
+ const timers = new WeakMap();
61
+ doc.addEventListener('scroll', (event) => {
62
+ const target = event.target === doc ? root : event.target;
63
+ if (!(target instanceof Element))
64
+ return;
65
+ if (!target.hasAttribute('data-oc-scrolling'))
66
+ target.setAttribute('data-oc-scrolling', '');
67
+ const pending = timers.get(target);
68
+ if (pending !== undefined)
69
+ clearTimeout(pending);
70
+ timers.set(target, setTimeout(() => {
71
+ timers.delete(target);
72
+ target.removeAttribute('data-oc-scrolling');
73
+ }, 1000));
74
+ }, { capture: true, passive: true });
75
+ }
76
+ /** Inline script form of `installGuestScrollbarActivity`, appended by the host to served guest HTML. */
77
+ export const GUEST_SCROLLBAR_SCRIPT = `(${installGuestScrollbarActivity.toString()})(document);`;
@@ -23,33 +23,42 @@ export declare const BROWSER_PROVIDER_ACTION_TIMEOUT_MS = 20000;
23
23
  export declare const BROWSER_PROVIDER_RESPONSE_MAX = 12000000;
24
24
  /** No action for this long stops the service; the next action starts it again. */
25
25
  export declare const BROWSER_PROVIDER_IDLE_MS: number;
26
- export type BrowserOpenParameters = {
26
+ /**
27
+ * Which tab an action is for: an `id` from `BrowserSnapshotData.tabs`, passed
28
+ * through from the agent untouched. Absent means the tab the user is looking
29
+ * at. A provider answers an id it did not issue with `ok: false`, never by
30
+ * acting on another tab; one without tabs issues none and refuses every id.
31
+ */
32
+ export type BrowserTabTarget = {
33
+ tabId?: string;
34
+ };
35
+ export type BrowserOpenParameters = BrowserTabTarget & {
27
36
  url: string;
28
37
  viewport?: BrowserViewportMode;
29
38
  };
30
- export type BrowserSnapshotParameters = {
39
+ export type BrowserSnapshotParameters = BrowserTabTarget & {
31
40
  selector?: string;
32
41
  };
33
- export type BrowserClickParameters = {
42
+ export type BrowserClickParameters = BrowserTabTarget & {
34
43
  selector?: string;
35
44
  text?: string;
36
45
  };
37
- export type BrowserTypeParameters = {
46
+ export type BrowserTypeParameters = BrowserTabTarget & {
38
47
  selector: string;
39
48
  value: string;
40
49
  submit: boolean;
41
50
  };
42
- export type BrowserScrollParameters = {
51
+ export type BrowserScrollParameters = BrowserTabTarget & {
43
52
  selector?: string;
44
53
  direction?: BrowserScrollDirection;
45
54
  };
46
- export type BrowserInspectParameters = {
55
+ export type BrowserInspectParameters = BrowserTabTarget & {
47
56
  selector: string;
48
57
  };
49
- export type BrowserCaptureParameters = {
58
+ export type BrowserCaptureParameters = BrowserTabTarget & {
50
59
  label?: string;
51
60
  };
52
- export type BrowserResizeParameters = {
61
+ export type BrowserResizeParameters = BrowserTabTarget & {
53
62
  viewport: BrowserViewportMode;
54
63
  };
55
64
  /**
@@ -86,10 +95,10 @@ export type BrowserProviderRequest = ProviderRequestEnvelope & ({
86
95
  parameters: BrowserScrollParameters;
87
96
  } | {
88
97
  action: 'browser.back';
89
- parameters: Record<never, never>;
98
+ parameters: BrowserTabTarget;
90
99
  } | {
91
100
  action: 'browser.forward';
92
- parameters: Record<never, never>;
101
+ parameters: BrowserTabTarget;
93
102
  } | {
94
103
  action: 'browser.inspect';
95
104
  parameters: BrowserInspectParameters;
@@ -124,6 +133,8 @@ export type BrowserSnapshotElement = {
124
133
  missingAccessibleName?: true;
125
134
  };
126
135
  export type BrowserOpenData = {
136
+ /** The tab the page opened in, when the provider has tabs; the agent names it in later actions. */
137
+ tabId?: string;
127
138
  url: string;
128
139
  title: string;
129
140
  opened: true;
@@ -146,6 +157,14 @@ export type BrowserSnapshotData = {
146
157
  viewport: BrowserViewportSummary;
147
158
  /** Warnings and errors the page logged since it was opened; absent when none. */
148
159
  consoleProblems?: BrowserConsoleProblem[];
160
+ /** Every open tab, so the agent can name one with `tabId`; `active` is the one the user sees. */
161
+ tabs?: BrowserTab[];
162
+ };
163
+ export type BrowserTab = {
164
+ id: string;
165
+ title: string;
166
+ url: string;
167
+ active: boolean;
149
168
  };
150
169
  export type BrowserConsoleProblem = {
151
170
  level: 'warning' | 'error';
@@ -15,9 +15,19 @@
15
15
  * is "the current picture, now". The host asks for one frame at a time per
16
16
  * viewer, so a slow viewer never piles up frames: that is the backpressure.
17
17
  * - `POST /surface/input`: `{ events: SurfaceInputEvent[] }`, coordinates in
18
- * frame pixels. Sent only while the user holds control.
19
- * - `POST /surface/control`: `{ controller }` whenever control changes, so the
20
- * service can pause its own automation while the user is in.
18
+ * frame pixels. Sent only while the user holds control. Headers name the
19
+ * viewer (`x-surface-viewer`) and the frame it last drew
20
+ * (`x-surface-frame-seq`, the service's own sequence number, `0` before the
21
+ * first). Answer 409 to refuse input made on a picture you no longer show.
22
+ * - `POST /surface/control`: `{ controller, viewer? }` whenever control
23
+ * changes, so the service can pause its own automation while the user is
24
+ * in. `viewer` is the controlling viewer's id when `controller` is `user`.
25
+ *
26
+ * A page of the same extension open in the same window as a viewer (a
27
+ * docked toolbar, an extension page) reaches the service with that viewer's
28
+ * `x-surface-viewer`, `x-surface-viewer-controls` (`1` while it holds
29
+ * control), and `x-surface-frame-seq` on every `serviceRequest`. The host
30
+ * sets all three itself; without a viewer in that window they are absent.
21
31
  * - `POST /surface/resize`: `{ width, height }` the panel can show; the
22
32
  * service answers the size it settled on, or 400 to keep its own.
23
33
  * - `GET /surface/clipboard`: `{ text }`, what the user copied inside the
@@ -34,6 +44,12 @@ export declare const SURFACE_HEIGHT_HEADER = "x-surface-height";
34
44
  export declare const SURFACE_TITLE_HEADER = "x-surface-title";
35
45
  /** `1` while the service's own automation (an agent) is driving the surface. */
36
46
  export declare const SURFACE_AGENT_ACTIVE_HEADER = "x-surface-agent-active";
47
+ /** Host → service: the host-issued id of the viewer a request comes from. */
48
+ export declare const SURFACE_VIEWER_HEADER = "x-surface-viewer";
49
+ /** Host → service: the service's sequence number of the frame that viewer last drew; `0` before its first. */
50
+ export declare const SURFACE_FRAME_SEQ_HEADER = "x-surface-frame-seq";
51
+ /** Host → service, on page `serviceRequest`s only: `1` while that viewer holds control, else `0`. */
52
+ export declare const SURFACE_VIEWER_CONTROLS_HEADER = "x-surface-viewer-controls";
37
53
  export declare const SURFACE_FRAME_MIMES: readonly ["image/jpeg", "image/png"];
38
54
  export type SurfaceFrameMime = (typeof SURFACE_FRAME_MIMES)[number];
39
55
  /** How long the host lets one frame request wait before asking again. */
@@ -92,6 +108,8 @@ export declare const SURFACE_CONTROLLERS: readonly ["none", "agent", "user"];
92
108
  export type SurfaceController = (typeof SURFACE_CONTROLLERS)[number];
93
109
  export type SurfaceControlNotice = {
94
110
  controller: SurfaceController;
111
+ /** The controlling viewer's id (`x-surface-viewer`), present only when `controller` is `user`. */
112
+ viewer?: string;
95
113
  };
96
114
  export type SurfaceResizeRequest = {
97
115
  width: number;
@@ -15,9 +15,19 @@
15
15
  * is "the current picture, now". The host asks for one frame at a time per
16
16
  * viewer, so a slow viewer never piles up frames: that is the backpressure.
17
17
  * - `POST /surface/input`: `{ events: SurfaceInputEvent[] }`, coordinates in
18
- * frame pixels. Sent only while the user holds control.
19
- * - `POST /surface/control`: `{ controller }` whenever control changes, so the
20
- * service can pause its own automation while the user is in.
18
+ * frame pixels. Sent only while the user holds control. Headers name the
19
+ * viewer (`x-surface-viewer`) and the frame it last drew
20
+ * (`x-surface-frame-seq`, the service's own sequence number, `0` before the
21
+ * first). Answer 409 to refuse input made on a picture you no longer show.
22
+ * - `POST /surface/control`: `{ controller, viewer? }` whenever control
23
+ * changes, so the service can pause its own automation while the user is
24
+ * in. `viewer` is the controlling viewer's id when `controller` is `user`.
25
+ *
26
+ * A page of the same extension open in the same window as a viewer (a
27
+ * docked toolbar, an extension page) reaches the service with that viewer's
28
+ * `x-surface-viewer`, `x-surface-viewer-controls` (`1` while it holds
29
+ * control), and `x-surface-frame-seq` on every `serviceRequest`. The host
30
+ * sets all three itself; without a viewer in that window they are absent.
21
31
  * - `POST /surface/resize`: `{ width, height }` the panel can show; the
22
32
  * service answers the size it settled on, or 400 to keep its own.
23
33
  * - `GET /surface/clipboard`: `{ text }`, what the user copied inside the
@@ -34,6 +44,12 @@ export const SURFACE_HEIGHT_HEADER = 'x-surface-height';
34
44
  export const SURFACE_TITLE_HEADER = 'x-surface-title';
35
45
  /** `1` while the service's own automation (an agent) is driving the surface. */
36
46
  export const SURFACE_AGENT_ACTIVE_HEADER = 'x-surface-agent-active';
47
+ /** Host → service: the host-issued id of the viewer a request comes from. */
48
+ export const SURFACE_VIEWER_HEADER = 'x-surface-viewer';
49
+ /** Host → service: the service's sequence number of the frame that viewer last drew; `0` before its first. */
50
+ export const SURFACE_FRAME_SEQ_HEADER = 'x-surface-frame-seq';
51
+ /** Host → service, on page `serviceRequest`s only: `1` while that viewer holds control, else `0`. */
52
+ export const SURFACE_VIEWER_CONTROLS_HEADER = 'x-surface-viewer-controls';
37
53
  export const SURFACE_FRAME_MIMES = ['image/jpeg', 'image/png'];
38
54
  /** How long the host lets one frame request wait before asking again. */
39
55
  export const SURFACE_FRAME_WAIT_MS = 25_000;
@@ -128,11 +144,14 @@ export const readSurfaceControlNotice = (body) => {
128
144
  }
129
145
  if (Object(parsed) !== parsed || parsed === null)
130
146
  return null;
131
- const { controller } = parsed;
147
+ const { controller, viewer } = parsed;
132
148
  if (!isText(controller) || !CONTROLLERS.has(controller))
133
149
  return null;
134
150
  // SAFETY: membership in SURFACE_CONTROLLERS was just checked.
135
- return { controller: controller };
151
+ const notice = { controller: controller };
152
+ if (controller === 'user' && isText(viewer) && viewer.length > 0)
153
+ notice.viewer = viewer;
154
+ return notice;
136
155
  };
137
156
  /** A service's read of the `POST /surface/resize` body. */
138
157
  export const readSurfaceResizeRequest = (body) => {
package/dist/ui/dom.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { installGuestScrollbarActivity } from "../scrollbar-style.js";
1
2
  const STYLE_ID = 'oc-sdk-ui-style';
2
3
  export const clearNode = (node) => {
3
4
  while (node.firstChild) {
@@ -12,6 +13,8 @@ export const ensureStyle = (css) => {
12
13
  }
13
14
  return;
14
15
  }
16
+ // Kit pages previewed outside the host still get scroll-time scrollbars.
17
+ installGuestScrollbarActivity(document);
15
18
  const style = document.createElement('style');
16
19
  style.id = STYLE_ID;
17
20
  style.textContent = css;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openchamber/sdk",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Contract for OpenChamber guests: manifest parse and iframe host bridge.",
5
5
  "private": false,
6
6
  "license": "MIT",