@ilya-lesikov/pi-pi 0.3.0 → 0.4.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",
@@ -109,7 +109,7 @@ export function registerCommandHandlers(orchestrator: Orchestrator): void {
109
109
  const { showPpMenu } = await import("./pp-menu.js");
110
110
  const text = await showPpMenu(orchestrator, ctx, "command");
111
111
  if (text) {
112
- pi.sendUserMessage(`[PI-PI] ${text}`);
112
+ pi.sendUserMessage(`[PI-PI] ${text}`, { deliverAs: "followUp" });
113
113
  }
114
114
  },
115
115
  });
@@ -943,7 +943,10 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
943
943
  if (event.toolName === "Agent" && orchestrator.active) {
944
944
  const input = event.input as Record<string, unknown>;
945
945
  const requestedType = ((input.subagent_type as string) || "").toLowerCase();
946
- const isExplore = !requestedType || requestedType === "explore";
946
+ if (!requestedType) {
947
+ return { block: true, reason: "subagent_type is required. Use Explore for codebase research, Librarian for external docs, or Task for implementation subtasks." };
948
+ }
949
+ const isExplore = requestedType === "explore";
947
950
  const isLibrarian = requestedType === "librarian";
948
951
 
949
952
  if (isExplore) {
@@ -1183,7 +1186,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
1183
1186
  orchestrator.pendingRetryTimer = setTimeout(() => {
1184
1187
  orchestrator.pendingRetryTimer = null;
1185
1188
  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}).`);
1189
+ pi.sendUserMessage(`[PI-PI] Previous request failed due to an API error. Continue working on the current phase (${phase}).`, { deliverAs: "followUp" });
1187
1190
  }, delay);
1188
1191
  } else {
1189
1192
  ctx.ui.notify(`API error persisted after 3 retries: ${errorMsg}. Stopping auto-retry.`, "error");
@@ -1218,7 +1221,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
1218
1221
  orchestrator.pendingSubagentSpawns = 0;
1219
1222
  orchestrator.active.state.step = "synthesize";
1220
1223
  saveTask(orchestrator.active.dir, orchestrator.active.state);
1221
- pi.sendUserMessage("[PI-PI] All planners completed. Read their outputs and synthesize the plan.");
1224
+ pi.sendUserMessage("[PI-PI] All planners completed. Read their outputs and synthesize the plan.", { deliverAs: "followUp" });
1222
1225
  }
1223
1226
  }
1224
1227
  } else if (orchestrator.active.state.step === "await_reviewers" && orchestrator.active.state.reviewCycle) {
@@ -1249,7 +1252,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
1249
1252
  { customType: "pp-review-ready", content: `[PI-PI] Reviewer outputs are ready.\n\n${rendered}`, display: false },
1250
1253
  { deliverAs: "followUp" },
1251
1254
  );
1252
- pi.sendUserMessage("[PI-PI] Review cycle is ready for apply_feedback. Read reviewer outputs and proceed.");
1255
+ pi.sendUserMessage("[PI-PI] Review cycle is ready for apply_feedback. Read reviewer outputs and proceed.", { deliverAs: "followUp" });
1253
1256
  }
1254
1257
  } else {
1255
1258
  clearInterval(orchestrator.awaitPollTimer!);
@@ -344,7 +344,7 @@ export class Orchestrator {
344
344
  } else if (isWaitingForPlanners) {
345
345
  ctx.ui.notify("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
346
346
  } else {
347
- this.pi.sendUserMessage(`[PI-PI] Entered ${this.active.state.phase} phase. Begin working.`);
347
+ this.pi.sendUserMessage(`[PI-PI] Entered ${this.active.state.phase} phase. Begin working.`, { deliverAs: "followUp" });
348
348
  }
349
349
 
350
350
  if (this.active.state.phase === "plan" && this.active.state.step === "await_planners") {
@@ -484,7 +484,7 @@ export class Orchestrator {
484
484
  if (this.active?.state.phase === "plan" && this.active.state.step === "await_planners") {
485
485
  ctx.ui.notify("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
486
486
  } else {
487
- this.pi.sendUserMessage(`[PI-PI] Entered ${phase} phase. Begin working.`);
487
+ this.pi.sendUserMessage(`[PI-PI] Entered ${phase} phase. Begin working.`, { deliverAs: "followUp" });
488
488
  }
489
489
  },
490
490
  onError: (err) => {
@@ -503,7 +503,7 @@ export class Orchestrator {
503
503
  if (this.active?.state.phase === "plan" && this.active.state.step === "await_planners") {
504
504
  ctx.ui.notify("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
505
505
  } else {
506
- this.pi.sendUserMessage(`[PI-PI] Entered ${phase} phase. Begin working.`);
506
+ this.pi.sendUserMessage(`[PI-PI] Entered ${phase} phase. Begin working.`, { deliverAs: "followUp" });
507
507
  }
508
508
  },
509
509
  });
@@ -22,7 +22,11 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
22
22
  "",
23
23
  "# Your job:",
24
24
  "1. Clarify the problem with the user if needed",
25
- '2. Spawn explore subagents via Agent(subagent_type="Explore", ...) for codebase research',
25
+ "2. Spawn subagents for research (subagent_type is REQUIRED calls without it are rejected):",
26
+ ' - Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups.',
27
+ ' - Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
28
+ ' - Agent(subagent_type="Task", ...) — only when you need a subtask that writes files or runs complex multi-step commands.',
29
+ " Explore is fast and cheap. Use it liberally for codebase questions. Spawn multiple in parallel.",
26
30
  "3. Use bash to run commands, check logs, reproduce issues",
27
31
  "4. Use tools to trace the bug:",
28
32
  " - cbm_search/cbm_search_code: find relevant code by concept or text",
@@ -31,8 +35,6 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
31
35
  " - lsp diagnostics: find type errors",
32
36
  " - cbm_trace: trace call chains to/from suspect functions",
33
37
  " - 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
38
  "",
37
39
  "Produce two files:",
38
40
  `- ${taskDir}/USER_REQUEST.md — the fix request. MUST follow this exact structure:`,
@@ -76,8 +78,11 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
76
78
  "",
77
79
  "# How to work:",
78
80
  "- 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.",
81
+ "- Spawn subagents for research (subagent_type is REQUIRED calls without it are rejected):",
82
+ ' Agent(subagent_type="Explore", ...) codebase research. Prefer this for most lookups. Fast and cheap.',
83
+ ' Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
84
+ ' Agent(subagent_type="Task", ...) — only when you need a subtask that writes files or runs complex multi-step commands.',
85
+ " Spawn multiple Explore agents in parallel for broad searches.",
81
86
  "- Use tools directly for quick lookups (cbm_search, lsp, ast_search, grep, etc.)",
82
87
  "- Present findings to the user and discuss them. Don't just dump raw results.",
83
88
  "- Do NOT modify project source code.",
@@ -120,18 +125,19 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
120
125
  "",
121
126
  "# Steps:",
122
127
  "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:",
128
+ "2. Spawn subagents for research (subagent_type is REQUIRED calls without it are rejected):",
129
+ ' - Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
130
+ ' - Agent(subagent_type="Librarian", ...) external docs, library APIs, web research.',
131
+ ' - Agent(subagent_type="Task", ...) — only when you need a subtask that writes files or runs complex multi-step commands.',
132
+ " Spawn multiple Explore agents in parallel for broad searches.",
133
+ "3. Use tools to understand code structure:",
126
134
  " - cbm_search: natural-language search across all symbols",
127
135
  " - cbm_search_code: graph-augmented grep (deduplicates into functions)",
128
136
  " - lsp documentSymbol, goToDefinition, findReferences, goToImplementation, hover",
129
137
  " - cbm_trace: trace call chains for dependency understanding",
130
138
  " - 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.",
139
+ "4. Ask the user follow-up questions as needed",
140
+ "5. Write findings into RESEARCH.md as results come back — don't wait for all subagents",
135
141
  "",
136
142
  "Produce two files:",
137
143
  `- ${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
  "",
@@ -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`,
@@ -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
+ pi.sendUserMessage(`[PI-PI] Resumed ${orchestrator.active.state.phase} phase. Read reviewer outputs and apply feedback.`, { deliverAs: "followUp" });
348
348
  } else {
349
- pi.sendUserMessage(`[PI-PI] Resumed ${orchestrator.active.state.phase} phase. Continue working.`);
349
+ pi.sendUserMessage(`[PI-PI] Resumed ${orchestrator.active.state.phase} phase. Continue working.`, { deliverAs: "followUp" });
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);
@@ -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.4.0",
4
4
  "description": "Pi-Pi Coding Agent: based on Pi Coding Agent, but twice as good",
5
5
  "keywords": [
6
6
  "pi-package",