summaryrefslogtreecommitdiff
path: root/subagents
diff options
context:
space:
mode:
Diffstat (limited to 'subagents')
-rw-r--r--subagents/jobs.lua15
-rw-r--r--subagents/workflow.lua30
2 files changed, 42 insertions, 3 deletions
diff --git a/subagents/jobs.lua b/subagents/jobs.lua
index 9fe5c93..dc166a7 100644
--- a/subagents/jobs.lua
+++ b/subagents/jobs.lua
@@ -280,6 +280,21 @@ function handle_mt:cancel()
end
end
+-- Cancel one known handle and wake parked awaiters. `handle:cancel()` remains
+-- the narrow primitive; workflow records use this helper because cancelling a
+-- queued child settles it synchronously.
+function M.cancel(handle)
+ if getmetatable(handle) ~= handle_mt then
+ error("jobs.cancel expects a job handle", 2)
+ end
+ if handle.settled ~= nil then
+ return false
+ end
+ handle:cancel()
+ wake()
+ return true
+end
+
-- start(startspec) -> handle | nil, err
--
-- startspec = {
diff --git a/subagents/workflow.lua b/subagents/workflow.lua
index 375285f..97dd858 100644
--- a/subagents/workflow.lua
+++ b/subagents/workflow.lua
@@ -58,8 +58,8 @@ local ok_uv, uv = pcall(require, "luv")
local M = {}
--- Session-scoped background workflows. The model sees only immutable proxies;
--- mutable state and live handles stay private in this module.
+-- Session-scoped background workflows. The model sees field-read-only proxies
+-- with cancellation closures; mutable state and live handles stay private.
local workflow_sequence = 0
local workflow_records = {}
local active_workflows = {}
@@ -389,6 +389,13 @@ ctx_mt.__name = "subagents.ctx"
-- exposing only `agent` and `await`. Weak keys so a finished run is collectable.
local state = setmetatable({}, { __mode = "k" })
+local function cancel_agent_record(record)
+ if record.status ~= "running" or record.job == nil then
+ return false
+ end
+ return jobs().cancel(record.job)
+end
+
local function make_agent_record(workflow_record, name)
local record = {
name = name,
@@ -400,6 +407,8 @@ local function make_agent_record(workflow_record, name)
record.proxy = readonly(function(_, key)
if key == "name" or key == "status" or key == "output" or key == "error" or key == "id" then
return record[key]
+ elseif key == "cancel" then
+ return function() return cancel_agent_record(record) end
end
end)
workflow_record.agent_order[#workflow_record.agent_order + 1] = record
@@ -412,6 +421,7 @@ local function settle_agent_record(record, result)
record.status = result.status or "failed"
record.id = result.id
record.error = result.error
+ record.job = nil
if result.output ~= nil then
record.output = M.output_text(result)
end
@@ -493,6 +503,7 @@ function ctx_mt:agent(input)
})
else
job_of[handle] = job
+ if agent_record then agent_record.job = job end
end
s.job_count = s.job_count + 1
@@ -689,6 +700,17 @@ function M.output_text(result)
return tostring(output)
end
+local function cancel_workflow_record(record)
+ if record.status ~= "running" then
+ return false
+ end
+ record.cancel_requested = true
+ for _, agent in ipairs(record.agent_order) do
+ cancel_agent_record(agent)
+ end
+ return true
+end
+
local function make_workflow_record(id)
local record = {
id = id,
@@ -719,6 +741,8 @@ local function make_workflow_record(id)
return record[key]
elseif key == "agents" then
return record.agents_proxy
+ elseif key == "cancel" then
+ return function() return cancel_workflow_record(record) end
end
end)
return record
@@ -804,8 +828,8 @@ end
function M.cancel_all(suppress_notification)
for _, record in pairs(active_workflows) do
- record.cancel_requested = true
if suppress_notification then record.suppress_notification = true end
+ cancel_workflow_record(record)
end
end