From 57d238a1ea60cd93e23cebcfafaba38ee6f3de96 Mon Sep 17 00:00:00 2001 From: Willem van den Ende Date: Mon, 20 Apr 2026 23:04:34 +0100 Subject: [PATCH] feat: restructure as pnpm monorepo workspace - Move limit-turns.ts into packages/pi-turn-limit/src/index.ts - Add root package.json (private workspace manifest) - Add pnpm-workspace.yaml - Add shared tsconfig.json - Add pi package manifest to extension (pi.extensions, pi-package keyword) - Update AGENTS.md with monorepo workflow --- .gitignore | 3 ++ AGENTS.md | 32 +++++++++++++++++ package.json | 11 ++++++ packages/pi-turn-limit/package.json | 17 +++++++++ packages/pi-turn-limit/src/index.ts | 56 +++++++++++++++++++++++++++++ pnpm-workspace.yaml | 2 ++ tsconfig.json | 18 ++++++++++ 7 files changed, 139 insertions(+) create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 package.json create mode 100644 packages/pi-turn-limit/package.json create mode 100644 packages/pi-turn-limit/src/index.ts create mode 100644 pnpm-workspace.yaml create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d6ab289 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.pnpm-store/ +pnpm-lock.yaml diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b3059bc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +This is a monorepo for pi extensions using pnpm workspaces. + +## Structure + +``` +packages/ + pi-turn-limit/ # First extension: limits the number of turns +``` + +## Development + +```bash +pnpm install # Install all workspace dependencies +``` + +Each extension lives in `packages//` with its own `package.json`. +The root `package.json` is the workspace manifest. + +## Adding a new extension + +1. Create `packages//` directory +2. Add `package.json` with `"pi-package"` keyword and `pi.extensions` manifest +3. Put source in `packages//src/index.ts` +4. Run `pnpm install` + +## Extension packaging + +Extensions are loaded by pi as TypeScript files via jiti (no compilation needed). +The `pi` key in `package.json` tells pi which files to load. + +See `~/.local/share/mise/installs/node/24.0.1/lib/node_modules/@mariozechner/pi-coding-agent/docs/extensions.md` +and `packages.md` for full details. diff --git a/package.json b/package.json new file mode 100644 index 0000000..3789733 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "pi-extensions", + "version": "0.1.0", + "private": true, + "description": "Pi coding agent extensions monorepo", + "type": "module", + "license": "MIT", + "engines": { + "node": ">=24" + } +} diff --git a/packages/pi-turn-limit/package.json b/packages/pi-turn-limit/package.json new file mode 100644 index 0000000..d2553b6 --- /dev/null +++ b/packages/pi-turn-limit/package.json @@ -0,0 +1,17 @@ +{ + "name": "pi-turn-limit", + "version": "0.1.0", + "description": "Pi coding agent extension to limit number of turns taken by a model", + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "keywords": ["pi-package"], + "pi": { + "extensions": ["src/index.ts"] + }, + "peerDependencies": { + "@mariozechner/pi-coding-agent": "*" + }, + "license": "MIT" +} diff --git a/packages/pi-turn-limit/src/index.ts b/packages/pi-turn-limit/src/index.ts new file mode 100644 index 0000000..e32584c --- /dev/null +++ b/packages/pi-turn-limit/src/index.ts @@ -0,0 +1,56 @@ +import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; + +// ============================================================================ +// Configuration +// ============================================================================ + +const DEFAULT_MAX_TURNS = 25; + +function getMaxTurns(): number { + const env = process.env.PI_MAX_TURNS; + if (!env) return DEFAULT_MAX_TURNS; + const parsed = parseInt(env, 10); + return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_TURNS; +} + +// ============================================================================ +// Pure detection logic (testable) +// ============================================================================ + +export function checkTurnLimit( + turnIndex: number, + maxTurns: number, +): { exceeded: boolean; turnIndex: number; maxTurns: number } { + return { + exceeded: turnIndex > maxTurns, + turnIndex, + maxTurns, + }; +} + +// ============================================================================ +// Extension handler +// ============================================================================ + +export default function (pi: ExtensionAPI) { + let turnCount = 0; + const maxTurns = getMaxTurns(); + + pi.on("agent_start", async (_event, ctx) => { + // Reset counter for each new user prompt + turnCount = 0; + }); + + pi.on("turn_start", async (event, ctx) => { + turnCount++; + + const { exceeded } = checkTurnLimit(event.turnIndex, maxTurns); + if (exceeded && ctx.hasUI) { + ctx.ui.notify( + `Turn limit exceeded: ${maxTurns} turns reached. Agent aborted.`, + "error", + ); + ctx.abort(); + } + }); +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..dee51e9 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - "packages/*" diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f92d86d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["packages/**/*"], + "exclude": ["node_modules"] +}