@xcanwin/manyoyo 8.2.1 → 8.3.1

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.
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+
5
+ // rootless podman 的默认网络组件:4.x 用 slirp4netns,5.x 起用 pasta
6
+ const PACKAGE_OF = { slirp4netns: 'slirp4netns', pasta: 'passt' };
7
+
8
+ function parsePodmanMajor(versionText) {
9
+ const match = /(\d+)\.\d+/.exec(String(versionText || ''));
10
+ return match ? Number(match[1]) : null;
11
+ }
12
+
13
+ function defaultNetworkComponent(major) {
14
+ return major !== null && major >= 5 ? 'pasta' : 'slirp4netns';
15
+ }
16
+
17
+ function readOsRelease() {
18
+ try {
19
+ return fs.readFileSync('/etc/os-release', 'utf8');
20
+ } catch (e) {
21
+ return '';
22
+ }
23
+ }
24
+
25
+ function hasCommand(name) {
26
+ return String(process.env.PATH || '').split(':').some(dir => dir && fs.existsSync(`${dir}/${name}`));
27
+ }
28
+
29
+ // 与 scripts/offline/install.sh 的 Linux 分支保持同一套规则;不认识的发行版返回 null(只给说明)
30
+ function installCommand(component, { osRelease = readOsRelease(), hasDnf = hasCommand('dnf') } = {}) {
31
+ const pkg = PACKAGE_OF[component] || component;
32
+ const ids = (String(osRelease).match(/^(?:ID|ID_LIKE)=(.*)$/gm) || [])
33
+ .map(line => line.split('=')[1].replace(/["']/g, '').toLowerCase())
34
+ .join(' ')
35
+ .split(/\s+/);
36
+ if (ids.some(id => id === 'debian' || id === 'ubuntu')) return `sudo apt-get install -y ${pkg}`;
37
+ if (ids.some(id => ['fedora', 'rhel', 'centos'].includes(id))) return `sudo ${hasDnf ? 'dnf' : 'yum'} install -y ${pkg}`;
38
+ return null;
39
+ }
40
+
41
+ function describeMissing(component, options) {
42
+ const pkg = PACKAGE_OF[component] || component;
43
+ const command = installCommand(component, options);
44
+ return {
45
+ reason: `rootless Podman 缺少默认网络组件 ${component},容器无法创建网络。`,
46
+ action: command ? `请执行: ${command}` : `请用系统包管理器安装 ${pkg}(提供 ${component})。`
47
+ };
48
+ }
49
+
50
+ /**
51
+ * 检查 rootless podman 的默认网络组件是否存在(只在 Linux 的 podman 上检查)。
52
+ * @returns {Promise<null|{component: string, reason: string, action: string}>} null 表示无需处理
53
+ */
54
+ async function checkRootlessNetwork(runCommand, { command, env, timeout, platform = process.platform } = {}) {
55
+ if (platform !== 'linux' || !/(^|\/)podman$/.test(String(command || ''))) return null;
56
+ const run = async args => String(await runCommand(command, args, { env, timeout }) || '').trim();
57
+ try {
58
+ if ((await run(['info', '--format', '{{.Host.Security.Rootless}}'])) !== 'true') return null;
59
+ const component = defaultNetworkComponent(parsePodmanMajor(await run(['--version'])));
60
+ const field = component === 'pasta' ? 'Pasta' : 'Slirp4NetNS';
61
+ if (await run(['info', '--format', `{{.Host.${field}.Executable}}`])) return { component, missing: false };
62
+ return { component, missing: true, ...describeMissing(component) };
63
+ } catch (e) {
64
+ return null;
65
+ }
66
+ }
67
+
68
+ module.exports = { parsePodmanMajor, defaultNetworkComponent, installCommand, describeMissing, checkRootlessNetwork };
package/lib/serve-log.js CHANGED
@@ -84,6 +84,16 @@ function sanitizeSensitiveData(obj) {
84
84
  return result;
85
85
  }
86
86
 
87
+ // 命令行参数里的 KEY=值(--env / 镜像构建参数等):敏感键的值整体打码,用于错误信息与 show-command
88
+ function redactCommandArgs(args) {
89
+ const sensitive = /KEY|TOKEN|SECRET|PASSWORD|PASS|AUTH|CREDENTIAL/i;
90
+ return (Array.isArray(args) ? args : []).map(arg => {
91
+ const text = String(arg);
92
+ const match = /^([A-Za-z_][A-Za-z0-9_.]*)=/.exec(text);
93
+ return match && sensitive.test(match[1]) ? `${match[1]}=****` : text;
94
+ });
95
+ }
96
+
87
97
  function formatServeLogValue(value) {
88
98
  if (value instanceof Error) {
89
99
  return sanitizeServeLogText(value.stack || value.message || String(value));
@@ -111,6 +121,7 @@ module.exports = {
111
121
  sanitizeProcessArgv,
112
122
  sanitizeServeLogText,
113
123
  sanitizeSensitiveData,
124
+ redactCommandArgs,
114
125
  formatServeLogValue,
115
126
  getServeProcessSnapshot
116
127
  };
package/lib/web/server.js CHANGED
@@ -10,6 +10,7 @@ const http = require('http');
10
10
  const WebSocket = require('ws');
11
11
  const JSON5 = require('json5');
12
12
  const { buildContainerRunArgs } = require('../container-run');
13
+ const { buildContainerIntegration, mergeIntegration } = require('../plugin');
13
14
  const { applyAptMirror } = require('../mirrors');
14
15
  const { normalizeMirrors } = require('../runtime-normalizers');
15
16
  const { estimateContainerCapacity } = require('../capacity');
@@ -3325,6 +3326,27 @@ function readWebMirrors(state) {
3325
3326
  }
3326
3327
  }
3327
3328
 
3329
+ // 会话容器与 CLI run 共用同一个 Playwright 浏览器模式集成(挂载目录、NO_PROXY 等)
3330
+ async function buildWebPlaywrightIntegration(ctx, state, runtime) {
3331
+ let globalConfig = {};
3332
+ try {
3333
+ const snapshot = readWebConfigSnapshot(state.webConfigPath);
3334
+ globalConfig = snapshot.parseError ? {} : (snapshot.parsed || {});
3335
+ } catch (e) {
3336
+ globalConfig = {};
3337
+ }
3338
+ const integration = await buildContainerIntegration({
3339
+ globalConfig,
3340
+ homeDir: ctx.homeDir,
3341
+ runtimeCommand: ctx.dockerCmd || 'docker',
3342
+ envEntries: (runtime.containerEnvs || []).filter((_, i) => i % 2 === 1)
3343
+ });
3344
+ if (integration.warning) {
3345
+ console.warn(integration.warning);
3346
+ }
3347
+ return mergeIntegration(runtime, integration);
3348
+ }
3349
+
3328
3350
  async function ensureWebContainer(ctx, state, containerInput, messageSessionRef = null) {
3329
3351
  const runtime = typeof containerInput === 'string'
3330
3352
  ? buildStaticContainerRuntime(ctx, containerInput)
@@ -3336,6 +3358,7 @@ async function ensureWebContainer(ctx, state, containerInput, messageSessionRef
3336
3358
 
3337
3359
  if (!ctx.containerExists(runtime.containerName)) {
3338
3360
  const mirrors = readWebMirrors(state);
3361
+ const playwright = await buildWebPlaywrightIntegration(ctx, state, runtime);
3339
3362
  const args = buildContainerRunArgs({
3340
3363
  mirrors,
3341
3364
  containerName: runtime.containerName,
@@ -3344,9 +3367,9 @@ async function ensureWebContainer(ctx, state, containerInput, messageSessionRef
3344
3367
  imageName: runtime.imageName,
3345
3368
  imageVersion: runtime.imageVersion,
3346
3369
  contModeArgs: runtime.contModeArgs,
3347
- containerExtraArgs: runtime.containerExtraArgs,
3348
- containerEnvs: runtime.containerEnvs,
3349
- containerVolumes: runtime.containerVolumes,
3370
+ containerExtraArgs: playwright.containerExtraArgs,
3371
+ containerEnvs: playwright.containerEnvs,
3372
+ containerVolumes: playwright.containerVolumes,
3350
3373
  containerPorts: runtime.containerPorts,
3351
3374
  defaultCommand: runtime.defaultCommand
3352
3375
  });
@@ -6327,6 +6350,7 @@ async function startWebServer(options) {
6327
6350
  authPassAuto: options.authPassAuto,
6328
6351
  serveTitle: typeof options.serveTitle === 'string' ? options.serveTitle : null,
6329
6352
  dockerCmd: options.dockerCmd,
6353
+ homeDir: options.homeDir,
6330
6354
  dockerEnv: options.dockerEnv,
6331
6355
  loginTokenDir: options.loginTokenDir || '',
6332
6356
  doctorCheck: options.doctorCheck,
@@ -68,38 +68,25 @@
68
68
  // }
69
69
  // },
70
70
 
71
- // 可选插件(manyoyo playwright)
71
+ // 可选插件(manyoyo playwright):容器内浏览器默认就能用,这里只放少量可调项
72
72
  "plugins": {
73
73
  "playwright": {
74
- // mixed: 支持容器+宿主机;container: 仅容器;host: 仅宿主机
75
- "runtime": "mixed",
76
- // 启用场景(可按需裁剪)
77
- "enabledScenes": ["mcp-cont-headless", "mcp-cont-headed", "mcp-host-headless", "mcp-host-headed", "cli-host-headless", "cli-host-headed", "dev-host-headed"],
78
- // my run 默认注入的 playwright-cli 宿主场景
79
- "cliSessionScene": "cli-host-headless",
80
- // mcp-add 默认 host(可改为 localhost / 127.0.0.1)
81
- "mcpDefaultHost": "host.docker.internal",
82
- // mcp-cont-headed 场景读取的密码环境变量名(默认 VNC_PASSWORD)
83
- "vncPasswordEnvKey": "VNC_PASSWORD",
84
- // playwright ext-download 的 CRX prodversion 参数
85
- "extensionProdversion": "132.0.0.0",
86
- // 注入 navigator.platform(默认与内置 UA 对齐为 MacIntel)
87
- "navigatorPlatform": "MacIntel",
74
+ // headed / chrome / vnc 模式的浏览器服务端口(容器通过 token 路径连接)
75
+ "port": 8935,
76
+ // vnc 模式的原生 VNC 端口与 noVNC 网页端口(只发布到 127.0.0.1)
77
+ "vncPort": 5900,
78
+ "novncPort": 6080,
79
+ // 浏览器语言与时区,留空时跟随宿主机
80
+ "locale": "",
81
+ "timezoneId": "",
82
+ // 强制 navigator.platform;留空则使用浏览器真实值(推荐,与 UA、字体、WebGL 自洽)
83
+ "navigatorPlatform": "",
88
84
  // 是否禁用 WebRTC(默认 false)
89
85
  "disableWebRTC": false,
90
- // dev-host-headed 读取的 Chrome DevToolsActivePort 文件;留空时自动探测常见路径
86
+ // chrome 模式读取的 DevToolsActivePort 文件;留空时自动探测常见路径
91
87
  "devtoolsActivePortPath": "",
92
- // dev-host-headed 连接 Chrome CDP 的超时时间(毫秒)
93
- "devtoolsCdpTimeout": 60000,
94
- "ports": {
95
- "mcpContHeadless": 8931,
96
- "mcpContHeaded": 8932,
97
- "mcpHostHeadless": 8933,
98
- "mcpHostHeaded": 8934,
99
- "cliHostHeadless": 8935,
100
- "cliHostHeaded": 8936,
101
- "mcpContHeadedNoVnc": 6080
102
- }
88
+ // playwright ext-download 的 CRX prodversion 参数
89
+ "extensionProdversion": "132.0.0.0"
103
90
  }
104
91
  },
105
92
 
@@ -118,7 +105,8 @@
118
105
  "yolo": "c",
119
106
  "plugins": {
120
107
  "playwright": {
121
- "runtime": "container"
108
+ "locale": "zh-CN",
109
+ "timezoneId": "Asia/Shanghai"
122
110
  }
123
111
  }
124
112
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "8.2.1",
4
- "imageVersion": "2.1.0-common",
3
+ "version": "8.3.1",
4
+ "imageVersion": "2.1.1-common",
5
5
  "playwrightCliVersion": "0.1.21",
6
- "description": "AI Agent CLI Security Sandbox for Docker and Podman",
6
+ "description": "MANYOYO – open-source AI agent sandbox: run AI coding agents (Claude Code / Codex / Gemini) safely in Docker/Podman",
7
7
  "keywords": [
8
8
  "manyoyo",
9
9
  "ai-agent",
@@ -40,7 +40,7 @@
40
40
  "prepack": "npm run build:web",
41
41
  "test": "jest --coverage && npm run test:web",
42
42
  "test:unit": "jest test/ --testPathIgnorePatterns '<rootDir>/temp/' '<rootDir>/test/integration/' && npm run test:web",
43
- "test:integration": "jest test/integration/",
43
+ "test:integration": "jest test/integration/ --runInBand",
44
44
  "test:installer": "jest test/installer.test.js",
45
45
  "lint:sh": "command -v shellcheck >/dev/null 2>&1 || { echo '未安装 shellcheck:macOS 用 brew install shellcheck,Debian/Ubuntu 用 sudo apt install shellcheck,或用 docker run --rm -v \"$PWD:/mnt\" -w /mnt koalaman/shellcheck:stable scripts/install.sh scripts/offline/*.sh' >&2; exit 1; } && shellcheck -s sh -x -e SC1091 scripts/install.sh scripts/offline/*.sh",
46
46
  "docs:dev": "vitepress dev docs --host 127.0.0.1 --port 5173",
@@ -65,10 +65,9 @@
65
65
  "manyoyo.example.json"
66
66
  ],
67
67
  "dependencies": {
68
- "@playwright/mcp": "0.0.82",
68
+ "@playwright/cli": "0.1.21",
69
69
  "commander": "^15.0.0",
70
70
  "json5": "^2.2.3",
71
- "playwright": "1.63.0",
72
71
  "ws": "^8.21.3"
73
72
  },
74
73
  "devDependencies": {
@@ -1,12 +0,0 @@
1
- 'use strict';
2
-
3
- (function () {
4
- const platformValue = 'MacIntel';
5
- try {
6
- const navProto = Object.getPrototypeOf(navigator);
7
- Object.defineProperty(navProto, 'platform', {
8
- configurable: true,
9
- get: () => platformValue
10
- });
11
- } catch (_) {}
12
- })();
@@ -1,37 +0,0 @@
1
- {
2
- "outputDir": "/tmp/.playwright-cli",
3
- "browser": {
4
- "chromiumSandbox": true,
5
- "browserName": "chromium",
6
- "initScript": [
7
- "/app/config/cli-cont-headless.init.js"
8
- ],
9
- "launchOptions": {
10
- "channel": "chromium",
11
- "headless": true,
12
- "args": [
13
- "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
14
- "--lang=zh-CN",
15
- "--window-size=1366,768",
16
- "--disable-blink-features=AutomationControlled",
17
- "--force-webrtc-ip-handling-policy=disable_non_proxied_udp"
18
- ]
19
- },
20
- "contextOptions": {
21
- "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
22
- "locale": "zh-CN",
23
- "timezoneId": "Asia/Shanghai",
24
- "viewport": {
25
- "width": 1366,
26
- "height": 768
27
- },
28
- "screen": {
29
- "width": 1366,
30
- "height": 768
31
- },
32
- "extraHTTPHeaders": {
33
- "Accept-Language": "zh-CN,zh;q=0.9"
34
- }
35
- }
36
- }
37
- }
@@ -1,19 +0,0 @@
1
- services:
2
- playwright:
3
- build:
4
- context: .
5
- dockerfile: headed.Dockerfile
6
- args:
7
- PLAYWRIGHT_MCP_BASE_IMAGE: mcr.microsoft.com/playwright/mcp:${PLAYWRIGHT_MCP_DOCKER_TAG:-latest}
8
- image: ${PLAYWRIGHT_MCP_IMAGE:-localhost/xcanwin/manyoyo-playwright-headed}
9
- container_name: ${PLAYWRIGHT_MCP_CONTAINER_NAME:-my-playwright-mcp-cont-headed}
10
- init: true
11
- stdin_open: true
12
- ports:
13
- - "127.0.0.1:${PLAYWRIGHT_MCP_PORT:-8932}:${PLAYWRIGHT_MCP_PORT:-8932}"
14
- - "127.0.0.1:${PLAYWRIGHT_MCP_NOVNC_PORT:-6080}:6080"
15
- environment:
16
- VNC_PASSWORD: "${VNC_PASSWORD:?VNC_PASSWORD is required}"
17
- volumes:
18
- - ${PLAYWRIGHT_MCP_CONFIG_PATH:?PLAYWRIGHT_MCP_CONFIG_PATH is required}:/app/config/playwright.json:ro
19
- command: ["node", "cli.js", "--config", "/app/config/playwright.json"]
@@ -1,11 +0,0 @@
1
- services:
2
- playwright:
3
- image: mcr.microsoft.com/playwright/mcp:${PLAYWRIGHT_MCP_DOCKER_TAG:-latest}
4
- container_name: ${PLAYWRIGHT_MCP_CONTAINER_NAME:-my-playwright-mcp-cont-headless}
5
- init: true
6
- stdin_open: true
7
- ports:
8
- - "127.0.0.1:${PLAYWRIGHT_MCP_PORT:-8931}:${PLAYWRIGHT_MCP_PORT:-8931}"
9
- volumes:
10
- - ${PLAYWRIGHT_MCP_CONFIG_PATH:?PLAYWRIGHT_MCP_CONFIG_PATH is required}:/app/config/playwright.json:ro
11
- command: ["--config", "/app/config/playwright.json"]
@@ -1,15 +0,0 @@
1
- ARG PLAYWRIGHT_MCP_BASE_IMAGE=mcr.microsoft.com/playwright/mcp:latest
2
- FROM ${PLAYWRIGHT_MCP_BASE_IMAGE}
3
-
4
- USER root
5
- RUN apt-get update && \
6
- apt-get install -y --no-install-recommends \
7
- ca-certificates xvfb x11vnc novnc websockify fluxbox && \
8
- update-ca-certificates && \
9
- apt-get clean && \
10
- rm -rf /tmp/* /var/tmp/* /var/log/apt /var/log/*.log /var/lib/apt/lists/*
11
-
12
- COPY init-headed.sh /usr/local/bin/init-headed.sh
13
- RUN chmod +x /usr/local/bin/init-headed.sh
14
-
15
- ENTRYPOINT ["/usr/local/bin/init-headed.sh"]
@@ -1,17 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- export DISPLAY=:99
5
-
6
- Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp &
7
- fluxbox >/tmp/fluxbox.log 2>&1 &
8
-
9
- if [[ -z "${VNC_PASSWORD:-}" ]]; then
10
- echo "VNC_PASSWORD is required."
11
- exit 1
12
- fi
13
-
14
- x11vnc -display :99 -forever -shared -rfbport 5900 -localhost -passwd "$VNC_PASSWORD" >/tmp/x11vnc.log 2>&1 &
15
- websockify --web=/usr/share/novnc/ 6080 localhost:5900 >/tmp/websockify.log 2>&1 &
16
-
17
- exec "$@"