miniswen 1.3.2 → 1.3.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68ce018afbb438d94868a940fc017020a317f8e892d9d93caf7a1d44a9ad61d5
4
- data.tar.gz: 852a7fa1a86b28d0de7a613e52d206b768a53459a53e7dce28537d384bbedf36
3
+ metadata.gz: d3124b6837dc7ec83d7abb8ec1a49da77b96767a0ab0e564d99b1cc639d6bf4f
4
+ data.tar.gz: b16c938ec5c39af746923c5814583e22ff576d357332c579ef8f7c0d9bdc73b1
5
5
  SHA512:
6
- metadata.gz: 489b57f01bcf406daefa8ed097093afcee51875e03557352bb24d5fb736c44329a19b2ce0b93d74855ca7485fb88cd72110ef72689191b0d7bf96266650a0fda
7
- data.tar.gz: 1d19b2c2d03c1fd666fbe9bcbcc47b2c3300e649d65719c1c7a180217a3ed21a3d3fd4c8006983b4fd804393f59321b340d0f3373a2a10008bd678b466de310c
6
+ metadata.gz: 3084347bf1ccf8c6fbcab21af5b1699ee00b30d27dc5f8dfe7610c684bf27709b47e82658ec5a4e00234ce235c27a180f532f1f520ec8613e20f6e02634c96a7
7
+ data.tar.gz: d7a7873f8af98236c23d32809b2361409b0f0d27a310979b93bb705feafab0d26dfac3764c4378ade8de2e56943b54f6746e258e8a675861ec93709dada0542a
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
 
@@ -513,7 +513,7 @@ module Miniswen
513
513
  body = e.response&.body.to_s
514
514
  detail = body.empty? ? e.message : "#{e.message}: #{body[0, 1000]}"
515
515
  raise InfrastructureError, "miniswen: the model call failed: #{detail}"
516
- rescue Faraday::SSLError, Faraday::ConnectionFailed, Faraday::TimeoutError => e
516
+ rescue Faraday::SSLError, Faraday::ConnectionFailed, Faraday::TimeoutError, Faraday::ParsingError => e
517
517
  raise InfrastructureError, "miniswen: the model call failed: #{e.class}: #{e.message}"
518
518
  end
519
519
 
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
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "miniswen/local"
4
+
5
+ module Miniswen
6
+ # Runs every command in its own namespaces: none of the harness's environment,
7
+ # no network, read-only system, none of its files.
8
+ class Jail < Local
9
+ SETUP = <<~SH
10
+ set -eu
11
+ ip link set lo up
12
+ for dir in /usr /etc /opt; do mount -o bind,ro "$dir" "$dir"; done
13
+ for dir in /tmp /run /root; do mkdir -p "/var/lib/miniswen$dir" && mount --bind "/var/lib/miniswen$dir" "$dir"; done
14
+ echo ready
15
+ exec sleep infinity
16
+ SH
17
+
18
+ def initialize(workdir: Dir.pwd)
19
+ @workdir = workdir
20
+ end
21
+
22
+ def start
23
+ _, @stdout, @stderr, @holder = Open3.popen3(
24
+ "unshare", "--net", "--mount", "--pid", "--fork", "--kill-child", "--mount-proc", "sh", "-c", SETUP, pgroup: true
25
+ )
26
+ return self if @stdout.gets == "ready\n"
27
+
28
+ raise InfrastructureError, "jail did not start: #{@stderr.read}"
29
+ end
30
+
31
+ def stop
32
+ Process.kill(:KILL, -@holder.pid) if @holder.alive?
33
+ end
34
+
35
+ private
36
+
37
+ def spawn_arguments(command, env)
38
+ [ ENV.to_h.merge(env.to_h).slice(*container_variables),
39
+ "nsenter", "--target", @holder.pid.to_s, "--net", "--mount", "--pid=/proc/#{@holder.pid}/ns/pid_for_children", "--wd=#{@workdir}",
40
+ "--", "setpriv", "--bounding-set=-all", "--inh-caps=-all", "--no-new-privs", "--", "sh", "-c", command ]
41
+ end
42
+
43
+ def spawn_options = super.merge(unsetenv_others: true)
44
+
45
+ def container_variables
46
+ @container_variables ||= File.read("/proc/1/environ").split("\0").map { it.split("=").first } | Agent::EXEC_ENV.keys
47
+ end
48
+ end
49
+ end
@@ -9,8 +9,10 @@ module Miniswen
9
9
  class Local < Environment
10
10
  TIMEOUT_EXIT_CODE = 124
11
11
 
12
+ # Always through a shell: Ruby execs a metacharacter-free string directly,
13
+ # and a missing binary would then raise ENOENT here instead of exiting 127.
12
14
  def exec(command, timeout: nil, env: nil)
13
- Open3.popen2e(env || {}, command, pgroup: true) do |stdin, io, wait_thr|
15
+ Open3.popen2e(*spawn_arguments(command, env), **spawn_options) do |stdin, io, wait_thr|
14
16
  stdin.close
15
17
  reader = Thread.new { io.read }
16
18
 
@@ -25,8 +27,14 @@ module Miniswen
25
27
  end
26
28
  end
27
29
 
30
+ def stop = nil
31
+
28
32
  private
29
33
 
34
+ def spawn_arguments(command, env) = [ env || {}, "sh", "-c", command ]
35
+
36
+ def spawn_options = { pgroup: true }
37
+
30
38
  def kill_group(pid)
31
39
  Process.kill(:KILL, -pid)
32
40
  rescue Errno::ESRCH, Errno::EPERM
@@ -8,9 +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 a minute of retries for egress blips (1, 2, 4, 8, 16, 32s plus jitter)
11
+ # About ten minutes of retries (1, 2, 4, ... 256s plus jitter): provider
12
+ # outages and rate-limit windows outlast the minute this used to allow.
12
13
  RubyLLM.configure do |config|
13
- config.max_retries = 6
14
+ config.max_retries = 9
14
15
  config.retry_interval = 1
15
16
  end
16
17
 
@@ -36,13 +37,13 @@ module Miniswen
36
37
  end
37
38
  end
38
39
 
39
- # A handshake reset never sent the request, so retrying is as safe as the
40
- # ConnectionFailed retries ruby_llm already does; it only lists SSL errors
41
- # as fatal.
42
- module RetryTransientSSL
43
- def retry_exceptions = super + [ Faraday::SSLError ]
40
+ # ruby_llm lists SSL errors as fatal, but a handshake reset never sent the
41
+ # request; a 200 with a truncated JSON body is the same dropped connection
42
+ # one step later.
43
+ module RetryTransientFailures
44
+ def retry_exceptions = super + [ Faraday::SSLError, Faraday::ParsingError ]
44
45
  end
45
46
  end
46
47
 
47
48
  RubyLLM::Providers::OpenRouter.prepend(Miniswen::VerbatimReasoningDetails)
48
- RubyLLM::Connection.prepend(Miniswen::RetryTransientSSL)
49
+ RubyLLM::Connection.prepend(Miniswen::RetryTransientFailures)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Miniswen
4
- VERSION = "1.3.2"
4
+ VERSION = "1.3.4"
5
5
  end
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.2
4
+ version: 1.3.4
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