diff options
| -rw-r--r-- | init.lua | 13 | ||||
| -rw-r--r-- | spec/test_init.lua | 27 | ||||
| -rw-r--r-- | spec/test_progress.lua | 26 | ||||
| -rw-r--r-- | spec/test_progress_replay.lua | 1 | ||||
| -rw-r--r-- | subagents/progress.lua | 12 |
5 files changed, 68 insertions, 11 deletions
@@ -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 |
