summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--init.lua13
-rw-r--r--spec/test_init.lua27
-rw-r--r--spec/test_progress.lua26
-rw-r--r--spec/test_progress_replay.lua1
-rw-r--r--subagents/progress.lua12
5 files changed, 68 insertions, 11 deletions
diff --git a/init.lua b/init.lua
index 2e4cab1..a104324 100644
--- a/init.lua
+++ b/init.lua
@@ -153,6 +153,7 @@ local function activate()
-- Escape reaches the children through the turn, not through a tool: the
-- primary is parked inside a tool call when they are running.
+ progress.begin_replay()
-- Startup session replay fires tool lifecycle events before the first
-- live turn. After that boundary, progress.claim must stay presentation-
-- only and never rescan the child catalog for ordinary calls.
@@ -193,7 +194,9 @@ local function activate()
},
handler = function(input, context)
progress.bind(context)
- return run.handle(input, profiles)
+ local result = run.handle(input, profiles)
+ progress.settle(context)
+ return result
end,
}
@@ -241,7 +244,9 @@ local function activate()
},
handler = function(input, context)
progress.bind(context)
- return luatool.handle(input, profiles)
+ local result = luatool.handle(input, profiles)
+ progress.settle(context)
+ return result
end,
}
@@ -278,7 +283,9 @@ local function activate()
},
handler = function(input, context)
progress.bind(context)
- return toml_workflows.handle(input, profiles)
+ local result = toml_workflows.handle(input, profiles)
+ progress.settle(context)
+ return result
end,
}
diff --git a/spec/test_init.lua b/spec/test_init.lua
index 2d5e7d1..97f0eba 100644
--- a/spec/test_init.lua
+++ b/spec/test_init.lua
@@ -176,8 +176,23 @@ return {
local claimed, pins = nil, {}
handle.emit("tool_call_complete", {
+ id = "replayed-call",
+ tool_name = "subagents.workflow",
+ set_component = function(_, component)
+ claimed = component
+ return {
+ invalidate = function() end,
+ alive = function() return true end,
+ set_pinned = function(_, value) pins[#pins + 1] = value end,
+ }
+ end,
+ })
+ assert(#pins == 0, "a workflow restored during startup stays in transcript order")
+
+ handle.emit("turn_start", {})
+ handle.emit("tool_call_complete", {
id = "call-1",
- tool_name = "subagents.run",
+ tool_name = "subagents.workflow",
set_component = function(_, component)
claimed = component
return {
@@ -189,9 +204,13 @@ return {
})
assert(type(claimed) == "table" and type(claimed.render) == "function",
"the entry is given a component that renders the cards")
- assert(pins[1] == true, "the progress component pins after claim")
- handle.emit("tool_result", { id = "call-1", tool_name = "subagents.run" })
- assert(pins[2] == false, "the matching result unpins it")
+ assert(pins[1] == true, "the live progress component pins after claim")
+ local output = handle.tools_by_name["subagents.workflow"].handler(
+ { prompt = "", steps = {} }, { tool_call_id = "call-1" })
+ assert(type(output) == "string", "the workflow handler returned its result")
+ assert(pins[2] == false, "handler completion unpins before the next model action")
+ handle.emit("tool_result", { id = "call-1", tool_name = "subagents.workflow" })
+ assert(#pins == 2, "the later host result is an inert fallback")
local foreign
handle.emit("tool_call_complete", {
diff --git a/spec/test_progress.lua b/spec/test_progress.lua
index 06509bf..5788998 100644
--- a/spec/test_progress.lua
+++ b/spec/test_progress.lua
@@ -20,6 +20,32 @@ local function plain(lines)
end
return {
+ { "startup replay stays in transcript order while live calls pin", function()
+ progress.reset()
+ progress.begin_replay()
+ local pins = {}
+ local function claim(id)
+ progress.claim({
+ id = id,
+ tool_name = "subagents.workflow",
+ set_component = function()
+ return {
+ alive = function() return true end,
+ invalidate = function() end,
+ set_pinned = function(_, value) pins[#pins + 1] = value end,
+ }
+ end,
+ })
+ end
+ claim("historical")
+ assert(#pins == 0, "replayed workflows must never pin")
+ progress.begin_live_turn()
+ claim("live")
+ assert(pins[1] == true, "a live workflow pins while it runs")
+ progress.reset()
+ assert(pins[2] == false, "reset releases the live workflow")
+ end },
+
{ "concurrent boards pin independently until their matching result", function()
progress.reset()
local pins = { a = {}, b = {} }
diff --git a/spec/test_progress_replay.lua b/spec/test_progress_replay.lua
index aba8e33..cf2925e 100644
--- a/spec/test_progress_replay.lua
+++ b/spec/test_progress_replay.lua
@@ -62,6 +62,7 @@ end
local function with_host(fn, opts)
local handle = fake.install(opts)
+ progress.begin_replay()
local ok, err = pcall(fn, handle)
progress.reset()
handle.restore()
diff --git a/subagents/progress.lua b/subagents/progress.lua
index fb95115..03fe62a 100644
--- a/subagents/progress.lua
+++ b/subagents/progress.lua
@@ -312,12 +312,12 @@ function M.claim(event)
local prior = boards[key]
if prior then set_board_pinned(prior, false) end
boards[key] = board
- set_board_pinned(board, true)
- -- During startup/restart the child catalog already contains the durable
- -- turns for this outer call. A live call normally finds nothing here; the
- -- ownership stamp makes that distinction without parsing outer results.
+ -- Replayed calls are already historical. Only a call dispatched during the
+ -- live turn belongs at the bottom of the screen.
if replaying then
replay_board(board, type(key) == "string" and key or nil)
+ else
+ set_board_pinned(board, true)
end
end
@@ -710,6 +710,10 @@ end
-- The host fires this before the first live model turn, after startup
-- conversation replay has finished. Keeping the mode explicit avoids scanning
-- child files for every ordinary foreground call.
+function M.begin_replay()
+ replaying = true
+end
+
function M.begin_live_turn()
replaying = false
end