@mearl/client 3.0.0-beta.9 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md ADDED
@@ -0,0 +1,616 @@
1
+ # @mearl/client
2
+
3
+ [简体中文](README.md) | [English](README.en.md)
4
+
5
+ Use `mearl` in a terminal or `@mearl/client` in TypeScript to control browsers, inspect requests and logs, or run Skill commands. The CLI, SDK, and script runner share browser routing and plugin runtimes. `@mearl/setup` manages installation and connections.
6
+
7
+ Run `mearl guide` for the core workflow in this version; `mearl guide --list` lists topics. For example, `mearl guide run` covers SDK composition and `mearl guide environment` covers devices/Apps. Guides read bundled npm resources without browser connections, plugin loading, installation, or automatic updates. Use `mearl <action> --help` for arguments and existing catalogs for installed target capabilities.
8
+
9
+ Guide content is maintained only in this package's `guides/`, copied to `dist/guides/` at build time, and checked by `pnpm check:skills`. CLI output converts local document links into guide commands. `skills/mearl/` distributes only a compact `SKILL.md`, without guide copies. MCP-only clients unable to run CLI use current tool descriptions/schemas.
10
+
11
+ A unified browser SDK and CLI discovering browsers on this machine and all connectors under configured cloud-server instances. Global `browserId` from `browser_list` selects local Socket or cloud WebSocket routing automatically.
12
+
13
+ Use type-safe named methods such as `getRequests` and `sendRequest`, or the flexible low-level `invoke(action, payload)`.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @mearl/client
19
+ # Or
20
+ pnpm add @mearl/client
21
+ ```
22
+
23
+ ## SDK usage
24
+
25
+ ```typescript
26
+ import * as client from '@mearl/client';
27
+
28
+ // Type-safe named methods
29
+ const requests = await client.getRequests({ tabId: 12345, count: 5 });
30
+ const result = await client.sendRequest({
31
+ url: 'https://api.example.com',
32
+ method: 'GET',
33
+ withCookies: true,
34
+ });
35
+
36
+ // Flexible low-level invocation
37
+ const data = await client.invoke('send_request', { url: '...' });
38
+
39
+ // browser_list aggregates local and all remote connectors by default
40
+ const browsers = await client.browserList();
41
+ await client.getLogs({ tabId: 12345 }, { browser: browsers.browsers[0].browserId });
42
+ ```
43
+
44
+ ## CLI usage
45
+
46
+ Installation provides the `mearl` command:
47
+
48
+ ```bash
49
+ # Get the five latest requests
50
+ mearl get_requests --payload '{"tabId":12345,"count":5}'
51
+
52
+ # Proxy an HTTP request
53
+ mearl send_request --payload '{"url":"https://api.example.com","method":"GET"}'
54
+
55
+ # Upload a local file with multipart/form-data
56
+ mearl send_request --payload '{"url":"https://api.example.com/upload","method":"POST","formData":{"folder":"assets"},"files":[{"fieldName":"file","filePath":"/absolute/path/image.png"}]}'
57
+
58
+ # Save a screenshot; tabId comes from tab_open or tab_list
59
+ mearl page_screenshot --payload '{"tabId":12345}' --output ./screenshot.png
60
+
61
+ # Read large parameters from a file; mock-data.json must include tabId
62
+ mearl set_mock --payload-file ./mock-data.json
63
+
64
+ # Check the environment
65
+ mearl check
66
+ mearl check --local --json
67
+
68
+ # Update the local environment
69
+ npx @mearl/setup update --local
70
+
71
+ # Show action help
72
+ mearl <action> --help
73
+
74
+ # Launch isolated headless Chrome instances
75
+ mearl browser_launch --payload '{"name":"browser-a","url":"https://example.com"}'
76
+ mearl browser_launch --payload '{"name":"browser-b","persistent":true}'
77
+ mearl browser_launch --payload '{"name":"cookie-copy","copyCookieDomains":["example.com"]}'
78
+ mearl browser_launch --payload '{"name":"figma","userAgentMode":"desktop","copyCookieDomains":["figma.com"]}'
79
+ mearl browser_list
80
+ mearl browser_release
81
+ ```
82
+
83
+ ### Agent scripts
84
+
85
+ `mearl run` reads JavaScript from stdin or `--file <path>`, injects the `mearl` SDK, and supports top-level `await`, conditions, loops, and data processing without task-directory dependencies:
86
+
87
+ ```bash
88
+ mearl run --session new <<'JS'
89
+ const tab = await mearl.tabOpen({ url: 'https://example.com', reuse: 'prefer' });
90
+ console.log({ tabId: tab.tabId, session: mearl.session });
91
+ console.log(await mearl.pageSnapshot({ tabId: tab.tabId, mode: 'viewport' }));
92
+ JS
93
+
94
+ mearl run --session '<sessionId>' --file /absolute/path/next-step.mjs
95
+ ```
96
+
97
+ Method names match the SDK, such as `mearl.pageClick(params, options?)`, `mearl.getRequests(params, options?)`,
98
+ and `mearl.tabList(options?)`. Use `mearl.invoke(action, payload, options?)` for generic calls.
99
+ Arguments/results match ordinary actions; consult `mearl <action> --help`. Action failures still use `action.success`,
100
+ `stage`, and `attempt`; scripts decide subsequent logic. Print needed results with `console.log` and diagnostics with `console.error`.
101
+ Returned data is not printed automatically. `run_actions` fits tight sequences without decisions; scripts fit workflows whose results code can evaluate.
102
+ When a model must interpret the page, print minimal observations and continue in the next round.
103
+
104
+ Scripts query command definitions through `help` and print returned text as needed. CLI `--help` shares arguments, results, constraints, and examples.
105
+
106
+ ```javascript
107
+ console.log(await mearl.help()); // Methods and registered plugins
108
+ console.log(await mearl.help('pageClick')); // Also accepts page_click
109
+ console.log(await mearl.help({ plugin: 'notes', command: 'search' }));
110
+
111
+ const result = await mearl.invokePlugin('notes', { command: 'search', input: { query: 'project' } });
112
+ console.log(mearl.structure(result));
113
+ ```
114
+
115
+ `structure(value, options?)` synchronously extracts an existing result's structure, replacing leaves with types. Defaults: depth 5, 50 keys per object,
116
+ one sample per array, 2000 total nodes. Adjust with `maxDepth`, `maxKeys`, `arraySamples`, and `maxNodes`.
117
+ Output includes array lengths/samples and marks cycles, accessors, and limits. Samples need not cover all optional fields or heterogeneous elements;
118
+ command help defines business meaning. The ordinary SDK also exports `structure`, sharing extraction logic with request `keysOnly`.
119
+ `keysOnly` follows the same default key/node limits and marks truncation.
120
+ Plugin help loads registration in isolated processes without executing the queried business command.
121
+
122
+ Compose registered Skill commands using names and inputs from plugin `--help`:
123
+
124
+ ```javascript
125
+ const result = await mearl.invokePlugin('notes', {
126
+ command: 'search',
127
+ input: { query: 'project' },
128
+ });
129
+ console.log(result);
130
+ ```
131
+
132
+ Browser APIs and plugin calls inherit script `--browser`, `--session`, `--connector`, `--local`, and `--cdp`;
133
+ individual `options` may override them. `--server` selects cloud-server. After `--session new` establishes a session,
134
+ later calls use its returned ID automatically; `mearl.session` and CLI stderr report session information.
135
+ First await a session-establishing browser call before concurrency or Skill commands. Script completion disconnects SDK connections but retains control sessions;
136
+ explicitly call `mearl.browserRelease` according to task lifetime. JavaScript variables reset each round; continue with returned `sessionId` and `tabId`.
137
+
138
+ Scripts run in separate Node.js processes inheriting caller cwd/environment with host Node.js permissions;
139
+ they are not security sandboxes. Load modules through dynamic imports such as `await import('node:fs/promises')`.
140
+ `--file` also executes as a script function without static `import` / `export`. Prefer absolute file URLs for local modules.
141
+ `window` / `document` exist only in page expressions through `mearl.pageEval`. Plugin registries, local files, and authentication belong to the CLI host.
142
+
143
+ `--timeout <seconds>` limits the complete script, default 60 seconds. API `options.timeoutSec` limits individual calls.
144
+ Combined stdout/stderr allows 256 KiB; source allows 1 MiB. Await every SDK call. Missing await, timeout, cancellation, or script exceptions exit 1;
145
+ input errors exit 2. Cancellation/timeouts stop scripts and process groups and propagate signals to pending Skill calls. Dispatched browser/business operations may still take effect;
146
+ use `pendingActions` / `mayHaveLateEffects` to determine reconciliation needs, without directly rerunning the entire script.
147
+ Skill commands use existing isolated executors; their stdout/stderr counts toward the script limit. Exception diagnostics are also bounded.
148
+ Result `success` means normal script completion only. Code must check action and business outcomes.
149
+
150
+ SDK hosts can import `executeScript(code, options)` from `@mearl/client/script`, returning
151
+ `{ success, stdout, stderr, session?, error?, executionStopped, mayHaveLateEffects, pendingActions }`。
152
+ It supports `signal` cancellation and `executionTimeoutSec`, sharing executors with CLI/MCP.
153
+
154
+ ### Control sessions and files
155
+
156
+ For multi-step tasks, use `--session new` on the first `tab_open` or `page_snapshot`. The response adds `session` alongside ordinary results; later `sessionId` routes automatically without separate creation/query actions. Sessions bind one browser/transport and associate pages on first use, tracking reused and created pages separately. One session controls a page at a time; sessionless calls cannot bypass ownership. Sessions do not isolate Cookies/storage.
157
+
158
+ `sessionId` is a short ID such as `s_aB3d_E-f`. The resident control host keeps sessions/browser/transport mappings in memory. New CLI processes internally query local/remote hosts for routing, without caller-managed routing or mapping files. Control-host exit invalidates sessions.
159
+
160
+ Calls may omit `--session`; existing actions, `run_actions`, and upload `filePaths` remain available. With active sessions, sessionless calls cannot manipulate owned pages, release/close browsers, or modify shared settings. Opening pages/closing browsers inside sessionless batches requires separate calls. CDP mocks are tab-scoped and must be configured per tab.
161
+
162
+ ```bash
163
+ mearl tab_open --session new --payload '{"url":"https://example.com"}'
164
+ mearl page_snapshot --session '<sessionId>' --payload '{"tabId":12345}'
165
+ mearl browser_control --session '<sessionId>' --payload '{"operation":"handoff","reason":"Please scan the QR code"}'
166
+ mearl page_download --session '<sessionId>' --payload '{"tabId":12345,"trigger":{"selector":"#export"}}' --output /absolute/path/report.csv
167
+ mearl page_upload --session '<sessionId>' --payload '{"tabId":12345,"selector":"input[type=file]","files":[{"filePath":"/absolute/path/report.csv"}]}'
168
+ mearl browser_release --session '<sessionId>' --payload '{"closeCreatedTabs":true,"keepTabIds":[12345]}'
169
+ ```
170
+
171
+ Users take over, return, or end sessions in Popup browser management. Handoff stops queued stale requests and later batch steps; dispatched actions may still apply. Take a fresh snapshot after return. CDP-only environments can use the same `browser_control` with `operation:"resume"` after explicit user confirmation. Sessions survive CLI connections, expire on host exit, and are not idle-cleaned while users work.
172
+
173
+ New extension background tabs join a collapsed `Mearl` group; foreground tabs join the expanded `Mearl · 待操作` group. Both reuse one existing window and merge same-name managed groups. Subsequent opened tabs enter that window and inherit source-group expansion. Closing the work window selects another existing window without creating one. Explicit `tab_open.group` overrides grouping; reused ordinary pages retain windows/groups. Handoff moves only managed session pages into the pending group; return moves those still pending into `Mearl`. Groups remain expanded if users view other grouped pages or the window has no other visible page.
174
+
175
+ Session release affects only that session and keeps pages/environments by default. `closeCreatedTabs` closes only created pages; `keepTabIds` retains results. Reused user pages always remain. `preserveEnvironment:false` clears those pages' environment. Active sessions block browser-wide release/close.
176
+
177
+ `page_download` listens before clicking and returns a browser artifact. CLI `--output` or SDK `saveArtifact(artifact, absolutePath, { session })` exports in 256 KiB chunks. `page_upload.files` accepts caller absolute `{filePath}` or browser `{artifactId}`; `filePaths` remains browser-host paths. Files are limited to 256 MiB. Caller-saved outputs are independent of temporary artifacts. Session release cleans temporary artifacts; `keepArtifactIds` retains selected ones. Sessionless artifacts expire after one idle hour; host exit clears staging.
178
+
179
+ Extension downloads require matching page referrers or an explicit expected `url`; unowned downloads are not guessed from recent files. CDP uses temporary directories only for managed browsers; shared browsers retain defaults and fail explicitly if Chrome provides no completed path. Timeouts may follow triggered downloads: inspect state before continuing. Session/download adapters support Extension/CDP; external action-handling Providers reject unsupported capabilities explicitly.
180
+
181
+ ### External browser Providers
182
+
183
+ Provider browsers appear beside Extension/CDP browsers in `browser_list` and use the same
184
+ global `browserId` routing. `@mearl/setup` manages installation, updates, and removal:
185
+
186
+ ```bash
187
+ npx @mearl/setup provider install @example/mearl-provider-device
188
+
189
+ # Inspect this Provider version's capabilities, arguments, and workflow
190
+ mearl example-device --help
191
+
192
+ # Provider launch arguments use providerOptions
193
+ mearl browser_launch --payload '{"provider":"example-device","providerOptions":{}}'
194
+ mearl browser_list
195
+
196
+ # Use global Browser IDs returned by browser_list for later operations
197
+ mearl tab_list --browser '<browserId>'
198
+ mearl page_snapshot --payload '{"tabId":12345}' --browser '<browserId>'
199
+
200
+ npx @mearl/setup provider update example-device
201
+ npx @mearl/setup provider uninstall example-device
202
+ ```
203
+
204
+ Creating a `pairing` instance with `browser_launch` returns `pairing.url` and an optional pairing code. Agents should render the URL directly as
205
+ a QR code, then confirm the same Browser ID becomes `connected` before page operations. Until the device connects,
206
+ it cannot become the default target. Use `browser_close` to delete the Provider instance. Each Provider
207
+ receives only explicitly supported actions; screenshot support distinguishes native captures, reconstructed images, and unsupported.
208
+
209
+ Unknown or misspelled actions return up to three candidates and examples based on available commands/common aliases,
210
+ without connecting to browsers or executing suggestions. For example, `mearl tab new` prefers `mearl tab_open`.
211
+
212
+ **Common options:**
213
+
214
+ | Option | Description |
215
+ | ------------------------ | ------------------------ |
216
+ | `--payload <json>` | JSON arguments |
217
+ | `--payload-file <path>` | Read JSON from a file |
218
+ | `--timeout <seconds>` | Request timeout |
219
+ | `--compact` | Force compact stdout |
220
+ | `--output <path>` | Save output, such as screenshots |
221
+ | `--browser <id\|name>` | Target browser |
222
+
223
+ CLI, SDK, and MCP share return structures. Snapshot `@eN` refs are directly actionable; internal mappings remain runtime-local.
224
+ Ordinary `session` includes session ID, browser, transport, state, and generation. Query tabs with `tab_list`;
225
+ `browser_control` handoff also returns session `tabs`. `run_actions` steps preserve individual action results.
226
+ stdout defaults to compact JSON in pipes/redirection and pretty output in interactive terminals. `--compact` forces compact layout without changing values. Snapshots collapse unnamed, stateless groups without refs while preserving control states and text.
227
+
228
+ With nonempty `fields`, `get_requests` omits request/response bodies by default, returning extracted fields and metadata;
229
+ pass `includeBody:true` for full bodies too. Omitted bodies exclude `postData` / `responseBody`;
230
+ without field selection, bodies are returned by default.
231
+
232
+ `send_request` includes ordinary Cookies matching the full target URL and CHIPS Cookies for its first-party site. For example, `https://app.example.com/` uses the `https://example.com` partition independently of open pages. Sites use schemes and the public suffix list including private suffixes, excluding other sites/schemes, cross-site ancestors, and opaque partitions. Same-name Cookies on different paths remain; parent-site host-only Cookies are not added. Third-party partitions embedded elsewhere, page signatures, or complete page identities require requests through the target page's `page_eval`.
233
+ Response `Set-Cookie` updates the original browser for later calls; `withCookies:false` disables both automatic inclusion and writeback. Chrome handles extension response Cookies. CDP Node requests write back through the original browser connection before returning, supporting HttpOnly, default paths, expiry, and deletion. CDP responses with `Set-Cookie` include `cookieSync: { updatedCount, rejectedCount, failedCount }` without values. Partitioned Cookies with unknown partitions count as rejected. Sync failures cannot undo completed HTTP operations: inspect state rather than resending business requests. Ordinary/streaming Protocol requests use the same refresh mechanism.
234
+ Extension temporary credentials match only the original full request URL; Chrome selects redirect Cookies for the new URL. CDP Node requests, including Protocol streams, do not automatically follow redirects, preventing original Cookies/signatures from reaching other addresses.
235
+
236
+ Mearl aggregates local and configured cloud-server browsers by default, routing automatically with global
237
+ `browserId` from `browser_list`. The overrides below restrict hosts/connections or troubleshoot transport, so default help omits them:
238
+
239
+ | Option | Description |
240
+ | ------------------------ | ------------------------------------- |
241
+ | `--connector <id\|name>` | Restrict to one remote machine |
242
+ | `--local` | Current machine only |
243
+ | `--cdp` | Force local CDP; list only CDP browsers and imply `--local` |
244
+ | `--server <url>` | Override the automatically read cloud-server URL |
245
+
246
+ `browser_list --help` and `browser_launch --help` show scenario-specific host options. For connection/transport troubleshooting, use
247
+ `mearl check --help`. `mearl check` checks connection/browser status. `mearl check --updates` also concurrently queries
248
+ latest client, native-host, cloud-connector, and extension releases. Release queries wait about 1.5 seconds at most;
249
+ unreachable sources report unconfirmed updates without affecting browser checks. `--json` returns machine-readable results,
250
+ including updates when combined with `--updates`. Available updates show appropriate commands: client/native-host use
251
+ `npx @mearl/setup update`; setup-managed extensions use `npx @mearl/setup extension`.
252
+
253
+ By default, globally installed `mearl` launches an independent background updater after the first use each day to query releases and compare
254
+ installed client, native-host, cloud-server, and MCP server versions. When needed, it invokes
255
+ `@mearl/setup update --yes --skip-skill` for outdated environments and their shared client, including standalone CLI installations.
256
+ The current command waits for neither network nor installation. Outcomes persist in
257
+ `~/.mearl/cli-auto-update-state.json`. Project dependencies and temporary `npx` copies are not modified automatically.
258
+ The browser extension retains its separate update entry.
259
+ Configure the interval or disable updates in `~/.mearl/config.json`:
260
+
261
+ ```json
262
+ { "cliAutoUpdate": { "intervalDays": 7 } }
263
+ ```
264
+
265
+ ```json
266
+ { "cliAutoUpdate": false }
267
+ ```
268
+
269
+ Intervals use local calendar days. Run `mearl check --updates` for immediate release checks.
270
+
271
+ ## Skill plugins
272
+
273
+ Install a Skill and automatically register commands, or register an existing entry:
274
+
275
+ ```bash
276
+ npx @mearl/setup plugin install https://github.com/mearlhq/mearl --skill browser-tab-manager --yes
277
+ npx @mearl/setup plugin /absolute/path/to/browser-tab-manager
278
+ npx @mearl/setup plugin uninstall tabs
279
+ mearl tabs summary
280
+ mearl tabs --payload '{"command":"summary","input":{}}'
281
+ mearl tabs --help
282
+ mearl tabs --version
283
+ ```
284
+
285
+ If a plugin and Provider share a name, `mearl <name> --help` shows Provider launch/lifecycle guidance first,
286
+ then appends registered plugin commands. `mearl <name> <command> --help` retains detailed command help.
287
+ Providers/plugins install, update, and uninstall independently. Help aggregation starts no Providers and executes no business commands.
288
+
289
+ Directories default to `scripts/main.mjs` or `scripts/main.py`, preferring JavaScript if both exist. JavaScript default-exports `register(app)`; Python exports a `register(app)` function. Both may declare short `name`/`description` metadata and use `app.command(signature, handler, description?)`
290
+ to register actions, `app.invoke(action, payload, options?)` for current-browser context, and `app.progress(message)` for progress. JS may pass `{ message, data }` with JSON-serializable stage/object metadata for tools; CLI shows only `message`. Python `app.invoke` is synchronous; handlers may be ordinary functions or `async def`. Python requires `python3 >= 3.9`, optionally selected by `MEARL_PYTHON`; Mearl neither installs Python nor runs pip. Cross-plugin payloads use `{ command, input }`, inheriting host options and rejecting cycles. CLI prints final results only unless
291
+ `--verbose` writes progress to stderr. Handlers return text/JSON; hosts manage `--output`, connections, and errors. User-invoked writes execute directly; business modules validate targets, state, idempotency, and outcomes.
292
+ Names prefer explicit registration, then entry `name`, then Skill directory. Normal registration needs only a path.
293
+ setup supplies missing client/native-host versions or upgrades those older than itself. Its Skills installer handles arguments using global shared installation.
294
+
295
+ JavaScript entries may export `setup({ runCommand })` to prepare Skill-owned dependencies. Both setup installation paths await this hook before registration; failures stop registration. Help, entry loading and ordinary calls do not execute it. Skills check their own dependencies on first use. Runtime code and resources belong inside the Skill.
296
+
297
+ For isolated checks, run `mearl <name> --version`. It resolves registration and loads the entry without browser connections;
298
+ exit 0 means registered/loadable. stdout shows the adjacent `SKILL.md` version or `unknown` without metadata.
299
+ Missing registration/entry or loading errors exit 2 with `PLUGIN_*`. Ordinary calls can handle these directly without a precheck.
300
+
301
+ Plugins accept positional arguments, string business options, or `--payload` with command/input. Prefer
302
+ `--name=value`; `--name value` is also supported. Fields match handler input independent of order. One field cannot use both
303
+ positional and business options; `--payload` cannot mix with either. JSON preserves field types. File/body inputs follow each
304
+ Skill's contract. Host options such as `browser`, `timeout`, and `output` keep their meaning. Same-name business fields, numbers,
305
+ booleans, objects, and arrays use `--payload`. Plugins do not use built-in actions' `--payload-file` entry.
306
+ Business option names begin with a letter and contain letters, digits, or underscores, preserving underscores.
307
+
308
+ Registered plugins are also SDK-callable; instance `client.invokePlugin` reuses connections/default browsers:
309
+
310
+ ```typescript
311
+ import { invokePlugin } from '@mearl/client';
312
+
313
+ const result = await invokePlugin(
314
+ 'tabs',
315
+ { command: 'summary', input: {} },
316
+ { browser: 'work' },
317
+ );
318
+ ```
319
+
320
+ MCP uses fixed `plugin_invoke` with `plugin`, `command`, and `input`. SDK/MCP use registered plugins. The installation-host-managed catalog may trigger setup matching the client version and one retry; other unregistered names return `PLUGIN_NOT_REGISTERED`.
321
+
322
+ Long commands use auto-starting local persistent task services isolated by registry directory. CLI/SDK/MCP/toolbox share records; caller exit does not cancel execution. Restart marks unfinished work `interrupted` without replay. Background tasks pin accepted source snapshots, cwd, and environment. Bundle third-party JS dependencies into Skills and keep state outside source directories. Snapshots pin source/relative resources without pre-resolving JS dependencies; executors report missing dependencies/syntax errors on load.
323
+
324
+ ```ts
325
+ import { PluginTaskClient } from '@mearl/client';
326
+
327
+ const tasks = new PluginTaskClient();
328
+ try {
329
+ const accepted = await tasks.start('example', { command: 'search', input: {} }, {
330
+ executionTimeoutMs: 120000,
331
+ idempotencyKey: 'document-123',
332
+ });
333
+ const result = await tasks.wait(accepted.taskId, { timeoutMs: 30000 });
334
+ console.log(result);
335
+ } finally {
336
+ tasks.disconnect();
337
+ }
338
+ ```
339
+
340
+ `wait` timeout or signal abort ends waiting only; `cancel(taskId)` cancels execution. Browser tasks require explicit `browser`, resolved to fixed IDs before acceptance. `get/list` return snapshots/result references; `list` has bounded pagination/`nextCursor`; `events` provides cursors, gaps, and snapshots; `subscribe` wraps long polling. `result(taskId)` losslessly reads and verifies chunks up to 64 MiB. Terminal records/results remain seven days.
341
+
342
+ Handshakes and `tasks.status()` return `version/buildId`; build IDs identify running code, while protocol versions determine compatibility. With matching protocols, different/missing version/build IDs do not interrupt tasks, models, pages, or state. To switch builds, confirm no active tasks/page operations, call `tasks.stopHost()`, and let the next business call start the current build. Reregistration does not restart services. After local client/host changes, root `pnpm build` generates matching fingerprints and both artifacts; `tsc --watch` alone does not.
343
+
344
+ SDK `PluginTaskClient` and toolbox `host.task.*` expose tasks. Plugins provide business commands through `mearl <plugin> <command>` or MCP `plugin_invoke`; there are no separate global task/model/state/page/diagnostic CLI/MCP tools. Toolbox-only plugins are absent from CLI lists; invoking their name shows where to open the tool. Tool pages use `window.mearl.waitTask(taskId)`, `readTaskResult(taskId)`, and `listTasks()` only for their own toolbox tasks.
345
+
346
+ `tasks.diagnose({ taskId?, plugin?, instanceId?, includeErrorMessages?, includeLocalPaths? })` reads bounded diagnostics within caller scope. Defaults exclude input, prompts, progress/checkpoint/result bodies, full URLs, environment, credentials, errors, and local paths. Error messages/paths may be explicitly included. coverage/history gaps describe collection and retention limits.
347
+
348
+ Records at `~/.mearl/plugins/<name>.json` save entry paths/optional descriptions. In-place business-code edits apply directly; path/description changes require registration again.
349
+ `plugin uninstall <name>` globally removes Skills under `~/.agents/skills` through the Skills installer,
350
+ then clears registrations, including tracking cleanup when entry directories were manually deleted.
351
+ Manual paths unregister without source deletion.
352
+ `PLUGIN_NOT_REGISTERED` means no registration. `PLUGIN_ENTRY_MISSING` shows the stale path and a cleanup
353
+ command with the actual plugin name. Other failures retain their specific cause.
354
+ The following sections describe toolbox, page, state, and task contracts.
355
+
356
+ Plugins may export `toolbox`, appearing in extension Popup after registration. Resources live within the Skill directory. Registration
357
+ inlines local CSS, JavaScript, and images referenced by entry HTML into an immutable page bundle, without HTTP services
358
+ or requiring builds for ordinary HTML/CSS/JS pages.
359
+
360
+ Right-click tools to enable/disable their Skills individually. Disabled status appears beside titles; tool pages/commands remain usable. Disabling renames
361
+ `SKILL.md` to `SKILL.md.mearl-disabled`, restored on enablement, affecting every Agent sharing that directory.
362
+ Symlink installs affect source directories. Existing sessions may retain loaded Skills until Agent restart/new sessions.
363
+ Toolbox updates/reinstalls preserve disabled state; external installers do not. Duplicate manifests require conflict resolution first.
364
+ Independent copies of same-name Skills disable separately. Plugins without Skill manifests show no toggle.
365
+
366
+ Example toolbox metadata:
367
+
368
+ ```javascript
369
+ export const toolbox = {
370
+ version: 1,
371
+ title: 'Workspace Notes',
372
+ description: 'Organize notes for a workspace',
373
+ icon: 'notebook-pen',
374
+ locales: {
375
+ en: {
376
+ title: 'Workspace Notes',
377
+ description: 'Organize notes for a workspace',
378
+ },
379
+ },
380
+ agent: {
381
+ skill: 'notebook-skill',
382
+ layout: 'workspace',
383
+ },
384
+ page: {
385
+ type: 'sandbox',
386
+ runtime: 'preact',
387
+ entry: 'assets/toolbox/index.html',
388
+ commands: ['list', 'create', 'delete'],
389
+ },
390
+ order: 100,
391
+ };
392
+
393
+ export default function register(app) {
394
+ app.command('list', async () => ({ items: await listItems() }));
395
+ app.command('create <name>', async input => ({ item: await createItem(input.name) }));
396
+ app.command('delete <id>', async input => ({ item: await deleteItem(input.id) }));
397
+ }
398
+ ```
399
+
400
+ `app.command(signature, handler, description?, examples?)` registers business commands for CLI, SDK, and Agent.
401
+
402
+ Set `toolbox.agent.pageElementActions` to `false` to hide page element selection and commenting in the toolbox's Agent chat. These actions are shown by default.
403
+
404
+ `toolbox.icon` supports the following strings, shared by tool lists, switch menus, and website catalogs:
405
+
406
+ ```js
407
+ icon: 'git-branch' // Built-in icon, at most 40 characters, follows theme
408
+ icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 4h16v16H4z"/></svg>' // Monochrome SVG follows theme and interaction
409
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#2563eb" d="M4 4h16v16H4z"/></svg>'
410
+ icon: 'https://example.com/tool-icon.svg' // HTTP(S) image URL, including SVG and PNG
411
+ icon: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%2F%3E'
412
+ ```
413
+
414
+ Icon strings allow 32768 characters. SVG using `currentColor` renders as a monochrome mask, inheriting built-in theme/hover/selection colors; prefer `fill="none" stroke="currentColor"` for outlines. Explicitly colored SVGs, multicolor data URLs, and remote images retain colors. SVG never injects into extension DOM. Remote images omit referrers; credentialed URLs are rejected. Unknown names/load failures show generic icons. Relative file paths are unsupported.
415
+
416
+ `app.toolboxCommand(signature, handler, description?, examples?)` registers tool-page-only commands with the same parameter/handler contract;
417
+ they do not appear in CLI help and cannot be called by CLI (including JSON), SDK, MCP, or cross-plugin `app.invoke`.
418
+ Tool pages use `window.mearl.invoke(command, input)` or `window.mearl.invoke('host.task.start', { command, input })`. Both command types must be explicitly listed in `toolbox.page.commands`.
419
+ Registered toolboxCommands absent from that allowlist fail plugin loading. Use them for chunked uploads/reads and temporary page-resource release;
420
+ use `app.command` for complete operations independently useful to Agents, such as queries, import, compression, or comparison.
421
+ Python also supports `app.toolboxCommand(...)` and `@app.toolboxCommand(..., description="...")` decorators.
422
+
423
+ Declaring `agent` lets tools delegate tasks to the conversational Agent. `layout` defaults to `drawer`, with a host-provided top entry.
424
+ Pages may select `drawer` / `workspace` / `inline` per interaction through `setAgentPanelVisible` or `runAgentTask`.
425
+ Agent conversations persist in Codex history. Workbenches with a fixed
426
+ 18rem left navigation can default to `workspace`, switch Agent through navigation, and fill the remaining main area; detail pages can still open right drawers.
427
+ `inline` embeds the Agent in a card/section, requiring an `anchor` element and stable
428
+ `key`. Hosts track anchor scrolling/resizing. `key` also isolates conversations and drafts by business object in drawer/workspace layouts;
429
+ `setAgentPanelVisible` may pass absolute `cwd` to bind newly created conversations to a directory.
430
+ `context` supplies background information without showing it as a chat message. On the first user message and session creation,
431
+ it is delivered as developer instructions.
432
+
433
+ `await window.mearl.ready()` returns `{ locale, theme, title, fontSize }`. Use
434
+ `window.mearl.invoke(command, input?)` for same-plugin allowlisted commands. Pages run in sandbox iframes without extension
435
+ APIs or network access by default. Native Host validates identity, command allowlists, and message size. Page bundles use
436
+ content-hash caches and chunked transfer without increasing Popup startup cost. Module scripts must first bundle into one file without
437
+ `import`. React/Vue projects may use built `index.html` as `entry`; registration performs final packaging.
438
+ For build-free components/state, declare `page.runtime: 'preact'`. After integrity checks, extensions inject pinned
439
+ Preact, Hooks, and HTM. Read `html`, `render`, `useState`, etc. from `window.mearl.ui`.
440
+ `window.mearl.ui.version` is diagnostic only; plugins cannot select runtime versions.
441
+ Hosts synchronize theme/font size and inject semantic variables including `--mearl-bg-primary`, `--mearl-bg-secondary`,
442
+ `--mearl-text-primary`, `--mearl-text-secondary`, `--mearl-border`, `--mearl-bg-hover`, and
443
+ `--mearl-accent`. Primary buttons use `--mearl-action-primary`,
444
+ `--mearl-action-primary-hover`, and `--mearl-action-primary-text` to match extension controls.
445
+
446
+ Toolboxes serve people; CLI serves Agents. Reuse business operations and consistent results without requiring shared intermediate state. Drafts, selections, previews, and other UI state default to browser memory. Use browser storage only for concrete refresh/reopen recovery needs. Sandboxes cannot assume localStorage, IndexedDB, or chrome.storage; use host capabilities. Node state is for business records, tasks continuing after close, safe retry receipts, or message-limited resource staging, with explicit lifetimes/cleanup.
447
+
448
+ Skill plugins can provide custom requests/data processing. CLI uses `mearl <plugin> <command>`; plugins reuse operations with `app.invoke(plugin, { command, input })`. Skill contracts define names, authentication, fields, and uploads.
449
+
450
+ Node plugins execute on the CLI/SDK/MCP Server host, never forwarded with browsers/connectors. File paths belong to that host. Browser operations require explicit targets. Installation hosts may provide on-demand installation through capability catalogs.
451
+
452
+ ## Supported actions
453
+
454
+ | Category | Actions |
455
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
456
+ | API debugging | `tab_checkpoint` `get_requests` `get_logs` `get_events` |
457
+ | Mocks and rules | `set_mock` `get_mocks` `set_rule` `get_rules` |
458
+ | Network requests | `send_request` `request_permission` |
459
+ | Tabs | `tab_open` `tab_list` `tab_close` |
460
+ | Page observation | `page_snapshot` `page_inspect` `page_screenshot` `page_frames` |
461
+ | Page actions | `page_click` `page_drag` `page_type` `page_hover` `page_scroll` `page_press` `page_wait` `page_navigate` `page_upload` `page_eval` |
462
+ | Page runtimes | `page_runtime` (internal document adapter; plugins use `app.page`) |
463
+ | Composition | `run_actions` |
464
+ | Environment and Cookies | `set_device_emulation` `set_app_profile` `set_timezone` `get_cookie` `set_cookie` |
465
+ | Browsers | `browser_list` `browser_launch` `browser_release` `browser_close` |
466
+
467
+ Interactive actions (`page_click` / `page_drag` / `page_type` / `page_hover` / `page_scroll` / `page_press` / `page_upload`) wait for asynchronous stability by default and return `{ action, observation }`. Pre-dispatch argument/ref/target failures return `action.stage: "precondition"`, omitting meaningless observation/diagnostics. Potentially dispatched input with confirmation timeout returns `action.stage: "dispatch-uncertain"` and observations for reconciliation. Known targets, hits, coordinates, dispatch modes, and input sequences appear in `action.attempt` as diagnostic evidence, not proof of effect. CLI failures exit 1. `observe: false` performs bare actions; `diagnostics: true` collects new error logs/business requests during the action, with analytics optionally enabled through `diagnostics.events`. `page_eval` defaults to bare execution and permits observation/diagnostics.
468
+
469
+ `tab_open active:true` requests activation. Extension do-not-disturb defaults on: HTTP(S) pages get background-focus emulation before first navigation without switching tabs/windows. Disabling it activates/restores windows. `activation.mode` distinguishes `background` / `foreground`; emulated visibilityState does not prove foreground. Handoff, release, and disconnect disable focus emulation regardless of preserveEnvironment. After return, reactivate the same URL with `reuse:"require", active:true` if needed. Sessionless tasks finish with browser_release. Pure CDP/external Providers follow their own activation support. Stability observation waits for bounded-duration CSS animations within budget. Selector/ref clicks first scroll targets clipped by nested containers, failing explicitly only if still clipped.
470
+
471
+ In extension mode, `tab_open` with `appProfile` applies the App environment. Set `emulation.deviceShell` to `true` for the simulated WebView and system bars. On supported fixed devices with a single WebView, the extension applies the saved **Phone frame** preference from Quick Settings. Agent page actions target the active business WebView.
472
+
473
+ Default `page_click` auto uses DOM fallback on hidden pages, supporting selectors, text, refs, and coordinates and returning `dispatchMode: "dom"`, `fallbackReason: "page-hidden"`. Ordinary background tasks need no activation for clicks. Only sites rejecting untrusted events require visible pages and forced `mouse` / `touch`.
474
+
475
+ `observation.effects.interactives` marks controls `recreated` when frameworks replace DOM nodes without content changes. It retains selectors/refs/semantics but omits unchanged values/states. Actual value/state changes still return full differences.
476
+
477
+ Optional `observation.effects.texts` returns current visible leaf text as `{ selector, text }`, including counters/amounts outside controls. Limits: eight entries, 160 characters each; repeated node changes retain final values. Nonempty text Range rectangles must be entirely within viewport/ancestor clipping and pass hit testing. CSS-masked text is excluded; empty text uses element boxes. These are neither interactive controls nor business-success signals. Complex/unreadable content and budget overflow retain snapshot fallback without changing navigation waits.
478
+
479
+ `page_press.pressMode` accepts `auto` / `dom` / `keyboard`. Default auto uses DOM key events on hidden pages, reporting `dispatchMode`, `fallbackReason`, and simulated `defaultAction`. Use visible pages and keyboard for native editing, selection, or scrolling.
480
+
481
+ `page_snapshot` defaults to full AX Tree. Use `mode: "viewport"` for long lists, `"interactive"` for viewport controls, or explicit `"dom"` when AX is empty/sparse. DOM returns rendered text, native controls, explicit roles, and high-confidence clickable nodes with actionable `@ref`, without inferring arbitrary business semantics. Scope with `rootSelector` or `rootRef` (optionally ancestorDepth), and search text/roles with query. interactive lacking AX control semantics falls back only to AX viewport with `fallbackMode: "viewport"`, never automatically switching sources. `maxNodes` / `maxChars` truncation preserves both beginning and end.
482
+
483
+ `page_inspect` uses CSS, snapshot `@ref`, or DevTools `$0` to return counts, bounding boxes, selected computed styles, center occlusion, horizontal/vertical overflow pixels, and nearest scroll containers. Omit selectors with `filter: "horizontal-overflow"` / `"vertical-overflow"` / `"scrollable"` for page-wide diagnosis. For text, first get `matches` / `matchCount` and refs through `page_snapshot.query`, then inspect refs without custom page_eval.
484
+
485
+ Disambiguate repeated text through `page_click.scope` CSS/ref subtrees. Extension coordinates in containers/iframes use `coordinateSpace: "active-frame"`; CSS/text queries accept frameId. Results distinguish requested semantics, resolved DOM, dispatch node, trusted hit, and coordinates through `requestedTarget`, `resolvedTarget`, `dispatchTarget`, `hitTarget`, and `coordinates`. Nested-clipped selector/ref targets scroll into view automatically; only remaining overflow clipping requires explicit handling. `page_scroll.containerPolicy: "nearest"` selects the nearest scrollable ancestor. Without selector, scrolling first chooses an inner container hit at representative viewport positions, then document scrolling or the topmost largest-visible container.
486
+
487
+ `browser_list` lists ordinary, Provider, and managed browsers. `type` distinguishes
488
+ `regular` / `managed`; `status` distinguishes `connected` / `pairing` / `running_disconnected` /
489
+ `stopped`. Only connected browsers are ordinary operation targets. Without a connected local extension browser,
490
+ browser_list automatically probes default CDP, even with Provider pairing records, without requiring --cdp.
491
+ `browser_list --cdp` actively probes if no ordinary CDP browser is registered and returns CDP browsers only;
492
+ connected extension browsers do not prevent this probe. With `MEARL_SOCKET_PATH`, inventory requests use that instance Socket.
493
+ `check --cdp` likewise checks only those CDP browsers.
494
+
495
+ Call `browser_release` only on explicit user requests to end debugging, detach, or clean session resources. It releases
496
+ debugger/CDP control and temporary session state while keeping browsers/tabs. Providers declare managed-instance launch, close, persistence,
497
+ access, and sign-in synchronization semantics; run `mearl <providerId>`. Access URLs are excluded from
498
+ `browser_list`; explicitly run `mearl check --browser <id|name>` when needed and treat returned
499
+ `browserAccess.url` as sensitive.
500
+
501
+ ## Architecture
502
+
503
+ ```
504
+ @mearl/client (CLI / SDK)
505
+ ├─ Local → @mearl/native-host → Chrome Extension / CDP
506
+ ├─ Local → @mearl/provider runtime → External Provider
507
+ └─ WebSocket → @mearl/cloud-server → @mearl/cloud-connector
508
+ ├─ @mearl/native-host → Chrome Extension / CDP
509
+ └─ @mearl/provider runtime → External Provider
510
+ ```
511
+
512
+ Infrastructure must avoid recursive routing: `@mearl/cloud-connector` directly connects to local native-host through `@mearl/client/local`. Ordinary Agent, CLI, and MCP integrations use the package root.
513
+
514
+ Plugin code is organized by responsibility in `src/plugins/`:
515
+
516
+ | Directory | Responsibility |
517
+ | --- | --- |
518
+ | `sdk/` | Plugin declarations, command interfaces, and page/model/state/recovery validation |
519
+ | `clients/` | Service connections and task/model/page/state clients |
520
+ | `runtime/` | Service assembly, scheduling, storage, snapshots, executors, and cleanup |
521
+ | Plugin directory root | Registration, resource packaging, official plugin installation, and shared client/server protocol |
522
+
523
+ Tests live in nearby `__tests__/`. Public exports use `@mearl/client` and `@mearl/client/plugin`; root `plugin.ts` is the public entry. Process entries `plugin-worker.ts`, `plugin-task-host.ts`, and `pluginModelSessionWorker.ts` remain in the `src/` root. `runtimePaths.ts` locates package versions and entries consistently for per-file compilation and Native Host bundling. `browser-core` still synchronizes generated protocol into `src/generated/`.
524
+
525
+ ## Build
526
+
527
+ ```bash
528
+ pnpm build # Compile with tsc into dist/ and make cli.js executable
529
+ pnpm dev # tsc --watch
530
+ pnpm typecheck # Type checking only
531
+ ```
532
+
533
+ ## License
534
+
535
+ Apache-2.0
536
+
537
+ ### Plugin page instances
538
+
539
+ `PluginPageClient` manages attach/get/list/send/detach/events/activation through local plugin services. Plugins declare independent `pageRuntime` and use `app.page()`. Hosts syntax-check code expressions with Node's compiler without executing them. Browser evaluation must produce self-contained functions; nonfunctions/external-reference errors appear at page runtime. Extension/pure CDP share instances, events, activation, and document runtimes. documentId binds instances; same-plugin/same-document entries share initialization. Navigation invalidates instances; activation rules rebuild them. `frame` accepts `main`, `business`, or `page_frames` IDs (numeric Extension, string CDP). External Providers must declare `page_runtime`.
540
+
541
+ Explicit manual `attach` reusing an automatic instance changes ownership to manual while preserving initialization and running tasks. Later narrowing automatic scopes does not remove it.
542
+
543
+ To change matching scope independently, pass `enabled: true, matchPatch: { add?: string[], remove?: string[] }`, mutually exclusive with `matches`. Hosts atomically add/remove scopes to avoid concurrent overwrites. Removing the final scope disables automatic activation while retaining manually attached instances. `persistence: 'persistent'` survives browser restarts. Initialization handlers can read current plugin preferences instead of copying volatile settings into rule input.
544
+
545
+ ```typescript
546
+ const pages = new PluginPageClient();
547
+ try {
548
+ const page = await pages.attach('example', { tabId: 123, frame: 'business' });
549
+ await pages.send('example', page.instanceId, { type: 'read' });
550
+ } finally { pages.disconnect(); }
551
+ ```
552
+
553
+ Background events declare schemas and serial, latest-coalescing, or bounded-concurrency policies. app.runtimeCommand handlers use `context.page` for fixed documents. Document-bound tasks may read their plugin's activation rules with `app.page('activation')` but cannot modify them. Messages/results allow 24 MiB each with automatic chunks/checks; events allow 32 KiB. Toolbox uses `host.page.*`; UI history subscription is independent of execution. See [plugin host capabilities](../../docs/plugin-runtime.en.md#page-runtimes-and-events).
554
+
555
+ Python exports the same `pageRuntime` dictionary, with `code` as browser-side JavaScript function strings. Register initialization/events through app.runtimeCommand or decorators. `app.page(operation, payload)`, `context.page.send(message)`, and `context.page.get()` are synchronous; the latter two require instance-bound handlers. JS/Python `context.page.send(message, {timeoutSec: 120})` sets per-response timeouts above 0 and at most 300 seconds (Python uses dictionaries). `app.page("send", {instanceId, message, timeoutSec})` and SDK `pages.send(plugin, instanceId, message, {timeoutSec})` also support it. Other requests are unaffected. Cancellation/detach still interrupts immediately; transport failures cancel the page request. Runtime-only commands remain hidden and inaccessible through CLI/toolbox; ordinary `app.command` event handlers remain publicly callable.
556
+
557
+ ### Model tasks and declared state
558
+
559
+ `PluginModelClient` uses local Codex without browsers. Model/command tasks share querying, cancellation, and lossless results with separate scheduling. Query `capabilities()` first; only explicit read-only Agent mode is supported, and strictly tool-free generation fails.
560
+
561
+ ```typescript
562
+ import { PluginModelClient, PluginStateClient } from '@mearl/client';
563
+ const models = new PluginModelClient();
564
+ try {
565
+ const result = await models.run('example', {
566
+ provider: 'codex', mode: 'agent', permissions: 'read-only', prompt: 'Return a short title.',
567
+ outputSchema: { type: 'object', properties: { title: { type: 'string' } }, required: ['title'], additionalProperties: false },
568
+ }, { signal: controller.signal });
569
+ console.log(result.output);
570
+ } finally { models.disconnect(); }
571
+ ```
572
+
573
+ `start` returns persistent taskId; `run` signals cancel execution and await cleanup. `app.model(request, { onProgress })` / `context.model(request, { onProgress })` subscribe to that call's `model.delta`, isolated across concurrent calls; Python uses `context.model(request, on_progress=callback)`. Incremental `data` contains `delta/index/partial` and optional `itemId/phase`. Isolate messages, skip commentary, and fall back to final results on sequence gaps. Deltas are temporary display only; final results come from successful turns' complete messages. outputSchema passes to Providers; hosts parse JSON; plugins validate structure/business constraints. Retract temporary output on failure; never cache it or infer success. Use `PluginModelClient`/context for catalogs/execution; toolbox uses `host.model.capabilities/list/start` and `waitTask`.
574
+
575
+ Declare collections in plugin entries, then access them from command contexts:
576
+
577
+ ```javascript
578
+ export const stateStorage = { version: 1, collections: {
579
+ settings: { scope: 'installation', storage: 'persistent', schemaVersion: 1, schema: { type: 'object' } },
580
+ } };
581
+ export default app => app.command('save', async (input, context) => {
582
+ const previous = await context.state('settings', 'get', { key: 'config' });
583
+ return context.state('settings', 'transact', { transaction: { operations: [
584
+ { type: 'put', key: 'config', value: input, expectedVersion: previous.version },
585
+ ] } });
586
+ });
587
+ ```
588
+
589
+ SDK `PluginStateClient.get/getMany/list/transact` and toolbox `host.state.*` share collections. Writes require CAS; conflicts require reread/merge/retry. `schemaVersion` upgrades use explicit migrations. Browser-session/instance scopes support `cache` or `memory` with host-verified page identities. `memory` resides only in the execution host, sharing CAS/schema/TTL/chunk limits. Browser-session scope survives document refresh and isolates new sessions; host restart clears it. Host-wide memory allows 128 MiB/512 namespaces on demand; `maxBytes` per collection allows 128 MiB. Instance end releases memory. See [plugin host capabilities](../../docs/plugin-runtime.en.md#state-and-activation) for quotas/TTL. Binary assets still use files/resource channels.
590
+
591
+ ### Explicit task recovery
592
+
593
+ Plugins declare `taskRecovery` with per-command checkpointVersion/schema and register `app.recoveryCommand(command, handler)`. `context.checkpoint(data, expectedSequence)` saves up to 64 KiB. Data, version, host schemaHash, sequence, and events atomically commit with tasks using CAS. Schemas establish identity/format compatibility; plugins validate checkpoint/migration structures.
594
+
595
+ `tasks.checkpoint(taskId)` reads checkpoints. `tasks.resume(taskId, { environment?, revision? })` creates new attempts for terminal root commands, retaining old terminal tasks and original installation, snapshot, input, directory, and route. Default environment comes freshly from the caller; old environments are not persisted. Recovery handlers read `context.recovery` and check external state first. Hosts neither replay original handlers nor guarantee atomic external submissions/checkpoints.
596
+
597
+ Use `tasks.checkpoint(taskId)` / `tasks.resume(taskId)`. Only public/service roots qualify; models, children, page bindings, and caller routing reject recovery, and original trees must be confirmed stopped. Original code is the default; explicit `revision: 'current'` pins request-time snapshots. Same-version schemas must match. Cross-version targets declare `taskRecovery` `migrateFrom: [{ version, schema }]` and hidden `app.checkpointMigration(command, handler)`, returning target checkpoint data. Hosts verify historical schemaHash, forbid host/nested-plugin APIs during migration, and manage processes with 10-second deadlines. Conversion must be retryable and side-effect-free; trusted code is not an OS sandbox. Concurrent recovery coalesces into one conversion/successor; original tasks/data remain. See [checkpoints and explicit recovery](../../docs/plugin-runtime.en.md#checkpoints-and-explicit-recovery).
598
+
599
+ Plugin CLI failures return nonzero and stderr JSON `{ "error": { "code", "message" } }`, preserving SDK/MCP codes without arbitrary exception extras. `PLUGIN_TASK_WAIT_TIMEOUT` stops observation only; `PLUGIN_EXECUTION_CANCELLED` / `PLUGIN_EXECUTION_DEADLINE` indicate cancellation/deadline; `PLUGIN_PAGE_TARGET_CHANGED` / `PLUGIN_PAGE_INVALIDATED` require new instances; `PLUGIN_STATE_CONFLICT` requires fresh CAS reads; `PLUGIN_MODEL_CAPABILITY_UNSUPPORTED` means unsupported capabilities; `PLUGIN_INVOCATION_CYCLE` / `PLUGIN_INVOCATION_DEPTH` reject cycles/depth. Unconfirmed process-tree termination retains failure records and rejects recovery.
600
+
601
+ Explicit toolbox page attach requests missing site permissions and continues after user confirmation. Background page runtimes never prompt automatically.
602
+
603
+ Query models through SDK `PluginModelClient.list()` or toolbox `host.model.list`. Entries include supported `reasoningEfforts` and `defaultReasoningEffort` where available; UI restricts choices accordingly. Hosts coalesce discovery, immediately return existing catalogs, and refresh stale data in background. Failures retain usable catalogs with short backoff and create no inference sessions. Batch state reads use `get-many` and `keys` (SDK `getMany`, toolbox `host.state.getMany`) for up to 128 `keys` at one revision, including missing keys' CAS versions, limited to 1 MiB.
604
+
605
+
606
+ `PluginHost` assembles tasks, state, pages, and model catalogs with shutdown/diagnostics. `PluginTaskManager` receives explicit dependencies and owns execution trees; page/state services do not mutate schedulers. `PluginHostConnection` manages RPC, discovery, handshake, reconnection, and bridging. Share connections through Task/Model/State constructor `connection` options or Page's second constructor argument. Creators close injected connections; client `disconnect()` closes only owned connections. Connections bind one caller scope and cannot cross plugins, interaction channels, or sources.
607
+
608
+ Continuous background model calls may specify `conversation` (1–80 letters, digits, or `:_-`). Same-name reuse is limited to the current page instance or current host-managed command. Plugin, revision, model, instructions, environment, and ownership isolate sessions. Omission creates independent calls. Detach/command end releases conversations; cancel/failure/timeout destroys them. Idle expiry is five minutes; temporary sessions restart every 32 turns to bound history. Conversations are memory-only with no new global CLI/MCP commands.
609
+
610
+ ## Managed plugin catalog
611
+
612
+ Installation hosts use client/plugin replaceOfficialPluginCatalog(revision, plugins) to save selected-preset catalogs. revision is the exact manifest digest; entries contain description, skill, installer installArgs, and optional setup grouping. Atomic replacement saves .official-catalog.json in the registry directory; catalogs default empty. Hosts own source/preset trust checks; catalogs alone do not install.
613
+
614
+ findOfficialPlugin/listOfficialPluginNames reread current catalogs and reuse setup for missing plugins. readOfficialPluginCatalog reports corruption to installation status; ordinary plugin/browser commands ignore corrupt optional catalogs without affecting registered capabilities. Compatibility export OFFICIAL_PLUGINS reflects the most recently read catalog in the process.
615
+
616
+ Extension update checks default to config.json from GitHub's latest stable release. Distribution hosts may set MEARL_EXTENSION_CONFIG_URL to an endpoint returning version or latestVersion. Until extension metadata is uploaded, update data is unavailable; source versions are not published extension versions. Check failures affect notifications only.