diff options
| author | t <t@tjp.lol> | 2026-08-21 14:11:28 -0600 |
|---|---|---|
| committer | t <t@tjp.lol> | 2026-08-21 14:12:35 -0600 |
| commit | 9f6287025c288e2cd687d0bbecd3c8fb4867effa (patch) | |
| tree | f1ca2d1e5cd79f9d3e0d1afd8280efd5257d405e /subagents | |
| parent | 49a6001dde72622df9410b738322e9a23e68ddee (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')
| -rw-r--r-- | subagents/progress.lua | 53 | ||||
| -rw-r--r-- | subagents/spawn.lua | 4 | ||||
| -rw-r--r-- | subagents/workflow.lua | 3 |
3 files changed, 52 insertions, 8 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 diff --git a/subagents/spawn.lua b/subagents/spawn.lua index 9117fdd..d90631d 100644 --- a/subagents/spawn.lua +++ b/subagents/spawn.lua @@ -434,7 +434,9 @@ function M.spawn(spec) if got_len and type(length) == "number" then turn_index = length + 1 end end - local card = progress.card(spec.label or "subagent", id, model_label, { + local card_detail = model_label + if card_detail and reasoning_label then card_detail = card_detail .. " (" .. reasoning_label .. ")" end + local card = progress.card(spec.label or "subagent", id, card_detail, { prompt = spec.prompt, system_prompt = spec.presentation_system_prompt, }) diff --git a/subagents/workflow.lua b/subagents/workflow.lua index 97dd858..671890e 100644 --- a/subagents/workflow.lua +++ b/subagents/workflow.lua @@ -767,6 +767,7 @@ local function finish_workflow(record, status, result, err) record.error = err record.coroutine = nil active_workflows[record.id] = nil + progress.workflow_finished(record.tool_call_id) notify(record) end @@ -785,6 +786,7 @@ function M.start(wf, opts) workflow_sequence = workflow_sequence + 1 local id = "workflow-" .. workflow_sequence local record = make_workflow_record(id) + record.tool_call_id = type(opts.tool_call_id) == "string" and opts.tool_call_id ~= "" and opts.tool_call_id or nil workflow_records[id] = record active_workflows[id] = record @@ -823,6 +825,7 @@ function M.start(wf, opts) finish_workflow(record, record.cancel_requested and "cancelled" or "failed", nil, tostring(err)) end end) + progress.workflow_started(record.tool_call_id) return id end |
