summaryrefslogtreecommitdiff
path: root/spec/run.lua
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-08-16 20:42:43 -0600
committert <t@tjp.lol>2026-08-17 20:31:29 -0600
commit4f0a91ef55fe96835172bdad34feec1e2a0a0977 (patch)
treeed4f3e86575aa6243043bc22f8037be153a609c6 /spec/run.lua
parentc1ab34754d3f3695fafd344fe1a181ecf0740761 (diff)
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.
Diffstat (limited to 'spec/run.lua')
-rw-r--r--spec/run.lua81
1 files changed, 81 insertions, 0 deletions
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)