Skip to content
Closed
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
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copy to .env and fill in values. Loaded from the project root (walks up from cwd)
# and passed to spawned PM/dev/verifier subagents.
# Alternatively use `/login` → OpenRouter; credentials are stored in ~/.pi/agent/auth.json.

# Required for OpenRouter models (see .pi/agents/*.md)
OPENROUTER_API_KEY=

# Optional: integration test harness (tests/harness/pm-memory.mjs)
# PI_TEST_MODEL=anthropic/claude-sonnet-4-5
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
node_modules
.DS_Store

# Environment / secrets
.env
.env.*
!.env.example
.pi/workflows/sessions/
.pnpm-store
npm-debug.log
bun.lock

# Coverage
coverage/
Expand Down
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/stepfun/step-3.5-flash
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/stepfun/step-3.5-flash
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/stepfun/step-3.5-flash
tools: read,grep,find,ls,bash
---

Expand Down
21 changes: 13 additions & 8 deletions .pi/extensions/workflow-orchestrator/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ export interface TaskFlowInput<TTask, TOutput> {
output: TOutput | null,
error?: string,
reason?: "verification_failed" | "malformed_output" | "error",
) => boolean;
applyGenericFailure: (task: TTask, error: string) => boolean;
) => void;
applyGenericFailure: (task: TTask, error: string) => void;
}

function defaultGetField(obj: any, path: string): any {
Expand All @@ -56,6 +56,11 @@ export async function runTaskFlow<TTask extends { retries: number }, TOutput>(
const getNextStageId = input.getNextStageId ?? defaultGetNextStageId;
let currentStageId = input.startStageId ?? input.stages[0]?.id;

function recordFailure(): boolean {
input.task.retries += 1;
return input.task.retries <= input.maxRetries;
}

while (currentStageId) {
if (input.isStopped?.(input.task)) return;

Expand All @@ -74,17 +79,17 @@ export async function runTaskFlow<TTask extends { retries: number }, TOutput>(
input.onError?.(stage, input.task, error instanceof Error ? error : new Error(message));

if (stage.id === "verify") {
const retry = input.applyVerifyFailure(input.task, stage.id, null, message, "error");
if (!retry) {
input.applyVerifyFailure(input.task, stage.id, null, message, "error");
if (!recordFailure()) {
input.markFailed(input.task, stage.id);
return;
}
currentStageId = input.stages[0]?.id;
continue;
}

const retry = input.applyGenericFailure(input.task, message);
if (!retry) {
input.applyGenericFailure(input.task, message);
if (!recordFailure()) {
input.markFailed(input.task, stage.id);
return;
}
Expand Down Expand Up @@ -123,8 +128,8 @@ export async function runTaskFlow<TTask extends { retries: number }, TOutput>(
if ((nextStageId === firstStageId || nextStageId === stage.id) && stage.id === "verify") {
const reason =
nextStageId === stage.id && !matchedTransition ? "malformed_output" : "verification_failed";
const retry = input.applyVerifyFailure(input.task, stage.id, output, undefined, reason);
if (!retry) {
input.applyVerifyFailure(input.task, stage.id, output, undefined, reason);
if (!recordFailure()) {
input.markFailed(input.task, stage.id);
return;
}
Expand Down
47 changes: 47 additions & 0 deletions .pi/extensions/workflow-orchestrator/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { config as loadDotenv } from "dotenv";
import * as fs from "node:fs";
import * as path from "node:path";

/** Walk upward from cwd to find the pi project root (has .pi/agents or pi package.json). */
export function findProjectRoot(startDir: string): string | null {
let current = path.resolve(startDir);
while (true) {
if (fs.existsSync(path.join(current, ".pi", "agents"))) return current;

const pkgPath = path.join(current, "package.json");
if (fs.existsSync(pkgPath)) {
try {
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")) as {
pi?: { extensions?: unknown };
};
if (pkg.pi?.extensions) return current;
} catch {
/* ignore malformed package.json */
}
}

const parent = path.dirname(current);
if (parent === current) return null;
current = parent;
}
}

const loadedRoots = new Set<string>();

/** Load `.env` from the project root into `process.env` (does not override existing vars). */
export function loadProjectEnv(cwd: string): void {
const root = findProjectRoot(cwd) ?? path.resolve(cwd);
if (loadedRoots.has(root)) return;

const envPath = path.join(root, ".env");
if (fs.existsSync(envPath)) {
loadDotenv({ path: envPath });
}
loadedRoots.add(root);
}

/** Environment for spawned `pi` subagents: parent env plus project `.env`. */
export function getSubagentEnv(cwd: string): NodeJS.ProcessEnv {

Check failure on line 44 in .pi/extensions/workflow-orchestrator/env.ts

View workflow job for this annotation

GitHub Actions / lint

'NodeJS' is not defined
loadProjectEnv(cwd);
return { ...process.env };
}
Loading
Loading