diff options
Diffstat (limited to 'subagents/progress.lua')
| -rw-r--r-- | subagents/progress.lua | 60 |
1 files changed, 57 insertions, 3 deletions
diff --git a/subagents/progress.lua b/subagents/progress.lua index aee198b..7cad2fa 100644 --- a/subagents/progress.lua +++ b/subagents/progress.lua @@ -51,6 +51,35 @@ local function display_tool_name(name) return type(name) == "string" and name:gsub("__", ".") or "tool" end +-- A board replaces the whole tool block, so the Lua a `subagents.lua` call ran +-- would otherwise never be visible — least of all for an inspection call that +-- starts no children and therefore has no cards at all. `tool_call_complete` +-- carries the raw input JSON, which a replayed entry gets too, so the source +-- survives /resume without any handler-side bookkeeping. +local function decode_input(text) + if type(text) ~= "string" or text == "" then return nil end + local ok, panto = pcall(require, "panto") + local json = ok and type(panto) == "table" and type(panto.ext) == "table" and panto.ext.json or nil + if type(json) ~= "table" or type(json.decode) ~= "function" then + local dk_ok, dkjson = pcall(require, "dkjson") + json = dk_ok and type(dkjson) == "table" and dkjson or nil + end + if type(json) ~= "table" or type(json.decode) ~= "function" then return nil end + local decoded_ok, value = pcall(json.decode, text) + if not decoded_ok or type(value) ~= "table" then return nil end + return value +end + +local function source_of(tool_name, input) + if tool_name ~= "subagents.lua" then return nil end + local decoded = decode_input(input) + local source = decoded and decoded.source + if type(source) ~= "string" or source == "" then return nil end + -- Tabs are stripped to a single space by sanitization, which would flatten + -- indented code; widen them first so the listing keeps its shape. + return sanitize_multiline((source:gsub("\t", " "))):sub(1, MAX_PRESENTATION_BYTES) +end + local function wrap(text, width, emit) for _, row in ipairs(require("panto").text.wrap(sanitize(text), math.max(1, width))) do emit(row) @@ -237,11 +266,34 @@ local function body_lines(card, width, collapsed) return out end +-- The executed source, verbatim: real newlines, one rendered row per wrapped +-- source line. Collapsed shows the head of it, expanded the whole thing. +local function source_lines(board, width, collapsed) + local limit = collapsed and COLLAPSED_LINES or MAX_PRESENTATION_LINES + local out, truncated = {}, false + local function add(row) + if #out < limit then out[#out + 1] = BODY_INDENT .. row else truncated = true end + end + for line in (board.source .. "\n"):gmatch("(.-)\n") do + if truncated then break end + wrap(line, width, add) + end + if truncated then out[#out + 1] = BODY_INDENT .. "…" end + return out +end + local function render(board, width) local out = {} width = math.floor(tonumber(width) or 0) - if #board.cards == 0 or width <= #BODY_INDENT then return out end + if width <= #BODY_INDENT then return out end + local source = board.source and source_lines(board, width - #BODY_INDENT, board.collapsed) or nil + if #board.cards == 0 and not source then return out end out[#out + 1] = "" + if source then + out[#out + 1] = require("panto").text.truncate( + sanitize("⚙ " .. display_tool_name(board.tool_name)), width) + for _, line in ipairs(source) do out[#out + 1] = line end + end for _, card in ipairs(board.cards) do local sid = card.sid ~= "" and (" " .. card.sid:sub(1, sid_width) .. (sid_width < #card.sid and "…" or "")) or "" @@ -277,9 +329,11 @@ local function prune_boards() end end -local function new_board(key) +local function new_board(key, tool_name, source) local board = { key = key, + tool_name = tool_name, + source = source, cards = {}, handle = nil, collapsed = tools_collapsed, @@ -298,7 +352,7 @@ function M.claim(event) if type(event.set_component) ~= "function" then return end if type(event.collapsed) == "boolean" then tools_collapsed = event.collapsed end local key = event.id or event.tool_call_id or name - local board = new_board(key) + local board = new_board(key, name, source_of(name, event.input)) local attached, handle = pcall(event.set_component, event, board.component) if not attached then return end board.handle = handle |
