@xcanwin/manyoyo 5.11.7 → 5.11.8

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.
@@ -16,7 +16,7 @@ const EXTENSIONS = [
16
16
  ['webgl-fingerprint-defender', 'olnbjpaejebpnokblkepbphhembdicik']
17
17
  ];
18
18
 
19
- const SCENE_ORDER = ['mcp-cont-headless', 'mcp-cont-headed', 'mcp-host-headless', 'mcp-host-headed', 'cli-host-headless', 'cli-host-headed'];
19
+ const SCENE_ORDER = ['mcp-cont-headless', 'mcp-cont-headed', 'mcp-host-headless', 'mcp-host-headed', 'cli-host-headless', 'cli-host-headed', 'dev-host-headed'];
20
20
 
21
21
  const SCENE_DEFS = {
22
22
  'mcp-cont-headless': {
@@ -72,6 +72,12 @@ const SCENE_DEFS = {
72
72
  portKey: 'cliHostHeaded',
73
73
  headless: false,
74
74
  listenHost: '0.0.0.0'
75
+ },
76
+ 'dev-host-headed': {
77
+ type: 'host',
78
+ engine: 'dev',
79
+ configFile: 'dev-host-headed.json',
80
+ headless: false
75
81
  }
76
82
  };
77
83
 
@@ -96,6 +102,14 @@ function isCliScene(sceneName) {
96
102
  return Boolean(SCENE_DEFS[sceneName] && SCENE_DEFS[sceneName].engine === 'cli');
97
103
  }
98
104
 
105
+ function isDevScene(sceneName) {
106
+ return Boolean(SCENE_DEFS[sceneName] && SCENE_DEFS[sceneName].engine === 'dev');
107
+ }
108
+
109
+ function isCliSessionScene(sceneName) {
110
+ return isCliScene(sceneName) || isDevScene(sceneName);
111
+ }
112
+
99
113
  function platformFromUserAgent(userAgent) {
100
114
  const ua = String(userAgent || '').toLowerCase();
101
115
  if (ua.includes('macintosh') || ua.includes('mac os x')) {
@@ -154,6 +168,20 @@ function asBoolean(value, fallback = false) {
154
168
  return fallback;
155
169
  }
156
170
 
171
+ function uniqueStrings(values) {
172
+ const out = [];
173
+ const seen = new Set();
174
+ for (const value of values) {
175
+ const text = String(value || '').trim();
176
+ if (!text || seen.has(text)) {
177
+ continue;
178
+ }
179
+ seen.add(text);
180
+ out.push(text);
181
+ }
182
+ return out;
183
+ }
184
+
157
185
  function isHostPermission(value) {
158
186
  if (value === '<all_urls>') {
159
187
  return true;
@@ -305,6 +333,8 @@ class PlaywrightPlugin {
305
333
  extensionProdversion: '132.0.0.0',
306
334
  navigatorPlatform: platformFromUserAgent(DEFAULT_FINGERPRINT_PROFILE.userAgent),
307
335
  disableWebRTC: false,
336
+ devtoolsActivePortPath: '',
337
+ devtoolsCdpTimeout: 60000,
308
338
  composeDir: path.join(__dirname, 'playwright-assets'),
309
339
  ports: {
310
340
  mcpContHeadless: 8931,
@@ -350,7 +380,10 @@ class PlaywrightPlugin {
350
380
  if (invalidScene) {
351
381
  throw new Error(`playwright.enabledScenes 包含未知场景: ${invalidScene}`);
352
382
  }
353
- if (merged.cliSessionScene && !isCliScene(merged.cliSessionScene)) {
383
+ merged.devtoolsActivePortPath = String(merged.devtoolsActivePortPath || '').trim();
384
+ merged.devtoolsCdpTimeout = Number(merged.devtoolsCdpTimeout) || defaultConfig.devtoolsCdpTimeout;
385
+
386
+ if (merged.cliSessionScene && !isCliSessionScene(merged.cliSessionScene)) {
354
387
  throw new Error(`playwright.cliSessionScene 无效: ${merged.cliSessionScene}`);
355
388
  }
356
389
 
@@ -509,6 +542,62 @@ class PlaywrightPlugin {
509
542
  fs.rmSync(this.sceneCliAttachConfigPath(sceneName), { force: true });
510
543
  }
511
544
 
545
+ devtoolsActivePortCandidates() {
546
+ const homeDir = this.config.homeDir;
547
+ const configured = this.config.devtoolsActivePortPath;
548
+ return uniqueStrings([
549
+ configured,
550
+ path.join(homeDir, 'Library', 'Application Support', 'Google', 'Chrome', 'DevToolsActivePort'),
551
+ path.join(homeDir, 'Library', 'Application Support', 'Google', 'Chrome Canary', 'DevToolsActivePort'),
552
+ path.join(homeDir, 'Library', 'Application Support', 'Chromium', 'DevToolsActivePort'),
553
+ path.join(homeDir, 'Library', 'Application Support', 'BraveSoftware', 'Brave-Browser', 'DevToolsActivePort'),
554
+ path.join(homeDir, '.config', 'google-chrome', 'DevToolsActivePort'),
555
+ path.join(homeDir, '.config', 'chromium', 'DevToolsActivePort'),
556
+ path.join(homeDir, '.config', 'google-chrome-beta', 'DevToolsActivePort'),
557
+ path.join(homeDir, '.config', 'google-chrome-unstable', 'DevToolsActivePort'),
558
+ path.join(homeDir, '.config', 'BraveSoftware', 'Brave-Browser', 'DevToolsActivePort'),
559
+ path.join(homeDir, 'AppData', 'Local', 'Google', 'Chrome', 'User Data', 'DevToolsActivePort'),
560
+ path.join(homeDir, 'AppData', 'Local', 'Google', 'Chrome Beta', 'User Data', 'DevToolsActivePort'),
561
+ path.join(homeDir, 'AppData', 'Local', 'Google', 'Chrome SxS', 'User Data', 'DevToolsActivePort'),
562
+ path.join(homeDir, 'AppData', 'Local', 'Chromium', 'User Data', 'DevToolsActivePort'),
563
+ path.join(homeDir, 'AppData', 'Local', 'BraveSoftware', 'Brave-Browser', 'User Data', 'DevToolsActivePort')
564
+ ]);
565
+ }
566
+
567
+ parseDevToolsActivePort(content, filePath = 'DevToolsActivePort') {
568
+ const lines = String(content || '')
569
+ .split(/\r?\n/)
570
+ .map(line => line.trim())
571
+ .filter(Boolean);
572
+ const port = Number(lines[0]);
573
+ const wsPath = lines[1] || '';
574
+
575
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
576
+ throw new Error(`${filePath} 内容无效: 第一行必须是端口号`);
577
+ }
578
+ if (!wsPath.startsWith('/devtools/browser/')) {
579
+ throw new Error(`${filePath} 内容无效: 第二行必须是 /devtools/browser/...`);
580
+ }
581
+
582
+ return { port, wsPath };
583
+ }
584
+
585
+ readDevToolsActivePort() {
586
+ const candidates = this.devtoolsActivePortCandidates();
587
+ for (const candidate of candidates) {
588
+ if (!fs.existsSync(candidate)) {
589
+ continue;
590
+ }
591
+ const content = fs.readFileSync(candidate, 'utf8');
592
+ return {
593
+ ...this.parseDevToolsActivePort(content, candidate),
594
+ filePath: candidate
595
+ };
596
+ }
597
+
598
+ throw new Error(`未找到 DevToolsActivePort,请先在 Chrome 启用 remote debugging,并挂载或配置 devtoolsActivePortPath。已检查: ${candidates.join(', ')}`);
599
+ }
600
+
512
601
  sceneInitScriptPath(sceneName) {
513
602
  const configFile = path.basename(this.sceneConfigPath(sceneName), '.json');
514
603
  return path.join(this.config.configDir, `${configFile}.init.js`);
@@ -988,6 +1077,10 @@ class PlaywrightPlugin {
988
1077
  return { envEntries: [], extraArgs: [], volumeEntries: [] };
989
1078
  }
990
1079
 
1080
+ if (isDevScene(sceneName)) {
1081
+ return this.buildDevHostCliSessionIntegration(dockerCmd, sceneName);
1082
+ }
1083
+
991
1084
  const endpoint = this.readSceneEndpoint(sceneName);
992
1085
  if (!endpoint || !Number.isInteger(endpoint.port) || endpoint.port <= 0 || typeof endpoint.wsPath !== 'string' || !endpoint.wsPath) {
993
1086
  return { envEntries: [], extraArgs: [], volumeEntries: [] };
@@ -1018,6 +1111,35 @@ class PlaywrightPlugin {
1018
1111
  return { envEntries, extraArgs, volumeEntries };
1019
1112
  }
1020
1113
 
1114
+ buildDevHostCliSessionIntegration(dockerCmd, sceneName) {
1115
+ const endpoint = this.readDevToolsActivePort();
1116
+ const normalizedDockerCmd = String(dockerCmd || '').trim().toLowerCase();
1117
+ const connectHost = normalizedDockerCmd === 'podman' ? 'host.containers.internal' : 'host.docker.internal';
1118
+ const cdpEndpoint = `ws://${connectHost}:${endpoint.port}${endpoint.wsPath}`;
1119
+ const hostConfigPath = this.sceneCliAttachConfigPath(sceneName);
1120
+ const containerConfigPath = `/tmp/manyoyo-playwright/${sceneName}.cli-attach.json`;
1121
+
1122
+ this.writeSceneCliAttachConfig(sceneName, {
1123
+ browser: {
1124
+ cdpEndpoint,
1125
+ cdpHeaders: {
1126
+ Host: `127.0.0.1:${endpoint.port}`
1127
+ },
1128
+ cdpTimeout: this.config.devtoolsCdpTimeout
1129
+ }
1130
+ });
1131
+
1132
+ const envEntries = [
1133
+ `PLAYWRIGHT_MCP_CONFIG=${containerConfigPath}`,
1134
+ 'PW_CHROMIUM_ATTACH_TO_OTHER=1'
1135
+ ];
1136
+ const extraArgs = normalizedDockerCmd === 'docker'
1137
+ ? ['--add-host', 'host.docker.internal:host-gateway']
1138
+ : [];
1139
+ const volumeEntries = ['--volume', `${hostConfigPath}:${containerConfigPath}:ro`];
1140
+ return { envEntries, extraArgs, volumeEntries };
1141
+ }
1142
+
1021
1143
  async startContainer(sceneName, options = {}) {
1022
1144
  const runtime = this.config.containerRuntime;
1023
1145
  if (!this.ensureCommandAvailable(runtime)) {
@@ -1323,6 +1445,17 @@ class PlaywrightPlugin {
1323
1445
  return 1;
1324
1446
  }
1325
1447
 
1448
+ startDevHost(sceneName) {
1449
+ try {
1450
+ const endpoint = this.readDevToolsActivePort();
1451
+ this.writeStdout(`[up] ${sceneName} ready via DevToolsActivePort (${endpoint.filePath}, 127.0.0.1:${endpoint.port})`);
1452
+ return 0;
1453
+ } catch (error) {
1454
+ this.writeStderr(`[up] ${sceneName} failed: ${error.message || String(error)}`);
1455
+ return 1;
1456
+ }
1457
+ }
1458
+
1326
1459
  async stopHost(sceneName) {
1327
1460
  const pidFile = this.scenePidFile(sceneName);
1328
1461
  const port = this.scenePort(sceneName);
@@ -1363,6 +1496,13 @@ class PlaywrightPlugin {
1363
1496
  return 0;
1364
1497
  }
1365
1498
 
1499
+ stopDevHost(sceneName) {
1500
+ this.removeSceneEndpoint(sceneName);
1501
+ this.removeSceneCliAttachConfig(sceneName);
1502
+ this.writeStdout(`[down] ${sceneName} no managed process`);
1503
+ return 0;
1504
+ }
1505
+
1366
1506
  async statusHost(sceneName) {
1367
1507
  const pidFile = this.scenePidFile(sceneName);
1368
1508
  const port = this.scenePort(sceneName);
@@ -1392,7 +1532,28 @@ class PlaywrightPlugin {
1392
1532
  return 0;
1393
1533
  }
1394
1534
 
1535
+ statusDevHost(sceneName) {
1536
+ try {
1537
+ const endpoint = this.readDevToolsActivePort();
1538
+ this.writeStdout(`[status] ${sceneName} configured (${endpoint.filePath}, 127.0.0.1:${endpoint.port})`);
1539
+ } catch {
1540
+ this.writeStdout(`[status] ${sceneName} not configured`);
1541
+ }
1542
+ return 0;
1543
+ }
1544
+
1395
1545
  async healthScene(sceneName) {
1546
+ if (isDevScene(sceneName)) {
1547
+ try {
1548
+ const endpoint = this.readDevToolsActivePort();
1549
+ this.writeStdout(`[health] ${sceneName} ok (DevToolsActivePort 127.0.0.1:${endpoint.port})`);
1550
+ return 0;
1551
+ } catch (error) {
1552
+ this.writeStdout(`[health] ${sceneName} fail (${error.message || String(error)})`);
1553
+ return 1;
1554
+ }
1555
+ }
1556
+
1396
1557
  const port = this.scenePort(sceneName);
1397
1558
  if (await this.portReady(port)) {
1398
1559
  this.writeStdout(`[health] ${sceneName} ok (127.0.0.1:${port})`);
@@ -1403,6 +1564,11 @@ class PlaywrightPlugin {
1403
1564
  }
1404
1565
 
1405
1566
  logsHost(sceneName) {
1567
+ if (isDevScene(sceneName)) {
1568
+ this.writeStdout(`[logs] ${sceneName} no managed process`);
1569
+ return 0;
1570
+ }
1571
+
1406
1572
  const logFile = this.sceneLogFile(sceneName);
1407
1573
  if (!fs.existsSync(logFile)) {
1408
1574
  this.writeStdout(`[logs] ${sceneName} no log file: ${logFile}`);
@@ -1613,6 +1779,23 @@ class PlaywrightPlugin {
1613
1779
 
1614
1780
  async runOnScene(action, sceneName, options = {}) {
1615
1781
  const def = SCENE_DEFS[sceneName];
1782
+ if (isDevScene(sceneName)) {
1783
+ if (action === 'up') {
1784
+ return this.startDevHost(sceneName);
1785
+ }
1786
+ if (action === 'down') {
1787
+ return this.stopDevHost(sceneName);
1788
+ }
1789
+ if (action === 'status') {
1790
+ return this.statusDevHost(sceneName);
1791
+ }
1792
+ if (action === 'health') {
1793
+ return await this.healthScene(sceneName);
1794
+ }
1795
+ if (action === 'logs') {
1796
+ return this.logsHost(sceneName);
1797
+ }
1798
+ }
1616
1799
  if (action === 'up') {
1617
1800
  return def.type === 'container'
1618
1801
  ? await this.startContainer(sceneName, options)
@@ -50,7 +50,7 @@
50
50
  // mixed: 支持容器+宿主机;container: 仅容器;host: 仅宿主机
51
51
  "runtime": "mixed",
52
52
  // 启用场景(可按需裁剪)
53
- "enabledScenes": ["mcp-cont-headless", "mcp-cont-headed", "mcp-host-headless", "mcp-host-headed", "cli-host-headless", "cli-host-headed"],
53
+ "enabledScenes": ["mcp-cont-headless", "mcp-cont-headed", "mcp-host-headless", "mcp-host-headed", "cli-host-headless", "cli-host-headed", "dev-host-headed"],
54
54
  // my run 默认注入的 playwright-cli 宿主场景
55
55
  "cliSessionScene": "cli-host-headless",
56
56
  // mcp-add 默认 host(可改为 localhost / 127.0.0.1)
@@ -63,6 +63,10 @@
63
63
  "navigatorPlatform": "MacIntel",
64
64
  // 是否禁用 WebRTC(默认 false)
65
65
  "disableWebRTC": false,
66
+ // dev-host-headed 读取的 Chrome DevToolsActivePort 文件;留空时自动探测常见路径
67
+ "devtoolsActivePortPath": "",
68
+ // dev-host-headed 连接 Chrome CDP 的超时时间(毫秒)
69
+ "devtoolsCdpTimeout": 60000,
66
70
  "ports": {
67
71
  "mcpContHeadless": 8931,
68
72
  "mcpContHeaded": 8932,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.11.7",
3
+ "version": "5.11.8",
4
4
  "imageVersion": "1.9.1-common",
5
5
  "playwrightCliVersion": "0.1.14",
6
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",