@qwen-code/qwen-code 0.16.2 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled/qc-helper/docs/configuration/settings.md +5 -1
- package/bundled/qc-helper/docs/features/channels/_meta.ts +1 -0
- package/bundled/qc-helper/docs/features/channels/feishu.md +170 -0
- package/chunks/{agent-RY5EB3XR.js → agent-KVXFGIOU.js} +17 -12
- package/chunks/{anthropicContentGenerator-LYI3OHBB.js → anthropicContentGenerator-L4HWAOIV.js} +5 -5
- package/chunks/{askUserQuestion-R3MKD2JT.js → askUserQuestion-DC6OWQIL.js} +5 -3
- package/chunks/chunk-24YKA2DA.js +233 -0
- package/chunks/{chunk-UFC57OYT.js → chunk-33RDTIU6.js} +5 -3
- package/chunks/{chunk-C6WMLUNB.js → chunk-3BJBCG2K.js} +1 -1
- package/chunks/chunk-3HX5LZ6R.js +1798 -0
- package/chunks/{chunk-GQXXO5HJ.js → chunk-4O2TWJK4.js} +25 -5
- package/chunks/{chunk-RDYWTWEM.js → chunk-5IFG2VC4.js} +299 -232
- package/chunks/chunk-6RQTH7UQ.js +115 -0
- package/chunks/{chunk-7HM6OB7M.js → chunk-7EHPK6TK.js} +1336 -5164
- package/chunks/{chunk-PJLEMR7N.js → chunk-7NNBQRV7.js} +6 -6
- package/chunks/{chunk-K5PGHDBN.js → chunk-AKBFRR6J.js} +112 -132
- package/chunks/{chunk-C27V5A2J.js → chunk-EMVEDSVZ.js} +1 -1
- package/chunks/{chunk-TXQI3VZ7.js → chunk-GJHMAWS7.js} +1 -1
- package/chunks/{chunk-K72FHBFO.js → chunk-HAQCNXSG.js} +1 -0
- package/chunks/{chunk-T4VD6OJ4.js → chunk-HCSJIOLR.js} +1 -1
- package/chunks/chunk-J37FGIOA.js +1623 -0
- package/chunks/chunk-J5VCSWPA.js +1467 -0
- package/chunks/{chunk-UE5LPQF7.js → chunk-JI7FDD65.js} +7 -7
- package/chunks/{chunk-4YNZFYJY.js → chunk-MEN6IEKX.js} +2533 -3943
- package/chunks/{chunk-USE2VQ5P.js → chunk-NP3ICQCN.js} +1 -1
- package/chunks/{chunk-66CJCYYZ.js → chunk-QEXSIXLX.js} +1 -1
- package/chunks/{chunk-YMDXEEOW.js → chunk-R2B65CAN.js} +1 -82
- package/chunks/chunk-SZOEIL6S.js +35 -0
- package/chunks/chunk-TI4GXJKO.js +4277 -0
- package/chunks/{chunk-E7E2MFYM.js → chunk-U2K6HDUJ.js} +434 -13
- package/chunks/{chunk-FO7BIVSR.js → chunk-UQRYJQBE.js} +5 -3
- package/chunks/{chunk-VMOWXTRC.js → chunk-V7LMZR76.js} +1 -1
- package/chunks/chunk-W57YDFU5.js +41 -0
- package/chunks/computer-use-2J5ZXEER.js +768 -0
- package/chunks/{contextCommand-DDGVLQSF.js → contextCommand-52NTEMCT.js} +19 -14
- package/chunks/{cron-create-BTEOGHPH.js → cron-create-FXRORK2U.js} +5 -3
- package/chunks/{cron-delete-56CEWELN.js → cron-delete-D24IN6CA.js} +5 -3
- package/chunks/{cron-list-SV6QRZW2.js → cron-list-SMOX26SL.js} +5 -3
- package/chunks/{devtools-WN62SQPV.js → devtools-IXE4UP72.js} +31 -3748
- package/chunks/{dist-MN2PDDPR.js → dist-6RUZ2JD6.js} +13 -1612
- package/chunks/{dist-R2SXPG74.js → dist-AHZNZWRI.js} +5 -4
- package/chunks/dist-GRQVFL3G.js +94147 -0
- package/chunks/{dist-BXDUQ2QY.js → dist-XTTPOFAH.js} +4 -3
- package/chunks/{edit-4LLGNYVZ.js → edit-RLFUTT5F.js} +20 -14
- package/chunks/{enter-worktree-E2R5XAFT.js → enter-worktree-CYRAPQKJ.js} +20 -14
- package/chunks/{exit-worktree-YVBYYYDD.js → exit-worktree-WQZM72QD.js} +20 -14
- package/chunks/{exitPlanMode-WD5IH7NS.js → exitPlanMode-STFEBQZE.js} +20 -14
- package/chunks/{geminiContentGenerator-LM65ADWM.js → geminiContentGenerator-DIV32SKO.js} +4 -3
- package/chunks/{glob-6X6OCEWE.js → glob-N3XO4RVI.js} +20 -14
- package/chunks/{grep-2UUPSSIQ.js → grep-AK5MP7P3.js} +20 -14
- package/chunks/{ls-MYXAM7LJ.js → ls-7FYQHPWF.js} +5 -3
- package/chunks/{lsp-PFGI35JL.js → lsp-DKG34USR.js} +5 -3
- package/chunks/{monitor-VUHPEGUW.js → monitor-IVBWJZEZ.js} +20 -14
- package/chunks/{notebook-edit-P4QVLW6I.js → notebook-edit-PM46AXFS.js} +21 -15
- package/chunks/{openaiContentGenerator-JH4YNZ3H.js → openaiContentGenerator-4QXCH7L2.js} +13 -10
- package/chunks/{qwenContentGenerator-5FE4UYUT.js → qwenContentGenerator-BLXQIIMX.js} +21 -16
- package/chunks/{read-file-J7DH4OKV.js → read-file-IEQAS3EZ.js} +10 -6
- package/chunks/{ripGrep-33DECY4F.js → ripGrep-HQO7IE4C.js} +17 -12
- package/chunks/{send-message-JUFP62VD.js → send-message-ZL7CDM7K.js} +5 -3
- package/chunks/{serve-7FX7MREA.js → serve-CYRAK4UM.js} +63 -1837
- package/chunks/{shell-ZNTQIRK6.js → shell-UZBGNO2Q.js} +17 -12
- package/chunks/{skill-CFCUIY23.js → skill-JVC34QYN.js} +24 -13
- package/chunks/{src-AHV2CWEQ.js → src-FOODLH7B.js} +49 -41
- package/chunks/{syntheticOutput-AKTXC6FR.js → syntheticOutput-U3YJ3GOO.js} +3 -2
- package/chunks/{task-stop-2NYFR2ES.js → task-stop-NPUI3YBA.js} +5 -3
- package/chunks/{todoWrite-WHZ2O2XP.js → todoWrite-Y6F7YEIM.js} +6 -4
- package/chunks/{tool-search-C2EMLFBJ.js → tool-search-P7PRPOW3.js} +13 -8
- package/chunks/{web-fetch-S6MZXPZ5.js → web-fetch-XWEK4TFX.js} +6 -4
- package/chunks/{write-file-EEPVRS4Q.js → write-file-SIIEUON5.js} +21 -15
- package/cli.js +412 -329
- package/package.json +2 -2
- package/chunks/chunk-JVD46YJV.js +0 -434
- package/chunks/undici-4ARNOH74.js +0 -8
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
// Force strict mode and setup for ESM
|
|
2
|
+
"use strict";
|
|
3
|
+
import {
|
|
4
|
+
Client,
|
|
5
|
+
StdioClientTransport
|
|
6
|
+
} from "./chunk-TI4GXJKO.js";
|
|
7
|
+
import "./chunk-AKBFRR6J.js";
|
|
8
|
+
import {
|
|
9
|
+
safeJsonStringify
|
|
10
|
+
} from "./chunk-W57YDFU5.js";
|
|
11
|
+
import {
|
|
12
|
+
BaseDeclarativeTool,
|
|
13
|
+
BaseToolInvocation
|
|
14
|
+
} from "./chunk-R2B65CAN.js";
|
|
15
|
+
import "./chunk-ACBGEKB7.js";
|
|
16
|
+
import "./chunk-QWSRH265.js";
|
|
17
|
+
import {
|
|
18
|
+
init_esbuild_shims
|
|
19
|
+
} from "./chunk-A4BMJM77.js";
|
|
20
|
+
import {
|
|
21
|
+
__name
|
|
22
|
+
} from "./chunk-J2S4EL5Y.js";
|
|
23
|
+
|
|
24
|
+
// packages/core/src/tools/computer-use/index.ts
|
|
25
|
+
init_esbuild_shims();
|
|
26
|
+
|
|
27
|
+
// packages/core/src/tools/computer-use/tool.ts
|
|
28
|
+
init_esbuild_shims();
|
|
29
|
+
|
|
30
|
+
// packages/core/src/tools/computer-use/client.ts
|
|
31
|
+
init_esbuild_shims();
|
|
32
|
+
|
|
33
|
+
// packages/core/src/tools/computer-use/constants.ts
|
|
34
|
+
init_esbuild_shims();
|
|
35
|
+
var PINNED_OPEN_COMPUTER_USE_VERSION = "0.1.51";
|
|
36
|
+
function resolveComputerUsePackageSpec() {
|
|
37
|
+
return process.env["QWEN_COMPUTER_USE_PACKAGE"] ?? `open-computer-use@${PINNED_OPEN_COMPUTER_USE_VERSION}`;
|
|
38
|
+
}
|
|
39
|
+
__name(resolveComputerUsePackageSpec, "resolveComputerUsePackageSpec");
|
|
40
|
+
|
|
41
|
+
// packages/core/src/tools/computer-use/client.ts
|
|
42
|
+
var ComputerUseClient = class _ComputerUseClient {
|
|
43
|
+
static {
|
|
44
|
+
__name(this, "ComputerUseClient");
|
|
45
|
+
}
|
|
46
|
+
static singleton;
|
|
47
|
+
packageSpec;
|
|
48
|
+
onProgress;
|
|
49
|
+
client;
|
|
50
|
+
startPromise;
|
|
51
|
+
constructor(options) {
|
|
52
|
+
this.packageSpec = options.packageSpec;
|
|
53
|
+
this.onProgress = options.onProgress ?? (() => {
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Shared singleton instance, created with default options on first
|
|
58
|
+
* access. Tests can replace it via `setSharedForTest()`.
|
|
59
|
+
*/
|
|
60
|
+
static shared() {
|
|
61
|
+
if (!_ComputerUseClient.singleton) {
|
|
62
|
+
_ComputerUseClient.singleton = new _ComputerUseClient({
|
|
63
|
+
packageSpec: resolveComputerUsePackageSpec()
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return _ComputerUseClient.singleton;
|
|
67
|
+
}
|
|
68
|
+
/** Test-only: replace the singleton. */
|
|
69
|
+
static setSharedForTest(replacement) {
|
|
70
|
+
_ComputerUseClient.singleton = replacement;
|
|
71
|
+
}
|
|
72
|
+
isStarted() {
|
|
73
|
+
return this.client !== void 0;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Start the upstream MCP server. Idempotent: concurrent callers share
|
|
77
|
+
* the same in-flight start promise.
|
|
78
|
+
*
|
|
79
|
+
* An optional `onProgress` callback can be supplied to receive download
|
|
80
|
+
* and startup messages during this call. It overrides the instance-level
|
|
81
|
+
* callback for the duration of the start operation only.
|
|
82
|
+
*
|
|
83
|
+
* Throws on spawn failure (network down, npx missing, etc.). The
|
|
84
|
+
* caller (bootstrap state machine) is responsible for mapping the
|
|
85
|
+
* throw into user-facing UX.
|
|
86
|
+
*/
|
|
87
|
+
async start(onProgress) {
|
|
88
|
+
if (this.client) return;
|
|
89
|
+
if (this.startPromise) return this.startPromise;
|
|
90
|
+
this.startPromise = this.doStart(onProgress).finally(() => {
|
|
91
|
+
this.startPromise = void 0;
|
|
92
|
+
});
|
|
93
|
+
return this.startPromise;
|
|
94
|
+
}
|
|
95
|
+
async doStart(onProgress) {
|
|
96
|
+
const progress = onProgress ?? this.onProgress;
|
|
97
|
+
progress("Starting Computer Use...");
|
|
98
|
+
const downloadHintTimer = setTimeout(() => {
|
|
99
|
+
progress(
|
|
100
|
+
"Downloading Computer Use binary (this can take ~60s on first use)..."
|
|
101
|
+
);
|
|
102
|
+
}, 3e3);
|
|
103
|
+
try {
|
|
104
|
+
const transport = new StdioClientTransport({
|
|
105
|
+
command: "npx",
|
|
106
|
+
args: ["-y", this.packageSpec, "mcp"],
|
|
107
|
+
// Inherit env so HTTPS_PROXY etc. flow through to npx
|
|
108
|
+
env: { ...process.env }
|
|
109
|
+
});
|
|
110
|
+
const client = new Client(
|
|
111
|
+
{ name: "qwen-code-computer-use", version: "1.0.0" },
|
|
112
|
+
{ capabilities: {} }
|
|
113
|
+
);
|
|
114
|
+
await client.connect(transport);
|
|
115
|
+
this.client = client;
|
|
116
|
+
} finally {
|
|
117
|
+
clearTimeout(downloadHintTimer);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* List the tools exposed by the upstream server. Used by the schema
|
|
122
|
+
* sync script and bootstrap diagnostics.
|
|
123
|
+
*/
|
|
124
|
+
async listTools() {
|
|
125
|
+
if (!this.client) throw new Error("ComputerUseClient not started");
|
|
126
|
+
return this.client.listTools();
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Call a tool by upstream name (NOT the qwen-code-facing
|
|
130
|
+
* `computer_use__` prefixed name). Returns the raw MCP result so the
|
|
131
|
+
* caller can inspect `isError` and parse text content.
|
|
132
|
+
*
|
|
133
|
+
* On transport-closed errors (e.g. macOS kills the upstream binary after
|
|
134
|
+
* the user grants Screen Recording permission), this method transparently
|
|
135
|
+
* tears down the stale connection, reconnects, and retries the call once.
|
|
136
|
+
* If the retry also fails, the error is re-thrown without further
|
|
137
|
+
* reconnect attempts.
|
|
138
|
+
*/
|
|
139
|
+
async callTool(name, args) {
|
|
140
|
+
if (!this.client) throw new Error("ComputerUseClient not started");
|
|
141
|
+
try {
|
|
142
|
+
return await this.client.callTool({
|
|
143
|
+
name,
|
|
144
|
+
arguments: args
|
|
145
|
+
});
|
|
146
|
+
} catch (err) {
|
|
147
|
+
if (!isTransportClosedError(err)) throw err;
|
|
148
|
+
await this.stop();
|
|
149
|
+
await this.start();
|
|
150
|
+
if (!this.client) throw new Error("ComputerUseClient reconnect failed");
|
|
151
|
+
return await this.client.callTool({
|
|
152
|
+
name,
|
|
153
|
+
arguments: args
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Tear down the child process. Safe to call multiple times. */
|
|
158
|
+
async stop() {
|
|
159
|
+
const client = this.client;
|
|
160
|
+
this.client = void 0;
|
|
161
|
+
if (client) {
|
|
162
|
+
try {
|
|
163
|
+
await client.close();
|
|
164
|
+
} catch {
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
function isTransportClosedError(err) {
|
|
170
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
171
|
+
return /connection closed|not connected/i.test(msg);
|
|
172
|
+
}
|
|
173
|
+
__name(isTransportClosedError, "isTransportClosedError");
|
|
174
|
+
|
|
175
|
+
// packages/core/src/tools/computer-use/bootstrap.ts
|
|
176
|
+
init_esbuild_shims();
|
|
177
|
+
import { execFile } from "node:child_process";
|
|
178
|
+
import { promisify } from "node:util";
|
|
179
|
+
import { homedir as homedir2 } from "node:os";
|
|
180
|
+
|
|
181
|
+
// packages/core/src/tools/computer-use/install-state.ts
|
|
182
|
+
init_esbuild_shims();
|
|
183
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
184
|
+
import { homedir } from "node:os";
|
|
185
|
+
import { join, dirname } from "node:path";
|
|
186
|
+
function installStatePathFor(home = homedir()) {
|
|
187
|
+
return join(home, ".qwen", "computer-use", "installed.json");
|
|
188
|
+
}
|
|
189
|
+
__name(installStatePathFor, "installStatePathFor");
|
|
190
|
+
async function loadInstallState(home = homedir()) {
|
|
191
|
+
try {
|
|
192
|
+
const text = await readFile(installStatePathFor(home), "utf8");
|
|
193
|
+
const parsed = JSON.parse(text);
|
|
194
|
+
if (typeof parsed?.approvedPackageSpec !== "string") return void 0;
|
|
195
|
+
if (typeof parsed?.approvedAtIso !== "string") return void 0;
|
|
196
|
+
return parsed;
|
|
197
|
+
} catch (err) {
|
|
198
|
+
if (err?.code === "ENOENT") return void 0;
|
|
199
|
+
return void 0;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
__name(loadInstallState, "loadInstallState");
|
|
203
|
+
async function saveInstallState(home = homedir(), state) {
|
|
204
|
+
const path = installStatePathFor(home);
|
|
205
|
+
await mkdir(dirname(path), { recursive: true });
|
|
206
|
+
await writeFile(path, JSON.stringify(state, null, 2), "utf8");
|
|
207
|
+
}
|
|
208
|
+
__name(saveInstallState, "saveInstallState");
|
|
209
|
+
async function isPackageSpecApproved(home = homedir(), packageSpec) {
|
|
210
|
+
const state = await loadInstallState(home);
|
|
211
|
+
return state?.approvedPackageSpec === packageSpec;
|
|
212
|
+
}
|
|
213
|
+
__name(isPackageSpecApproved, "isPackageSpecApproved");
|
|
214
|
+
|
|
215
|
+
// packages/core/src/tools/computer-use/permission-detector.ts
|
|
216
|
+
init_esbuild_shims();
|
|
217
|
+
|
|
218
|
+
// packages/core/src/tools/computer-use/bootstrap.ts
|
|
219
|
+
var execFileAsync = promisify(execFile);
|
|
220
|
+
function parseDoctorStdout(stdout) {
|
|
221
|
+
const accessibilityGranted = /accessibility\s*=\s*granted/i.test(stdout);
|
|
222
|
+
const screenRecordingGranted = /screenrecording\s*=\s*granted/i.test(stdout);
|
|
223
|
+
if (!accessibilityGranted) return "accessibility";
|
|
224
|
+
if (!screenRecordingGranted) return "screenRecording";
|
|
225
|
+
return "ok";
|
|
226
|
+
}
|
|
227
|
+
__name(parseDoctorStdout, "parseDoctorStdout");
|
|
228
|
+
async function probePermissionsViaDoctor(packageSpec) {
|
|
229
|
+
try {
|
|
230
|
+
const { stdout } = await execFileAsync(
|
|
231
|
+
"npx",
|
|
232
|
+
["-y", packageSpec, "doctor"],
|
|
233
|
+
{
|
|
234
|
+
timeout: 3e4,
|
|
235
|
+
env: process.env
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
return parseDoctorStdout(stdout);
|
|
239
|
+
} catch {
|
|
240
|
+
return "other";
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
__name(probePermissionsViaDoctor, "probePermissionsViaDoctor");
|
|
244
|
+
function defaultDeps() {
|
|
245
|
+
const packageSpec = resolveComputerUsePackageSpec();
|
|
246
|
+
return {
|
|
247
|
+
homeDir: homedir2(),
|
|
248
|
+
packageSpec,
|
|
249
|
+
platform: process.platform,
|
|
250
|
+
promptInstallApproval: /* @__PURE__ */ __name(async (spec) => {
|
|
251
|
+
process.stderr.write(
|
|
252
|
+
`
|
|
253
|
+
[Computer Use] First-time install
|
|
254
|
+
Package: ${spec}
|
|
255
|
+
This will fetch ~50MB from the npm registry the first time.
|
|
256
|
+
Computer Use can click, type, and read your desktop apps.
|
|
257
|
+
On macOS you'll be guided through Accessibility and Screen Recording permissions next.
|
|
258
|
+
Set QWEN_COMPUTER_USE_AUTO_APPROVE=1 to skip this prompt.
|
|
259
|
+
`
|
|
260
|
+
);
|
|
261
|
+
return process.env["QWEN_COMPUTER_USE_AUTO_APPROVE"] === "1";
|
|
262
|
+
}, "promptInstallApproval"),
|
|
263
|
+
probePermissions: probePermissionsViaDoctor
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
__name(defaultDeps, "defaultDeps");
|
|
267
|
+
async function runBootstrap(client, ctx, depsOverride) {
|
|
268
|
+
const deps = { ...defaultDeps(), ...depsOverride };
|
|
269
|
+
const pollIntervalMs = deps.pollIntervalMs ?? 5e3;
|
|
270
|
+
const pollTimeoutMs = deps.pollTimeoutMs ?? 10 * 6e4;
|
|
271
|
+
const approved = await isPackageSpecApproved(deps.homeDir, deps.packageSpec);
|
|
272
|
+
if (!approved) {
|
|
273
|
+
ctx.updateOutput?.("Computer Use needs to be installed (first use).");
|
|
274
|
+
const ok = await deps.promptInstallApproval(deps.packageSpec);
|
|
275
|
+
if (!ok) {
|
|
276
|
+
throw new Error(
|
|
277
|
+
`Computer Use install declined by user. Re-invoke the tool to be prompted again.`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
await saveInstallState(deps.homeDir, {
|
|
281
|
+
approvedPackageSpec: deps.packageSpec,
|
|
282
|
+
approvedAtIso: (/* @__PURE__ */ new Date()).toISOString()
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
const wasAlreadyStarted = client.isStarted();
|
|
286
|
+
if (!wasAlreadyStarted) {
|
|
287
|
+
await client.start(ctx.updateOutput);
|
|
288
|
+
}
|
|
289
|
+
if (wasAlreadyStarted) return;
|
|
290
|
+
if (deps.platform !== "darwin") return;
|
|
291
|
+
const probe = await deps.probePermissions(deps.packageSpec);
|
|
292
|
+
if (probe === "ok" || probe === "other") {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
ctx.updateOutput?.(
|
|
296
|
+
`Computer Use needs macOS permissions (${probe}). The onboarding window is opening \u2014 please grant Accessibility and Screen Recording, then this will continue automatically.`
|
|
297
|
+
);
|
|
298
|
+
let lastProbeKind = probe;
|
|
299
|
+
const startedAt = Date.now();
|
|
300
|
+
for (; ; ) {
|
|
301
|
+
if (ctx.signal.aborted) {
|
|
302
|
+
throw new Error("Computer Use bootstrap aborted.");
|
|
303
|
+
}
|
|
304
|
+
if (Date.now() - startedAt > pollTimeoutMs) {
|
|
305
|
+
throw new Error(
|
|
306
|
+
`Computer Use permission grant timed out after ${Math.round(pollTimeoutMs / 1e3)}s. Re-invoke the tool to retry.`
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
310
|
+
const next = await deps.probePermissions(deps.packageSpec);
|
|
311
|
+
if (next === "ok" || next === "other") return;
|
|
312
|
+
if (next !== lastProbeKind) {
|
|
313
|
+
ctx.updateOutput?.(
|
|
314
|
+
`Now waiting for ${next} permission. The onboarding window remains open \u2014 please grant this permission to continue.`
|
|
315
|
+
);
|
|
316
|
+
lastProbeKind = next;
|
|
317
|
+
}
|
|
318
|
+
const elapsedSec = Math.round((Date.now() - startedAt) / 1e3);
|
|
319
|
+
ctx.updateOutput?.(`Waiting for ${next} permission... (${elapsedSec}s)`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
__name(runBootstrap, "runBootstrap");
|
|
323
|
+
|
|
324
|
+
// packages/core/src/tools/computer-use/tool.ts
|
|
325
|
+
import { homedir as homedir3 } from "node:os";
|
|
326
|
+
var INSTALL_REASON = "This will install the open-computer-use binary (~50MB) via npx the first time. Computer Use can click, type, and read your desktop apps. On macOS you'll be guided through Accessibility / Screen Recording permissions next.";
|
|
327
|
+
var ComputerUseInvocation = class extends BaseToolInvocation {
|
|
328
|
+
constructor(upstreamName, params) {
|
|
329
|
+
super(params);
|
|
330
|
+
this.upstreamName = upstreamName;
|
|
331
|
+
}
|
|
332
|
+
static {
|
|
333
|
+
__name(this, "ComputerUseInvocation");
|
|
334
|
+
}
|
|
335
|
+
getDescription() {
|
|
336
|
+
return safeJsonStringify(this.params);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Always returns 'ask' so every desktop action surfaces through the
|
|
340
|
+
* standard tool-permission dialog. The PermissionManager rule system
|
|
341
|
+
* handles "always allow" per tool via ProceedAlwaysTool — that's the
|
|
342
|
+
* single source of truth for repeat-approval behavior.
|
|
343
|
+
*
|
|
344
|
+
* Earlier this returned 'allow' once the install-state file existed,
|
|
345
|
+
* which conflated install approval with per-action approval and
|
|
346
|
+
* effectively granted blanket permission for all 9 computer_use__*
|
|
347
|
+
* tools (including mutating actions like click / type_text / drag)
|
|
348
|
+
* after the first install confirmation. See PR #4590 review for the
|
|
349
|
+
* full discussion.
|
|
350
|
+
*/
|
|
351
|
+
async getDefaultPermission() {
|
|
352
|
+
return "ask";
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Builds the confirmation dialog. Two variants:
|
|
356
|
+
*
|
|
357
|
+
* 1. Install not yet approved → show install info (download size,
|
|
358
|
+
* permission flow to follow). onConfirm writes the install state
|
|
359
|
+
* so runBootstrap() inside execute() skips its env-var fallback
|
|
360
|
+
* prompt for headless contexts.
|
|
361
|
+
*
|
|
362
|
+
* 2. Install already approved → show per-action info (which tool +
|
|
363
|
+
* which args) so the user can decide whether THIS specific action
|
|
364
|
+
* is OK to perform.
|
|
365
|
+
*
|
|
366
|
+
* Both variants set permissionRules so the standard "Always allow"
|
|
367
|
+
* outcomes (ProceedAlwaysTool / ProceedAlwaysUser / ProceedAlwaysProject)
|
|
368
|
+
* add a rule via PermissionManager — subsequent calls of the SAME
|
|
369
|
+
* tool then skip the dialog. Different tools each need their own
|
|
370
|
+
* "always allow" choice; install approval no longer grants blanket
|
|
371
|
+
* access.
|
|
372
|
+
*
|
|
373
|
+
* On Cancel: install state is NOT written; execute() / runBootstrap()
|
|
374
|
+
* will use the env-var fallback (QWEN_COMPUTER_USE_AUTO_APPROVE),
|
|
375
|
+
* which defaults to refusing — producing a clear error message.
|
|
376
|
+
*/
|
|
377
|
+
async getConfirmationDetails(_abortSignal) {
|
|
378
|
+
const permissionRules = [`computer_use__${this.upstreamName}`];
|
|
379
|
+
const installApproved = await isPackageSpecApproved(
|
|
380
|
+
homedir3(),
|
|
381
|
+
resolveComputerUsePackageSpec()
|
|
382
|
+
);
|
|
383
|
+
const prompt = installApproved ? `Tool: computer_use__${this.upstreamName}
|
|
384
|
+
|
|
385
|
+
Args: ${safeJsonStringify(this.params)}
|
|
386
|
+
|
|
387
|
+
This will act on your desktop via the Computer Use binary.` : `Tool: computer_use__${this.upstreamName}
|
|
388
|
+
|
|
389
|
+
${INSTALL_REASON}`;
|
|
390
|
+
const details = {
|
|
391
|
+
type: "info",
|
|
392
|
+
title: `Allow Computer Use (${this.upstreamName})`,
|
|
393
|
+
prompt,
|
|
394
|
+
permissionRules,
|
|
395
|
+
onConfirm: /* @__PURE__ */ __name(async (outcome, _payload) => {
|
|
396
|
+
if (outcome !== "cancel" /* Cancel */) {
|
|
397
|
+
await saveInstallState(homedir3(), {
|
|
398
|
+
approvedPackageSpec: resolveComputerUsePackageSpec(),
|
|
399
|
+
approvedAtIso: (/* @__PURE__ */ new Date()).toISOString()
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}, "onConfirm")
|
|
403
|
+
};
|
|
404
|
+
return details;
|
|
405
|
+
}
|
|
406
|
+
async execute(signal, updateOutput) {
|
|
407
|
+
const client = ComputerUseClient.shared();
|
|
408
|
+
await runBootstrap(client, { signal, updateOutput });
|
|
409
|
+
let mcpResult;
|
|
410
|
+
try {
|
|
411
|
+
mcpResult = await client.callTool(this.upstreamName, this.params);
|
|
412
|
+
} catch (err) {
|
|
413
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
414
|
+
return {
|
|
415
|
+
llmContent: `Computer Use tool '${this.upstreamName}' failed: ${message}`,
|
|
416
|
+
returnDisplay: `Error: ${message}`,
|
|
417
|
+
error: { message }
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
const llmContent = buildLlmContent(mcpResult.content, this.upstreamName);
|
|
421
|
+
const returnDisplay = buildDisplayText(mcpResult.content);
|
|
422
|
+
if (mcpResult.isError) {
|
|
423
|
+
const errorText = returnDisplay || `Tool '${this.upstreamName}' returned isError=true`;
|
|
424
|
+
return {
|
|
425
|
+
llmContent: llmContent || errorText,
|
|
426
|
+
returnDisplay: errorText,
|
|
427
|
+
error: { message: errorText }
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
llmContent,
|
|
432
|
+
returnDisplay
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
var ComputerUseTool = class extends BaseDeclarativeTool {
|
|
437
|
+
constructor(upstreamName, schema) {
|
|
438
|
+
const qwenName = `computer_use__${upstreamName}`;
|
|
439
|
+
super(
|
|
440
|
+
qwenName,
|
|
441
|
+
qwenName,
|
|
442
|
+
// displayName == name; no MCP branding in UI
|
|
443
|
+
schema.description,
|
|
444
|
+
"other" /* Other */,
|
|
445
|
+
schema.parameterSchema,
|
|
446
|
+
true,
|
|
447
|
+
// isOutputMarkdown — many results are JSON-ish text or screenshots
|
|
448
|
+
true,
|
|
449
|
+
// canUpdateOutput — bootstrap streams progress
|
|
450
|
+
true,
|
|
451
|
+
// shouldDefer — surface only via ToolSearch
|
|
452
|
+
false,
|
|
453
|
+
// alwaysLoad
|
|
454
|
+
`computer use desktop click type screenshot mouse keyboard scroll drag automation gui app native`
|
|
455
|
+
);
|
|
456
|
+
this.upstreamName = upstreamName;
|
|
457
|
+
}
|
|
458
|
+
static {
|
|
459
|
+
__name(this, "ComputerUseTool");
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Coerce parameter types before schema validation.
|
|
463
|
+
* Models can send the wrong JS type for a field:
|
|
464
|
+
* - qwen3.6 sends `element_index: 2` (number) but upstream wants "2" (string)
|
|
465
|
+
* - Some models send `x: "500"` (string) but upstream wants 500 (number)
|
|
466
|
+
* Pre-coercing avoids spurious validation failures without loosening schema types.
|
|
467
|
+
*/
|
|
468
|
+
validateToolParams(params) {
|
|
469
|
+
const coerced = coerceTypes(
|
|
470
|
+
params,
|
|
471
|
+
this.parameterSchema
|
|
472
|
+
);
|
|
473
|
+
return super.validateToolParams(coerced);
|
|
474
|
+
}
|
|
475
|
+
build(params) {
|
|
476
|
+
const coerced = coerceTypes(
|
|
477
|
+
params,
|
|
478
|
+
this.parameterSchema
|
|
479
|
+
);
|
|
480
|
+
return super.build(coerced);
|
|
481
|
+
}
|
|
482
|
+
createInvocation(params) {
|
|
483
|
+
return new ComputerUseInvocation(this.upstreamName, params);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
function coerceTypes(params, schema) {
|
|
487
|
+
const properties = schema.properties;
|
|
488
|
+
if (!properties) return params;
|
|
489
|
+
const result = { ...params };
|
|
490
|
+
for (const [key, value] of Object.entries(result)) {
|
|
491
|
+
const fieldType = properties[key]?.type;
|
|
492
|
+
if ((fieldType === "integer" || fieldType === "number") && typeof value === "string") {
|
|
493
|
+
const trimmed = value.trim();
|
|
494
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
495
|
+
const parsed = fieldType === "integer" ? parseInt(trimmed, 10) : parseFloat(trimmed);
|
|
496
|
+
if (Number.isFinite(parsed)) {
|
|
497
|
+
result[key] = parsed;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
} else if (fieldType === "string" && typeof value === "number") {
|
|
501
|
+
result[key] = String(value);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return result;
|
|
505
|
+
}
|
|
506
|
+
__name(coerceTypes, "coerceTypes");
|
|
507
|
+
function buildLlmContent(content, toolName) {
|
|
508
|
+
const parts = [];
|
|
509
|
+
for (const block of content) {
|
|
510
|
+
if (block.type === "text" && block.text) {
|
|
511
|
+
parts.push({ text: block.text });
|
|
512
|
+
} else if ((block.type === "image" || block.type === "audio") && block.mimeType && block.data) {
|
|
513
|
+
parts.push({
|
|
514
|
+
text: `[Tool '${toolName}' provided the following ${block.type} data with mime-type: ${block.mimeType}]`
|
|
515
|
+
});
|
|
516
|
+
parts.push({
|
|
517
|
+
inlineData: {
|
|
518
|
+
mimeType: block.mimeType,
|
|
519
|
+
data: block.data
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const hasNonText = parts.some((p) => p.inlineData !== void 0);
|
|
525
|
+
if (!hasNonText) {
|
|
526
|
+
return parts.map((p) => p.text ?? "").filter(Boolean).join("\n");
|
|
527
|
+
}
|
|
528
|
+
return parts;
|
|
529
|
+
}
|
|
530
|
+
__name(buildLlmContent, "buildLlmContent");
|
|
531
|
+
function buildDisplayText(content) {
|
|
532
|
+
return content.map((block) => block.type === "text" ? block.text ?? "" : "").filter(Boolean).join("\n");
|
|
533
|
+
}
|
|
534
|
+
__name(buildDisplayText, "buildDisplayText");
|
|
535
|
+
|
|
536
|
+
// packages/core/src/tools/computer-use/schemas.ts
|
|
537
|
+
init_esbuild_shims();
|
|
538
|
+
var COMPUTER_USE_TOOL_NAMES = [
|
|
539
|
+
"click",
|
|
540
|
+
"drag",
|
|
541
|
+
"get_app_state",
|
|
542
|
+
"list_apps",
|
|
543
|
+
"perform_secondary_action",
|
|
544
|
+
"press_key",
|
|
545
|
+
"scroll",
|
|
546
|
+
"set_value",
|
|
547
|
+
"type_text"
|
|
548
|
+
];
|
|
549
|
+
var COMPUTER_USE_SCHEMAS = {
|
|
550
|
+
click: {
|
|
551
|
+
description: "Click an element by index or pixel coordinates from screenshot. This tool is part of plugin `Computer Use`.",
|
|
552
|
+
parameterSchema: {
|
|
553
|
+
type: "object",
|
|
554
|
+
properties: {
|
|
555
|
+
click_count: {
|
|
556
|
+
type: "integer",
|
|
557
|
+
description: "Number of clicks. Defaults to 1"
|
|
558
|
+
},
|
|
559
|
+
mouse_button: {
|
|
560
|
+
description: "Mouse button to click. Defaults to left.",
|
|
561
|
+
enum: ["left", "right", "middle"],
|
|
562
|
+
type: "string"
|
|
563
|
+
},
|
|
564
|
+
element_index: {
|
|
565
|
+
type: "string",
|
|
566
|
+
description: "Element index to click"
|
|
567
|
+
},
|
|
568
|
+
y: {
|
|
569
|
+
type: "number",
|
|
570
|
+
description: "Y coordinate in screenshot pixel coordinates"
|
|
571
|
+
},
|
|
572
|
+
app: {
|
|
573
|
+
type: "string",
|
|
574
|
+
description: "App name or bundle identifier"
|
|
575
|
+
},
|
|
576
|
+
x: {
|
|
577
|
+
description: "X coordinate in screenshot pixel coordinates",
|
|
578
|
+
type: "number"
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
required: ["app"],
|
|
582
|
+
additionalProperties: false
|
|
583
|
+
}
|
|
584
|
+
},
|
|
585
|
+
drag: {
|
|
586
|
+
description: "Drag from one point to another using pixel coordinates. This tool is part of plugin `Computer Use`.",
|
|
587
|
+
parameterSchema: {
|
|
588
|
+
type: "object",
|
|
589
|
+
properties: {
|
|
590
|
+
app: {
|
|
591
|
+
type: "string",
|
|
592
|
+
description: "App name or bundle identifier"
|
|
593
|
+
},
|
|
594
|
+
from_x: {
|
|
595
|
+
description: "Start X coordinate",
|
|
596
|
+
type: "number"
|
|
597
|
+
},
|
|
598
|
+
from_y: {
|
|
599
|
+
type: "number",
|
|
600
|
+
description: "Start Y coordinate"
|
|
601
|
+
},
|
|
602
|
+
to_x: {
|
|
603
|
+
description: "End X coordinate",
|
|
604
|
+
type: "number"
|
|
605
|
+
},
|
|
606
|
+
to_y: {
|
|
607
|
+
type: "number",
|
|
608
|
+
description: "End Y coordinate"
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
required: ["app", "from_x", "from_y", "to_x", "to_y"],
|
|
612
|
+
additionalProperties: false
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
get_app_state: {
|
|
616
|
+
description: "Start an app use session if needed, then get the state of the app's key window and return a screenshot and accessibility tree. This must be called once per assistant turn before interacting with the app. This tool is part of plugin `Computer Use`.",
|
|
617
|
+
parameterSchema: {
|
|
618
|
+
type: "object",
|
|
619
|
+
properties: {
|
|
620
|
+
app: {
|
|
621
|
+
description: "App name or bundle identifier",
|
|
622
|
+
type: "string"
|
|
623
|
+
}
|
|
624
|
+
},
|
|
625
|
+
required: ["app"],
|
|
626
|
+
additionalProperties: false
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
list_apps: {
|
|
630
|
+
description: "List the apps on this computer. Returns the set of apps that are currently running, as well as any that have been used in the last 14 days, including details on usage frequency. This tool is part of plugin `Computer Use`.",
|
|
631
|
+
parameterSchema: {
|
|
632
|
+
type: "object",
|
|
633
|
+
properties: {},
|
|
634
|
+
additionalProperties: false
|
|
635
|
+
}
|
|
636
|
+
},
|
|
637
|
+
perform_secondary_action: {
|
|
638
|
+
description: "Invoke a secondary accessibility action exposed by an element. This tool is part of plugin `Computer Use`.",
|
|
639
|
+
parameterSchema: {
|
|
640
|
+
type: "object",
|
|
641
|
+
properties: {
|
|
642
|
+
action: {
|
|
643
|
+
description: "Secondary accessibility action name",
|
|
644
|
+
type: "string"
|
|
645
|
+
},
|
|
646
|
+
app: {
|
|
647
|
+
description: "App name or bundle identifier",
|
|
648
|
+
type: "string"
|
|
649
|
+
},
|
|
650
|
+
element_index: {
|
|
651
|
+
description: "Element identifier",
|
|
652
|
+
type: "string"
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
required: ["app", "element_index", "action"],
|
|
656
|
+
additionalProperties: false
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
press_key: {
|
|
660
|
+
description: 'Press a key or key-combination on the keyboard, including modifier and navigation keys.\n - This supports xdotool\'s `key` syntax.\n - Examples: "a", "Return", "Tab", "super+c", "Up", "KP_0" (for the numpad 0 key). This tool is part of plugin `Computer Use`.',
|
|
661
|
+
parameterSchema: {
|
|
662
|
+
type: "object",
|
|
663
|
+
properties: {
|
|
664
|
+
app: {
|
|
665
|
+
description: "App name or bundle identifier",
|
|
666
|
+
type: "string"
|
|
667
|
+
},
|
|
668
|
+
key: {
|
|
669
|
+
type: "string",
|
|
670
|
+
description: "Key or key combination to press"
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
required: ["app", "key"],
|
|
674
|
+
additionalProperties: false
|
|
675
|
+
}
|
|
676
|
+
},
|
|
677
|
+
scroll: {
|
|
678
|
+
description: "Scroll an element in a direction by a number of pages. This tool is part of plugin `Computer Use`.",
|
|
679
|
+
parameterSchema: {
|
|
680
|
+
type: "object",
|
|
681
|
+
properties: {
|
|
682
|
+
pages: {
|
|
683
|
+
type: "number",
|
|
684
|
+
description: "Number of pages to scroll. Fractional values are supported. Defaults to 1"
|
|
685
|
+
},
|
|
686
|
+
app: {
|
|
687
|
+
description: "App name or bundle identifier",
|
|
688
|
+
type: "string"
|
|
689
|
+
},
|
|
690
|
+
element_index: {
|
|
691
|
+
description: "Element identifier",
|
|
692
|
+
type: "string"
|
|
693
|
+
},
|
|
694
|
+
direction: {
|
|
695
|
+
description: "Scroll direction: up, down, left, or right",
|
|
696
|
+
type: "string"
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
required: ["app", "element_index", "direction"],
|
|
700
|
+
additionalProperties: false
|
|
701
|
+
}
|
|
702
|
+
},
|
|
703
|
+
set_value: {
|
|
704
|
+
description: "Set the value of a settable accessibility element. This tool is part of plugin `Computer Use`.",
|
|
705
|
+
parameterSchema: {
|
|
706
|
+
type: "object",
|
|
707
|
+
properties: {
|
|
708
|
+
element_index: {
|
|
709
|
+
type: "string",
|
|
710
|
+
description: "Element identifier"
|
|
711
|
+
},
|
|
712
|
+
value: {
|
|
713
|
+
type: "string",
|
|
714
|
+
description: "Value to assign"
|
|
715
|
+
},
|
|
716
|
+
app: {
|
|
717
|
+
description: "App name or bundle identifier",
|
|
718
|
+
type: "string"
|
|
719
|
+
}
|
|
720
|
+
},
|
|
721
|
+
required: ["app", "element_index", "value"],
|
|
722
|
+
additionalProperties: false
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
type_text: {
|
|
726
|
+
description: "Type literal text using keyboard input. This tool is part of plugin `Computer Use`.",
|
|
727
|
+
parameterSchema: {
|
|
728
|
+
type: "object",
|
|
729
|
+
properties: {
|
|
730
|
+
app: {
|
|
731
|
+
type: "string",
|
|
732
|
+
description: "App name or bundle identifier"
|
|
733
|
+
},
|
|
734
|
+
text: {
|
|
735
|
+
type: "string",
|
|
736
|
+
description: "Literal text to type"
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
required: ["app", "text"],
|
|
740
|
+
additionalProperties: false
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
// packages/core/src/tools/computer-use/index.ts
|
|
746
|
+
async function registerComputerUseTools(registerLazy) {
|
|
747
|
+
for (const upstreamName of COMPUTER_USE_TOOL_NAMES) {
|
|
748
|
+
const schema = COMPUTER_USE_SCHEMAS[upstreamName];
|
|
749
|
+
const qwenName = `computer_use__${upstreamName}`;
|
|
750
|
+
await registerLazy(
|
|
751
|
+
qwenName,
|
|
752
|
+
async () => new ComputerUseTool(upstreamName, schema)
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
__name(registerComputerUseTools, "registerComputerUseTools");
|
|
757
|
+
export {
|
|
758
|
+
COMPUTER_USE_SCHEMAS,
|
|
759
|
+
COMPUTER_USE_TOOL_NAMES,
|
|
760
|
+
ComputerUseClient,
|
|
761
|
+
ComputerUseTool,
|
|
762
|
+
registerComputerUseTools
|
|
763
|
+
};
|
|
764
|
+
/**
|
|
765
|
+
* @license
|
|
766
|
+
* Copyright 2025 Qwen Team
|
|
767
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
768
|
+
*/
|