miniswen 1.3.3 → 1.3.5
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.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/lib/miniswen/cli.rb +10 -0
- data/lib/miniswen/environment.rb +2 -0
- data/lib/miniswen/jail.rb +58 -0
- data/lib/miniswen/local.rb +5 -1
- data/lib/miniswen/ruby_llm.rb +2 -2
- data/lib/miniswen/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bb0210425289d60629f23be4579598a50bd5e901da6ba2c8198ba3d9cb5760a0
|
|
4
|
+
data.tar.gz: e9377f196e3342d3cc3b67cd1ff5be7af22937be9a49647872a90a9a5ff888d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8fbdd1b68f84eab112e32de6a8eed4d85872424cae07ae3166aa334bb924e2c00b73654d59386b22e58b4ad4125fb0405dbff3b82484495c9927e650173f1af4
|
|
7
|
+
data.tar.gz: 5f6e01261b89cc6fc048f3ea960a89eef3ce5e43b9d2d16e2758b2c8899731e3eb849a1b7bd996224b30f21c216ad7abf1baf540b3ae9036c6278531669a1d6b
|
data/README.md
CHANGED
|
@@ -15,6 +15,7 @@ lemans is a harness for benchmarking coding agents, the Ruby way:
|
|
|
15
15
|
- Ruby 3.4+ is required to run `lemans`
|
|
16
16
|
- Daytona account (API token) or Docker (for local sandboxes)
|
|
17
17
|
- Some LLM provider/proxy credentials (e.g., OpenRouter)
|
|
18
|
+
- iproute2 in the sandbox image, so a jailed agent keeps loopback while cut off the internet
|
|
18
19
|
|
|
19
20
|
## Getting started
|
|
20
21
|
|
data/lib/miniswen/cli.rb
CHANGED
|
@@ -22,6 +22,7 @@ module Miniswen
|
|
|
22
22
|
@atif_path = nil
|
|
23
23
|
@refresh_registry = false
|
|
24
24
|
@skip_registry_refresh = false
|
|
25
|
+
@jail = false
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def run
|
|
@@ -42,6 +43,9 @@ module Miniswen
|
|
|
42
43
|
if @docker_id
|
|
43
44
|
require "miniswen/environment/docker"
|
|
44
45
|
Environment::Docker.new(@docker_id)
|
|
46
|
+
elsif @jail
|
|
47
|
+
require "miniswen/jail"
|
|
48
|
+
Jail.new.start
|
|
45
49
|
else
|
|
46
50
|
Local.new
|
|
47
51
|
end
|
|
@@ -53,6 +57,8 @@ module Miniswen
|
|
|
53
57
|
rescue StandardError => e
|
|
54
58
|
write_results(agent.partial_result(error_message(e)))
|
|
55
59
|
raise
|
|
60
|
+
ensure
|
|
61
|
+
environment.stop
|
|
56
62
|
end
|
|
57
63
|
|
|
58
64
|
write_results(result)
|
|
@@ -151,6 +157,10 @@ module Miniswen
|
|
|
151
157
|
@docker_id = v
|
|
152
158
|
end
|
|
153
159
|
|
|
160
|
+
opts.on("--jail", "Run every command in its own namespaces: none of the harness's environment, no network, read-only system, none of its files") do
|
|
161
|
+
@jail = true
|
|
162
|
+
end
|
|
163
|
+
|
|
154
164
|
opts.on("--refresh-registry", "Refresh the model registry, persist it, and exit") do
|
|
155
165
|
@refresh_registry = true
|
|
156
166
|
end
|
data/lib/miniswen/environment.rb
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shellwords"
|
|
4
|
+
|
|
5
|
+
require "miniswen/local"
|
|
6
|
+
|
|
7
|
+
module Miniswen
|
|
8
|
+
# Runs every command in its own namespaces: none of the harness's environment,
|
|
9
|
+
# no network, read-only system, none of its files.
|
|
10
|
+
class Jail < Local
|
|
11
|
+
def initialize(workdir: Dir.pwd)
|
|
12
|
+
@workdir = workdir
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def start
|
|
16
|
+
_, @stdout, @stderr, @holder = Open3.popen3(
|
|
17
|
+
ENV.to_h.slice(*container_variables),
|
|
18
|
+
"unshare", "--net", "--mount", "--pid", "--fork", "--kill-child", "--mount-proc", "sh", "-c", setup,
|
|
19
|
+
pgroup: true, unsetenv_others: true
|
|
20
|
+
)
|
|
21
|
+
return self if @stdout.gets == "ready\n"
|
|
22
|
+
|
|
23
|
+
raise InfrastructureError, "jail did not start: #{@stderr.read}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def stop
|
|
27
|
+
Process.kill(:KILL, -@holder.pid) if @holder.alive?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def setup = <<~SH
|
|
33
|
+
set -eu
|
|
34
|
+
# Bring loopback up
|
|
35
|
+
ip link set lo up
|
|
36
|
+
# Make the workdir its own mount
|
|
37
|
+
mount --bind #{Shellwords.escape(@workdir)} #{Shellwords.escape(@workdir)}
|
|
38
|
+
# Make private temp dirs their own mounts
|
|
39
|
+
for dir in /tmp /run /root; do mkdir -p "/var/lib/miniswen$dir" && mount --bind "/var/lib/miniswen$dir" "$dir"; done
|
|
40
|
+
# Make everything without its own mount read-only
|
|
41
|
+
mount -o remount,bind,ro /
|
|
42
|
+
echo ready
|
|
43
|
+
exec sleep infinity
|
|
44
|
+
SH
|
|
45
|
+
|
|
46
|
+
def spawn_arguments(command, env)
|
|
47
|
+
[ ENV.to_h.merge(env.to_h).slice(*container_variables),
|
|
48
|
+
"nsenter", "--target", @holder.pid.to_s, "--net", "--mount", "--pid=/proc/#{@holder.pid}/ns/pid_for_children", "--wd=#{@workdir}",
|
|
49
|
+
"--", "setpriv", "--bounding-set=-all", "--inh-caps=-all", "--no-new-privs", "--", "sh", "-c", command ]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def spawn_options = super.merge(unsetenv_others: true)
|
|
53
|
+
|
|
54
|
+
def container_variables
|
|
55
|
+
@container_variables ||= File.read("/proc/1/environ").split("\0").map { it.split("=").first } | Agent::EXEC_ENV.keys
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/miniswen/local.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Miniswen
|
|
|
12
12
|
# Always through a shell: Ruby execs a metacharacter-free string directly,
|
|
13
13
|
# and a missing binary would then raise ENOENT here instead of exiting 127.
|
|
14
14
|
def exec(command, timeout: nil, env: nil)
|
|
15
|
-
Open3.popen2e(
|
|
15
|
+
Open3.popen2e(*spawn_arguments(command, env), **spawn_options) do |stdin, io, wait_thr|
|
|
16
16
|
stdin.close
|
|
17
17
|
reader = Thread.new { io.read }
|
|
18
18
|
|
|
@@ -29,6 +29,10 @@ module Miniswen
|
|
|
29
29
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
|
+
def spawn_arguments(command, env) = [ env || {}, "sh", "-c", command ]
|
|
33
|
+
|
|
34
|
+
def spawn_options = { pgroup: true }
|
|
35
|
+
|
|
32
36
|
def kill_group(pid)
|
|
33
37
|
Process.kill(:KILL, -pid)
|
|
34
38
|
rescue Errno::ESRCH, Errno::EPERM
|
data/lib/miniswen/ruby_llm.rb
CHANGED
|
@@ -8,10 +8,10 @@ RubyLLM.configure do |config|
|
|
|
8
8
|
config.logger = Logger.new(IO::NULL) unless ENV["MINISWEN_DEBUG"] == "1"
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
# About
|
|
11
|
+
# About ten minutes of retries (1, 2, 4, ... 256s plus jitter): provider
|
|
12
12
|
# outages and rate-limit windows outlast the minute this used to allow.
|
|
13
13
|
RubyLLM.configure do |config|
|
|
14
|
-
config.max_retries =
|
|
14
|
+
config.max_retries = 9
|
|
15
15
|
config.retry_interval = 1
|
|
16
16
|
end
|
|
17
17
|
|
data/lib/miniswen/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: miniswen
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Svyatoslav Kryukov
|
|
@@ -58,6 +58,7 @@ files:
|
|
|
58
58
|
- lib/miniswen/cli/reporter.rb
|
|
59
59
|
- lib/miniswen/environment.rb
|
|
60
60
|
- lib/miniswen/environment/docker.rb
|
|
61
|
+
- lib/miniswen/jail.rb
|
|
61
62
|
- lib/miniswen/local.rb
|
|
62
63
|
- lib/miniswen/ruby_llm.rb
|
|
63
64
|
- lib/miniswen/testing.rb
|