Commit abb2232

mo khan <mo@mokhan.ca>
2026-01-17 00:39:40
refactor: remove web_fetch, web_search and eval tools. Restore grep and list tools. add git context to system prompt. truncate stdout and stderr
1 parent bce2f1f
lib/elelem/agent.rb
@@ -3,6 +3,7 @@
 module Elelem
   class Agent
     COMMANDS = %w[/clear /context /exit /help].freeze
+    MAX_LINES = 30
 
     attr_reader :history, :client, :toolbox, :terminal
 
@@ -56,7 +57,7 @@ module Elelem
         tool_calls.each do |tc|
           name, args = tc[:name], tc[:arguments]
           terminal.say "\n#{format_tool_display(name, args)}"
-          result = toolbox.run(name, args)
+          result = truncate(toolbox.run(name, args))
           terminal.say format_tool_result(name, result)
           ctx << { role: "tool", tool_call_id: tc[:id], content: result.to_json }
           errors += 1 if result[:error]
@@ -95,10 +96,21 @@ module Elelem
       result[:error] ? "  ! #{text.lines.first&.strip}" : text
     end
 
+    def truncate(result)
+      %w[stdout stderr].each do |k|
+        next unless result[k].is_a?(String) && result[k].lines.size > MAX_LINES
+        result[k] = result[k].lines.first(MAX_LINES).join + "… (truncated)"
+      end
+      result
+    end
+
     def system_prompt
+      branch = `git branch --show-current 2>/dev/null`.strip
+      dirty = `git status --porcelain 2>/dev/null`.lines.first(5).map(&:strip).join(", ")
       <<~PROMPT.strip
         Terminal agent. Act directly, verify your work. Stay grounded - only respond to what is asked.
         pwd: #{Dir.pwd}
+        #{"git: #{branch}" + (dirty.empty? ? "" : " [#{dirty}]") unless branch.empty?}
       PROMPT
     end
   end
lib/elelem/toolbox.rb
@@ -21,23 +21,17 @@ module Elelem
         required: ["cmd"],
         fn: ->(a) { Elelem.sh(a["cmd"], args: a["args"] || [], stdin: a["stdin"]) }
       },
-      "web_fetch" => {
-        desc: "Fetch URL content",
-        params: { url: { type: "string" } },
-        required: ["url"],
-        fn: ->(a) { r = Net::Hippie::Client.new.get(a["url"]); { status: r.code.to_i, body: r.body } }
-      },
-      "web_search" => {
-        desc: "Search web via DuckDuckGo",
+      "grep" => {
+        desc: "Search git-tracked files",
         params: { query: { type: "string" } },
         required: ["query"],
-        fn: ->(a) { q = CGI.escape(a["query"]); JSON.parse(Net::Hippie::Client.new.get("https://api.duckduckgo.com/?q=#{q}&format=json&no_html=1").body) }
+        fn: ->(a) { Elelem.sh("git", args: ["grep", "-nI", a["query"]]) }
       },
-      "eval" => {
-        desc: "Execute Ruby code",
-        params: { ruby: { type: "string" } },
-        required: ["ruby"],
-        fn: nil
+      "list" => {
+        desc: "List git-tracked files",
+        params: { path: { type: "string" } },
+        required: [],
+        fn: ->(a) { Elelem.sh("git", args: a["path"] ? ["ls-files", "--", a["path"]] : ["ls-files"]) }
       }
     }.freeze
 
@@ -70,7 +64,6 @@ module Elelem
       name = ALIASES.fetch(name, name)
       tool = tools[name]
       return { error: "unknown tool: #{name}" } unless tool
-      return { result: binding.eval(args["ruby"]) } if name == "eval"
 
       tool[:fn].call(args)
     rescue => e
lib/elelem.rb
@@ -1,9 +1,7 @@
 # frozen_string_literal: true
 
-require "cgi"
 require "fileutils"
 require "json"
-require "net/hippie"
 require "net/llm"
 require "open3"
 require "pathname"
spec/elelem/toolbox_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Elelem::Toolbox do
   describe "#to_h" do
     it "returns all tools in API format" do
       tool_names = subject.to_h.map { |t| t.dig(:function, :name) }
-      expect(tool_names).to include("read", "write", "exec", "web_fetch", "web_search", "eval")
+      expect(tool_names).to include("read", "write", "exec", "grep", "list")
     end
   end
 
@@ -26,9 +26,9 @@ RSpec.describe Elelem::Toolbox do
       expect(result[:error]).to include("unknown tool")
     end
 
-    it "executes eval tool" do
-      result = subject.run("eval", { "ruby" => "2 + 2" })
-      expect(result[:result]).to eq(4)
+    it "executes grep tool" do
+      result = subject.run("grep", { "query" => "RSpec.describe" })
+      expect(result["stdout"]).to include("toolbox_spec.rb")
     end
   end
 end
elelem.gemspec
@@ -40,10 +40,8 @@ Gem::Specification.new do |spec|
   spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
   spec.require_paths = ["lib"]
 
-  spec.add_dependency "cgi", "~> 0.1"
   spec.add_dependency "fileutils", "~> 1.0"
   spec.add_dependency "json", "~> 2.0"
-  spec.add_dependency "net-hippie", "~> 1.0"
   spec.add_dependency "net-llm", "~> 0.5", ">= 0.5.0"
   spec.add_dependency "open3", "~> 0.1"
   spec.add_dependency "pathname", "~> 0.1"
Gemfile.lock
@@ -2,10 +2,8 @@ PATH
   remote: .
   specs:
     elelem (0.8.0)
-      cgi (~> 0.1)
       fileutils (~> 1.0)
       json (~> 2.0)
-      net-hippie (~> 1.0)
       net-llm (~> 0.5, >= 0.5.0)
       open3 (~> 0.1)
       pathname (~> 0.1)
@@ -15,7 +13,6 @@ GEM
   remote: https://rubygems.org/
   specs:
     base64 (0.3.0)
-    cgi (0.5.1)
     date (3.5.1)
     diff-lcs (1.6.2)
     erb (6.0.1)