summaryrefslogtreecommitdiff
path: root/subagents/jobs.lua
diff options
context:
space:
mode:
Diffstat (limited to 'subagents/jobs.lua')
-rw-r--r--subagents/jobs.lua35
1 files changed, 27 insertions, 8 deletions
diff --git a/subagents/jobs.lua b/subagents/jobs.lua
index a774b27..9fe5c93 100644
--- a/subagents/jobs.lua
+++ b/subagents/jobs.lua
@@ -11,10 +11,9 @@
-- an edge trigger, never a count, so every wake drains the pipe, then drains
-- `job:next_event()` to exhaustion, then checks `job:result()`.
--
--- Waiting parks the CALLING coroutine — the tool handler's — and a poll
--- callback resumes exactly that coroutine. subagents/workflow.lua's rule that
--- a workflow callback must never run on a nested coroutine follows from this:
--- the parked thread is the one the resume goes to.
+-- Waiting parks the CALLING coroutine and a poll callback resumes that exact
+-- coroutine. Foreground tools use their handler coroutine; background
+-- workflows provide a dedicated coroutine anchored in their registry record.
--
-- The wake pipe is therefore mandatory wherever luv is: a started job whose
-- pipe or poll could not be armed is a start failure, not a degraded job.
@@ -43,9 +42,9 @@ local READ_CHUNK = 4096
local M = {}
--- The session-wide bound. A field, not a constant, so a caller (or a spec) can
--- lower it without reaching into the queue.
-M.MAX_CONCURRENT = 4
+-- The activation-time default is five; init.lua may replace it from the
+-- layered `[subagents] max_concurrent` setting before any child can start.
+M.MAX_CONCURRENT = 5
local handle_mt = {}
handle_mt.__index = handle_mt
@@ -58,6 +57,7 @@ local queued = {}
local waiters = {}
local running = 0
local pumping = false
+local cancelling = false
-- ---------------------------------------------------------------------------
-- Wake pipes
@@ -191,7 +191,7 @@ end
-- Start queued jobs while the gate has room. Reentrant: a job that settles the
-- instant it starts calls back in here, and the outer loop keeps going.
function pump_queue()
- if pumping then
+ if pumping or cancelling then
return
end
pumping = true
@@ -442,9 +442,28 @@ end
-- The turn was interrupted: ask every child to stop. Cancellation is a request,
-- not a settle — each job still reports its own cancelled result.
function M.cancel_all()
+ cancelling = true
for index = #live, 1, -1 do
live[index]:cancel()
end
+ cancelling = false
+ pump_queue()
+ wake()
+end
+
+-- Close settled jobs without disturbing queued or running background work.
+-- Called at ordinary turn boundaries; session teardown still uses close_all.
+function M.reap()
+ local kept = {}
+ for _, handle in ipairs(live) do
+ if handle.settled ~= nil then
+ close_pipe(handle)
+ close_job(handle)
+ else
+ kept[#kept + 1] = handle
+ end
+ end
+ live = kept
end
-- The turn is over: cancel every child and drop the state. Never blocks — a