qz-browser-agent 0.1.3 → 0.1.5

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.md CHANGED
@@ -2,54 +2,78 @@
2
2
 
3
3
  An open-source browser agent for humans and coding agents.
4
4
 
5
- qz is a local-first extension for Chrome and Edge.
6
- People use it from the side panel; agents such as Claude Code, Codex, and pi hand it web tasks
7
- through this CLI or MCP. qz works in your own browser, with your existing logins, and returns
8
- tables, files, or reports with sources. Everything runs locally, and your data goes only to the
9
- model you choose.
5
+ qz is a local-first extension for Chrome and Edge. This CLI and its MCP endpoint let coding agents
6
+ such as Claude Code, Codex, and pi drive your own browser, with your existing logins, or hand qz a
7
+ whole web task. Driving the browser needs no model configured in qz: your agent writes a short
8
+ script, and qz returns a compact result and trace that keep its token use low. People can watch
9
+ and step in from the side panel. Everything runs locally.
10
10
 
11
11
  [Website](https://alibaba.github.io/qz-browser-agent/) ·
12
12
  [GitHub](https://github.com/alibaba/qz-browser-agent) ·
13
13
  [Chrome Web Store](https://chromewebstore.google.com/detail/dknbdgkgoinlgjpfebapkklbmjjgdblm) ·
14
14
  [Full CLI and MCP reference](https://github.com/alibaba/qz-browser-agent/blob/main/docs/cli.md)
15
15
 
16
- ![A coding agent handing a GitHub research task to qz](https://raw.githubusercontent.com/alibaba/qz-browser-agent/main/docs/images/examples/en/pi-delegate-en.gif)
17
-
18
16
  ## Quick start
19
17
 
20
18
  Requires Node.js 22 or later.
21
19
 
22
20
  1. **Install the extension.** Click **Add to Chrome** on the
23
- [Chrome Web Store](https://chromewebstore.google.com/detail/dknbdgkgoinlgjpfebapkklbmjjgdblm),
24
- open the qz side panel, and choose a provider and model in **Settings**.
25
- 2. **Connect.** This starts the local service and pairs automatically with your default browser.
21
+ [Chrome Web Store](https://chromewebstore.google.com/detail/dknbdgkgoinlgjpfebapkklbmjjgdblm).
22
+ 2. **Install the CLI.** Or use `npx qz-browser-agent@latest` in place of `qz`.
23
+
24
+ ```bash
25
+ npm install --global qz-browser-agent@latest
26
+ ```
27
+ 3. **Drive the browser.** No model setup is needed in qz.
26
28
 
27
29
  ```bash
28
- npx qz-browser-agent@latest connect
30
+ qz session create --title "Read example page"
31
+ qz script --session SESSION_ID - <<'JS'
32
+ const page = qz.tab(await qz.openTab({ url: "https://example.com" }));
33
+ return await page.read({ limit: 2000 });
34
+ JS
29
35
  ```
30
36
 
31
- 3. **Run your first task.**
37
+ The first command connects the browser automatically. To teach your agent the full workflow,
38
+ install the qz Skill with `npx skills add alibaba/qz-browser-agent -g`, or have it read `qz skill`.
39
+ 4. **Or hand qz the whole task.** Choose a provider and model in the side panel's **Settings**, then:
32
40
 
33
41
  ```bash
34
- npx qz-browser-agent@latest create "Find this year's Nobel Prize winners in every category and put them in a table with source links." --stream
42
+ qz create "Find this year's Nobel Prize winners in every category and put them in a table with source links." --stream
35
43
  ```
36
44
 
37
45
  `--stream` shows progress, any questions qz needs you to answer, and the final result.
38
46
 
39
- For regular use, install the `qz` command globally and use it in place of
40
- `npx qz-browser-agent@latest`:
47
+ ## Drive the browser
41
48
 
42
49
  ```bash
43
- npm install --global qz-browser-agent@latest
44
- qz --version
50
+ qz skill
51
+ qz session create --title "Read example page" --json
52
+ qz script --session SESSION_ID task.js --args '{"query":"example"}'
53
+ qz session show SESSION_ID
54
+ qz session cancel SESSION_ID
45
55
  ```
46
56
 
47
- To let your agent learn when and how to use qz, install the Skill with
48
- `npx skills add alibaba/qz-browser-agent -g`.
57
+ `session create` starts no model. With `--json`, `result.qz_skills` lists the Skills the new session
58
+ can use right away, and `qz.call("load_skill",{name})` makes another Skill's tools callable at once.
59
+
60
+ Scripts run outside pages and use the `qz` SDK. Bind `qz.tab(tabRef)` for page operations,
61
+ use `page.locator(query)` for element actions and waits, and `page.evaluate(code,args?)` for DOM access.
62
+ Discover additional capabilities with `qz.listTools()` or `qz.listTools({names:["list_images"]})`,
63
+ then invoke them through `qz.call(name,args)`. stdout is JSON `{ok,result,trace}`: execution status, the returned business value,
64
+ and ordered error and `trace()`/`console` messages without levels. Failures put `ToolError` in `result`; `undefined`
65
+ returns become `null`.
66
+
67
+ Browser operations prepare their connection automatically: they reuse the first responding
68
+ online browser, or open the connection page once in the system default browser. When qz does not respond
69
+ there, the page links to the Chrome Web Store and shows its URL with a copy button for a browser that has qz.
70
+ Select an existing profile with --client NAME|UUID; it never falls back to another profile. An incompatible
71
+ local service is restarted once before browser operations. Complete the extension’s connection
72
+ prompt when needed.
49
73
 
50
74
  ## Connect an MCP client
51
75
 
52
- After `connect`, print the MCP URL:
76
+ Print the MCP URL; this starts the local service if needed:
53
77
 
54
78
  ```bash
55
79
  npx qz-browser-agent@latest mcp
@@ -80,15 +104,17 @@ claude mcp add --transport http qz http://127.0.0.1:18765/mcp
80
104
  }
81
105
  ```
82
106
 
83
- CLI and MCP share the same browser connection. The MCP tool list is in the
107
+ CLI and MCP share the same browser connection. The default MCP catalog is the direct-drive set;
108
+ `qz mcp --profile full` prints the URL for the task and diagnostic catalog. The MCP tool list is in the
84
109
  [reference](https://github.com/alibaba/qz-browser-agent/blob/main/docs/cli.md#task-and-resource-operations).
85
110
 
86
111
  ## Common commands
87
112
 
88
113
  | Command | What it does |
89
114
  | --- | --- |
90
- | `qz connect` | Start the local service and pair a browser |
115
+ | `qz connect uri` | Print a URL to open in a chosen browser profile |
91
116
  | `qz status` | Check the connection and service address |
117
+ | `qz cleanup` | Stop registered local services and remove runtime files and logs |
92
118
  | `qz browsers` | List paired browsers, or `forget` an offline one |
93
119
  | `qz create "PROMPT"` | Start a task; add `--stream` or `--wait` to follow it |
94
120
  | `qz send TASK_ID "MESSAGE"` | Send a follow-up, or `--steer` the current run |
@@ -102,7 +128,6 @@ CLI and MCP share the same browser connection. The MCP tool list is in the
102
128
  | `qz skills` | Find, read, or export Skills |
103
129
  | `qz settings` | Read or update settings |
104
130
  | `qz automations` | List or run automations |
105
- | `qz stop` | Stop the local service and disconnect all browsers |
106
131
 
107
132
  With several browser profiles connected, choose one with `--client NAME|UUID` (or `-c`).
108
133
  Run `qz COMMAND --help` for options. Interactive terminals show readable text; pipes receive JSON.
@@ -116,8 +141,12 @@ Timeout or Ctrl-C ends observation while the browser task continues; execute the
116
141
  ## Privacy and local access
117
142
 
118
143
  The service accepts connections only on your computer, and ordinary websites cannot use its MCP
119
- endpoint. Local programs can control connected qz tasks while the service is running. `qz stop`
120
- disconnects all browsers and MCP clients; tasks already running in the browser keep going.
144
+ endpoint. Local programs can control connected qz tasks while the service is running. Disconnect
145
+ a profile in its qz connection settings; task cancellation remains a separate operation.
146
+ Run `qz cleanup` to stop local services across installations and remove their runtime files and logs.
147
+ It preserves browser registrations, tabs, tasks and saved data; the next browser command starts its
148
+ service automatically. Active CLI requests and direct-drive scripts are interrupted; side-panel tasks
149
+ continue. `QZ_STATE_DIR` selects a different runtime directory when set.
121
150
  Connection settings are stored in `~/.qz-browser-agent/run/`, and pairing credentials never
122
151
  leave your computer.
123
152
 
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "qz-browser-agent",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Local browser tools and MCP access for the qz extension",
5
5
  "type": "module",
6
6
  "engines": { "node": ">=22" },
7
7
  "bin": { "qz": "src/bin.mjs" },
8
- "files": ["src/*.mjs", "src/catalog.json", "README.md", "LICENSE"],
8
+ "files": ["src/*.mjs", "src/catalog.json", "src/qz-drive-SKILL.md", "src/qz-logo.png", "src/qz-logo-day.png", "README.md", "LICENSE"],
9
+ "scripts": {
10
+ "prepack": "cp ../../skills/qz-browser-agent/SKILL.md src/qz-drive-SKILL.md && cp ../../src/icons/icon128.png src/qz-logo.png && cp ../../src/icons/logo-day.png src/qz-logo-day.png"
11
+ },
9
12
  "license": "Apache-2.0",
10
13
  "homepage": "https://alibaba.github.io/qz-browser-agent/",
11
14
  "bugs": { "url": "https://github.com/alibaba/qz-browser-agent/issues" },
package/src/args.mjs CHANGED
@@ -1,10 +1,16 @@
1
1
  import { parseArgs } from "node:util";
2
2
  import { QzError } from "./client.mjs";
3
3
  import { browserName } from "./browser-registry.mjs";
4
- import { COMMANDS, commandOptions } from "./commands.mjs";
4
+ import { COMMANDS, DRIVE_SESSION_REQUIRED, commandOptions } from "./commands.mjs";
5
5
 
6
6
  function invalid(message) {
7
- throw new QzError("invalid_arguments", message);
7
+ throw new QzError("INVALID_ARGUMENTS", message, {
8
+ hint: "Run qz --help, or npm run qz -- --help, to check the command and its arguments.",
9
+ });
10
+ }
11
+
12
+ function requireDriveSession(command, options) {
13
+ if (!String(options.session || "").trim()) invalid(`qz ${command} requires --session <id>. ${DRIVE_SESSION_REQUIRED}`);
8
14
  }
9
15
 
10
16
  function helpTopic(parts) {
@@ -14,20 +20,29 @@ function helpTopic(parts) {
14
20
  invalid("Use qz help COMMAND.");
15
21
  }
16
22
 
23
+ const argumentOptions = {
24
+ session: { type: "string", short: "s" }, timeout: { type: "string" },
25
+ title: { type: "string", short: "t" }, client: { type: "string", short: "c" },
26
+ "wait-id": { type: "string" }, format: { type: "string" }, query: { type: "string" },
27
+ start: { type: "string" }, limit: { type: "string" }, scope: { type: "string" }, filename: { type: "string" },
28
+ parent: { type: "string" }, port: { type: "string" }, name: { type: "string" },
29
+ wait: { type: "boolean" }, stream: { type: "boolean" }, steer: { type: "boolean" },
30
+ json: { type: "boolean" }, plain: { type: "boolean" }, "no-color": { type: "boolean" },
31
+ lang: { type: "string" }, profile: { type: "string" }, args: { type: "string" },
32
+ keep: { type: "string", multiple: true },
33
+ help: { type: "boolean", short: "h" }, version: { type: "boolean", short: "v" },
34
+ };
35
+
36
+ /** Identify the command for syntax-error presentation while consuming option values normally. */
37
+ export function commandNameFromArgs(argv) {
38
+ return parseArgs({ args: argv, allowPositionals: true, strict: false, options: argumentOptions }).positionals[0] || "help";
39
+ }
40
+
17
41
  /** Parse syntax only. A prompt or args value of "-" is read from stdin by the caller. */
18
42
  export function parseCommand(argv) {
19
43
  let parsed;
20
44
  try {
21
- parsed = parseArgs({ args: argv, allowPositionals: true, strict: true, tokens: true, options: {
22
- session: { type: "string", short: "s" }, timeout: { type: "string" },
23
- title: { type: "string", short: "t" }, client: { type: "string", short: "c" },
24
- "wait-id": { type: "string" }, format: { type: "string" }, query: { type: "string" },
25
- start: { type: "string" }, limit: { type: "string" }, scope: { type: "string" }, filename: { type: "string" },
26
- parent: { type: "string" }, port: { type: "string" }, name: { type: "string" }, "no-open": { type: "boolean" },
27
- wait: { type: "boolean" }, stream: { type: "boolean" }, steer: { type: "boolean" },
28
- json: { type: "boolean" }, plain: { type: "boolean" }, "no-color": { type: "boolean" },
29
- help: { type: "boolean", short: "h" }, version: { type: "boolean", short: "v" },
30
- } });
45
+ parsed = parseArgs({ args: argv, allowPositionals: true, strict: true, tokens: true, options: argumentOptions });
31
46
  } catch (error) { invalid(error.message); }
32
47
 
33
48
  const options = { ...parsed.values };
@@ -35,7 +50,7 @@ export function parseCommand(argv) {
35
50
  for (const token of parsed.tokens) {
36
51
  if (token.kind !== "option" || token.value === undefined) continue;
37
52
  const name = token.name;
38
- if (seen.has(name)) invalid(`Provide --${name} only once.`);
53
+ if (seen.has(name) && !argumentOptions[name].multiple) invalid(`Provide --${name} only once.`);
39
54
  seen.add(name);
40
55
  }
41
56
  if (options.json && options.plain) invalid("Choose --json or --plain.");
@@ -57,11 +72,15 @@ export function parseCommand(argv) {
57
72
  for (const name of ["session", "client", "wait-id", "parent"]) {
58
73
  if (options[name] !== undefined && !options[name].trim()) invalid(`--${name} requires a non-empty ID.`);
59
74
  }
75
+ if (options.keep !== undefined) {
76
+ options.keep = options.keep.map(value => value.trim());
77
+ if (options.keep.some(value => !value)) invalid("--keep requires a non-empty tabRef.");
78
+ }
60
79
  if (options.name !== undefined) {
61
80
  try { options.name = browserName(options.name); } catch (error) { invalid(error.message); }
62
81
  }
63
- if (options.client !== undefined && (options.name !== undefined || options["no-open"])) {
64
- invalid("Use --client to select an existing browser, or --name/--no-open to prepare a binding URL.");
82
+ if (options.client !== undefined && options.name !== undefined) {
83
+ invalid("Use --client to select an existing browser, or --name to name a new profile.");
65
84
  }
66
85
  if (options.port !== undefined) {
67
86
  const port = Number(options.port);
@@ -70,6 +89,8 @@ export function parseCommand(argv) {
70
89
  }
71
90
  if (options.filename !== undefined && !options.filename.trim()) invalid("--filename requires a non-empty name.");
72
91
  if (options.scope !== undefined && !["all", "global", "site", "resident"].includes(options.scope)) invalid("--scope must be all, global, site, or resident.");
92
+ if (options.profile !== undefined && !["full", "drive"].includes(options.profile)) invalid("--profile must be full or drive.");
93
+ if (options.lang !== undefined && !options.lang.trim()) invalid("--lang requires a value.");
73
94
  if (options.client !== undefined) {
74
95
  options["client-id"] = options.client;
75
96
  delete options.client;
@@ -90,7 +111,10 @@ export function parseCommand(argv) {
90
111
  }
91
112
 
92
113
  let command = originalCommand, action, id;
93
- if (command === "browsers") {
114
+ if (command === "connect") {
115
+ if (words.length !== 1 || words[0] !== "uri") invalid("Use qz connect uri to print a browser connection URL.");
116
+ action = "uri";
117
+ } else if (command === "browsers") {
94
118
  action = words[0] || "list";
95
119
  if (action === "list" ? words.length > 1 : action !== "forget" || words.length !== 2 || !words[1].trim()) invalid("Use qz browsers [list], or qz browsers forget NAME|UUID.");
96
120
  id = words[1];
@@ -141,6 +165,23 @@ export function parseCommand(argv) {
141
165
  id = words[0];
142
166
  if (command === "submit") options.args = words[1];
143
167
  if (command === "watch") options.wait = true;
168
+ } else if (command === "script") {
169
+ if (words.length > 1) invalid("Use qz script --session ID [FILE|-] [--args JSON].");
170
+ requireDriveSession("script", options);
171
+ options.file = words[0] ?? "-";
172
+ if (options.args !== undefined) {
173
+ try { options.args = JSON.parse(options.args); } catch { invalid("--args must be valid JSON."); }
174
+ }
175
+ } else if (command === "session") {
176
+ action = words[0] || "";
177
+ const count = ["create", "list"].includes(action) ? 1 : 2;
178
+ if (!Object.hasOwn(COMMANDS.session.actions, action) || words.length !== count || words.some(word => !word.trim())) {
179
+ invalid("Use qz session create --title TITLE, qz session list, qz session show ID, or qz session cancel ID.");
180
+ }
181
+ if (action === "create" && !String(options.title || "").trim()) invalid("qz session create requires --title TITLE. Choose a readable task title.");
182
+ id = words[1];
183
+ } else if (command === "skill") {
184
+ if (words.length) invalid("Use qz skill.");
144
185
  } else if (command === "tasks") {
145
186
  if (words.length) invalid("Use qz tasks to list tasks, qz create PROMPT, qz show ID, or qz cancel ID.");
146
187
  action = "list";
package/src/bin.mjs CHANGED
@@ -9,11 +9,11 @@ if (isEntry) {
9
9
  const argv = process.argv.slice(2);
10
10
  if (Number(process.versions.node.split(".")[0]) < 22) {
11
11
  const error = {
12
- code: "unsupported_node_version",
12
+ code: "UNSUPPORTED_NODE_VERSION",
13
13
  message: `qz requires Node.js 22 or later. Current version: ${process.versions.node}.`,
14
14
  hint: "Install Node.js 22 or later, then run qz again.",
15
15
  };
16
- if (argv.includes("--json")) process.stdout.write(`${JSON.stringify({ ok: false, error })}\n`);
16
+ if (argv.includes("--json")) process.stdout.write(`${JSON.stringify(error)}\n`);
17
17
  else process.stderr.write(`${error.message}\n${error.hint}\n`);
18
18
  process.exitCode = 1;
19
19
  } else {
package/src/catalog.json CHANGED
@@ -53,6 +53,8 @@
53
53
  "properties": {
54
54
  "limit": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Limit"},
55
55
  "excludeSessionId": {"default": "", "title": "Excludesessionid", "type": "string"},
56
+ "scope": {"type": "string", "description": "session returns one session. Omit it to list tasks."},
57
+ "sessionId": {"type": "string", "description": "Session to return when scope is session."},
56
58
  "timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
57
59
  "clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
58
60
  },
@@ -228,7 +230,7 @@
228
230
  },
229
231
  {
230
232
  "name": "qingzhou_get_session_snapshot",
231
- "description": "Read a task snapshot: session, runtime, current input, transcript, recent messages, artifacts, and optional trace/tools. Pending input includes field IDs, option values, instructions, and responseTemplate for answering directly. Tool transcript argsState/argsGenerating distinguish generating arguments from final empty arguments.",
233
+ "description": "Read a task snapshot: session, runtime, current input, transcript, recent messages, artifacts, cumulative usage, and optional trace/tools. usage sums recorded model calls (inputTokens, outputTokens, cachedTokens, costUsd, modelCalls) plus toolCalls, screenshots, and contextCompressions. Pending input includes field IDs, option values, instructions, and responseTemplate for answering directly. Tool transcript argsState/argsGenerating distinguish generating arguments from final empty arguments.",
232
234
  "inputSchema": {
233
235
  "properties": {
234
236
  "sessionId": {"default": "default", "title": "Sessionid", "type": "string"},
@@ -264,11 +266,13 @@
264
266
  },
265
267
  {
266
268
  "name": "qingzhou_stop_session",
267
- "description": "停止轻舟会话运行。",
269
+ "description": "结束任务或直驱会话。直驱会话结束后不可再执行工具,也无需再调用 close_tab;默认自动关闭该会话创建的标签页,并保留用户原有或已交回给用户的标签页。keepTabRefs 指定要交还用户的会话标签,keepTabs:true 保留全部标签页。End a task or direct-drive session, closing its created tabs by default while preserving user tabs. Pass keepTabRefs to return selected session tabs to the user, or keepTabs:true to retain all tabs.",
268
270
  "inputSchema": {
269
271
  "properties": {
270
272
  "sessionId": {"title": "Sessionid", "type": "string"},
271
273
  "cascade": {"default": true, "title": "Cascade", "type": "boolean"},
274
+ "keepTabs": {"title": "Keeptabs", "type": "boolean"},
275
+ "keepTabRefs": {"type": "array", "items": {"anyOf": [{"type": "integer"}, {"type": "string"}]}, "description": "Session tabRef values to keep after ending the session. Validated before stopping."},
272
276
  "timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
273
277
  "clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
274
278
  },
@@ -279,10 +283,10 @@
279
283
  },
280
284
  {
281
285
  "name": "qingzhou_list_tools",
282
- "description": "列出当前 session 可用的轻舟内置工具和插件工具 schema。",
286
+ "description": "List default direct-drive tool signatures; sessionId is optional. Query non-default or loaded Skill tools by exact name with qingzhou_get_tool (pass sessionId for loaded Skills). For full schemas, call tools.list_tools({names:[...]}) inside run_script. Never creates a session.",
283
287
  "inputSchema": {
284
288
  "properties": {
285
- "sessionId": {"default": "default", "title": "Sessionid", "type": "string"},
289
+ "sessionId": {"title": "Sessionid", "type": "string"},
286
290
  "timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
287
291
  "clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
288
292
  },
@@ -337,20 +341,79 @@
337
341
  "type": "object"
338
342
  }
339
343
  },
344
+ {
345
+ "name": "qingzhou_list_drive_sessions",
346
+ "description": "List direct-drive sessions as {sessionId,title,status} observations.",
347
+ "inputSchema": {
348
+ "properties": {
349
+ "timeoutSeconds": {"type": ["number", "null"], "default": null},
350
+ "clientId": {"type": "string", "default": "", "description": "Browser name or UUID."}
351
+ },
352
+ "type": "object"
353
+ }
354
+ },
355
+ {
356
+ "name": "qingzhou_get_drive_session",
357
+ "description": "Read one direct-drive session as {sessionId,title,status}.",
358
+ "inputSchema": {
359
+ "properties": {
360
+ "sessionId": {"type": "string", "minLength": 1},
361
+ "timeoutSeconds": {"type": ["number", "null"], "default": null},
362
+ "clientId": {"type": "string", "default": "", "description": "Browser name or UUID."}
363
+ },
364
+ "required": ["sessionId"],
365
+ "type": "object"
366
+ }
367
+ },
368
+ {
369
+ "name": "qingzhou_create_drive_session",
370
+ "description": "Create one direct-drive session with a readable title and return sessionId, title, status, and qz_skills (summaries of the user workflows available to the session). Pass that id on every tool execution. This does not start a model.",
371
+ "inputSchema": {
372
+ "required": ["title"],
373
+ "properties": {
374
+ "title": {"type": "string", "minLength": 1, "pattern": "\\S", "description": "Readable task title."},
375
+ "timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
376
+ "clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
377
+ },
378
+ "title": "qingzhou_create_drive_sessionArguments",
379
+ "type": "object"
380
+ }
381
+ },
340
382
  {
341
383
  "name": "qingzhou_execute_tool",
342
- "description": "直接执行轻舟工具,用于系统 skill/plugin 调试。依赖 Agent 生命周期的页面工具请传当前 runId;wait=false 时后台执行并立即返回 jobId。",
384
+ "description": "在已连接的浏览器里执行现有工具。先调用 qingzhou_create_drive_session 创建会话,再传入该 sessionId(不会隐式创建会话)。单次调用传 name 与 args;已知参数的并发或顺序批次传 calls(每项含 id、name、args,id 仅用于关联结果,不支持跨项插值);多步逻辑与条件编排可调用 name:\"run_script\"(页面外执行,支持顶层 await/return 与 tools.*,页面内 DOM 函数用 evaluate)。页面工具显式传 tabRef。逐项检查 isError,批次与脚本均非事务;run_script 的 result 为 {ok,result,trace} 回执,trace 按序记录报错与显式 trace()/console 输出。Execute browser tools in an explicitly created session (pass sessionId from qingzhou_create_drive_session). Supply name/args for one call, calls ([{id,name,args}]) for a static batch, or name:\"run_script\" for outer JS orchestration (use evaluate for in-page DOM functions). Page tools require an explicit tabRef. Check isError on each call; batches and scripts are not transactions. run_script returns {ok,result,trace}, where trace lists errors and explicit trace()/console diagnostics in order.",
343
385
  "inputSchema": {
344
386
  "properties": {
387
+ "scriptApiVersion": {"type": "integer", "minimum": 1, "description": "Script API protocol version; must match the extension before executing the request."},
345
388
  "name": {"title": "Name", "type": "string"},
346
389
  "args": {"anyOf": [{"additionalProperties": true, "type": "object"}, {"type": "null"}], "default": null, "title": "Args"},
347
- "sessionId": {"default": "default", "title": "Sessionid", "type": "string"},
348
- "runId": {"default": "", "title": "Runid", "type": "string"},
390
+ "calls": {
391
+ "type": "array",
392
+ "items": {
393
+ "type": "object",
394
+ "additionalProperties": false,
395
+ "required": ["id", "name"],
396
+ "properties": {
397
+ "id": {"type": "string"},
398
+ "name": {"type": "string"},
399
+ "args": {"type": "object", "additionalProperties": true}
400
+ }
401
+ }
402
+ },
403
+ "limits": {
404
+ "type": "object",
405
+ "additionalProperties": false,
406
+ "properties": {
407
+ "timeoutSeconds": {"type": "number"},
408
+ "maxCalls": {"type": "number"}
409
+ }
410
+ },
411
+ "sessionId": {"title": "Sessionid", "type": "string"},
349
412
  "wait": {"default": true, "title": "Wait", "type": "boolean"},
350
413
  "timeoutSeconds": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Timeoutseconds"},
351
414
  "clientId": {"default": "", "title": "Clientid", "type": "string", "description": "Browser name or UUID. Required when multiple browsers are online. Offline or unknown selections never fall back to another browser."}
352
415
  },
353
- "required": ["name"],
416
+ "required": ["sessionId"],
354
417
  "title": "qingzhou_execute_toolArguments",
355
418
  "type": "object"
356
419
  }
@@ -372,12 +435,12 @@
372
435
  },
373
436
  {
374
437
  "name": "qingzhou_get_tool",
375
- "description": "Read one browser tool's description and input schema by exact name in the selected task's context.",
438
+ "description": "Read a tool by exact name without a session. Pass sessionId for a loaded Skill tool. Direct drive returns a compact signature and description; this call never creates a session.",
376
439
  "inputSchema": {
377
440
  "type": "object",
378
441
  "properties": {
379
442
  "name": {"type": "string", "minLength": 1},
380
- "sessionId": {"type": "string", "default": "default"},
443
+ "sessionId": {"type": "string"},
381
444
  "timeoutSeconds": {"type": ["number", "null"], "default": null},
382
445
  "clientId": {"type": "string", "default": ""}
383
446
  },
@@ -11,9 +11,9 @@ export function settingsPatch(id, value, json = {}) {
11
11
  try { parsed = JSON.parse(value); } catch { /* An unquoted CLI value is a string. */ }
12
12
  config = { [id]: parsed };
13
13
  }
14
- if (!record(config) || !Object.keys(config).length) throw new QzError("invalid_arguments", "Settings must be a non-empty JSON object.");
14
+ if (!record(config) || !Object.keys(config).length) throw new QzError("INVALID_ARGUMENTS", "Settings must be a non-empty JSON object.");
15
15
  if (Object.keys(config).some(key => !key.trim() || ["__proto__", "prototype", "constructor"].includes(key))) {
16
- throw new QzError("invalid_arguments", "Settings contain an invalid field name.");
16
+ throw new QzError("INVALID_ARGUMENTS", "Settings contain an invalid field name.");
17
17
  }
18
18
  return config;
19
19
  }