summaryrefslogtreecommitdiff
path: root/spec/test_toml_workflows.lua
diff options
context:
space:
mode:
Diffstat (limited to 'spec/test_toml_workflows.lua')
-rw-r--r--spec/test_toml_workflows.lua161
1 files changed, 112 insertions, 49 deletions
diff --git a/spec/test_toml_workflows.lua b/spec/test_toml_workflows.lua
index 4755b8e..4ec4803 100644
--- a/spec/test_toml_workflows.lua
+++ b/spec/test_toml_workflows.lua
@@ -91,6 +91,20 @@ return {
assert(def.by_id.b.needs[1] == "a")
end },
+ { "`output` chooses the reported steps, in its own order", function()
+ local def = assert(toml_workflows.validate({
+ name = "branch",
+ steps = BRANCH_DEF.steps,
+ output = { "c", "a" },
+ }, "fallback"))
+ assert(def.report[1] == "c" and def.report[2] == "a" and #def.report == 2,
+ "the file's order wins: " .. table.concat(def.report, ","))
+
+ local default = assert(toml_workflows.validate(BRANCH_DEF, "fallback"))
+ assert(default.report[1] == "b" and default.report[2] == "c" and #default.report == 2,
+ "without `output`, terminal steps in declaration order")
+ end },
+
{ "the name falls back to the file stem", function()
local def = assert(toml_workflows.validate({ steps = BRANCH_DEF.steps }, "from-stem"))
assert(def.name == "from-stem", tostring(def.name))
@@ -108,6 +122,10 @@ return {
bad({ steps = { { id = "a", agent = "alpha" } } }, "`prompt` is required")
bad({ steps = { { id = "a", prompt = "p" } } }, "`agent` is required")
bad({ steps = { { agent = "alpha", prompt = "p" } } }, "`id` is required")
+ bad({ steps = BRANCH_DEF.steps, output = {} }, "`output` must be a non-empty array")
+ bad({ steps = BRANCH_DEF.steps, output = "b" }, "`output` must be a non-empty array")
+ bad({ steps = BRANCH_DEF.steps, output = { "ghost" } }, "`output` names unknown step 'ghost'")
+ bad({ steps = BRANCH_DEF.steps, output = { "b", "b" } }, "`output` names 'b' twice")
bad({ steps = {
{ id = "a", agent = "alpha", prompt = "p" },
{ id = "a", agent = "beta", prompt = "q" },
@@ -143,6 +161,11 @@ return {
"",
"[failed: boom]",
}, "\n"), string.format("unexpected prompt:\n%s", prompt))
+
+ -- An unparameterized workflow gets no input heading at all.
+ local bare = toml_workflows.step_prompt(
+ { id = "a", agent = "alpha", prompt = "Do it.", needs = {} }, "", {})
+ assert(bare == "Do it.", string.format("unexpected bare prompt:\n%s", bare))
end },
{ "a dependent receives its dependency's output and runs after it", function()
@@ -167,6 +190,26 @@ return {
end)
end },
+ { "`output` reports an intermediate step instead of the terminal one", function()
+ with_host(function(handle, profiles)
+ local def = assert(toml_workflows.validate({
+ name = "chain",
+ steps = {
+ { id = "a", agent = "alpha", prompt = "Inspect." },
+ { id = "b", agent = "beta", prompt = "Summarize.", needs = { "a" } },
+ },
+ output = { "a" },
+ }, "chain"))
+ handle.queue_for("alpha", { output = "A output" })
+ handle.queue_for("beta", { output = "B output" })
+
+ local results = toml_workflows.run(def, "the input", profiles)
+ assert(#handle.spawns == 2, "both steps still run")
+ assert(#results == 1 and results[1].id == "a", "only the named step is reported")
+ assert(results[1].output == "A output")
+ end)
+ end },
+
{ "a failed step skips its dependents while other branches finish", function()
with_host(function(handle, profiles)
local def = assert(toml_workflows.validate(BRANCH_DEF, "branch"))
@@ -319,6 +362,29 @@ return {
end)
end },
+ { "a workflow naming an undiscovered agent is rejected at discovery", function()
+ return with_layers({
+ ["project/workflows/ghosted.toml"] = table.concat({
+ '[[steps]]',
+ 'id = "one"',
+ 'agent = "nobody"',
+ 'prompt = "Do it."',
+ }, "\n"),
+ }, function()
+ with_host(function(handle, profiles)
+ local found = toml_workflows.discover_and_register(profiles)
+ local entry = found.by_name["ghosted"]
+ assert(entry and entry.definition == nil, "the definition must not survive")
+ has(entry.error, "unknown agent 'nobody'")
+ assert(#handle.commands == 0, "no command is registered for it")
+ has(table.concat(found.warnings, " | "), "unknown agent 'nobody'")
+ has(toml_workflows.handle({ name = "ghosted", prompt = "x" }, profiles),
+ "unknown agent 'nobody'")
+ assert(#handle.spawns == 0, "nothing ran before the failure")
+ end)
+ end)
+ end },
+
{ "a broken project workflow shadows the user one under its declared name", function()
local user_valid = table.concat({
'name = "dup"',
@@ -367,65 +433,62 @@ return {
local ok, err = pcall(toml_workflows.run, def, "input", profiles)
assert(not ok, "an unknown agent stops the workflow")
has(tostring(err), "unknown agent 'nobody'")
-
- has(toml_workflows.handle({
- prompt = "x",
- steps = { { id = "one", agent = "nobody", prompt = "Do it." } },
- }, profiles), "Error:")
assert(#handle.spawns == 0)
end)
end },
- { "the tool takes exactly one of name and steps", function()
+ { "the tool requires both a name and a prompt", function()
with_host(function(handle, profiles)
- has(toml_workflows.handle({ prompt = "x" }, profiles), "pass exactly one of `name`")
- has(toml_workflows.handle({ prompt = "x", name = "a", steps = {} }, profiles), "not both")
+ has(toml_workflows.handle({ prompt = "x" }, profiles), "`name` is required")
has(toml_workflows.handle({ name = "a" }, profiles), "prompt is required")
assert(#handle.spawns == 0)
end)
end },
- { "the tool runs a transient definition and presents each child prompt only when expanded", function()
- with_host(function(handle, profiles)
- handle.queue_for("alpha", { output = "transient output" })
- progress.reset()
- local component
- progress.claim({
- id = "workflow-call",
- tool_name = "subagents.workflow",
- collapsed = true,
- set_component = function(_, value)
- component = value
- return {
- invalidate = function() end,
- alive = function() return true end,
- set_pinned = function() end,
- }
- end,
- })
- progress.bind({ tool_call_id = "workflow-call" })
- local text = toml_workflows.handle({
- prompt = "the input",
- steps = { { id = "only", agent = "alpha", prompt = "Do it." } },
- }, profiles)
- has(text, "step: only")
- has(text, "transient output")
- has(handle.spawns[1].prompt, "Do it.\n\n## Workflow input\n\nthe input")
- local compact = table.concat(component:render(100), "\n")
- assert(not compact:find("Do it.", 1, true), compact)
- assert(not compact:find("the input", 1, true), compact)
- progress.collapse({ collapsed = false })
- local expanded = table.concat(component:render(100), "\n")
- has(expanded, "prompt: Do it.")
- has(expanded, "## Workflow input")
- has(expanded, "the input")
- assert(not expanded:find("system prompt:", 1, true), expanded)
-
- has(toml_workflows.handle({
- prompt = "x",
- steps = { { id = "only", agent = "alpha", prompt = "p", needs = { "ghost" } } },
- }, profiles), "unknown dependency 'ghost'")
- progress.reset()
+ { "the tool runs a named workflow and presents each child prompt only when expanded", function()
+ return with_layers({
+ ["project/workflows/solo.toml"] = table.concat({
+ 'name = "solo"',
+ '[[steps]]',
+ 'id = "only"',
+ 'agent = "alpha"',
+ 'prompt = "Do it."',
+ }, "\n"),
+ }, function()
+ with_host(function(handle, profiles)
+ toml_workflows.discover_and_register(profiles)
+ handle.queue_for("alpha", { output = "named output" })
+ progress.reset()
+ local component
+ progress.claim({
+ id = "workflow-call",
+ tool_name = "subagents.workflow",
+ collapsed = true,
+ set_component = function(_, value)
+ component = value
+ return {
+ invalidate = function() end,
+ alive = function() return true end,
+ set_pinned = function() end,
+ }
+ end,
+ })
+ progress.bind({ tool_call_id = "workflow-call" })
+ local text = toml_workflows.handle({ name = "solo", prompt = "the input" }, profiles)
+ has(text, "step: only")
+ has(text, "named output")
+ has(handle.spawns[1].prompt, "Do it.\n\n## Workflow input\n\nthe input")
+ local compact = table.concat(component:render(100), "\n")
+ assert(not compact:find("Do it.", 1, true), compact)
+ assert(not compact:find("the input", 1, true), compact)
+ progress.collapse({ collapsed = false })
+ local expanded = table.concat(component:render(100), "\n")
+ has(expanded, "prompt: Do it.")
+ has(expanded, "## Workflow input")
+ has(expanded, "the input")
+ assert(not expanded:find("system prompt:", 1, true), expanded)
+ progress.reset()
+ end)
end)
end },
}