pi-taskflow 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extensions/index.ts +18 -3
- package/extensions/runtime.ts +26 -4
- package/package.json +1 -1
package/extensions/index.ts
CHANGED
|
@@ -50,8 +50,8 @@ const ShorthandStep = Type.Object(
|
|
|
50
50
|
);
|
|
51
51
|
|
|
52
52
|
const TaskflowParams = Type.Object({
|
|
53
|
-
action: StringEnum(["run", "save", "resume", "list"] as const, {
|
|
54
|
-
description: "What to do: run a flow, save a definition, resume a paused run, or list
|
|
53
|
+
action: StringEnum(["run", "save", "resume", "list", "agents"] as const, {
|
|
54
|
+
description: "What to do: run a flow, save a definition, resume a paused run, list saved flows, or list available agents you can use in phases",
|
|
55
55
|
default: "run",
|
|
56
56
|
}),
|
|
57
57
|
name: Type.Optional(Type.String({ description: "Name of a saved flow (for run/save without inline define)" })),
|
|
@@ -219,7 +219,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
219
219
|
"Phases (agent, parallel, map, gate, reduce, approval, flow) form a DAG; intermediate outputs stay out of your context — only the final phase output is returned.",
|
|
220
220
|
"Use action=run with an inline `define` (you write the DSL) or a saved `name`.",
|
|
221
221
|
"For simple non-DAG delegations (like the subagent tool) skip the DSL: pass `task` (+optional `agent`) for one task, `tasks:[{task,agent?}]` to run in parallel, or `chain:[{task,agent?}]` to run sequentially (reference the prior step with {previous.output}).",
|
|
222
|
-
"Use action=save to persist a definition as a reusable /tf:<name> command. action=resume continues a paused run. action=list shows saved flows.",
|
|
222
|
+
"Use action=save to persist a definition as a reusable /tf:<name> command. action=resume continues a paused run. action=list shows saved flows. Use action=agents to list available agents — do NOT invent agent names; either use an agent from that list or omit the 'agent' field to auto-select the default agent.",
|
|
223
223
|
"DSL: {name, args?, concurrency?, budget?:{maxUSD,maxTokens}, phases:[{id, type, agent, task, dependsOn?, join?:'all'|'any', when?, retry?:{max,backoffMs,factor}, over?(map), as?(map), branches?(parallel), from?(reduce), use?(flow), with?(flow), output?:'json', final?}]}.",
|
|
224
224
|
"Phase types: agent (one subagent), parallel (static branches), map (dynamic fan-out over an array), gate (VERDICT: PASS/BLOCK quality gate), reduce (aggregate from N phases), approval (human-in-the-loop pause), flow (run a saved sub-flow). join:'any' is an OR-join; when is a conditional guard; retry adds backoff; budget caps run cost.",
|
|
225
225
|
"Interpolation: {args.X}, {steps.ID.output}, {steps.ID.json}, {item} (map), {previous.output}.",
|
|
@@ -235,6 +235,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
235
235
|
async execute(_id, params, signal, onUpdate, ctx) {
|
|
236
236
|
const action = params.action ?? "run";
|
|
237
237
|
|
|
238
|
+
// agents — list available agents the LLM can use in phase definitions
|
|
239
|
+
if (action === "agents") {
|
|
240
|
+
const scope = params.scope ?? "both";
|
|
241
|
+
const { agents } = discoverAgents(ctx.cwd, scope as AgentScope, undefined);
|
|
242
|
+
const text = agents.length
|
|
243
|
+
? agents
|
|
244
|
+
.map(
|
|
245
|
+
(a) =>
|
|
246
|
+
`- ${a.name} (${a.source}): ${a.description}${a.model ? ` [model: ${a.model}]` : ""}${a.tools?.length ? ` [tools: ${a.tools.join(", ")}]` : ""}`,
|
|
247
|
+
)
|
|
248
|
+
.join("\n")
|
|
249
|
+
: "No agents found. Use the default agent by omitting the 'agent' field in phases.";
|
|
250
|
+
return { content: [{ type: "text", text }], details: { action } satisfies TaskflowDetails };
|
|
251
|
+
}
|
|
252
|
+
|
|
238
253
|
// list
|
|
239
254
|
if (action === "list") {
|
|
240
255
|
const flows = listFlows(ctx.cwd);
|
package/extensions/runtime.ts
CHANGED
|
@@ -414,11 +414,12 @@ async function executePhase(
|
|
|
414
414
|
if (type === "agent" || type === "gate" || type === "reduce") {
|
|
415
415
|
const { text } = interpolate(phase.task ?? "", ctx);
|
|
416
416
|
const fullTask = preRead + text;
|
|
417
|
-
const
|
|
417
|
+
const agentName = resolveAgent(phase.agent, deps, state);
|
|
418
|
+
const inputHash = hashInput(phase.id, agentName, fullTask);
|
|
418
419
|
const cached = cachedPhase(prior, inputHash);
|
|
419
420
|
if (cached) return cached;
|
|
420
421
|
|
|
421
|
-
const r = await runOne(
|
|
422
|
+
const r = await runOne(agentName, fullTask, liveSink(state, phase.id, emitProgress));
|
|
422
423
|
const ps = resultToPhaseState(phase.id, r, inputHash, parseJson);
|
|
423
424
|
if (type === "gate" && ps.status === "done") ps.gate = parseGateVerdict(r.output);
|
|
424
425
|
return ps;
|
|
@@ -428,7 +429,7 @@ async function executePhase(
|
|
|
428
429
|
const branches = (phase.branches ?? []).map((b) => {
|
|
429
430
|
const r = interpolate(b.task, ctx);
|
|
430
431
|
return {
|
|
431
|
-
agent: b.agent ?? phase.agent
|
|
432
|
+
agent: resolveAgent(b.agent ?? phase.agent, deps, state),
|
|
432
433
|
task: preRead + r.text,
|
|
433
434
|
};
|
|
434
435
|
});
|
|
@@ -458,7 +459,7 @@ async function executePhase(
|
|
|
458
459
|
const tasks = arr.map((item) => {
|
|
459
460
|
const localCtx = buildInterpolationContext(state, previousOutput, { [loopVar]: item });
|
|
460
461
|
return {
|
|
461
|
-
agent: phase.agent
|
|
462
|
+
agent: resolveAgent(phase.agent, deps, state),
|
|
462
463
|
task: preRead + interpolate(phase.task ?? "", localCtx).text,
|
|
463
464
|
};
|
|
464
465
|
});
|
|
@@ -641,6 +642,27 @@ function cachedPhase(prior: PhaseState | undefined, inputHash: string): PhaseSta
|
|
|
641
642
|
return null;
|
|
642
643
|
}
|
|
643
644
|
|
|
645
|
+
/**
|
|
646
|
+
* Resolve an agent name against available agents. Falls back to the default
|
|
647
|
+
* agent if the requested agent isn't found, logging a warning via safeEmit.
|
|
648
|
+
*/
|
|
649
|
+
function resolveAgent(name: string | undefined, deps: RuntimeDeps, state: RunState): string {
|
|
650
|
+
const resolved = name ?? defaultAgent(deps);
|
|
651
|
+
if (name && !deps.agents.some((a) => a.name === name)) {
|
|
652
|
+
const fallback = defaultAgent(deps);
|
|
653
|
+
// Log only once per run to avoid noise.
|
|
654
|
+
if (!(state as any).__unknownAgentWarned) {
|
|
655
|
+
(state as any).__unknownAgentWarned = new Set<string>();
|
|
656
|
+
}
|
|
657
|
+
if (!(state as any).__unknownAgentWarned.has(name)) {
|
|
658
|
+
(state as any).__unknownAgentWarned.add(name);
|
|
659
|
+
console.warn(`[taskflow] Unknown agent "${name}", falling back to "${fallback}". Use action=agents to list available agents.`);
|
|
660
|
+
}
|
|
661
|
+
return fallback;
|
|
662
|
+
}
|
|
663
|
+
return resolved;
|
|
664
|
+
}
|
|
665
|
+
|
|
644
666
|
function defaultAgent(deps: RuntimeDeps): string {
|
|
645
667
|
return deps.agents[0]?.name ?? "default";
|
|
646
668
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-taskflow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Lightweight workflow orchestration for the Pi coding agent — declarative multi-phase taskflows with dynamic fan-out, isolated subagent context, resumable runs, and saveable commands.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|