@kernel.chat/kbot 3.99.30 → 3.99.33
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 +1 -1
- package/dist/agents/security-agent.d.ts +31 -0
- package/dist/agents/security-agent.js +180 -0
- package/dist/agents/security-rules.d.ts +35 -0
- package/dist/agents/security-rules.js +206 -0
- package/dist/agents/specialists.d.ts +6 -0
- package/dist/agents/specialists.js +45 -0
- package/dist/architect.js +5 -0
- package/dist/auth.js +1 -1
- package/dist/channels/matrix.d.ts +4 -0
- package/dist/channels/matrix.js +28 -0
- package/dist/channels/office.d.ts +78 -0
- package/dist/channels/office.js +169 -0
- package/dist/channels/registry.d.ts +8 -0
- package/dist/channels/registry.js +38 -0
- package/dist/channels/signal.d.ts +4 -0
- package/dist/channels/signal.js +29 -0
- package/dist/channels/slack.d.ts +4 -0
- package/dist/channels/slack.js +97 -0
- package/dist/channels/teams.d.ts +4 -0
- package/dist/channels/teams.js +29 -0
- package/dist/channels/telegram.d.ts +4 -0
- package/dist/channels/telegram.js +28 -0
- package/dist/channels/types.d.ts +50 -0
- package/dist/channels/types.js +13 -0
- package/dist/channels/whatsapp.d.ts +4 -0
- package/dist/channels/whatsapp.js +28 -0
- package/dist/computer-use-coordinator.d.ts +44 -0
- package/dist/computer-use-coordinator.js +0 -0
- package/dist/doctor.js +6 -3
- package/dist/file-library.d.ts +76 -0
- package/dist/file-library.js +269 -0
- package/dist/managed-agents-anthropic.d.ts +90 -0
- package/dist/managed-agents-anthropic.js +123 -0
- package/dist/plugins-integrity.d.ts +72 -0
- package/dist/plugins-integrity.js +153 -0
- package/dist/plugins.d.ts +13 -2
- package/dist/plugins.js +87 -10
- package/dist/tools/anthropic-managed-agents-tools.d.ts +22 -0
- package/dist/tools/anthropic-managed-agents-tools.js +191 -0
- package/dist/tools/channel-tools.d.ts +4 -0
- package/dist/tools/channel-tools.js +80 -0
- package/dist/tools/computer-coordinator-tools.d.ts +13 -0
- package/dist/tools/computer-coordinator-tools.js +104 -0
- package/dist/tools/computer.js +463 -299
- package/dist/tools/file-library-tools.d.ts +12 -0
- package/dist/tools/file-library-tools.js +191 -0
- package/dist/tools/image-thoughtful.d.ts +31 -0
- package/dist/tools/image-thoughtful.js +233 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/security-agent-tools.d.ts +34 -0
- package/dist/tools/security-agent-tools.js +30 -0
- package/dist/tools/swarm-2026-04.d.ts +2 -0
- package/dist/tools/swarm-2026-04.js +91 -0
- package/dist/tools/workspace-agent-tools.d.ts +19 -0
- package/dist/tools/workspace-agent-tools.js +191 -0
- package/dist/workspace-agents.d.ts +132 -0
- package/dist/workspace-agents.js +379 -0
- package/package.json +1 -1
package/dist/tools/computer.js
CHANGED
|
@@ -3,66 +3,111 @@
|
|
|
3
3
|
// Capabilities: screenshot, click, type, scroll, drag, key combos,
|
|
4
4
|
// app launch/focus, window management (list/resize/move/minimize)
|
|
5
5
|
//
|
|
6
|
-
// Safety: per-app
|
|
7
|
-
// terminal excluded from screenshots,
|
|
6
|
+
// Safety: per-app sub-locks via the Coordinator (parallel multi-agent),
|
|
7
|
+
// per-app session approval, terminal excluded from screenshots,
|
|
8
|
+
// permission check flow.
|
|
8
9
|
//
|
|
9
10
|
// Requires explicit opt-in via --computer-use flag.
|
|
10
11
|
// macOS: AppleScript + screencapture + cliclick fallback
|
|
11
12
|
// Linux: xdotool + import/gnome-screenshot
|
|
12
13
|
import { execSync } from 'node:child_process';
|
|
14
|
+
import { randomUUID } from 'node:crypto';
|
|
13
15
|
import { tmpdir, homedir } from 'node:os';
|
|
14
16
|
import { join } from 'node:path';
|
|
15
|
-
import { readFileSync,
|
|
17
|
+
import { readFileSync, unlinkSync, existsSync, mkdirSync, rmSync } from 'node:fs';
|
|
16
18
|
import { registerTool } from './index.js';
|
|
19
|
+
import { Coordinator } from '../computer-use-coordinator.js';
|
|
17
20
|
const platform = process.platform;
|
|
18
21
|
const LOCK_DIR = join(homedir(), '.kbot');
|
|
22
|
+
// Legacy single-session lock path. Retained as a constant for back-compat
|
|
23
|
+
// with any callers that referenced it; the actual locking is now performed
|
|
24
|
+
// per-app via the Coordinator.
|
|
19
25
|
const LOCK_FILE = join(LOCK_DIR, 'computer-use.lock');
|
|
20
26
|
// ── Session state ──────────────────────────────────────────────────
|
|
21
27
|
/** Apps approved for this session */
|
|
22
28
|
const approvedApps = new Set();
|
|
23
29
|
/** Whether permissions have been verified this session */
|
|
24
30
|
let permissionsVerified = false;
|
|
25
|
-
/**
|
|
31
|
+
/** Legacy single-session "lock held" flag — kept so computer_check / screen_info
|
|
32
|
+
* can still report something meaningful. The real locking is the Coordinator. */
|
|
26
33
|
let lockHeld = false;
|
|
27
|
-
// ──
|
|
34
|
+
// ── Coordinator (per-app sub-locks) ────────────────────────────────
|
|
35
|
+
/** Stable agent id for this kbot process. Override with KBOT_COMPUTER_USE_AGENT_ID
|
|
36
|
+
* for deterministic tests / multi-process coordination. */
|
|
37
|
+
const AGENT_ID = process.env.KBOT_COMPUTER_USE_AGENT_ID || randomUUID();
|
|
38
|
+
/** Module-scoped coordinator. Lock files live on disk under
|
|
39
|
+
* ~/.kbot/computer-use/<app>.lock, so even if other processes have their own
|
|
40
|
+
* Coordinator instance, they'll see each other's locks. */
|
|
41
|
+
const coordinator = new Coordinator();
|
|
42
|
+
/** Format a Coordinator denial as a single error string. */
|
|
43
|
+
function formatDenied(app, heldBy, since) {
|
|
44
|
+
const sinceStr = since ? new Date(since).toISOString() : 'unknown';
|
|
45
|
+
return `computer_use: app '${app}' is held by ${heldBy ?? 'unknown'} since ${sinceStr} — wait or unregister that agent.`;
|
|
46
|
+
}
|
|
47
|
+
/** Acquire a per-app claim. Returns an error string on denial, or null on success.
|
|
48
|
+
* When `app` is undefined (legacy callers that didn't specify an app), falls
|
|
49
|
+
* back to the single-session legacy behaviour: just mark `lockHeld = true` and
|
|
50
|
+
* let any prior global lock file get cleaned up.
|
|
51
|
+
*
|
|
52
|
+
* App names are normalised to lowercase for the Coordinator so that
|
|
53
|
+
* `Ableton` and `ableton` collide on the same lock file. */
|
|
54
|
+
function claimApp(app) {
|
|
55
|
+
if (!app) {
|
|
56
|
+
// Legacy single-lock fallback — don't break existing scripts that call a
|
|
57
|
+
// tool without specifying an app. We still set lockHeld so screen_info /
|
|
58
|
+
// computer_check report sane state.
|
|
59
|
+
if (!existsSync(LOCK_DIR))
|
|
60
|
+
mkdirSync(LOCK_DIR, { recursive: true });
|
|
61
|
+
lockHeld = true;
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const key = app.toLowerCase();
|
|
65
|
+
// Make sure this agent is registered for the app before claiming.
|
|
66
|
+
if (!approvedApps.has(key)) {
|
|
67
|
+
return `Error: ${app} is not approved. Call app_approve first.`;
|
|
68
|
+
}
|
|
69
|
+
// Re-register so the coordinator knows about the (possibly new) app.
|
|
70
|
+
coordinator.register(AGENT_ID, { apps: [...approvedApps] });
|
|
71
|
+
const result = coordinator.claim(AGENT_ID, key);
|
|
72
|
+
if (!result.granted) {
|
|
73
|
+
return formatDenied(app, result.heldBy, result.since);
|
|
74
|
+
}
|
|
75
|
+
lockHeld = true;
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
/** Release a per-app claim. Safe to call when nothing was claimed. */
|
|
79
|
+
function releaseApp(app) {
|
|
80
|
+
if (!app)
|
|
81
|
+
return;
|
|
82
|
+
try {
|
|
83
|
+
coordinator.release(AGENT_ID, app.toLowerCase());
|
|
84
|
+
}
|
|
85
|
+
catch { /* best effort */ }
|
|
86
|
+
}
|
|
87
|
+
// Clean up any locks held by this process on exit.
|
|
88
|
+
const cleanupOnExit = () => {
|
|
89
|
+
try {
|
|
90
|
+
coordinator.unregister(AGENT_ID);
|
|
91
|
+
}
|
|
92
|
+
catch { /* best effort */ }
|
|
93
|
+
try {
|
|
94
|
+
if (existsSync(LOCK_FILE))
|
|
95
|
+
rmSync(LOCK_FILE);
|
|
96
|
+
}
|
|
97
|
+
catch { /* best effort */ }
|
|
98
|
+
};
|
|
99
|
+
process.on('exit', cleanupOnExit);
|
|
100
|
+
process.on('SIGINT', cleanupOnExit);
|
|
101
|
+
process.on('SIGTERM', cleanupOnExit);
|
|
102
|
+
// ── Legacy lock helpers (kept as no-op stubs for back-compat) ──────
|
|
103
|
+
/** @deprecated kept for back-compat — Coordinator handles locking now. */
|
|
28
104
|
function acquireLock() {
|
|
29
105
|
if (!existsSync(LOCK_DIR))
|
|
30
106
|
mkdirSync(LOCK_DIR, { recursive: true });
|
|
31
|
-
if (existsSync(LOCK_FILE)) {
|
|
32
|
-
try {
|
|
33
|
-
const lock = JSON.parse(readFileSync(LOCK_FILE, 'utf-8'));
|
|
34
|
-
// Check if the holding process is still alive
|
|
35
|
-
try {
|
|
36
|
-
process.kill(lock.pid, 0); // signal 0 = existence check
|
|
37
|
-
return `Computer use is held by another kbot session (PID ${lock.pid}, started ${lock.started}). Finish that session first.`;
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
// Process is dead — stale lock, clean it up
|
|
41
|
-
rmSync(LOCK_FILE);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
rmSync(LOCK_FILE);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
writeFileSync(LOCK_FILE, JSON.stringify({
|
|
49
|
-
pid: process.pid,
|
|
50
|
-
started: new Date().toISOString(),
|
|
51
|
-
}));
|
|
52
107
|
lockHeld = true;
|
|
53
|
-
// Clean up on exit
|
|
54
|
-
const cleanup = () => {
|
|
55
|
-
try {
|
|
56
|
-
if (existsSync(LOCK_FILE))
|
|
57
|
-
rmSync(LOCK_FILE);
|
|
58
|
-
}
|
|
59
|
-
catch { /* best effort */ }
|
|
60
|
-
};
|
|
61
|
-
process.on('exit', cleanup);
|
|
62
|
-
process.on('SIGINT', cleanup);
|
|
63
|
-
process.on('SIGTERM', cleanup);
|
|
64
108
|
return null;
|
|
65
109
|
}
|
|
110
|
+
/** @deprecated kept for back-compat — Coordinator handles locking now. */
|
|
66
111
|
function releaseLock() {
|
|
67
112
|
if (lockHeld) {
|
|
68
113
|
try {
|
|
@@ -141,11 +186,12 @@ function ensurePermissions() {
|
|
|
141
186
|
permissionsVerified = true;
|
|
142
187
|
return null;
|
|
143
188
|
}
|
|
144
|
-
/** Ensure lock
|
|
189
|
+
/** Ensure base lock dir exists. Per-app claims are handled by claimApp(). */
|
|
145
190
|
function ensureLock() {
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
191
|
+
if (!existsSync(LOCK_DIR))
|
|
192
|
+
mkdirSync(LOCK_DIR, { recursive: true });
|
|
193
|
+
lockHeld = true;
|
|
194
|
+
return null;
|
|
149
195
|
}
|
|
150
196
|
// ── App approval system ────────────────────────────────────────────
|
|
151
197
|
/** Apps with elevated access warnings */
|
|
@@ -218,11 +264,13 @@ export function registerComputerTools() {
|
|
|
218
264
|
if (permErr)
|
|
219
265
|
return permErr;
|
|
220
266
|
const approvedList = getApprovedApps();
|
|
267
|
+
const coordStatus = coordinator.status();
|
|
221
268
|
return [
|
|
222
269
|
'Computer use ready.',
|
|
223
270
|
`Platform: ${platform}`,
|
|
224
|
-
`
|
|
271
|
+
`Agent ID: ${AGENT_ID}`,
|
|
225
272
|
`Approved apps: ${approvedList.length > 0 ? approvedList.join(', ') : 'none yet (use app_approve to approve apps)'}`,
|
|
273
|
+
`Coordinator: ${JSON.stringify(coordStatus)}`,
|
|
226
274
|
].join('\n');
|
|
227
275
|
},
|
|
228
276
|
});
|
|
@@ -244,6 +292,14 @@ export function registerComputerTools() {
|
|
|
244
292
|
result += `Warning: ${app} — ${warning}\n`;
|
|
245
293
|
}
|
|
246
294
|
approveApp(app);
|
|
295
|
+
// Re-register with the coordinator so it knows this agent intends to
|
|
296
|
+
// drive the newly-approved app.
|
|
297
|
+
try {
|
|
298
|
+
coordinator.register(AGENT_ID, { apps: [...approvedApps] });
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
return `${result}Approved ${app} but coordinator registration failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
302
|
+
}
|
|
247
303
|
result += `Approved ${app} for this session.`;
|
|
248
304
|
return result;
|
|
249
305
|
},
|
|
@@ -273,29 +329,37 @@ export function registerComputerTools() {
|
|
|
273
329
|
if (!isAppApproved(app)) {
|
|
274
330
|
return `Error: ${app} is not approved. Call app_approve first.`;
|
|
275
331
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
332
|
+
const claimErr = claimApp(app);
|
|
333
|
+
if (claimErr)
|
|
334
|
+
return `Error: ${claimErr}`;
|
|
335
|
+
try {
|
|
336
|
+
if (platform === 'darwin') {
|
|
337
|
+
try {
|
|
338
|
+
osascript(`tell application "${escapeAppleScript(app)}" to activate`);
|
|
339
|
+
// Wait a beat for the app to come forward
|
|
340
|
+
await new Promise(r => setTimeout(r, 500));
|
|
341
|
+
return `Launched/focused: ${app}`;
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
return `Error launching ${app}: ${err instanceof Error ? err.message : String(err)}`;
|
|
345
|
+
}
|
|
282
346
|
}
|
|
283
|
-
|
|
284
|
-
|
|
347
|
+
else if (platform === 'linux') {
|
|
348
|
+
try {
|
|
349
|
+
execSync(`wmctrl -a "${app}" 2>/dev/null || xdg-open "${app}" 2>/dev/null`, {
|
|
350
|
+
timeout: 10_000, stdio: 'pipe',
|
|
351
|
+
});
|
|
352
|
+
return `Launched/focused: ${app}`;
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return `Error: Could not launch ${app}. Ensure it's installed.`;
|
|
356
|
+
}
|
|
285
357
|
}
|
|
358
|
+
return 'Error: Unsupported platform';
|
|
286
359
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
execSync(`wmctrl -a "${app}" 2>/dev/null || xdg-open "${app}" 2>/dev/null`, {
|
|
290
|
-
timeout: 10_000, stdio: 'pipe',
|
|
291
|
-
});
|
|
292
|
-
return `Launched/focused: ${app}`;
|
|
293
|
-
}
|
|
294
|
-
catch {
|
|
295
|
-
return `Error: Could not launch ${app}. Ensure it's installed.`;
|
|
296
|
-
}
|
|
360
|
+
finally {
|
|
361
|
+
releaseApp(app);
|
|
297
362
|
}
|
|
298
|
-
return 'Error: Unsupported platform';
|
|
299
363
|
},
|
|
300
364
|
});
|
|
301
365
|
// ── Screenshot ──
|
|
@@ -305,12 +369,19 @@ export function registerComputerTools() {
|
|
|
305
369
|
parameters: {
|
|
306
370
|
window: { type: 'string', description: 'Window title to capture (optional — captures full screen if omitted)' },
|
|
307
371
|
region: { type: 'string', description: 'Capture region as "x,y,w,h" (optional)' },
|
|
372
|
+
// Optional `app` enables Coordinator per-app locking. When omitted we
|
|
373
|
+
// fall back to the legacy single-lock path so existing scripts work.
|
|
374
|
+
app: { type: 'string', description: 'App being targeted, for parallel-agent coordination (optional)' },
|
|
308
375
|
},
|
|
309
376
|
tier: 'free',
|
|
310
377
|
async execute(args) {
|
|
311
378
|
const lockErr = ensureLock();
|
|
312
379
|
if (lockErr)
|
|
313
380
|
return `Error: ${lockErr}`;
|
|
381
|
+
const app = args.app ? String(args.app) : undefined;
|
|
382
|
+
const claimErr = claimApp(app);
|
|
383
|
+
if (claimErr)
|
|
384
|
+
return `Error: ${claimErr}`;
|
|
314
385
|
const tmpPath = join(tmpdir(), `kbot-screenshot-${Date.now()}.png`);
|
|
315
386
|
try {
|
|
316
387
|
if (platform === 'darwin') {
|
|
@@ -377,6 +448,9 @@ export function registerComputerTools() {
|
|
|
377
448
|
catch (err) {
|
|
378
449
|
return `Screenshot failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
379
450
|
}
|
|
451
|
+
finally {
|
|
452
|
+
releaseApp(app);
|
|
453
|
+
}
|
|
380
454
|
},
|
|
381
455
|
});
|
|
382
456
|
// ── Mouse click ──
|
|
@@ -387,63 +461,77 @@ export function registerComputerTools() {
|
|
|
387
461
|
x: { type: 'number', description: 'X coordinate', required: true },
|
|
388
462
|
y: { type: 'number', description: 'Y coordinate', required: true },
|
|
389
463
|
button: { type: 'string', description: 'Mouse button: left, right, double (default: left)' },
|
|
464
|
+
// Optional `app` enables per-app coordination so multiple agents can
|
|
465
|
+
// drive different apps in parallel. Omit for legacy single-lock fallback.
|
|
466
|
+
app: { type: 'string', description: 'App being targeted, for parallel-agent coordination (optional)' },
|
|
390
467
|
},
|
|
391
468
|
tier: 'free',
|
|
392
469
|
async execute(args) {
|
|
393
470
|
const lockErr = ensureLock();
|
|
394
471
|
if (lockErr)
|
|
395
472
|
return `Error: ${lockErr}`;
|
|
473
|
+
const app = args.app ? String(args.app) : undefined;
|
|
474
|
+
const claimErr = claimApp(app);
|
|
475
|
+
if (claimErr)
|
|
476
|
+
return `Error: ${claimErr}`;
|
|
396
477
|
const x = Math.round(Number(args.x));
|
|
397
478
|
const y = Math.round(Number(args.y));
|
|
398
479
|
const button = String(args.button || 'left').toLowerCase();
|
|
399
|
-
if (isNaN(x) || isNaN(y))
|
|
480
|
+
if (isNaN(x) || isNaN(y)) {
|
|
481
|
+
releaseApp(app);
|
|
400
482
|
return 'Error: x and y must be numbers';
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
if (platform === 'darwin') {
|
|
486
|
+
try {
|
|
487
|
+
if (button === 'double') {
|
|
488
|
+
// Double click
|
|
489
|
+
try {
|
|
490
|
+
execSync(`cliclick dc:${x},${y}`, { timeout: 5_000, stdio: 'pipe' });
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
osascript(`tell application "System Events" to click at {${x}, ${y}}`);
|
|
494
|
+
await new Promise(r => setTimeout(r, 100));
|
|
495
|
+
osascript(`tell application "System Events" to click at {${x}, ${y}}`);
|
|
496
|
+
}
|
|
412
497
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
498
|
+
else if (button === 'right') {
|
|
499
|
+
try {
|
|
500
|
+
execSync(`cliclick rc:${x},${y}`, { timeout: 5_000, stdio: 'pipe' });
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
osascript(`tell application "System Events" to click at {${x}, ${y}} using control down`);
|
|
504
|
+
}
|
|
417
505
|
}
|
|
418
|
-
|
|
419
|
-
|
|
506
|
+
else {
|
|
507
|
+
try {
|
|
508
|
+
execSync(`cliclick c:${x},${y}`, { timeout: 5_000, stdio: 'pipe' });
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
osascript(`tell application "System Events" to click at {${x}, ${y}}`);
|
|
512
|
+
}
|
|
420
513
|
}
|
|
514
|
+
return `Clicked ${button} at (${x}, ${y})`;
|
|
421
515
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
execSync(`cliclick c:${x},${y}`, { timeout: 5_000, stdio: 'pipe' });
|
|
425
|
-
}
|
|
426
|
-
catch {
|
|
427
|
-
osascript(`tell application "System Events" to click at {${x}, ${y}}`);
|
|
428
|
-
}
|
|
516
|
+
catch (err) {
|
|
517
|
+
return `Click failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
429
518
|
}
|
|
430
|
-
return `Clicked ${button} at (${x}, ${y})`;
|
|
431
519
|
}
|
|
432
|
-
|
|
433
|
-
|
|
520
|
+
else if (platform === 'linux') {
|
|
521
|
+
try {
|
|
522
|
+
const btn = button === 'right' ? 3 : button === 'double' ? '--repeat 2 1' : '1';
|
|
523
|
+
execSync(`xdotool mousemove ${x} ${y} click ${btn}`, { timeout: 5_000 });
|
|
524
|
+
return `Clicked ${button} at (${x}, ${y})`;
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
return 'Error: Click requires xdotool (apt install xdotool)';
|
|
528
|
+
}
|
|
434
529
|
}
|
|
530
|
+
return 'Error: Unsupported platform';
|
|
435
531
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const btn = button === 'right' ? 3 : button === 'double' ? '--repeat 2 1' : '1';
|
|
439
|
-
execSync(`xdotool mousemove ${x} ${y} click ${btn}`, { timeout: 5_000 });
|
|
440
|
-
return `Clicked ${button} at (${x}, ${y})`;
|
|
441
|
-
}
|
|
442
|
-
catch {
|
|
443
|
-
return 'Error: Click requires xdotool (apt install xdotool)';
|
|
444
|
-
}
|
|
532
|
+
finally {
|
|
533
|
+
releaseApp(app);
|
|
445
534
|
}
|
|
446
|
-
return 'Error: Unsupported platform';
|
|
447
535
|
},
|
|
448
536
|
});
|
|
449
537
|
// ── Mouse scroll ──
|
|
@@ -455,72 +543,83 @@ export function registerComputerTools() {
|
|
|
455
543
|
amount: { type: 'number', description: 'Scroll amount in clicks (default: 3)' },
|
|
456
544
|
x: { type: 'number', description: 'X coordinate to scroll at (optional — uses current position)' },
|
|
457
545
|
y: { type: 'number', description: 'Y coordinate to scroll at (optional)' },
|
|
546
|
+
// Optional `app` enables Coordinator per-app locking. Omit for legacy fallback.
|
|
547
|
+
app: { type: 'string', description: 'App being targeted, for parallel-agent coordination (optional)' },
|
|
458
548
|
},
|
|
459
549
|
tier: 'free',
|
|
460
550
|
async execute(args) {
|
|
461
551
|
const lockErr = ensureLock();
|
|
462
552
|
if (lockErr)
|
|
463
553
|
return `Error: ${lockErr}`;
|
|
464
|
-
const
|
|
465
|
-
const
|
|
466
|
-
if (
|
|
467
|
-
return
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (platform === 'darwin') {
|
|
474
|
-
try {
|
|
475
|
-
execSync(`cliclick m:${x},${y}`, { timeout: 3_000, stdio: 'pipe' });
|
|
476
|
-
}
|
|
477
|
-
catch { /* best effort move */ }
|
|
554
|
+
const app = args.app ? String(args.app) : undefined;
|
|
555
|
+
const claimErr = claimApp(app);
|
|
556
|
+
if (claimErr)
|
|
557
|
+
return `Error: ${claimErr}`;
|
|
558
|
+
try {
|
|
559
|
+
const direction = String(args.direction).toLowerCase();
|
|
560
|
+
const amount = Math.round(Number(args.amount) || 3);
|
|
561
|
+
if (!['up', 'down', 'left', 'right'].includes(direction)) {
|
|
562
|
+
return 'Error: direction must be up, down, left, or right';
|
|
478
563
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
564
|
+
// Move mouse first if coordinates given
|
|
565
|
+
if (args.x !== undefined && args.y !== undefined) {
|
|
566
|
+
const x = Math.round(Number(args.x));
|
|
567
|
+
const y = Math.round(Number(args.y));
|
|
568
|
+
if (platform === 'darwin') {
|
|
569
|
+
try {
|
|
570
|
+
execSync(`cliclick m:${x},${y}`, { timeout: 3_000, stdio: 'pipe' });
|
|
571
|
+
}
|
|
572
|
+
catch { /* best effort move */ }
|
|
482
573
|
}
|
|
483
|
-
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
if (platform === 'darwin') {
|
|
487
|
-
try {
|
|
488
|
-
// cliclick scroll: positive = up, negative = down
|
|
489
|
-
const scrollDir = direction === 'up' ? amount : direction === 'down' ? -amount : 0;
|
|
490
|
-
if (direction === 'up' || direction === 'down') {
|
|
574
|
+
else if (platform === 'linux') {
|
|
491
575
|
try {
|
|
492
|
-
execSync(`
|
|
576
|
+
execSync(`xdotool mousemove ${x} ${y}`, { timeout: 3_000, stdio: 'pipe' });
|
|
493
577
|
}
|
|
494
|
-
catch {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
578
|
+
catch { /* best effort move */ }
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (platform === 'darwin') {
|
|
582
|
+
try {
|
|
583
|
+
// cliclick scroll: positive = up, negative = down
|
|
584
|
+
const scrollDir = direction === 'up' ? amount : direction === 'down' ? -amount : 0;
|
|
585
|
+
if (direction === 'up' || direction === 'down') {
|
|
586
|
+
try {
|
|
587
|
+
execSync(`cliclick "ku:${scrollDir > 0 ? `+${scrollDir}` : scrollDir}"`, { timeout: 5_000, stdio: 'pipe' });
|
|
588
|
+
}
|
|
589
|
+
catch {
|
|
590
|
+
// Fallback to AppleScript scroll
|
|
591
|
+
const scrollAmount = direction === 'up' ? -amount : amount;
|
|
592
|
+
osascript(`tell application "System Events" to scroll area 1 by ${scrollAmount}`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
// Horizontal scroll via AppleScript
|
|
597
|
+
const horiz = direction === 'left' ? -amount : amount;
|
|
598
|
+
osascript(`tell application "System Events" to scroll area 1 by ${horiz}`);
|
|
498
599
|
}
|
|
600
|
+
return `Scrolled ${direction} by ${amount}`;
|
|
499
601
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const horiz = direction === 'left' ? -amount : amount;
|
|
503
|
-
osascript(`tell application "System Events" to scroll area 1 by ${horiz}`);
|
|
602
|
+
catch (err) {
|
|
603
|
+
return `Scroll failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
504
604
|
}
|
|
505
|
-
return `Scrolled ${direction} by ${amount}`;
|
|
506
605
|
}
|
|
507
|
-
|
|
508
|
-
|
|
606
|
+
else if (platform === 'linux') {
|
|
607
|
+
try {
|
|
608
|
+
// xdotool: button 4=up, 5=down, 6=left, 7=right
|
|
609
|
+
const buttonMap = { up: 4, down: 5, left: 6, right: 7 };
|
|
610
|
+
const btn = buttonMap[direction];
|
|
611
|
+
execSync(`xdotool click --repeat ${amount} ${btn}`, { timeout: 5_000 });
|
|
612
|
+
return `Scrolled ${direction} by ${amount}`;
|
|
613
|
+
}
|
|
614
|
+
catch {
|
|
615
|
+
return 'Error: Scroll requires xdotool';
|
|
616
|
+
}
|
|
509
617
|
}
|
|
618
|
+
return 'Error: Unsupported platform';
|
|
510
619
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
// xdotool: button 4=up, 5=down, 6=left, 7=right
|
|
514
|
-
const buttonMap = { up: 4, down: 5, left: 6, right: 7 };
|
|
515
|
-
const btn = buttonMap[direction];
|
|
516
|
-
execSync(`xdotool click --repeat ${amount} ${btn}`, { timeout: 5_000 });
|
|
517
|
-
return `Scrolled ${direction} by ${amount}`;
|
|
518
|
-
}
|
|
519
|
-
catch {
|
|
520
|
-
return 'Error: Scroll requires xdotool';
|
|
521
|
-
}
|
|
620
|
+
finally {
|
|
621
|
+
releaseApp(app);
|
|
522
622
|
}
|
|
523
|
-
return 'Error: Unsupported platform';
|
|
524
623
|
},
|
|
525
624
|
});
|
|
526
625
|
// ── Mouse drag ──
|
|
@@ -533,48 +632,59 @@ export function registerComputerTools() {
|
|
|
533
632
|
to_x: { type: 'number', description: 'End X coordinate', required: true },
|
|
534
633
|
to_y: { type: 'number', description: 'End Y coordinate', required: true },
|
|
535
634
|
duration_ms: { type: 'number', description: 'Drag duration in milliseconds (default: 500)' },
|
|
635
|
+
// Optional `app` enables Coordinator per-app locking. Omit for legacy fallback.
|
|
636
|
+
app: { type: 'string', description: 'App being targeted, for parallel-agent coordination (optional)' },
|
|
536
637
|
},
|
|
537
638
|
tier: 'free',
|
|
538
639
|
async execute(args) {
|
|
539
640
|
const lockErr = ensureLock();
|
|
540
641
|
if (lockErr)
|
|
541
642
|
return `Error: ${lockErr}`;
|
|
542
|
-
const
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
643
|
+
const app = args.app ? String(args.app) : undefined;
|
|
644
|
+
const claimErr = claimApp(app);
|
|
645
|
+
if (claimErr)
|
|
646
|
+
return `Error: ${claimErr}`;
|
|
647
|
+
try {
|
|
648
|
+
const fx = Math.round(Number(args.from_x));
|
|
649
|
+
const fy = Math.round(Number(args.from_y));
|
|
650
|
+
const tx = Math.round(Number(args.to_x));
|
|
651
|
+
const ty = Math.round(Number(args.to_y));
|
|
652
|
+
if ([fx, fy, tx, ty].some(isNaN))
|
|
653
|
+
return 'Error: All coordinates must be numbers';
|
|
654
|
+
if (platform === 'darwin') {
|
|
550
655
|
try {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
656
|
+
try {
|
|
657
|
+
execSync(`cliclick dd:${fx},${fy} du:${tx},${ty}`, { timeout: 10_000, stdio: 'pipe' });
|
|
658
|
+
}
|
|
659
|
+
catch {
|
|
660
|
+
// Fallback: AppleScript mouse down, move, mouse up
|
|
661
|
+
osascript(`
|
|
556
662
|
tell application "System Events"
|
|
557
663
|
set mouseLocation to {${fx}, ${fy}}
|
|
558
664
|
click at mouseLocation
|
|
559
665
|
end tell
|
|
560
666
|
`.trim(), 10_000);
|
|
667
|
+
}
|
|
668
|
+
return `Dragged from (${fx},${fy}) to (${tx},${ty})`;
|
|
669
|
+
}
|
|
670
|
+
catch (err) {
|
|
671
|
+
return `Drag failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
561
672
|
}
|
|
562
|
-
return `Dragged from (${fx},${fy}) to (${tx},${ty})`;
|
|
563
673
|
}
|
|
564
|
-
|
|
565
|
-
|
|
674
|
+
else if (platform === 'linux') {
|
|
675
|
+
try {
|
|
676
|
+
execSync(`xdotool mousemove ${fx} ${fy} mousedown 1 mousemove --sync ${tx} ${ty} mouseup 1`, { timeout: 10_000 });
|
|
677
|
+
return `Dragged from (${fx},${fy}) to (${tx},${ty})`;
|
|
678
|
+
}
|
|
679
|
+
catch {
|
|
680
|
+
return 'Error: Drag requires xdotool';
|
|
681
|
+
}
|
|
566
682
|
}
|
|
683
|
+
return 'Error: Unsupported platform';
|
|
567
684
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
execSync(`xdotool mousemove ${fx} ${fy} mousedown 1 mousemove --sync ${tx} ${ty} mouseup 1`, { timeout: 10_000 });
|
|
571
|
-
return `Dragged from (${fx},${fy}) to (${tx},${ty})`;
|
|
572
|
-
}
|
|
573
|
-
catch {
|
|
574
|
-
return 'Error: Drag requires xdotool';
|
|
575
|
-
}
|
|
685
|
+
finally {
|
|
686
|
+
releaseApp(app);
|
|
576
687
|
}
|
|
577
|
-
return 'Error: Unsupported platform';
|
|
578
688
|
},
|
|
579
689
|
});
|
|
580
690
|
// ── Keyboard type ──
|
|
@@ -583,35 +693,46 @@ export function registerComputerTools() {
|
|
|
583
693
|
description: 'Type text using the keyboard. Types each character as if pressed by the user.',
|
|
584
694
|
parameters: {
|
|
585
695
|
text: { type: 'string', description: 'Text to type', required: true },
|
|
696
|
+
// Optional `app` enables Coordinator per-app locking. Omit for legacy fallback.
|
|
697
|
+
app: { type: 'string', description: 'App being targeted, for parallel-agent coordination (optional)' },
|
|
586
698
|
},
|
|
587
699
|
tier: 'free',
|
|
588
700
|
async execute(args) {
|
|
589
701
|
const lockErr = ensureLock();
|
|
590
702
|
if (lockErr)
|
|
591
703
|
return `Error: ${lockErr}`;
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
return
|
|
704
|
+
const app = args.app ? String(args.app) : undefined;
|
|
705
|
+
const claimErr = claimApp(app);
|
|
706
|
+
if (claimErr)
|
|
707
|
+
return `Error: ${claimErr}`;
|
|
708
|
+
try {
|
|
709
|
+
const text = String(args.text);
|
|
710
|
+
if (!text)
|
|
711
|
+
return 'Error: text is required';
|
|
712
|
+
if (platform === 'darwin') {
|
|
713
|
+
const escaped = escapeAppleScript(text);
|
|
714
|
+
try {
|
|
715
|
+
osascript(`tell application "System Events" to keystroke "${escaped}"`, 10_000);
|
|
716
|
+
return `Typed: ${text.slice(0, 80)}${text.length > 80 ? '...' : ''}`;
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
return 'Error: Typing requires Accessibility permissions';
|
|
720
|
+
}
|
|
600
721
|
}
|
|
601
|
-
|
|
602
|
-
|
|
722
|
+
else if (platform === 'linux') {
|
|
723
|
+
try {
|
|
724
|
+
execSync(`xdotool type -- "${text.replace(/"/g, '\\"')}"`, { timeout: 10_000 });
|
|
725
|
+
return `Typed: ${text.slice(0, 80)}${text.length > 80 ? '...' : ''}`;
|
|
726
|
+
}
|
|
727
|
+
catch {
|
|
728
|
+
return 'Error: Typing requires xdotool';
|
|
729
|
+
}
|
|
603
730
|
}
|
|
731
|
+
return 'Error: Unsupported platform';
|
|
604
732
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
execSync(`xdotool type -- "${text.replace(/"/g, '\\"')}"`, { timeout: 10_000 });
|
|
608
|
-
return `Typed: ${text.slice(0, 80)}${text.length > 80 ? '...' : ''}`;
|
|
609
|
-
}
|
|
610
|
-
catch {
|
|
611
|
-
return 'Error: Typing requires xdotool';
|
|
612
|
-
}
|
|
733
|
+
finally {
|
|
734
|
+
releaseApp(app);
|
|
613
735
|
}
|
|
614
|
-
return 'Error: Unsupported platform';
|
|
615
736
|
},
|
|
616
737
|
});
|
|
617
738
|
// ── Keyboard key ──
|
|
@@ -620,72 +741,83 @@ export function registerComputerTools() {
|
|
|
620
741
|
description: 'Press a key or key combination. Supports modifiers: cmd/ctrl/alt/shift + key.',
|
|
621
742
|
parameters: {
|
|
622
743
|
key: { type: 'string', description: 'Key: enter, tab, escape, space, backspace, delete, up, down, left, right, cmd+c, ctrl+v, cmd+shift+s, etc.', required: true },
|
|
744
|
+
// Optional `app` enables Coordinator per-app locking. Omit for legacy fallback.
|
|
745
|
+
app: { type: 'string', description: 'App being targeted, for parallel-agent coordination (optional)' },
|
|
623
746
|
},
|
|
624
747
|
tier: 'free',
|
|
625
748
|
async execute(args) {
|
|
626
749
|
const lockErr = ensureLock();
|
|
627
750
|
if (lockErr)
|
|
628
751
|
return `Error: ${lockErr}`;
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
752
|
+
const app = args.app ? String(args.app) : undefined;
|
|
753
|
+
const claimErr = claimApp(app);
|
|
754
|
+
if (claimErr)
|
|
755
|
+
return `Error: ${claimErr}`;
|
|
756
|
+
try {
|
|
757
|
+
const key = String(args.key).toLowerCase();
|
|
758
|
+
if (platform === 'darwin') {
|
|
759
|
+
// Key code map for non-character keys
|
|
760
|
+
const keyCodeMap = {
|
|
761
|
+
enter: 36, return: 36, tab: 48, escape: 53, space: 49,
|
|
762
|
+
backspace: 51, delete: 117, up: 126, down: 125, left: 123, right: 124,
|
|
763
|
+
home: 115, end: 119, pageup: 116, pagedown: 121,
|
|
764
|
+
f1: 122, f2: 120, f3: 99, f4: 118, f5: 96, f6: 97,
|
|
765
|
+
f7: 98, f8: 100, f9: 101, f10: 109, f11: 103, f12: 111,
|
|
766
|
+
};
|
|
767
|
+
try {
|
|
768
|
+
if (key.includes('+')) {
|
|
769
|
+
const parts = key.split('+');
|
|
770
|
+
const mainKey = parts.pop();
|
|
771
|
+
const modifiers = parts.map(m => {
|
|
772
|
+
if (m === 'cmd' || m === 'command')
|
|
773
|
+
return 'command down';
|
|
774
|
+
if (m === 'ctrl' || m === 'control')
|
|
775
|
+
return 'control down';
|
|
776
|
+
if (m === 'alt' || m === 'option')
|
|
777
|
+
return 'option down';
|
|
778
|
+
if (m === 'shift')
|
|
779
|
+
return 'shift down';
|
|
780
|
+
return '';
|
|
781
|
+
}).filter(Boolean).join(', ');
|
|
782
|
+
const code = keyCodeMap[mainKey];
|
|
783
|
+
if (code !== undefined) {
|
|
784
|
+
osascript(`tell application "System Events" to key code ${code} using {${modifiers}}`);
|
|
785
|
+
}
|
|
786
|
+
else {
|
|
787
|
+
osascript(`tell application "System Events" to keystroke "${escapeAppleScript(mainKey)}" using {${modifiers}}`);
|
|
788
|
+
}
|
|
657
789
|
}
|
|
658
790
|
else {
|
|
659
|
-
|
|
791
|
+
const code = keyCodeMap[key];
|
|
792
|
+
if (code !== undefined) {
|
|
793
|
+
osascript(`tell application "System Events" to key code ${code}`);
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
osascript(`tell application "System Events" to keystroke "${escapeAppleScript(key)}"`);
|
|
797
|
+
}
|
|
660
798
|
}
|
|
799
|
+
return `Pressed: ${key}`;
|
|
661
800
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if (code !== undefined) {
|
|
665
|
-
osascript(`tell application "System Events" to key code ${code}`);
|
|
666
|
-
}
|
|
667
|
-
else {
|
|
668
|
-
osascript(`tell application "System Events" to keystroke "${escapeAppleScript(key)}"`);
|
|
669
|
-
}
|
|
801
|
+
catch {
|
|
802
|
+
return 'Error: Key press requires Accessibility permissions';
|
|
670
803
|
}
|
|
671
|
-
return `Pressed: ${key}`;
|
|
672
804
|
}
|
|
673
|
-
|
|
674
|
-
|
|
805
|
+
else if (platform === 'linux') {
|
|
806
|
+
try {
|
|
807
|
+
// xdotool uses + for combos: ctrl+c, super+l, etc.
|
|
808
|
+
const xdoKey = key.replace('cmd', 'super').replace('command', 'super');
|
|
809
|
+
execSync(`xdotool key ${xdoKey}`, { timeout: 5_000 });
|
|
810
|
+
return `Pressed: ${key}`;
|
|
811
|
+
}
|
|
812
|
+
catch {
|
|
813
|
+
return 'Error: Key press requires xdotool';
|
|
814
|
+
}
|
|
675
815
|
}
|
|
816
|
+
return 'Error: Unsupported platform';
|
|
676
817
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
// xdotool uses + for combos: ctrl+c, super+l, etc.
|
|
680
|
-
const xdoKey = key.replace('cmd', 'super').replace('command', 'super');
|
|
681
|
-
execSync(`xdotool key ${xdoKey}`, { timeout: 5_000 });
|
|
682
|
-
return `Pressed: ${key}`;
|
|
683
|
-
}
|
|
684
|
-
catch {
|
|
685
|
-
return 'Error: Key press requires xdotool';
|
|
686
|
-
}
|
|
818
|
+
finally {
|
|
819
|
+
releaseApp(app);
|
|
687
820
|
}
|
|
688
|
-
return 'Error: Unsupported platform';
|
|
689
821
|
},
|
|
690
822
|
});
|
|
691
823
|
// ── Window management ──
|
|
@@ -756,32 +888,40 @@ export function registerComputerTools() {
|
|
|
756
888
|
return `Error: ${app} not approved. Call app_approve first.`;
|
|
757
889
|
if (isNaN(w) || isNaN(h))
|
|
758
890
|
return 'Error: width and height must be numbers';
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
891
|
+
const claimErr = claimApp(app);
|
|
892
|
+
if (claimErr)
|
|
893
|
+
return `Error: ${claimErr}`;
|
|
894
|
+
try {
|
|
895
|
+
if (platform === 'darwin') {
|
|
896
|
+
try {
|
|
897
|
+
osascript(`tell application "${escapeAppleScript(app)}" to set bounds of front window to {0, 0, ${w}, ${h}}`, 5_000);
|
|
898
|
+
return `Resized ${app} to ${w}x${h}`;
|
|
899
|
+
}
|
|
900
|
+
catch {
|
|
901
|
+
// Fallback via System Events
|
|
902
|
+
try {
|
|
903
|
+
osascript(`tell application "System Events" to tell process "${escapeAppleScript(app)}" to set size of front window to {${w}, ${h}}`);
|
|
904
|
+
return `Resized ${app} to ${w}x${h}`;
|
|
905
|
+
}
|
|
906
|
+
catch (err) {
|
|
907
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
763
910
|
}
|
|
764
|
-
|
|
765
|
-
// Fallback via System Events
|
|
911
|
+
else if (platform === 'linux') {
|
|
766
912
|
try {
|
|
767
|
-
|
|
913
|
+
execSync(`wmctrl -r "${app}" -e 0,-1,-1,${w},${h}`, { timeout: 5_000 });
|
|
768
914
|
return `Resized ${app} to ${w}x${h}`;
|
|
769
915
|
}
|
|
770
|
-
catch
|
|
771
|
-
return
|
|
916
|
+
catch {
|
|
917
|
+
return 'Error: Requires wmctrl';
|
|
772
918
|
}
|
|
773
919
|
}
|
|
920
|
+
return 'Error: Unsupported platform';
|
|
774
921
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
execSync(`wmctrl -r "${app}" -e 0,-1,-1,${w},${h}`, { timeout: 5_000 });
|
|
778
|
-
return `Resized ${app} to ${w}x${h}`;
|
|
779
|
-
}
|
|
780
|
-
catch {
|
|
781
|
-
return 'Error: Requires wmctrl';
|
|
782
|
-
}
|
|
922
|
+
finally {
|
|
923
|
+
releaseApp(app);
|
|
783
924
|
}
|
|
784
|
-
return 'Error: Unsupported platform';
|
|
785
925
|
},
|
|
786
926
|
});
|
|
787
927
|
registerTool({
|
|
@@ -799,25 +939,33 @@ export function registerComputerTools() {
|
|
|
799
939
|
const y = Math.round(Number(args.y));
|
|
800
940
|
if (!isAppApproved(app))
|
|
801
941
|
return `Error: ${app} not approved. Call app_approve first.`;
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
942
|
+
const claimErr = claimApp(app);
|
|
943
|
+
if (claimErr)
|
|
944
|
+
return `Error: ${claimErr}`;
|
|
945
|
+
try {
|
|
946
|
+
if (platform === 'darwin') {
|
|
947
|
+
try {
|
|
948
|
+
osascript(`tell application "System Events" to tell process "${escapeAppleScript(app)}" to set position of front window to {${x}, ${y}}`);
|
|
949
|
+
return `Moved ${app} to (${x}, ${y})`;
|
|
950
|
+
}
|
|
951
|
+
catch (err) {
|
|
952
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
953
|
+
}
|
|
806
954
|
}
|
|
807
|
-
|
|
808
|
-
|
|
955
|
+
else if (platform === 'linux') {
|
|
956
|
+
try {
|
|
957
|
+
execSync(`wmctrl -r "${app}" -e 0,${x},${y},-1,-1`, { timeout: 5_000 });
|
|
958
|
+
return `Moved ${app} to (${x}, ${y})`;
|
|
959
|
+
}
|
|
960
|
+
catch {
|
|
961
|
+
return 'Error: Requires wmctrl';
|
|
962
|
+
}
|
|
809
963
|
}
|
|
964
|
+
return 'Error: Unsupported platform';
|
|
810
965
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
execSync(`wmctrl -r "${app}" -e 0,${x},${y},-1,-1`, { timeout: 5_000 });
|
|
814
|
-
return `Moved ${app} to (${x}, ${y})`;
|
|
815
|
-
}
|
|
816
|
-
catch {
|
|
817
|
-
return 'Error: Requires wmctrl';
|
|
818
|
-
}
|
|
966
|
+
finally {
|
|
967
|
+
releaseApp(app);
|
|
819
968
|
}
|
|
820
|
-
return 'Error: Unsupported platform';
|
|
821
969
|
},
|
|
822
970
|
});
|
|
823
971
|
registerTool({
|
|
@@ -833,35 +981,43 @@ export function registerComputerTools() {
|
|
|
833
981
|
const action = String(args.action || 'minimize').toLowerCase();
|
|
834
982
|
if (!isAppApproved(app))
|
|
835
983
|
return `Error: ${app} not approved. Call app_approve first.`;
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
984
|
+
const claimErr = claimApp(app);
|
|
985
|
+
if (claimErr)
|
|
986
|
+
return `Error: ${claimErr}`;
|
|
987
|
+
try {
|
|
988
|
+
if (platform === 'darwin') {
|
|
989
|
+
try {
|
|
990
|
+
if (action === 'restore') {
|
|
991
|
+
osascript(`tell application "${escapeAppleScript(app)}" to activate`);
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
osascript(`tell application "System Events" to tell process "${escapeAppleScript(app)}" to set miniaturized of front window to true`);
|
|
995
|
+
}
|
|
996
|
+
return `${action === 'restore' ? 'Restored' : 'Minimized'} ${app}`;
|
|
840
997
|
}
|
|
841
|
-
|
|
842
|
-
|
|
998
|
+
catch (err) {
|
|
999
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
843
1000
|
}
|
|
844
|
-
return `${action === 'restore' ? 'Restored' : 'Minimized'} ${app}`;
|
|
845
|
-
}
|
|
846
|
-
catch (err) {
|
|
847
|
-
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
848
1001
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1002
|
+
else if (platform === 'linux') {
|
|
1003
|
+
try {
|
|
1004
|
+
if (action === 'restore') {
|
|
1005
|
+
execSync(`wmctrl -r "${app}" -b remove,hidden`, { timeout: 5_000 });
|
|
1006
|
+
}
|
|
1007
|
+
else {
|
|
1008
|
+
execSync(`xdotool search --name "${app}" windowminimize`, { timeout: 5_000 });
|
|
1009
|
+
}
|
|
1010
|
+
return `${action === 'restore' ? 'Restored' : 'Minimized'} ${app}`;
|
|
854
1011
|
}
|
|
855
|
-
|
|
856
|
-
|
|
1012
|
+
catch {
|
|
1013
|
+
return 'Error: Requires wmctrl/xdotool';
|
|
857
1014
|
}
|
|
858
|
-
return `${action === 'restore' ? 'Restored' : 'Minimized'} ${app}`;
|
|
859
|
-
}
|
|
860
|
-
catch {
|
|
861
|
-
return 'Error: Requires wmctrl/xdotool';
|
|
862
1015
|
}
|
|
1016
|
+
return 'Error: Unsupported platform';
|
|
1017
|
+
}
|
|
1018
|
+
finally {
|
|
1019
|
+
releaseApp(app);
|
|
863
1020
|
}
|
|
864
|
-
return 'Error: Unsupported platform';
|
|
865
1021
|
},
|
|
866
1022
|
});
|
|
867
1023
|
// ── Screen info ──
|
|
@@ -928,10 +1084,18 @@ export function registerComputerTools() {
|
|
|
928
1084
|
parameters: {},
|
|
929
1085
|
tier: 'free',
|
|
930
1086
|
async execute() {
|
|
1087
|
+
let released = [];
|
|
1088
|
+
try {
|
|
1089
|
+
released = coordinator.unregister(AGENT_ID);
|
|
1090
|
+
}
|
|
1091
|
+
catch { /* best effort */ }
|
|
931
1092
|
releaseLock();
|
|
932
1093
|
approvedApps.clear();
|
|
933
1094
|
permissionsVerified = false;
|
|
934
|
-
|
|
1095
|
+
const releasedNote = released.length > 0
|
|
1096
|
+
? ` Released app locks: ${released.join(', ')}.`
|
|
1097
|
+
: '';
|
|
1098
|
+
return `Computer use session ended.${releasedNote}`;
|
|
935
1099
|
},
|
|
936
1100
|
});
|
|
937
1101
|
}
|