@janvitos/pi-plan-build 0.1.26 → 0.1.28
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 +5 -5
- package/index.ts +45 -15
- package/package.json +2 -2
- package/utils.ts +15 -1
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ A global [Pi coding agent](https://github.com/badlogic/pi-mono) extension that a
|
|
|
25
25
|
|
|
26
26
|
## Requirements
|
|
27
27
|
|
|
28
|
-
- Pi `0.84.
|
|
28
|
+
- Pi `0.84.2` or newer
|
|
29
29
|
- Node.js `22.6` or newer for the test command
|
|
30
30
|
- TUI or RPC UI support for interactive questions and approval dialogs
|
|
31
31
|
|
|
@@ -62,7 +62,7 @@ Do not install more than one npm, Git, or local copy at the same time; duplicate
|
|
|
62
62
|
| `/plan` | Select Plan mode |
|
|
63
63
|
| `/build` | Select Build mode |
|
|
64
64
|
| `pi --plan` | Start a new session in Plan mode |
|
|
65
|
-
| `/build-fresh` |
|
|
65
|
+
| `/build-fresh` | Start a pending clean-session implementation manually |
|
|
66
66
|
|
|
67
67
|
The agent may also enter Plan mode with `plan_enter` when planning or investigation is safer than immediate execution.
|
|
68
68
|
|
|
@@ -71,10 +71,10 @@ The agent may also enter Plan mode with `plan_enter` when planning or investigat
|
|
|
71
71
|
When planning is complete, `plan_exit` displays the entire persisted plan and asks whether to:
|
|
72
72
|
|
|
73
73
|
1. implement in the current session;
|
|
74
|
-
2.
|
|
74
|
+
2. start a clean linked implementation session; or
|
|
75
75
|
3. stay in Plan mode.
|
|
76
76
|
|
|
77
|
-
Selecting **Start fresh and implement** stops the current run and
|
|
77
|
+
Selecting **Start fresh and implement** stops the current run and automatically dispatches `/build-fresh`. Pi 0.84.2 or newer is required for extension command dispatch from an injected user message. The command creates a linked child session, copies the approved plan to its canonical plan file, preserves the model and thinking level selected for the action, switches it to Build, and starts implementation without transferring the planning conversation.
|
|
78
78
|
|
|
79
79
|
Selecting **Stay in Plan mode**, or pressing Escape while the approval dialog is open, displays:
|
|
80
80
|
|
|
@@ -111,7 +111,7 @@ npm test
|
|
|
111
111
|
npm pack --dry-run
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
The tests cover state decoding, safe plan paths, mutation restrictions, deferred transitions, mode and provider rendering, session-based prompt history restoration, complete plan rendering, approval decisions, stop behavior, fresh-session handoff content, and question formatting.
|
|
114
|
+
The tests cover state decoding, safe plan paths, mutation restrictions, deferred transitions, mode and provider rendering, session-based prompt history restoration, complete plan rendering, approval decisions, stop behavior, fresh-session settings and handoff content, and question formatting.
|
|
115
115
|
|
|
116
116
|
### Publishing
|
|
117
117
|
|
package/index.ts
CHANGED
|
@@ -14,10 +14,12 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
applyManualSelection,
|
|
16
16
|
buildFreshImplementationHandoff,
|
|
17
|
+
buildFreshImplementationRequest,
|
|
17
18
|
buildPlanExitFreshResult,
|
|
18
19
|
buildPlanExitStayResult,
|
|
19
20
|
buildPlanReviewMessage,
|
|
20
21
|
classifyPlanExitChoice,
|
|
22
|
+
type FreshImplementationRequest,
|
|
21
23
|
decodeModeState,
|
|
22
24
|
extractPromptHistory,
|
|
23
25
|
formatFooterCwd,
|
|
@@ -73,7 +75,7 @@ export default function planBuildModes(pi: ExtensionAPI): void {
|
|
|
73
75
|
let toolsBeforeModes: string[] = [];
|
|
74
76
|
let currentContext: ExtensionContext | undefined;
|
|
75
77
|
let requestEditorRender: (() => void) | undefined;
|
|
76
|
-
let
|
|
78
|
+
let freshImplementationRequest: FreshImplementationRequest | undefined;
|
|
77
79
|
|
|
78
80
|
pi.registerFlag("plan", {
|
|
79
81
|
description: "Start in Plan mode",
|
|
@@ -160,28 +162,47 @@ export default function planBuildModes(pi: ExtensionAPI): void {
|
|
|
160
162
|
pi.registerCommand("build-fresh", {
|
|
161
163
|
description: "Start a clean linked session and implement the plan selected in plan_exit",
|
|
162
164
|
handler: async (_args, ctx) => {
|
|
163
|
-
const
|
|
164
|
-
if (!
|
|
165
|
+
const request = freshImplementationRequest;
|
|
166
|
+
if (!request) {
|
|
165
167
|
ctx.ui.notify("No fresh implementation is pending. Choose ‘Start fresh and implement’ from plan_exit first.", "warning");
|
|
166
168
|
return;
|
|
167
169
|
}
|
|
168
170
|
if (selectedMode !== "plan") {
|
|
169
|
-
|
|
171
|
+
freshImplementationRequest = undefined;
|
|
170
172
|
ctx.ui.notify("Fresh implementation is no longer available because Plan mode is not active.", "warning");
|
|
171
173
|
return;
|
|
172
174
|
}
|
|
173
175
|
if (ctx.mode === "print" || ctx.mode === "json") {
|
|
174
176
|
throw new Error("Fresh implementation requires TUI or RPC mode");
|
|
175
177
|
}
|
|
176
|
-
if (!
|
|
178
|
+
if (!request.model) {
|
|
177
179
|
ctx.ui.notify("Cannot start implementation because no model is selected.", "warning");
|
|
178
180
|
return;
|
|
179
181
|
}
|
|
182
|
+
const currentModel = ctx.model;
|
|
183
|
+
const implementationModel = ctx.modelRegistry.find(request.model.provider, request.model.id)
|
|
184
|
+
?? (currentModel?.provider === request.model.provider && currentModel.id === request.model.id ? currentModel : undefined);
|
|
185
|
+
if (!implementationModel) {
|
|
186
|
+
ctx.ui.notify(`Cannot start implementation because ${request.model.provider}/${request.model.id} is unavailable.`, "warning");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const modelSelected = await pi.setModel(implementationModel);
|
|
191
|
+
if (modelSelected === false) {
|
|
192
|
+
ctx.ui.notify(`Cannot start implementation because no API key is available for ${request.model.provider}/${request.model.id}.`, "warning");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
pi.setThinkingLevel(request.thinkingLevel);
|
|
196
|
+
} catch (error: unknown) {
|
|
197
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
198
|
+
ctx.ui.notify(`Cannot start implementation with ${request.model.provider}/${request.model.id}: ${detail}`, "warning");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
180
201
|
|
|
181
|
-
|
|
202
|
+
freshImplementationRequest = undefined;
|
|
182
203
|
const parentSession = ctx.sessionManager.getSessionFile();
|
|
183
204
|
const sourceTools = [...toolsBeforeModes];
|
|
184
|
-
const handoff = buildFreshImplementationHandoff(plan);
|
|
205
|
+
const handoff = buildFreshImplementationHandoff(request.plan);
|
|
185
206
|
let destinationPlanPath = "";
|
|
186
207
|
let setupError: string | undefined;
|
|
187
208
|
let kickoffError: string | undefined;
|
|
@@ -195,7 +216,9 @@ export default function planBuildModes(pi: ExtensionAPI): void {
|
|
|
195
216
|
sessionManager.getSessionId(),
|
|
196
217
|
);
|
|
197
218
|
await fs.promises.mkdir(path.dirname(destinationPlanPath), { recursive: true });
|
|
198
|
-
await fs.promises.writeFile(destinationPlanPath, plan, "utf8");
|
|
219
|
+
await fs.promises.writeFile(destinationPlanPath, request.plan, "utf8");
|
|
220
|
+
sessionManager.appendModelChange(request.model.provider, request.model.id);
|
|
221
|
+
sessionManager.appendThinkingLevelChange(request.thinkingLevel);
|
|
199
222
|
sessionManager.appendCustomEntry(STATE_TYPE, {
|
|
200
223
|
version: 1,
|
|
201
224
|
selectedMode: "build",
|
|
@@ -232,11 +255,11 @@ export default function planBuildModes(pi: ExtensionAPI): void {
|
|
|
232
255
|
},
|
|
233
256
|
});
|
|
234
257
|
if (result.cancelled) {
|
|
235
|
-
|
|
258
|
+
freshImplementationRequest = request;
|
|
236
259
|
ctx.ui.notify("Fresh implementation cancelled; the source plan remains available.", "info");
|
|
237
260
|
}
|
|
238
261
|
} catch (error: unknown) {
|
|
239
|
-
|
|
262
|
+
freshImplementationRequest = request;
|
|
240
263
|
const detail = error instanceof Error ? error.message : String(error);
|
|
241
264
|
try {
|
|
242
265
|
ctx.ui.notify(`Unable to start a fresh implementation session: ${detail}`, "error");
|
|
@@ -296,16 +319,23 @@ export default function planBuildModes(pi: ExtensionAPI): void {
|
|
|
296
319
|
));
|
|
297
320
|
const decision = classifyPlanExitChoice(selection.choice);
|
|
298
321
|
if (decision === "stay") {
|
|
299
|
-
|
|
322
|
+
freshImplementationRequest = undefined;
|
|
300
323
|
pi.appendEntry(MODE_NOTICE_ENTRY_TYPE, { message: PLAN_EXIT_STAY_ACKNOWLEDGEMENT });
|
|
301
324
|
return buildPlanExitStayResult(planPath, selection.cancelled);
|
|
302
325
|
}
|
|
303
326
|
if (decision === "implement-fresh") {
|
|
304
|
-
|
|
305
|
-
|
|
327
|
+
freshImplementationRequest = buildFreshImplementationRequest(
|
|
328
|
+
plan,
|
|
329
|
+
ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
|
|
330
|
+
pi.getThinkingLevel(),
|
|
331
|
+
);
|
|
332
|
+
pi.sendUserMessage("/build-fresh", {
|
|
333
|
+
deliverAs: "followUp",
|
|
334
|
+
expandPromptTemplates: true,
|
|
335
|
+
});
|
|
306
336
|
return buildPlanExitFreshResult(planPath);
|
|
307
337
|
}
|
|
308
|
-
|
|
338
|
+
freshImplementationRequest = undefined;
|
|
309
339
|
await selectMode("build", ctx, "tool");
|
|
310
340
|
return {
|
|
311
341
|
content: [
|
|
@@ -324,7 +354,7 @@ export default function planBuildModes(pi: ExtensionAPI): void {
|
|
|
324
354
|
const details = result.details as { approved?: boolean; action?: string } | undefined;
|
|
325
355
|
if (details?.action === "implement-fresh" && !context.isError) {
|
|
326
356
|
return new Text(
|
|
327
|
-
theme.fg("success", "Clean-session implementation selected —
|
|
357
|
+
theme.fg("success", "Clean-session implementation selected — starting automatically."),
|
|
328
358
|
0,
|
|
329
359
|
0,
|
|
330
360
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@janvitos/pi-plan-build",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "Plan safely, approve explicitly, then implement here or in a clean session.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"prepublishOnly": "npm test"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@earendil-works/pi-coding-agent": "
|
|
36
|
+
"@earendil-works/pi-coding-agent": ">=0.84.2",
|
|
37
37
|
"@earendil-works/pi-tui": "*",
|
|
38
38
|
"typebox": "*"
|
|
39
39
|
},
|
package/utils.ts
CHANGED
|
@@ -166,7 +166,7 @@ export function buildPlanExitFreshResult(planPath: string) {
|
|
|
166
166
|
content: [
|
|
167
167
|
{
|
|
168
168
|
type: "text" as const,
|
|
169
|
-
text: "The user selected clean-session implementation. Stop now;
|
|
169
|
+
text: "The user selected clean-session implementation. Stop now; /build-fresh is starting automatically.",
|
|
170
170
|
},
|
|
171
171
|
],
|
|
172
172
|
details: { approved: true, action: "implement-fresh" as const, mode: "plan" as const, planPath },
|
|
@@ -174,6 +174,20 @@ export function buildPlanExitFreshResult(planPath: string) {
|
|
|
174
174
|
};
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
export interface FreshImplementationRequest {
|
|
178
|
+
plan: string;
|
|
179
|
+
model?: { provider: string; id: string };
|
|
180
|
+
thinkingLevel: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function buildFreshImplementationRequest(
|
|
184
|
+
plan: string,
|
|
185
|
+
model: { provider: string; id: string } | undefined,
|
|
186
|
+
thinkingLevel: string,
|
|
187
|
+
): FreshImplementationRequest {
|
|
188
|
+
return { plan, model, thinkingLevel };
|
|
189
|
+
}
|
|
190
|
+
|
|
177
191
|
export function buildFreshImplementationHandoff(plan: string): string {
|
|
178
192
|
return `Plan mode is now disabled. Full tool access is restored. Implement this approved plan now:\n\n${plan}`;
|
|
179
193
|
}
|