@openchamber/sdk 2.4.0 → 2.5.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 +23 -4
- package/DOCUMENTATION.md +29 -1
- package/GUEST_SERVICES.md +3 -2
- package/dist/contract.d.ts +46 -4
- package/dist/contract.js +18 -2
- package/dist/host.d.ts +19 -1
- package/dist/host.js +30 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/protocol.d.ts +51 -0
- package/dist/protocol.js +14 -6
- package/dist/service-provider-schemas.d.ts +1 -0
- package/dist/service-providers.d.ts +15 -2
- package/dist/service-providers.js +1 -0
- package/package.json +1 -1
package/API.md
CHANGED
|
@@ -74,6 +74,7 @@ Each returns an unsubscribe function. State subscriptions replay the last known
|
|
|
74
74
|
| `onItem(listener)` | `GuestItem | null` | The item this surface was opened for: the chip (`AttachIssueRequest`), a message (`GuestMessageItem`), or a session (`GuestSessionItem`); `null` from the rail icon or + menu |
|
|
75
75
|
| `onStatusControl(listener)` | `{ id, value? }` | Host Work Status control activation. Register synchronously; only status frames with advertised status controls receive events |
|
|
76
76
|
| `onPopoverClosed(listener)` | `{ id, reason }` | Dismissal of this frame's anchored popover. Events go to the owner, are not replayed, and identify the opening being closed |
|
|
77
|
+
| `onLateGenerateResult(listener)` | `LateGenerateResult` | A `generate` answer that came after the frame that asked was gone (the user closed the panel, switched project, or the frame reloaded). `{ request, ok: true, result: { text } }` or `{ request, ok: false, code, error }`; `request` is what that frame sent. Each one goes to one frame of the same extension, surface and project in the same window, once. Requires `features.lateGenerateResults`; see Late generate answers below |
|
|
77
78
|
| `onResolve(handler)` | `{ command, args }` → `Promise<AttachIssueRequest \| null>` | Answers a `contributes.commands` slash command. Return the chip to attach, `null` for nothing (the user sees a short notice), or throw (the message reaches the user). One handler at a time |
|
|
78
79
|
| `onAction(handler)` | `GuestActionItem` → `void \| Promise<void>` | Runs a `mode: "background"` message or session action. Register synchronously after `connectHost`. Await every operation; the frame is removed when the handler settles. Throw to report an error. One handler at a time; returns an unsubscribe function |
|
|
79
80
|
| `onFileOpen(listener)` | `FileEditorDocument` | File editors only: `{ path, name, readOnly, encoding: 'text', content }` or `{ ..., encoding: 'binary', bytes }` (a `Uint8Array`) of the file this frame edits. Replays the last file; the same file pushed again is not repeated. Registering also sends Cmd/Ctrl+S inside the frame to the host's save |
|
|
@@ -100,7 +101,7 @@ Each returns an unsubscribe function. State subscriptions replay the last known
|
|
|
100
101
|
|
|
101
102
|
`theme.tokens` includes `primaryText`, `successText`, `warningText`, `errorText`, and `infoText`. The host computes these for text on neutral surfaces and the UI kit's tinted controls. Keep using the base colors for fills and `primaryForeground` for text on a solid primary fill.
|
|
102
103
|
|
|
103
|
-
`features` is optional and only advertises implemented host operations: `deviceStorage?: true`, `statusControls?: true`, and `
|
|
104
|
+
`features` is optional and only advertises implemented host operations: `deviceStorage?: true`, `statusControls?: true`, `popovers?: true`, and `lateGenerateResults?: true`. Optional syntax tokens are `syntaxKeyword`, `syntaxString`, `syntaxNumber`, `syntaxFunction`, `syntaxType`, `syntaxComment`, `syntaxVariable`, and `syntaxOperator`. `applyHostReady` exposes each as `--oc-syntax-*` and the matching legacy `--syntax-*` name, and clears a missing token on a later snapshot. Use a semantic-token fallback in guest CSS when supporting older hosts.
|
|
104
105
|
|
|
105
106
|
`applyHostReady` exposes the computed colors as `--primary-text`, `--success-text`, `--warning-text`, `--error-text`, and `--info-text`, with matching `--oc-*-text` aliases. These are required theme fields. Apply each `onReady` snapshot to update them when the theme changes.
|
|
106
107
|
|
|
@@ -141,7 +142,7 @@ Access tokens never appear in `ready` or in request results.
|
|
|
141
142
|
| ----------------- | --------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------- |
|
|
142
143
|
| `toast` | `{ kind: 'info' \| 'success' \| 'error', message, copy?, dismiss?, persistent? }` | `Promise<void>` | Show a toast, optionally with host-owned Copy and OK buttons; see Toast buttons below |
|
|
143
144
|
| `openUrl` | `url: string` | `Promise<void>` | Open URL in the host |
|
|
144
|
-
| `openSurface` | `surfaceId: string` | `Promise<void>` |
|
|
145
|
+
| `openSurface` | `surfaceId: string` | `Promise<void>` | Show that surface; resolves once it is on screen, rejects `NO_DIRECTORY` or `UNSUPPORTED` when it cannot open |
|
|
145
146
|
| `writeClipboard` | `text: string` | `Promise<void>` | Copy in the host (1–32000 chars) |
|
|
146
147
|
| `compose` | `{ text, mode?: 'append' | 'replace' }` | `Promise<void>` |
|
|
147
148
|
| `attach` | `AttachIssueRequest` | `Promise<void>` | Composer chip, alongside other attached items |
|
|
@@ -165,7 +166,7 @@ Access tokens never appear in `ready` or in request results.
|
|
|
165
166
|
| `openPopover` | `GuestPopoverRequest` | `Promise<void>` | Open a sandboxed frame outside this frame's bounds, anchored to a rectangle in its viewport. Requires `features.popovers` |
|
|
166
167
|
| `closePopover` | `id, reason?: 'closed' \| 'escape'` | `Promise<void>` | Close this frame's matching opening, or the popover frame itself. A stale id cannot close a replacement |
|
|
167
168
|
| `setPopoverAnchorActive` | `id, active: boolean` | `Promise<void>` | Report anchor pointer/focus activity so the host can allow travel between the anchor and popover |
|
|
168
|
-
| `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
|
|
169
|
+
| `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 960 s |
|
|
169
170
|
| `reportFileChange` | `{ dirty, edited }` | `void` | File editors only. `dirty` against the last saved version; `edited` when the document itself changed, which holds autosave back until edits stop |
|
|
170
171
|
| `requestFileSave` | — | `void` | File editors only. Save now, as Cmd/Ctrl+S does |
|
|
171
172
|
| `reportFileUnsupported` | — | `void` | File editors only. This file cannot be opened here; the host shows its source and a notice |
|
|
@@ -288,6 +289,22 @@ The 20-second deadline covers loading and execution. At most eight background in
|
|
|
288
289
|
|
|
289
290
|
See [Actions without opening a panel](./README.md#actions-without-opening-a-panel) for a toast example.
|
|
290
291
|
|
|
292
|
+
### Late generate answers
|
|
293
|
+
|
|
294
|
+
A model can take minutes, and the user may close the panel, switch to another project, or reload the frame before `generate` answers. The request still runs to the server's deadline; there is no cancellation. When the answer arrives and the frame that asked is gone, the host keeps it. The next frame of the same extension on the same surface, in the same project and the same window, receives it through `onLateGenerateResult` as soon as it registers a listener; an answer that lands while such a frame already listens arrives at once. Each answer is handed over once. One that reaches the frame after its last listener was removed waits in the SDK for the next listener in that frame. A failed generation is kept as `{ ok: false, code, error }`, so the extension can say what happened. Hidden frames (background actions, and a panel entry the host loads only to answer a slash command) do not advertise the feature: they neither receive kept answers nor leave their own behind.
|
|
295
|
+
|
|
296
|
+
`request` is the trimmed `{ prompt, system?, maxOutputTokens? }` the frame sent; use it to tell which of your requests this answers. The extension decides what to show; the host shows nothing of its own.
|
|
297
|
+
|
|
298
|
+
The host keeps answers in memory for one hour, at most 8 per extension, the oldest dropped first. A page reload, an app restart or another window starts empty: the request belongs to the window that sent it. Disabling, removing or reinstalling the extension, or withdrawing its `model` approval, drops its kept answers. Older hosts do not advertise `features.lateGenerateResults` and never deliver them; the SDK then sends nothing. Extension frames load on web and desktop, directly or through the private relay; VS Code and mobile do not load extensions.
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
host.onLateGenerateResult((late) => {
|
|
302
|
+
if (late.request.system !== SUMMARY_SYSTEM) return;
|
|
303
|
+
if (late.ok) showSummary(late.result.text);
|
|
304
|
+
else showError(late.error);
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
291
308
|
### Anchored popovers
|
|
292
309
|
|
|
293
310
|
`openPopover` accepts `{ id, anchor: { x, y, width, height }, width, height, side?, focus?, data }`. The anchor uses CSS pixels relative to the calling iframe's viewport. Width is 160..640 pixels, height is 48..480 pixels, and `data` is JSON limited to 16,000 serialized characters. IDs contain 1..80 ASCII letters, digits, underscores or hyphens. Use a new id for each opening. `side` is `left`, `right`, `top` or `bottom`, defaulting to `left`; the host flips or shifts the card to fit the viewport. `focus` defaults to false, so pointer previews do not take keyboard focus.
|
|
@@ -358,7 +375,9 @@ Use `mountPopoverAnchor` from `@openchamber/sdk/ui` for DOM anchors. It measures
|
|
|
358
375
|
| `generate` prompt / system | 64 000 / 8 000 |
|
|
359
376
|
| `generate` `maxOutputTokens` | 4 000 |
|
|
360
377
|
| `generate` answer | 256 000 |
|
|
361
|
-
| `generate` timeout |
|
|
378
|
+
| `generate` timeout | 960 000 ms |
|
|
379
|
+
| Late `generate` answers kept per extension | 8 (the oldest goes first) |
|
|
380
|
+
| Late `generate` answer kept for | 3 600 000 ms |
|
|
362
381
|
|
|
363
382
|
|
|
364
383
|
---
|
package/DOCUMENTATION.md
CHANGED
|
@@ -28,6 +28,7 @@ Public author pages live in `packages/docs/content/docs/sdk.mdx`, `sdk/host.mdx`
|
|
|
28
28
|
- 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`.
|
|
29
29
|
|
|
30
30
|
- `contributes.statusSection` is `true` or `{ entry, title?, height?, defaultExpanded?, requiresProject? }` (`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). `defaultExpanded` defaults to true and only applies when the user has no saved expansion choice. `requiresProject` defaults to false; when true, the actual Work Status directory controls both presence and body rendering. `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. Host-rendered controls exist only after a mounted, expanded status frame calls `setStatusControls`; folding, hiding, or retiring the frame removes them. A project change clears header controls; publish them again from `onDirectory`. Workspace subscriptions remain active.
|
|
31
|
+
- `openSurface(surfaceId)` posts `open-surface { surfaceId }`; an id that is not a context panel surface is `HOST_REJECTED`. The host (`lib/guests/open-surface.ts`) opens it the way its rail icon does, under the frame's directory, or under the directory the rail shows when the frame has none (a Work Status section in a chat draft without a project has none of its own). The result follows what the panel shows afterwards: a surface already on screen stays open where the icon would toggle it closed; one that did not open (`chat`, which needs a session beside it) is `UNSUPPORTED`; no directory yet is `NO_DIRECTORY`. VS Code and mobile answer `UNSUPPORTED` explicitly. A resolved call means the surface is on screen.
|
|
31
32
|
- `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.
|
|
32
33
|
- 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.
|
|
33
34
|
- The UI kit's look follows the running app; its geometry follows the bundle. `src/ui/style.ts` splits the kit CSS into sizes (display, dimensions, spacing, type scale), which ship in each bundle, and `GUEST_UI_LOOK_CSS` (colours, fonts, borders, radii, shadows, hover/pressed/selected/focus), exported from `@openchamber/sdk/ui`. The server appends the look to the same `data-openchamber-guest-styles` element after the authored document, so it follows the kit's own `<style>` in the head and wins at equal specificity: an installed extension takes a restyle without a rebuild, while a size change reaches it only when its author rebuilds. Look selectors keep the specificity of the rules they override in older bundles, and `style.test.ts` fails when a geometry property enters the look. Rules for markup an older bundle lacks are keyed on what newer markup adds (a select option carries `data-mark` with its check mark; an option without it keeps the selection fill). Authors override kit rules with a more specific selector; an equal-specificity override in the head loses to the host's look.
|
|
@@ -53,7 +54,7 @@ Public author pages live in `packages/docs/content/docs/sdk.mdx`, `sdk/host.mdx`
|
|
|
53
54
|
- 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.
|
|
54
55
|
- `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, `file` as a Files view editor), `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`. `background` is the page the frame sits on, not a fixed theme slot: a frame in a docked context or Work Status panel gets the panel's colour (the theme's muted surface in dark themes, halfway between it and the chat colour in light ones) and `mutedSurface` swaps to the chat colour so soft blocks stay a step off the page (`usePageColors` in `packages/ui`). `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.
|
|
55
56
|
- `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.
|
|
56
|
-
- `contributes.capabilities` is a list of `prompt`, `sessions`, `files`, `model`, and/or `shells` (`DECLARED_GUEST_CAPABILITIES`). `model` gates `generate`: `{ prompt, system?, maxOutputTokens? }` → `{ text }`, posted with a `GUEST_GENERATE_TIMEOUT_MS` (
|
|
57
|
+
- `contributes.capabilities` is a list of `prompt`, `sessions`, `files`, `model`, and/or `shells` (`DECLARED_GUEST_CAPABILITIES`). `model` gates `generate`: `{ prompt, system?, maxOutputTokens? }` → `{ text }`, posted with a `GUEST_GENERATE_TIMEOUT_MS` (960s, above the Small Model's largest prompt-derived deadline) 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`, `filesystem`, and `origins` from a session action with `payload: ["messages"]`, `contributes.service`, `contributes.integration`, `contributes.filesystem`, and `contributes.origins`; 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`, the four file calls, and the three shell calls; a refusal is `NOT_GRANTED` (`NO_SERVICE` for the service).
|
|
57
58
|
- `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.
|
|
58
59
|
- `readFile` / `writeFile` / `listDir` / `stat` are `file-read` / `file-write` / `file-list` / `file-stat` on the wire. `guestFileScope` decides the grant: a relative path is `project` (capability `files`, joined to the open project; none open is `NO_DIRECTORY`), `/…` or `~/…` is `filesystem` (must match a declared pattern). The server compares canonical paths, so a symlink out of either tree is `BAD_PATH`, as is any `..` segment. Results are `{ content }`, `{ written: true }`, `{ entries: { name, kind }[] }`, and `{ kind, size, mtime }` where `kind` may be `missing`. `GUEST_FILE_PATH_MAX` (1024), `GUEST_FILE_CONTENT_MAX` (2 000 000 characters, read and write), `GUEST_FILE_LIST_MAX` (2000 entries). The client refuses an empty, oversized, NUL, or backslash path as `BAD_PATH` and oversized content as `FILE_TOO_LARGE` before posting; the host schema accepts only what `isGuestFilePath` does, so a dropped message cannot turn into `HOST_TIMEOUT`. Nothing about the write is logged, not even the path.
|
|
59
60
|
- `contributes.attach` is `true`, `false`, `"panel"`, `"dialog"`, or `{ "mode": "panel" | "dialog", "entry"?: string }`. `true` and `"panel"` add a + menu row that opens the rail. `"dialog"` opens a host window that mounts the same iframe. The object form's `entry` is an HTML page inside the package (same path rules as `panel.entry`, install checks it exists and that its scripts are built) that the dialog loads instead of `panel.entry`; it is only accepted with `mode: "dialog"`, and `resolveAttachEntry(contributes)` returns it or `null`. The public catalog row carries it as `attachEntry`. Clicking the guest's chip on the composer reopens that guest with the chip as `ready.item`: a dialog guest in the attach window, a panel guest on the rail. New Worktree shows the same dialog-mode guests next to GitHub and Linear. Omitted or `false` stays off those menus. Public 1.0 is web and desktop. VS Code and mobile mark the guest catalog `unsupported`. They do not show the row.
|
|
@@ -132,6 +133,33 @@ The SDK adds `HostActionMessage`, `GuestActionResultMessage`, `GuestActionItem`,
|
|
|
132
133
|
|
|
133
134
|
The runner cancels on runtime/catalog invalidation and marks the operation inactive synchronously, so queued messages cannot mutate host state during React teardown. Timeout, completion, frame-load failure, or host unmount clears the invocation and subscriptions. Unmount cleanup yields one microtask for Strict Mode's effect reattachment. Already accepted remote effects are not rolled back; no automatic retry. Web/desktop use the existing direct or relay frame loader; VS Code and mobile remain unsupported. `examples/hello-kit` demonstrates a background message-length toast.
|
|
134
135
|
|
|
136
|
+
## Late generate answers
|
|
137
|
+
|
|
138
|
+
`LateGenerateResult`, `ready.features.lateGenerateResults`, the guest notice `generate-late-listen` and the host push `generate-late` live here; `HostClient.onLateGenerateResult` sends the notice on registration and again on every `ready` while a listener exists. The UI host owns keeping: `packages/ui/src/lib/guests/generate-results.ts` holds the outcomes, `PluginPane` decides when a `generate` reply has no frame to go to and registers a listening frame.
|
|
139
|
+
|
|
140
|
+
- A `generate` reply goes to the frame that asked while the pane still owns that frame and no later document in it has said `hello` (`PluginPane` counts them). Otherwise it is kept. A reply that lands while a frame is navigating, before the new document's `hello`, still goes to the frame and is lost there.
|
|
141
|
+
- Hidden frames (slash-command resolvers, background actions) do not advertise the feature, ignore `generate-late-listen`, and keep nothing: they are no panel the user pressed a button in, and a resolver frame would otherwise take an answer meant for the visible panel.
|
|
142
|
+
- A kept outcome belongs to runtime, extension id, installation `storageId`, surface and project directory, and goes to a listening frame with the same owner, once. A frame that cannot take it leaves it kept.
|
|
143
|
+
- Bounds: `GUEST_GENERATE_LATE_KEEP_MS` (one hour), `GUEST_GENERATE_LATE_MAX` (8) per extension with the oldest dropped first, and 2 000 000 characters across all kept outcomes.
|
|
144
|
+
- A ready catalog of the same runtime that no longer has the extension active, approved for `model`, or with the same `storageId` drops its outcomes at once; a loading catalog or another runtime's catalog drops nothing.
|
|
145
|
+
- The request runs to the server's own deadline. No layer cancels it when the frame goes.
|
|
146
|
+
|
|
147
|
+
### Why keep the answer and not cancel the request
|
|
148
|
+
|
|
149
|
+
The user pressed Generate and asked for that text; closing a panel or switching project is not a request to throw it away. Cancelling end to end (issue #5040's first proposal) needs a cancel message, an abort path through the host bridge and the route, and still loses the answer. The maintainer chose delivery; the server's prompt-derived deadline stays the only limit on the work.
|
|
150
|
+
|
|
151
|
+
### Why the host keeps it in the window's memory and not on the server
|
|
152
|
+
|
|
153
|
+
The answer already comes back to the window that sent the request, and that window is where the user pressed the button, so the window can hand it to the next frame with no new route and no copy of model output on disk. A reload or another window starts empty; the request belongs to the window that sent it, and an app restart ends the request anyway. A server store would deliver to whichever client opened the panel first, a phone or another window.
|
|
154
|
+
|
|
155
|
+
### Why the owner includes surface and project
|
|
156
|
+
|
|
157
|
+
The outcome should appear where the user pressed Generate. Panel tabs are kept per project, so a panel in another project or an attach dialog of the same extension is somewhere else. A frame that follows the user across projects reads its current directory at each delivery.
|
|
158
|
+
|
|
159
|
+
### Why a listen notice and not a push to every reconnecting frame
|
|
160
|
+
|
|
161
|
+
An extension built against an older SDK has no listener, and a push it ignores would count as delivered. The frame asks only when its code registered `onLateGenerateResult`, and the host only advertises the feature it implements, so old guests and old hosts keep working unchanged.
|
|
162
|
+
|
|
135
163
|
## Not here yet
|
|
136
164
|
|
|
137
165
|
`issues.search` / `issues.get` on the host, a public OAuth broker, a second `host.provider`, file watching or globbing from the guest (it gets `listDir` and `stat`, not a search). `attach` is the chip. `startSession` creates a session and writes that snapshot. `sessionLink` writes that snapshot on the current session. `prompt` writes or sends on that session. `onSessionLifecycle` is the live phase. Search and the task list live in the guest. `@openchamber/sdk/ui` is the drawing kit: buttons, fields, select, list, tabs, badges, banners, menu, and `mountText`. No issue page, card, attach picker, or PR window; the guest composes those from the blocks. `mountText` lifts `` and `[label](https://…)` and hands link clicks to `onOpenUrl`. `ready.theme.tokens` carries the host surface and interactive colors. Call `applyHostReady` before mount.
|
package/GUEST_SERVICES.md
CHANGED
|
@@ -172,7 +172,7 @@ Ship `service/main.js` already built. Same packaging rule as `panel/main.js`.
|
|
|
172
172
|
|
|
173
173
|
## Browser provider (`provides: ["browser"]`)
|
|
174
174
|
|
|
175
|
-
The host's `openchamber_web` tool gives agents
|
|
175
|
+
The host's `openchamber_web` tool gives agents eleven `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.
|
|
176
176
|
|
|
177
177
|
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.
|
|
178
178
|
|
|
@@ -219,8 +219,9 @@ Parameters the host sends and `data` the service answers; types are exported fro
|
|
|
219
219
|
| `browser.inspect` | `selector` | `selector`, `tag`, `label`, `bounds`, `inViewport`, `styles` (computed, as strings) |
|
|
220
220
|
| `browser.capture` | `label?` | `base64`, `mime`, `width`, `height`, `url`, `title`, `viewport`; the host writes the file into the project and returns its path |
|
|
221
221
|
| `browser.resize` | `viewport` (`mobile`/`tablet`/`desktop`/`fill`) | `viewport` |
|
|
222
|
+
| `browser.close` | none (see `tabId` below) | `closed: true`, optional `tabId`; a provider with a single page answers `ok: false` instead |
|
|
222
223
|
|
|
223
|
-
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.
|
|
224
|
+
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. `browser.close` is the exception to "the tab the user sees": it closes a tab the agent opened, so without `tabId` it closes the one this caller opened most recently and refuses a tab it did not open.
|
|
224
225
|
|
|
225
226
|
`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.
|
|
226
227
|
|
package/dist/contract.d.ts
CHANGED
|
@@ -167,6 +167,22 @@ export type GenerateRequest = {
|
|
|
167
167
|
export type GenerateResult = {
|
|
168
168
|
text: string;
|
|
169
169
|
};
|
|
170
|
+
/**
|
|
171
|
+
* The outcome of a `generate` whose frame was gone when the model answered:
|
|
172
|
+
* the panel closed or its frame reloaded first. `request` is what that frame
|
|
173
|
+
* sent. The host keeps it for a frame of the same extension on the same
|
|
174
|
+
* surface and project, and hands it over once (`onLateGenerateResult`).
|
|
175
|
+
*/
|
|
176
|
+
export type LateGenerateResult = {
|
|
177
|
+
request: GenerateRequest;
|
|
178
|
+
} & ({
|
|
179
|
+
ok: true;
|
|
180
|
+
result: GenerateResult;
|
|
181
|
+
} | {
|
|
182
|
+
ok: false;
|
|
183
|
+
code: HostRequestErrorCode;
|
|
184
|
+
error: string;
|
|
185
|
+
});
|
|
170
186
|
export type HostResultPayload = GuestStorageResult | GuestWorkspaceSnapshot | GuestShellOutputResult | GuestShellStopResult | GuestRequestResult | StartSessionResult | PromptResult | ServiceStatusResult | FileReadResult | FileWriteResult | FileListResult | FileStatResult | GenerateResult;
|
|
171
187
|
export declare const isStartSessionResult: (value: HostResultPayload | undefined) => value is StartSessionResult;
|
|
172
188
|
export declare const isPromptResult: (value: HostResultPayload | undefined) => value is PromptResult;
|
|
@@ -193,6 +209,8 @@ export type HostReadyContext = {
|
|
|
193
209
|
deviceStorage?: true;
|
|
194
210
|
statusControls?: true;
|
|
195
211
|
popovers?: true;
|
|
212
|
+
/** The host keeps `generate` answers that arrive after their frame is gone (`onLateGenerateResult`). */
|
|
213
|
+
lateGenerateResults?: true;
|
|
196
214
|
};
|
|
197
215
|
};
|
|
198
216
|
export type GuestItemRole = 'user' | 'assistant';
|
|
@@ -352,8 +370,23 @@ export declare const GUEST_GENERATE_SYSTEM_MAX = 8000;
|
|
|
352
370
|
export declare const GUEST_GENERATE_OUTPUT_TOKENS_MAX = 4000;
|
|
353
371
|
/** Characters in a `generate` answer. */
|
|
354
372
|
export declare const GUEST_GENERATE_TEXT_MAX = 256000;
|
|
355
|
-
/**
|
|
356
|
-
|
|
373
|
+
/**
|
|
374
|
+
* How long `generate` waits for the model before `HOST_TIMEOUT`.
|
|
375
|
+
*
|
|
376
|
+
* Must exceed the largest deadline the Small Model server can derive for a
|
|
377
|
+
* guest-shaped request, plus margin for the iframe → host bridge → HTTP chain.
|
|
378
|
+
* The server no longer cuts at a flat 60 s: it budgets 60 s base plus 40 ms per
|
|
379
|
+
* estimated token (~4 chars/token), capped at 900 s
|
|
380
|
+
* (`generationBudgetMs` in `packages/web/server/lib/small-model/index.js`,
|
|
381
|
+
* issue #5017). At the guest limits (`GUEST_GENERATE_PROMPT_MAX` +
|
|
382
|
+
* `GUEST_GENERATE_SYSTEM_MAX` = 72 000 chars ≈ 18 000 tokens) that is 780 s,
|
|
383
|
+
* so 960 s leaves the chain ~3 minutes.
|
|
384
|
+
*/
|
|
385
|
+
export declare const GUEST_GENERATE_TIMEOUT_MS = 960000;
|
|
386
|
+
/** How long the host keeps a `LateGenerateResult` nobody has taken. */
|
|
387
|
+
export declare const GUEST_GENERATE_LATE_KEEP_MS: number;
|
|
388
|
+
/** `LateGenerateResult`s the host keeps per extension; a newer one drops the oldest. */
|
|
389
|
+
export declare const GUEST_GENERATE_LATE_MAX = 8;
|
|
357
390
|
/** Characters of one message's text on a `GuestMessageItem` or a `GuestSessionItem` message. */
|
|
358
391
|
export declare const GUEST_ITEM_MESSAGE_TEXT_MAX = 200000;
|
|
359
392
|
/** `JSON.stringify` length ceiling for a `GuestSessionItem`; the host drops the oldest messages to stay under it. */
|
|
@@ -481,6 +514,11 @@ export type HostFileSavedMessage = Envelope & {
|
|
|
481
514
|
version: string;
|
|
482
515
|
};
|
|
483
516
|
};
|
|
517
|
+
/** A kept `generate` outcome, pushed once to a frame that sent `generate-late-listen`. */
|
|
518
|
+
export type HostGenerateLateMessage = Envelope & {
|
|
519
|
+
type: 'generate-late';
|
|
520
|
+
payload: LateGenerateResult;
|
|
521
|
+
};
|
|
484
522
|
export type HostResultMessage = Envelope & {
|
|
485
523
|
type: 'result';
|
|
486
524
|
id: string;
|
|
@@ -502,7 +540,7 @@ export type HostShellsMessage = Envelope & {
|
|
|
502
540
|
export type HostMessage = (Envelope & {
|
|
503
541
|
type: 'workspace';
|
|
504
542
|
payload: GuestWorkspaceUpdate;
|
|
505
|
-
}) | HostShellsMessage | HostReadyMessage | HostDirectoryMessage | HostSessionMessage | HostConnectionMessage | HostSettingsMessage | HostSessionLifecycleMessage | HostItemMessage | HostStatusControlEventMessage | HostPopoverClosedMessage | HostResolveMessage | HostActionMessage | HostFileOpenMessage | HostFileSnapshotMessage | HostFileSavedMessage | HostResultMessage;
|
|
543
|
+
}) | HostShellsMessage | HostReadyMessage | HostDirectoryMessage | HostSessionMessage | HostConnectionMessage | HostSettingsMessage | HostSessionLifecycleMessage | HostItemMessage | HostStatusControlEventMessage | HostPopoverClosedMessage | HostResolveMessage | HostActionMessage | HostFileOpenMessage | HostFileSnapshotMessage | HostFileSavedMessage | HostGenerateLateMessage | HostResultMessage;
|
|
506
544
|
type GuestCall<Type extends string, Payload = never> = Envelope & {
|
|
507
545
|
type: Type;
|
|
508
546
|
id: string;
|
|
@@ -586,6 +624,10 @@ export type GuestFileSaveMessage = Envelope & {
|
|
|
586
624
|
export type GuestFileUnsupportedMessage = Envelope & {
|
|
587
625
|
type: 'file-unsupported';
|
|
588
626
|
};
|
|
627
|
+
/** This frame takes kept `generate` outcomes; the host pushes them as `generate-late`. Fire and forget, repeated on every `ready`. */
|
|
628
|
+
export type GuestGenerateLateListenMessage = Envelope & {
|
|
629
|
+
type: 'generate-late-listen';
|
|
630
|
+
};
|
|
589
631
|
export type GuestShellsSubscribeMessage = GuestCall<'shells-subscribe', GuestShellsSubscription>;
|
|
590
632
|
export type GuestShellsUnsubscribeMessage = GuestCall<'shells-unsubscribe', {
|
|
591
633
|
subscriptionId: string;
|
|
@@ -598,7 +640,7 @@ export type GuestMessage = GuestCall<'workspace-read', GuestWorkspaceQuery> | Gu
|
|
|
598
640
|
subscriptionId: string;
|
|
599
641
|
}> | GuestShellsSubscribeMessage | GuestShellsUnsubscribeMessage | GuestShellOutputMessage | GuestShellStopMessage | GuestCall<'storage', GuestStorageRequest> | GuestCall<'open-session', {
|
|
600
642
|
sessionId: string;
|
|
601
|
-
}> | GuestHelloMessage | GuestToastMessage | GuestOpenUrlMessage | GuestOpenSurfaceMessage | GuestClipboardWriteMessage | GuestComposeMessage | GuestAttachMessage | GuestStartSessionMessage | GuestPromptMessage | GuestSessionLinkMessage | GuestCloseMessage | GuestOauthStartMessage | GuestOauthDisconnectMessage | GuestRequestMessage | GuestServiceRequestMessage | GuestServiceStatusMessage | GuestFileReadMessage | GuestFileWriteMessage | GuestFileListMessage | GuestFileStatMessage | GuestGenerateMessage | GuestBadgeMessage | GuestResizeMessage | GuestOpenCommitMessage | GuestStatusControlsMessage | GuestPopoverOpenMessage | GuestPopoverCloseMessage | GuestPopoverAnchorMessage | GuestActionResultMessage | GuestResolveResultMessage | GuestFileSnapshotResultMessage | GuestFileChangeMessage | GuestFileSaveMessage | GuestFileUnsupportedMessage;
|
|
643
|
+
}> | GuestHelloMessage | GuestToastMessage | GuestOpenUrlMessage | GuestOpenSurfaceMessage | GuestClipboardWriteMessage | GuestComposeMessage | GuestAttachMessage | GuestStartSessionMessage | GuestPromptMessage | GuestSessionLinkMessage | GuestCloseMessage | GuestOauthStartMessage | GuestOauthDisconnectMessage | GuestRequestMessage | GuestServiceRequestMessage | GuestServiceStatusMessage | GuestFileReadMessage | GuestFileWriteMessage | GuestFileListMessage | GuestFileStatMessage | GuestGenerateMessage | GuestBadgeMessage | GuestResizeMessage | GuestOpenCommitMessage | GuestStatusControlsMessage | GuestPopoverOpenMessage | GuestPopoverCloseMessage | GuestPopoverAnchorMessage | GuestActionResultMessage | GuestResolveResultMessage | GuestFileSnapshotResultMessage | GuestFileChangeMessage | GuestFileSaveMessage | GuestFileUnsupportedMessage | GuestGenerateLateListenMessage;
|
|
602
644
|
export declare const isServiceStatusResult: (value: HostResultPayload | undefined) => value is ServiceStatusResult;
|
|
603
645
|
export declare const isGuestRequestResult: (value: HostResultPayload | undefined) => value is GuestRequestResult;
|
|
604
646
|
export declare const isFileReadResult: (value: HostResultPayload | undefined) => value is FileReadResult;
|
package/dist/contract.js
CHANGED
|
@@ -50,8 +50,23 @@ export const GUEST_GENERATE_SYSTEM_MAX = 8_000;
|
|
|
50
50
|
export const GUEST_GENERATE_OUTPUT_TOKENS_MAX = 4_000;
|
|
51
51
|
/** Characters in a `generate` answer. */
|
|
52
52
|
export const GUEST_GENERATE_TEXT_MAX = 256_000;
|
|
53
|
-
/**
|
|
54
|
-
|
|
53
|
+
/**
|
|
54
|
+
* How long `generate` waits for the model before `HOST_TIMEOUT`.
|
|
55
|
+
*
|
|
56
|
+
* Must exceed the largest deadline the Small Model server can derive for a
|
|
57
|
+
* guest-shaped request, plus margin for the iframe → host bridge → HTTP chain.
|
|
58
|
+
* The server no longer cuts at a flat 60 s: it budgets 60 s base plus 40 ms per
|
|
59
|
+
* estimated token (~4 chars/token), capped at 900 s
|
|
60
|
+
* (`generationBudgetMs` in `packages/web/server/lib/small-model/index.js`,
|
|
61
|
+
* issue #5017). At the guest limits (`GUEST_GENERATE_PROMPT_MAX` +
|
|
62
|
+
* `GUEST_GENERATE_SYSTEM_MAX` = 72 000 chars ≈ 18 000 tokens) that is 780 s,
|
|
63
|
+
* so 960 s leaves the chain ~3 minutes.
|
|
64
|
+
*/
|
|
65
|
+
export const GUEST_GENERATE_TIMEOUT_MS = 960_000;
|
|
66
|
+
/** How long the host keeps a `LateGenerateResult` nobody has taken. */
|
|
67
|
+
export const GUEST_GENERATE_LATE_KEEP_MS = 60 * 60_000;
|
|
68
|
+
/** `LateGenerateResult`s the host keeps per extension; a newer one drops the oldest. */
|
|
69
|
+
export const GUEST_GENERATE_LATE_MAX = 8;
|
|
55
70
|
/** Characters of one message's text on a `GuestMessageItem` or a `GuestSessionItem` message. */
|
|
56
71
|
export const GUEST_ITEM_MESSAGE_TEXT_MAX = 200_000;
|
|
57
72
|
/** `JSON.stringify` length ceiling for a `GuestSessionItem`; the host drops the oldest messages to stay under it. */
|
|
@@ -217,6 +232,7 @@ const HOST_PUSH_TYPES = new Set([
|
|
|
217
232
|
'ready', 'directory', 'session', 'connection', 'settings', 'session-lifecycle', 'item', 'resolve', 'action',
|
|
218
233
|
'status-control-event', 'popover-closed',
|
|
219
234
|
'file-open', 'file-snapshot', 'file-saved',
|
|
235
|
+
'generate-late',
|
|
220
236
|
]);
|
|
221
237
|
const asWireRecord = (data) => (Object(data) === data ? data : null);
|
|
222
238
|
const isNonEmptyString = (value) => (String(value) === value && value.length > 0);
|
package/dist/host.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { GuestStatusControl, GuestStatusControlEvent } from './status-contr
|
|
|
3
3
|
import type { JsonValue } from './contract.ts';
|
|
4
4
|
import { type GuestPopoverClosedEvent, type GuestPopoverRequest } from './popover.ts';
|
|
5
5
|
import { type FileEditorChange, type FileEditorDocument, type FileEditorSnapshot, type FileSnapshotPurpose } from './file-editor.ts';
|
|
6
|
-
import { type AttachIssueRequest, type GuestActionItem, type ComposeRequest, type PromptRequest, type PromptResult, type SessionLifecycleEvent, type StartSessionRequest, type GuestConnection, type GuestItem, type GuestMessage, type GuestRequest, type GuestRequestResult, type GuestSettings, type HostReadyContext, type HostRequestErrorCode, type ResolveRequest, type SessionSnapshot, type StartSessionResult, type ToastRequest, type ServiceStatusResult, type FileListResult, type FileReadResult, type FileStatResult, type FileWriteResult, type GenerateRequest, type GenerateResult } from './contract.ts';
|
|
6
|
+
import { type AttachIssueRequest, type GuestActionItem, type ComposeRequest, type PromptRequest, type PromptResult, type SessionLifecycleEvent, type StartSessionRequest, type GuestConnection, type GuestItem, type GuestMessage, type GuestRequest, type GuestRequestResult, type GuestSettings, type HostReadyContext, type HostRequestErrorCode, type ResolveRequest, type SessionSnapshot, type StartSessionResult, type ToastRequest, type ServiceStatusResult, type FileListResult, type FileReadResult, type FileStatResult, type FileWriteResult, type GenerateRequest, type GenerateResult, type LateGenerateResult } from './contract.ts';
|
|
7
7
|
import { type GuestRunningShellsSnapshot, type GuestShellOutputResult, type GuestShellStopResult, type GuestShellsScope } from './shells.ts';
|
|
8
8
|
export type HostFrame = {
|
|
9
9
|
addEventListener: Window['addEventListener'];
|
|
@@ -82,6 +82,11 @@ export type HostClient = {
|
|
|
82
82
|
* Diff view `UNSUPPORTED`.
|
|
83
83
|
*/
|
|
84
84
|
openCommit: (sha: string) => Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Show a context panel surface (`git`, or `plugin:<panel id>`). Resolves once
|
|
87
|
+
* it is on screen; one already open stays open. No chat or project yet is
|
|
88
|
+
* `NO_DIRECTORY`, a surface that cannot open here `UNSUPPORTED`.
|
|
89
|
+
*/
|
|
85
90
|
openSurface: (surfaceId: string) => Promise<void>;
|
|
86
91
|
writeClipboard: (text: string) => Promise<void>;
|
|
87
92
|
compose: (request: ComposeRequest) => Promise<void>;
|
|
@@ -121,6 +126,19 @@ export type HostClient = {
|
|
|
121
126
|
* failed is `MODEL_FAILED`. Waits up to `GUEST_GENERATE_TIMEOUT_MS`.
|
|
122
127
|
*/
|
|
123
128
|
generate: (request: GenerateRequest) => Promise<GenerateResult>;
|
|
129
|
+
/**
|
|
130
|
+
* `generate` outcomes that arrived after the frame that asked was gone:
|
|
131
|
+
* the user closed the panel, or the frame reloaded, before the model
|
|
132
|
+
* answered. The host keeps each one for a frame of this extension on the
|
|
133
|
+
* same surface and project and hands it over once, up to
|
|
134
|
+
* `GUEST_GENERATE_LATE_MAX` per extension for `GUEST_GENERATE_LATE_KEEP_MS`.
|
|
135
|
+
* One that lands while this frame listens arrives at once. Register when
|
|
136
|
+
* the frame can use them; the host hands nothing over before the first
|
|
137
|
+
* listener, or on a host without `ready.features.lateGenerateResults`.
|
|
138
|
+
* One that arrives after the last listener was removed waits in this frame
|
|
139
|
+
* for the next listener.
|
|
140
|
+
*/
|
|
141
|
+
onLateGenerateResult: (listener: (result: LateGenerateResult) => void) => () => void;
|
|
124
142
|
/** Number on this guest's rail icon (0 to `GUEST_BADGE_MAX`); `null` clears it. Opening the panel clears it too. */
|
|
125
143
|
setBadge: (count: number | null) => Promise<void>;
|
|
126
144
|
/**
|
package/dist/host.js
CHANGED
|
@@ -51,6 +51,10 @@ export const connectHost = (options = {}) => {
|
|
|
51
51
|
const itemListeners = new Set();
|
|
52
52
|
const statusControlListeners = new Set();
|
|
53
53
|
const popoverClosedListeners = new Set();
|
|
54
|
+
const lateGenerateListeners = new Set();
|
|
55
|
+
// Answers the host handed over after the last listener left; each one comes
|
|
56
|
+
// from a `generate` this extension made.
|
|
57
|
+
const unclaimedLateGenerate = [];
|
|
54
58
|
let resolveHandler = null;
|
|
55
59
|
let actionHandler = null;
|
|
56
60
|
const fileOpenListeners = new Set();
|
|
@@ -79,6 +83,14 @@ export const connectHost = (options = {}) => {
|
|
|
79
83
|
const post = (message) => {
|
|
80
84
|
target.parent.postMessage(message, '*');
|
|
81
85
|
};
|
|
86
|
+
// Sent again on every `ready`: the host forgets a listening frame when it
|
|
87
|
+
// re-attaches, and the notice costs nothing to repeat.
|
|
88
|
+
const listenForLateGenerate = () => {
|
|
89
|
+
if (disposed || target.parent === target || lateGenerateListeners.size === 0
|
|
90
|
+
|| lastReady?.features?.lateGenerateResults !== true)
|
|
91
|
+
return;
|
|
92
|
+
post({ channel: OPENCHAMBER_SDK_CHANNEL, v: OPENCHAMBER_SDK_API_VERSION, type: 'generate-late-listen' });
|
|
93
|
+
};
|
|
82
94
|
// One listener that throws must not starve the ones after it; the error
|
|
83
95
|
// surfaces on the console the way an event handler's would.
|
|
84
96
|
const emit = (listeners, value) => {
|
|
@@ -123,6 +135,15 @@ export const connectHost = (options = {}) => {
|
|
|
123
135
|
emit(connectionListeners, message.payload.connection);
|
|
124
136
|
emit(settingsListeners, message.payload.settings);
|
|
125
137
|
emit(itemListeners, message.payload.item);
|
|
138
|
+
listenForLateGenerate();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (message.type === 'generate-late') {
|
|
142
|
+
// The host already let go of it; keep it for the next listener.
|
|
143
|
+
if (lateGenerateListeners.size === 0)
|
|
144
|
+
unclaimedLateGenerate.push(message.payload);
|
|
145
|
+
else
|
|
146
|
+
emit(lateGenerateListeners, message.payload);
|
|
126
147
|
return;
|
|
127
148
|
}
|
|
128
149
|
if (message.type === 'directory') {
|
|
@@ -864,6 +885,13 @@ export const connectHost = (options = {}) => {
|
|
|
864
885
|
return result;
|
|
865
886
|
});
|
|
866
887
|
},
|
|
888
|
+
onLateGenerateResult: (listener) => {
|
|
889
|
+
lateGenerateListeners.add(listener);
|
|
890
|
+
for (const result of unclaimedLateGenerate.splice(0))
|
|
891
|
+
emit([listener], result);
|
|
892
|
+
listenForLateGenerate();
|
|
893
|
+
return () => { lateGenerateListeners.delete(listener); };
|
|
894
|
+
},
|
|
867
895
|
setBadge: (count) => request({
|
|
868
896
|
channel: OPENCHAMBER_SDK_CHANNEL,
|
|
869
897
|
v: OPENCHAMBER_SDK_API_VERSION,
|
|
@@ -944,6 +972,8 @@ export const connectHost = (options = {}) => {
|
|
|
944
972
|
itemListeners.clear();
|
|
945
973
|
statusControlListeners.clear();
|
|
946
974
|
popoverClosedListeners.clear();
|
|
975
|
+
lateGenerateListeners.clear();
|
|
976
|
+
unclaimedLateGenerate.length = 0;
|
|
947
977
|
},
|
|
948
978
|
};
|
|
949
979
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -18,9 +18,9 @@ export type { HostClient, HostClientOptions, HostFrame } from './host.ts';
|
|
|
18
18
|
export { GUEST_POPOVER_COORDINATE_MAX, GUEST_POPOVER_DATA_DEPTH_MAX, GUEST_POPOVER_DATA_MAX, GUEST_POPOVER_HEIGHT_MAX, GUEST_POPOVER_HEIGHT_MIN, GUEST_POPOVER_ID, GUEST_POPOVER_SIDES, GUEST_POPOVER_WIDTH_MAX, GUEST_POPOVER_WIDTH_MIN, GUEST_POPOVER_CLOSE_REASONS, isGuestPopoverRequest, } from './popover.ts';
|
|
19
19
|
export type { GuestPopoverAnchor, GuestPopoverClosedEvent, GuestPopoverContext, GuestPopoverRequest, GuestPopoverSide } from './popover.ts';
|
|
20
20
|
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';
|
|
21
|
-
export type { BadgeRequest, ActionResultPayload, GuestActionItem, GuestActionResultMessage, HostActionMessage, HostFileOpenMessage, HostFileSnapshotMessage, HostFileSavedMessage, GuestFileSnapshotResultMessage, GuestFileChangeMessage, GuestFileSaveMessage, GuestFileUnsupportedMessage, GuestBadgeMessage, GuestResizeMessage, GuestOpenCommitMessage, GuestStatusControlsMessage, GuestPopoverAnchorMessage, GuestPopoverCloseMessage, GuestPopoverOpenMessage, 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, GuestShellOutputMessage, GuestShellsSubscribeMessage, GuestShellsUnsubscribeMessage, GuestShellStopMessage, GuestAttachMessage, GuestClipboardWriteMessage, GuestCloseMessage, GuestComposeMessage, GuestConnection, GuestHelloMessage, GuestMessage, GuestOauthDisconnectMessage, GuestOauthStartMessage, GuestOpenSurfaceMessage, GuestOpenUrlMessage, GuestRequest, GuestRequestMessage, GuestRequestMethod, GuestPromptMessage, GuestSessionLinkMessage, GuestStartSessionMessage, GuestRequestResult, GuestSettings, GuestToastMessage, GuestHostSurface, HostConnectionMessage, HostDirectoryMessage, HostItemMessage, HostStatusControlEventMessage, HostPopoverClosedMessage, HostMessage, HostReadyContext, HostReadyMessage, HostResultMessage, HostSessionLifecycleMessage, HostSessionMessage, HostSettingsMessage, HostShellsMessage, HostTheme, HostThemeMode, HostThemeTokens, SessionSnapshot, ToastKind, ToastRequest, } from './contract.ts';
|
|
22
|
-
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';
|
|
21
|
+
export type { BadgeRequest, ActionResultPayload, GuestActionItem, GuestActionResultMessage, HostActionMessage, HostFileOpenMessage, HostFileSnapshotMessage, HostFileSavedMessage, HostGenerateLateMessage, GuestFileSnapshotResultMessage, GuestFileChangeMessage, GuestFileSaveMessage, GuestFileUnsupportedMessage, GuestGenerateLateListenMessage, GuestBadgeMessage, GuestResizeMessage, GuestOpenCommitMessage, GuestStatusControlsMessage, GuestPopoverAnchorMessage, GuestPopoverCloseMessage, GuestPopoverOpenMessage, OpenCommitRequest, ResizeRequest, GuestItem, GuestItemRole, GuestMessageItem, GuestResolveResultMessage, GuestSessionItem, GuestSessionItemMessage, HostResolveMessage, ResolveRequest, ResolveResultPayload, FileListEntry, FileListRequest, FileListResult, FileReadRequest, FileReadResult, FileStatRequest, FileStatResult, FileWriteRequest, FileWriteResult, GenerateRequest, GenerateResult, LateGenerateResult, 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, GuestShellOutputMessage, GuestShellsSubscribeMessage, GuestShellsUnsubscribeMessage, GuestShellStopMessage, GuestAttachMessage, GuestClipboardWriteMessage, GuestCloseMessage, GuestComposeMessage, GuestConnection, GuestHelloMessage, GuestMessage, GuestOauthDisconnectMessage, GuestOauthStartMessage, GuestOpenSurfaceMessage, GuestOpenUrlMessage, GuestRequest, GuestRequestMessage, GuestRequestMethod, GuestPromptMessage, GuestSessionLinkMessage, GuestStartSessionMessage, GuestRequestResult, GuestSettings, GuestToastMessage, GuestHostSurface, HostConnectionMessage, HostDirectoryMessage, HostItemMessage, HostStatusControlEventMessage, HostPopoverClosedMessage, HostMessage, HostReadyContext, HostReadyMessage, HostResultMessage, HostSessionLifecycleMessage, HostSessionMessage, HostSettingsMessage, HostShellsMessage, HostTheme, HostThemeMode, HostThemeTokens, SessionSnapshot, ToastKind, ToastRequest, } from './contract.ts';
|
|
22
|
+
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_GENERATE_LATE_KEEP_MS, GUEST_GENERATE_LATE_MAX, 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';
|
|
23
23
|
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';
|
|
24
|
-
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';
|
|
24
|
+
export type { BrowserCaptureData, BrowserCaptureParameters, BrowserClickData, BrowserClickParameters, BrowserCloseData, BrowserCloseParameters, 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';
|
|
25
25
|
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';
|
|
26
26
|
export type { SurfaceClipboardAnswer, SurfaceControlNotice, SurfaceController, SurfaceFrameMime, SurfaceInputBatch, SurfaceInputEvent, SurfaceKeyEvent, SurfaceModifiers, SurfacePointerEvent, SurfaceResizeAnswer, SurfaceResizeRequest, SurfaceTextEvent, SurfaceWheelEvent, } from './service-surface.ts';
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,6 @@ export { GUEST_FILE_EDITORS_MAX, GUEST_FILE_EDITOR_CONTENT_MAX, GUEST_FILE_EDITO
|
|
|
10
10
|
export { connectHost, HostRequestError } from './host.js';
|
|
11
11
|
export { GUEST_POPOVER_COORDINATE_MAX, GUEST_POPOVER_DATA_DEPTH_MAX, GUEST_POPOVER_DATA_MAX, GUEST_POPOVER_HEIGHT_MAX, GUEST_POPOVER_HEIGHT_MIN, GUEST_POPOVER_ID, GUEST_POPOVER_SIDES, GUEST_POPOVER_WIDTH_MAX, GUEST_POPOVER_WIDTH_MIN, GUEST_POPOVER_CLOSE_REASONS, isGuestPopoverRequest, } from './popover.js';
|
|
12
12
|
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';
|
|
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.js';
|
|
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_GENERATE_LATE_KEEP_MS, GUEST_GENERATE_LATE_MAX, 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';
|
|
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.js';
|
|
15
15
|
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';
|
package/dist/protocol.d.ts
CHANGED
|
@@ -258,6 +258,7 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
258
258
|
deviceStorage: z.ZodOptional<z.ZodLiteral<true>>;
|
|
259
259
|
statusControls: z.ZodOptional<z.ZodLiteral<true>>;
|
|
260
260
|
popovers: z.ZodOptional<z.ZodLiteral<true>>;
|
|
261
|
+
lateGenerateResults: z.ZodOptional<z.ZodLiteral<true>>;
|
|
261
262
|
}, z.core.$strip>>;
|
|
262
263
|
}, z.core.$strip>;
|
|
263
264
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -924,6 +925,52 @@ export declare const hostMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
924
925
|
payload: z.ZodObject<{
|
|
925
926
|
version: z.ZodString;
|
|
926
927
|
}, z.core.$strip>;
|
|
928
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
929
|
+
channel: z.ZodLiteral<"openchamber.sdk">;
|
|
930
|
+
v: z.ZodLiteral<1>;
|
|
931
|
+
type: z.ZodLiteral<"generate-late">;
|
|
932
|
+
payload: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
933
|
+
request: z.ZodObject<{
|
|
934
|
+
prompt: z.ZodString;
|
|
935
|
+
system: z.ZodOptional<z.ZodString>;
|
|
936
|
+
maxOutputTokens: z.ZodOptional<z.ZodNumber>;
|
|
937
|
+
}, z.core.$strip>;
|
|
938
|
+
ok: z.ZodLiteral<true>;
|
|
939
|
+
result: z.ZodObject<{
|
|
940
|
+
text: z.ZodString;
|
|
941
|
+
}, z.core.$strip>;
|
|
942
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
943
|
+
request: z.ZodObject<{
|
|
944
|
+
prompt: z.ZodString;
|
|
945
|
+
system: z.ZodOptional<z.ZodString>;
|
|
946
|
+
maxOutputTokens: z.ZodOptional<z.ZodNumber>;
|
|
947
|
+
}, z.core.$strip>;
|
|
948
|
+
ok: z.ZodLiteral<false>;
|
|
949
|
+
code: z.ZodEnum<{
|
|
950
|
+
BAD_PATH: "BAD_PATH";
|
|
951
|
+
DENIED: "DENIED";
|
|
952
|
+
DISABLED: "DISABLED";
|
|
953
|
+
DISCONNECTED: "DISCONNECTED";
|
|
954
|
+
FILE_TOO_LARGE: "FILE_TOO_LARGE";
|
|
955
|
+
HOST_REJECTED: "HOST_REJECTED";
|
|
956
|
+
HOST_TIMEOUT: "HOST_TIMEOUT";
|
|
957
|
+
HOST_UNAVAILABLE: "HOST_UNAVAILABLE";
|
|
958
|
+
MODEL_FAILED: "MODEL_FAILED";
|
|
959
|
+
NOT_FOUND: "NOT_FOUND";
|
|
960
|
+
NOT_GRANTED: "NOT_GRANTED";
|
|
961
|
+
NO_DIRECTORY: "NO_DIRECTORY";
|
|
962
|
+
NO_INTEGRATION: "NO_INTEGRATION";
|
|
963
|
+
NO_MODEL: "NO_MODEL";
|
|
964
|
+
NO_SERVICE: "NO_SERVICE";
|
|
965
|
+
NO_SESSION: "NO_SESSION";
|
|
966
|
+
REQUEST_FAILED: "REQUEST_FAILED";
|
|
967
|
+
RESPONSE_TOO_LARGE: "RESPONSE_TOO_LARGE";
|
|
968
|
+
SERVICE_FAILED: "SERVICE_FAILED";
|
|
969
|
+
SESSION_BUSY: "SESSION_BUSY";
|
|
970
|
+
UNSUPPORTED: "UNSUPPORTED";
|
|
971
|
+
}>;
|
|
972
|
+
error: z.ZodString;
|
|
973
|
+
}, z.core.$strip>], "ok">;
|
|
927
974
|
}, z.core.$strip>]>;
|
|
928
975
|
export declare const guestMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
929
976
|
channel: z.ZodLiteral<"openchamber.sdk">;
|
|
@@ -1453,6 +1500,10 @@ export declare const guestMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1453
1500
|
channel: z.ZodLiteral<"openchamber.sdk">;
|
|
1454
1501
|
v: z.ZodLiteral<1>;
|
|
1455
1502
|
type: z.ZodLiteral<"file-unsupported">;
|
|
1503
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1504
|
+
channel: z.ZodLiteral<"openchamber.sdk">;
|
|
1505
|
+
v: z.ZodLiteral<1>;
|
|
1506
|
+
type: z.ZodLiteral<"generate-late-listen">;
|
|
1456
1507
|
}, z.core.$strip>], "type">;
|
|
1457
1508
|
type HostWire = z.input<typeof hostMessageSchema>;
|
|
1458
1509
|
type GuestWire = z.input<typeof guestMessageSchema>;
|
package/dist/protocol.js
CHANGED
|
@@ -4,7 +4,7 @@ import { guestStatusControlEventSchema, guestStatusControlsSchema } from './stat
|
|
|
4
4
|
import { guestPopoverClosedEventSchema, guestPopoverDataSchema, guestPopoverRequestSchema } from './popover-schemas.js';
|
|
5
5
|
import { GUEST_SHELL_END_STATUSES, GUEST_SHELL_ID_MAX, GUEST_SHELLS_MAX, GUEST_SHELL_OUTPUT_TAIL_MAX } from './shells.js';
|
|
6
6
|
import { OPENCHAMBER_SDK_API_VERSION, OPENCHAMBER_SDK_CHANNEL } from './api-version.js';
|
|
7
|
-
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';
|
|
7
|
+
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, HOST_REQUEST_ERROR_CODES, } from './contract.js';
|
|
8
8
|
import { GUEST_FILE_EDITOR_CONTENT_MAX, GUEST_FILE_EDITOR_VERSION_MAX } from './file-editor.js';
|
|
9
9
|
// Structured clone recreates a frame's Uint8Array in the receiving realm, so
|
|
10
10
|
// `instanceof` holds for bytes that crossed postMessage.
|
|
@@ -99,6 +99,15 @@ const fileStatResultPayloadSchema = z.object({
|
|
|
99
99
|
const generateResultPayloadSchema = z.object({
|
|
100
100
|
text: z.string().max(GUEST_GENERATE_TEXT_MAX),
|
|
101
101
|
});
|
|
102
|
+
const generateRequestSchema = z.object({
|
|
103
|
+
prompt: z.string().trim().min(1).max(GUEST_GENERATE_PROMPT_MAX),
|
|
104
|
+
system: z.string().trim().min(1).max(GUEST_GENERATE_SYSTEM_MAX).optional(),
|
|
105
|
+
maxOutputTokens: z.number().int().min(1).max(GUEST_GENERATE_OUTPUT_TOKENS_MAX).optional(),
|
|
106
|
+
});
|
|
107
|
+
const lateGenerateResultSchema = z.discriminatedUnion('ok', [
|
|
108
|
+
z.object({ request: generateRequestSchema, ok: z.literal(true), result: generateResultPayloadSchema }),
|
|
109
|
+
z.object({ request: generateRequestSchema, ok: z.literal(false), code: z.enum(HOST_REQUEST_ERROR_CODES), error: z.string().min(1) }),
|
|
110
|
+
]);
|
|
102
111
|
const shellOutputResultPayloadSchema = z.object({
|
|
103
112
|
output: z.string(),
|
|
104
113
|
cursor: z.number(),
|
|
@@ -181,6 +190,7 @@ const readyPayloadSchema = z.object({
|
|
|
181
190
|
deviceStorage: z.literal(true).optional(),
|
|
182
191
|
statusControls: z.literal(true).optional(),
|
|
183
192
|
popovers: z.literal(true).optional(),
|
|
193
|
+
lateGenerateResults: z.literal(true).optional(),
|
|
184
194
|
}).optional(),
|
|
185
195
|
});
|
|
186
196
|
const hostResultSchema = z.object({
|
|
@@ -356,6 +366,7 @@ export const hostMessageSchema = z.union([
|
|
|
356
366
|
type: z.literal('file-saved'),
|
|
357
367
|
payload: z.object({ version: z.string().max(GUEST_FILE_EDITOR_VERSION_MAX) }),
|
|
358
368
|
}),
|
|
369
|
+
z.object({ ...envelope, type: z.literal('generate-late'), payload: lateGenerateResultSchema }),
|
|
359
370
|
]);
|
|
360
371
|
const filePathSchema = z.string().min(1).max(GUEST_FILE_PATH_MAX).refine(isGuestFilePath);
|
|
361
372
|
export const guestMessageSchema = z.discriminatedUnion('type', [
|
|
@@ -539,11 +550,7 @@ export const guestMessageSchema = z.discriminatedUnion('type', [
|
|
|
539
550
|
...envelope,
|
|
540
551
|
type: z.literal('generate'),
|
|
541
552
|
id: z.string().min(1),
|
|
542
|
-
payload:
|
|
543
|
-
prompt: z.string().trim().min(1).max(GUEST_GENERATE_PROMPT_MAX),
|
|
544
|
-
system: z.string().trim().min(1).max(GUEST_GENERATE_SYSTEM_MAX).optional(),
|
|
545
|
-
maxOutputTokens: z.number().int().min(1).max(GUEST_GENERATE_OUTPUT_TOKENS_MAX).optional(),
|
|
546
|
-
}),
|
|
553
|
+
payload: generateRequestSchema,
|
|
547
554
|
}),
|
|
548
555
|
z.object({
|
|
549
556
|
...envelope,
|
|
@@ -599,6 +606,7 @@ export const guestMessageSchema = z.discriminatedUnion('type', [
|
|
|
599
606
|
}),
|
|
600
607
|
z.object({ ...envelope, type: z.literal('file-save') }),
|
|
601
608
|
z.object({ ...envelope, type: z.literal('file-unsupported') }),
|
|
609
|
+
z.object({ ...envelope, type: z.literal('generate-late-listen') }),
|
|
602
610
|
]);
|
|
603
611
|
// The hand-written contract types and the schemas must describe the same wire
|
|
604
612
|
// shapes; either drifting from the other fails this file's type-check.
|
|
@@ -15,6 +15,7 @@ export declare const browserControlActionSchema: z.ZodEnum<{
|
|
|
15
15
|
"browser.back": "browser.back";
|
|
16
16
|
"browser.capture": "browser.capture";
|
|
17
17
|
"browser.click": "browser.click";
|
|
18
|
+
"browser.close": "browser.close";
|
|
18
19
|
"browser.forward": "browser.forward";
|
|
19
20
|
"browser.inspect": "browser.inspect";
|
|
20
21
|
"browser.open": "browser.open";
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
/** Route on the service loopback the host posts every action to. */
|
|
12
12
|
export declare const BROWSER_PROVIDER_PATH = "/browser-control";
|
|
13
|
-
export declare const BROWSER_CONTROL_ACTIONS: readonly ['browser.open', 'browser.snapshot', 'browser.click', 'browser.type', 'browser.scroll', 'browser.back', 'browser.forward', 'browser.inspect', 'browser.capture', 'browser.resize'];
|
|
13
|
+
export declare const BROWSER_CONTROL_ACTIONS: readonly ['browser.open', 'browser.snapshot', 'browser.click', 'browser.type', 'browser.scroll', 'browser.back', 'browser.forward', 'browser.inspect', 'browser.capture', 'browser.resize', 'browser.close'];
|
|
14
14
|
export type BrowserControlAction = (typeof BROWSER_CONTROL_ACTIONS)[number];
|
|
15
15
|
export declare const BROWSER_VIEWPORT_MODES: readonly ['mobile', 'tablet', 'desktop', 'fill'];
|
|
16
16
|
export type BrowserViewportMode = (typeof BROWSER_VIEWPORT_MODES)[number];
|
|
@@ -61,6 +61,7 @@ export type BrowserCaptureParameters = BrowserTabTarget & {
|
|
|
61
61
|
export type BrowserResizeParameters = BrowserTabTarget & {
|
|
62
62
|
viewport: BrowserViewportMode;
|
|
63
63
|
};
|
|
64
|
+
export type BrowserCloseParameters = BrowserTabTarget;
|
|
64
65
|
/**
|
|
65
66
|
* Where an action came from: the project the agent works in and the chat it
|
|
66
67
|
* runs in. Filled by the host from the tool call, never typed by the model.
|
|
@@ -108,6 +109,9 @@ export type BrowserProviderRequest = ProviderRequestEnvelope & ({
|
|
|
108
109
|
} | {
|
|
109
110
|
action: 'browser.resize';
|
|
110
111
|
parameters: BrowserResizeParameters;
|
|
112
|
+
} | {
|
|
113
|
+
action: 'browser.close';
|
|
114
|
+
parameters: BrowserCloseParameters;
|
|
111
115
|
});
|
|
112
116
|
export type BrowserViewportSummary = {
|
|
113
117
|
mode: BrowserViewportMode | 'custom';
|
|
@@ -213,7 +217,16 @@ export type BrowserCaptureData = {
|
|
|
213
217
|
export type BrowserResizeData = {
|
|
214
218
|
viewport: BrowserViewportSummary;
|
|
215
219
|
};
|
|
216
|
-
|
|
220
|
+
/**
|
|
221
|
+
* A closed tab: what the provider or panel actually removed. `tabId` names it
|
|
222
|
+
* when the provider has tabs; a provider with a single page refuses the action
|
|
223
|
+
* rather than reporting a close it did not do.
|
|
224
|
+
*/
|
|
225
|
+
export type BrowserCloseData = {
|
|
226
|
+
closed: true;
|
|
227
|
+
tabId?: string;
|
|
228
|
+
};
|
|
229
|
+
export type BrowserProviderData = BrowserOpenData | BrowserSnapshotData | BrowserClickData | BrowserTypeData | BrowserScrollData | BrowserNavigationData | BrowserInspectData | BrowserCaptureData | BrowserResizeData | BrowserCloseData;
|
|
217
230
|
/**
|
|
218
231
|
* What the service answers. `error` is read by the agent, so it should say
|
|
219
232
|
* what to do differently ("No element matches #save"), not what broke inside.
|
|
@@ -21,6 +21,7 @@ export const BROWSER_CONTROL_ACTIONS = [
|
|
|
21
21
|
'browser.inspect',
|
|
22
22
|
'browser.capture',
|
|
23
23
|
'browser.resize',
|
|
24
|
+
'browser.close',
|
|
24
25
|
];
|
|
25
26
|
export const BROWSER_VIEWPORT_MODES = ['mobile', 'tablet', 'desktop', 'fill'];
|
|
26
27
|
export const BROWSER_SCROLL_DIRECTIONS = ['up', 'down', 'top', 'bottom'];
|