summaryrefslogtreecommitdiff
path: root/subagents/progress.lua
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-08-21 14:11:28 -0600
committert <t@tjp.lol>2026-08-21 14:12:35 -0600
commit9f6287025c288e2cd687d0bbecd3c8fb4867effa (patch)
treef1ca2d1e5cd79f9d3e0d1afd8280efd5257d405e /subagents/progress.lua
parent49a6001dde72622df9410b738322e9a23e68ddee (diff)
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.
Diffstat (limited to 'subagents/progress.lua')
-rw-r--r--subagents/progress.lua53
1 files changed, 46 insertions, 7 deletions
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