From 69a1ee138bb78ad4663fe2d9e58678f7b0d07b74 Mon Sep 17 00:00:00 2001 From: t Date: Tue, 23 Jun 2026 09:38:23 -0600 Subject: ponytail simplifications to panto cli and lua extensiosn --- agent/tools/edit.lua | 93 +++++++++++++++++++++------------------------------- 1 file changed, 38 insertions(+), 55 deletions(-) (limited to 'agent/tools/edit.lua') diff --git a/agent/tools/edit.lua b/agent/tools/edit.lua index b32516a..2fa3468 100644 --- a/agent/tools/edit.lua +++ b/agent/tools/edit.lua @@ -10,8 +10,7 @@ -- All matches are evaluated against the original file, not -- progressively against the result of earlier entries. -local panto = require("panto") -local uv = require("luv") +local std = require("_std") local tool = { name = "std.edit", @@ -38,82 +37,39 @@ local tool = { }, } -panto.ext.on("tool", function(e) - if e.tool_name ~= tool.name then return end - e:set_component(e:get_component()) -end) - --- --------------------------------------------------------------------------- --- Coroutine-synchronous libuv wrappers. panto runs each tool handler in --- its own coroutine and drives a single `uv.run()` to completion; a --- handler may only block by yielding on a pending libuv op whose --- callback resumes it exactly once. `await` encapsulates that: arm one --- op, yield, return the callback's `(err, value)` results. --- --------------------------------------------------------------------------- -local function await(arm) - local co = assert(coroutine.running(), - "edit: await must run inside a tool handler coroutine") - local res, fired = nil, false - arm(function(...) - assert(not fired, "edit: libuv callback fired twice") - fired = true - res = table.pack(...) - local ok, err = coroutine.resume(co) - if not ok then error(err, 0) end - end) - coroutine.yield() - return table.unpack(res, 1, res.n) -end - -local function fs_open(path, flags, mode) - return await(function(resolve) uv.fs_open(path, flags, mode, resolve) end) -end -local function fs_fstat(fd) - return await(function(resolve) uv.fs_fstat(fd, resolve) end) -end -local function fs_read(fd, size, offset) - return await(function(resolve) uv.fs_read(fd, size, offset, resolve) end) -end -local function fs_write(fd, data, offset) - return await(function(resolve) uv.fs_write(fd, data, offset, resolve) end) -end -local function fs_close(fd) - return await(function(resolve) uv.fs_close(fd, resolve) end) -end - -- Read an entire file into a string. Returns (content, nil) or -- (nil, err). EOF on `fs_read` is the empty string, not nil. local function read_all(path) - local err, fd = fs_open(path, "r", 0) + local err, fd = std.fs_open(path, "r", 0) if not fd then return nil, err end - local serr, st = fs_fstat(fd) - if not st then fs_close(fd); return nil, serr end + local serr, st = std.fs_fstat(fd) + if not st then std.fs_close(fd); return nil, serr end local parts, offset = {}, 0 while true do - local rerr, data = fs_read(fd, 64 * 1024, offset) - if rerr then fs_close(fd); return nil, rerr end + local rerr, data = std.fs_read(fd, 64 * 1024, offset) + if rerr then std.fs_close(fd); return nil, rerr end if data == nil or #data == 0 then break end parts[#parts + 1] = data offset = offset + #data end - fs_close(fd) + std.fs_close(fd) return table.concat(parts), nil end -- Overwrite a file with `content` (O_WRONLY|O_CREAT|O_TRUNC, 0644). -- Returns (true, nil) or (nil, err). local function write_all(path, content) - local err, fd = fs_open(path, "w", tonumber("644", 8)) + local err, fd = std.fs_open(path, "w", tonumber("644", 8)) if not fd then return nil, err end -- A single `fs_write` may short-write; loop until all bytes land. local offset = 0 while offset < #content do - local werr, n = fs_write(fd, content:sub(offset + 1), offset) - if not n then fs_close(fd); return nil, werr end + local werr, n = std.fs_write(fd, content:sub(offset + 1), offset) + if not n then std.fs_close(fd); return nil, werr end offset = offset + n end -- Empty content still needs the truncate that O_TRUNC gave us. - fs_close(fd) + std.fs_close(fd) return true, nil end @@ -261,4 +217,31 @@ tool.handler = function(input) ) end +local function edit_diff(input) + local obj = std.decode(input) + if not obj or type(obj.edits) ~= "table" then return nil end + local out = {} + for _, e in ipairs(obj.edits) do + if type(e) == "table" then + if type(e.old) == "string" then + for l in (e.old .. "\n"):gmatch("(.-)\n") do if l ~= "" then out[#out + 1] = "-" .. l end end + end + if type(e.new) == "string" then + for l in (e.new .. "\n"):gmatch("(.-)\n") do if l ~= "" then out[#out + 1] = "+" .. l end end + end + end + end + return table.concat(out, "\n") +end + +std.install_renderer(tool.name, function(st) + local obj = std.decode(st.input) + local n = obj and type(obj.edits) == "table" and #obj.edits or 0 + if n == 1 then return "edit " .. tostring(obj.path or "") .. " (1 edit)" end + if n > 1 then return string.format("edit %s (%d edits)", tostring(obj.path or ""), n) end + return "edit " .. tostring(obj and obj.path or "") +end, function(st) + return edit_diff(st.input) or st.output +end) + return tool -- cgit v1.3