153 lines
6.3 KiB
Markdown
153 lines
6.3 KiB
Markdown
# Turn Limit Extension — Widget & Confirmation Plan
|
|
|
|
## Goal
|
|
|
|
Extend the `pi-turn-limit` extension with:
|
|
1. A `/turn-limit <N>` command to change the max turns mid-session
|
|
2. A live widget showing `Turns: {current}/{max}`
|
|
3. 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 = 25`
|
|
- `getMaxTurns()` reads `PI_MAX_TURNS` env var, falls back to default
|
|
- `checkTurnLimit()` pure function — **do not modify**
|
|
- `agent_start` resets `turnCount = 0`
|
|
- `turn_start` increments counter, checks limit, aborts if exceeded
|
|
- `ctx.hasUI` guard 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 `args` as 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 `maxTurns` variable
|
|
- Call `ctx.ui.setWidget()` to update the widget display immediately
|
|
- Optionally notify: `ctx.ui.notify("Turn limit set to {N}.", "info")`
|
|
|
|
#### 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)
|
|
- **Example output:** `Turns: 12/25`
|
|
|
|
#### C. Confirmation dialog at boundary
|
|
|
|
- **Trigger condition:** `turnIndex === maxTurns` (the boundary turn, NOT `> maxTurns`)
|
|
- **Guard:** Only if `ctx.hasUI` is 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
|
|
- **On "No" (not confirmed):**
|
|
- Call `ctx.ui.notify("Agent aborted by user.", "error")`
|
|
- Call `ctx.abort()`
|
|
- **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-limit` command registration
|
|
- Use `pi.registerCommand("turn-limit", { description, handler })`
|
|
- Handler parses args, validates positive integer
|
|
- Updates `maxTurns` in-memory variable
|
|
- Updates widget via `ctx.ui.setWidget("turn-limit", ["Turns: {current}/{max}"])`
|
|
- Shows notify on success or error
|
|
|
|
- [ ] **Step 2:** Add live widget updates
|
|
- In `turn_start` handler, after incrementing `turnCount`, call `ctx.ui.setWidget("turn-limit", ["Turns: ${turnCount}/${maxTurns}"])`
|
|
- In `agent_end` handler, call `ctx.ui.setWidget("turn-limit", undefined)` to clear
|
|
|
|
- [ ] **Step 3:** Add boundary confirmation logic
|
|
- In `turn_start` handler, after incrementing, check `if (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()`
|
|
- If `!ctx.hasUI`: abort silently (same as current behavior)
|
|
|
|
- [ ] **Step 4:** Verify existing behavior is preserved
|
|
- `checkTurnLimit()` function unchanged
|
|
- `getMaxTurns()` function unchanged
|
|
- `agent_start` still resets counter
|
|
- `DEFAULT_MAX_TURNS` still 25
|
|
- Env var `PI_MAX_TURNS` still 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-limit` calls**: Each call updates `maxTurns` in-memory. Widget reflects the new value. No persistence needed (per-session).
|
|
- **`ctx.hasUI` false**: 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.registerCommand` is available on the `pi` object. `ctx.ui.confirm`, `ctx.ui.notify`, `ctx.ui.setWidget`, `ctx.abort` are all available on `ctx`.
|
|
|
|
---
|
|
|
|
## Testing (manual)
|
|
|
|
1. Start pi with the extension loaded
|
|
2. Run agent tasks and watch the widget update: `Turns: 1/25`, `Turns: 2/25`, ...
|
|
3. When turns hit 25, confirm dialog appears → click "Yes" → counter resets to 0, widget shows `Turns: 0/25`
|
|
4. Click "No" → agent aborts with error notification
|
|
5. Run `/turn-limit 10` → widget immediately shows `Turns: 0/10`
|
|
6. Run `/turn-limit abc` → error notification shown
|
|
7. Run `/turn-limit 0` → confirmation dialog every turn
|
|
8. Run with `PI_MAX_TURNS=5` → boundary at turn 5
|