From 9f6287025c288e2cd687d0bbecd3c8fb4867effa Mon Sep 17 00:00:00 2001 From: t Date: Fri, 21 Aug 2026 14:11:28 -0600 Subject: Keep subagent boards pinned while background workflows run A background subagents.lua workflow outlives the tool call that started it, so settling the outer result unpinned a board whose agents were still working. Track a per-tool-call workflow count and derive the pin from both the tool being active and that count, so the board only returns to transcript order once its last workflow finishes. Also show the reasoning level alongside the model on agent cards. --- subagents/progress.lua | 53 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) (limited to 'subagents/progress.lua') diff --git a/subagents/progress.lua b/subagents/progress.lua index d88e239..b39fe00 100644 --- a/subagents/progress.lua +++ b/subagents/progress.lua @@ -32,6 +32,7 @@ local GLYPHS = { local M = {} local boards = {} +local background_workflows = {} local bound = setmetatable({}, { __mode = "k" }) local tools_collapsed = true local sid_width = 8 @@ -284,6 +285,11 @@ local function set_board_pinned(board, pinned) end end +local function sync_board_pin(board) + local workflows = type(board.key) == "string" and background_workflows[board.key] or 0 + set_board_pinned(board, board.tool_active or (workflows or 0) > 0) +end + local function prune_boards() for key, board in pairs(boards) do if board.handle and board.handle.alive then @@ -293,8 +299,16 @@ local function prune_boards() end end -local function new_board() - local board = { cards = {}, handle = nil, collapsed = tools_collapsed, next_sequence = 0, pinned = false } +local function new_board(key) + local board = { + key = key, + cards = {}, + handle = nil, + collapsed = tools_collapsed, + next_sequence = 0, + pinned = false, + tool_active = false, + } board.component = { render = function(_, width) return render(board, width) end } return board end @@ -305,7 +319,8 @@ function M.claim(event) if not PROGRESS_TOOLS[name] then return end if type(event.set_component) ~= "function" then return end if type(event.collapsed) == "boolean" then tools_collapsed = event.collapsed end - local key, board = event.id or event.tool_call_id or name, new_board() + local key = event.id or event.tool_call_id or name + local board = new_board(key) local attached, handle = pcall(event.set_component, event, board.component) if not attached then return end board.handle = handle @@ -317,20 +332,41 @@ function M.claim(event) if replaying then replay_board(board, type(key) == "string" and key or nil) else - set_board_pinned(board, true) + board.tool_active = true end + sync_board_pin(board) end -- A result settles the outer tool invocation. Keep the board attached to its --- transcript entry, but return it to that entry's original ordering. +-- transcript entry, but leave it pinned while a background workflow it started +-- is still running. function M.settle(event) local key = type(event) == "table" and (event.id or event.tool_call_id) or nil local board = key and boards[key] - if board then set_board_pinned(board, false) end + if board then + board.tool_active = false + sync_board_pin(board) + end local co = coroutine.running() if co and bound[co] == key then bound[co] = nil end end +function M.workflow_started(tool_call_id) + if type(tool_call_id) ~= "string" or tool_call_id == "" then return end + background_workflows[tool_call_id] = (background_workflows[tool_call_id] or 0) + 1 + local board = boards[tool_call_id] + if board then sync_board_pin(board) end +end + +function M.workflow_finished(tool_call_id) + if type(tool_call_id) ~= "string" or tool_call_id == "" then return end + local count = background_workflows[tool_call_id] + if count == nil then return end + if count <= 1 then background_workflows[tool_call_id] = nil else background_workflows[tool_call_id] = count - 1 end + local board = boards[tool_call_id] + if board then sync_board_pin(board) end +end + function M.collapse(event) if type(event.collapsed) ~= "boolean" then return end prune_boards() @@ -510,6 +546,8 @@ local function replay_turn(board, conv, messages, start, finish, info, agent, sy local id = nonempty(info and info.id) local model = nonempty(metadata and metadata.model) or nonempty(info and info.model) + local reasoning = nonempty(metadata and metadata.reasoning) or nonempty(info and info.reasoning) + if model and reasoning then model = model .. " (" .. reasoning .. ")" end local card = new_card(board, agent or "subagent", id, model, { prompt = prompt, system_prompt = system_prompt, @@ -727,7 +765,8 @@ end function M.reset() for _, board in pairs(boards) do - set_board_pinned(board, false) + board.tool_active = false + sync_board_pin(board) end prune_boards() -- `bound` has weak coroutine keys. Foreground handlers clear themselves in -- cgit v1.3