Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pi/agents/developer.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: developer
description: Implements assigned tasks.
model: openrouter/stepfun/step-3.5-flash:free
model: openrouter/free
tools: read,edit,write,bash,grep,find,ls
---

Expand Down
2 changes: 1 addition & 1 deletion .pi/agents/pm.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: pm
description: Project manager who plans and delegates tasks in waves.
model: openrouter/stepfun/step-3.5-flash:free
model: openrouter/free
tools: read,grep,find,ls
---

Expand Down
2 changes: 1 addition & 1 deletion .pi/agents/verifier.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: verifier
description: Reviews developer work and validates requirements.
model: openrouter/stepfun/step-3.5-flash:free
model: openrouter/free
tools: read,grep,find,ls,bash
---

Expand Down
7 changes: 6 additions & 1 deletion .pi/extensions/workflow-orchestrator/agents.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { getAgentDir, parseFrontmatter } from "@mariozechner/pi-coding-agent";
import { getPackagePiRoot } from "./setup.js";

export type AgentSource = "user" | "project";
export type AgentSource = "user" | "project" | "package";

export interface AgentConfig {
name: string;
Expand Down Expand Up @@ -89,8 +90,12 @@ export function discoverAgents(cwd: string): AgentDiscoveryResult {
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
const userAgents = loadAgentsFromDir(userDir, "user");
const projectAgents = projectAgentsDir ? loadAgentsFromDir(projectAgentsDir, "project") : [];
const packageAgents = projectAgentsDir
? []
: loadAgentsFromDir(path.join(getPackagePiRoot(), "agents"), "package");

const agentMap = new Map<string, AgentConfig>();
for (const agent of packageAgents) agentMap.set(agent.name, agent);
for (const agent of userAgents) agentMap.set(agent.name, agent);
for (const agent of projectAgents) agentMap.set(agent.name, agent);

Expand Down
127 changes: 127 additions & 0 deletions .pi/extensions/workflow-orchestrator/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { DEFAULT_WORKFLOW_NAME } from "./setup.js";
import { normalizeGoal } from "./utils.js";

export const WORKFLOW_COMMANDS = new Set([
"start",
"resume",
"status",
"stop",
"stop-task",
"message",
"expand",
"collapse",
"help",
]);

export interface ParsedWorkflowStart {
workflowName: string;
goal?: string;
model?: string;
}

/** Tokenize command arguments while preserving quoted phrases as one token. */
export function tokenizeWorkflowArgs(args: string): string[] {
const tokens: string[] = [];
let token = "";
let quote: '"' | "'" | undefined;
let escaped = false;

const pushToken = () => {
if (token) tokens.push(token);
token = "";
};

for (const char of args) {
if (escaped) {
token += char;
escaped = false;
continue;
}

if (char === "\\" && quote !== "'") {
escaped = true;
continue;
}

if (quote) {
if (char === quote) quote = undefined;
else token += char;
continue;
}

if (char === '"' || (char === "'" && token.length === 0)) {
quote = char;
continue;
}

if (/\s/.test(char)) pushToken();
else token += char;
}

if (escaped) token += "\\";
pushToken();
return tokens;
}

export function isValidWorkflowName(name: string): boolean {
return /^[a-zA-Z0-9_-]+$/.test(name) && name.length > 0 && name.length <= 50;
}

export function extractModelFlag(tokens: string[]): { tokens: string[]; model?: string } {
const stripped: string[] = [];
let model: string | undefined;

for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
if (token === "--model" && i + 1 < tokens.length) {
model = normalizeGoal(tokens[++i]);
continue;
}
if (token === "--model") continue;
stripped.push(token);
}

return { tokens: stripped, model };
}

export function parseWorkflowStartArgs(tokens: string[]): ParsedWorkflowStart | null {
const { tokens: rest, model } = extractModelFlag(tokens.slice(1));
if (rest.length === 0) return null;

if (rest.length === 1) {
const token = rest[0];
if (isValidWorkflowName(token)) {
return { workflowName: token, goal: undefined, model };
}
return {
workflowName: DEFAULT_WORKFLOW_NAME,
goal: normalizeGoal(token),
model,
};
}

if (!isValidWorkflowName(rest[0])) {
return {
workflowName: DEFAULT_WORKFLOW_NAME,
goal: normalizeGoal(rest.join(" ")),
model,
};
}

return {
workflowName: rest[0],
goal: normalizeGoal(rest.slice(1).join(" ")),
model,
};
}

export function parseWorkflowShorthandGoal(tokens: string[]): {
goal?: string;
model?: string;
} {
const { tokens: stripped, model } = extractModelFlag(tokens);
return {
goal: normalizeGoal(stripped.join(" ")),
model,
};
}
7 changes: 2 additions & 5 deletions .pi/extensions/workflow-orchestrator/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { Type, type Static } from "@sinclair/typebox";
import { Value } from "@sinclair/typebox/value";
import { resolveWorkflowPath } from "./setup.js";

const TransitionSchema = Type.Object({
when: Type.Object({
Expand Down Expand Up @@ -110,10 +110,7 @@ function sanitizeWorkflowName(name: string): string {

export function loadWorkflowConfig(cwd: string, name: string): LoadedWorkflow {
const safeName = sanitizeWorkflowName(name);
const workflowPath = path.join(cwd, ".pi", "workflows", `${safeName}.workflow.json`);
if (!fs.existsSync(workflowPath)) {
throw new Error(`Workflow not found: ${workflowPath}`);
}
const workflowPath = resolveWorkflowPath(cwd, safeName);

const raw = fs.readFileSync(workflowPath, "utf-8");
let parsed: unknown;
Expand Down
Loading
Loading