@xcanwin/manyoyo 8.5.0 → 8.6.0

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/lib/web/server.js CHANGED
@@ -15,17 +15,17 @@ const { applyAptMirror } = require('../mirrors');
15
15
  const containerState = require('../container-state');
16
16
  const { buildExecArgs, resolveContainerId } = require('../container-exec');
17
17
  const { createNetworkManager, NETWORK_NAME } = require('../container-network');
18
- const { normalizePolicy } = require('../network-policy');
18
+ const { normalizePolicy, isUnrestricted: isUnrestrictedPolicy } = require('../network-policy');
19
19
  const { withEnvEndpoints } = require('../network-endpoints');
20
20
  const { egressPaths, isSidecarName } = require('../egress-sidecar');
21
21
  const { read: readDenied } = require('../egress-denied');
22
22
  const { isBrowserBackground } = require('../browser-background');
23
23
  const { createDeniedNotifier } = require('./denied-notify');
24
- const { domainAllowed } = require('../network-policy');
24
+ const { domainRuleAllows } = require('../network-policy');
25
25
  const { createPortForwarder } = require('../port-forward');
26
26
  const { createContainerManageRoutes, pendingRisks } = require('./container-manage');
27
27
  const { resolveManageOptions } = require('../container-manage-options');
28
- const { defaultPolicy } = require('../network-policy');
28
+ const { defaultPolicy, usesProxy } = require('../network-policy');
29
29
  const { normalizeMirrors } = require('../runtime-normalizers');
30
30
  const { estimateContainerCapacity } = require('../capacity');
31
31
  const { extractAgentMessageFromCodexJsonl } = require('../codex-output');
@@ -2826,10 +2826,10 @@ function buildCreateRuntime(ctx, state, payload) {
2826
2826
  parseServerListen: () => ({ host: '', port: 0 })
2827
2827
  });
2828
2828
 
2829
- // 自启动与网络:请求 > runs.<name> > 全局配置;请求里放开限制(open / 非本机绑定)要带 confirmRisk
2829
+ // 自启动与网络:请求 > runs.<name> > 全局配置;请求里放开限制(自定义 / 很宽的允许规则 / 非本机绑定)要带 confirmRisk
2830
2830
  const manage = resolveManageOptions({ requestOptions, runConfig, globalConfig: config });
2831
2831
  if (manage.fromRequest && requestOptions.confirmRisk !== true && pendingRisks(defaultPolicy(), manage.network).length) {
2832
- throw new Error('该网络设置会放开限制(open 预设或绑定非本机地址),需要确认(confirmRisk)');
2832
+ throw new Error('该网络设置会放开限制(自定义模式、很宽的允许规则或绑定非本机地址),需要确认(confirmRisk)');
2833
2833
  }
2834
2834
 
2835
2835
  const containerName = resolvedBase.containerName;
@@ -2952,7 +2952,7 @@ function buildCreateRuntime(ctx, state, payload) {
2952
2952
  agentPromptCommand,
2953
2953
  defaultCommand: buildDefaultCommand(shellPrefix, shell, shellSuffix) || '/bin/bash',
2954
2954
  // 仅供服务端内部复用(如"创建相同配置容器"/"复制容器"),绝不能透传给任何对外响应字段,
2955
- // 因为 env/volumes/ports 可能包含密钥原始值(对外响应向来只暴露 envCount 等计数)。
2955
+ // 因为 env/volumes/ports 可能包含密钥原始值(对外响应不暴露它们)。
2956
2956
  runtimeSnapshot: {
2957
2957
  hostPath,
2958
2958
  containerPath,
@@ -2987,11 +2987,8 @@ function buildCreateRuntime(ctx, state, payload) {
2987
2987
  agentProgram: agentProgram || '',
2988
2988
  resumeSupported,
2989
2989
  yolo: yolo || '',
2990
- envCount: Math.floor(containerEnvs.length / 2),
2991
2990
  autostartEnabled: Boolean(manage.autostart.trim()),
2992
- networkPreset: manage.network.preset,
2993
- volumeCount: Math.floor(containerVolumes.length / 2),
2994
- portCount: Math.floor(containerPorts.length / 2)
2991
+ networkPreset: manage.network.preset
2995
2992
  }
2996
2993
  };
2997
2994
  }
@@ -3336,10 +3333,10 @@ function createWebDeniedNotifier(ctx, sessionRef, emit) {
3336
3333
  if (!id || !containerState.stateExists(homeDir, id)) return null;
3337
3334
  const deniedDir = egressPaths(homeDir).denied;
3338
3335
  const policyOf = () => normalizePolicy(containerState.readNetworkRaw(homeDir, id) || undefined);
3339
- if (policyOf().preset !== 'allowlist') return null;
3336
+ if (!usesProxy(policyOf())) return null;
3340
3337
  return createDeniedNotifier({
3341
3338
  read: () => readDenied(deniedDir, id),
3342
- isIgnored: host => isBrowserBackground(host) || domainAllowed(policyOf().egress.domains, host),
3339
+ isIgnored: host => isBrowserBackground(host) || domainRuleAllows(policyOf(), host),
3343
3340
  emit
3344
3341
  });
3345
3342
  } catch (e) {
@@ -3503,8 +3500,8 @@ async function ensureWebContainer(ctx, state, containerInput, messageSessionRef
3503
3500
  const userEnvLines = containerState.userEnvLines(runtime.containerEnvs);
3504
3501
  const playwright = await buildWebPlaywrightIntegration(ctx, state, runtime);
3505
3502
  const containerEnvs = containerState.stripEnvKeys(playwright.containerEnvs, new Set(userEnvLines.map(containerState.envLineKey)));
3506
- const networkPolicy = await withEnvEndpoints(normalizePolicy(runtime.network), [...userEnvLines, ...containerState.readEnvFilePaths(runtime.envFiles || []).flatMap(file => file.entries.map(entry => `${entry.key}=${entry.value}`))]);
3507
3503
  const networkManager = getWebNetworkManager(ctx, state);
3504
+ const networkPolicy = await withEnvEndpoints(await networkManager.resolveRefs(normalizePolicy(runtime.network)), [...userEnvLines, ...containerState.readEnvFilePaths(runtime.envFiles || []).flatMap(file => file.entries.map(entry => `${entry.key}=${entry.value}`))]);
3508
3505
  if (!hasNetworkArg([...(runtime.contModeArgs || []), ...(playwright.containerExtraArgs || [])])) {
3509
3506
  await networkManager.ensureBridgeNetwork();
3510
3507
  }
@@ -3514,7 +3511,7 @@ async function ensureWebContainer(ctx, state, containerInput, messageSessionRef
3514
3511
  envFiles: runtime.envFiles,
3515
3512
  autostart: runtime.autostart,
3516
3513
  network: networkPolicy,
3517
- netRequired: networkPolicy.preset !== 'open',
3514
+ netRequired: !isUnrestrictedPolicy(networkPolicy),
3518
3515
  meta: { name: runtime.containerName }
3519
3516
  });
3520
3517
  const args = buildContainerRunArgs({
@@ -4619,9 +4616,6 @@ function buildSessionFallbackApplied(ctx, state, name, history, summary) {
4619
4616
  agentProgram: effectiveAgentProgram,
4620
4617
  resumeSupported: effectiveResumeSupported,
4621
4618
  yolo: defaults.yolo || '',
4622
- envCount: Object.keys(defaults.env || {}).length,
4623
- volumeCount: Array.isArray(defaults.volumes) ? defaults.volumes.length : 0,
4624
- portCount: Array.isArray(defaults.ports) ? defaults.ports.length : 0,
4625
4619
  status: summary.status || 'history'
4626
4620
  };
4627
4621
  }
@@ -6333,7 +6327,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
6333
6327
  });
6334
6328
 
6335
6329
  };
6336
- // 域名白名单下被过滤代理拦下的新域名:对话进行中立即提示一次
6330
+ // 有域名规则时被过滤代理拦下的新域名:对话进行中立即提示一次
6337
6331
  const deniedNotifier = createWebDeniedNotifier(ctx, sessionRef, text => handleStreamEvent({ type: 'trace', stream: 'stdout', text }));
6338
6332
  try {
6339
6333
  if (deniedNotifier) deniedNotifier.start();
@@ -30,14 +30,17 @@
30
30
  "ports": ["8888:8888"],
31
31
 
32
32
  // 网络策略(新建容器时写入,Web「容器」页随时改,不需要重启容器)。不写 = 默认收紧:允许公网,
33
- // 禁止宿主机其他端口、局域网 / 私有网段、云元数据与其他容器。
34
- // preset:restricted(默认)/ allowlist(域名白名单,需要 serve 运行)/ open(不加规则)
33
+ // 禁止宿主机、局域网 / 私有网段、云元数据与其他容器。
34
+ // preset:restricted(默认)/ allowlist(只放行你写的允许规则)/ custom(没匹配的就允许)
35
+ // outbound / inbound:规则从上往下,第一条命中的生效。action:allow / deny;target(入站为 source):
36
+ // IP / 网段 / 域名(仅出站)/ @containers @container:<名称> @host @private @public @metadata @any;ports 空 = 全部;proto:all / tcp / udp
35
37
  "network": {
38
+ "version": 2,
36
39
  "preset": "restricted",
37
- // 允许容器访问的宿主机端口(宿主机上监听 0.0.0.0 的服务)
38
- "host": [],
39
- // 直连放行的 IP 规则,例如局域网里的模型网关:{ "cidr": "192.168.1.50", "ports": "8000", "proto": "tcp" }
40
- "egress": { "domains": [], "rules": [] },
40
+ // 例:允许容器访问宿主机的 Ollama:{ "action": "allow", "target": "@host", "ports": "11434", "proto": "tcp" }
41
+ "outbound": [],
42
+ // 例:允许容器 B 访问我的 7000 端口:{ "action": "allow", "source": "@container:B", "ports": "7000", "proto": "tcp" }
43
+ "inbound": [],
41
44
  // 端口暴露(需要 serve 运行):监听地址默认 127.0.0.1;绑 0.0.0.0 等于局域网 / 公网可见
42
45
  "expose": []
43
46
  },
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "8.5.0",
3
+ "version": "8.6.0",
4
4
  "imageVersion": "2.2.0-common",
5
5
  "playwrightCliVersion": "0.1.19",
6
6
  "patchrightCoreVersion": "1.63.0",
7
7
  "patchrightCoreIntegrity": "sha512-nuRJVTMVKoOFzY8vmwFqJZR3F0trUCKiPzDSgCOPq/yHHTefm9czILV23dXJsi5mvS7sCy7U1CqG7edA/21DtQ==",
8
- "description": "MANYOYO – open-source AI agent sandbox: run AI coding agents (Claude Code / Codex / Gemini) safely in Docker/Podman",
8
+ "description": "MANYOYO(慢悠悠)– open-source AI coding agent sandbox: run Claude Code, Codex, Gemini CLI and OpenCode in YOLO mode safely inside Docker/Podman",
9
9
  "keywords": [
10
10
  "manyoyo",
11
11
  "ai-agent",
12
+ "ai-sandbox",
13
+ "coding-agent",
12
14
  "agent-cli",
13
15
  "security-sandbox",
14
16
  "claude-code",
@@ -23,7 +25,7 @@
23
25
  "type": "git",
24
26
  "url": "git+https://github.com/xcanwin/manyoyo.git"
25
27
  },
26
- "license": "MIT",
28
+ "license": "Apache-2.0",
27
29
  "author": "xcanwin",
28
30
  "type": "commonjs",
29
31
  "main": "bin/manyoyo.js",
package/README.en.md DELETED
@@ -1,88 +0,0 @@
1
- <p align="center">
2
- <img src="./assets/manyoyo-logo-09-cyberpunk-terminal.svg" alt="MANYOYO logo" width="560" />
3
- </p>
4
-
5
- # <p align="center"><a href="https://github.com/xcanwin/manyoyo">MANYOYO</a></p>
6
- <p align="center"><b>MANYOYO – open-source sandbox for running AI coding agents (Claude Code / Codex / Gemini) safely in Docker/Podman</b></p>
7
- <p align="center">Run Claude Code / Codex / Gemini / OpenCode in YOLO mode inside a container: let the AI go wild without hurting your computer.</p>
8
- <p align="center">
9
- <a href="https://www.npmjs.com/package/@xcanwin/manyoyo"><img alt="npm" src="https://img.shields.io/npm/v/@xcanwin/manyoyo?style=flat-square" /></a>
10
- <a href="https://github.com/xcanwin/manyoyo/actions/workflows/npm-publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/xcanwin/manyoyo/npm-publish.yml?style=flat-square" /></a>
11
- <a href="https://github.com/xcanwin/manyoyo/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/badge/License-MIT-yellow.svg" /></a>
12
- </p>
13
-
14
- <p align="center">
15
- <a href="README.md">中文</a> |
16
- <a href="README.en.md"><b>English</b></a>
17
- </p>
18
- <p align="center">
19
- Docs: <a href="https://xcanwin.github.io/manyoyo/en/">https://xcanwin.github.io/manyoyo/en/</a>
20
- </p>
21
-
22
- ---
23
-
24
- ## Install
25
-
26
- ### macOS / Linux (recommended)
27
-
28
- ```bash
29
- curl -fsSL https://github.com/xcanwin/manyoyo/raw/main/scripts/install.sh | sh
30
- ```
31
-
32
- When it finishes, follow the on-screen prompts to finish setup. macOS needs nothing installed first. Linux: if the container runtime is missing, the installer explains why and, with your consent, installs it with sudo.
33
-
34
- ### Already have Node.js and Docker/Podman
35
-
36
- ```bash
37
- npm install -g @xcanwin/manyoyo
38
- manyoyo
39
- ```
40
-
41
- ## Daily use
42
-
43
- ```bash
44
- manyoyo # open the web UI
45
- manyoyo update # upgrade
46
- manyoyo uninstall # uninstall (config and data are kept by default)
47
- ```
48
-
49
- ## Command line (optional)
50
-
51
- ```bash
52
- manyoyo init all # import the agent configs already on this machine
53
- manyoyo run -y c # start Claude Code in YOLO mode inside the sandbox
54
- ```
55
-
56
- The default image `ghcr.io/xcanwin/manyoyo` is pulled automatically on first use; run `manyoyo build --iv 2.1.1-common` only if you need a custom image. More commands: [CLI reference](https://xcanwin.github.io/manyoyo/en/reference/cli-options).
57
-
58
- ## What it does
59
-
60
- - **Multiple agents**: `claude`, `codex`, `gemini`, `opencode` behind one entry point
61
- - **Container isolation**: Docker / Podman; the agent only sees the work directory you give it
62
- - **Web UI**: chat, browse files and open a terminal in the browser, phone friendly
63
- - **One config**: run profiles, environment variables, mounts and image arguments in `~/.manyoyo/manyoyo.json`
64
- - **One command** to install, upgrade and uninstall
65
-
66
- | | Running the agent CLI directly | MANYOYO |
67
- | --- | --- | --- |
68
- | Host exposure | High | Lower |
69
- | Run boundary | Scattered | Centralised in the container and config |
70
- | Reproducible environment | Weak | Strong (image + config) |
71
- | High-risk mode warnings | Left to the tool | Explicit YOLO / SOLO / sock warnings |
72
-
73
- ## Security notes
74
-
75
- MANYOYO reduces risk but is not "absolutely safe": the main isolation is a container, not a virtual machine; `YOLO / SOLO` can still run dangerous commands; `sock` mode exposes the host Docker socket; set a strong password before listening on a public address. See [Security notes](https://xcanwin.github.io/manyoyo/en/guide/security).
76
-
77
- ## Docs
78
-
79
- - 中文: <https://xcanwin.github.io/manyoyo/>
80
- - English: <https://xcanwin.github.io/manyoyo/en/> ([Install](https://xcanwin.github.io/manyoyo/en/guide/quick-start), [CLI reference](https://xcanwin.github.io/manyoyo/en/reference/cli-options), [Configuration](https://xcanwin.github.io/manyoyo/en/configuration/), [FAQ](https://xcanwin.github.io/manyoyo/en/troubleshooting/))
81
-
82
- ## License
83
-
84
- MIT
85
-
86
- ## Contributing
87
-
88
- [Issues](https://github.com/xcanwin/manyoyo/issues) and pull requests are welcome.