@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/README.md +66 -25
- package/bin/manyoyo.js +50 -13
- package/lib/app-update.js +93 -35
- package/lib/container-network.js +100 -48
- package/lib/container-run.js +1 -1
- package/lib/egress-proxy.js +146 -64
- package/lib/egress-sidecar-main.js +23 -5
- package/lib/egress-sidecar.js +22 -12
- package/lib/net-proxy.js +183 -0
- package/lib/network-endpoints.js +18 -19
- package/lib/network-policy.js +296 -77
- package/lib/plugin/playwright.js +4 -2
- package/lib/web/AGENTS.md +1 -1
- package/lib/web/container-manage.js +68 -30
- package/lib/web/index.html +80 -81
- package/lib/web/server.js +12 -18
- package/manyoyo.example.json +9 -6
- package/package.json +5 -3
- package/README.en.md +0 -88
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 {
|
|
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> >
|
|
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('
|
|
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
|
|
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()
|
|
3336
|
+
if (!usesProxy(policyOf())) return null;
|
|
3340
3337
|
return createDeniedNotifier({
|
|
3341
3338
|
read: () => readDenied(deniedDir, id),
|
|
3342
|
-
isIgnored: host => isBrowserBackground(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
|
|
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();
|
package/manyoyo.example.json
CHANGED
|
@@ -30,14 +30,17 @@
|
|
|
30
30
|
"ports": ["8888:8888"],
|
|
31
31
|
|
|
32
32
|
// 网络策略(新建容器时写入,Web「容器」页随时改,不需要重启容器)。不写 = 默认收紧:允许公网,
|
|
33
|
-
//
|
|
34
|
-
// preset:restricted(默认)/ allowlist
|
|
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
|
-
//
|
|
38
|
-
"
|
|
39
|
-
//
|
|
40
|
-
"
|
|
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.
|
|
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
|
|
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": "
|
|
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.
|