pi-plans 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -17
- package/index.ts +34 -59
- package/package.json +1 -1
- package/references/pi-planning-workflow.md +6 -3
- package/references/state-and-config.md +1 -1
- package/src/autocomplete.ts +163 -0
- package/src/compaction.ts +502 -0
- package/src/exec.ts +557 -333
- package/src/plan.ts +37 -0
- package/src/query-hook.ts +82 -0
- package/src/refine-ui-helpers.ts +89 -0
- package/src/refine-ui-state.ts +78 -0
- package/src/refine-ui.ts +322 -0
- package/src/state.ts +9 -27
- package/src/subagent.ts +196 -69
- package/tests/autocomplete.test.ts +142 -0
- package/tests/compaction.test.ts +74 -0
- package/tests/exec.test.ts +153 -248
- package/tests/execute-plan.test.ts +65 -0
- package/tests/plan.test.ts +11 -1
- package/tests/plans.test.ts +6 -5
- package/tests/query-hook.test.ts +82 -0
- package/tests/refine-ui.test.ts +127 -0
- package/tests/state.test.ts +12 -15
- package/tests/subagent.test.ts +114 -0
- package/tools/ask-choice.ts +22 -0
- package/tools/execute-plan.ts +7 -39
- package/tools/plans.ts +1 -18
- package/tools/refine.ts +125 -71
- package/src/execution-panel.ts +0 -633
- package/tests/execution-panel.test.ts +0 -234
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<h2 align="center"><b>Plan. Review. Execute.</b></h2>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<i>Versioned, reviewed Markdown plans land before any code changes.<br>Human-in-the-loop planning for the Pi coding agent
|
|
10
|
+
<i>Versioned, reviewed Markdown plans land before any code changes.<br>Human-in-the-loop planning for the <a href="https://github.com/earendil-works/pi">Pi coding agent</a>.</i>
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
<a href="https://www.npmjs.com/package/pi-plans"><img alt="npm downloads" src="https://img.shields.io/npm/dt/pi-plans?color=38bdf8" /></a>
|
|
17
17
|
<a href="https://www.npmjs.com/package/pi-plans"><img alt="npm version" src="https://img.shields.io/npm/v/pi-plans?color=60a5fa" /></a>
|
|
18
18
|
<a href="https://github.com/earendil-works/pi"><img alt="Pi package" src="https://img.shields.io/badge/Pi-package-fbbf24" /></a>
|
|
19
|
+
<a href="https://github.com/earendil-works/pi"><img alt="Pi coding agent" src="https://img.shields.io/badge/Pi%20coding%20agent-earendil--works-22d3ee" /></a>
|
|
19
20
|
<a href="./LICENSE"><img alt="License" src="https://img.shields.io/npm/l/pi-plans?color=22c55e" /></a>
|
|
20
21
|
<a href="https://github.com/MaxInGaussian/pi-plans/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/MaxInGaussian/pi-plans/actions/workflows/ci.yml/badge.svg" /></a>
|
|
21
22
|
</p>
|
|
@@ -62,7 +63,7 @@ A rough change request becomes a versioned Markdown plan instead of a surprise d
|
|
|
62
63
|
tracked execution loop
|
|
63
64
|
fused AGENTS.md × Ponytail executor rules
|
|
64
65
|
checklist injected each turn, [DONE:VC-xxx]
|
|
65
|
-
markers tracked via bottom status bar
|
|
66
|
+
markers tracked via bottom status bar
|
|
66
67
|
|
|
|
67
68
|
v
|
|
68
69
|
run status: done
|
|
@@ -112,13 +113,15 @@ Planning artifacts live under `./docs/pi-plans/YYYY-MM-DD-<topic>/` by default (
|
|
|
112
113
|
| Capability | In short |
|
|
113
114
|
|---|---|
|
|
114
115
|
| Planning router + five specialist skills | Start with `/skill:planning` to route to the narrowest matching specialist (`plan-small` → `plan-big`, `debug-and-plan`, `plan-with-refs`) |
|
|
115
|
-
| Choice prompts | `ask_choice`: recommended option first, answers auto-recorded per run |
|
|
116
|
-
| Refinement rounds | Read-only reviewer/criticizer Pi subagents consolidate findings into the next plan version |
|
|
116
|
+
| Choice prompts | `ask_choice`: recommended option first, answers auto-recorded per run; choosing Auto-complete enables recommendation-only answers for later eligible questions in the current planning run, with `/plans-autocomplete-stop` available to take back control |
|
|
117
|
+
| Refinement rounds | Read-only reviewer/criticizer Pi subagents consolidate findings into the next plan version; delegated runs have standalone `Reviewer`/`Criticizer` progress overlays that close before the tool result returns |
|
|
117
118
|
| Workspace state | Config, runs, decisions, refs, and subagent ledgers in `.git/pi_plans/` (git common dir) |
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
|
121
|
-
|
|
|
119
|
+
| Smart compact (I-aware) | History is sliced by `I-###` instead of VC. The current-I slice above 20% of the model window triggers a bounded summary with paired `Read:` records, retains a legal recent suffix, targets <10% post-context, and records a hard-floor reason when unreachable. Planning phase falls back to the latest plan/Q&A focus when no current marker exists; cooldown + resume guard prevent ping-pong; one hidden continuation is queued when Pi reports `willRetry: false` |
|
|
120
|
+
| Visible Refiner overlay | Delegated reviewer/criticizer subagents surface as a named public overlay in the TUI — one `Reviewer`/`Criticizer` panel with per-lane tool progress, bounded output preview, and clean cancelled/timed-out vs completed states. The overlay opens when the round starts and closes before the tool result returns to the main session |
|
|
121
|
+
| Tracked execution | Checklist injected each turn; `[DONE:VC-xxx]` markers drive completion; implementation items report progress with `[I-xxx:implemented]` / `[I-xxx:validating]` markers; the bottom status bar shows lifecycle, `x/y` progress, elapsed time, and input/output token usage in real time |
|
|
122
|
+
| Execution handoff | The accepted plan resumes in the current session model; no separate model selection is performed. |
|
|
123
|
+
| Execution-phase compaction | Pi core owns threshold, overflow, and manual scheduling; pi-plans adds a current-I proactive check once the current-I slice exceeds 20% of the model window, summarizes I-level history and bounded `Read:` records, retains a legal recent suffix, targets under 10% when possible, and records hard-floor reasons when not; one hidden continuation is queued when Pi reports `willRetry: false` |
|
|
124
|
+
| Planning-phase auto compaction | In active planning runs (run.status=planning, no execution), the current-I check uses the same 20%/10% best-effort policy when a marker exists; without a marker it protects the latest plan/Q&A focus, while Pi threshold, overflow, and manual compaction remain supported; cooldown + resume guard prevent ping-pong and hidden resume messages stay out of model context |
|
|
122
125
|
| Efficient executor prompt | Each turn, the executor is steered by a fused rule set — Marcos Hernanz's AGENTS.md principles × Ponytail minimalism: layered growth, simplest implementation, long-term architecture (no stopgaps), library discipline — so plans finish in fewer tokens and fewer detours |
|
|
123
126
|
| Write guard | `edit`/`write` blocked outside planning artifacts while a run is active |
|
|
124
127
|
|
|
@@ -126,17 +129,37 @@ Planning artifacts live under `./docs/pi-plans/YYYY-MM-DD-<topic>/` by default (
|
|
|
126
129
|
|
|
127
130
|
| Tool / Command | Purpose |
|
|
128
131
|
|---|---|
|
|
129
|
-
| `plans` | State CLI: `init`, `show`, `set-language`, `set-artifact-root`, `set-
|
|
132
|
+
| `plans` | State CLI: `init`, `show`, `set-language`, `set-artifact-root`, `set-role`, `start-run`, `set-status`, `record-decision`, `record-ref`, `record-subagent` |
|
|
130
133
|
| `ask_choice` | Numbered choice prompt; `autoComplete: false` for the merged accept/execute question and external-state questions |
|
|
131
|
-
| `refine` | Reviewer/criticizer round via read-only subagents (`--tools read,grep,find,ls`); `reviewers: 3` for big plans; enforces role/model confirmation gates |
|
|
132
|
-
| `execute_plan` | Execution handoff: re-confirms with the user
|
|
134
|
+
| `refine` | Reviewer/criticizer round via standalone read-only subagents (`--mode json -p --no-session --tools read,grep,find,ls`); delegated TUI runs show one `Reviewer`/`Criticizer` overlay and close it before returning; `reviewers: 3` for big plans; enforces role/model confirmation gates |
|
|
135
|
+
| `execute_plan` | Execution handoff: re-confirms with the user and enters extension-managed execution mode |
|
|
133
136
|
| `/plans` | Show config, active run, and execution progress |
|
|
134
|
-
| `/plans-
|
|
135
|
-
| `/plans-execute [plan.md]` | Manual execution handoff (defaults to highest `PLAN_vN.md`; prompts for an execution model if unset) |
|
|
137
|
+
| `/plans-execute [plan.md]` | Manual execution handoff (defaults to highest `PLAN_vN.md`) |
|
|
136
138
|
| `/update-plan [plan.md] [reason…]` | Interrupt-and-refine: stops execution (if any), returns the run to planning, and directs the agent to revise the plan into `PLAN_vN+1.md` while preserving verified work |
|
|
139
|
+
| `/plans-autocomplete-stop` | Stop the current run's Auto-complete mode and return later planning questions to normal interaction |
|
|
137
140
|
| `/plans-stop` | Stop execution mode |
|
|
138
141
|
| `/plans-abandon` | Abandon the active run (lifts the write guard; artifacts stay) |
|
|
139
|
-
| Status bar (lifecycle) | 💬 Q&A → 📝 draft written (planning sub-phases) → ⌛ executing `x/y · spent · in/out-toks` in the
|
|
142
|
+
| Status bar (lifecycle) | 💬 Q&A → 📝 draft written (planning sub-phases) → ⌛ executing `x/y · spent · in/out-toks` in the bottom status bar → ⛔ stopped / 🎯 done / 🚫 abandoned |
|
|
143
|
+
|
|
144
|
+
## Smart compact (I-aware)
|
|
145
|
+
|
|
146
|
+
Default `compaction` is Pi-core-owned: threshold, overflow, and manual `/compact` always run as designed. On top of that, pi-plans layers an **I-aware policy** so long, tool-heavy sessions survive the same run instead of running out of context:
|
|
147
|
+
|
|
148
|
+
- **Slice by implementation item.** History is grouped by `[I-###:current]` markers instead of `[DONE:VC-xxx]`. The current I's prefix can be summarized, the current I's recent suffix stays raw, and finished `I-###` items become independent sections.
|
|
149
|
+
- **Bounded read history.** Every paired `read` call/result is reduced to `Read: <path> line <X-Y> Extracted information summary: ...` (line range is `unknown` when no offset/limit is given). Records deduplicate by path/range across compactions and never embed full raw tool output.
|
|
150
|
+
- **20%/10% best-effort budget.** When the current-I slice exceeds 20% of `ctx.getContextUsage().contextWindow`, a compact is requested on the next settled turn. The summary's `details` record `contextWindow`, `tokensBefore`, `currentITokens`, `summaryTokens`, `keptSuffixTokens`, `estimatedAfterTokens`, `targetRatio`, `currentI`, `firstKeptEntryId`, `targetMet`, and a `hardFloorReason` when the 10% target cannot be reached (system prompt, tool definitions, single oversized tool result). Hard floors stop the loop; they do not silently fall through.
|
|
151
|
+
- **Pi-owned scheduling preserved.** Threshold, overflow, and manual triggers still come from Pi core. pi-plans only customizes the summary and re-arms once usage falls below the low watermark. The hidden `Continue execution.` resume message is queued on non-retry compactions and never enters model context.
|
|
152
|
+
- **Bounded model call.** Custom summaries reuse the current Pi model via `ctx.modelRegistry.complete(model, context, options)` with `event.signal`, `cacheRetention: "none"`, a fresh `sessionId`, and bounded `maxTokens`. Empty, length-stopped, error, or tool-call responses fall back to Pi's default compaction — no half-checkpoint is ever written.
|
|
153
|
+
- **Phase isolation.** Planning and execution keep independent compaction state (`pi-plans-plan-resume` vs `pi-plans-exec-resume`); planning without a current marker protects the latest plan/Q&A focus rather than leaking execution state.
|
|
154
|
+
|
|
155
|
+
## Visible Refiner overlay
|
|
156
|
+
|
|
157
|
+
Delegated `refine` rounds (reviewer or criticizer) show their progress directly inside the Pi TUI instead of disappearing into the child process's terminal. The overlay is a public, named panel so users always know who is doing what:
|
|
158
|
+
|
|
159
|
+
- **Named public overlays.** Each round uses the literal overlay name `Reviewer` or `Criticizer` (no dependency on `pi-btw`; the renderer is built on Pi's public `pi-tui` primitives). The big-plan concurrent reviewer round renders one reviewer lane per subagent under the same `Reviewer` overlay.
|
|
160
|
+
- **Bounded live detail.** The overlay tracks lane state (`pending → running → completed | cancelled | timed-out`) and the most recent tool call plus a clipped argument preview. Raw tool output is never surfaced, so progress stays legible even when subagents read large files.
|
|
161
|
+
- **Clean lifecycle edges.** The overlay opens at round start, advances via the JSONL progress feed emitted by `pi --mode json`, and is `close()`d before the round's conclusion returns as a tool result to the main session. Cancelled and timed-out children render as terminal states with the original error message — never as silent drops.
|
|
162
|
+
- **Tool-only progress.** The overlay only consumes tool and message lifecycle events from the child; unrelated `pi` events are ignored, so a noisy upstream release does not desync the panel.
|
|
140
163
|
|
|
141
164
|
## The execution rules
|
|
142
165
|
|
|
@@ -154,6 +177,9 @@ Once you approve the handoff, every turn injects a compact rule set that fuses M
|
|
|
154
177
|
|
|
155
178
|
The rules cost four lines per turn and buy back far more: fewer wrong turns, shorter implementation paths, plans that finish in fewer tokens.
|
|
156
179
|
|
|
180
|
+
Waiting for subprocess-backed verification:
|
|
181
|
+
For subprocess-backed verification, when a step starts a subprocess and needs its result before verifying, use literal `waiting for` with backoff `5s -> 10s -> 20s -> 40s -> 80s`, then keep polling at 80s; restart at 5s for each new subprocess.
|
|
182
|
+
|
|
157
183
|
## Skills
|
|
158
184
|
|
|
159
185
|
Invoked via `resources_discover`, callable as `/skill:<name>`, directly as `/<name>` (e.g. `/planning`, `/plan-small` — extension aliases that forward to the skill), or picked automatically from the task description.
|
|
@@ -194,12 +220,12 @@ or register the absolute path in `~/.pi/agent/settings.json`:
|
|
|
194
220
|
pi-plans/
|
|
195
221
|
├── index.ts # Extension entry: tools, commands, guard, execution loop
|
|
196
222
|
├── tools/ # plans, ask-choice, refine, execute-plan
|
|
197
|
-
├── src/ # state, guard, plan parsing, subagent runner, exec loop
|
|
223
|
+
├── src/ # state, guard, plan parsing, subagent runner, refine overlay, exec loop
|
|
198
224
|
├── skills/ # The planning router plus five specialist planning skills
|
|
199
225
|
├── references/ # Shared workflow, state/config, plan template (normative)
|
|
200
226
|
├── agents/ # reviewer.md / criticizer.md subagent prompts
|
|
201
227
|
├── scripts/validate.ts # Structure validator
|
|
202
|
-
└── tests/ # node:test suite (state, guard, plan parsing, execution)
|
|
228
|
+
└── tests/ # node:test suite (state, guard, plan parsing, execution, refine progress)
|
|
203
229
|
```
|
|
204
230
|
|
|
205
231
|
## Safety model
|
|
@@ -223,7 +249,7 @@ The plan is the contract. Refinement converges on scope while nothing is writabl
|
|
|
223
249
|
|
|
224
250
|
**What can Auto-complete decide on my behalf?**
|
|
225
251
|
|
|
226
|
-
Planning and refinement choices only (the recommended option). It is never offered for execution approval, installs, publishing, deployment, merge, push, or credentials — those questions stop and wait for you.
|
|
252
|
+
Planning and refinement choices only (the recommended option). Choosing Auto-complete enables the recommended answer for later eligible planning questions in the current run and the extension continues the planning turn when the model stops early. Use `/plans-autocomplete-stop` to take back control. It is never offered for execution approval, installs, publishing, deployment, merge, push, or credentials — those questions stop and wait for you.
|
|
227
253
|
|
|
228
254
|
**Where does all the state live?**
|
|
229
255
|
|
package/index.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - `refine` tool — reviewer/criticizer rounds via read-only pi subagents
|
|
11
11
|
* - `execute_plan` — execution handoff into the tracked execution loop
|
|
12
12
|
* - write guard — planning runs may only write planning artifacts
|
|
13
|
-
* - execution loop — checklist injection, [DONE:VC-xxx] tracking, progress
|
|
13
|
+
* - execution loop — checklist injection, [DONE:VC-xxx] tracking, progress status
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
@@ -19,7 +19,6 @@ import * as path from "node:path";
|
|
|
19
19
|
import { dirname, join } from "node:path";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
21
|
import {
|
|
22
|
-
consumePendingPanelSync,
|
|
23
22
|
consumePlanningCompactionResumeGuard,
|
|
24
23
|
drainExecutionFlush,
|
|
25
24
|
executionContextMessage,
|
|
@@ -29,23 +28,28 @@ import {
|
|
|
29
28
|
handleExecutionBeforeCompact,
|
|
30
29
|
handleExecutionCompact,
|
|
31
30
|
handleExecutionCompactFailed,
|
|
31
|
+
handleExecutionTurnCompaction,
|
|
32
32
|
handlePlanningBeforeCompact,
|
|
33
33
|
handlePlanningCompact,
|
|
34
34
|
handlePlanningCompactFailed,
|
|
35
35
|
PLANNING_PLAN_WRITTEN_CUSTOM_TYPE,
|
|
36
36
|
registerExecutionTurnHandlers,
|
|
37
|
-
recordTouchedPaths,
|
|
38
37
|
refreshPlanningCompactionCooldown,
|
|
39
38
|
requestPlanningCompaction,
|
|
40
39
|
restoreFromSession,
|
|
41
|
-
ensureExecutionModelActive,
|
|
42
40
|
stopExecution,
|
|
43
|
-
syncExecutionPanel,
|
|
44
|
-
toggleExecutionPanelView,
|
|
45
41
|
updateStatusWidget,
|
|
46
42
|
shouldTriggerPlanningCompaction,
|
|
47
43
|
} from "./src/exec.ts";
|
|
44
|
+
import {
|
|
45
|
+
autoCompleteStatus,
|
|
46
|
+
disableAutoComplete,
|
|
47
|
+
markPlanWritten,
|
|
48
|
+
registerAutoCompleteTurnHandlers,
|
|
49
|
+
restoreAutoCompleteFromSession,
|
|
50
|
+
} from "./src/autocomplete.ts";
|
|
48
51
|
import { planningWriteBlockReason } from "./src/guard.ts";
|
|
52
|
+
import { registerQueryInterviewHooks } from "./src/query-hook.ts";
|
|
49
53
|
import { latestPlanVersion, nextPlanVersionPath } from "./src/plan.ts";
|
|
50
54
|
import { getRun, readActive, recordDecision, resolveStateRootOrNull, setRunStatus } from "./src/state.ts";
|
|
51
55
|
import { registerAskChoiceTool } from "./tools/ask-choice.ts";
|
|
@@ -80,20 +84,12 @@ function extensionStalenessLine(): string {
|
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
cleaned.includes("/") ||
|
|
90
|
-
cleaned.startsWith(".") ||
|
|
91
|
-
cleaned.startsWith("~") ||
|
|
92
|
-
/^[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+$/.test(cleaned);
|
|
93
|
-
if (!looksLikePath) continue;
|
|
94
|
-
values.add(cleaned);
|
|
95
|
-
}
|
|
96
|
-
return [...values];
|
|
87
|
+
function hasActivePlanningWorkflow(ctx: Parameters<typeof updateStatusWidget>[0]): boolean {
|
|
88
|
+
if (getExecution()) return true;
|
|
89
|
+
const active = readActive(ctx.cwd);
|
|
90
|
+
if (!active) return false;
|
|
91
|
+
const status = getRun(ctx.cwd, active.run_id)?.status;
|
|
92
|
+
return status === "planning" || status === "accepted" || status === "executing";
|
|
97
93
|
}
|
|
98
94
|
|
|
99
95
|
export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
@@ -101,6 +97,7 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
101
97
|
registerAskChoiceTool(pi);
|
|
102
98
|
registerRefineTool(pi, baseDir);
|
|
103
99
|
registerExecutePlanTool(pi);
|
|
100
|
+
registerQueryInterviewHooks(pi, hasActivePlanningWorkflow);
|
|
104
101
|
|
|
105
102
|
// Contribute the router skill plus the five specialist planning skills.
|
|
106
103
|
pi.on("resources_discover", () => ({
|
|
@@ -142,25 +139,14 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
142
139
|
// not been approved), edit/write may only target planning artifacts.
|
|
143
140
|
// -----------------------------------------------------------------------
|
|
144
141
|
pi.on("tool_call", async (event, ctx) => {
|
|
145
|
-
|
|
146
|
-
if (execution) {
|
|
147
|
-
if (event.toolName === "edit" || event.toolName === "write") {
|
|
148
|
-
const rawPath = String((event.input as { path?: string }).path ?? "");
|
|
149
|
-
if (rawPath) recordTouchedPaths(ctx.cwd, [rawPath]);
|
|
150
|
-
}
|
|
151
|
-
if (event.toolName === "bash") {
|
|
152
|
-
const command = String((event.input as { command?: string }).command ?? "");
|
|
153
|
-
if (command) recordTouchedPaths(ctx.cwd, extractPathsFromBash(command));
|
|
154
|
-
}
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
142
|
+
if (getExecution()) return;
|
|
157
143
|
const rawPath = String((event.input as { path?: string }).path ?? "");
|
|
158
144
|
if (!rawPath) return;
|
|
159
145
|
const reason = planningWriteBlockReason({ workdir: ctx.cwd, toolName: event.toolName, rawPath });
|
|
160
146
|
if (reason) return { block: true, reason };
|
|
161
147
|
// Allowed write: if it lands exactly on the run's latest plan file, drop a
|
|
162
148
|
// marker entry so planning-phase compaction can anchor its cut point there.
|
|
163
|
-
if (event.toolName === "write") {
|
|
149
|
+
if (event.toolName === "write" || event.toolName === "edit") {
|
|
164
150
|
const active = readActive(ctx.cwd);
|
|
165
151
|
if (active) {
|
|
166
152
|
const latest = latestPlanVersion(active.artifact_dir);
|
|
@@ -169,6 +155,7 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
169
155
|
runId: active.run_id,
|
|
170
156
|
planPath: latest.path,
|
|
171
157
|
});
|
|
158
|
+
markPlanWritten(ctx);
|
|
172
159
|
}
|
|
173
160
|
}
|
|
174
161
|
}
|
|
@@ -210,7 +197,6 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
210
197
|
// -----------------------------------------------------------------------
|
|
211
198
|
pi.on("before_agent_start", async (_event, ctx) => {
|
|
212
199
|
drainExecutionFlush(pi, ctx);
|
|
213
|
-
await ensureExecutionModelActive(pi, ctx);
|
|
214
200
|
const content = executionContextMessage();
|
|
215
201
|
if (!content) {
|
|
216
202
|
if (!getExecution() && shouldTriggerPlanningCompaction(ctx)) {
|
|
@@ -228,10 +214,11 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
228
214
|
});
|
|
229
215
|
|
|
230
216
|
registerExecutionTurnHandlers(pi, async (ctx) => {
|
|
231
|
-
if (
|
|
217
|
+
if (getExecution()) {
|
|
218
|
+
handleExecutionTurnCompaction(ctx);
|
|
219
|
+
} else {
|
|
232
220
|
refreshPlanningCompactionCooldown(ctx);
|
|
233
221
|
if (consumePlanningCompactionResumeGuard(ctx)) {
|
|
234
|
-
if (consumePendingPanelSync()) syncExecutionPanel(ctx);
|
|
235
222
|
updateStatusWidget(ctx);
|
|
236
223
|
return;
|
|
237
224
|
}
|
|
@@ -239,26 +226,14 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
239
226
|
requestPlanningCompaction(ctx);
|
|
240
227
|
}
|
|
241
228
|
}
|
|
242
|
-
// A
|
|
243
|
-
// turn just ended, so this is the safe point to apply it.
|
|
244
|
-
if (consumePendingPanelSync()) syncExecutionPanel(ctx);
|
|
229
|
+
// A completed turn is the safe point for status updates.
|
|
245
230
|
updateStatusWidget(ctx);
|
|
246
231
|
});
|
|
232
|
+
registerAutoCompleteTurnHandlers(pi);
|
|
247
233
|
|
|
248
234
|
// -----------------------------------------------------------------------
|
|
249
|
-
// Commands
|
|
235
|
+
// Commands
|
|
250
236
|
// -----------------------------------------------------------------------
|
|
251
|
-
pi.registerShortcut("alt+o", {
|
|
252
|
-
description: "Toggle pi-plans execution checklist widget",
|
|
253
|
-
handler: async (ctx) => {
|
|
254
|
-
const expanded = toggleExecutionPanelView(pi, ctx);
|
|
255
|
-
if (expanded === null) {
|
|
256
|
-
ctx.ui.notify("No execution in progress.", "info");
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
ctx.ui.notify(expanded ? "Execution checklist expanded." : "Execution checklist collapsed.", "info");
|
|
260
|
-
},
|
|
261
|
-
});
|
|
262
237
|
|
|
263
238
|
pi.registerCommand("plans", {
|
|
264
239
|
description: "Show pi-plans state: config, active run, and execution progress",
|
|
@@ -283,20 +258,17 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
283
258
|
lines.push(` ${item.done ? "☑" : "☐"} ${item.id}`);
|
|
284
259
|
}
|
|
285
260
|
}
|
|
261
|
+
lines.push(`Auto-complete: ${autoCompleteStatus(ctx)}`);
|
|
286
262
|
lines.push(extensionStalenessLine());
|
|
287
263
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
288
264
|
},
|
|
289
265
|
});
|
|
290
266
|
|
|
291
|
-
pi.registerCommand("plans-
|
|
292
|
-
description: "
|
|
267
|
+
pi.registerCommand("plans-autocomplete-stop", {
|
|
268
|
+
description: "Stop Auto-complete for the active planning run",
|
|
293
269
|
handler: async (_args, ctx) => {
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
ctx.ui.notify("No execution in progress.", "info");
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
ctx.ui.notify(expanded ? "Execution checklist expanded." : "Execution checklist collapsed.", "info");
|
|
270
|
+
const stopped = disableAutoComplete(ctx, "stopped by user");
|
|
271
|
+
ctx.ui.notify(stopped ? "Auto-complete stopped." : "Auto-complete is not active.", stopped ? "info" : "warning");
|
|
300
272
|
},
|
|
301
273
|
});
|
|
302
274
|
|
|
@@ -333,6 +305,7 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
333
305
|
|
|
334
306
|
const active = readActive(ctx.cwd);
|
|
335
307
|
const execution = getExecution();
|
|
308
|
+
disableAutoComplete(ctx, "plan update");
|
|
336
309
|
|
|
337
310
|
// Resolve the plan to revise: explicit arg > running execution > latest in artifact dir.
|
|
338
311
|
let sourcePlanPath: string | null = planArg
|
|
@@ -441,6 +414,7 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
441
414
|
);
|
|
442
415
|
if (!ok) return;
|
|
443
416
|
// Abandon must end execution first so the planning model is restored.
|
|
417
|
+
disableAutoComplete(ctx, "run abandoned");
|
|
444
418
|
if (getExecution()) {
|
|
445
419
|
await stopExecution(pi, ctx, "run abandoned via /plans-abandon");
|
|
446
420
|
}
|
|
@@ -459,5 +433,6 @@ export default function piPlansExtension(pi: ExtensionAPI): void {
|
|
|
459
433
|
// -----------------------------------------------------------------------
|
|
460
434
|
pi.on("session_start", async (_event, ctx) => {
|
|
461
435
|
await restoreFromSession(pi, ctx, ctx.sessionManager.getEntries() as unknown as Parameters<typeof restoreFromSession>[2]);
|
|
436
|
+
restoreAutoCompleteFromSession(ctx, ctx.sessionManager.getEntries() as unknown as Parameters<typeof restoreAutoCompleteFromSession>[1]);
|
|
462
437
|
});
|
|
463
438
|
}
|
package/package.json
CHANGED
|
@@ -9,8 +9,9 @@ This skill set is written for the Pi coding agent's documented behavior:
|
|
|
9
9
|
- the five skills are contributed by the pi-plans extension and loaded as Pi skills (also invokable as `/skill:<name>`);
|
|
10
10
|
- skill references and helper sources are resolved relative to the directory containing `SKILL.md`;
|
|
11
11
|
- the extension provides these tools: `plans` (workspace state), `ask_choice` (choice prompts), `refine` (reviewer/criticizer subagents), and `execute_plan` (execution handoff);
|
|
12
|
-
- `refine` spawns read-only Pi subagents (`pi --mode json -p --no-session --tools read,grep,find,ls`) with isolated context;
|
|
13
|
-
- the execution loop is extension-managed: remaining verifier items are injected each turn and `[DONE:VC-xxx]` markers are tracked with a
|
|
12
|
+
- `refine` spawns read-only Pi subagents (`pi --mode json -p --no-session --tools read,grep,find,ls`) with isolated context; delegated Reviewer/Criticizer runs show a standalone aggregate overlay titled `Reviewer` or `Criticizer`, close the overlay before returning, and return conclusions to the main session as tool output;
|
|
13
|
+
- the execution loop is extension-managed: remaining verifier items are injected each turn, implementation items emit `[I-###:current]`/`[I-###:implemented|validating]`, and `[DONE:VC-xxx]` markers are tracked with a bottom status bar;
|
|
14
|
+
- execution and planning compaction keep Pi's SessionManager as the history owner, group compacted history by implementation item, retain a legal current-I suffix, summarize paired reads as bounded `Read: <path> line <X-Y> Extracted information summary: ...` records, and target `<10%` of the model window after a current-I slice exceeds `20%`; hard-floor cases are recorded instead of looping;
|
|
14
15
|
|
|
15
16
|
## Planning Boundary
|
|
16
17
|
|
|
@@ -60,6 +61,7 @@ Every user-facing planning or refinement question goes through the `ask_choice`
|
|
|
60
61
|
- `options`: ordered options, recommended option first with `recommended: true` (exactly one), each with the tradeoff that matters in `description`;
|
|
61
62
|
- do not add `Other` or `Auto-complete` yourself — the tool appends `Other…` second-last and `Auto-complete` last;
|
|
62
63
|
- pass `autoComplete: false` for the merged accept/execute question — it contains the execution approval, so Auto-complete never appears there — and for any install waiver, publishing, deployment, merge, push, credential, or external-state question. Auto-complete may choose the recommended planning or refinement option only.
|
|
64
|
+
- When the user selects Auto-complete, it remains active for the current planning run: later eligible questions use their recommended options automatically, and the extension queues one deduplicated follow-up if the model stops after an auto-completed answer. `/plans-autocomplete-stop` disables it; session restore may reactivate it only for the same active run while its status is `planning`.
|
|
63
65
|
|
|
64
66
|
Answers are recorded automatically in the active run's `decisions.jsonl`. You must still maintain `DECISIONS.md` in the artifact directory (summary table of questions, options, answers, answer sources, open assumptions).
|
|
65
67
|
|
|
@@ -126,7 +128,8 @@ A refinement round is complete when all reviewer outputs have returned or all cr
|
|
|
126
128
|
|
|
127
129
|
When the user picks `✓ Accept PLAN_vN and execute it now` in the merged question, mark the plan accepted and call the `execute_plan` tool (or the user runs `/plans-execute`). It re-confirms with the user, then the extension enters execution mode:
|
|
128
130
|
|
|
129
|
-
- every agent turn is injected with the remaining verifier checklist and execution rules (layered simplest implementation, no stopgaps, dependency and library discipline, minimum tests);
|
|
131
|
+
- every agent turn is injected with the remaining verifier checklist and execution rules (layered simplest implementation, waiting for subprocess-backed verification with backoff 5s -> 10s -> 20s -> 40s -> 80s, then keep polling at 80s and restart at 5s for each new subprocess, no stopgaps, dependency and library discipline, minimum tests);
|
|
132
|
+
- execution-phase compaction may proactively compact the current-I slice above 20% of the model window, summarizes the oldest eligible prefix and bounded Read records, preserves a legal recent suffix, and records whether the <10% target was met; Pi still owns threshold, overflow, and manual scheduling;
|
|
130
133
|
- the read-only guard lifts: full write access returns;
|
|
131
134
|
- the run status moves to `executing`, then `done` when the last `[DONE:VC-xxx]` marker lands;
|
|
132
135
|
- `/plans-stop` stops execution; `/plans` shows progress.
|
|
@@ -120,7 +120,7 @@ If a spawn later fails because the stored selector is unavailable, reset the mar
|
|
|
120
120
|
|
|
121
121
|
## Subagent Spawning
|
|
122
122
|
|
|
123
|
-
When `mode` is `delegated-subagent`, the `refine` tool spawns a read-only `pi` subprocess (`--mode json -p --no-session --tools read,grep,find,ls`) whose system prompt comes from `agents/reviewer.md` or `agents/criticizer.md`. The subagent:
|
|
123
|
+
When `mode` is `delegated-subagent`, the `refine` tool spawns a read-only `pi` subprocess (`--mode json -p --no-session --tools read,grep,find,ls`) whose system prompt comes from `agents/reviewer.md` or `agents/criticizer.md`. In TUI mode, delegated runs also show a standalone `Reviewer` or `Criticizer` overlay with live lane/tool status; the child is awaited and the overlay is closed before the tool result returns. The subagent:
|
|
124
124
|
|
|
125
125
|
- performs read-only analysis and never edits files;
|
|
126
126
|
- receives the full plan text and a review/criticism brief;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/** Run-scoped Auto-complete state and planning-turn continuation. */
|
|
2
|
+
|
|
3
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { getRun, readActive } from "./state.ts";
|
|
5
|
+
|
|
6
|
+
export const AUTOCOMPLETE_ENTRY = "pi-plans-autocomplete";
|
|
7
|
+
const AUTOCOMPLETE_CONTINUE = "Continue the current planning workflow. Raise the next relevant question with ask_choice; do not stop after an auto-completed answer.";
|
|
8
|
+
|
|
9
|
+
type SessionEntry = { type: string; customType?: string; data?: unknown };
|
|
10
|
+
|
|
11
|
+
type AutoCompleteState = {
|
|
12
|
+
runId: string;
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
pendingFollowUp: boolean;
|
|
15
|
+
askChoiceCount: number;
|
|
16
|
+
autoChoiceCount: number;
|
|
17
|
+
planWritten: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type SessionWithAutoComplete = ExtensionContext["sessionManager"] & {
|
|
21
|
+
__piPlansAutoComplete?: AutoCompleteState;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
let api: ExtensionAPI | null = null;
|
|
25
|
+
|
|
26
|
+
export function setAutoCompleteApi(next: ExtensionAPI | null): void {
|
|
27
|
+
api = next;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function sessionState(ctx: ExtensionContext): AutoCompleteState | undefined {
|
|
31
|
+
return (ctx.sessionManager as SessionWithAutoComplete).__piPlansAutoComplete;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function activePlanningRun(ctx: ExtensionContext): { runId: string } | null {
|
|
35
|
+
const active = readActive(ctx.cwd);
|
|
36
|
+
if (!active) return null;
|
|
37
|
+
const run = getRun(ctx.cwd, active.run_id);
|
|
38
|
+
return run?.status === "planning" ? { runId: run.run_id } : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function appendState(runId: string, enabled: boolean, reason?: string): void {
|
|
42
|
+
if (!api) return;
|
|
43
|
+
api.appendEntry(AUTOCOMPLETE_ENTRY, { runId, enabled, ...(reason ? { reason } : {}) });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function enableAutoComplete(ctx: ExtensionContext): boolean {
|
|
47
|
+
const run = activePlanningRun(ctx);
|
|
48
|
+
if (!run) return false;
|
|
49
|
+
const current = sessionState(ctx);
|
|
50
|
+
if (current?.enabled && current.runId === run.runId) return true;
|
|
51
|
+
(ctx.sessionManager as SessionWithAutoComplete).__piPlansAutoComplete = {
|
|
52
|
+
runId: run.runId,
|
|
53
|
+
enabled: true,
|
|
54
|
+
pendingFollowUp: false,
|
|
55
|
+
askChoiceCount: 0,
|
|
56
|
+
autoChoiceCount: 0,
|
|
57
|
+
planWritten: false,
|
|
58
|
+
};
|
|
59
|
+
appendState(run.runId, true);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function disableAutoComplete(ctx: ExtensionContext, reason = "disabled"): boolean {
|
|
64
|
+
const state = sessionState(ctx);
|
|
65
|
+
const run = activePlanningRun(ctx);
|
|
66
|
+
const runId = state?.runId ?? run?.runId;
|
|
67
|
+
if (!runId && !state?.enabled) return false;
|
|
68
|
+
if (state) state.enabled = false;
|
|
69
|
+
if (runId) appendState(runId, false, reason);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function isAutoCompleteEnabled(ctx: ExtensionContext): boolean {
|
|
74
|
+
const state = sessionState(ctx);
|
|
75
|
+
const run = activePlanningRun(ctx);
|
|
76
|
+
return !!state?.enabled && !!run && state.runId === run.runId;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Record every ask_choice call for this turn; autoChoice marks a recommendation selected by the mode. */
|
|
80
|
+
export function recordAskChoice(ctx: ExtensionContext, autoChoice: boolean): void {
|
|
81
|
+
const state = sessionState(ctx);
|
|
82
|
+
if (!state) return;
|
|
83
|
+
state.askChoiceCount += 1;
|
|
84
|
+
if (autoChoice) state.autoChoiceCount += 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function markPlanWritten(ctx: ExtensionContext): void {
|
|
88
|
+
const state = sessionState(ctx);
|
|
89
|
+
if (state) state.planWritten = true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function resetAutoCompleteTurn(ctx: ExtensionContext): void {
|
|
93
|
+
const state = sessionState(ctx);
|
|
94
|
+
if (!state) return;
|
|
95
|
+
state.pendingFollowUp = false;
|
|
96
|
+
state.askChoiceCount = 0;
|
|
97
|
+
state.autoChoiceCount = 0;
|
|
98
|
+
state.planWritten = false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function shouldContinueAutoComplete(ctx: ExtensionContext): boolean {
|
|
102
|
+
const state = sessionState(ctx);
|
|
103
|
+
return isAutoCompleteEnabled(ctx)
|
|
104
|
+
&& !!state
|
|
105
|
+
&& state.autoChoiceCount > 0
|
|
106
|
+
&& state.askChoiceCount === state.autoChoiceCount
|
|
107
|
+
&& !state.planWritten
|
|
108
|
+
&& !state.pendingFollowUp;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function continueAutoComplete(ctx: ExtensionContext): Promise<boolean> {
|
|
112
|
+
if (!api || !shouldContinueAutoComplete(ctx)) return false;
|
|
113
|
+
const state = sessionState(ctx);
|
|
114
|
+
if (!state) return false;
|
|
115
|
+
state.pendingFollowUp = true;
|
|
116
|
+
try {
|
|
117
|
+
await api.sendUserMessage(AUTOCOMPLETE_CONTINUE, { deliverAs: "followUp" });
|
|
118
|
+
return true;
|
|
119
|
+
} catch {
|
|
120
|
+
state.pendingFollowUp = false;
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function registerAutoCompleteTurnHandlers(pi: ExtensionAPI): void {
|
|
126
|
+
setAutoCompleteApi(pi);
|
|
127
|
+
pi.on("turn_start", async (_event, ctx) => {
|
|
128
|
+
resetAutoCompleteTurn(ctx);
|
|
129
|
+
});
|
|
130
|
+
pi.on("turn_end", async (event, ctx) => {
|
|
131
|
+
const message = event.message as { role?: string } | undefined;
|
|
132
|
+
if (message?.role === "assistant") await continueAutoComplete(ctx);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function restoreAutoCompleteFromSession(ctx: ExtensionContext, entries: SessionEntry[]): void {
|
|
137
|
+
const session = ctx.sessionManager as SessionWithAutoComplete;
|
|
138
|
+
delete session.__piPlansAutoComplete;
|
|
139
|
+
let restored: { runId: string; enabled: boolean } | null = null;
|
|
140
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
141
|
+
const entry = entries[i];
|
|
142
|
+
if (entry.type !== "custom" || entry.customType !== AUTOCOMPLETE_ENTRY || !entry.data) continue;
|
|
143
|
+
const data = entry.data as { runId?: unknown; enabled?: unknown };
|
|
144
|
+
if (typeof data.runId === "string" && typeof data.enabled === "boolean") {
|
|
145
|
+
restored = { runId: data.runId, enabled: data.enabled };
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const active = activePlanningRun(ctx);
|
|
150
|
+
if (!restored || !restored.enabled || !active || restored.runId !== active.runId) return;
|
|
151
|
+
session.__piPlansAutoComplete = {
|
|
152
|
+
runId: active.runId,
|
|
153
|
+
enabled: true,
|
|
154
|
+
pendingFollowUp: false,
|
|
155
|
+
askChoiceCount: 0,
|
|
156
|
+
autoChoiceCount: 0,
|
|
157
|
+
planWritten: false,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function autoCompleteStatus(ctx: ExtensionContext): "enabled" | "disabled" {
|
|
162
|
+
return isAutoCompleteEnabled(ctx) ? "enabled" : "disabled";
|
|
163
|
+
}
|