@ilya-lesikov/pi-pi 0.3.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.
@@ -51,7 +51,10 @@ export function createBrainstormReviewerAgent(
51
51
  "- SUGGESTIONS: (improvements, not required)",
52
52
  "- VERDICT: APPROVE or NEEDS_WORK (with reason)",
53
53
  "",
54
- 'You can spawn subagents: Agent(subagent_type="Explore", ...) for codebase, Agent(subagent_type="Librarian", ...) for external docs.',
54
+ "subagent_type is REQUIRED when spawning subagents calls without it are rejected:",
55
+ '- Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
56
+ '- Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
57
+ "Spawn multiple Explore agents in parallel for broad searches.",
55
58
  "",
56
59
  "# MANDATORY: Write your review to this exact file using the write tool:",
57
60
  ` ${outputPath}`,
@@ -65,7 +65,10 @@ export function createCodeReviewerAgent(
65
65
  "- OPEN QUESTIONS: (low-confidence concerns, speculative follow-ups)",
66
66
  "- VERDICT: APPROVE or NEEDS_CHANGES",
67
67
  "",
68
- 'You can spawn subagents: Agent(subagent_type="Explore", ...) for codebase, Agent(subagent_type="Librarian", ...) for external docs.',
68
+ "subagent_type is REQUIRED when spawning subagents calls without it are rejected:",
69
+ '- Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
70
+ '- Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
71
+ "Spawn multiple Explore agents in parallel for broad searches.",
69
72
  "",
70
73
  // --- dynamic suffix ---
71
74
  "# MANDATORY: Write your review to this exact file using the write tool:",
@@ -51,7 +51,10 @@ export function createPlanReviewerAgent(
51
51
  "- SUGGESTIONS: (improvements, not required)",
52
52
  "- VERDICT: APPROVE or REJECT (with reason)",
53
53
  "",
54
- 'You can spawn subagents: Agent(subagent_type="Explore", ...) for codebase, Agent(subagent_type="Librarian", ...) for external docs.',
54
+ "subagent_type is REQUIRED when spawning subagents calls without it are rejected:",
55
+ '- Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
56
+ '- Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
57
+ "Spawn multiple Explore agents in parallel for broad searches.",
55
58
  "",
56
59
  // --- dynamic suffix ---
57
60
  "# MANDATORY OUTPUT",
@@ -40,7 +40,10 @@ export function createPlannerAgent(
40
40
  "- No other top-level sections allowed",
41
41
  "- Describe outcomes, not code-level mechanics",
42
42
  "",
43
- 'You can spawn subagents: Agent(subagent_type="Explore", ...) for codebase, Agent(subagent_type="Librarian", ...) for external docs.',
43
+ "subagent_type is REQUIRED when spawning subagents calls without it are rejected:",
44
+ '- Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
45
+ '- Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
46
+ "Spawn multiple Explore agents in parallel for broad searches.",
44
47
  "",
45
48
  // --- dynamic suffix ---
46
49
  "# MANDATORY OUTPUT",
@@ -28,8 +28,10 @@ export function createTaskAgent(
28
28
  FAILURE_RECOVERY,
29
29
  "",
30
30
  "# Constraints",
31
- "- Do NOT spawn task subagents (no recursion)",
32
- '- You CAN spawn subagents: Agent(subagent_type="Explore", ...) for codebase, Agent(subagent_type="Librarian", ...) for external docs',
31
+ "- Do NOT spawn task subagents (no recursion)",
32
+ "- subagent_type is REQUIRED when spawning subagents calls without it are rejected:",
33
+ ' Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
34
+ ' Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
33
35
  "- Focus only on your subtask — do not modify unrelated code",
34
36
  "- Before modifying a function, use lsp findReferences to understand all callers",
35
37
  "- After editing files, run lsp diagnostics and fix errors before moving on",
@@ -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
- orchestrator.compactAndTransition(ctx, orchestrator.active.dir, orchestrator.active.state.phase);
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
- pi.sendUserMessage(`[PI-PI] ${text}`);
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
- pi.sendUserMessage("[PI-PI] Review cycle is ready for apply_feedback. Read reviewer outputs and proceed.", { deliverAs: "followUp" });
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
- await orchestrator.waitForCompaction();
315
- return { content: [{ type: "text" as const, text: "Phase transition complete." }], details: {} };
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
- await orchestrator.waitForCompaction();
413
- return { content: [{ type: "text" as const, text: "Phase transition complete." }], details: {} };
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
- pi.sendUserMessage(`[PI-PI] Retrying failed planners: ${variantsText}.`, { deliverAs: "followUp" });
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
- pi.sendUserMessage(`[PI-PI] ${await stopTask(orchestrator)}`, { deliverAs: "followUp" });
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
- pi.sendUserMessage("[PI-PI] All planners completed. Read their outputs and synthesize the plan.", { deliverAs: "followUp" });
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
- pi.sendUserMessage(`[PI-PI] Retrying failed reviewers: ${variantsText}.`, { deliverAs: "followUp" });
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
- pi.sendUserMessage(`[PI-PI] ${await stopTask(orchestrator)}`, { deliverAs: "followUp" });
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
- pi.sendUserMessage("[PI-PI] Review cycle skipped. Use /pp to choose the next action.", { deliverAs: "followUp" });
726
+ orchestrator.safeSendUserMessage("[PI-PI] Review cycle skipped. Use /pp to choose the next action.");
726
727
  return;
727
728
  }
728
729
 
@@ -943,7 +944,10 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
943
944
  if (event.toolName === "Agent" && orchestrator.active) {
944
945
  const input = event.input as Record<string, unknown>;
945
946
  const requestedType = ((input.subagent_type as string) || "").toLowerCase();
946
- const isExplore = !requestedType || requestedType === "explore";
947
+ if (!requestedType) {
948
+ return { block: true, reason: "subagent_type is required. Use Explore for codebase research, Librarian for external docs, or Task for implementation subtasks." };
949
+ }
950
+ const isExplore = requestedType === "explore";
947
951
  const isLibrarian = requestedType === "librarian";
948
952
 
949
953
  if (isExplore) {
@@ -1096,9 +1100,11 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
1096
1100
  if (!orchestrator.active || orchestrator.active.state.phase === "done") return;
1097
1101
 
1098
1102
  if (orchestrator.phaseCompactionPending) {
1103
+ const summary = orchestrator.phaseCompactionSummary || "Phase transition in progress.";
1104
+ orchestrator.phaseCompactionSummary = "";
1099
1105
  return {
1100
1106
  compaction: {
1101
- summary: `Previous phase (${orchestrator.active.state.phase}) completed. Transitioning to next phase.`,
1107
+ summary,
1102
1108
  firstKeptEntryId: event.preparation.firstKeptEntryId,
1103
1109
  tokensBefore: event.preparation.tokensBefore,
1104
1110
  },
@@ -1140,6 +1146,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
1140
1146
  }
1141
1147
 
1142
1148
  if (!orchestrator.active || orchestrator.active.state.phase === "done") return;
1149
+ if (orchestrator.phaseCompactionPending || orchestrator.taskDoneCompactionPending) return;
1143
1150
  orchestrator.updateStatus(ctx);
1144
1151
 
1145
1152
  if (
@@ -1183,7 +1190,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
1183
1190
  orchestrator.pendingRetryTimer = setTimeout(() => {
1184
1191
  orchestrator.pendingRetryTimer = null;
1185
1192
  if (orchestrator.activeTaskToken !== taskToken || !orchestrator.active) return;
1186
- pi.sendUserMessage(`[PI-PI] Previous request failed due to an API error. Continue working on the current phase (${phase}).`);
1193
+ orchestrator.safeSendUserMessage(`[PI-PI] Previous request failed due to an API error. Continue working on the current phase (${phase}).`);
1187
1194
  }, delay);
1188
1195
  } else {
1189
1196
  ctx.ui.notify(`API error persisted after 3 retries: ${errorMsg}. Stopping auto-retry.`, "error");
@@ -1205,6 +1212,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
1205
1212
  orchestrator.awaitPollTimer = null;
1206
1213
  return;
1207
1214
  }
1215
+ if (orchestrator.phaseCompactionPending) return;
1208
1216
  const taskDir = orchestrator.active.dir;
1209
1217
  if (orchestrator.active.state.step === "await_planners") {
1210
1218
  const plansDir = join(taskDir, "plans");
@@ -1218,7 +1226,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
1218
1226
  orchestrator.pendingSubagentSpawns = 0;
1219
1227
  orchestrator.active.state.step = "synthesize";
1220
1228
  saveTask(orchestrator.active.dir, orchestrator.active.state);
1221
- pi.sendUserMessage("[PI-PI] All planners completed. Read their outputs and synthesize the plan.");
1229
+ orchestrator.safeSendUserMessage("[PI-PI] All planners completed. Read their outputs and synthesize the plan.");
1222
1230
  }
1223
1231
  }
1224
1232
  } else if (orchestrator.active.state.step === "await_reviewers" && orchestrator.active.state.reviewCycle) {
@@ -1249,7 +1257,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
1249
1257
  { customType: "pp-review-ready", content: `[PI-PI] Reviewer outputs are ready.\n\n${rendered}`, display: false },
1250
1258
  { deliverAs: "followUp" },
1251
1259
  );
1252
- pi.sendUserMessage("[PI-PI] Review cycle is ready for apply_feedback. Read reviewer outputs and proceed.");
1260
+ orchestrator.safeSendUserMessage("[PI-PI] Review cycle is ready for apply_feedback. Read reviewer outputs and proceed.");
1253
1261
  }
1254
1262
  } else {
1255
1263
  clearInterval(orchestrator.awaitPollTimer!);
@@ -1278,19 +1286,16 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
1278
1286
  const endsWithText = lastPart?.type === "text" && lastPart?.text?.trim();
1279
1287
  if (hasText && (!hasToolCalls || endsWithText)) {
1280
1288
  const step = orchestrator.active.state.step;
1281
- if (step !== "await_planners" && step !== "await_reviewers") {
1282
- pi.sendMessage(
1283
- {
1284
- customType: "pp-phase-complete-reminder",
1285
- 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.`,
1286
- display: false,
1287
- },
1288
- { deliverAs: "followUp" },
1289
- );
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.`);
1290
1292
  }
1293
+ } else {
1294
+ orchestrator.textStopReminderSent = false;
1291
1295
  }
1292
1296
  return;
1293
1297
  }
1298
+ orchestrator.textStopReminderSent = false;
1294
1299
  if (orchestrator.nudgeHalted) return;
1295
1300
  if (orchestrator.spawnedAgentIds.size > 0 || orchestrator.pendingSubagentSpawns > 0) return;
1296
1301
 
@@ -1303,14 +1308,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
1303
1308
  orchestrator.nudgeTimestamps = orchestrator.nudgeTimestamps.filter((t) => now - t < 60000);
1304
1309
 
1305
1310
  const sendNudge = () => {
1306
- pi.sendMessage(
1307
- {
1308
- customType: "pp-continuation",
1309
- content: `[PI-PI] Your previous response was interrupted. Continue working on the current phase (${phase}). Pick up where you left off.`,
1310
- display: false,
1311
- },
1312
- { deliverAs: "followUp" },
1313
- );
1311
+ orchestrator.safeSendUserMessage(`[PI-PI] Your previous response was interrupted. Continue working on the current phase (${phase}). Pick up where you left off.`);
1314
1312
  };
1315
1313
 
1316
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
- phaseCompactionResolve: (() => void) | null = null;
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
- `Finishing previous task "${this.active.description}" (phase: ${this.active.state.phase})…`,
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.pi.sendUserMessage(`[PI-PI] Entered ${this.active.state.phase} phase. Begin working.`);
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.phaseCompactionResolve = null;
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
- waitForCompaction(): Promise<void> {
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
- if (this.phaseCompactionResolve) {
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.`);
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
- if (this.phaseCompactionResolve) {
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.`);
507
- }
507
+ finalize();
508
508
  },
509
509
  });
510
510
  }
@@ -14,15 +14,18 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
14
14
  "",
15
15
  "Read-only diagnosis mode.",
16
16
  "",
17
- "# FORBIDDENdo NOT do any of these:",
18
- "- Do NOT modify project source code (no write or edit tools on project files)",
19
- "- Do NOT create or modify any files outside the task directory",
20
- "- Do NOT implement fixesonly diagnose and recommend",
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",
25
- '2. Spawn explore subagents via Agent(subagent_type="Explore", ...) for codebase research',
24
+ "2. Spawn subagents for research (subagent_type is REQUIRED calls without it are rejected):",
25
+ ' - Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups.',
26
+ ' - Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
27
+ ' - Agent(subagent_type="Task", ...) — only when you need a subtask that writes files or runs complex multi-step commands.',
28
+ " Explore is fast and cheap. Use it liberally for codebase questions. Spawn multiple in parallel.",
26
29
  "3. Use bash to run commands, check logs, reproduce issues",
27
30
  "4. Use tools to trace the bug:",
28
31
  " - cbm_search/cbm_search_code: find relevant code by concept or text",
@@ -31,8 +34,6 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
31
34
  " - lsp diagnostics: find type errors",
32
35
  " - cbm_trace: trace call chains to/from suspect functions",
33
36
  " - ast_search: find structural patterns (e.g. error handling, goroutines)",
34
- "",
35
- "IMPORTANT: Always specify subagent_type. Use Explore for codebase, Librarian for external docs.",
36
37
  "",
37
38
  "Produce two files:",
38
39
  `- ${taskDir}/USER_REQUEST.md — the fix request. MUST follow this exact structure:`,
@@ -76,16 +77,22 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
76
77
  "",
77
78
  "# How to work:",
78
79
  "- Discuss the topic with the user. Ask clarifying questions. Propose approaches. Analyze tradeoffs.",
79
- '- Spawn subagents for research: Agent(subagent_type="Explore", ...) for codebase, Agent(subagent_type="Librarian", ...) for external docs.',
80
- " IMPORTANT: Always specify subagent_type. Do NOT omit it.",
80
+ "- Spawn subagents for research (subagent_type is REQUIRED calls without it are rejected):",
81
+ ' Agent(subagent_type="Explore", ...) codebase research. Prefer this for most lookups. Fast and cheap.',
82
+ ' Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
83
+ ' Agent(subagent_type="Task", ...) — only when you need a subtask that writes files or runs complex multi-step commands.',
84
+ " Spawn multiple Explore agents in parallel for broad searches.",
81
85
  "- Use tools directly for quick lookups (cbm_search, lsp, ast_search, grep, etc.)",
82
86
  "- Present findings to the user and discuss them. Don't just dump raw results.",
83
- "- Do NOT modify project source code.",
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.",
84
92
  "",
85
93
  "# When to finish:",
86
94
  "Do NOT call pp_phase_complete on your own. The user will tell you when they're done,",
87
95
  "or use /pp to advance. Keep the conversation going until then.",
88
- "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.",
89
96
  "",
90
97
  "# Optional artifacts (only when the conversation naturally produces them):",
91
98
  "If the discussion leads to a clear action plan or the user asks you to capture conclusions,",
@@ -112,26 +119,26 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
112
119
  "Your job is to produce USER_REQUEST.md and RESEARCH.md — complete enough that",
113
120
  "downstream agents can work without re-exploring the codebase or re-interviewing the user.",
114
121
  "",
115
- "# FORBIDDEN do NOT do any of these:",
116
- "- Do NOT modify project source code (no write or edit tools on project files)",
117
- "- Do NOT create or modify any files outside the task directory",
118
- "- Do NOT start implementingonly research and document",
119
- "- 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.",
120
126
  "",
121
127
  "# Steps:",
122
128
  "1. Clarify requirements with the user if anything is ambiguous",
123
- '2. Spawn explore subagents via Agent(subagent_type="Explore", ...) for codebase research',
124
- '3. Spawn librarian subagents via Agent(subagent_type="Librarian", ...) for external docs/library research',
125
- "4. Use tools to understand code structure:",
129
+ "2. Spawn subagents for research (subagent_type is REQUIRED calls without it are rejected):",
130
+ ' - Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
131
+ ' - Agent(subagent_type="Librarian", ...) external docs, library APIs, web research.',
132
+ ' - Agent(subagent_type="Task", ...) — only when you need a subtask that writes files or runs complex multi-step commands.',
133
+ " Spawn multiple Explore agents in parallel for broad searches.",
134
+ "3. Use tools to understand code structure:",
126
135
  " - cbm_search: natural-language search across all symbols",
127
136
  " - cbm_search_code: graph-augmented grep (deduplicates into functions)",
128
137
  " - lsp documentSymbol, goToDefinition, findReferences, goToImplementation, hover",
129
138
  " - cbm_trace: trace call chains for dependency understanding",
130
139
  " - ast_search: find structural patterns across the codebase",
131
- "5. Ask the user follow-up questions as needed",
132
- "6. Write findings into RESEARCH.md as results come back — don't wait for all subagents",
133
- "",
134
- "IMPORTANT: Always specify subagent_type. Use Explore for codebase, Librarian for external docs.",
140
+ "4. Ask the user follow-up questions as needed",
141
+ "5. Write findings into RESEARCH.md as results come back — don't wait for all subagents",
135
142
  "",
136
143
  "Produce two files:",
137
144
  `- ${taskDir}/USER_REQUEST.md — MUST follow this exact structure:`,
@@ -22,9 +22,11 @@ export function implementationSystemPrompt(taskDir: string): string {
22
22
  " - cbm_trace: trace dependencies across the codebase",
23
23
  " - ast_search: find structural patterns (e.g. error handling conventions)",
24
24
  "6. After completing changes, run cbm_changes to verify blast radius",
25
- '7. For parallelizable, self-contained subtasks, delegate via Agent(subagent_type="Task", ...)',
26
- '8. For codebase research, use Agent(subagent_type="Explore", ...)',
27
- '9. For external docs research, use Agent(subagent_type="Librarian", ...)',
25
+ "7. Spawn subagents as needed (subagent_type is REQUIRED — calls without it are rejected):",
26
+ ' - Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
27
+ ' - Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
28
+ ' - Agent(subagent_type="Task", ...) — parallelizable, self-contained implementation subtasks.',
29
+ " Spawn multiple Explore agents in parallel for broad searches.",
28
30
  "",
29
31
  "If a fix fails 3 times, STOP and re-plan the approach — do not keep pushing.",
30
32
  "",
@@ -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
- "# FORBIDDENdo NOT do any of these:",
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.",
@@ -10,6 +10,11 @@ export function reviewSystemPrompt(taskDir: string): string {
10
10
  "- read, lsp, grep, find for understanding the code",
11
11
  "- gh pr view for GitHub PR context if a PR URL is mentioned",
12
12
  "",
13
+ "Spawn subagents as needed (subagent_type is REQUIRED — calls without it are rejected):",
14
+ '- Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
15
+ '- Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
16
+ "Spawn multiple Explore agents in parallel for broad searches.",
17
+ "",
13
18
  "Write your findings:",
14
19
  `- ${taskDir}/USER_REQUEST.md — update with a clear problem statement of issues found`,
15
20
  `- ${taskDir}/RESEARCH.md — detailed technical analysis`,
@@ -25,6 +30,10 @@ export function reviewSystemPrompt(taskDir: string): string {
25
30
  "- ## Constraints & Edge Cases",
26
31
  "- ## Open Questions (optional)",
27
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
+ "",
28
37
  "Focus on: correctness, edge cases, style consistency, missing tests, potential bugs.",
29
38
  "",
30
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
- pi.sendUserMessage(`[PI-PI] Resumed ${orchestrator.active.state.phase} phase. Read reviewer outputs and apply feedback.`);
347
+ orchestrator.safeSendUserMessage(`[PI-PI] Resumed ${orchestrator.active.state.phase} phase. Read reviewer outputs and apply feedback.`);
348
348
  } else {
349
- pi.sendUserMessage(`[PI-PI] Resumed ${orchestrator.active.state.phase} phase. Continue working.`);
349
+ orchestrator.safeSendUserMessage(`[PI-PI] Resumed ${orchestrator.active.state.phase} phase. Continue working.`);
350
350
  }
351
351
 
352
352
  return { ok: true };
@@ -591,6 +591,9 @@ function showUsage(ctx: any): void {
591
591
  getTotalInputTokens(): number; getTotalOutputTokens(): number;
592
592
  getTotalCacheReadTokens(): number; getTotalCacheWriteTokens(): number;
593
593
  getTotalCost(): number; getCacheHitRate(): number;
594
+ getMainInputTokens(): number; getMainOutputTokens(): number;
595
+ getMainCacheReadTokens(): number; getMainCacheWriteTokens(): number;
596
+ getMainCost(): number;
594
597
  getPerModelUsage(): Record<string, { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; turns: number }>;
595
598
  getSubagentList(): Array<{ description: string; agentType: string; modelId: string; inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; cost: number; durationMs: number; toolUses: number }>;
596
599
  }
@@ -601,31 +604,21 @@ function showUsage(ctx: any): void {
601
604
  return;
602
605
  }
603
606
 
604
- const mainInput = tracker.getTotalInputTokens();
605
- const mainOutput = tracker.getTotalOutputTokens();
606
- const mainCacheRead = tracker.getTotalCacheReadTokens();
607
- const mainCacheWrite = tracker.getTotalCacheWriteTokens();
608
- const mainCost = tracker.getTotalCost();
609
- const models = tracker.getPerModelUsage();
610
- const subagents = tracker.getSubagentList();
611
-
612
- let saInput = 0, saOutput = 0, saCacheRead = 0, saCacheWrite = 0, saCost = 0;
613
- for (const sa of subagents) {
614
- saInput += sa.inputTokens;
615
- saOutput += sa.outputTokens;
616
- saCacheRead += sa.cacheReadTokens;
617
- saCacheWrite += sa.cacheWriteTokens;
618
- saCost += sa.cost;
619
- }
620
-
621
- const totalInput = mainInput + saInput;
622
- const totalOutput = mainOutput + saOutput;
623
- const totalCacheRead = mainCacheRead + saCacheRead;
624
- const totalCost = mainCost + saCost;
607
+ const totalInput = tracker.getTotalInputTokens();
608
+ const totalOutput = tracker.getTotalOutputTokens();
609
+ const totalCacheRead = tracker.getTotalCacheReadTokens();
610
+ const totalCost = tracker.getTotalCost();
625
611
  const totalCacheRate = (totalCacheRead + totalInput) > 0
626
612
  ? totalCacheRead / (totalCacheRead + totalInput)
627
613
  : 0;
628
614
 
615
+ const mainInput = tracker.getMainInputTokens();
616
+ const mainOutput = tracker.getMainOutputTokens();
617
+ const mainCacheRead = tracker.getMainCacheReadTokens();
618
+ const mainCost = tracker.getMainCost();
619
+ const models = tracker.getPerModelUsage();
620
+ const subagents = tracker.getSubagentList();
621
+
629
622
  const byModel = new Map<string, { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number }>();
630
623
  const mainModelEntries = Object.entries(models);
631
624
  const mainTotalTokens = mainModelEntries.reduce((s, [, u]) => s + u.inputTokens + u.outputTokens, 0);
@@ -716,33 +709,46 @@ function showUsage(ctx: any): void {
716
709
  ctx.ui.notify(lines.join("\n"), "info");
717
710
  }
718
711
 
719
- async function showSettingsMenu(orchestrator: Orchestrator, ctx: any, showFlant = true): Promise<typeof BACK> {
712
+ async function showInfoMenu(orchestrator: Orchestrator, ctx: any): Promise<typeof BACK> {
720
713
  while (true) {
721
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" });
722
718
  if (orchestrator.active) {
723
719
  options.push({ title: "Task status", description: "Show current task phase, step, and timing" });
724
720
  }
725
- options.push({ title: "Usage", description: "Show session token usage and cost breakdown" });
726
- options.push({ title: "LSP", description: "Language server status and controls" });
727
- if (showFlant) {
728
- options.push({ title: "Flant AI Infrastructure", description: "Configure corporate AI model provider" });
729
- }
730
721
  options.push({ title: "Back", description: "Return to the previous menu" });
731
722
 
732
- const choice = await selectOption(ctx, "Settings", options);
723
+ const choice = await selectOption(ctx, "Info", options);
733
724
  if (!choice || choice === "Back") return BACK;
734
- if (choice === "Task status") {
735
- showStatus(orchestrator, ctx);
725
+ if (choice === "Subagents") {
726
+ await showSubagentsMenu(ctx);
727
+ continue;
728
+ }
729
+ if (choice === "LSP") {
730
+ await showLspMenu(ctx);
736
731
  continue;
737
732
  }
738
733
  if (choice === "Usage") {
739
734
  showUsage(ctx);
740
735
  continue;
741
736
  }
742
- if (choice === "LSP") {
743
- await showLspMenu(ctx);
737
+ if (choice === "Task status") {
738
+ showStatus(orchestrator, ctx);
744
739
  continue;
745
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;
746
752
  await showFlantInfraMenu(orchestrator, ctx);
747
753
  }
748
754
  }
@@ -1028,56 +1034,72 @@ async function showTaskTypeMenu(
1028
1034
  }
1029
1035
  }
1030
1036
 
1031
- async function showNoActiveMenu(orchestrator: Orchestrator, ctx: any): Promise<string | undefined> {
1037
+ async function showTaskMenu(orchestrator: Orchestrator, ctx: any): Promise<typeof BACK | "started"> {
1032
1038
  while (true) {
1033
- const choice = await selectOption(ctx, "/pp", [
1039
+ const choice = await selectOption(ctx, "Task", [
1034
1040
  { title: "Debug", description: "Diagnose an issue. Then (optionally) fix it" },
1035
1041
  { title: "Brainstorm", description: "Explore and brainstorm. Then (optionally) plan and implement" },
1036
1042
  { title: "Implement", description: "Brainstorm, plan and implement" },
1037
1043
  { title: "Review", description: "Review code changes, diffs, or pull requests" },
1038
1044
  { title: "Resume", description: "Resume a previously unfinished task" },
1039
- { title: "Subagents", description: "Manage running agents" },
1040
- { title: "Settings", description: "LSP, Flant AI, and other configuration" },
1041
- { title: "Back", description: "Close this menu" },
1045
+ { title: "Back", description: "Return to the previous menu" },
1042
1046
  ]);
1043
- if (!choice || choice === "Back") return undefined;
1047
+ if (!choice || choice === "Back") return BACK;
1044
1048
 
1045
1049
  if (choice === "Debug") {
1046
1050
  const result = await showTaskTypeMenu(orchestrator, ctx, "debug", "Describe the task");
1047
- if (result === "started") return undefined;
1051
+ if (result === "started") return "started";
1048
1052
  continue;
1049
1053
  }
1050
1054
 
1051
1055
  if (choice === "Brainstorm") {
1052
1056
  const result = await showTaskTypeMenu(orchestrator, ctx, "brainstorm", "Describe the task");
1053
- if (result === "started") return undefined;
1057
+ if (result === "started") return "started";
1054
1058
  continue;
1055
1059
  }
1056
1060
 
1057
1061
  if (choice === "Implement") {
1058
1062
  const result = await showImplementMenu(orchestrator, ctx);
1059
- if (result === "started") return undefined;
1063
+ if (result === "started") return "started";
1060
1064
  continue;
1061
1065
  }
1062
1066
 
1063
1067
  if (choice === "Review") {
1064
1068
  const result = await showReviewMenu(orchestrator, ctx);
1065
- if (result === "started") return undefined;
1069
+ if (result === "started") return "started";
1066
1070
  continue;
1067
1071
  }
1068
1072
 
1069
1073
  if (choice === "Resume") {
1070
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);
1071
1093
  if (result === "started") return undefined;
1072
1094
  continue;
1073
1095
  }
1074
1096
 
1075
- if (choice === "Subagents") {
1076
- await showSubagentsMenu(ctx);
1097
+ if (choice === "Info") {
1098
+ await showInfoMenu(orchestrator, ctx);
1077
1099
  continue;
1078
1100
  }
1079
1101
 
1080
- await showSettingsMenu(orchestrator, ctx, true);
1102
+ await showSettingsMenu(orchestrator, ctx);
1081
1103
  }
1082
1104
  }
1083
1105
 
@@ -1133,12 +1155,12 @@ export async function showActiveTaskMenu(
1133
1155
  const opt = (title: string, description: string): OptionInput => ({ title, description });
1134
1156
 
1135
1157
  const options: OptionInput[] = [];
1136
- options.push(opt("Finish", "Complete, pause, or continue to next phase"));
1158
+ options.push(opt("Next", "Complete, pause, or continue to next phase"));
1137
1159
  if (!waiting) {
1138
1160
  options.push(opt("Review", "Auto review, Plannotator, or manual review"));
1139
1161
  }
1140
- options.push(opt("Subagents", "Manage running agents"));
1141
- options.push(opt("Settings", "Task status, usage, and LSP"));
1162
+ options.push(opt("Info", "Subagents, LSP, usage, and task status"));
1163
+ options.push(opt("Settings", "Flant AI and other configuration"));
1142
1164
  options.push(opt("Back", "Return to the prompt and keep working"));
1143
1165
 
1144
1166
  const headerLines = [`/pp\n\nTask: ${task.type}\nPhase: ${phase}`];
@@ -1149,12 +1171,12 @@ export async function showActiveTaskMenu(
1149
1171
  return "";
1150
1172
  }
1151
1173
 
1152
- if (choice === "Subagents") {
1153
- await showSubagentsMenu(ctx);
1174
+ if (choice === "Info") {
1175
+ await showInfoMenu(orchestrator, ctx);
1154
1176
  continue;
1155
1177
  }
1156
1178
  if (choice === "Settings") {
1157
- await showSettingsMenu(orchestrator, ctx, false);
1179
+ await showSettingsMenu(orchestrator, ctx);
1158
1180
  continue;
1159
1181
  }
1160
1182
 
@@ -1162,7 +1184,7 @@ export async function showActiveTaskMenu(
1162
1184
  await abortCurrentWork(orchestrator, ctx);
1163
1185
  }
1164
1186
 
1165
- if (choice === "Finish") {
1187
+ if (choice === "Next") {
1166
1188
  const canContinue = phase !== "implement" && !waiting;
1167
1189
  const continueLabel = phase === "plan" ? "Continue to implement" : "Continue to plan & implement";
1168
1190
  const finishOptions: OptionInput[] = [];
@@ -1173,7 +1195,7 @@ export async function showActiveTaskMenu(
1173
1195
  finishOptions.push(opt("Pause", "Suspend task to resume later"));
1174
1196
  finishOptions.push(opt("Back", "Return to the previous menu"));
1175
1197
 
1176
- const finishChoice = await selectOption(ctx, "Finish", finishOptions);
1198
+ const finishChoice = await selectOption(ctx, "Next", finishOptions);
1177
1199
  if (!finishChoice || finishChoice === "Back") continue;
1178
1200
  if (finishChoice === "Pause") {
1179
1201
  const text = await pauseTask(orchestrator, ctx);
@@ -19,6 +19,11 @@ export interface UsageTracker {
19
19
  getTotalCacheReadTokens(): number;
20
20
  getTotalCacheWriteTokens(): number;
21
21
  getTotalCost(): number;
22
+ getMainInputTokens(): number;
23
+ getMainOutputTokens(): number;
24
+ getMainCacheReadTokens(): number;
25
+ getMainCacheWriteTokens(): number;
26
+ getMainCost(): number;
22
27
  getCacheHitRate(): number;
23
28
  getPerModelUsage(): Record<string, ModelUsage>;
24
29
  getSubagentTotals(): { inputTokens: number; outputTokens: number; cost: number };
@@ -203,11 +208,11 @@ export function createUsageTracker(): UsageTracker {
203
208
  },
204
209
 
205
210
  getTotalInputTokens(): number {
206
- return state.totalInputTokens;
211
+ return state.totalInputTokens + state.subagentInputTokens;
207
212
  },
208
213
 
209
214
  getTotalOutputTokens(): number {
210
- return state.totalOutputTokens;
215
+ return state.totalOutputTokens + state.subagentOutputTokens;
211
216
  },
212
217
 
213
218
  getTotalCacheReadTokens(): number {
@@ -219,13 +224,35 @@ export function createUsageTracker(): UsageTracker {
219
224
  },
220
225
 
221
226
  getTotalCost(): number {
227
+ return state.totalCost + state.subagentCost;
228
+ },
229
+
230
+ getMainInputTokens(): number {
231
+ return state.totalInputTokens;
232
+ },
233
+
234
+ getMainOutputTokens(): number {
235
+ return state.totalOutputTokens;
236
+ },
237
+
238
+ getMainCacheReadTokens(): number {
239
+ return state.totalCacheReadTokens;
240
+ },
241
+
242
+ getMainCacheWriteTokens(): number {
243
+ return state.totalCacheWriteTokens;
244
+ },
245
+
246
+ getMainCost(): number {
222
247
  return state.totalCost;
223
248
  },
224
249
 
225
250
  getCacheHitRate(): number {
226
- const denominator = state.totalCacheReadTokens + state.totalInputTokens;
251
+ const totalInput = state.totalInputTokens + state.subagentInputTokens;
252
+ const totalCacheRead = state.totalCacheReadTokens;
253
+ const denominator = totalCacheRead + totalInput;
227
254
  if (denominator <= 0) return 0;
228
- return state.totalCacheReadTokens / denominator;
255
+ return totalCacheRead / denominator;
229
256
  },
230
257
 
231
258
  getPerModelUsage(): Record<string, ModelUsage> {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ilya-lesikov/pi-pi",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Pi-Pi Coding Agent: based on Pi Coding Agent, but twice as good",
5
5
  "keywords": [
6
6
  "pi-package",