@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 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) {
@@ -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 = spawn(process.execPath, [CLI_BIN, ...argv], {
183
- stdio: ['ignore', 'pipe', 'pipe'],
184
- windowsHide: true,
185
- env,
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));
@@ -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 inherited, so this process is a transparent pipe: stdin and
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 BIN = require.resolve('agent-browser/bin/agent-browser.js');
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 = [BIN, 'mcp'];
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 args = browserArgs(readSettings());
55
- const child = spawn(process.execPath, args, { stdio: 'inherit', windowsHide: true });
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.2",
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
+ }