@openchamber/sdk 1.24.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/API.md CHANGED
@@ -116,7 +116,7 @@ type GuestSessionItem = {
116
116
  sessionId: string;
117
117
  sessionTitle: string;
118
118
  directory: string | null; // the session's project directory
119
- messages?: Array<{ id: string; role: 'user' | 'assistant'; text: string; createdAt: number }>; // oldest first; only with payload ["messages"] and the conversation grant
119
+ messages?: Array<{ id: string; role: 'user' | 'assistant'; text: string; createdAt: number }>; // oldest first; only with payload ["messages"] and the conversation grant. Same messages the Markdown export writes: the conversation plus context the user attached (`user`); OpenCode's own plumbing messages are left out
120
120
  truncated?: boolean; // the oldest messages were dropped so the item stays under 2 000 000 serialized chars
121
121
  };
122
122
  ```
@@ -199,7 +199,7 @@ Snapshots carry `state: 'loading' | 'ready' | 'error'`; session snapshots also c
199
199
 
200
200
  Projects contain `id`, `name`, `directory`. Worktrees contain `directory`, `name`, `branch`, and `status: 'ready' | 'pending' | 'invalid' | 'missing'`. Session records contain `id`, `title`, `projectId`, `directory`, `parentId`, `createdAt`, `updatedAt`, `archivedAt`, `worktree`, `activity`, `outcome`, and `items`. Item references contain only this extension's `id` and optional `data`.
201
201
 
202
- `activity` is `unknown`, `idle`, `running`, `retrying`, `waiting-permission`, or `waiting-question`. `outcome` is the last observed `completed` or `failed` turn, or `null` when unknown or working. Outcomes are in memory for the latest 2,000 observed sessions, reset on runtime switch, and are not reconstructed from persisted history. A later idle event preserves an observed failure until another run starts. `completed` never means the extension's task is Done. Blocking-request contents and approve/reply actions are not exposed.
202
+ `activity` is `unknown`, `idle`, `running`, `retrying`, `waiting-permission`, or `waiting-question` (the agent put a form to the user and is waiting on the answer). `outcome` is the last observed `completed` or `failed` turn, or `null` when unknown or working. Outcomes are in memory for the latest 2,000 observed sessions, reset on runtime switch, and are not reconstructed from persisted history. A later idle event preserves an observed failure until another run starts. `completed` never means the extension's task is Done. Blocking-request contents and approve/reply actions are not exposed.
203
203
 
204
204
  ### Extension storage
205
205
 
@@ -435,7 +435,7 @@ Used by the OpenChamber host and by tools that validate packages. Guests rarely
435
435
  | `tools` | Optional, 1–16 entries that say how the extension's tool calls look in the chat. `match` is the full tool name OpenCode reports (`mcp.jira.search`, `jira_search`), 1–128 chars of `[A-Za-z0-9_.:-]`, with `*` allowed once at the end as a suffix wildcard (`mcp.jira.*`). Optional `name` (1–40, the header title when `title` is absent or renders empty), `icon` (Remixicon name or package `.svg` path, same rules as `panel.icon`; the SVG is drawn in the text colour at the glyph size), `title` / `subtitle` templates (1–200, `{input.path}` / `{output.path}` / `{metadata.path}` placeholders, a missing path renders empty, values are cut at 200), `output` `"auto"` (default) \| `"text"` \| `"json"` \| `"markdown"` \| `"code"` \| `"table"`, `language` (code only), `columns` (table only, 1–16 dotted paths; rows are the output array or `output.items`). Bad shape is `invalid-tools`. An exact `match` beats a wildcard from any extension; among equals the first extension wins. Only an enabled, fully approved extension's rules apply |
436
436
  | `filesystem` | Optional, 1–16 globs, each 1–256 chars, starting with `/` or `~/`; `**` spans folders, `*` / `?` stay in one segment; no `..`, empty segment, or backslash (`invalid-filesystem`). Declaring it adds the `filesystem` capability and the dialog lists the globs |
437
437
  | `integration` | Optional. Exactly one of `oauth`, `token`, or `host` (`provider: "linear"` only) |
438
- | `service` | Optional. `entry` must be a built `.js` file on disk. See [GUEST_SERVICES.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/GUEST_SERVICES.md) |
438
+ | `service` | Optional. `entry` must be a built `.js` file on disk. `provides: ["browser"]` makes it the agent's browser when the user selects it; `surface: true` gives it a host-drawn live panel the user can take over (no `panel.entry` then). Neither needs a panel or background entry. See [GUEST_SERVICES.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/GUEST_SERVICES.md) |
439
439
 
440
440
 
441
441
  Extra keys are dropped, not forwarded.
@@ -495,6 +495,10 @@ Panel → `serviceRequest` → host → `127.0.0.1:port` → service process →
495
495
 
496
496
  Manifest sketch: `service.entry` (path to **built** JS, e.g. `service/main.js`), `runtime: "host"`, `permissions.sockets` and/or `permissions.exec`. Install refuses with `missing-build` when that file is absent. Declaring a service adds `service` to the capabilities the user approves at install; until then `serviceRequest` is `NO_SERVICE`.
497
497
 
498
+ A service with `provides: ["browser"]` answers the agent's `browser.*` actions at `POST /browser-control` instead of the desktop app's browser panel, once the user selects it in Settings → OpenChamber Tools. The host starts it on the first action and stops it when idle. Parse the body with `readBrowserProviderRequest`; request and answer types (`BrowserProviderRequest`, `BrowserProviderResult`, per-action `Browser*Parameters` / `Browser*Data`) and the limits (`BROWSER_PROVIDER_*`) are exported from `@openchamber/sdk`. Full contract in [GUEST_SERVICES.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/GUEST_SERVICES.md#browser-provider-provides-browser).
499
+
500
+ A service with `surface: true` shows a live picture in the extension's rail panel: the host pulls frames from `GET /surface/frame`, draws them, sends the user's input to `POST /surface/input`, and owns who is in control (nobody, the agent, the user). Paths, event types, and the `readSurface*` parsers are exported from `@openchamber/sdk`; contract in [GUEST_SERVICES.md](https://github.com/openchamber/openchamber/blob/main/packages/sdk/GUEST_SERVICES.md#shared-surface-surface-true).
501
+
498
502
  Bundle the service with the Node target:
499
503
 
500
504
  ```bash
package/DOCUMENTATION.md CHANGED
@@ -16,6 +16,8 @@ Public author pages live in `packages/docs/content/docs/sdk.mdx`, `sdk/host.mdx`
16
16
  - `scripts/bundle-guest.ts`: official IIFE build, exposed as the `openchamber-guest-bundle` bin. Run `bunx openchamber-guest-bundle panel/main.ts panel/main.js` from the guest folder; paths resolve against the current directory. Needs Bun. Any bundler with an IIFE target is an equivalent substitute.
17
17
  - `dist/`: the published output (`bun run --cwd packages/sdk build`, `tsc -p tsconfig.build.json`). `package.json` `exports` point here so Node loads the SDK from `node_modules` (packaged Electron, `openchamber serve`). Inside the monorepo, Vite aliases and tsconfig `paths` resolve `@openchamber/sdk` to `src/` so builds and type-checks never depend on a stale `dist`. The server under Bun and Node uses `dist`; the root `postinstall` builds it. The package ships to npm from the release workflow with `bun publish`, right before `@openchamber/web`; `scripts/bump-version.mjs` bumps it with the app, so the SDK version always equals the OpenChamber version it was released with.
18
18
  - `GUEST_SERVICES.md`: local service contract (`contributes.service`, `serviceRequest`, grant, loopback).
19
+ - `src/service-providers.ts`: contracts a service fulfils for a host role (`contributes.service.provides`). Today only `browser`: the ten `browser.*` actions, their parameter and answer types, the limits, `BrowserProviderContext` (the calling project and chat, filled by the host from the tool call; `null` fields mean unknown), and `readBrowserProviderRequest` (a zod-free boundary parse for the service side). `src/service-provider-schemas.ts` is the host's zod parse of the answer, exported from `@openchamber/sdk/schemas`. A providing service needs no panel or background entry (`runtimeContributions` in `parse.ts`). The host side is `packages/web/server/lib/browser-control/provider.js`.
20
+ - `src/service-surface.ts`: the shared-surface contract (`contributes.service.surface`): loopback paths and headers, input event types, limits, and the zod-free `readSurfaceInputBatch` / `readSurfaceControlNotice` / `readSurfaceResizeRequest` for the service side. `src/service-surface-schemas.ts` (host entry) holds the viewer↔host socket messages (`surfaceViewerMessageSchema`, `surfaceHostMessageSchema`) shared by `packages/ui/src/lib/guests/surface-client.ts` and `packages/web/server/lib/guests/surface.js`, plus the service answer schemas. A surface service needs no entry of its own; with `panel.entry` that page is docked to one edge of the picture, `panel.dock` (`GUEST_SURFACE_DOCKS`) and `panel.size` px (`GUEST_SURFACE_DOCK_SIZE_*`), only valid with both. The rail panel is `packages/ui/src/components/layout/GuestSurfacePane.tsx` (host-drawn canvas, not an iframe); `ContextPanel` lays the page iframe (kept mounted) beside the pane (mounted only while shown) per `DOCK_LAYOUT`, and `lib/guests/surfaces.ts#guestSurfaceDocking` decides.
19
21
  - `src/ui/`: guest drawing kit. Building blocks only (`mountButton`, `mountTextField`, `mountSearchField`, `mountSelect`, `mountCheckbox`, `mountSwitch`, `mountTabs`, `mountBadge`, `mountList`, `mountEmpty`, `mountSpinner`, `mountBanner`, `mountSeparator`, `mountProgress`, `mountMenu`, `mountText`); the guest composes screens. See `src/ui/DOCUMENTATION.md`. Import `@openchamber/sdk/ui`. Do not import this from `packages/ui`. Kit changes follow package semver, not `apiVersion`.
20
22
 
21
23
  ## Invariants
package/GUEST_SERVICES.md CHANGED
@@ -88,6 +88,8 @@ Parse rules:
88
88
  - `contributes.service` is optional. A guest without `service` is HTML-only.
89
89
  - `service.entry` is a relative path inside the package. Ship compiled JS; the host never compiles TypeScript.
90
90
  - `service.runtime` phase 1 accepts only `"host"`.
91
+ - `service.provides` is optional: roles the service stands in for on the host. Only `"browser"` exists today (below). A service that provides a role needs no `panel.entry` or `background.entry`; the host starts it itself.
92
+ - `service.surface` is optional (`true`): the service shows a live picture the host draws in the extension's rail panel and takes the user's input back (below). With `panel.entry` too, that page is docked to one edge of the picture (`panel.dock`: `top`, `bottom`, `left`, or `right`, default `top`; `panel.size` in CSS px across that edge, 24 to 480, default 40); without it, the picture is the whole panel.
91
93
  - `service.permissions.sockets` and `service.permissions.exec` are shown to the user in the approval dialog. They describe intent and do not confine the process: a service runs with the user's full access. Declaring `contributes.service` adds the `service` capability to the package's request list; the user approves the whole list once when the package is installed (Settings → Extensions), and the first `serviceRequest` is refused with `NO_SERVICE` until then.
92
94
  - The catalog adds `service.socketBindings`: `{ id, candidates, resolved, override }` for this host. The user can override a path in Extensions. Override empty clears it and the next spawn re-resolves.
93
95
  - `contributes.integration` remains valid next to `service`. Cloud `request` and `serviceRequest` may both exist on one guest.
@@ -107,7 +109,8 @@ Error codes:
107
109
 
108
110
  - `NO_SERVICE` — no service declared, not granted, not started, or already torn down
109
111
  - `DISABLED` — extension paused in Settings → Extensions (service stopped; tokens/grants stay)
110
- - `SERVICE_FAILED` — process crashed or never became ready
112
+ - `SERVICE_FAILED` — process crashed or never became ready; nothing was sent to it
113
+ - `REQUEST_FAILED` — the request was sent but got no usable answer (timeout, dropped connection); the service may have acted on it
111
114
  - Existing: `HOST_TIMEOUT`, `HOST_REJECTED`, `HOST_UNAVAILABLE`, `BAD_PATH`
112
115
 
113
116
  Server routes (authenticated UI session):
@@ -164,3 +167,83 @@ Ship `service/main.js` already built. Same packaging rule as `panel/main.js`.
164
167
  ## Example
165
168
 
166
169
  `examples/service-echo` is a checked-in service extension: a Node HTTP server on loopback that the panel calls through `serviceRequest` and whose status it shows. Bundle the service with `--node`, install the folder from Settings → Extensions, allow the local service in the approval dialog, then open the rail panel. Streaming from a service to the panel (shell into a container, log tails) is deferred; it needs a streaming call on the SDK first.
170
+
171
+ ## Browser provider (`provides: ["browser"]`)
172
+
173
+ The host's `openchamber_web` tool gives agents ten `browser.*` actions. By default a connected desktop app answers them with its browser panel; on a server with no desktop client the agent gets nothing. A service that declares `provides: ["browser"]` can answer instead, from a browser it runs itself (headless Chrome over CDP, for instance). The agent sees one tool either way.
174
+
175
+ The user picks the provider in Settings → General → OpenChamber Tools → Browser provider. The dropdown lists installed, enabled, fully approved extensions with the role; with none it shows only "OpenChamber Web", disabled. The choice is `browserProvider` in the instance's `settings.json` (`builtin` or the extension id) and is read on every action, so it applies to the next action without a restart. Pausing, removing, or withdrawing approval from the selected extension puts `builtin` back and every open client shows a toast saying so; the same happens on the next action if the extension became unusable any other way.
176
+
177
+ Lifecycle differs from a panel-driven service in two ways. The host starts the service on the first action, so the agent can browse with no panel and no viewer; the grant is the same one the user gave at install. After `BROWSER_PROVIDER_IDLE_MS` (ten minutes) without an action the host stops the process; the next action starts it again. A `browser.open` may take `BROWSER_PROVIDER_OPEN_TIMEOUT_MS` (45 s), every other action `BROWSER_PROVIDER_ACTION_TIMEOUT_MS` (20 s). Answers are read up to `BROWSER_PROVIDER_RESPONSE_MAX` (12 MB), enough for a screenshot.
178
+
179
+ ### Wire
180
+
181
+ `POST /browser-control` on the service loopback, same bearer as every request, JSON body:
182
+
183
+ ```json
184
+ {
185
+ "requestId": "browser-…",
186
+ "action": "browser.click",
187
+ "parameters": { "selector": "#save" },
188
+ "context": { "directory": "/Users/me/app", "sessionId": "ses_…" }
189
+ }
190
+ ```
191
+
192
+ `readBrowserProviderRequest(body)` from `@openchamber/sdk` parses it (`null` → answer HTTP 400). The host validated `parameters` for the action before posting, so the service can trust the shape.
193
+
194
+ `context` says where the action came from: the project the agent works in and the chat it runs in. The host fills it from the tool call; the model never types it. A provider that keeps one browser per project or chat keys its targets on these; one that keeps a single browser ignores them. Either field is `null` when the host had none (an action sent from the CLI, for example); treat that as "unknown", not as a scope of its own. The shared surface is still one per service: if you keep several targets, choose which one the picture shows.
195
+
196
+ Answer HTTP 200 with one of:
197
+
198
+ ```json
199
+ { "ok": true, "data": { … } }
200
+ { "ok": false, "error": "No element matches #save" }
201
+ ```
202
+
203
+ `error` goes to the agent as the tool's error, so write it as what to do differently, not what broke inside. Any other status or shape is reported to the agent as "unknown page state".
204
+
205
+ ### Actions
206
+
207
+ Parameters the host sends and `data` the service answers; types are exported from `@openchamber/sdk` (`BrowserOpenParameters`, `BrowserOpenData`, …). Every shape is what the desktop app's own panel answers, so an agent's prompt written against one works against the other.
208
+
209
+ | Action | Parameters | `data` |
210
+ |---|---|---|
211
+ | `browser.open` | `url` (absolute http/https), `viewport?` | `url`, `title`, `opened: true`, `settled` (`false` = still loading, not a failure), `viewport` |
212
+ | `browser.snapshot` | `selector?` (scope) | `url`, `title`, `scope`, `scrollY`, `maxScrollY`, `text`, `elements[]`, `viewport`, optional `textTruncated`/`textTotalChars`, `elementsTruncated`/`interactiveElementsOnPage`, `consoleProblems[]` |
213
+ | `browser.click` | `selector?` or `text?` (visible label) | `clicked` (selector), `label`, `url` |
214
+ | `browser.type` | `selector`, `value`, `submit` | `selector`, `url` |
215
+ | `browser.scroll` | `direction?` (`up`/`down`/`top`/`bottom`) or `selector?` | `scrollY`, `maxScrollY`, `atTop`, `atBottom`, optional `scrolledTo`/`direction` |
216
+ | `browser.back` / `browser.forward` | none | `url`, `title` |
217
+ | `browser.inspect` | `selector` | `selector`, `tag`, `label`, `bounds`, `inViewport`, `styles` (computed, as strings) |
218
+ | `browser.capture` | `label?` | `base64`, `mime`, `width`, `height`, `url`, `title`, `viewport`; the host writes the file into the project and returns its path |
219
+ | `browser.resize` | `viewport` (`mobile`/`tablet`/`desktop`/`fill`) | `viewport` |
220
+
221
+ `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
+
223
+ `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.
224
+
225
+ ## Shared surface (`surface: true`)
226
+
227
+ A surface lets a person watch what an agent is working in and step in for a manual step. It is not about browsers: a service that drives a browser, a simulator, or a desktop app can show one. The host draws it in the extension's rail panel (a canvas; the extension runs no code there), sends the user's pointer, keyboard, and paste back, and decides who is in control.
228
+
229
+ Control has three states. Nobody, the agent (for `SURFACE_AGENT_HOLD_MS`, 30 s, after each action the host ran against this extension, such as a browser provider action), or the user. The user takes control by acting: the first click, wheel, key, or paste while nobody or the agent holds it. Moving the pointer over the picture is looking, not acting; it takes nothing and is not even sent until the user holds control. While the user holds it, the host refuses the browser provider's actions with a message that tells the agent to wait or ask, and the panel shows a "Hand back to agent" button. A second device sees who holds control and cannot take it. Every change is posted to the service so its own automation can pause. The service is kept running while a viewer is attached, and returns to its idle window when the last one leaves.
230
+
231
+ ### Wire
232
+
233
+ Plain HTTP on the service loopback, same bearer as everything else:
234
+
235
+ | Call | Body / answer |
236
+ |---|---|
237
+ | `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/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
+ | `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
+
243
+ Input, control notices, resizes, and clipboard reads reach the service one at a time, in the order the viewer sent them, so a batch never overtakes the one before it. Copy and paste: the host sends `Ctrl/Cmd+C` as a `key` event and then, behind it, reads `/surface/clipboard`; it never sends the paste chord, it sends a `text` event with the pasted text instead. Every other key reaches you as pressed, including the host's own shortcuts, which stand down while the surface has focus.
244
+
245
+ ### Your own controls beside the picture
246
+
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.
248
+
249
+ The types and paths are exported from `@openchamber/sdk` (`SURFACE_*`, `SurfaceInputEvent`, …). `examples/browser-provider-stub` also declares `surface: true` and paints its fake page with rectangles, so the viewer, the hand-off, and the input path can be seen working without a browser; its `panel/` is a one-line address bar docked above the picture.
package/README.md CHANGED
@@ -96,7 +96,7 @@ Git installs also accept SSH addresses such as `git@github.com:owner/extension.g
96
96
  - `tools` is optional: how your tool calls look in the chat, up to 16, no code. `match` is the tool name OpenCode reports (`mcp.tasks.*` matches every tool under that prefix); `name` and `icon` (a Remixicon name or an SVG inside the folder, like `panel.icon`) set the header, `title` and `subtitle` are templates like `{input.id}` or `{output.total} open`, and `output` picks the body: `text`, `json`, `markdown`, `code` (with `language`), or `table` (with `columns`, rows from the output array or `output.items`). Leave `output` out to keep the app's own detection.
97
97
  - `capabilities` lists what needs the user's approval: `prompt` to send messages, `sessions` to create sessions and worktrees, `files` to read and write inside the open project, `model` for one-off text generation with the user's Small Model (`host.generate`, no session involved). An `integration` adds `network`, a `service` adds `service`, `filesystem` patterns (like `["~/.config/opencode/opencode.json"]`) add `filesystem`, which lets `readFile`, `writeFile`, `listDir`, and `stat` reach those paths outside the project, and a session action with `payload: ["messages"]` adds `conversation`. The user approves the whole list once at install. Calls outside it fail with `NOT_GRANTED`.
98
98
  - `integration` is optional. It adds a card at Settings → Integrations. `token` takes a pasted API token (`scheme: "bearer"` for `Authorization: Bearer`, `"basic"` for a username and token pair as Jira Cloud wants), `oauth` runs an authorize flow with a pasted client id, and `host: { "provider": "linear" }` reuses the Linear account already connected in OpenChamber. The page never sees the token; OpenChamber makes the calls through `host.request`.
99
- - `service` is optional. It declares a local process OpenChamber starts next to the extension. It runs with the user's full access and no sandbox, so declare one only when the page cannot do the job. See [GUEST_SERVICES.md](./GUEST_SERVICES.md).
99
+ - `service` is optional. It declares a local process OpenChamber starts next to the extension. It runs with the user's full access and no sandbox, so declare one only when the page cannot do the job. See [GUEST_SERVICES.md](./GUEST_SERVICES.md). With `provides: ["browser"]` the service can stand in for the agent's browser, so agents browse on the server with no desktop app open; the user picks it in Settings → OpenChamber Tools. With `surface: true` it shows a live picture in the rail that the user can watch and take over, and hand back to the agent; add `panel.entry` (with `panel.dock` and `panel.size`) for your own controls docked beside it.
100
100
 
101
101
  ## Actions without opening a panel
102
102
 
package/dist/index.d.ts CHANGED
@@ -4,10 +4,14 @@ export type { GuestLoadState, GuestProject, GuestWorktree, GuestSessionActivity,
4
4
  export { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_KEYS_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_TOTAL_BYTES } from './workspace.ts';
5
5
  export type { OpenChamberManifestApiVersion } from './api-version.ts';
6
6
  export { compareOpenChamberVersions, hostMeetsOpenChamberEngine, openChamberEngineMinimum, OPENCHAMBER_ENGINE_PATTERN, parseOpenChamberVersion, } from './host-version.ts';
7
- export { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_CAPABILITIES, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, GUEST_TOOL_TEMPLATE_VALUE_MAX, guestActionsNeedConversation, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_FILESYSTEM_PATTERNS_MAX, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, resolveAttachMode, resolveIntegrationApi, resolveIntegrationAuth, toPublicService, toPublicIntegration, } from './manifest.ts';
8
- export type { ServiceContribution, ServicePermissions, GuestActionContribution, GuestActionPayload, GuestActionRole, GuestActionWhere, GuestCommandContribution, GuestToolContribution, GuestToolOutput, AttachContribution, AttachContributionObject, AttachMode, DeclaredGuestCapability, GuestAuthorization, GuestCapability, IntegrationAuth, IntegrationContribution, IntegrationHost, IntegrationHostProvider, IntegrationOAuth, IntegrationOAuthAccount, IntegrationSettingField, IntegrationToken, IntegrationTokenScheme, OpenChamberContributes, OpenChamberEngines, OpenChamberManifest, PanelContribution, BackgroundContribution, PageContribution, ParseManifestErrorCode, ParseManifestFailure, ParseManifestResult, ParseManifestSuccess, PublicService, PublicServicePermissions, PublicGuestCapabilities, PublicIntegration, PublicIntegrationToken, PublicSocketBinding, ResolvedGuestApi, SocketBinding, SocketPlatform, } from './manifest.ts';
7
+ export { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_CAPABILITIES, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, GUEST_TOOL_TEMPLATE_VALUE_MAX, guestActionsNeedConversation, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_SERVICE_PROVIDES, GUEST_SURFACE_DOCKS, GUEST_SURFACE_DOCK_DEFAULT, GUEST_SURFACE_DOCK_SIZE_DEFAULT, GUEST_SURFACE_DOCK_SIZE_MAX, GUEST_SURFACE_DOCK_SIZE_MIN, 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';
9
9
  export { connectHost, HostRequestError } from './host.ts';
10
10
  export type { HostClient, HostClientOptions, HostFrame } from './host.ts';
11
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
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
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';
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';
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
@@ -2,7 +2,9 @@ export { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL, OPENCHAMBER_SDK_M
2
2
  export { GUEST_SCROLLBAR_CSS } from "./scrollbar-style.js";
3
3
  export { GUEST_STORAGE_KEY_MAX, GUEST_STORAGE_KEYS_MAX, GUEST_STORAGE_VALUE_BYTES, GUEST_STORAGE_TOTAL_BYTES } from "./workspace.js";
4
4
  export { compareOpenChamberVersions, hostMeetsOpenChamberEngine, openChamberEngineMinimum, OPENCHAMBER_ENGINE_PATTERN, parseOpenChamberVersion, } from "./host-version.js";
5
- export { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_CAPABILITIES, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, GUEST_TOOL_TEMPLATE_VALUE_MAX, guestActionsNeedConversation, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_FILESYSTEM_PATTERNS_MAX, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, resolveAttachMode, resolveIntegrationApi, resolveIntegrationAuth, toPublicService, toPublicIntegration, } from "./manifest.js";
5
+ export { DECLARED_GUEST_CAPABILITIES, GUEST_ACTIONS_MAX, GUEST_ACTION_LABEL_MAX, GUEST_CAPABILITIES, GUEST_COMMANDS_MAX, GUEST_COMMAND_DESCRIPTION_MAX, GUEST_COMMAND_NAME, GUEST_TOOLS_MAX, GUEST_TOOL_COLUMNS_MAX, GUEST_TOOL_COLUMN_MAX, GUEST_TOOL_LANGUAGE_MAX, GUEST_TOOL_MATCH, GUEST_TOOL_MATCH_MAX, GUEST_TOOL_NAME_MAX, GUEST_TOOL_OUTPUTS, GUEST_TOOL_TEMPLATE_MAX, GUEST_TOOL_TEMPLATE_VALUE_MAX, guestActionsNeedConversation, GUEST_FILESYSTEM_PATTERN_MAX, GUEST_FILESYSTEM_PATTERNS_MAX, GUEST_SERVICE_PROVIDES, GUEST_SURFACE_DOCKS, GUEST_SURFACE_DOCK_DEFAULT, GUEST_SURFACE_DOCK_SIZE_DEFAULT, GUEST_SURFACE_DOCK_SIZE_MAX, GUEST_SURFACE_DOCK_SIZE_MIN, HOST_LINEAR_API_ORIGIN, isGuestFilesystemPattern, hasGuestCapability, hasGuestPage, isGuestApproved, isGuestPackageSvgIcon, requestedGuestCapabilities, resolveAttachEntry, resolvePageEntry, resolveAttachMode, resolveIntegrationApi, resolveIntegrationAuth, serviceProvides, toPublicService, toPublicIntegration, } from "./manifest.js";
6
6
  export { connectHost, HostRequestError } from "./host.js";
7
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
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";
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,7 +10,24 @@ export type PanelContribution = {
10
10
  name: string;
11
11
  icon: string;
12
12
  entry?: string;
13
+ /**
14
+ * Beside a shared surface (`service.surface`), the panel page is docked to
15
+ * one edge of the host-drawn picture: a toolbar above it, an inspector
16
+ * below, a tool column beside it. `dock` is the edge (default
17
+ * `GUEST_SURFACE_DOCK_DEFAULT`) and `size` the page's thickness in CSS
18
+ * pixels across that edge (default `GUEST_SURFACE_DOCK_SIZE_DEFAULT`).
19
+ * Both only make sense with `entry` and `service.surface` together.
20
+ */
21
+ dock?: GuestSurfaceDock;
22
+ size?: number;
13
23
  };
24
+ export declare const GUEST_SURFACE_DOCKS: readonly ["top", "bottom", "left", "right"];
25
+ export type GuestSurfaceDock = (typeof GUEST_SURFACE_DOCKS)[number];
26
+ export declare const GUEST_SURFACE_DOCK_DEFAULT: GuestSurfaceDock;
27
+ /** Thickness in CSS px of a docked `panel.entry` when the manifest names none. */
28
+ export declare const GUEST_SURFACE_DOCK_SIZE_DEFAULT = 40;
29
+ export declare const GUEST_SURFACE_DOCK_SIZE_MIN = 24;
30
+ export declare const GUEST_SURFACE_DOCK_SIZE_MAX = 480;
14
31
  /** Sandboxed HTML loaded on demand for background actions and slash commands. */
15
32
  export type BackgroundContribution = {
16
33
  entry: string;
@@ -199,18 +216,37 @@ export type PublicSocketBinding = {
199
216
  resolved: string | null;
200
217
  override: string | null;
201
218
  };
219
+ /**
220
+ * Host roles a service can stand in for. `browser` answers the agent's
221
+ * `browser.*` actions in place of the in-app browser view; the contract is in
222
+ * `service-providers.ts`. A service that provides a role needs no panel or
223
+ * background entry: the host starts it on the first action.
224
+ */
225
+ export declare const GUEST_SERVICE_PROVIDES: readonly ["browser"];
226
+ export type GuestServiceProvides = (typeof GUEST_SERVICE_PROVIDES)[number];
202
227
  export type ServiceContribution = {
203
228
  entry: string;
204
229
  runtime: 'host';
205
230
  permissions?: ServicePermissions;
231
+ provides?: GuestServiceProvides[];
232
+ /**
233
+ * The service shows a live surface (frames out, input in) that the host
234
+ * draws in this extension's rail panel; see `service-surface.ts`. With
235
+ * `panel.entry` too, that page is docked to one edge of the picture (see
236
+ * `PanelContribution.dock`); without it, the panel is the surface alone.
237
+ */
238
+ surface?: true;
206
239
  };
207
240
  /** Catalog card for a local service. Drops nothing secret; grant is host state. */
208
241
  export type PublicService = {
209
242
  runtime: 'host';
210
243
  permissions?: PublicServicePermissions;
211
244
  socketBindings?: PublicSocketBinding[];
245
+ provides?: GuestServiceProvides[];
246
+ surface?: true;
212
247
  granted: boolean;
213
248
  };
249
+ export declare const serviceProvides: (service: Pick<ServiceContribution, "provides"> | undefined, role: GuestServiceProvides) => boolean;
214
250
  /**
215
251
  * What a guest may do beyond drawing its own panel. The user approves the
216
252
  * full list once, when the package is installed; a later package that asks
package/dist/manifest.js CHANGED
@@ -1,4 +1,10 @@
1
1
  export const PANEL_ID = /^[a-z][a-z0-9-]*$/;
2
+ export const GUEST_SURFACE_DOCKS = ['top', 'bottom', 'left', 'right'];
3
+ export const GUEST_SURFACE_DOCK_DEFAULT = 'top';
4
+ /** Thickness in CSS px of a docked `panel.entry` when the manifest names none. */
5
+ export const GUEST_SURFACE_DOCK_SIZE_DEFAULT = 40;
6
+ export const GUEST_SURFACE_DOCK_SIZE_MIN = 24;
7
+ export const GUEST_SURFACE_DOCK_SIZE_MAX = 480;
2
8
  /** How many `contributes.actions` entries a package may declare. */
3
9
  export const GUEST_ACTIONS_MAX = 8;
4
10
  /** Characters in an action label. */
@@ -71,6 +77,14 @@ export const resolveIntegrationApi = (integration) => {
71
77
  }
72
78
  return null;
73
79
  };
80
+ /**
81
+ * Host roles a service can stand in for. `browser` answers the agent's
82
+ * `browser.*` actions in place of the in-app browser view; the contract is in
83
+ * `service-providers.ts`. A service that provides a role needs no panel or
84
+ * background entry: the host starts it on the first action.
85
+ */
86
+ export const GUEST_SERVICE_PROVIDES = ['browser'];
87
+ export const serviceProvides = (service, role) => Boolean(service?.provides?.includes(role));
74
88
  /**
75
89
  * What a guest may do beyond drawing its own panel. The user approves the
76
90
  * full list once, when the package is installed; a later package that asks
@@ -217,6 +231,12 @@ export const toPublicService = (service, granted, socketBindings) => {
217
231
  override: binding.override,
218
232
  }));
219
233
  }
234
+ if (service.provides && service.provides.length > 0) {
235
+ next.provides = [...service.provides];
236
+ }
237
+ if (service.surface) {
238
+ next.surface = true;
239
+ }
220
240
  return next;
221
241
  };
222
242
  /** Guest package version: `1.2.3`, optional prerelease / build. */
package/dist/parse.d.ts CHANGED
@@ -11,6 +11,13 @@ export declare const openChamberManifestSchema: z.ZodObject<{
11
11
  name: z.ZodString;
12
12
  icon: z.ZodString;
13
13
  entry: z.ZodOptional<z.ZodString>;
14
+ dock: z.ZodOptional<z.ZodEnum<{
15
+ top: "top";
16
+ bottom: "bottom";
17
+ left: "left";
18
+ right: "right";
19
+ }>>;
20
+ size: z.ZodOptional<z.ZodNumber>;
14
21
  }, z.core.$strip>;
15
22
  background: z.ZodOptional<z.ZodObject<{
16
23
  entry: z.ZodString;
@@ -97,6 +104,10 @@ export declare const openChamberManifestSchema: z.ZodObject<{
97
104
  sockets?: SocketBinding[] | undefined;
98
105
  exec?: string[] | undefined;
99
106
  }>>>;
107
+ provides: z.ZodOptional<z.ZodArray<z.ZodEnum<{
108
+ browser: "browser";
109
+ }>>>;
110
+ surface: z.ZodOptional<z.ZodLiteral<true>>;
100
111
  }, z.core.$strip>>;
101
112
  filesystem: z.ZodOptional<z.ZodArray<z.ZodString>>;
102
113
  actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -156,6 +167,13 @@ export declare const packageManifestSchema: z.ZodObject<{
156
167
  name: z.ZodString;
157
168
  icon: z.ZodString;
158
169
  entry: z.ZodOptional<z.ZodString>;
170
+ dock: z.ZodOptional<z.ZodEnum<{
171
+ top: "top";
172
+ bottom: "bottom";
173
+ left: "left";
174
+ right: "right";
175
+ }>>;
176
+ size: z.ZodOptional<z.ZodNumber>;
159
177
  }, z.core.$strip>;
160
178
  background: z.ZodOptional<z.ZodObject<{
161
179
  entry: z.ZodString;
@@ -242,6 +260,10 @@ export declare const packageManifestSchema: z.ZodObject<{
242
260
  sockets?: SocketBinding[] | undefined;
243
261
  exec?: string[] | undefined;
244
262
  }>>>;
263
+ provides: z.ZodOptional<z.ZodArray<z.ZodEnum<{
264
+ browser: "browser";
265
+ }>>>;
266
+ surface: z.ZodOptional<z.ZodLiteral<true>>;
245
267
  }, z.core.$strip>>;
246
268
  filesystem: z.ZodOptional<z.ZodArray<z.ZodString>>;
247
269
  actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
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_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, } 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, } from "./manifest.js";
5
5
  const isPanelIcon = (value) => (PANEL_ID.test(value) || isGuestPackageSvgIcon(value));
6
6
  const isHttpsUrl = (value) => {
7
7
  try {
@@ -48,6 +48,8 @@ const panelSchema = z.object({
48
48
  name: z.string().trim().min(1),
49
49
  icon: z.string().trim().refine(isPanelIcon),
50
50
  entry: z.string().trim().refine(isSafeAssetPath).optional(),
51
+ dock: z.enum(GUEST_SURFACE_DOCKS).optional(),
52
+ size: z.number().int().min(GUEST_SURFACE_DOCK_SIZE_MIN).max(GUEST_SURFACE_DOCK_SIZE_MAX).optional(),
51
53
  });
52
54
  const integrationSettingSchema = z.object({
53
55
  id: z.string().trim().regex(PANEL_ID),
@@ -140,6 +142,10 @@ const serviceSchema = z.object({
140
142
  entry: z.string().trim().refine(isSafeAssetPath),
141
143
  runtime: z.literal('host'),
142
144
  permissions: servicePermissionsSchema.optional(),
145
+ provides: z.array(z.enum(GUEST_SERVICE_PROVIDES)).min(1).max(GUEST_SERVICE_PROVIDES.length)
146
+ .refine((roles) => new Set(roles).size === roles.length, { message: 'provides entries must be unique' })
147
+ .optional(),
148
+ surface: z.literal(true).optional(),
143
149
  });
144
150
  const uniqueBy = (items, key) => (new Set(items.map(key)).size === items.length);
145
151
  // `roles` only means something on a message action and `payload` only on a
@@ -208,7 +214,10 @@ const runtimeContributions = (contributes) => {
208
214
  declared.push('capabilities');
209
215
  if (contributes.integration !== undefined)
210
216
  declared.push('integration');
211
- if (contributes.service !== undefined)
217
+ // A service the host starts itself (it provides a role or a surface) needs
218
+ // no frame; one that only answers a panel's `serviceRequest` has no caller
219
+ // without one.
220
+ if (contributes.service !== undefined && !contributes.service.provides?.length && !contributes.service.surface)
212
221
  declared.push('service');
213
222
  if (contributes.filesystem !== undefined)
214
223
  declared.push('filesystem');
@@ -224,6 +233,15 @@ export const openChamberManifestSchema = z.object({
224
233
  openchamber: z.string().trim().regex(OPENCHAMBER_ENGINE_PATTERN),
225
234
  }).strict().optional(),
226
235
  contributes: contributesSchema.superRefine((contributes, ctx) => {
236
+ // Docking only means something for a page beside a shared surface.
237
+ const docked = contributes.panel.dock !== undefined || contributes.panel.size !== undefined;
238
+ if (docked && !(contributes.service?.surface && hasGuestPage(contributes))) {
239
+ ctx.addIssue({
240
+ code: 'custom', path: ['panel', 'dock'],
241
+ message: 'panel.dock and panel.size place panel.entry beside service.surface; they need both.',
242
+ });
243
+ return;
244
+ }
227
245
  if (hasGuestPage(contributes))
228
246
  return;
229
247
  if (contributes.background) {
@@ -301,6 +319,12 @@ const failureFromIssue = (issue) => {
301
319
  return fail('invalid-panel-icon', 'panel.icon must be a Remixicon name or a package .svg path.');
302
320
  case 'contributes.panel.entry':
303
321
  return fail('invalid-panel-entry', 'panel.entry must be a relative path inside the package.');
322
+ case 'contributes.panel.dock':
323
+ return fail('invalid-panel', issue.code === 'custom'
324
+ ? issue.message
325
+ : `panel.dock is one of ${GUEST_SURFACE_DOCKS.join(', ')}.`);
326
+ case 'contributes.panel.size':
327
+ return fail('invalid-panel', `panel.size is a whole number of CSS pixels from ${GUEST_SURFACE_DOCK_SIZE_MIN} to ${GUEST_SURFACE_DOCK_SIZE_MAX}.`);
304
328
  case 'contributes.capabilities':
305
329
  return fail('invalid-capabilities', 'contributes.capabilities may list "prompt", "sessions", and "files".');
306
330
  default:
@@ -334,7 +358,7 @@ const failureFromIssue = (issue) => {
334
358
  return fail('invalid-integration', 'contributes.integration needs a name, description, and oauth, token, or host.');
335
359
  }
336
360
  if (path.startsWith('contributes.service')) {
337
- return fail('invalid-service', 'contributes.service needs entry, runtime "host", and optional permissions.');
361
+ return fail('invalid-service', 'contributes.service needs entry, runtime "host", optional permissions, optional provides ("browser"), and optional surface (true).');
338
362
  }
339
363
  return fail('missing-panel', 'contributes.panel is required.');
340
364
  };
package/dist/schemas.d.ts CHANGED
@@ -2,3 +2,6 @@ export { openChamberManifestSchema, PACKAGE_VERSION_PATTERN, packageManifestSche
2
2
  export type { ManifestDocument } from './parse.ts';
3
3
  export { guestStorageRequestSchema, guestStorageResultSchema, guestWorkspaceSnapshotSchema } from './workspace-schemas.ts';
4
4
  export { guestMessageSchema, hostMessageSchema, parseGuestMessage, parseHostMessage, } from './protocol.ts';
5
+ export { browserControlActionSchema, browserProviderResultSchema } from './service-provider-schemas.ts';
6
+ export { surfaceClipboardAnswerSchema, surfaceControllerSchema, surfaceHostMessageSchema, surfaceInputEventSchema, surfaceResizeAnswerSchema, surfaceViewerMessageSchema, } from './service-surface-schemas.ts';
7
+ export type { SurfaceHostMessage, SurfaceViewerMessage } from './service-surface-schemas.ts';
package/dist/schemas.js CHANGED
@@ -4,3 +4,5 @@
4
4
  export { openChamberManifestSchema, PACKAGE_VERSION_PATTERN, packageManifestSchema, parseManifest, parseManifestJson, } from "./parse.js";
5
5
  export { guestStorageRequestSchema, guestStorageResultSchema, guestWorkspaceSnapshotSchema } from "./workspace-schemas.js";
6
6
  export { guestMessageSchema, hostMessageSchema, parseGuestMessage, parseHostMessage, } from "./protocol.js";
7
+ export { browserControlActionSchema, browserProviderResultSchema } from "./service-provider-schemas.js";
8
+ export { surfaceClipboardAnswerSchema, surfaceControllerSchema, surfaceHostMessageSchema, surfaceInputEventSchema, surfaceResizeAnswerSchema, surfaceViewerMessageSchema, } from "./service-surface-schemas.js";
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Host-side parse of what a browser provider answers. `data` stays whatever
4
+ * the service produced: the host hands it to the agent as is, and only
5
+ * `browser.capture` is read further (the image the host writes to disk).
6
+ */
7
+ export declare const browserProviderResultSchema: z.ZodUnion<readonly [z.ZodObject<{
8
+ ok: z.ZodLiteral<true>;
9
+ data: z.ZodObject<{}, z.core.$loose>;
10
+ }, z.core.$strip>, z.ZodObject<{
11
+ ok: z.ZodLiteral<false>;
12
+ error: z.ZodString;
13
+ }, z.core.$strip>]>;
14
+ export declare const browserControlActionSchema: z.ZodEnum<{
15
+ "browser.open": "browser.open";
16
+ "browser.snapshot": "browser.snapshot";
17
+ "browser.click": "browser.click";
18
+ "browser.type": "browser.type";
19
+ "browser.scroll": "browser.scroll";
20
+ "browser.back": "browser.back";
21
+ "browser.forward": "browser.forward";
22
+ "browser.inspect": "browser.inspect";
23
+ "browser.capture": "browser.capture";
24
+ "browser.resize": "browser.resize";
25
+ }>;
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+ import { BROWSER_CONTROL_ACTIONS } from "./service-providers.js";
3
+ /**
4
+ * Host-side parse of what a browser provider answers. `data` stays whatever
5
+ * the service produced: the host hands it to the agent as is, and only
6
+ * `browser.capture` is read further (the image the host writes to disk).
7
+ */
8
+ export const browserProviderResultSchema = z.union([
9
+ z.object({ ok: z.literal(true), data: z.object({}).passthrough() }),
10
+ z.object({ ok: z.literal(false), error: z.string().min(1).max(2_000) }),
11
+ ]);
12
+ export const browserControlActionSchema = z.enum(BROWSER_CONTROL_ACTIONS);