Browser
With browser in dozin.capabilities, a Connector or task gets ctx.browser to read and act on web pages in Cowork tabs. Only tabs the person hands over can be used, and when dozin.origins is given, only pages on those sites.
ctx.browser.adoptTab(tabId)
Takes control of a tab. Cowork hands the tabId to the Extension when the person asks it to take over a page; an Extension cannot pick a tab by itself.
| Name | Type | Required | Description |
|---|---|---|---|
tabId | string | Yes | The tab Cowork handed over |
Returns Promise<{ tabId, url, documentId }>. documentId changes each time the page loads a new document. At most 8 tabs at a time.
const tab = await ctx.browser.adoptTab(tabId);
ctx.log(`watching ${tab.url}`);ctx.browser.releaseTab(tabId)
Gives the tab back. Its injected scripts and audio stop.
| Name | Type | Required | Description |
|---|---|---|---|
tabId | string | Yes | A tab this Extension adopted |
Returns Promise<void>.
await ctx.browser.releaseTab(tabId);ctx.browser.evaluate(tabId, script)
Runs a script once in the page and returns its result.
| Name | Type | Required | Description |
|---|---|---|---|
tabId | string | Yes | An adopted tab |
script.source | string | Yes | A function source, at most 64 KB; it receives args |
script.args | unknown | No | Passed as JSON, not pasted into the source |
Returns Promise<unknown>: the result, as JSON, at most 64 KB.
const title = await ctx.browser.evaluate(tabId, { source: '(args) => document.title' });ctx.browser.inject(tabId, script)
Installs a script that stays in the page and can send messages back.
| Name | Type | Required | Description |
|---|---|---|---|
tabId | string | Yes | An adopted tab |
script.id | string | Yes | 1–80 letters, digits, _ or -; unique in this tab |
script.source | string | Yes | A function (args, { emit, onDispose }) => cleanup?, at most 64 KB; emit(data) arrives as a message event |
script.args | unknown | No | Passed as JSON |
Returns Promise<void>. At most 16 scripts per tab.
await ctx.browser.inject(tabId, {
id: 'chat-watch',
source: `(args, { emit }) => {
const observer = new MutationObserver(() => emit({ count: document.querySelectorAll(args.selector).length }));
observer.observe(document.body, { childList: true, subtree: true });
return () => observer.disconnect();
}`,
args: { selector: '.message' }
});ctx.browser.uninject(tabId, injectionId)
Removes an injected script and runs its cleanup.
| Name | Type | Required | Description |
|---|---|---|---|
tabId | string | Yes | An adopted tab |
injectionId | string | Yes | The id given to inject |
Returns Promise<void>.
await ctx.browser.uninject(tabId, 'chat-watch');ctx.browser.captureAudio(tabId)
Needs audio too. Starts capturing the tab's sound.
| Name | Type | Required | Description |
|---|---|---|---|
tabId | string | Yes | An adopted tab that has finished loading |
Returns Promise<AudioStream>:
| Field | Type | Description |
|---|---|---|
tabId · url · documentId | The tab, as in adoptTab | |
streamId | string | This capture |
frames | AsyncIterable<{ streamId, sequence, timestamp, sampleRate, channels, pcm: Float32Array }> | Sound, chunk by chunk |
ended | Promise | Settles when the capture ends |
stop() | () => Promise<void> | Stop it |
One capture per tab at a time.
const stream = await ctx.browser.captureAudio(tabId);
for await (const chunk of stream.frames) handle(chunk.pcm, chunk.sampleRate);ctx.browser.stopAudio(tabId)
Stops the tab's capture.
| Name | Type | Required | Description |
|---|---|---|---|
tabId | string | Yes | A tab with a running capture |
Returns Promise<void>.
await ctx.browser.stopAudio(tabId);ctx.browser.onEvent(listener)
| Name | Type | Required | Description |
|---|---|---|---|
listener | (event) => void | Yes | Called for every event of the Extension's tabs |
Returns () => void to stop listening. Every event carries tabId, url and documentId, and a type:
type | Extra fields | When |
|---|---|---|
url-changed · navigation-start · ready | — | The page moves or finishes loading |
closed · crashed | — | The tab is gone |
injected | injectionId | A script was installed |
message | injectionId, data | An injected script called emit(data) |
error | code | Something failed in the tab |
audio-ended | streamId, lastSequence | A capture ended |
const off = ctx.browser.onEvent((event) => {
if (event.type === 'message') ctx.emit({ text: String(event.data.count) });
if (event.type === 'closed') off();
});Errors
The functions reject with one of these codes:
| Code | Meaning |
|---|---|
capability_denied | browser (or audio) not declared |
invalid_tab · tab_not_granted · tab_not_owned | Bad tab id, Cowork did not hand this tab over, or it was released |
origin_not_allowed | The page's site is not in dozin.origins |
tab_limit · injection_limit | More than 8 tabs or 16 scripts |
tab_unavailable · document_not_ready · stale_document | The tab closed, is still loading, or loaded another page |
invalid_script · invalid_injection_id · injection_exists · invalid_json · result_too_large | Bad input or output |
audio_already_started · audio_transport_unavailable · invalid_audio_request | Audio capture could not start |
session_closed · browser_operation_failed | The Extension is stopping, or Cowork could not complete the call |