From 4f0a91ef55fe96835172bdad34feec1e2a0a0977 Mon Sep 17 00:00:00 2001 From: t Date: Sun, 16 Aug 2026 20:42:43 -0600 Subject: subagents extension on the generic host surfaces The rock now owns all subagent policy on top of libpanto-lua's generic APIs: children are ordinary panto.agent instances over rock-constructed stores, started with agent:run_async and awaited by arming uv.new_poll on each job's wake_fd from the tool handler's coroutine. subagents/jobs.lua carries the session policy the host used to own: the concurrency gate (4 running, FIFO queue, cancel-while-queued never starts), the await contract (results in input order; "first" returns settled plus remaining by identity), and settle-time shaping. subagents/spawn.lua seeds new children (primary system context, child role, profile body with manifest metadata), resolves model/reasoning through panto.ext.resolve_model, filters subagents.* out of the inherited tool set via agent:set_tools, and reads resume defaults back from stored message metadata. One-shot structured workers are a null_store agent with a declaration-only output tool, tool_choice forced, dispatch_tools=false. subagents/progress.lua renders per-tool-entry cards through the component handle's invalidate seam; turn_interrupt cancels live children, turn_end closes them. Spec suite rewritten against fakes of the new surfaces (98 cases), including gate/queue/cancel bounds, resume-default extraction, one-shot capture via unresolved tool calls, tool filtering, and manifest seeding. --- spec/run.lua | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 spec/run.lua (limited to 'spec/run.lua') diff --git a/spec/run.lua b/spec/run.lua new file mode 100644 index 0000000..ee9f432 --- /dev/null +++ b/spec/run.lua @@ -0,0 +1,81 @@ +-- The spec runner: `lua spec/run.lua` from anywhere in the repo. +-- +-- Expected environment. Plain Lua 5.4 with the repo root on package.path, which +-- this file arranges from `arg[0]`, plus the rocks the extension depends on +-- (lyaml, toml2lua, luv) and dkjson for the specs' JSON decoding. `mise run +-- check` puts the ./.rocks tree on LUA_PATH/LUA_CPATH first and is the intended +-- entry point; a bare `lua spec/run.lua` also works if those rocks are on the +-- default path. `panto lua spec/run.lua` works too — panto's own rocks tree +-- already carries luv. +-- +-- Missing optional rocks do not fail the run. A test that needs one requires it +-- lazily and returns `"skip", reason`; the runner prints a SKIP line, counts it, +-- and still exits 0. Only a real assertion failure or an unexpected error exits +-- 1. `mise run deps` installs everything, so a local run exercises all of it. +-- +-- Test files. Every spec/test_*.lua returns an ordered array of { name, fn }. +-- `fn` asserts and returns nothing to pass, or returns "skip", reason. Ordering +-- is the array's, so a file reads top to bottom. + +local script = (arg and arg[0]) or "spec/run.lua" +local spec_dir = script:match("^(.*)/[^/]+$") or "." +local root = spec_dir:match("^(.*)/[^/]+$") or "." + +package.path = root .. "/?.lua;" .. root .. "/?/init.lua;" .. package.path + +local function test_files() + local found = {} + local pipe = io.popen("ls '" .. spec_dir .. "'/test_*.lua 2>/dev/null") + if not pipe then + return found + end + for line in pipe:lines() do + found[#found + 1] = line + end + pipe:close() + table.sort(found) + return found +end + +local function traceback(err) + return debug.traceback(tostring(err), 2) +end + +local passed, skipped, failed = 0, 0, 0 + +local function record(label, ok, first, second) + if not ok then + failed = failed + 1 + print("FAIL " .. label) + print(first) + elseif first == "skip" then + skipped = skipped + 1 + print("SKIP " .. label .. " — " .. tostring(second)) + else + passed = passed + 1 + print("ok " .. label) + end +end + +for _, file in ipairs(test_files()) do + local name = file:match("[^/]+$") + local chunk, load_err = loadfile(file) + if not chunk then + record(name, false, "could not load: " .. tostring(load_err)) + else + local loaded, cases = xpcall(chunk, traceback) + if not loaded then + record(name, false, cases) + elseif type(cases) ~= "table" then + record(name, false, "expected an array of { name, fn }, got " .. type(cases)) + else + for _, case in ipairs(cases) do + local ok, first, second = xpcall(case[2], traceback) + record(name .. ": " .. tostring(case[1]), ok, first, second) + end + end + end +end + +print(string.format("\n%d passed, %d skipped, %d failed", passed, skipped, failed)) +os.exit(failed == 0 and 0 or 1) -- cgit v1.3