6.3 KiB
6.3 KiB
Turn Limit Extension — Widget & Confirmation Plan
Goal
Extend the pi-turn-limit extension with:
- A
/turn-limit <N>command to change the max turns mid-session - A live widget showing
Turns: {current}/{max} - A yes/no confirmation dialog when the turn limit boundary is reached (instead of immediate abort)
Files to Change
| File | Change |
|---|---|
packages/pi-turn-limit/src/turn-limit.ts |
Only file to modify. Add command, widget, and confirmation logic. |
No other files need changes. No new files, no dependency updates, no config changes.
Design Details
Current behavior (preserve these)
DEFAULT_MAX_TURNS = 25getMaxTurns()readsPI_MAX_TURNSenv var, falls back to defaultcheckTurnLimit()pure function — do not modifyagent_startresetsturnCount = 0turn_startincrements counter, checks limit, aborts if exceededctx.hasUIguard before UI calls
New behavior
A. /turn-limit <N> command
Register via pi.registerCommand().
- Name:
turn-limit - Description:
Set the maximum number of agent turns for this session - Handler:
- Parse
argsas integer - If invalid (not a positive integer): show error via
ctx.ui.notify("Invalid turn limit. Must be a positive integer.", "error")and return - Update the in-memory
maxTurnsvariable - Call
ctx.ui.setWidget()to update the widget display immediately - Optionally notify:
ctx.ui.notify("Turn limit set to {N}.", "info")
- Parse
B. Live widget
- Widget name:
"turn-limit"(unique string identifier) - Content:
["Turns: {current}/{max}"]— single line array - Placement: default (above editor)
- Update timing:
- Set on every
turn_start(after incrementing turnCount) - Clear on
agent_end(new user prompt starts)
- Set on every
- Example output:
Turns: 12/25
C. Confirmation dialog at boundary
- Trigger condition:
turnIndex === maxTurns(the boundary turn, NOT> maxTurns) - Guard: Only if
ctx.hasUIis true (print/JSON mode aborts silently) - Dialog:
Title: "Turn limit reached" Message: `You've used ${maxTurns} turns. Continue?` - On "Yes" (confirmed):
- Reset
turnCount = 0 - Do NOT abort — let the turn proceed
- Reset
- On "No" (not confirmed):
- Call
ctx.ui.notify("Agent aborted by user.", "error") - Call
ctx.abort()
- Call
- After confirmation: Update the widget with the reset counter (
Turns: 0/{max})
D. Exit condition
The agent still aborts when the user has said "No" to every boundary confirmation. Since the counter resets on "Yes", the user can re-approve multiple times. The only way out is "No" or PI_MAX_TURNS = 0 (edge case).
Implementation Checklist
-
Step 1: Add
/turn-limitcommand registration- Use
pi.registerCommand("turn-limit", { description, handler }) - Handler parses args, validates positive integer
- Updates
maxTurnsin-memory variable - Updates widget via
ctx.ui.setWidget("turn-limit", ["Turns: {current}/{max}"]) - Shows notify on success or error
- Use
-
Step 2: Add live widget updates
- In
turn_starthandler, after incrementingturnCount, callctx.ui.setWidget("turn-limit", ["Turns: ${turnCount}/${maxTurns}"]) - In
agent_endhandler, callctx.ui.setWidget("turn-limit", undefined)to clear
- In
-
Step 3: Add boundary confirmation logic
- In
turn_starthandler, after incrementing, checkif (event.turnIndex === maxTurns) - If
ctx.hasUI:- Show confirmation dialog via
ctx.ui.confirm("Turn limit reached", \You've used ${maxTurns} turns. Continue?`)` - If confirmed: reset
turnCount = 0, update widget, continue (do not abort) - If not confirmed: notify user, call
ctx.abort()
- Show confirmation dialog via
- If
!ctx.hasUI: abort silently (same as current behavior)
- In
-
Step 4: Verify existing behavior is preserved
checkTurnLimit()function unchangedgetMaxTurns()function unchangedagent_startstill resets counterDEFAULT_MAX_TURNSstill 25- Env var
PI_MAX_TURNSstill respected
Code Structure (target file)
packages/pi-turn-limit/src/turn-limit.ts
├── DEFAULT_MAX_TURNS = 25 (unchanged)
├── getMaxTurns() (unchanged)
├── checkTurnLimit() (unchanged)
└── export default function (pi: ExtensionAPI)
├── let turnCount = 0
├── let maxTurns = getMaxTurns()
│
├── pi.on("session_start", ...) (unchanged reset logic)
├── pi.on("agent_start", ...) (unchanged reset logic)
├── pi.on("agent_end", ...) (NEW: clear widget)
├── pi.on("turn_start", ...) (MODIFIED: widget + confirmation)
│
└── pi.registerCommand("turn-limit", ...) (NEW: set max turns)
Edge Cases & Notes
PI_MAX_TURNS = 0: Every turn hits the boundary immediately. The user gets a confirmation dialog every turn. This is the correct behavior — the user explicitly set 0, so they're asked each time.PI_MAX_TURNS = 1: First turn hits boundary. User confirms → counter resets → second turn hits boundary → user confirms again. Works as expected.- Multiple
/turn-limitcalls: Each call updatesmaxTurnsin-memory. Widget reflects the new value. No persistence needed (per-session). ctx.hasUIfalse: In print/JSON mode, the confirmation dialog can't be shown. Fall back to immediate abort (current behavior).- Widget name collision: Use
"turn-limit"as the widget name. It's unique to this extension. - Import requirements: No new imports needed.
pi.registerCommandis available on thepiobject.ctx.ui.confirm,ctx.ui.notify,ctx.ui.setWidget,ctx.abortare all available onctx.
Testing (manual)
- Start pi with the extension loaded
- Run agent tasks and watch the widget update:
Turns: 1/25,Turns: 2/25, ... - When turns hit 25, confirm dialog appears → click "Yes" → counter resets to 0, widget shows
Turns: 0/25 - Click "No" → agent aborts with error notification
- Run
/turn-limit 10→ widget immediately showsTurns: 0/10 - Run
/turn-limit abc→ error notification shown - Run
/turn-limit 0→ confirmation dialog every turn - Run with
PI_MAX_TURNS=5→ boundary at turn 5