@ilya-lesikov/pi-pi 0.4.0 → 0.5.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/extensions/orchestrator/command-handlers.ts +7 -5
- package/extensions/orchestrator/event-handlers.ts +31 -36
- package/extensions/orchestrator/orchestrator.ts +43 -43
- package/extensions/orchestrator/phases/brainstorm.ts +13 -12
- package/extensions/orchestrator/phases/planning.ts +3 -1
- package/extensions/orchestrator/phases/review-task.ts +4 -0
- package/extensions/orchestrator/pp-menu.ts +63 -34
- package/package.json +1 -1
|
@@ -72,15 +72,15 @@ export async function transitionToNextPhase(
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
orchestrator.updateStatus(ctx);
|
|
75
|
+
orchestrator.phaseCompactionSummary = `Phase "${currentPhase}" completed. Now entering "${next}" phase.`;
|
|
75
76
|
|
|
76
77
|
if (next === "plan") {
|
|
77
78
|
orchestrator.active.state.step = "await_planners";
|
|
78
79
|
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (next === "plan") {
|
|
82
|
+
const onReady = next === "plan" ? () => {
|
|
83
|
+
if (!orchestrator.active) return;
|
|
84
84
|
orchestrator.pendingSubagentSpawns = Object.values(orchestrator.config.planners).filter((v) => v.enabled).length;
|
|
85
85
|
orchestrator.failedPlannerVariants = [];
|
|
86
86
|
spawnPlanners(orchestrator.pi, orchestrator.cwd, orchestrator.active.dir, orchestrator.active.taskId, orchestrator.config).then((result) => {
|
|
@@ -94,7 +94,9 @@ export async function transitionToNextPhase(
|
|
|
94
94
|
orchestrator.pendingSubagentSpawns = 0;
|
|
95
95
|
console.error(`[pi-pi] spawnPlanners failed: ${err.message}`);
|
|
96
96
|
});
|
|
97
|
-
}
|
|
97
|
+
} : undefined;
|
|
98
|
+
|
|
99
|
+
orchestrator.compactAndTransition(ctx, orchestrator.active.dir, orchestrator.active.state.phase, onReady);
|
|
98
100
|
|
|
99
101
|
return { ok: true };
|
|
100
102
|
}
|
|
@@ -109,7 +111,7 @@ export function registerCommandHandlers(orchestrator: Orchestrator): void {
|
|
|
109
111
|
const { showPpMenu } = await import("./pp-menu.js");
|
|
110
112
|
const text = await showPpMenu(orchestrator, ctx, "command");
|
|
111
113
|
if (text) {
|
|
112
|
-
|
|
114
|
+
orchestrator.safeSendUserMessage(`[PI-PI] ${text}`);
|
|
113
115
|
}
|
|
114
116
|
},
|
|
115
117
|
});
|
|
@@ -94,7 +94,7 @@ function tryCompleteReviewCycle(orchestrator: Orchestrator): void {
|
|
|
94
94
|
},
|
|
95
95
|
{ deliverAs: "followUp" },
|
|
96
96
|
);
|
|
97
|
-
|
|
97
|
+
orchestrator.safeSendUserMessage("[PI-PI] Review cycle is ready for apply_feedback. Read reviewer outputs and proceed.");
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
export async function enterReviewCycle(orchestrator: Orchestrator, ctx: any, kind: "auto" | "auto-deep" | "plannotator") {
|
|
@@ -204,13 +204,12 @@ export async function enterReviewCycle(orchestrator: Orchestrator, ctx: any, kin
|
|
|
204
204
|
export async function stopTask(orchestrator: Orchestrator): Promise<string> {
|
|
205
205
|
if (!orchestrator.active) return "No active task.";
|
|
206
206
|
orchestrator.abortAllSubagents();
|
|
207
|
-
orchestrator.active.state.phase = "done";
|
|
208
207
|
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
209
208
|
const desc = orchestrator.active.description;
|
|
210
209
|
await orchestrator.cleanupActive();
|
|
211
210
|
const taskStore = (globalThis as any)[Symbol.for("pi-tasks:store")];
|
|
212
211
|
taskStore?.clearAll?.();
|
|
213
|
-
return `Task "${desc}" stopped.`;
|
|
212
|
+
return `Task "${desc}" stopped. Use /pp → Resume to continue.`;
|
|
214
213
|
}
|
|
215
214
|
|
|
216
215
|
export function finalizeReviewCycle(task: ActiveTask): void {
|
|
@@ -311,8 +310,8 @@ function registerSpecifyReviewsTool(orchestrator: Orchestrator): void {
|
|
|
311
310
|
const { showActiveTaskMenu } = await import("./pp-menu.js");
|
|
312
311
|
const text = await showActiveTaskMenu(orchestrator, ctx, `Plannotator review complete.\n\n${summary}`, "tool");
|
|
313
312
|
if (orchestrator.phaseCompactionPending || orchestrator.taskDoneCompactionPending) {
|
|
314
|
-
|
|
315
|
-
return { content: [{ type: "text" as const, text: "
|
|
313
|
+
ctx.abort?.();
|
|
314
|
+
return { content: [{ type: "text" as const, text: "" }], details: {} };
|
|
316
315
|
}
|
|
317
316
|
if (!text) {
|
|
318
317
|
return { content: [{ type: "text" as const, text: "User dismissed the menu. Wait for the user's next message. When you resume work, update USER_REQUEST.md and RESEARCH.md with any new findings before calling pp_phase_complete." }], details: {} };
|
|
@@ -409,8 +408,8 @@ function registerPhaseCompleteTool(orchestrator: Orchestrator): void {
|
|
|
409
408
|
const { showActiveTaskMenu } = await import("./pp-menu.js");
|
|
410
409
|
const text = await showActiveTaskMenu(orchestrator, ctx, params.summary, "tool");
|
|
411
410
|
if (orchestrator.phaseCompactionPending || orchestrator.taskDoneCompactionPending) {
|
|
412
|
-
|
|
413
|
-
return { content: [{ type: "text" as const, text: "
|
|
411
|
+
ctx.abort?.();
|
|
412
|
+
return { content: [{ type: "text" as const, text: "" }], details: {} };
|
|
414
413
|
}
|
|
415
414
|
if (!text) {
|
|
416
415
|
return { content: [{ type: "text" as const, text: "User dismissed the menu. Wait for the user's next message. When you resume work, update USER_REQUEST.md and RESEARCH.md with any new findings before calling pp_phase_complete." }], details: {} };
|
|
@@ -545,7 +544,8 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
545
544
|
orchestrator.active.state.phase !== "plan" ||
|
|
546
545
|
orchestrator.active.state.step !== "await_planners" ||
|
|
547
546
|
orchestrator.spawnedAgentIds.size > 0 ||
|
|
548
|
-
orchestrator.pendingSubagentSpawns > 0
|
|
547
|
+
orchestrator.pendingSubagentSpawns > 0 ||
|
|
548
|
+
orchestrator.phaseCompactionPending
|
|
549
549
|
) return;
|
|
550
550
|
|
|
551
551
|
const plansDir = join(orchestrator.active.dir, "plans");
|
|
@@ -594,13 +594,13 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
594
594
|
console.error(`[pi-pi] retry spawnPlanners failed: ${err.message}`);
|
|
595
595
|
checkPlannerCompletion();
|
|
596
596
|
});
|
|
597
|
-
|
|
597
|
+
orchestrator.safeSendUserMessage(`[PI-PI] Retrying failed planners: ${variantsText}.`);
|
|
598
598
|
return;
|
|
599
599
|
}
|
|
600
600
|
}
|
|
601
601
|
|
|
602
602
|
if (choice === "Stop task") {
|
|
603
|
-
|
|
603
|
+
orchestrator.safeSendUserMessage(`[PI-PI] ${await stopTask(orchestrator)}`);
|
|
604
604
|
return;
|
|
605
605
|
}
|
|
606
606
|
|
|
@@ -632,7 +632,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
632
632
|
orchestrator.failedPlannerVariants = [];
|
|
633
633
|
orchestrator.active.state.step = "synthesize";
|
|
634
634
|
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
635
|
-
|
|
635
|
+
orchestrator.safeSendUserMessage("[PI-PI] All planners completed. Read their outputs and synthesize the plan.");
|
|
636
636
|
}
|
|
637
637
|
|
|
638
638
|
function checkReviewCycleCompletion(): void {
|
|
@@ -640,7 +640,8 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
640
640
|
!orchestrator.active?.state.reviewCycle ||
|
|
641
641
|
orchestrator.active.state.reviewCycle.step !== "await_reviewers" ||
|
|
642
642
|
orchestrator.spawnedAgentIds.size > 0 ||
|
|
643
|
-
orchestrator.pendingSubagentSpawns > 0
|
|
643
|
+
orchestrator.pendingSubagentSpawns > 0 ||
|
|
644
|
+
orchestrator.phaseCompactionPending
|
|
644
645
|
) return;
|
|
645
646
|
|
|
646
647
|
const failedReviewerVariants = [...orchestrator.failedReviewerVariants];
|
|
@@ -705,14 +706,14 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
705
706
|
console.error(`[pi-pi] retry spawn reviewers failed (${phase}): ${err.message}`);
|
|
706
707
|
checkReviewCycleCompletion();
|
|
707
708
|
});
|
|
708
|
-
|
|
709
|
+
orchestrator.safeSendUserMessage(`[PI-PI] Retrying failed reviewers: ${variantsText}.`);
|
|
709
710
|
return;
|
|
710
711
|
}
|
|
711
712
|
}
|
|
712
713
|
}
|
|
713
714
|
|
|
714
715
|
if (choice === "Stop task") {
|
|
715
|
-
|
|
716
|
+
orchestrator.safeSendUserMessage(`[PI-PI] ${await stopTask(orchestrator)}`);
|
|
716
717
|
return;
|
|
717
718
|
}
|
|
718
719
|
|
|
@@ -722,7 +723,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
722
723
|
orchestrator.active.state.reviewCycle = null;
|
|
723
724
|
orchestrator.active.state.step = "user_gate";
|
|
724
725
|
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
725
|
-
|
|
726
|
+
orchestrator.safeSendUserMessage("[PI-PI] Review cycle skipped. Use /pp to choose the next action.");
|
|
726
727
|
return;
|
|
727
728
|
}
|
|
728
729
|
|
|
@@ -1099,9 +1100,11 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
1099
1100
|
if (!orchestrator.active || orchestrator.active.state.phase === "done") return;
|
|
1100
1101
|
|
|
1101
1102
|
if (orchestrator.phaseCompactionPending) {
|
|
1103
|
+
const summary = orchestrator.phaseCompactionSummary || "Phase transition in progress.";
|
|
1104
|
+
orchestrator.phaseCompactionSummary = "";
|
|
1102
1105
|
return {
|
|
1103
1106
|
compaction: {
|
|
1104
|
-
summary
|
|
1107
|
+
summary,
|
|
1105
1108
|
firstKeptEntryId: event.preparation.firstKeptEntryId,
|
|
1106
1109
|
tokensBefore: event.preparation.tokensBefore,
|
|
1107
1110
|
},
|
|
@@ -1143,6 +1146,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
1143
1146
|
}
|
|
1144
1147
|
|
|
1145
1148
|
if (!orchestrator.active || orchestrator.active.state.phase === "done") return;
|
|
1149
|
+
if (orchestrator.phaseCompactionPending || orchestrator.taskDoneCompactionPending) return;
|
|
1146
1150
|
orchestrator.updateStatus(ctx);
|
|
1147
1151
|
|
|
1148
1152
|
if (
|
|
@@ -1186,7 +1190,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
1186
1190
|
orchestrator.pendingRetryTimer = setTimeout(() => {
|
|
1187
1191
|
orchestrator.pendingRetryTimer = null;
|
|
1188
1192
|
if (orchestrator.activeTaskToken !== taskToken || !orchestrator.active) return;
|
|
1189
|
-
|
|
1193
|
+
orchestrator.safeSendUserMessage(`[PI-PI] Previous request failed due to an API error. Continue working on the current phase (${phase}).`);
|
|
1190
1194
|
}, delay);
|
|
1191
1195
|
} else {
|
|
1192
1196
|
ctx.ui.notify(`API error persisted after 3 retries: ${errorMsg}. Stopping auto-retry.`, "error");
|
|
@@ -1208,6 +1212,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
1208
1212
|
orchestrator.awaitPollTimer = null;
|
|
1209
1213
|
return;
|
|
1210
1214
|
}
|
|
1215
|
+
if (orchestrator.phaseCompactionPending) return;
|
|
1211
1216
|
const taskDir = orchestrator.active.dir;
|
|
1212
1217
|
if (orchestrator.active.state.step === "await_planners") {
|
|
1213
1218
|
const plansDir = join(taskDir, "plans");
|
|
@@ -1221,7 +1226,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
1221
1226
|
orchestrator.pendingSubagentSpawns = 0;
|
|
1222
1227
|
orchestrator.active.state.step = "synthesize";
|
|
1223
1228
|
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
1224
|
-
|
|
1229
|
+
orchestrator.safeSendUserMessage("[PI-PI] All planners completed. Read their outputs and synthesize the plan.");
|
|
1225
1230
|
}
|
|
1226
1231
|
}
|
|
1227
1232
|
} else if (orchestrator.active.state.step === "await_reviewers" && orchestrator.active.state.reviewCycle) {
|
|
@@ -1252,7 +1257,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
1252
1257
|
{ customType: "pp-review-ready", content: `[PI-PI] Reviewer outputs are ready.\n\n${rendered}`, display: false },
|
|
1253
1258
|
{ deliverAs: "followUp" },
|
|
1254
1259
|
);
|
|
1255
|
-
|
|
1260
|
+
orchestrator.safeSendUserMessage("[PI-PI] Review cycle is ready for apply_feedback. Read reviewer outputs and proceed.");
|
|
1256
1261
|
}
|
|
1257
1262
|
} else {
|
|
1258
1263
|
clearInterval(orchestrator.awaitPollTimer!);
|
|
@@ -1281,19 +1286,16 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
1281
1286
|
const endsWithText = lastPart?.type === "text" && lastPart?.text?.trim();
|
|
1282
1287
|
if (hasText && (!hasToolCalls || endsWithText)) {
|
|
1283
1288
|
const step = orchestrator.active.state.step;
|
|
1284
|
-
if (step !== "await_planners" && step !== "await_reviewers") {
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
customType: "pp-phase-complete-reminder",
|
|
1288
|
-
content: `[PI-PI] You stopped without calling pp_phase_complete. If you are done with the ${phase} phase, call pp_phase_complete now. If not, continue working.`,
|
|
1289
|
-
display: false,
|
|
1290
|
-
},
|
|
1291
|
-
{ deliverAs: "followUp" },
|
|
1292
|
-
);
|
|
1289
|
+
if (step !== "await_planners" && step !== "await_reviewers" && !orchestrator.textStopReminderSent) {
|
|
1290
|
+
orchestrator.textStopReminderSent = true;
|
|
1291
|
+
orchestrator.safeSendUserMessage(`[PI-PI] You stopped without calling pp_phase_complete. If you are done with the ${phase} phase, call pp_phase_complete now. If not, continue working.`);
|
|
1293
1292
|
}
|
|
1293
|
+
} else {
|
|
1294
|
+
orchestrator.textStopReminderSent = false;
|
|
1294
1295
|
}
|
|
1295
1296
|
return;
|
|
1296
1297
|
}
|
|
1298
|
+
orchestrator.textStopReminderSent = false;
|
|
1297
1299
|
if (orchestrator.nudgeHalted) return;
|
|
1298
1300
|
if (orchestrator.spawnedAgentIds.size > 0 || orchestrator.pendingSubagentSpawns > 0) return;
|
|
1299
1301
|
|
|
@@ -1306,14 +1308,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
1306
1308
|
orchestrator.nudgeTimestamps = orchestrator.nudgeTimestamps.filter((t) => now - t < 60000);
|
|
1307
1309
|
|
|
1308
1310
|
const sendNudge = () => {
|
|
1309
|
-
|
|
1310
|
-
{
|
|
1311
|
-
customType: "pp-continuation",
|
|
1312
|
-
content: `[PI-PI] Your previous response was interrupted. Continue working on the current phase (${phase}). Pick up where you left off.`,
|
|
1313
|
-
display: false,
|
|
1314
|
-
},
|
|
1315
|
-
{ deliverAs: "followUp" },
|
|
1316
|
-
);
|
|
1311
|
+
orchestrator.safeSendUserMessage(`[PI-PI] Your previous response was interrupted. Continue working on the current phase (${phase}). Pick up where you left off.`);
|
|
1317
1312
|
};
|
|
1318
1313
|
|
|
1319
1314
|
if (orchestrator.nudgeTimestamps.length <= 3) {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
loadTask,
|
|
8
8
|
saveTask,
|
|
9
9
|
lockTask,
|
|
10
|
+
appendTaskLog,
|
|
10
11
|
type TaskType,
|
|
11
12
|
type TaskState,
|
|
12
13
|
type Phase,
|
|
@@ -60,7 +61,7 @@ export class Orchestrator {
|
|
|
60
61
|
}>();
|
|
61
62
|
staleAgentTimer: ReturnType<typeof setInterval> | null = null;
|
|
62
63
|
phaseCompactionPending = false;
|
|
63
|
-
|
|
64
|
+
phaseCompactionSummary = "";
|
|
64
65
|
taskDoneCompactionPending = false;
|
|
65
66
|
taskDoneCompactionSummary = "";
|
|
66
67
|
nudgeTimestamps: number[] = [];
|
|
@@ -69,6 +70,7 @@ export class Orchestrator {
|
|
|
69
70
|
pendingSubagentSpawns = 0;
|
|
70
71
|
errorRetryCount = 0;
|
|
71
72
|
commitReminderSent = false;
|
|
73
|
+
textStopReminderSent = false;
|
|
72
74
|
phaseStartTime = 0;
|
|
73
75
|
awaitPollTimer: ReturnType<typeof setInterval> | null = null;
|
|
74
76
|
pendingRetryTimer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -86,6 +88,25 @@ export class Orchestrator {
|
|
|
86
88
|
|
|
87
89
|
constructor(readonly pi: ExtensionAPI) {}
|
|
88
90
|
|
|
91
|
+
safeSendUserMessage(text: string): void {
|
|
92
|
+
const log = (event: string, extra?: Record<string, unknown>) => {
|
|
93
|
+
if (this.active) appendTaskLog(this.active.dir, "debug.jsonl", { timestamp: new Date().toISOString(), event, text: text.slice(0, 200), ...extra });
|
|
94
|
+
};
|
|
95
|
+
const attempt = (retries: number) => {
|
|
96
|
+
try {
|
|
97
|
+
this.pi.sendUserMessage(text);
|
|
98
|
+
log("safeSend_sent", { retries });
|
|
99
|
+
} catch (err: any) {
|
|
100
|
+
if (retries < 30) {
|
|
101
|
+
setTimeout(() => attempt(retries + 1), 1000);
|
|
102
|
+
} else {
|
|
103
|
+
log("safeSend_failed", { error: err?.message ?? String(err), retries });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
attempt(0);
|
|
108
|
+
}
|
|
109
|
+
|
|
89
110
|
truncateResult(result: string): string {
|
|
90
111
|
const trimmed = result.trim();
|
|
91
112
|
if (!trimmed) return "";
|
|
@@ -247,11 +268,10 @@ export class Orchestrator {
|
|
|
247
268
|
): Promise<void> {
|
|
248
269
|
if (this.active) {
|
|
249
270
|
ctx.ui.notify(
|
|
250
|
-
`
|
|
271
|
+
`Pausing previous task "${this.active.description}" (phase: ${this.active.state.phase})…`,
|
|
251
272
|
"info",
|
|
252
273
|
);
|
|
253
274
|
this.abortAllSubagents();
|
|
254
|
-
this.active.state.phase = "done";
|
|
255
275
|
saveTask(this.active.dir, this.active.state);
|
|
256
276
|
unregisterAgentDefinitions(this.pi);
|
|
257
277
|
await this.cleanupActive();
|
|
@@ -344,7 +364,7 @@ export class Orchestrator {
|
|
|
344
364
|
} else if (isWaitingForPlanners) {
|
|
345
365
|
ctx.ui.notify("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
|
|
346
366
|
} else {
|
|
347
|
-
this.
|
|
367
|
+
this.safeSendUserMessage(`[PI-PI] Entered ${this.active.state.phase} phase. Begin working.`);
|
|
348
368
|
}
|
|
349
369
|
|
|
350
370
|
if (this.active.state.phase === "plan" && this.active.state.step === "await_planners") {
|
|
@@ -383,11 +403,12 @@ export class Orchestrator {
|
|
|
383
403
|
this.pendingSubagentSpawns = 0;
|
|
384
404
|
this.errorRetryCount = 0;
|
|
385
405
|
this.commitReminderSent = false;
|
|
406
|
+
this.textStopReminderSent = false;
|
|
386
407
|
this.nudgeTimestamps = [];
|
|
387
408
|
this.cooldownHits = [];
|
|
388
409
|
this.nudgeHalted = false;
|
|
389
410
|
this.phaseCompactionPending = false;
|
|
390
|
-
this.
|
|
411
|
+
this.phaseCompactionSummary = "";
|
|
391
412
|
this.phaseStartTime = 0;
|
|
392
413
|
this.userGatePending = false;
|
|
393
414
|
this.reviewTransitionToken = -1;
|
|
@@ -458,53 +479,32 @@ export class Orchestrator {
|
|
|
458
479
|
}
|
|
459
480
|
}
|
|
460
481
|
|
|
461
|
-
|
|
462
|
-
if (!this.phaseCompactionPending && !this.taskDoneCompactionPending) return Promise.resolve();
|
|
463
|
-
return new Promise((resolve) => {
|
|
464
|
-
this.phaseCompactionResolve = resolve;
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
compactAndTransition(ctx: ExtensionContext, taskDir: string, phase: Phase): void {
|
|
482
|
+
compactAndTransition(ctx: ExtensionContext, taskDir: string, phase: Phase, onReady?: () => void): void {
|
|
469
483
|
this.phaseCompactionPending = true;
|
|
484
|
+
const finalize = () => {
|
|
485
|
+
this.phaseStartTime = Date.now();
|
|
486
|
+
if (this.active && (phase === "plan" || phase === "implement")) {
|
|
487
|
+
const modelConfig = this.config.mainModel.implement;
|
|
488
|
+
this.switchModel(ctx, modelConfig.model, modelConfig.thinking).catch(() => {});
|
|
489
|
+
}
|
|
490
|
+
this.injectContextAndArtifacts(taskDir, phase);
|
|
491
|
+
onReady?.();
|
|
492
|
+
if (this.active?.state.phase === "plan" && this.active.state.step === "await_planners") {
|
|
493
|
+
ctx.ui.notify("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
|
|
494
|
+
} else {
|
|
495
|
+
this.safeSendUserMessage(`[PI-PI] Entered ${phase} phase. Begin working.`);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
470
498
|
ctx.compact({
|
|
471
499
|
customInstructions: "Phase transition — discard all prior conversation. Produce a one-line summary: 'Previous phase completed.'",
|
|
472
500
|
onComplete: () => {
|
|
473
501
|
this.phaseCompactionPending = false;
|
|
474
|
-
|
|
475
|
-
this.phaseCompactionResolve();
|
|
476
|
-
this.phaseCompactionResolve = null;
|
|
477
|
-
}
|
|
478
|
-
this.phaseStartTime = Date.now();
|
|
479
|
-
if (this.active && (phase === "plan" || phase === "implement")) {
|
|
480
|
-
const modelConfig = this.config.mainModel.implement;
|
|
481
|
-
this.switchModel(ctx, modelConfig.model, modelConfig.thinking).catch(() => {});
|
|
482
|
-
}
|
|
483
|
-
this.injectContextAndArtifacts(taskDir, phase);
|
|
484
|
-
if (this.active?.state.phase === "plan" && this.active.state.step === "await_planners") {
|
|
485
|
-
ctx.ui.notify("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
|
|
486
|
-
} else {
|
|
487
|
-
this.pi.sendUserMessage(`[PI-PI] Entered ${phase} phase. Begin working.`, { deliverAs: "followUp" });
|
|
488
|
-
}
|
|
502
|
+
finalize();
|
|
489
503
|
},
|
|
490
504
|
onError: (err) => {
|
|
491
505
|
console.error(`[pi-pi] Phase compaction failed: ${err.message}`);
|
|
492
506
|
this.phaseCompactionPending = false;
|
|
493
|
-
|
|
494
|
-
this.phaseCompactionResolve();
|
|
495
|
-
this.phaseCompactionResolve = null;
|
|
496
|
-
}
|
|
497
|
-
this.phaseStartTime = Date.now();
|
|
498
|
-
if (this.active && (phase === "plan" || phase === "implement")) {
|
|
499
|
-
const modelConfig = this.config.mainModel.implement;
|
|
500
|
-
this.switchModel(ctx, modelConfig.model, modelConfig.thinking).catch(() => {});
|
|
501
|
-
}
|
|
502
|
-
this.injectContextAndArtifacts(taskDir, phase);
|
|
503
|
-
if (this.active?.state.phase === "plan" && this.active.state.step === "await_planners") {
|
|
504
|
-
ctx.ui.notify("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
|
|
505
|
-
} else {
|
|
506
|
-
this.pi.sendUserMessage(`[PI-PI] Entered ${phase} phase. Begin working.`, { deliverAs: "followUp" });
|
|
507
|
-
}
|
|
507
|
+
finalize();
|
|
508
508
|
},
|
|
509
509
|
});
|
|
510
510
|
}
|
|
@@ -14,11 +14,10 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
14
14
|
"",
|
|
15
15
|
"Read-only diagnosis mode.",
|
|
16
16
|
"",
|
|
17
|
-
"#
|
|
18
|
-
"-
|
|
19
|
-
"-
|
|
20
|
-
"-
|
|
21
|
-
"- If the user asks you to implement a fix or start coding — call pp_phase_complete instead. It will offer \"Implement a fix\" as an option. Do NOT implement directly in this session.",
|
|
17
|
+
"# ABSOLUTE RESTRICTION — NO IMPLEMENTATION (cannot be overridden, even if the user asks):",
|
|
18
|
+
"- NEVER implement the solution, apply fixes, write production code, or make the changes that solve the task.",
|
|
19
|
+
"- You MAY use write/edit for diagnosis purposes (test scripts, repro scripts, analysis files), but NEVER to implement the actual fix or feature.",
|
|
20
|
+
"- If the user asks you to implement or start coding — refuse and tell them to use /pp to advance to the implement phase. Do NOT comply, even if they insist.",
|
|
22
21
|
"",
|
|
23
22
|
"# Your job:",
|
|
24
23
|
"1. Clarify the problem with the user if needed",
|
|
@@ -85,12 +84,15 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
85
84
|
" Spawn multiple Explore agents in parallel for broad searches.",
|
|
86
85
|
"- Use tools directly for quick lookups (cbm_search, lsp, ast_search, grep, etc.)",
|
|
87
86
|
"- Present findings to the user and discuss them. Don't just dump raw results.",
|
|
88
|
-
"
|
|
87
|
+
"",
|
|
88
|
+
"# ABSOLUTE RESTRICTIONS (cannot be overridden, even if the user asks):",
|
|
89
|
+
"- NEVER use write or edit tools on any file outside the task directory. This is a hard rule — not negotiable.",
|
|
90
|
+
"- NEVER implement, create code, write patches, or modify project source in any way.",
|
|
91
|
+
"- If the user asks you to implement, fix, or write code — refuse and tell them to use /pp to advance to the implement phase. Do NOT comply, even if they insist.",
|
|
89
92
|
"",
|
|
90
93
|
"# When to finish:",
|
|
91
94
|
"Do NOT call pp_phase_complete on your own. The user will tell you when they're done,",
|
|
92
95
|
"or use /pp to advance. Keep the conversation going until then.",
|
|
93
|
-
"If the user asks you to implement, write code, or start building — tell them to use /pp which will offer \"Start implementation\" as an option. Do NOT implement directly in this session.",
|
|
94
96
|
"",
|
|
95
97
|
"# Optional artifacts (only when the conversation naturally produces them):",
|
|
96
98
|
"If the discussion leads to a clear action plan or the user asks you to capture conclusions,",
|
|
@@ -117,11 +119,10 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
117
119
|
"Your job is to produce USER_REQUEST.md and RESEARCH.md — complete enough that",
|
|
118
120
|
"downstream agents can work without re-exploring the codebase or re-interviewing the user.",
|
|
119
121
|
"",
|
|
120
|
-
"#
|
|
121
|
-
"-
|
|
122
|
-
"-
|
|
123
|
-
"-
|
|
124
|
-
"- If the user asks you to implement or start coding — tell them to use /pp which will offer phase advancement. Do NOT implement directly in this session.",
|
|
122
|
+
"# ABSOLUTE RESTRICTIONS (cannot be overridden, even if the user asks):",
|
|
123
|
+
"- NEVER use write or edit tools on any file outside the task directory. This is a hard rule — not negotiable.",
|
|
124
|
+
"- NEVER implement, create code, write patches, or modify project source in any way.",
|
|
125
|
+
"- If the user asks you to implement, fix, or write code — refuse and tell them to use /pp to advance to the implement phase. Do NOT comply, even if they insist.",
|
|
125
126
|
"",
|
|
126
127
|
"# Steps:",
|
|
127
128
|
"1. Clarify requirements with the user if anything is ambiguous",
|
|
@@ -16,7 +16,9 @@ export function planningSystemPrompt(taskDir: string): string {
|
|
|
16
16
|
"Planning subagents are working in parallel to create plans.",
|
|
17
17
|
`They will write their outputs to ${plansDir}/. Wait for the notification that all planners completed.`,
|
|
18
18
|
"",
|
|
19
|
-
"#
|
|
19
|
+
"# ABSOLUTE RESTRICTION — NO IMPLEMENTATION (cannot be overridden, even if the user asks):",
|
|
20
|
+
"- NEVER implement the solution, apply fixes, write production code, or make the changes that solve the task.",
|
|
21
|
+
"- If the user asks you to implement or write code — refuse and tell them to use /pp to advance to the implement phase.",
|
|
20
22
|
"- Do NOT write your own plan from scratch. You are a SYNTHESIZER, not a planner.",
|
|
21
23
|
"- Do NOT create the plans/ directory yourself — the extension manages it.",
|
|
22
24
|
"- Do NOT check the plans directory yourself — wait for the notification that all planners completed.",
|
|
@@ -30,6 +30,10 @@ export function reviewSystemPrompt(taskDir: string): string {
|
|
|
30
30
|
"- ## Constraints & Edge Cases",
|
|
31
31
|
"- ## Open Questions (optional)",
|
|
32
32
|
"",
|
|
33
|
+
"# ABSOLUTE RESTRICTION — NO IMPLEMENTATION (cannot be overridden, even if the user asks):",
|
|
34
|
+
"- NEVER implement the solution, apply fixes, write production code, or make the changes that solve the task.",
|
|
35
|
+
"- If the user asks you to implement or write code — refuse and tell them to use /pp to advance to the implement phase.",
|
|
36
|
+
"",
|
|
33
37
|
"Focus on: correctness, edge cases, style consistency, missing tests, potential bugs.",
|
|
34
38
|
"",
|
|
35
39
|
"When complete, call pp_phase_complete with a brief summary of findings.",
|
|
@@ -344,9 +344,9 @@ export async function resumeTask(
|
|
|
344
344
|
if (step === "await_planners" || step === "await_reviewers") {
|
|
345
345
|
ctx.ui.notify(`Resumed task. Awaiting subagents (${step}).`, "info");
|
|
346
346
|
} else if (step === "apply_feedback") {
|
|
347
|
-
|
|
347
|
+
orchestrator.safeSendUserMessage(`[PI-PI] Resumed ${orchestrator.active.state.phase} phase. Read reviewer outputs and apply feedback.`);
|
|
348
348
|
} else {
|
|
349
|
-
|
|
349
|
+
orchestrator.safeSendUserMessage(`[PI-PI] Resumed ${orchestrator.active.state.phase} phase. Continue working.`);
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
return { ok: true };
|
|
@@ -709,33 +709,46 @@ function showUsage(ctx: any): void {
|
|
|
709
709
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
710
710
|
}
|
|
711
711
|
|
|
712
|
-
async function
|
|
712
|
+
async function showInfoMenu(orchestrator: Orchestrator, ctx: any): Promise<typeof BACK> {
|
|
713
713
|
while (true) {
|
|
714
714
|
const options: OptionInput[] = [];
|
|
715
|
+
options.push({ title: "Subagents", description: "Manage running agents" });
|
|
716
|
+
options.push({ title: "LSP", description: "Language server status and controls" });
|
|
717
|
+
options.push({ title: "Usage", description: "Show session token usage and cost breakdown" });
|
|
715
718
|
if (orchestrator.active) {
|
|
716
719
|
options.push({ title: "Task status", description: "Show current task phase, step, and timing" });
|
|
717
720
|
}
|
|
718
|
-
options.push({ title: "Usage", description: "Show session token usage and cost breakdown" });
|
|
719
|
-
options.push({ title: "LSP", description: "Language server status and controls" });
|
|
720
|
-
if (showFlant) {
|
|
721
|
-
options.push({ title: "Flant AI Infrastructure", description: "Configure corporate AI model provider" });
|
|
722
|
-
}
|
|
723
721
|
options.push({ title: "Back", description: "Return to the previous menu" });
|
|
724
722
|
|
|
725
|
-
const choice = await selectOption(ctx, "
|
|
723
|
+
const choice = await selectOption(ctx, "Info", options);
|
|
726
724
|
if (!choice || choice === "Back") return BACK;
|
|
727
|
-
if (choice === "
|
|
728
|
-
|
|
725
|
+
if (choice === "Subagents") {
|
|
726
|
+
await showSubagentsMenu(ctx);
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
if (choice === "LSP") {
|
|
730
|
+
await showLspMenu(ctx);
|
|
729
731
|
continue;
|
|
730
732
|
}
|
|
731
733
|
if (choice === "Usage") {
|
|
732
734
|
showUsage(ctx);
|
|
733
735
|
continue;
|
|
734
736
|
}
|
|
735
|
-
if (choice === "
|
|
736
|
-
|
|
737
|
+
if (choice === "Task status") {
|
|
738
|
+
showStatus(orchestrator, ctx);
|
|
737
739
|
continue;
|
|
738
740
|
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
async function showSettingsMenu(orchestrator: Orchestrator, ctx: any): Promise<typeof BACK> {
|
|
745
|
+
while (true) {
|
|
746
|
+
const options: OptionInput[] = [];
|
|
747
|
+
options.push({ title: "Flant AI Infrastructure", description: "Configure corporate AI model provider" });
|
|
748
|
+
options.push({ title: "Back", description: "Return to the previous menu" });
|
|
749
|
+
|
|
750
|
+
const choice = await selectOption(ctx, "Settings", options);
|
|
751
|
+
if (!choice || choice === "Back") return BACK;
|
|
739
752
|
await showFlantInfraMenu(orchestrator, ctx);
|
|
740
753
|
}
|
|
741
754
|
}
|
|
@@ -1021,56 +1034,72 @@ async function showTaskTypeMenu(
|
|
|
1021
1034
|
}
|
|
1022
1035
|
}
|
|
1023
1036
|
|
|
1024
|
-
async function
|
|
1037
|
+
async function showTaskMenu(orchestrator: Orchestrator, ctx: any): Promise<typeof BACK | "started"> {
|
|
1025
1038
|
while (true) {
|
|
1026
|
-
const choice = await selectOption(ctx, "
|
|
1039
|
+
const choice = await selectOption(ctx, "Task", [
|
|
1027
1040
|
{ title: "Debug", description: "Diagnose an issue. Then (optionally) fix it" },
|
|
1028
1041
|
{ title: "Brainstorm", description: "Explore and brainstorm. Then (optionally) plan and implement" },
|
|
1029
1042
|
{ title: "Implement", description: "Brainstorm, plan and implement" },
|
|
1030
1043
|
{ title: "Review", description: "Review code changes, diffs, or pull requests" },
|
|
1031
1044
|
{ title: "Resume", description: "Resume a previously unfinished task" },
|
|
1032
|
-
{ title: "
|
|
1033
|
-
{ title: "Settings", description: "LSP, Flant AI, and other configuration" },
|
|
1034
|
-
{ title: "Back", description: "Close this menu" },
|
|
1045
|
+
{ title: "Back", description: "Return to the previous menu" },
|
|
1035
1046
|
]);
|
|
1036
|
-
if (!choice || choice === "Back") return
|
|
1047
|
+
if (!choice || choice === "Back") return BACK;
|
|
1037
1048
|
|
|
1038
1049
|
if (choice === "Debug") {
|
|
1039
1050
|
const result = await showTaskTypeMenu(orchestrator, ctx, "debug", "Describe the task");
|
|
1040
|
-
if (result === "started") return
|
|
1051
|
+
if (result === "started") return "started";
|
|
1041
1052
|
continue;
|
|
1042
1053
|
}
|
|
1043
1054
|
|
|
1044
1055
|
if (choice === "Brainstorm") {
|
|
1045
1056
|
const result = await showTaskTypeMenu(orchestrator, ctx, "brainstorm", "Describe the task");
|
|
1046
|
-
if (result === "started") return
|
|
1057
|
+
if (result === "started") return "started";
|
|
1047
1058
|
continue;
|
|
1048
1059
|
}
|
|
1049
1060
|
|
|
1050
1061
|
if (choice === "Implement") {
|
|
1051
1062
|
const result = await showImplementMenu(orchestrator, ctx);
|
|
1052
|
-
if (result === "started") return
|
|
1063
|
+
if (result === "started") return "started";
|
|
1053
1064
|
continue;
|
|
1054
1065
|
}
|
|
1055
1066
|
|
|
1056
1067
|
if (choice === "Review") {
|
|
1057
1068
|
const result = await showReviewMenu(orchestrator, ctx);
|
|
1058
|
-
if (result === "started") return
|
|
1069
|
+
if (result === "started") return "started";
|
|
1059
1070
|
continue;
|
|
1060
1071
|
}
|
|
1061
1072
|
|
|
1062
1073
|
if (choice === "Resume") {
|
|
1063
1074
|
const result = await showResumeMenu(orchestrator, ctx, undefined, "No paused tasks found.");
|
|
1075
|
+
if (result === "started") return "started";
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
async function showNoActiveMenu(orchestrator: Orchestrator, ctx: any): Promise<string | undefined> {
|
|
1082
|
+
while (true) {
|
|
1083
|
+
const choice = await selectOption(ctx, "/pp", [
|
|
1084
|
+
{ title: "Task", description: "Start a new task or resume a paused one" },
|
|
1085
|
+
{ title: "Info", description: "Subagents, LSP, usage, and task status" },
|
|
1086
|
+
{ title: "Settings", description: "Flant AI and other configuration" },
|
|
1087
|
+
{ title: "Back", description: "Close this menu" },
|
|
1088
|
+
]);
|
|
1089
|
+
if (!choice || choice === "Back") return undefined;
|
|
1090
|
+
|
|
1091
|
+
if (choice === "Task") {
|
|
1092
|
+
const result = await showTaskMenu(orchestrator, ctx);
|
|
1064
1093
|
if (result === "started") return undefined;
|
|
1065
1094
|
continue;
|
|
1066
1095
|
}
|
|
1067
1096
|
|
|
1068
|
-
if (choice === "
|
|
1069
|
-
await
|
|
1097
|
+
if (choice === "Info") {
|
|
1098
|
+
await showInfoMenu(orchestrator, ctx);
|
|
1070
1099
|
continue;
|
|
1071
1100
|
}
|
|
1072
1101
|
|
|
1073
|
-
await showSettingsMenu(orchestrator, ctx
|
|
1102
|
+
await showSettingsMenu(orchestrator, ctx);
|
|
1074
1103
|
}
|
|
1075
1104
|
}
|
|
1076
1105
|
|
|
@@ -1126,12 +1155,12 @@ export async function showActiveTaskMenu(
|
|
|
1126
1155
|
const opt = (title: string, description: string): OptionInput => ({ title, description });
|
|
1127
1156
|
|
|
1128
1157
|
const options: OptionInput[] = [];
|
|
1129
|
-
options.push(opt("
|
|
1158
|
+
options.push(opt("Next", "Complete, pause, or continue to next phase"));
|
|
1130
1159
|
if (!waiting) {
|
|
1131
1160
|
options.push(opt("Review", "Auto review, Plannotator, or manual review"));
|
|
1132
1161
|
}
|
|
1133
|
-
options.push(opt("
|
|
1134
|
-
options.push(opt("Settings", "
|
|
1162
|
+
options.push(opt("Info", "Subagents, LSP, usage, and task status"));
|
|
1163
|
+
options.push(opt("Settings", "Flant AI and other configuration"));
|
|
1135
1164
|
options.push(opt("Back", "Return to the prompt and keep working"));
|
|
1136
1165
|
|
|
1137
1166
|
const headerLines = [`/pp\n\nTask: ${task.type}\nPhase: ${phase}`];
|
|
@@ -1142,12 +1171,12 @@ export async function showActiveTaskMenu(
|
|
|
1142
1171
|
return "";
|
|
1143
1172
|
}
|
|
1144
1173
|
|
|
1145
|
-
if (choice === "
|
|
1146
|
-
await
|
|
1174
|
+
if (choice === "Info") {
|
|
1175
|
+
await showInfoMenu(orchestrator, ctx);
|
|
1147
1176
|
continue;
|
|
1148
1177
|
}
|
|
1149
1178
|
if (choice === "Settings") {
|
|
1150
|
-
await showSettingsMenu(orchestrator, ctx
|
|
1179
|
+
await showSettingsMenu(orchestrator, ctx);
|
|
1151
1180
|
continue;
|
|
1152
1181
|
}
|
|
1153
1182
|
|
|
@@ -1155,7 +1184,7 @@ export async function showActiveTaskMenu(
|
|
|
1155
1184
|
await abortCurrentWork(orchestrator, ctx);
|
|
1156
1185
|
}
|
|
1157
1186
|
|
|
1158
|
-
if (choice === "
|
|
1187
|
+
if (choice === "Next") {
|
|
1159
1188
|
const canContinue = phase !== "implement" && !waiting;
|
|
1160
1189
|
const continueLabel = phase === "plan" ? "Continue to implement" : "Continue to plan & implement";
|
|
1161
1190
|
const finishOptions: OptionInput[] = [];
|
|
@@ -1166,7 +1195,7 @@ export async function showActiveTaskMenu(
|
|
|
1166
1195
|
finishOptions.push(opt("Pause", "Suspend task to resume later"));
|
|
1167
1196
|
finishOptions.push(opt("Back", "Return to the previous menu"));
|
|
1168
1197
|
|
|
1169
|
-
const finishChoice = await selectOption(ctx, "
|
|
1198
|
+
const finishChoice = await selectOption(ctx, "Next", finishOptions);
|
|
1170
1199
|
if (!finishChoice || finishChoice === "Back") continue;
|
|
1171
1200
|
if (finishChoice === "Pause") {
|
|
1172
1201
|
const text = await pauseTask(orchestrator, ctx);
|