@xcanwin/manyoyo 5.11.7 → 5.11.9

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: [] };
@@ -999,6 +1092,7 @@ class PlaywrightPlugin {
999
1092
  const hostConfigPath = this.sceneCliAttachConfigPath(sceneName);
1000
1093
  const containerConfigPath = `/tmp/manyoyo-playwright/${sceneName}.cli-attach.json`;
1001
1094
  this.writeSceneCliAttachConfig(sceneName, {
1095
+ outputDir: '/tmp/.playwright-cli',
1002
1096
  browser: {
1003
1097
  // launch-server(非 shared)不预创建 context;remoteEndpoint 存在时
1004
1098
  // playwright-cli 默认 isolated=false 会走 contexts()[0] 并报
@@ -1018,6 +1112,36 @@ class PlaywrightPlugin {
1018
1112
  return { envEntries, extraArgs, volumeEntries };
1019
1113
  }
1020
1114
 
1115
+ buildDevHostCliSessionIntegration(dockerCmd, sceneName) {
1116
+ const endpoint = this.readDevToolsActivePort();
1117
+ const normalizedDockerCmd = String(dockerCmd || '').trim().toLowerCase();
1118
+ const connectHost = normalizedDockerCmd === 'podman' ? 'host.containers.internal' : 'host.docker.internal';
1119
+ const cdpEndpoint = `ws://${connectHost}:${endpoint.port}${endpoint.wsPath}`;
1120
+ const hostConfigPath = this.sceneCliAttachConfigPath(sceneName);
1121
+ const containerConfigPath = `/tmp/manyoyo-playwright/${sceneName}.cli-attach.json`;
1122
+
1123
+ this.writeSceneCliAttachConfig(sceneName, {
1124
+ outputDir: '/tmp/.playwright-cli',
1125
+ browser: {
1126
+ cdpEndpoint,
1127
+ cdpHeaders: {
1128
+ Host: `127.0.0.1:${endpoint.port}`
1129
+ },
1130
+ cdpTimeout: this.config.devtoolsCdpTimeout
1131
+ }
1132
+ });
1133
+
1134
+ const envEntries = [
1135
+ `PLAYWRIGHT_MCP_CONFIG=${containerConfigPath}`,
1136
+ 'PW_CHROMIUM_ATTACH_TO_OTHER=1'
1137
+ ];
1138
+ const extraArgs = normalizedDockerCmd === 'docker'
1139
+ ? ['--add-host', 'host.docker.internal:host-gateway']
1140
+ : [];
1141
+ const volumeEntries = ['--volume', `${hostConfigPath}:${containerConfigPath}:ro`];
1142
+ return { envEntries, extraArgs, volumeEntries };
1143
+ }
1144
+
1021
1145
  async startContainer(sceneName, options = {}) {
1022
1146
  const runtime = this.config.containerRuntime;
1023
1147
  if (!this.ensureCommandAvailable(runtime)) {
@@ -1323,6 +1447,17 @@ class PlaywrightPlugin {
1323
1447
  return 1;
1324
1448
  }
1325
1449
 
1450
+ startDevHost(sceneName) {
1451
+ try {
1452
+ const endpoint = this.readDevToolsActivePort();
1453
+ this.writeStdout(`[up] ${sceneName} ready via DevToolsActivePort (${endpoint.filePath}, 127.0.0.1:${endpoint.port})`);
1454
+ return 0;
1455
+ } catch (error) {
1456
+ this.writeStderr(`[up] ${sceneName} failed: ${error.message || String(error)}`);
1457
+ return 1;
1458
+ }
1459
+ }
1460
+
1326
1461
  async stopHost(sceneName) {
1327
1462
  const pidFile = this.scenePidFile(sceneName);
1328
1463
  const port = this.scenePort(sceneName);
@@ -1363,6 +1498,13 @@ class PlaywrightPlugin {
1363
1498
  return 0;
1364
1499
  }
1365
1500
 
1501
+ stopDevHost(sceneName) {
1502
+ this.removeSceneEndpoint(sceneName);
1503
+ this.removeSceneCliAttachConfig(sceneName);
1504
+ this.writeStdout(`[down] ${sceneName} no managed process`);
1505
+ return 0;
1506
+ }
1507
+
1366
1508
  async statusHost(sceneName) {
1367
1509
  const pidFile = this.scenePidFile(sceneName);
1368
1510
  const port = this.scenePort(sceneName);
@@ -1392,7 +1534,28 @@ class PlaywrightPlugin {
1392
1534
  return 0;
1393
1535
  }
1394
1536
 
1537
+ statusDevHost(sceneName) {
1538
+ try {
1539
+ const endpoint = this.readDevToolsActivePort();
1540
+ this.writeStdout(`[status] ${sceneName} configured (${endpoint.filePath}, 127.0.0.1:${endpoint.port})`);
1541
+ } catch {
1542
+ this.writeStdout(`[status] ${sceneName} not configured`);
1543
+ }
1544
+ return 0;
1545
+ }
1546
+
1395
1547
  async healthScene(sceneName) {
1548
+ if (isDevScene(sceneName)) {
1549
+ try {
1550
+ const endpoint = this.readDevToolsActivePort();
1551
+ this.writeStdout(`[health] ${sceneName} ok (DevToolsActivePort 127.0.0.1:${endpoint.port})`);
1552
+ return 0;
1553
+ } catch (error) {
1554
+ this.writeStdout(`[health] ${sceneName} fail (${error.message || String(error)})`);
1555
+ return 1;
1556
+ }
1557
+ }
1558
+
1396
1559
  const port = this.scenePort(sceneName);
1397
1560
  if (await this.portReady(port)) {
1398
1561
  this.writeStdout(`[health] ${sceneName} ok (127.0.0.1:${port})`);
@@ -1403,6 +1566,11 @@ class PlaywrightPlugin {
1403
1566
  }
1404
1567
 
1405
1568
  logsHost(sceneName) {
1569
+ if (isDevScene(sceneName)) {
1570
+ this.writeStdout(`[logs] ${sceneName} no managed process`);
1571
+ return 0;
1572
+ }
1573
+
1406
1574
  const logFile = this.sceneLogFile(sceneName);
1407
1575
  if (!fs.existsSync(logFile)) {
1408
1576
  this.writeStdout(`[logs] ${sceneName} no log file: ${logFile}`);
@@ -1613,6 +1781,23 @@ class PlaywrightPlugin {
1613
1781
 
1614
1782
  async runOnScene(action, sceneName, options = {}) {
1615
1783
  const def = SCENE_DEFS[sceneName];
1784
+ if (isDevScene(sceneName)) {
1785
+ if (action === 'up') {
1786
+ return this.startDevHost(sceneName);
1787
+ }
1788
+ if (action === 'down') {
1789
+ return this.stopDevHost(sceneName);
1790
+ }
1791
+ if (action === 'status') {
1792
+ return this.statusDevHost(sceneName);
1793
+ }
1794
+ if (action === 'health') {
1795
+ return await this.healthScene(sceneName);
1796
+ }
1797
+ if (action === 'logs') {
1798
+ return this.logsHost(sceneName);
1799
+ }
1800
+ }
1616
1801
  if (action === 'up') {
1617
1802
  return def.type === 'container'
1618
1803
  ? 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.9",
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",