moonbridge 0.6.0__py3-none-any.whl → 0.8.0__py3-none-any.whl
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.
- moonbridge/__init__.py +1 -1
- moonbridge/adapters/codex.py +1 -0
- moonbridge/sandbox.py +252 -0
- moonbridge/server.py +94 -2
- {moonbridge-0.6.0.dist-info → moonbridge-0.8.0.dist-info}/METADATA +29 -1
- moonbridge-0.8.0.dist-info/RECORD +15 -0
- moonbridge-0.6.0.dist-info/RECORD +0 -14
- {moonbridge-0.6.0.dist-info → moonbridge-0.8.0.dist-info}/WHEEL +0 -0
- {moonbridge-0.6.0.dist-info → moonbridge-0.8.0.dist-info}/entry_points.txt +0 -0
- {moonbridge-0.6.0.dist-info → moonbridge-0.8.0.dist-info}/licenses/LICENSE +0 -0
moonbridge/__init__.py
CHANGED
moonbridge/adapters/codex.py
CHANGED
moonbridge/sandbox.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""Copy-on-run sandbox for agent execution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import difflib
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import tempfile
|
|
9
|
+
import time
|
|
10
|
+
from collections.abc import Callable, Iterator
|
|
11
|
+
from dataclasses import dataclass, replace
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from moonbridge.adapters.base import AgentResult
|
|
15
|
+
|
|
16
|
+
SANDBOX_IGNORE_DIRS = {
|
|
17
|
+
".git",
|
|
18
|
+
".venv",
|
|
19
|
+
".tox",
|
|
20
|
+
"__pycache__",
|
|
21
|
+
".mypy_cache",
|
|
22
|
+
".pytest_cache",
|
|
23
|
+
".ruff_cache",
|
|
24
|
+
"node_modules",
|
|
25
|
+
"dist",
|
|
26
|
+
"build",
|
|
27
|
+
}
|
|
28
|
+
SANDBOX_IGNORE_FILES = {".DS_Store"}
|
|
29
|
+
MAX_COPY_BYTES = 500 * 1024 * 1024
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class SandboxResult:
|
|
34
|
+
diff: str
|
|
35
|
+
summary: dict[str, int]
|
|
36
|
+
truncated: bool
|
|
37
|
+
sandbox_path: str | None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _should_ignore(name: str) -> bool:
|
|
41
|
+
if name in SANDBOX_IGNORE_DIRS:
|
|
42
|
+
return True
|
|
43
|
+
if name in SANDBOX_IGNORE_FILES:
|
|
44
|
+
return True
|
|
45
|
+
return name.endswith((".pyc", ".pyo"))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _ignore_names(_dirpath: str, names: list[str]) -> set[str]:
|
|
49
|
+
return {name for name in names if _should_ignore(name)}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _filtered_walk(root: str) -> Iterator[tuple[str, list[str], list[str]]]:
|
|
53
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
54
|
+
dirnames[:] = [d for d in dirnames if not _should_ignore(d)]
|
|
55
|
+
filenames = [f for f in filenames if not _should_ignore(f)]
|
|
56
|
+
yield dirpath, dirnames, filenames
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _collect_files(root: str) -> set[str]:
|
|
60
|
+
files: set[str] = set()
|
|
61
|
+
for dirpath, _dirnames, filenames in _filtered_walk(root):
|
|
62
|
+
rel_dir = os.path.relpath(dirpath, root)
|
|
63
|
+
for filename in filenames:
|
|
64
|
+
rel_path = filename if rel_dir == "." else os.path.join(rel_dir, filename)
|
|
65
|
+
files.add(rel_path)
|
|
66
|
+
return files
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _read_text(path: str) -> str | None:
|
|
70
|
+
data = Path(path).read_bytes()
|
|
71
|
+
try:
|
|
72
|
+
return data.decode("utf-8")
|
|
73
|
+
except UnicodeDecodeError:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _diff_trees(
|
|
78
|
+
original: str,
|
|
79
|
+
sandbox: str,
|
|
80
|
+
max_bytes: int,
|
|
81
|
+
) -> tuple[str, dict[str, int], bool]:
|
|
82
|
+
original_files = _collect_files(original)
|
|
83
|
+
sandbox_files = _collect_files(sandbox)
|
|
84
|
+
all_files = sorted(original_files | sandbox_files)
|
|
85
|
+
diff_chunks: list[str] = []
|
|
86
|
+
size = 0
|
|
87
|
+
truncated = False
|
|
88
|
+
summary = {"added": 0, "modified": 0, "deleted": 0, "binary": 0}
|
|
89
|
+
|
|
90
|
+
def append_chunk(chunk: str) -> None:
|
|
91
|
+
nonlocal size, truncated
|
|
92
|
+
if truncated or not chunk:
|
|
93
|
+
return
|
|
94
|
+
remaining = max_bytes - size
|
|
95
|
+
if remaining <= 0:
|
|
96
|
+
truncated = True
|
|
97
|
+
return
|
|
98
|
+
if len(chunk) > remaining:
|
|
99
|
+
diff_chunks.append(chunk[:remaining])
|
|
100
|
+
truncated = True
|
|
101
|
+
size = max_bytes
|
|
102
|
+
return
|
|
103
|
+
diff_chunks.append(chunk)
|
|
104
|
+
size += len(chunk)
|
|
105
|
+
|
|
106
|
+
for rel_path in all_files:
|
|
107
|
+
original_path = os.path.join(original, rel_path)
|
|
108
|
+
sandbox_path = os.path.join(sandbox, rel_path)
|
|
109
|
+
original_exists = os.path.exists(original_path)
|
|
110
|
+
sandbox_exists = os.path.exists(sandbox_path)
|
|
111
|
+
|
|
112
|
+
if not original_exists and sandbox_exists:
|
|
113
|
+
summary["added"] += 1
|
|
114
|
+
sandbox_text = _read_text(sandbox_path)
|
|
115
|
+
if sandbox_text is None:
|
|
116
|
+
summary["binary"] += 1
|
|
117
|
+
append_chunk(f"Binary files /dev/null and b/{rel_path} differ\n")
|
|
118
|
+
continue
|
|
119
|
+
diff = difflib.unified_diff(
|
|
120
|
+
[],
|
|
121
|
+
sandbox_text.splitlines(keepends=True),
|
|
122
|
+
fromfile="/dev/null",
|
|
123
|
+
tofile=f"b/{rel_path}",
|
|
124
|
+
)
|
|
125
|
+
append_chunk("".join(diff))
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
if original_exists and not sandbox_exists:
|
|
129
|
+
summary["deleted"] += 1
|
|
130
|
+
original_text = _read_text(original_path)
|
|
131
|
+
if original_text is None:
|
|
132
|
+
summary["binary"] += 1
|
|
133
|
+
append_chunk(f"Binary files a/{rel_path} and /dev/null differ\n")
|
|
134
|
+
continue
|
|
135
|
+
diff = difflib.unified_diff(
|
|
136
|
+
original_text.splitlines(keepends=True),
|
|
137
|
+
[],
|
|
138
|
+
fromfile=f"a/{rel_path}",
|
|
139
|
+
tofile="/dev/null",
|
|
140
|
+
)
|
|
141
|
+
append_chunk("".join(diff))
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
if not original_exists or not sandbox_exists:
|
|
145
|
+
continue
|
|
146
|
+
|
|
147
|
+
original_bytes = Path(original_path).read_bytes()
|
|
148
|
+
sandbox_bytes = Path(sandbox_path).read_bytes()
|
|
149
|
+
if original_bytes == sandbox_bytes:
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
original_text = None
|
|
153
|
+
sandbox_text = None
|
|
154
|
+
try:
|
|
155
|
+
original_text = original_bytes.decode("utf-8")
|
|
156
|
+
sandbox_text = sandbox_bytes.decode("utf-8")
|
|
157
|
+
except UnicodeDecodeError:
|
|
158
|
+
summary["binary"] += 1
|
|
159
|
+
append_chunk(f"Binary files a/{rel_path} and b/{rel_path} differ\n")
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
summary["modified"] += 1
|
|
163
|
+
diff = difflib.unified_diff(
|
|
164
|
+
original_text.splitlines(keepends=True),
|
|
165
|
+
sandbox_text.splitlines(keepends=True),
|
|
166
|
+
fromfile=f"a/{rel_path}",
|
|
167
|
+
tofile=f"b/{rel_path}",
|
|
168
|
+
)
|
|
169
|
+
append_chunk("".join(diff))
|
|
170
|
+
|
|
171
|
+
if truncated:
|
|
172
|
+
diff_chunks.append("\n... diff truncated ...\n")
|
|
173
|
+
return ("".join(diff_chunks), summary, truncated)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _estimate_copy_size(root: str, max_bytes: int) -> int:
|
|
177
|
+
total = 0
|
|
178
|
+
for dirpath, _dirnames, filenames in _filtered_walk(root):
|
|
179
|
+
for filename in filenames:
|
|
180
|
+
path = os.path.join(dirpath, filename)
|
|
181
|
+
total += os.path.getsize(path)
|
|
182
|
+
if total > max_bytes:
|
|
183
|
+
return total
|
|
184
|
+
return total
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _agent_index(fn: Callable[[str], AgentResult]) -> int:
|
|
188
|
+
value = getattr(fn, "agent_index", 0)
|
|
189
|
+
return value if isinstance(value, int) else 0
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def run_sandboxed(
|
|
193
|
+
fn: Callable[[str], AgentResult],
|
|
194
|
+
cwd: str,
|
|
195
|
+
*,
|
|
196
|
+
max_diff_bytes: int = 500_000,
|
|
197
|
+
max_copy_bytes: int = MAX_COPY_BYTES,
|
|
198
|
+
keep: bool = False,
|
|
199
|
+
) -> tuple[AgentResult, SandboxResult | None]:
|
|
200
|
+
"""Run fn in a copy of cwd. Returns (agent_result, sandbox_result).
|
|
201
|
+
|
|
202
|
+
On sandbox infrastructure error, returns (error_result, None).
|
|
203
|
+
"""
|
|
204
|
+
start = time.monotonic()
|
|
205
|
+
sandbox_root: str | None = None
|
|
206
|
+
agent_index = _agent_index(fn)
|
|
207
|
+
|
|
208
|
+
def error_result(reason: str) -> AgentResult:
|
|
209
|
+
duration_ms = int((time.monotonic() - start) * 1000)
|
|
210
|
+
return AgentResult(
|
|
211
|
+
status="error",
|
|
212
|
+
output="",
|
|
213
|
+
stderr=f"sandbox error: {reason}",
|
|
214
|
+
returncode=-1,
|
|
215
|
+
duration_ms=duration_ms,
|
|
216
|
+
agent_index=agent_index,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
total_bytes = _estimate_copy_size(cwd, max_copy_bytes)
|
|
221
|
+
if total_bytes > max_copy_bytes:
|
|
222
|
+
return error_result(
|
|
223
|
+
f"copy size {total_bytes} exceeds max {max_copy_bytes}"
|
|
224
|
+
), None
|
|
225
|
+
|
|
226
|
+
sandbox_root = tempfile.mkdtemp(prefix="moonbridge-sandbox-")
|
|
227
|
+
sandbox_cwd = os.path.join(sandbox_root, "workspace")
|
|
228
|
+
shutil.copytree(cwd, sandbox_cwd, symlinks=False, ignore=_ignore_names)
|
|
229
|
+
|
|
230
|
+
result = fn(sandbox_cwd)
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
diff, summary, truncated = _diff_trees(cwd, sandbox_cwd, max_diff_bytes)
|
|
234
|
+
sandbox_result = SandboxResult(
|
|
235
|
+
diff=diff,
|
|
236
|
+
summary=summary,
|
|
237
|
+
truncated=truncated,
|
|
238
|
+
sandbox_path=sandbox_root if keep else None,
|
|
239
|
+
)
|
|
240
|
+
return result, sandbox_result
|
|
241
|
+
except Exception as exc:
|
|
242
|
+
raw = dict(result.raw or {})
|
|
243
|
+
sandbox_payload: dict[str, object] = {"enabled": True, "error": str(exc)}
|
|
244
|
+
if keep:
|
|
245
|
+
sandbox_payload["path"] = sandbox_root
|
|
246
|
+
raw["sandbox"] = sandbox_payload
|
|
247
|
+
return replace(result, raw=raw), None
|
|
248
|
+
except Exception as exc:
|
|
249
|
+
return error_result(str(exc)), None
|
|
250
|
+
finally:
|
|
251
|
+
if not keep and sandbox_root:
|
|
252
|
+
shutil.rmtree(sandbox_root, ignore_errors=True)
|
moonbridge/server.py
CHANGED
|
@@ -11,6 +11,7 @@ import signal
|
|
|
11
11
|
import sys
|
|
12
12
|
import time
|
|
13
13
|
import weakref
|
|
14
|
+
from dataclasses import replace
|
|
14
15
|
from subprocess import PIPE, Popen, TimeoutExpired
|
|
15
16
|
from typing import Any
|
|
16
17
|
|
|
@@ -36,6 +37,17 @@ ALLOWED_DIRS = [
|
|
|
36
37
|
if path
|
|
37
38
|
]
|
|
38
39
|
MAX_PROMPT_LENGTH = 100_000
|
|
40
|
+
_SANDBOX_ENV = os.environ.get("MOONBRIDGE_SANDBOX", "").strip().lower()
|
|
41
|
+
SANDBOX_MODE = _SANDBOX_ENV in {"1", "true", "yes", "copy"}
|
|
42
|
+
SANDBOX_KEEP = os.environ.get("MOONBRIDGE_SANDBOX_KEEP", "").strip().lower() in {
|
|
43
|
+
"1",
|
|
44
|
+
"true",
|
|
45
|
+
"yes",
|
|
46
|
+
}
|
|
47
|
+
SANDBOX_MAX_DIFF_BYTES = int(os.environ.get("MOONBRIDGE_SANDBOX_MAX_DIFF", "500000"))
|
|
48
|
+
SANDBOX_MAX_COPY_BYTES = int(
|
|
49
|
+
os.environ.get("MOONBRIDGE_SANDBOX_MAX_COPY", str(500 * 1024 * 1024))
|
|
50
|
+
)
|
|
39
51
|
|
|
40
52
|
_active_processes: set[weakref.ref[Popen[str]]] = set()
|
|
41
53
|
|
|
@@ -194,6 +206,53 @@ def _auth_error(stderr: str | None, adapter: CLIAdapter) -> bool:
|
|
|
194
206
|
return any(pattern in lowered for pattern in adapter.config.auth_patterns)
|
|
195
207
|
|
|
196
208
|
|
|
209
|
+
def _run_cli_sandboxed(
|
|
210
|
+
adapter: CLIAdapter,
|
|
211
|
+
prompt: str,
|
|
212
|
+
thinking: bool,
|
|
213
|
+
cwd: str,
|
|
214
|
+
timeout_seconds: int,
|
|
215
|
+
agent_index: int,
|
|
216
|
+
model: str | None = None,
|
|
217
|
+
reasoning_effort: str | None = None,
|
|
218
|
+
) -> AgentResult:
|
|
219
|
+
from moonbridge.sandbox import run_sandboxed
|
|
220
|
+
|
|
221
|
+
def run_agent(sandbox_cwd: str) -> AgentResult:
|
|
222
|
+
return _run_cli_sync(
|
|
223
|
+
adapter,
|
|
224
|
+
prompt,
|
|
225
|
+
thinking,
|
|
226
|
+
sandbox_cwd,
|
|
227
|
+
timeout_seconds,
|
|
228
|
+
agent_index,
|
|
229
|
+
model,
|
|
230
|
+
reasoning_effort,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
run_agent.agent_index = agent_index # type: ignore[attr-defined]
|
|
234
|
+
|
|
235
|
+
result, sandbox_result = run_sandboxed(
|
|
236
|
+
run_agent,
|
|
237
|
+
cwd,
|
|
238
|
+
max_diff_bytes=SANDBOX_MAX_DIFF_BYTES,
|
|
239
|
+
max_copy_bytes=SANDBOX_MAX_COPY_BYTES,
|
|
240
|
+
keep=SANDBOX_KEEP,
|
|
241
|
+
)
|
|
242
|
+
if sandbox_result:
|
|
243
|
+
raw = dict(result.raw or {})
|
|
244
|
+
raw["sandbox"] = {
|
|
245
|
+
"enabled": True,
|
|
246
|
+
"summary": sandbox_result.summary,
|
|
247
|
+
"diff": sandbox_result.diff,
|
|
248
|
+
"truncated": sandbox_result.truncated,
|
|
249
|
+
}
|
|
250
|
+
if sandbox_result.sandbox_path:
|
|
251
|
+
raw["sandbox"]["path"] = sandbox_result.sandbox_path
|
|
252
|
+
return replace(result, raw=raw)
|
|
253
|
+
return result
|
|
254
|
+
|
|
255
|
+
|
|
197
256
|
def _run_cli_sync(
|
|
198
257
|
adapter: CLIAdapter,
|
|
199
258
|
prompt: str,
|
|
@@ -304,6 +363,39 @@ def _run_cli_sync(
|
|
|
304
363
|
_untrack_process(proc)
|
|
305
364
|
|
|
306
365
|
|
|
366
|
+
def _run_cli(
|
|
367
|
+
adapter: CLIAdapter,
|
|
368
|
+
prompt: str,
|
|
369
|
+
thinking: bool,
|
|
370
|
+
cwd: str,
|
|
371
|
+
timeout_seconds: int,
|
|
372
|
+
agent_index: int,
|
|
373
|
+
model: str | None = None,
|
|
374
|
+
reasoning_effort: str | None = None,
|
|
375
|
+
) -> AgentResult:
|
|
376
|
+
if SANDBOX_MODE:
|
|
377
|
+
return _run_cli_sandboxed(
|
|
378
|
+
adapter,
|
|
379
|
+
prompt,
|
|
380
|
+
thinking,
|
|
381
|
+
cwd,
|
|
382
|
+
timeout_seconds,
|
|
383
|
+
agent_index,
|
|
384
|
+
model,
|
|
385
|
+
reasoning_effort,
|
|
386
|
+
)
|
|
387
|
+
return _run_cli_sync(
|
|
388
|
+
adapter,
|
|
389
|
+
prompt,
|
|
390
|
+
thinking,
|
|
391
|
+
cwd,
|
|
392
|
+
timeout_seconds,
|
|
393
|
+
agent_index,
|
|
394
|
+
model,
|
|
395
|
+
reasoning_effort,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
|
|
307
399
|
def _json_text(payload: Any) -> list[TextContent]:
|
|
308
400
|
return [TextContent(type="text", text=json.dumps(payload, ensure_ascii=True))]
|
|
309
401
|
|
|
@@ -378,7 +470,7 @@ async def handle_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]
|
|
|
378
470
|
try:
|
|
379
471
|
result = await loop.run_in_executor(
|
|
380
472
|
None,
|
|
381
|
-
|
|
473
|
+
_run_cli,
|
|
382
474
|
adapter,
|
|
383
475
|
prompt,
|
|
384
476
|
thinking,
|
|
@@ -416,7 +508,7 @@ async def handle_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]
|
|
|
416
508
|
tasks.append(
|
|
417
509
|
loop.run_in_executor(
|
|
418
510
|
None,
|
|
419
|
-
|
|
511
|
+
_run_cli,
|
|
420
512
|
adapter,
|
|
421
513
|
prompt,
|
|
422
514
|
thinking,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: moonbridge
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: MCP server for spawning AI coding agents (Kimi, Codex, and more)
|
|
5
5
|
Project-URL: Homepage, https://github.com/misty-step/moonbridge
|
|
6
6
|
Project-URL: Repository, https://github.com/misty-step/moonbridge
|
|
@@ -151,6 +151,7 @@ All tools return JSON with these fields:
|
|
|
151
151
|
| `duration_ms` | int | Execution time in milliseconds |
|
|
152
152
|
| `agent_index` | int | Agent index (0 for single, 0-N for parallel) |
|
|
153
153
|
| `message` | string? | Human-readable error context (when applicable) |
|
|
154
|
+
| `raw` | object? | Optional structured metadata (e.g., sandbox diff) |
|
|
154
155
|
|
|
155
156
|
## Configuration
|
|
156
157
|
|
|
@@ -163,6 +164,10 @@ All tools return JSON with these fields:
|
|
|
163
164
|
| `MOONBRIDGE_MAX_AGENTS` | Maximum parallel agents |
|
|
164
165
|
| `MOONBRIDGE_ALLOWED_DIRS` | Colon-separated allowlist of working directories |
|
|
165
166
|
| `MOONBRIDGE_STRICT` | Set to `1` to require `ALLOWED_DIRS` (exits if unset) |
|
|
167
|
+
| `MOONBRIDGE_SANDBOX` | Set to `1` to run agents in a temp copy of cwd |
|
|
168
|
+
| `MOONBRIDGE_SANDBOX_KEEP` | Set to `1` to keep sandbox dir for inspection |
|
|
169
|
+
| `MOONBRIDGE_SANDBOX_MAX_DIFF` | Max diff size in bytes (default 500000) |
|
|
170
|
+
| `MOONBRIDGE_SANDBOX_MAX_COPY` | Max sandbox copy size in bytes (default 500MB) |
|
|
166
171
|
| `MOONBRIDGE_LOG_LEVEL` | Set to `DEBUG` for verbose logging |
|
|
167
172
|
|
|
168
173
|
## Troubleshooting
|
|
@@ -229,6 +234,29 @@ By default, Moonbridge warns at startup if no directory restrictions are configu
|
|
|
229
234
|
export MOONBRIDGE_ALLOWED_DIRS="/path/to/project:/another/path"
|
|
230
235
|
```
|
|
231
236
|
|
|
237
|
+
## Sandbox Mode (Copy-on-Run)
|
|
238
|
+
|
|
239
|
+
Enable sandbox mode to run agents in a temporary copy of the working directory:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
export MOONBRIDGE_SANDBOX=1
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
When enabled:
|
|
246
|
+
- Agents run in a temp copy of `cwd`.
|
|
247
|
+
- Host files stay unchanged by default.
|
|
248
|
+
- A unified diff + summary is included in `raw.sandbox`.
|
|
249
|
+
|
|
250
|
+
Optional:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
export MOONBRIDGE_SANDBOX_KEEP=1 # keep temp dir
|
|
254
|
+
export MOONBRIDGE_SANDBOX_MAX_DIFF=200000
|
|
255
|
+
export MOONBRIDGE_SANDBOX_MAX_COPY=300000000
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Limitations: this is not OS-level isolation. Agents can still read/write arbitrary host paths if they choose to. Use containers/VMs for strong isolation.
|
|
259
|
+
|
|
232
260
|
To enforce restrictions (exit instead of warn):
|
|
233
261
|
|
|
234
262
|
```bash
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
moonbridge/__init__.py,sha256=8Yz-_YwybWMK-Sze7iG20GXQGLCNhL7dBjHmblAHULA,198
|
|
2
|
+
moonbridge/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
3
|
+
moonbridge/sandbox.py,sha256=4-eIu-rURtaxKKg-d3iWwNq_x6MB_uiJBBG2uMwoKcM,7907
|
|
4
|
+
moonbridge/server.py,sha256=FqnoAd-WAWOX-elNgyOQ-vAiFqsGOdsyQCIsnu1e1t0,19469
|
|
5
|
+
moonbridge/tools.py,sha256=uw338Dilrto2t5dL9XbK4O31-JdB7Vh9RqCXHg20gHI,10126
|
|
6
|
+
moonbridge/version_check.py,sha256=VQueK0O_b-2Xc-XjupJsoW3Zs1Kce5q_BgqBhANGXN8,4579
|
|
7
|
+
moonbridge/adapters/__init__.py,sha256=w3pLvjtC2XnUhf9UzNmniQB3oq4rG8gorSH0tWR-BEE,988
|
|
8
|
+
moonbridge/adapters/base.py,sha256=REoEsAcqEvyVQpTgz6ytd9ioxag--nnvX90YBXMQG8Y,1716
|
|
9
|
+
moonbridge/adapters/codex.py,sha256=G0q_A6vP5Usqix3sE8ssrG_qzrCWMeFcO9oWcNKTO3g,2964
|
|
10
|
+
moonbridge/adapters/kimi.py,sha256=ejCxG2OGr0Qr4n0psL6p96_mMJ3lLKMbGcNYWkuC0uA,2189
|
|
11
|
+
moonbridge-0.8.0.dist-info/METADATA,sha256=RiRWB1Hr5IFT3wEpRHVcqIclyBS2CORWE-j90Z4C19U,8305
|
|
12
|
+
moonbridge-0.8.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
13
|
+
moonbridge-0.8.0.dist-info/entry_points.txt,sha256=kgL38HQy3adncDQl_o5sdtPRog56zKdHk6pKKzyR6Ww,54
|
|
14
|
+
moonbridge-0.8.0.dist-info/licenses/LICENSE,sha256=7WMSJoybL2cUot_wb9GUrw5mzfFmtrDzqlMS9ZE709g,1065
|
|
15
|
+
moonbridge-0.8.0.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
moonbridge/__init__.py,sha256=K5-NRJiYdFbIITQmjIg3_Fkef0UEWhe2hqO3rJ0MZII,198
|
|
2
|
-
moonbridge/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
3
|
-
moonbridge/server.py,sha256=o-u0J-HsVoa7sxaweWPHo_iHDXJfao16eDFe_9bm6RA,17071
|
|
4
|
-
moonbridge/tools.py,sha256=uw338Dilrto2t5dL9XbK4O31-JdB7Vh9RqCXHg20gHI,10126
|
|
5
|
-
moonbridge/version_check.py,sha256=VQueK0O_b-2Xc-XjupJsoW3Zs1Kce5q_BgqBhANGXN8,4579
|
|
6
|
-
moonbridge/adapters/__init__.py,sha256=w3pLvjtC2XnUhf9UzNmniQB3oq4rG8gorSH0tWR-BEE,988
|
|
7
|
-
moonbridge/adapters/base.py,sha256=REoEsAcqEvyVQpTgz6ytd9ioxag--nnvX90YBXMQG8Y,1716
|
|
8
|
-
moonbridge/adapters/codex.py,sha256=GtU4CrJ4zt0WDcKKaOeN7gH4JFIBAo3L7KAZ99zRjiY,2935
|
|
9
|
-
moonbridge/adapters/kimi.py,sha256=ejCxG2OGr0Qr4n0psL6p96_mMJ3lLKMbGcNYWkuC0uA,2189
|
|
10
|
-
moonbridge-0.6.0.dist-info/METADATA,sha256=nJndtrv2GuSSPeNrV6ryJa6-82viUtankQXduB6Nkn4,7298
|
|
11
|
-
moonbridge-0.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
12
|
-
moonbridge-0.6.0.dist-info/entry_points.txt,sha256=kgL38HQy3adncDQl_o5sdtPRog56zKdHk6pKKzyR6Ww,54
|
|
13
|
-
moonbridge-0.6.0.dist-info/licenses/LICENSE,sha256=7WMSJoybL2cUot_wb9GUrw5mzfFmtrDzqlMS9ZE709g,1065
|
|
14
|
-
moonbridge-0.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|