Commit 4b622d9

mo khan <mo@mokhan.ca>
2026-01-28 00:03:22
fix: timout on webrick server, provide tool_call_id, add before(:each) hook, improve ctags fallback
1 parent 8dc33be
lib/elelem/mcp/oauth.rb
@@ -146,24 +146,29 @@ module Elelem
 
       def wait_for_callback(expected_state)
         code = nil
-        server = WEBrick::HTTPServer.new(
+        @server = WEBrick::HTTPServer.new(
           Port: CALLBACK_PORT,
           Logger: WEBrick::Log.new(File::NULL),
           AccessLog: []
         )
 
-        server.mount_proc("/callback") do |req, res|
+        at_exit { @server&.shutdown }
+
+        @server.mount_proc("/callback") do |req, res|
           state = req.query["state"]
           raise "State mismatch" unless state == expected_state
 
           code = req.query["code"]
           res.content_type = "text/html"
           res.body = "<html><body><h1>Authorization complete</h1><p>You can close this window.</p></body></html>"
-          server.shutdown
+          @server.shutdown
         end
 
-        server.start
+        Timeout.timeout(120) { @server.start }
         code
+      rescue Timeout::Error
+        @server.shutdown
+        raise "OAuth callback timed out"
       end
 
       def exchange_code(metadata, client, code, verifier)
lib/elelem/plugins/zz_confirm.rb
@@ -3,9 +3,7 @@
 Elelem::Plugins.register(:confirm) do |agent|
   permissions = Elelem::Permissions.new
 
-  agent.toolbox.tools.each_key do |tool_name|
-    agent.toolbox.before(tool_name) do |args|
-      permissions.check(tool_name, args, terminal: agent.terminal)
-    end
+  agent.toolbox.before do |args, tool_name:|
+    permissions.check(tool_name, args, terminal: agent.terminal)
   end
 end
lib/elelem/conversation.rb
@@ -8,9 +8,9 @@ module Elelem
       @messages = []
     end
 
-    def add(role:, content:)
+    def add(role:, content:, **extra)
       raise ArgumentError, "invalid role: #{role}" unless ROLES.include?(role)
-      @messages << { role: role, content: content }
+      @messages << { role: role, content: content, **extra }.compact
     end
 
     def last = @messages.last
lib/elelem/system_prompt.rb
@@ -100,12 +100,17 @@ module Elelem
     end
 
     def ctags_fallback(files)
-      output = `ctags -x --languages=Ruby --kinds-Ruby=cfm -L - 2>/dev/null <<< "#{files.join("\n")}"`
-      return [] unless $?.success?
+      return [] if files.empty?
+
+      output = IO.popen(["ctags", "-x", "--languages=Ruby", "--kinds-Ruby=cfm", "-L", "-"], "r+") do |io|
+        io.puts(files)
+        io.close_write
+        io.read
+      end
 
       output.lines.map do |line|
         parts = line.split(/\s+/, 4)
-        { file: parts[3]&.split&.first, name: parts[0] }
+        { file: parts[2], name: parts[0] }
       end
     rescue Errno::ENOENT
       []
lib/elelem/toolbox.rb
@@ -16,11 +16,11 @@ module Elelem
       tool.aliases.each { |a| @aliases[a] = name }
     end
 
-    def before(tool_name, &block)
+    def before(tool_name = :*, &block)
       @hooks[:before][tool_name] << block
     end
 
-    def after(tool_name, &block)
+    def after(tool_name = :*, &block)
       @hooks[:after][tool_name] << block
     end
 
@@ -38,8 +38,10 @@ module Elelem
       errors = tool.validate(args)
       return failure(error: errors.join(", ")) if errors.any?
 
+      @hooks[:before][:*].each { |h| h.call(args, tool_name: tool.name) }
       @hooks[:before][tool.name].each { |h| h.call(args) }
       result = tool.call(args)
+      @hooks[:after][:*].each { |h| h.call(args, result, tool_name: tool.name) }
       @hooks[:after][tool.name].each { |h| h.call(args, result) }
       result[:error] ? failure(result) : success(result)
     rescue => e