@aryacode/dsh-plugin-arya-computer-use 1.0.2 → 1.0.4
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/client.js +8 -5
- package/index.js +46 -2
- package/mcp/adapter-server.mjs +30 -5
- package/mcp/browser-launcher.mjs +104 -7
- package/mcp/settings.mjs +7 -4
- package/package.json +2 -1
- package/scripts/hide-consoles.ps1 +120 -0
package/client.js
CHANGED
|
@@ -14,8 +14,8 @@ window.__ModuleLoader__.load({
|
|
|
14
14
|
remoteIndicatorVisible: true, indicatorText: 'Arya is controlling your computer', pointerLabel: 'Arya', indicatorPosition: 'top',
|
|
15
15
|
indicatorIdleMs: 800, stopShortcut: '', resumeToken: 0, previewToken: 0,
|
|
16
16
|
headedDefault: false, overlayDefault: true, driverLockEnabled: true,
|
|
17
|
-
toolPreset: 'lite',
|
|
18
|
-
browserTools: 'core', browserProfile: '', browserSession: '', browserStateFile: '', browserCdp: '', browserHeaded: false,
|
|
17
|
+
toolPreset: 'lite',
|
|
18
|
+
browserTools: 'core', browserProfile: '', browserSession: '', browserStateFile: '', browserCdp: '', browserHeaded: false, browserToolFlags: 'strip',
|
|
19
19
|
};
|
|
20
20
|
const equal = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
21
21
|
const dictionaries = {
|
|
@@ -36,11 +36,12 @@ window.__ModuleLoader__.load({
|
|
|
36
36
|
progressOpacity: '背景不透明度(%)', progressWidth: '宽度(像素)', progressHeight: '高度(像素)',
|
|
37
37
|
progressPosition: '浮窗位置', progress_topRight: '右上', progress_topLeft: '左上', progress_bottomRight: '右下', progress_bottomLeft: '左下', progress_custom: '自定义',
|
|
38
38
|
progressX: 'X 坐标', progressY: 'Y 坐标', progressShortcut: '浮窗解锁快捷键', progressShortcutHint: '按下后临时解锁浮窗以便拖动。留空表示不设置。',
|
|
39
|
-
toolSurfaceTitle: '工具范围', toolPreset: '工具预设', toolPresetHint: 'lite 直接列出全部 16 个工具;lazy 只暴露 tools / snapshot / click,其余按需加载。修改后需重启 MCP 生效。', toolPresetLite: 'lite · 全部 16 个', toolPresetLazy: 'lazy · 按需加载',
|
|
39
|
+
toolSurfaceTitle: '工具范围', toolPreset: '工具预设', toolPresetHint: 'lite 直接列出全部 16 个工具;lazy 只暴露 tools / snapshot / click,其余按需加载。修改后需重启 MCP 生效。', toolPresetLite: 'lite · 全部 16 个', toolPresetLazy: 'lazy · 按需加载',
|
|
40
40
|
behaviorTitle: '操作方式', headedDefault: 'Real mouse and keyboard', headedDefaultHint: 'Off by default: input goes through background accessibility patterns and never moves your pointer or steals focus. Turn on to use real keyboard/mouse input, which briefly focuses the target window.',
|
|
41
41
|
overlayDefault: '引擎会话叠加层', overlayDefaultHint: '启动桌面会话时启用引擎自带的光标叠加层。Arya 的虚拟光标不依赖它。',
|
|
42
42
|
driverLock: '单会话驱动', driverLockHint: '同一时间只允许一个对话操作桌面;其他对话调用桌面工具会收到 BUSY 提示。关闭后最近使用的对话自动接管。',
|
|
43
43
|
browserTitle: '网页通道', browserTools: '工具范围', browserToolsHint: 'agent-browser 向 MCP 暴露的工具集。修改后需重启 MCP 生效。',
|
|
44
|
+
browserFlags: '精简工具定义', browserFlagsHint: '插件启动网页通道时已经设好域名、证书、会话等全局参数,每个工具里重复的这些参数可以删掉:strip 全部删除(约省 1.8 万 token),lean 保留会话与状态恢复相关项,off 保持 agent-browser 原样。修改后需重启 MCP 生效。', browserFlagsStrip: 'strip · 全部删除', browserFlagsLean: 'lean · 保留会话项', browserFlagsOff: 'off · 保持原样',
|
|
44
45
|
browserProfile: 'Chrome 配置', browserProfileHint: '配置名称(如 Default)或目录路径,让网页通道复用你的登录状态。',
|
|
45
46
|
browserSession: '会话名', browserSessionHint: '隔离的会话键;不同会话名的 Cookie 与标签页彼此独立。',
|
|
46
47
|
browserCdp: '连接我的浏览器', browserCdpHint: 'CDP 地址,例如 http://127.0.0.1:9222。用 --remote-debugging-port=9222 启动 Chrome 即可共享已登录的窗口。',
|
|
@@ -87,11 +88,12 @@ window.__ModuleLoader__.load({
|
|
|
87
88
|
progressOpacity: 'Background opacity (%)', progressWidth: 'Width (px)', progressHeight: 'Height (px)',
|
|
88
89
|
progressPosition: 'Overlay position', progress_topRight: 'Top right', progress_topLeft: 'Top left', progress_bottomRight: 'Bottom right', progress_bottomLeft: 'Bottom left', progress_custom: 'Custom',
|
|
89
90
|
progressX: 'X coordinate', progressY: 'Y coordinate', progressShortcut: 'Overlay unlock shortcut', progressShortcutHint: 'Press to temporarily unlock the overlay so you can drag it. Empty means none.',
|
|
90
|
-
toolSurfaceTitle: 'Tool surface', toolPreset: 'Tool preset', toolPresetHint: 'lite lists all 16 tools up front; lazy exposes tools / snapshot / click and loads the rest on demand. Changes apply after the next MCP restart.', toolPresetLite: 'lite · all 16', toolPresetLazy: 'lazy · load on demand',
|
|
91
|
+
toolSurfaceTitle: 'Tool surface', toolPreset: 'Tool preset', toolPresetHint: 'lite lists all 16 tools up front; lazy exposes tools / snapshot / click and loads the rest on demand. Changes apply after the next MCP restart.', toolPresetLite: 'lite · all 16', toolPresetLazy: 'lazy · load on demand',
|
|
91
92
|
behaviorTitle: 'Control style', headedDefault: 'Real mouse and keyboard', headedDefaultHint: 'Off by default: input goes through background accessibility patterns and never moves your pointer or steals focus. Turn on to use real keyboard/mouse input, which briefly focuses the target window.',
|
|
92
93
|
overlayDefault: 'Engine session overlay', overlayDefaultHint: "Starts the desktop session with the engine's own cursor overlay. Arya's ghost cursor does not depend on it; leave it off unless you want the engine marker too.",
|
|
93
94
|
driverLock: 'One conversation drives at a time', driverLockHint: 'While one conversation is driving, desktop calls from other conversations receive a BUSY notice. Turn off to let the most recent conversation take over automatically.',
|
|
94
95
|
browserTitle: 'Web lane', browserTools: 'Tool surface', browserToolsHint: 'Which agent-browser tool profiles the MCP server exposes. Changing this applies on the next MCP restart.',
|
|
96
|
+
browserFlags: 'Trim tool schemas', browserFlagsHint: 'The plugin already sets domains, certificates, session and other host-wide flags when it starts the web lane, so the copies repeated inside every tool can go: strip removes them all (about 18k tokens saved), lean keeps the session and state-restore ones, off leaves the agent-browser schemas untouched. Changes apply after the next MCP restart.', browserFlagsStrip: 'strip · remove all', browserFlagsLean: 'lean · keep session flags', browserFlagsOff: 'off · keep as shipped',
|
|
95
97
|
browserProfile: 'Chrome profile', browserProfileHint: 'A profile name such as Default, or a directory path, so the web lane reuses your sign-ins.',
|
|
96
98
|
browserSession: 'Session', browserSessionHint: 'An isolated session key; cookies and tabs stay separate per name.',
|
|
97
99
|
browserCdp: 'Connect to my browser', browserCdpHint: 'A CDP endpoint such as http://127.0.0.1:9222. Start Chrome with --remote-debugging-port=9222 to share a signed-in window.',
|
|
@@ -350,12 +352,13 @@ window.__ModuleLoader__.load({
|
|
|
350
352
|
h('div', { className: 'arya-grid' },
|
|
351
353
|
switchItem('headedDefault', 'headedDefault', 'headedDefaultHint', arts.keyboard(draft.headedDefault)),
|
|
352
354
|
switchItem('overlayDefault', 'overlayDefault', 'overlayDefaultHint', arts.cursor(draft.overlayDefault)),
|
|
353
|
-
switchItem('driverLockEnabled', 'driverLock', 'driverLockHint', arts.shield(draft.driverLockEnabled)),
|
|
355
|
+
switchItem('driverLockEnabled', 'driverLock', 'driverLockHint', arts.shield(draft.driverLockEnabled)),
|
|
354
356
|
field(title(t('toolPreset'), 'toolPresetHint', { htmlFor: idPrefix + '-toolPreset' }), h('select', { id: idPrefix + '-toolPreset', value: draft.toolPreset, disabled, onChange: (e) => change('toolPreset', e.target.value) }, h('option', { value: 'lite' }, t('toolPresetLite')), h('option', { value: 'lazy' }, t('toolPresetLazy')))))),
|
|
355
357
|
h('section', { className: 'arya-section', 'aria-labelledby': idPrefix + '-browser-title' },
|
|
356
358
|
head(idPrefix + '-browser-title', 'browserTitle'),
|
|
357
359
|
h('div', { className: 'arya-grid' },
|
|
358
360
|
field(title(t('browserTools'), 'browserToolsHint', { htmlFor: idPrefix + '-browserTools' }), h('select', { id: idPrefix + '-browserTools', value: draft.browserTools, disabled, onChange: (e) => change('browserTools', e.target.value) }, ...['core', 'network', 'state', 'debug', 'tabs', 'react', 'mobile', 'all'].map((key) => h('option', { key, value: key }, key)))),
|
|
361
|
+
field(title(t('browserFlags'), 'browserFlagsHint', { htmlFor: idPrefix + '-browserToolFlags' }), h('select', { id: idPrefix + '-browserToolFlags', value: draft.browserToolFlags, disabled, onChange: (e) => change('browserToolFlags', e.target.value) }, ...[['strip', 'browserFlagsStrip'], ['lean', 'browserFlagsLean'], ['off', 'browserFlagsOff']].map(([value, label]) => h('option', { key: value, value }, t(label))))),
|
|
359
362
|
switchItem('browserHeaded', 'browserHeaded', 'browserHeadedHint', arts.monitor && arts.monitor(draft.browserHeaded) || arts.cursor(draft.browserHeaded))),
|
|
360
363
|
h('div', { className: 'arya-fields' },
|
|
361
364
|
field(title(t('browserProfile'), 'browserProfileHint', { htmlFor: idPrefix + '-browserProfile' }), h('input', { id: idPrefix + '-browserProfile', type: 'text', maxLength: 200, placeholder: 'Default', value: draft.browserProfile, disabled, onChange: (e) => change('browserProfile', e.target.value) })),
|
package/index.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import z from '@deepseek-ai/schemastery';
|
|
20
20
|
import os from 'node:os';
|
|
21
21
|
import { readFile, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
22
|
-
import { execFileSync } from 'node:child_process';
|
|
22
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
23
23
|
import { dirname, join } from 'node:path';
|
|
24
24
|
import { createRequire } from 'node:module';
|
|
25
25
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
@@ -33,6 +33,7 @@ export const inject = ['tools'];
|
|
|
33
33
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
34
34
|
const SKILL_DIR = join(HERE, 'skills', 'arya-desktop');
|
|
35
35
|
const SKILL_FILE = join(SKILL_DIR, 'SKILL.md');
|
|
36
|
+
const CONSOLE_HIDER_SCRIPT = join(HERE, 'scripts', 'hide-consoles.ps1');
|
|
36
37
|
const PROVIDER_NAME = 'arya-computer-use';
|
|
37
38
|
const RANK = 350;
|
|
38
39
|
const STATUS_PATH = '/plugins/arya/status';
|
|
@@ -222,6 +223,7 @@ const fields = {
|
|
|
222
223
|
driverLockEnabled: z.boolean().default(true).description('One conversation drives the desktop at a time'),
|
|
223
224
|
toolPreset: z.union(['lite', 'lazy'].map(value => z.const(value))).default('lite').description('Tool surface size: lite lists all 16 tools; lazy starts with tools/snapshot/click and reveals the rest on demand'),
|
|
224
225
|
browserTools: z.union(['core', 'network', 'state', 'debug', 'tabs', 'react', 'mobile', 'all'].map(value => z.const(value))).default('core').description('agent-browser tool profiles exposed on the MCP row'),
|
|
226
|
+
browserToolFlags: z.union(['strip', 'lean', 'off'].map(value => z.const(value))).default('strip').description('Web lane schema size: strip drops the host/launch flags the plugin sets at launch from every tool, lean keeps session/restore/timeout, off keeps the agent-browser schemas unchanged'),
|
|
225
227
|
browserProfile: z.string().default('').description('Chrome profile name or directory the web lane reuses for sign-ins'),
|
|
226
228
|
browserSession: z.string().default('').description('Isolated browser session key'),
|
|
227
229
|
browserStateFile: z.string().default('').description('Saved sign-in state file for the web lane'),
|
|
@@ -295,7 +297,47 @@ export { ensureMarketSource as __ensureMarketSource };
|
|
|
295
297
|
|
|
296
298
|
/* -------------------------------------------------------------------- main */
|
|
297
299
|
|
|
300
|
+
// Windows gives a console window titled after its executable to any console
|
|
301
|
+
// child that was not created with CREATE_NO_WINDOW. Both engines ship a JS shim
|
|
302
|
+
// (agent-browser/bin/agent-browser.js, agent-desktop/bin/agent-desktop) that
|
|
303
|
+
// runs the native helper with `stdio: 'inherit'` and `windowsHide: false`, so a
|
|
304
|
+
// black console stays on the desktop for as long as the helper lives - the
|
|
305
|
+
// browser daemon keeps it for the whole session - and closing it kills the
|
|
306
|
+
// helper, which is why it comes straight back. The plugin spawns the natives
|
|
307
|
+
// itself wherever the call site allows it; this watcher covers whatever else
|
|
308
|
+
// starts them (a lane restart, an older launcher, a helper started outside the
|
|
309
|
+
// plugin). It hides only classic console windows whose title or image names one
|
|
310
|
+
// of the two engine helpers, and hiding leaves stdio alone, so the helper keeps
|
|
311
|
+
// working.
|
|
312
|
+
function startConsoleHider(ctx) {
|
|
313
|
+
if (process.platform !== 'win32') return () => {};
|
|
314
|
+
try {
|
|
315
|
+
if (!statSync(CONSOLE_HIDER_SCRIPT).isFile()) return () => {};
|
|
316
|
+
} catch {
|
|
317
|
+
return () => {};
|
|
318
|
+
}
|
|
319
|
+
let child;
|
|
320
|
+
try {
|
|
321
|
+
child = spawn(
|
|
322
|
+
'powershell.exe',
|
|
323
|
+
['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', CONSOLE_HIDER_SCRIPT, '-ParentPid', String(process.pid)],
|
|
324
|
+
{ stdio: 'ignore', windowsHide: true },
|
|
325
|
+
);
|
|
326
|
+
} catch (error) {
|
|
327
|
+
ctx.logger.warn('dsh-arya-computer-use: could not start the console watcher: %s', error?.message ?? String(error));
|
|
328
|
+
return () => {};
|
|
329
|
+
}
|
|
330
|
+
child.on('error', error => {
|
|
331
|
+
ctx.logger.warn('dsh-arya-computer-use: console watcher stopped: %s', error?.message ?? String(error));
|
|
332
|
+
});
|
|
333
|
+
child.unref?.();
|
|
334
|
+
return () => {
|
|
335
|
+
try { child.kill(); } catch { /* already gone */ }
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
298
339
|
export async function apply(ctx, config) {
|
|
340
|
+
const stopConsoleHider = startConsoleHider(ctx);
|
|
299
341
|
registerSkillProvider(ctx);
|
|
300
342
|
ensureMarketSource(ctx);
|
|
301
343
|
void (async () => {
|
|
@@ -438,7 +480,9 @@ export async function apply(ctx, config) {
|
|
|
438
480
|
state: readJson(runtime.stateFile),
|
|
439
481
|
});
|
|
440
482
|
}
|
|
441
|
-
return () => {
|
|
483
|
+
return () => {
|
|
484
|
+
stopConsoleHider();
|
|
485
|
+
};
|
|
442
486
|
}
|
|
443
487
|
|
|
444
488
|
function isLoopbackAddress(address) {
|
package/mcp/adapter-server.mjs
CHANGED
|
@@ -25,6 +25,25 @@ const require = createRequire(import.meta.url);
|
|
|
25
25
|
const CLI_BIN = require.resolve('agent-desktop/bin/agent-desktop');
|
|
26
26
|
const OCR_PS1 = join(HERE, '..', 'scripts', 'ocr.ps1');
|
|
27
27
|
|
|
28
|
+
// Windows console-window trap, the same one browser-launcher.mjs documents and
|
|
29
|
+
// avoids: bin/agent-desktop (the npm shim) spawns the native exe with
|
|
30
|
+
// stdio:'inherit' + windowsHide:false, so every tool call pops a console window
|
|
31
|
+
// on the user's desktop. Spawn the native binary directly instead; stdio stays
|
|
32
|
+
// piped and windowsHide holds. Null when the platform has no released binary,
|
|
33
|
+
// in which case the shim is the only way to run the CLI.
|
|
34
|
+
const NATIVE_BIN = (() => {
|
|
35
|
+
try {
|
|
36
|
+
const pkgDir = dirname(require.resolve('agent-desktop/package.json'));
|
|
37
|
+
const { resolve } = require('agent-desktop/lib/platform');
|
|
38
|
+
const entry = resolve(process.platform, process.arch);
|
|
39
|
+
if (!entry || !entry.released || !entry.binaryName) return null;
|
|
40
|
+
const bin = join(pkgDir, 'bin', entry.binaryName);
|
|
41
|
+
return existsSync(bin) ? bin : null;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
})();
|
|
46
|
+
|
|
28
47
|
// Engine state root: the engine's own default (~/.agent-desktop) — it creates
|
|
29
48
|
// that root with its protected DACL and passes its private-file checks.
|
|
30
49
|
// Do NOT point AGENT_DESKTOP_HOME at AppData\Local: inherited AppContainer ACEs
|
|
@@ -179,11 +198,17 @@ function runCli(args, { headed = effectiveHeaded() } = {}) {
|
|
|
179
198
|
const env = { ...process.env };
|
|
180
199
|
if (state.sessionStarted) env.AGENT_DESKTOP_SESSION = state.sessionId;
|
|
181
200
|
return new Promise((resolve) => {
|
|
182
|
-
const child =
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
201
|
+
const child = NATIVE_BIN
|
|
202
|
+
? spawn(NATIVE_BIN, argv, {
|
|
203
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
204
|
+
windowsHide: true,
|
|
205
|
+
env,
|
|
206
|
+
})
|
|
207
|
+
: spawn(process.execPath, [CLI_BIN, ...argv], {
|
|
208
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
209
|
+
windowsHide: true,
|
|
210
|
+
env,
|
|
211
|
+
});
|
|
187
212
|
let stdout = '';
|
|
188
213
|
let stderr = '';
|
|
189
214
|
child.stdout.on('data', (d) => (stdout += d));
|
package/mcp/browser-launcher.mjs
CHANGED
|
@@ -3,18 +3,42 @@
|
|
|
3
3
|
// DSH launches this file instead of agent-browser itself so the browser lane can
|
|
4
4
|
// follow the plugin's settings (tool surface, Chrome profile, session, CDP
|
|
5
5
|
// endpoint, headed window) without editing the Cordis patch by hand. The real
|
|
6
|
-
// server's stdio is
|
|
6
|
+
// server's stdio is piped through this process, a transparent relay: stdin and
|
|
7
7
|
// stdout carry the MCP protocol untouched, and settings changes are picked up on
|
|
8
8
|
// the next MCP restart.
|
|
9
|
+
//
|
|
10
|
+
// Two window traps on Windows, both avoided here:
|
|
11
|
+
// - Node ignores windowsHide when stdio is 'inherit', so stdio is piped.
|
|
12
|
+
// - bin/agent-browser.js (the npm CLI shim) spawns the native exe with
|
|
13
|
+
// stdio:'inherit' + windowsHide:false, which pops a console window for the
|
|
14
|
+
// grandchild on the user's desktop. We skip the shim and spawn the native
|
|
15
|
+
// exe directly; it speaks MCP over stdio itself.
|
|
9
16
|
|
|
10
17
|
import { spawn } from 'node:child_process';
|
|
11
18
|
import { existsSync, readFileSync } from 'node:fs';
|
|
12
19
|
import { createRequire } from 'node:module';
|
|
13
|
-
import { homedir } from 'node:os';
|
|
14
|
-
import { join } from 'node:path';
|
|
20
|
+
import { homedir, platform, arch } from 'node:os';
|
|
21
|
+
import { dirname, join } from 'node:path';
|
|
22
|
+
import { Transform } from 'node:stream';
|
|
15
23
|
|
|
16
24
|
const require = createRequire(import.meta.url);
|
|
17
|
-
const
|
|
25
|
+
const SHIM = require.resolve('agent-browser/bin/agent-browser.js');
|
|
26
|
+
|
|
27
|
+
// Resolve the platform native binary shipped inside the agent-browser package,
|
|
28
|
+
// mirroring bin/agent-browser.js's naming (with the win32-arm64 → x64 fallback).
|
|
29
|
+
function nativeBin() {
|
|
30
|
+
const dir = dirname(require.resolve('agent-browser/package.json'));
|
|
31
|
+
const osKey = { darwin: 'darwin', linux: 'linux', win32: 'win32' }[platform()];
|
|
32
|
+
const archKey = { x64: 'x64', arm64: 'arm64' }[arch()];
|
|
33
|
+
if (!osKey || !archKey) return null;
|
|
34
|
+
const ext = osKey === 'win32' ? '.exe' : '';
|
|
35
|
+
let name = `agent-browser-${osKey}-${archKey}${ext}`;
|
|
36
|
+
if (osKey === 'win32' && archKey === 'arm64' && !existsSync(join(dir, name))) {
|
|
37
|
+
name = 'agent-browser-win32-x64.exe';
|
|
38
|
+
}
|
|
39
|
+
const bin = join(dir, name);
|
|
40
|
+
return existsSync(bin) ? bin : null;
|
|
41
|
+
}
|
|
18
42
|
|
|
19
43
|
function controlDir() {
|
|
20
44
|
const local = process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local');
|
|
@@ -34,8 +58,71 @@ function readSettings() {
|
|
|
34
58
|
|
|
35
59
|
const text = (value) => (typeof value === 'string' ? value.trim() : '');
|
|
36
60
|
|
|
61
|
+
// Every agent-browser tool repeats the same host/launch flags in its schema, and
|
|
62
|
+
// the plugin already sets them on the server command line below. They are worth
|
|
63
|
+
// about 630 tokens per tool, so the relay rewrites tools/list responses to drop
|
|
64
|
+
// them. agent-browser rejects undeclared argument keys with an invalid-params
|
|
65
|
+
// error naming the key, so a caller that passes a dropped flag is told, not
|
|
66
|
+
// silently mis-ruled.
|
|
67
|
+
export const FLAG_KEYS_HOST = ['extraArgs', 'caCert', 'clearCaCert', 'useSystemCa', 'allowedDomains', 'namespace', 'idleTimeout'];
|
|
68
|
+
export const FLAG_KEYS_REST = ['restore', 'restoreSave', 'restoreCheckFn', 'restoreCheckText', 'restoreCheckUrl', 'session', 'timeoutMs'];
|
|
69
|
+
|
|
70
|
+
export function flagKeys(mode) {
|
|
71
|
+
if (mode === 'lean') return FLAG_KEYS_HOST;
|
|
72
|
+
if (mode === 'off') return [];
|
|
73
|
+
return [...FLAG_KEYS_HOST, ...FLAG_KEYS_REST];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function stripToolFlags(tools, keys) {
|
|
77
|
+
const drop = new Set(keys);
|
|
78
|
+
let removed = 0;
|
|
79
|
+
for (const tool of Array.isArray(tools) ? tools : []) {
|
|
80
|
+
const schema = tool?.inputSchema;
|
|
81
|
+
if (schema?.properties) {
|
|
82
|
+
for (const key of drop) if (key in schema.properties) { delete schema.properties[key]; removed++; }
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(schema?.required)) {
|
|
85
|
+
const kept = schema.required.filter((key) => !drop.has(key));
|
|
86
|
+
removed += schema.required.length - kept.length;
|
|
87
|
+
schema.required = kept;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return removed;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// MCP stdio is one JSON message per line, so the relay transforms line by line.
|
|
94
|
+
// Anything that is not a tools/list response is forwarded byte-for-byte.
|
|
95
|
+
export function transformLine(line, drop) {
|
|
96
|
+
if (!line.includes('"tools"')) return line;
|
|
97
|
+
let message;
|
|
98
|
+
try { message = JSON.parse(line); } catch { return line; }
|
|
99
|
+
if (!Array.isArray(message?.result?.tools)) return line;
|
|
100
|
+
return stripToolFlags(message.result.tools, drop) ? JSON.stringify(message) : line;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function createFlagStripTransform(keys) {
|
|
104
|
+
const drop = new Set(keys);
|
|
105
|
+
let pending = '';
|
|
106
|
+
return new Transform({
|
|
107
|
+
transform(chunk, encoding, callback) {
|
|
108
|
+
pending += chunk.toString('utf8');
|
|
109
|
+
let cut = pending.indexOf('\n');
|
|
110
|
+
while (cut >= 0) {
|
|
111
|
+
this.push(`${transformLine(pending.slice(0, cut), drop)}\n`);
|
|
112
|
+
pending = pending.slice(cut + 1);
|
|
113
|
+
cut = pending.indexOf('\n');
|
|
114
|
+
}
|
|
115
|
+
callback();
|
|
116
|
+
},
|
|
117
|
+
flush(callback) {
|
|
118
|
+
if (pending) this.push(transformLine(pending, drop));
|
|
119
|
+
callback();
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
37
124
|
export function browserArgs(settings = {}) {
|
|
38
|
-
const args = [
|
|
125
|
+
const args = [SHIM, 'mcp'];
|
|
39
126
|
const tools = text(settings.browserTools);
|
|
40
127
|
if (tools) args.push('--tools', tools);
|
|
41
128
|
const cdp = text(settings.browserCdp);
|
|
@@ -51,13 +138,23 @@ export function browserArgs(settings = {}) {
|
|
|
51
138
|
}
|
|
52
139
|
|
|
53
140
|
function main() {
|
|
54
|
-
const
|
|
55
|
-
const
|
|
141
|
+
const settings = readSettings();
|
|
142
|
+
const args = browserArgs(settings);
|
|
143
|
+
const flagKeys_ = flagKeys(text(settings.browserToolFlags));
|
|
144
|
+
const native = nativeBin();
|
|
145
|
+
const child = native
|
|
146
|
+
? spawn(native, args.slice(1), { stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true })
|
|
147
|
+
: spawn(process.execPath, args, { stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
56
148
|
child.on('error', (error) => {
|
|
57
149
|
process.stderr.write(`arya browser lane: could not start agent-browser: ${error.message}\n`);
|
|
58
150
|
process.exit(1);
|
|
59
151
|
});
|
|
60
152
|
child.on('exit', (code, signal) => process.exit(code ?? (signal ? 1 : 0)));
|
|
153
|
+
process.stdin.pipe(child.stdin);
|
|
154
|
+
if (flagKeys_.length) child.stdout.pipe(createFlagStripTransform(flagKeys_)).pipe(process.stdout);
|
|
155
|
+
else child.stdout.pipe(process.stdout);
|
|
156
|
+
child.stderr.pipe(process.stderr);
|
|
157
|
+
child.stdin.on('error', () => { /* child gone mid-write; the exit handler ends us */ });
|
|
61
158
|
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
|
|
62
159
|
process.on(signal, () => { try { child.kill(signal); } catch { /* already gone */ } });
|
|
63
160
|
}
|
package/mcp/settings.mjs
CHANGED
|
@@ -10,6 +10,7 @@ export const DEFAULT_SETTINGS = Object.freeze({
|
|
|
10
10
|
resumeToken: 0, previewToken: 0,
|
|
11
11
|
headedDefault: false, overlayDefault: true, driverLockEnabled: true, toolPreset: 'lite',
|
|
12
12
|
browserTools: 'core', browserProfile: '', browserSession: '', browserStateFile: '', browserCdp: '', browserHeaded: false,
|
|
13
|
+
browserToolFlags: 'strip',
|
|
13
14
|
});
|
|
14
15
|
|
|
15
16
|
export const STOP_SHORTCUT_PATTERN = /^(?:(?:Ctrl\+)?(?:Alt\+)?(?:Shift\+)?(?:Win\+)?(?:[A-Z0-9]|F(?:[1-9]|1[0-9]|2[0-4])|Space|Enter|Tab|Backspace|Delete|Insert|Home|End|PageUp|PageDown|Up|Down|Left|Right|Pause|CapsLock|NumLock|ScrollLock))?$/;
|
|
@@ -37,6 +38,7 @@ const SETTING_SWITCHES = ['progressOverlayEnabled', 'indicatorEnabled', 'cursorI
|
|
|
37
38
|
SETTING_CHOICES.progressOverlayPosition = ['top-right','top-left','bottom-right','bottom-left','custom'];
|
|
38
39
|
SETTING_CHOICES.progressOverlayMode = ['source','summary'];
|
|
39
40
|
SETTING_CHOICES.browserTools = ['core', 'network', 'state', 'debug', 'tabs', 'react', 'mobile', 'all'];
|
|
41
|
+
SETTING_CHOICES.browserToolFlags = ['strip', 'lean', 'off'];
|
|
40
42
|
SETTING_CHOICES.toolPreset = ['lite', 'lazy'];
|
|
41
43
|
export function repairStaleSettings(input) {
|
|
42
44
|
if (!input || typeof input !== 'object' || Array.isArray(input)) return { value: input, repaired: [] };
|
|
@@ -56,10 +58,10 @@ export function normalizeSettings(input = {}) {
|
|
|
56
58
|
if (typeof value.indicatorText !== 'string' || !value.indicatorText.trim() || value.indicatorText.length > 120 || /[\r\n\x00-\x1f]/.test(value.indicatorText)) {
|
|
57
59
|
throw new Error('indicatorText must be one line of 1–120 characters');
|
|
58
60
|
}
|
|
59
|
-
if (typeof value.pointerLabel !== 'string' || !value.pointerLabel.trim() || value.pointerLabel.trim().length > 16 || /[\r\n\x00-\x1f]/.test(value.pointerLabel)) {
|
|
60
|
-
throw new Error('pointerLabel must be one line of 1–16 characters');
|
|
61
|
-
}
|
|
62
|
-
value.pointerLabel = value.pointerLabel.trim();
|
|
61
|
+
if (typeof value.pointerLabel !== 'string' || !value.pointerLabel.trim() || value.pointerLabel.trim().length > 16 || /[\r\n\x00-\x1f]/.test(value.pointerLabel)) {
|
|
62
|
+
throw new Error('pointerLabel must be one line of 1–16 characters');
|
|
63
|
+
}
|
|
64
|
+
value.pointerLabel = value.pointerLabel.trim();
|
|
63
65
|
if (!['top', 'bottom'].includes(value.indicatorPosition)) throw new Error('indicatorPosition must be top or bottom');
|
|
64
66
|
if (!Number.isInteger(value.indicatorIdleMs) || value.indicatorIdleMs < 200 || value.indicatorIdleMs > 3000) throw new Error('indicatorIdleMs must be 200–3000');
|
|
65
67
|
value.stopShortcut = normalizeStopShortcut(value.stopShortcut);
|
|
@@ -72,6 +74,7 @@ export function normalizeSettings(input = {}) {
|
|
|
72
74
|
if (!SETTING_CHOICES.progressOverlayMode.includes(value.progressOverlayMode)) throw new Error('Invalid progressOverlayMode');
|
|
73
75
|
if (!SETTING_CHOICES.toolPreset.includes(value.toolPreset)) throw new Error('toolPreset must be one of ' + SETTING_CHOICES.toolPreset.join(', '));
|
|
74
76
|
if (!SETTING_CHOICES.browserTools.includes(value.browserTools)) throw new Error('browserTools must be one of ' + SETTING_CHOICES.browserTools.join(', '));
|
|
77
|
+
if (!SETTING_CHOICES.browserToolFlags.includes(value.browserToolFlags)) throw new Error('browserToolFlags must be one of ' + SETTING_CHOICES.browserToolFlags.join(', '));
|
|
75
78
|
for (const key of ['browserProfile', 'browserSession', 'browserStateFile', 'browserCdp']) {
|
|
76
79
|
if (typeof value[key] !== 'string') throw new Error(`${key} must be a string`);
|
|
77
80
|
if (/[\r\n\x00-\x1f]/.test(value[key])) throw new Error(`${key} must be one line`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aryacode/dsh-plugin-arya-computer-use",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Computer use for DSH/AryaCode: agent-desktop UIA engine, agent-browser web lane, takeover UI with a ghost cursor",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"scripts/windows-takeover.ps1",
|
|
30
30
|
"scripts/windows-takeover.cs",
|
|
31
31
|
"scripts/ocr.ps1",
|
|
32
|
+
"scripts/hide-consoles.ps1",
|
|
32
33
|
"skills/arya-desktop/SKILL.md",
|
|
33
34
|
"locale/en.json",
|
|
34
35
|
"icon.svg",
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Arya Computer Use - hide the console windows the automation sidecars hand to
|
|
2
|
+
# the desktop.
|
|
3
|
+
#
|
|
4
|
+
# Windows gives a console window named after the executable to any console
|
|
5
|
+
# process that was not started with CREATE_NO_WINDOW. Both engines ship a JS
|
|
6
|
+
# shim (agent-browser/bin/agent-browser.js, agent-desktop/bin/agent-desktop)
|
|
7
|
+
# that runs the native helper with `stdio: 'inherit'` and `windowsHide: false`,
|
|
8
|
+
# and a native helper that outlives the call (the browser daemon) keeps its
|
|
9
|
+
# console for the whole session. The plugin starts the natives itself wherever
|
|
10
|
+
# it can, so the shim never runs, but a lane restart or a helper started by
|
|
11
|
+
# something else still puts a black console on screen - and closing it kills the
|
|
12
|
+
# helper, which is why it comes back.
|
|
13
|
+
#
|
|
14
|
+
# This watcher hides those windows instead of closing them. Hiding only removes
|
|
15
|
+
# the chrome: stdio is piped, the helper keeps running and stays reachable.
|
|
16
|
+
# Nothing else is touched - the window must be a classic console AND its title
|
|
17
|
+
# or its image path must name one of the two engine helpers.
|
|
18
|
+
#
|
|
19
|
+
# Started hidden by index.js for the lifetime of the host process
|
|
20
|
+
# (-ParentPid). -Once -Report prints what would be hidden and changes nothing.
|
|
21
|
+
|
|
22
|
+
param(
|
|
23
|
+
[int]$ParentPid = 0,
|
|
24
|
+
[int]$IntervalMs = 600,
|
|
25
|
+
[switch]$Once,
|
|
26
|
+
[switch]$Report
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
$ErrorActionPreference = 'Stop'
|
|
30
|
+
|
|
31
|
+
# Console window titles on Windows default to the application path, and the
|
|
32
|
+
# vendor shim does not overwrite them. A few releases rename the helper, so the
|
|
33
|
+
# match stays on the package name plus its bin directory.
|
|
34
|
+
$script:TitlePattern = 'agent-(browser|desktop)(-win32-(x64|arm64))?\.exe'
|
|
35
|
+
$script:PathPattern = '\\node_modules\\(agent-browser|agent-desktop)\\bin\\'
|
|
36
|
+
|
|
37
|
+
Add-Type -Namespace AryaCU -Name Win32 -MemberDefinition @'
|
|
38
|
+
public delegate bool EnumWindowsProc(System.IntPtr hWnd, System.IntPtr lParam);
|
|
39
|
+
|
|
40
|
+
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
|
41
|
+
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, System.IntPtr lParam);
|
|
42
|
+
|
|
43
|
+
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
|
|
44
|
+
public static extern int GetWindowTextW(System.IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);
|
|
45
|
+
|
|
46
|
+
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
|
|
47
|
+
public static extern int GetClassNameW(System.IntPtr hWnd, System.Text.StringBuilder lpClassName, int nMaxCount);
|
|
48
|
+
|
|
49
|
+
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
|
50
|
+
public static extern bool IsWindowVisible(System.IntPtr hWnd);
|
|
51
|
+
|
|
52
|
+
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
|
53
|
+
public static extern bool ShowWindow(System.IntPtr hWnd, int nCmdShow);
|
|
54
|
+
|
|
55
|
+
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
|
56
|
+
public static extern uint GetWindowThreadProcessId(System.IntPtr hWnd, out uint lpdwProcessId);
|
|
57
|
+
'@
|
|
58
|
+
|
|
59
|
+
# Returns the visible console windows that belong to one of the engine helpers.
|
|
60
|
+
function Get-EngineConsoles {
|
|
61
|
+
$found = New-Object System.Collections.ArrayList
|
|
62
|
+
|
|
63
|
+
$callback = [AryaCU.Win32+EnumWindowsProc] {
|
|
64
|
+
param([IntPtr]$hWnd, [IntPtr]$lParam)
|
|
65
|
+
|
|
66
|
+
if (-not [AryaCU.Win32]::IsWindowVisible($hWnd)) { return $true }
|
|
67
|
+
|
|
68
|
+
$class = New-Object System.Text.StringBuilder 256
|
|
69
|
+
[void][AryaCU.Win32]::GetClassNameW($hWnd, $class, 256)
|
|
70
|
+
# ConPTY terminals and Windows Terminal use their own classes; only the
|
|
71
|
+
# classic console host is a candidate.
|
|
72
|
+
if ($class.ToString() -ne 'ConsoleWindowClass') { return $true }
|
|
73
|
+
|
|
74
|
+
$title = New-Object System.Text.StringBuilder 1024
|
|
75
|
+
[void][AryaCU.Win32]::GetWindowTextW($hWnd, $title, 1024)
|
|
76
|
+
$text = $title.ToString()
|
|
77
|
+
|
|
78
|
+
$ownerPid = 0
|
|
79
|
+
[void][AryaCU.Win32]::GetWindowThreadProcessId($hWnd, [ref]$ownerPid)
|
|
80
|
+
|
|
81
|
+
$hit = $text -match $script:TitlePattern
|
|
82
|
+
if (-not $hit -and $ownerPid -gt 0) {
|
|
83
|
+
# The title can be blanked by the shim's parent; fall back to the image
|
|
84
|
+
# path when the process is readable (the host runs elevated, so it is).
|
|
85
|
+
try {
|
|
86
|
+
$path = (Get-Process -Id $ownerPid -ErrorAction Stop).Path
|
|
87
|
+
if ($path -and $path -match $script:PathPattern) { $hit = $true }
|
|
88
|
+
} catch { }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if ($hit) {
|
|
92
|
+
[void]$found.Add([pscustomobject]@{ Handle = $hWnd; Title = $text; OwnerPid = $ownerPid })
|
|
93
|
+
}
|
|
94
|
+
return $true
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
[void][AryaCU.Win32]::EnumWindows($callback, [IntPtr]::Zero)
|
|
98
|
+
return @($found.ToArray())
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
while ($true) {
|
|
102
|
+
try {
|
|
103
|
+
foreach ($window in Get-EngineConsoles) {
|
|
104
|
+
if ($Report) {
|
|
105
|
+
'{0} {1} {2}' -f $window.OwnerPid, $window.Handle, $window.Title
|
|
106
|
+
} else {
|
|
107
|
+
[void][AryaCU.Win32]::ShowWindow($window.Handle, 0) # SW_HIDE
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
if ($Once) { throw }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if ($Once) { break }
|
|
115
|
+
|
|
116
|
+
Start-Sleep -Milliseconds ([Math]::Max(200, $IntervalMs))
|
|
117
|
+
|
|
118
|
+
# Leave with the host: an orphaned watcher would keep polling forever.
|
|
119
|
+
if ($ParentPid -gt 0 -and -not (Get-Process -Id $ParentPid -ErrorAction SilentlyContinue)) { break }
|
|
120
|
+
}
|