Chrome extension

Use Agentak as an MV3 side panel that follows the active tab.

The Agentak repository includes a Chrome MV3 side panel built with the same ChatPanel and Pi session as the embeddable widget. The panel adds browser capabilities that a chat inside a normal page does not need.

#Install the unpacked extension

Download and unzip the archive.
Open chrome://extensions.
Enable Developer mode.
Select Load unpacked and choose the unzipped folder.
Click the Agentak toolbar button to open the side panel.

The extension is not in the Chrome Web Store yet.

#What the panel adds

#Read the active page

The built-in read_active_tab tool returns the active tab's title, URL, and rendered text. It is read-only, so it runs without a confirmation. Page text is marked untrusted: the model and the transcript both warn that it is data, not instructions.

The panel follows tab changes and navigation, so the next call reads the page currently in front of the user rather than the tab where the panel first opened.

An empty panel offers one starter at the foot of its greeting, Summarize this page, which is that tool in one click. It is the panel's prompts, and it goes with the greeting once something has been said.

The panel drops this tool when the active page publishes a reader of its own — a WebMCP tool named read_page, read_current_page, read_page_content, read_this_page, get_page_content, or get_page_text. A page that reads itself knows its own structure, and two readers under one list only make the model choose between them.

#Discover WebMCP tools

The panel also lists tools published by the active page on document.modelContext. Calls run in the tab's main world because a live WebMCP tool cannot be serialized into the extension. Only metadata, JSON arguments, and result text cross the bridge.

The toolbar badge shows how many tools the page publishes. It does not count read_active_tab, which exists on nearly every reachable page.

A relative link such as /settings in an answer belongs to the page being discussed. The panel passes the active tab URL as linkBase, so the link resolves against that site instead of chrome-extension:. A browser-internal page with no safe HTTP base leaves an unsafe link as text.

#Keep history per site

Provider keys and model choices belong to the whole extension. Conversations do not: the panel keeps a separate history shelf per site. Moving to another origin moves the chat to that site's shelf. A conversation being answered during navigation remains owned by the site where it started.

Tabs without an HTTP origin share a shelf named for the current tab.

#Bundle provider catalogs

Normal Agentak pages load keyed-provider catalogs from esm.sh. MV3 blocks remote modules, so the extension bundles one lazy catalog chunk per keyed provider and supplies it through useCatalogSource().

The extension can list Kilo Gateway and OpenCode Zen because its host permissions are not gated by page CORS. Chrome Built-in AI is listed when the Prompt API is available.

#Bundle wllama

On Device (wllama) is listed in the panel as well, for the same reason and with one extra step. extension/wllama/ supplies the module and the wasm through useWllamaSource(), which is what puts the row back in the picker.

The extra step is the Worker. wllama joins the emscripten glue and its own worker code into one string per run and starts it as a blob: URL. MV3 matches a worker script against worker-src, which falls back to script-src 'self', and blob: is not 'self' — nor can the policy be widened to allow it. So the build writes that worker out as a file next to the bundle and rewrites wllama's createWorker() to load it, passing the per-run options on the worker's URL. The package is not patched; the rewrite is a Vite transform, and the version is pinned exactly so an update is read rather than assumed.

The panel runs single-threaded. Threads mean pthreads, which emscripten also starts from a blob, and wllama asks for SharedArrayBuffer first — the manifest declares no cross-origin isolation, so it never gets one.

Model weights still come from Hugging Face on first use and are cached in OPFS, so the first answer on a model waits for the download and later ones do not.

#Storage

The panel uses chrome.storage.local for keys, choices, and conversation shelves. It reads and writes that area directly, because PiStorage is asynchronous, and mounts on session.ready so the first render never shows forgotten choices. A write the area refuses for want of room rejects, which is how built-in history learns to give up an older conversation.

Closing Chrome's side panel destroys its document. Stored state restores on the next open, and the composer requests focus because the chat is the whole document.

#Permissions

The manifest requests:

  • sidePanel to provide the browser panel
  • storage for choices, keys, and conversations
  • scripting to read the active tab and bridge WebMCP
  • host access to HTTP and HTTPS pages and to follow their selected URLs

Broad host access is intentional. activeTab would cover only the tab where the toolbar button was clicked, while a side panel remains open as the user changes tabs. The cost is Chrome's all-sites install warning.

The panel cannot read browser-internal screens such as chrome:// pages. That failure is returned as a tool error instead of breaking the whole turn.

#Build it from source

From the repository root:

pnpm build:extension

Load extension/dist unpacked. The same command creates the zip served by this documentation site.

The MV3 Content Security Policy permits WebAssembly because Agentak's Markdown parser uses wasm. It still disallows remote executable code and Workers built at run time, which is why catalogs are bundled and why wllama's module, wasm, and Worker are all written into the build. The panel is about 8 MB larger than it would otherwise be, most of it the wllama wasm.