Commit 7a5c2b8

mo khan <mo@mokhan.ca>
2026-09-07 16:35:54
refactor: namespace process-exec helpers under Elelem::Builtins
sh/truncate/terminate/command_timeout reopened Elelem itself, which is a plugin silently patching core's namespace. Move them under Elelem::Builtins so ownership is explicit; callers outside this gem (elelem-evals) now depend on elelem-builtins explicitly instead of assuming core provides Elelem.sh. Claude-Session: https://claude.ai/code/session_01DUuj4amvRrPxDnHPHBkvz5
1 parent 5eb5c7e
Changed files (3)
lib/elelem/builtins/execute.rb
@@ -8,7 +8,7 @@ Elelem.configure do |config|
       required: ["command"],
       aliases: ["bash", "sh", "exec", "execute<|channel|>"]
     ) do |a|
-      Elelem.sh("bash", args: ["-c", a["command"]], timeout: Elelem.command_timeout) { |x| agent.output.print(x) }
+      Elelem::Builtins.sh("bash", args: ["-c", a["command"]], timeout: Elelem::Builtins.command_timeout) { |x| agent.output.print(x) }
     end
 
     agent.toolbox.after("execute") do |args, result|
lib/elelem/builtins.rb
@@ -11,51 +11,53 @@ require "tempfile"
 require_relative "builtins/version"
 
 module Elelem
-  def self.sh(cmd, args: [], cwd: Dir.pwd, env: {}, timeout: nil)
-    output = StringIO.new
-    options = { chdir: cwd }
-    options[:pgroup] = true if timeout
+  module Builtins
+    def self.sh(cmd, args: [], cwd: Dir.pwd, env: {}, timeout: nil)
+      output = StringIO.new
+      options = { chdir: cwd }
+      options[:pgroup] = true if timeout
 
-    Open3.popen2e(env, cmd, *args, **options) do |stdin, out, wait_thr|
-      stdin.close
-      timed_out = false
-      watchdog = timeout && Thread.new do
-        sleep(timeout)
-        timed_out = true
-        terminate(wait_thr.pid)
-      end
+      Open3.popen2e(env, cmd, *args, **options) do |stdin, out, wait_thr|
+        stdin.close
+        timed_out = false
+        watchdog = timeout && Thread.new do
+          sleep(timeout)
+          timed_out = true
+          terminate(wait_thr.pid)
+        end
 
-      out.each_line do |line|
-        yield line if block_given?
-        output.write(line)
-      end
-      timed_out ? watchdog&.join : watchdog&.kill
+        out.each_line do |line|
+          yield line if block_given?
+          output.write(line)
+        end
+        timed_out ? watchdog&.join : watchdog&.kill
 
-      status = wait_thr.value
-      note = timed_out ? "\n[command timed out after #{timeout}s]" : ""
-      { exit_status: status.exitstatus || (timed_out ? 124 : 1), content: truncate(output.string) + note }
+        status = wait_thr.value
+        note = timed_out ? "\n[command timed out after #{timeout}s]" : ""
+        { exit_status: status.exitstatus || (timed_out ? 124 : 1), content: truncate(output.string) + note }
+      end
     end
-  end
 
-  def self.truncate(content, limit: 10_000)
-    return content if content.length <= limit
+    def self.truncate(content, limit: 10_000)
+      return content if content.length <= limit
 
-    half = limit / 2
-    elided = content.length - limit
-    "#{content[0, half]}\n[#{elided} characters elided]\n#{content[-half, half]}"
-  end
+      half = limit / 2
+      elided = content.length - limit
+      "#{content[0, half]}\n[#{elided} characters elided]\n#{content[-half, half]}"
+    end
 
-  def self.terminate(pid)
-    Process.kill("TERM", -pid)
-    sleep 0.5
-    Process.kill("KILL", -pid)
-  rescue Errno::ESRCH
-    nil
-  end
+    def self.terminate(pid)
+      Process.kill("TERM", -pid)
+      sleep 0.5
+      Process.kill("KILL", -pid)
+    rescue Errno::ESRCH
+      nil
+    end
 
-  def self.command_timeout
-    value = ENV["ELELEM_CMD_TIMEOUT"]
-    value && !value.empty? ? Integer(value) : nil
+    def self.command_timeout
+      value = ENV["ELELEM_CMD_TIMEOUT"]
+      value && !value.empty? ? Integer(value) : nil
+    end
   end
 end
 
elelem-builtins.gemspec
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
   end
   spec.require_paths = ["lib"]
 
-  spec.add_dependency "elelem", "~> 0.11"
+  spec.add_dependency "elelem", "~> 0.12"
   spec.add_dependency "fileutils", "~> 1.8"
   spec.add_dependency "json", "~> 2.21"
   spec.add_dependency "open3", "~> 0.2"