-- init.lua: the extension entry point pantograph evaluates and activates. -- -- Activation is the whole contract with the host: the wrong shape, a missing -- tool, a description that does not name the discovered profiles, or a missing -- lifecycle subscription is invisible until a user notices the tools are gone or -- a cancelled turn leaves children running. Discovery is pointed at a temporary -- config layer, so the assertions do not depend on this machine's ~/.config; the -- first case skips when luv or lyaml is missing because the profile it looks for -- could not be read without them. local fake = require("spec.fake_ext") local jobs = require("subagents.jobs") local paths = require("subagents.paths") local entry = require("init") local function has(text, needle) assert(type(text) == "string", "expected a string, got " .. type(text)) assert(text:find(needle, 1, true), "expected to find " .. needle .. " in:\n" .. tostring(text)) end -- Activate against the fake host with discovery pointed at nothing, for the -- cases that care about registration rather than profiles. Profile discovery is -- cached in subagents.spawn, so a case that ran earlier may have filled it. local function activate_bare(fn, opts) local original = paths.config_roots paths.config_roots = function() return {} end local handle = fake.install(opts) local ok, err = pcall(function() if opts and opts.before then opts.before(handle) end entry.activate() end) paths.config_roots = original local result = table.pack(pcall(fn, handle, ok, err)) handle.restore() if not result[1] then error(result[2], 0) end end return { { "the entry is the extension shape pantograph expects", function() assert(entry.name == "subagents", tostring(entry.name)) assert(type(entry.activate) == "function", "activate must be a function") end }, { "activation registers the four tools and names the discovered profiles", function() local ok_uv, uv = pcall(require, "luv") if not ok_uv then return "skip", "luv is not installed" end if not pcall(require, "lyaml") then return "skip", "lyaml is not installed" end local tmp = assert(uv.fs_mkdtemp("/tmp/panto-subagents-init-XXXXXX")) assert(os.execute("mkdir -p " .. tmp .. "/agents")) local file = assert(io.open(tmp .. "/agents/reviewer.md", "w")) file:write("---\ndescription: Reviews changes\n---\nYou are a reviewer.\n") file:close() local original_roots = paths.config_roots paths.config_roots = function(kind) return { tmp .. "/" .. kind } end local handle = fake.install() local ok, err = pcall(entry.activate) paths.config_roots = original_roots handle.restore() os.execute("rm -rf " .. tmp) assert(ok, tostring(err)) for _, name in ipairs({ "subagents.run", "subagents.models", "subagents.lua", "subagents.workflow" }) do assert(handle.tools_by_name[name], "missing tool " .. name) end assert(#handle.tools == 4, "expected exactly four tools, saw " .. #handle.tools) local run_tool = handle.tools_by_name["subagents.run"] has(run_tool.description, "reviewer — Reviews changes") has(run_tool.description, "exactly one of `agent`") has(run_tool.description, "subagents.models") has(run_tool.description, "one tool batch") assert(run_tool.schema.required[1] == "prompt", "prompt is the only required field") assert(run_tool.schema.properties.agent and run_tool.schema.properties.id) assert(type(run_tool.handler) == "function") assert(handle.tools_by_name["subagents.lua"].schema.properties.source, "the lua tool takes source") assert(handle.tools_by_name["subagents.workflow"].schema.properties.steps.items.required, "the workflow tool describes its step shape") end }, { "an interrupted turn cancels every live child, and its end closes them", function() activate_bare(function(handle, ok, err) assert(ok, tostring(err)) assert(type(handle.on_by_name["turn_interrupt"]) == "function", "an interrupted turn must be able to cancel its children") assert(type(handle.on_by_name["turn_end"]) == "function", "a finished turn must be able to close its children") -- A child that would not settle on its own, so the lifecycle is the -- only thing that can end it. close_all runs whatever happens, or a -- leaked handle would count against the next case's gate. local job = fake.job({ settle = 99 }) local started = assert(jobs.start({ label = "alpha", build = function() return job end })) local checked, failure = pcall(function() assert(started:result() == nil, "the child is still running") handle.emit("turn_interrupt", { phase = "interrupt" }) assert(job._cancel_requested, "an interrupted turn asks its children to stop") handle.emit("turn_end", { phase = "end", reason = "interrupted" }) assert(job._closed, "the end of the turn joins every child") end) jobs.close_all() assert(checked, failure) end) end }, { "only the subagents tool calls claim a progress component", function() activate_bare(function(handle, ok, err) assert(ok, tostring(err)) assert(type(handle.on_by_name["tool_call_complete"]) == "function", "children report progress through the tool call that started them") local claimed handle.emit("tool_call_complete", { id = "call-1", tool_name = "subagents.run", set_component = function(_, component) claimed = component return { invalidate = function() end, alive = function() return true end } end, }) assert(type(claimed) == "table" and type(claimed.render) == "function", "the entry is given a component that renders the cards") local foreign handle.emit("tool_call_complete", { id = "call-2", tool_name = "read_file", set_component = function(_, component) foreign = component end, }) assert(foreign == nil, "another tool's entry is left alone") end) end }, { "a host without resolve_model fails activation loudly", function() activate_bare(function(handle, ok, err) assert(not ok, "activation must not silently register unusable tools") has(tostring(err), "too old") assert(#handle.tools == 0, "nothing is registered by a failed activation") end, { before = function(handle) handle.ext.resolve_model = nil end, }) end }, { "a host without panto.agent fails activation loudly", function() activate_bare(function(handle, ok, err) assert(not ok, "a host that cannot build a child agent is too old") has(tostring(err), "too old") assert(#handle.tools == 0, "nothing is registered by a failed activation") end, { before = function() package.loaded.panto.agent = nil end, }) end }, }