@ilya-lesikov/pi-pi 0.2.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.
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +1 -6
- package/3p/pi-plannotator/packages/shared/review-core.ts +1 -22
- package/3p/pi-plannotator/packages/shared/vcs-core.ts +1 -1
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +4 -1
- package/extensions/orchestrator/agents/code-reviewer.ts +4 -1
- package/extensions/orchestrator/agents/plan-reviewer.ts +4 -1
- package/extensions/orchestrator/agents/planner.ts +4 -1
- package/extensions/orchestrator/agents/task.ts +4 -2
- package/extensions/orchestrator/command-handlers.ts +1 -1
- package/extensions/orchestrator/event-handlers.ts +36 -15
- package/extensions/orchestrator/orchestrator.ts +18 -3
- package/extensions/orchestrator/phases/brainstorm.ts +18 -12
- package/extensions/orchestrator/phases/implementation.ts +5 -3
- package/extensions/orchestrator/phases/review-task.ts +5 -0
- package/extensions/orchestrator/pp-menu.ts +164 -89
- package/extensions/orchestrator/usage-tracker.ts +36 -6
- package/package.json +1 -1
|
@@ -442,12 +442,7 @@ export async function startCodeReviewBrowserSession(
|
|
|
442
442
|
gitRef = result.gitRef;
|
|
443
443
|
diffError = result.error;
|
|
444
444
|
initialBase = result.base;
|
|
445
|
-
|
|
446
|
-
const range = diffType.slice("range:".length);
|
|
447
|
-
if (!gitCtx.diffOptions.some((o: { id: string }) => o.id === diffType)) {
|
|
448
|
-
gitCtx.diffOptions.push({ id: diffType, label: `Commits ${range}` });
|
|
449
|
-
}
|
|
450
|
-
}
|
|
445
|
+
|
|
451
446
|
} else {
|
|
452
447
|
workspace = await buildLocalWorkspaceReview(cwd, {
|
|
453
448
|
requestedDiffType: options.diffType,
|
|
@@ -22,7 +22,6 @@ export type DiffType =
|
|
|
22
22
|
| "branch"
|
|
23
23
|
| "merge-base"
|
|
24
24
|
| "all"
|
|
25
|
-
| `range:${string}`
|
|
26
25
|
| `worktree:${string}`
|
|
27
26
|
| "p4-default"
|
|
28
27
|
| `p4-changelist:${string}`;
|
|
@@ -737,28 +736,8 @@ export async function runGitDiff(
|
|
|
737
736
|
break;
|
|
738
737
|
}
|
|
739
738
|
|
|
740
|
-
default:
|
|
741
|
-
if (effectiveDiffType.startsWith("range:")) {
|
|
742
|
-
const range = effectiveDiffType.slice("range:".length);
|
|
743
|
-
const rangeArgs = [
|
|
744
|
-
"diff",
|
|
745
|
-
"--no-ext-diff",
|
|
746
|
-
...wFlag,
|
|
747
|
-
"--src-prefix=a/",
|
|
748
|
-
"--dst-prefix=b/",
|
|
749
|
-
"--end-of-options",
|
|
750
|
-
range,
|
|
751
|
-
];
|
|
752
|
-
const rangeDiff = assertGitSuccess(
|
|
753
|
-
await runtime.runGit(rangeArgs, { cwd }),
|
|
754
|
-
rangeArgs,
|
|
755
|
-
);
|
|
756
|
-
patch = rangeDiff.stdout;
|
|
757
|
-
label = `Commits ${range}`;
|
|
758
|
-
break;
|
|
759
|
-
}
|
|
739
|
+
default:
|
|
760
740
|
return { patch: "", label: "Unknown diff type" };
|
|
761
|
-
}
|
|
762
741
|
}
|
|
763
742
|
} catch (error) {
|
|
764
743
|
const raw = error instanceof Error ? error.message : String(error);
|
|
@@ -158,7 +158,7 @@ export function createGitProvider(runtime: ReviewGitRuntime): VcsProvider {
|
|
|
158
158
|
},
|
|
159
159
|
|
|
160
160
|
ownsDiffType(diffType: string): boolean {
|
|
161
|
-
return GIT_DIFF_TYPES.has(diffType) || diffType.startsWith("worktree:")
|
|
161
|
+
return GIT_DIFF_TYPES.has(diffType) || diffType.startsWith("worktree:");
|
|
162
162
|
},
|
|
163
163
|
|
|
164
164
|
canStageFiles(diffType: string): boolean {
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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
|
});
|
|
@@ -150,7 +150,7 @@ export async function enterReviewCycle(orchestrator: Orchestrator, ctx: any, kin
|
|
|
150
150
|
orchestrator.active.state.step = "synthesize";
|
|
151
151
|
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
152
152
|
const feedback = result.feedback ? `\n\nFeedback:\n${result.feedback}` : "";
|
|
153
|
-
return `Plannotator requested changes.${feedback}\n\
|
|
153
|
+
return `Plannotator requested changes.${feedback}\n\nAddress the user's feedback. If the feedback contains questions, answer them. If it requests changes, make the changes. Then call pp_phase_complete when done.`;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
orchestrator.active.state.reviewCycle = null;
|
|
@@ -276,9 +276,11 @@ function registerSpecifyReviewsTool(orchestrator: Orchestrator): void {
|
|
|
276
276
|
let hasNeedsChanges = false;
|
|
277
277
|
for (const review of params.reviews) {
|
|
278
278
|
ctx.ui?.setWorkingMessage?.(`Waiting for Plannotator review: ${review.range}…`);
|
|
279
|
+
const rangeBase = review.range.includes("..") ? review.range.split("..")[0] : review.range;
|
|
279
280
|
const result = await openCodeReviewDirect(pi, {
|
|
280
281
|
cwd: review.cwd,
|
|
281
|
-
diffType:
|
|
282
|
+
diffType: "branch",
|
|
283
|
+
defaultBranch: rangeBase,
|
|
282
284
|
});
|
|
283
285
|
if ("error" in result) {
|
|
284
286
|
results.push(`${review.cwd} (${review.range}): ${result.error}`);
|
|
@@ -299,7 +301,7 @@ function registerSpecifyReviewsTool(orchestrator: Orchestrator): void {
|
|
|
299
301
|
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
300
302
|
}
|
|
301
303
|
return {
|
|
302
|
-
content: [{ type: "text" as const, text: `Plannotator review complete.\n\n${summary}\n\
|
|
304
|
+
content: [{ type: "text" as const, text: `Plannotator review complete.\n\n${summary}\n\nAddress the user's feedback. If the feedback contains questions, answer them. If it requests changes, make the changes. Then call pp_phase_complete when done.` }],
|
|
303
305
|
details: {},
|
|
304
306
|
};
|
|
305
307
|
}
|
|
@@ -309,10 +311,11 @@ function registerSpecifyReviewsTool(orchestrator: Orchestrator): void {
|
|
|
309
311
|
const { showActiveTaskMenu } = await import("./pp-menu.js");
|
|
310
312
|
const text = await showActiveTaskMenu(orchestrator, ctx, `Plannotator review complete.\n\n${summary}`, "tool");
|
|
311
313
|
if (orchestrator.phaseCompactionPending || orchestrator.taskDoneCompactionPending) {
|
|
312
|
-
|
|
314
|
+
await orchestrator.waitForCompaction();
|
|
315
|
+
return { content: [{ type: "text" as const, text: "Phase transition complete." }], details: {} };
|
|
313
316
|
}
|
|
314
317
|
if (!text) {
|
|
315
|
-
return { content: [{ type: "text" as const, text: "
|
|
318
|
+
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: {} };
|
|
316
319
|
}
|
|
317
320
|
return { content: [{ type: "text" as const, text }], details: {} };
|
|
318
321
|
} finally {
|
|
@@ -348,11 +351,22 @@ function registerCommitTool(orchestrator: Orchestrator): void {
|
|
|
348
351
|
if (!orchestrator.config.autoCommit) {
|
|
349
352
|
return { content: [{ type: "text" as const, text: "autoCommit is disabled in config." }], details: {} };
|
|
350
353
|
}
|
|
351
|
-
|
|
354
|
+
const files: string[] = [];
|
|
355
|
+
try {
|
|
356
|
+
const gitResult = await pi.exec("git", ["diff", "--name-only"], { cwd: orchestrator.cwd, timeout: 5000 });
|
|
357
|
+
if (gitResult.code === 0 && gitResult.stdout.trim()) {
|
|
358
|
+
files.push(...gitResult.stdout.trim().split("\n").filter(Boolean));
|
|
359
|
+
}
|
|
360
|
+
const stagedResult = await pi.exec("git", ["diff", "--name-only", "--cached"], { cwd: orchestrator.cwd, timeout: 5000 });
|
|
361
|
+
if (stagedResult.code === 0 && stagedResult.stdout.trim()) {
|
|
362
|
+
for (const f of stagedResult.stdout.trim().split("\n").filter(Boolean)) {
|
|
363
|
+
if (!files.includes(f)) files.push(f);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
} catch {}
|
|
367
|
+
if (files.length === 0) {
|
|
352
368
|
return { content: [{ type: "text" as const, text: "No modified files to commit." }], details: {} };
|
|
353
369
|
}
|
|
354
|
-
|
|
355
|
-
const files = [...orchestrator.active.modifiedFiles];
|
|
356
370
|
const result = autoCommit(files, params.message, orchestrator.cwd);
|
|
357
371
|
if (result.ok) {
|
|
358
372
|
orchestrator.active.modifiedFiles.clear();
|
|
@@ -395,10 +409,11 @@ function registerPhaseCompleteTool(orchestrator: Orchestrator): void {
|
|
|
395
409
|
const { showActiveTaskMenu } = await import("./pp-menu.js");
|
|
396
410
|
const text = await showActiveTaskMenu(orchestrator, ctx, params.summary, "tool");
|
|
397
411
|
if (orchestrator.phaseCompactionPending || orchestrator.taskDoneCompactionPending) {
|
|
398
|
-
|
|
412
|
+
await orchestrator.waitForCompaction();
|
|
413
|
+
return { content: [{ type: "text" as const, text: "Phase transition complete." }], details: {} };
|
|
399
414
|
}
|
|
400
415
|
if (!text) {
|
|
401
|
-
return { content: [{ type: "text" as const, text: "
|
|
416
|
+
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: {} };
|
|
402
417
|
}
|
|
403
418
|
return { content: [{ type: "text" as const, text }], details: {} };
|
|
404
419
|
} finally {
|
|
@@ -728,6 +743,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
728
743
|
if (usageTracker && data?.tokens) {
|
|
729
744
|
usageTracker.recordSubagentCompletion(data.tokens, undefined, {
|
|
730
745
|
description: data.description || data.type || data.id || "unknown",
|
|
746
|
+
agentType: data.type || "unknown",
|
|
731
747
|
modelId: data.modelId || "unknown",
|
|
732
748
|
durationMs: data.durationMs,
|
|
733
749
|
toolUses: data.toolUses,
|
|
@@ -927,7 +943,10 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
927
943
|
if (event.toolName === "Agent" && orchestrator.active) {
|
|
928
944
|
const input = event.input as Record<string, unknown>;
|
|
929
945
|
const requestedType = ((input.subagent_type as string) || "").toLowerCase();
|
|
930
|
-
|
|
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";
|
|
931
950
|
const isLibrarian = requestedType === "librarian";
|
|
932
951
|
|
|
933
952
|
if (isExplore) {
|
|
@@ -1167,7 +1186,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
1167
1186
|
orchestrator.pendingRetryTimer = setTimeout(() => {
|
|
1168
1187
|
orchestrator.pendingRetryTimer = null;
|
|
1169
1188
|
if (orchestrator.activeTaskToken !== taskToken || !orchestrator.active) return;
|
|
1170
|
-
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" });
|
|
1171
1190
|
}, delay);
|
|
1172
1191
|
} else {
|
|
1173
1192
|
ctx.ui.notify(`API error persisted after 3 retries: ${errorMsg}. Stopping auto-retry.`, "error");
|
|
@@ -1202,7 +1221,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
1202
1221
|
orchestrator.pendingSubagentSpawns = 0;
|
|
1203
1222
|
orchestrator.active.state.step = "synthesize";
|
|
1204
1223
|
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
1205
|
-
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" });
|
|
1206
1225
|
}
|
|
1207
1226
|
}
|
|
1208
1227
|
} else if (orchestrator.active.state.step === "await_reviewers" && orchestrator.active.state.reviewCycle) {
|
|
@@ -1233,7 +1252,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
1233
1252
|
{ customType: "pp-review-ready", content: `[PI-PI] Reviewer outputs are ready.\n\n${rendered}`, display: false },
|
|
1234
1253
|
{ deliverAs: "followUp" },
|
|
1235
1254
|
);
|
|
1236
|
-
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" });
|
|
1237
1256
|
}
|
|
1238
1257
|
} else {
|
|
1239
1258
|
clearInterval(orchestrator.awaitPollTimer!);
|
|
@@ -1258,7 +1277,9 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
1258
1277
|
const turnWasEmpty = !hasText && !hasToolCalls && !hasToolResults;
|
|
1259
1278
|
|
|
1260
1279
|
if (!turnWasEmpty) {
|
|
1261
|
-
|
|
1280
|
+
const lastPart = contentParts.length > 0 ? contentParts[contentParts.length - 1] : null;
|
|
1281
|
+
const endsWithText = lastPart?.type === "text" && lastPart?.text?.trim();
|
|
1282
|
+
if (hasText && (!hasToolCalls || endsWithText)) {
|
|
1262
1283
|
const step = orchestrator.active.state.step;
|
|
1263
1284
|
if (step !== "await_planners" && step !== "await_reviewers") {
|
|
1264
1285
|
pi.sendMessage(
|
|
@@ -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") {
|
|
@@ -458,6 +458,13 @@ export class Orchestrator {
|
|
|
458
458
|
}
|
|
459
459
|
}
|
|
460
460
|
|
|
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
|
+
|
|
461
468
|
compactAndTransition(ctx: ExtensionContext, taskDir: string, phase: Phase): void {
|
|
462
469
|
this.phaseCompactionPending = true;
|
|
463
470
|
ctx.compact({
|
|
@@ -469,11 +476,15 @@ export class Orchestrator {
|
|
|
469
476
|
this.phaseCompactionResolve = null;
|
|
470
477
|
}
|
|
471
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
|
+
}
|
|
472
483
|
this.injectContextAndArtifacts(taskDir, phase);
|
|
473
484
|
if (this.active?.state.phase === "plan" && this.active.state.step === "await_planners") {
|
|
474
485
|
ctx.ui.notify("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
|
|
475
486
|
} else {
|
|
476
|
-
this.pi.sendUserMessage(`[PI-PI] Entered ${phase} phase. Begin working
|
|
487
|
+
this.pi.sendUserMessage(`[PI-PI] Entered ${phase} phase. Begin working.`, { deliverAs: "followUp" });
|
|
477
488
|
}
|
|
478
489
|
},
|
|
479
490
|
onError: (err) => {
|
|
@@ -484,11 +495,15 @@ export class Orchestrator {
|
|
|
484
495
|
this.phaseCompactionResolve = null;
|
|
485
496
|
}
|
|
486
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
|
+
}
|
|
487
502
|
this.injectContextAndArtifacts(taskDir, phase);
|
|
488
503
|
if (this.active?.state.phase === "plan" && this.active.state.step === "await_planners") {
|
|
489
504
|
ctx.ui.notify("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
|
|
490
505
|
} else {
|
|
491
|
-
this.pi.sendUserMessage(`[PI-PI] Entered ${phase} phase. Begin working
|
|
506
|
+
this.pi.sendUserMessage(`[PI-PI] Entered ${phase} phase. Begin working.`, { deliverAs: "followUp" });
|
|
492
507
|
}
|
|
493
508
|
},
|
|
494
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
124
|
-
'
|
|
125
|
-
"
|
|
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
|
-
"
|
|
132
|
-
"
|
|
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
|
-
|
|
26
|
-
'
|
|
27
|
-
'
|
|
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,8 +591,11 @@ 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
|
-
getSubagentList(): Array<{ description: string; modelId: string; inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; cost: number; durationMs: number; toolUses: number }>;
|
|
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
|
}
|
|
597
600
|
| undefined;
|
|
598
601
|
|
|
@@ -601,44 +604,34 @@ function showUsage(ctx: any): void {
|
|
|
601
604
|
return;
|
|
602
605
|
}
|
|
603
606
|
|
|
604
|
-
const
|
|
605
|
-
const
|
|
606
|
-
const
|
|
607
|
-
const
|
|
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);
|
|
624
|
+
const mainTotalTokens = mainModelEntries.reduce((s, [, u]) => s + u.inputTokens + u.outputTokens, 0);
|
|
625
|
+
for (const [modelId, usage] of mainModelEntries) {
|
|
626
|
+
const modelTokens = usage.inputTokens + usage.outputTokens;
|
|
627
|
+
const modelCostShare = mainTotalTokens > 0 ? mainCost * (modelTokens / mainTotalTokens) : 0;
|
|
631
628
|
byModel.set(modelId, {
|
|
632
629
|
input: usage.inputTokens, output: usage.outputTokens,
|
|
633
|
-
cacheRead: usage.cacheReadTokens, cacheWrite: usage.cacheWriteTokens, cost:
|
|
630
|
+
cacheRead: usage.cacheReadTokens, cacheWrite: usage.cacheWriteTokens, cost: modelCostShare,
|
|
634
631
|
});
|
|
635
632
|
}
|
|
636
|
-
if (mainCost > 0 && Object.keys(models).length === 1) {
|
|
637
|
-
const entry = byModel.values().next().value;
|
|
638
|
-
if (entry) entry.cost = mainCost;
|
|
639
|
-
}
|
|
640
633
|
for (const sa of subagents) {
|
|
641
|
-
const key = sa.modelId !== "unknown" ? sa.modelId : sa.description
|
|
634
|
+
const key = sa.modelId !== "unknown" ? sa.modelId : `subagent:${sa.description}`;
|
|
642
635
|
const existing = byModel.get(key);
|
|
643
636
|
if (existing) {
|
|
644
637
|
existing.input += sa.inputTokens;
|
|
@@ -658,7 +651,7 @@ function showUsage(ctx: any): void {
|
|
|
658
651
|
lines.push(` Input: ${formatTokenCount(totalInput)} tokens`);
|
|
659
652
|
lines.push(` Output: ${formatTokenCount(totalOutput)} tokens`);
|
|
660
653
|
if (totalCacheRead > 0) lines.push(` Cache: ⚡${Math.round(totalCacheRate * 100)}% hit rate`);
|
|
661
|
-
if (totalCost > 0) lines.push(` Cost: $${totalCost.toFixed(
|
|
654
|
+
if (totalCost > 0) lines.push(` Cost: $${totalCost.toFixed(2)}`);
|
|
662
655
|
|
|
663
656
|
if (byModel.size > 0) {
|
|
664
657
|
lines.push("");
|
|
@@ -667,31 +660,50 @@ function showUsage(ctx: any): void {
|
|
|
667
660
|
const cr = (m.cacheRead + m.input) > 0 ? Math.round(m.cacheRead / (m.cacheRead + m.input) * 100) : 0;
|
|
668
661
|
const parts = [`↑${formatTokenCount(m.input)}`, `↓${formatTokenCount(m.output)}`];
|
|
669
662
|
if (cr > 0) parts.push(`⚡${cr}%`);
|
|
670
|
-
if (m.cost > 0) parts.push(`$${m.cost.toFixed(
|
|
663
|
+
if (m.cost > 0) parts.push(`$${m.cost.toFixed(2)}`);
|
|
671
664
|
lines.push(` ${modelId}: ${parts.join(" ")}`);
|
|
672
665
|
}
|
|
673
666
|
}
|
|
674
667
|
|
|
675
668
|
lines.push("");
|
|
676
669
|
lines.push("By agent:");
|
|
677
|
-
const
|
|
678
|
-
if (
|
|
670
|
+
const agentModelNames = Object.keys(models);
|
|
671
|
+
if (agentModelNames.length > 0) {
|
|
679
672
|
const mainParts = [`↑${formatTokenCount(mainInput)}`, `↓${formatTokenCount(mainOutput)}`];
|
|
680
673
|
const mainCR = (mainCacheRead + mainInput) > 0 ? Math.round(mainCacheRead / (mainCacheRead + mainInput) * 100) : 0;
|
|
681
674
|
if (mainCR > 0) mainParts.push(`⚡${mainCR}%`);
|
|
682
|
-
if (mainCost > 0) mainParts.push(`$${mainCost.toFixed(
|
|
683
|
-
|
|
684
|
-
lines.push(` Main (${mainModel}): ${mainParts.join(" ")}`);
|
|
675
|
+
if (mainCost > 0) mainParts.push(`$${mainCost.toFixed(2)}`);
|
|
676
|
+
lines.push(` Main (${agentModelNames.join(", ")}): ${mainParts.join(" ")}`);
|
|
685
677
|
}
|
|
678
|
+
const byAgentType = new Map<string, { input: number; output: number; cacheRead: number; cost: number; durationMs: number; toolUses: number; count: number }>();
|
|
686
679
|
for (const sa of subagents) {
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
680
|
+
const key = sa.agentType || sa.description;
|
|
681
|
+
const existing = byAgentType.get(key);
|
|
682
|
+
if (existing) {
|
|
683
|
+
existing.input += sa.inputTokens;
|
|
684
|
+
existing.output += sa.outputTokens;
|
|
685
|
+
existing.cacheRead += sa.cacheReadTokens;
|
|
686
|
+
existing.cost += sa.cost;
|
|
687
|
+
existing.durationMs += sa.durationMs;
|
|
688
|
+
existing.toolUses += sa.toolUses;
|
|
689
|
+
existing.count += 1;
|
|
690
|
+
} else {
|
|
691
|
+
byAgentType.set(key, {
|
|
692
|
+
input: sa.inputTokens, output: sa.outputTokens, cacheRead: sa.cacheReadTokens,
|
|
693
|
+
cost: sa.cost, durationMs: sa.durationMs, toolUses: sa.toolUses, count: 1,
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
for (const [agentType, agg] of byAgentType) {
|
|
698
|
+
const saCR = (agg.cacheRead + agg.input) > 0
|
|
699
|
+
? Math.round(agg.cacheRead / (agg.cacheRead + agg.input) * 100) : 0;
|
|
700
|
+
const parts = [`↑${formatTokenCount(agg.input)}`, `↓${formatTokenCount(agg.output)}`];
|
|
690
701
|
if (saCR > 0) parts.push(`⚡${saCR}%`);
|
|
691
|
-
if (
|
|
692
|
-
if (
|
|
693
|
-
if (
|
|
694
|
-
|
|
702
|
+
if (agg.cost > 0) parts.push(`$${agg.cost.toFixed(2)}`);
|
|
703
|
+
if (agg.durationMs > 0) parts.push(formatDuration(agg.durationMs));
|
|
704
|
+
if (agg.toolUses > 0) parts.push(`${agg.toolUses} tools`);
|
|
705
|
+
const countSuffix = agg.count > 1 ? ` (×${agg.count})` : "";
|
|
706
|
+
lines.push(` ${agentType}${countSuffix}: ${parts.join(" ")}`);
|
|
695
707
|
}
|
|
696
708
|
|
|
697
709
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
@@ -699,10 +711,12 @@ function showUsage(ctx: any): void {
|
|
|
699
711
|
|
|
700
712
|
async function showSettingsMenu(orchestrator: Orchestrator, ctx: any, showFlant = true): Promise<typeof BACK> {
|
|
701
713
|
while (true) {
|
|
702
|
-
const options: OptionInput[] = [
|
|
703
|
-
|
|
704
|
-
{ title: "
|
|
705
|
-
|
|
714
|
+
const options: OptionInput[] = [];
|
|
715
|
+
if (orchestrator.active) {
|
|
716
|
+
options.push({ title: "Task status", description: "Show current task phase, step, and timing" });
|
|
717
|
+
}
|
|
718
|
+
options.push({ title: "Usage", description: "Show session token usage and cost breakdown" });
|
|
719
|
+
options.push({ title: "LSP", description: "Language server status and controls" });
|
|
706
720
|
if (showFlant) {
|
|
707
721
|
options.push({ title: "Flant AI Infrastructure", description: "Configure corporate AI model provider" });
|
|
708
722
|
}
|
|
@@ -710,6 +724,10 @@ async function showSettingsMenu(orchestrator: Orchestrator, ctx: any, showFlant
|
|
|
710
724
|
|
|
711
725
|
const choice = await selectOption(ctx, "Settings", options);
|
|
712
726
|
if (!choice || choice === "Back") return BACK;
|
|
727
|
+
if (choice === "Task status") {
|
|
728
|
+
showStatus(orchestrator, ctx);
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
713
731
|
if (choice === "Usage") {
|
|
714
732
|
showUsage(ctx);
|
|
715
733
|
continue;
|
|
@@ -878,9 +896,10 @@ async function detectCurrentPrContext(orchestrator: Orchestrator): Promise<{ prU
|
|
|
878
896
|
}
|
|
879
897
|
}
|
|
880
898
|
|
|
881
|
-
async function
|
|
882
|
-
if (!orchestrator.active) return "No active task.";
|
|
899
|
+
async function openCodeReviewInPlannotator(orchestrator: Orchestrator, diffType?: string, defaultBranch?: string): Promise<string> {
|
|
883
900
|
const payload: Record<string, unknown> = { cwd: orchestrator.cwd };
|
|
901
|
+
if (diffType) payload.diffType = diffType;
|
|
902
|
+
if (defaultBranch) payload.defaultBranch = defaultBranch;
|
|
884
903
|
|
|
885
904
|
return await new Promise((resolve) => {
|
|
886
905
|
let handled = false;
|
|
@@ -1109,33 +1128,20 @@ export async function showActiveTaskMenu(
|
|
|
1109
1128
|
const options: OptionInput[] = [];
|
|
1110
1129
|
options.push(opt("Finish", "Complete, pause, or continue to next phase"));
|
|
1111
1130
|
if (!waiting) {
|
|
1112
|
-
options.push(opt(
|
|
1113
|
-
options.push(opt(deepLabel, "Run automated review with higher thinking level"));
|
|
1114
|
-
if (hasPlannotator) {
|
|
1115
|
-
if (isReviewPhase) {
|
|
1116
|
-
options.push(opt("Review in Plannotator", "Open visual diff review in browser"));
|
|
1117
|
-
} else {
|
|
1118
|
-
options.push(opt("Review in Plannotator", "Open visual review in browser"));
|
|
1119
|
-
}
|
|
1120
|
-
if (!isReviewPhase) {
|
|
1121
|
-
options.push(opt("Review on my own", "Review manually, then continue"));
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1131
|
+
options.push(opt("Review", "Auto review, Plannotator, or manual review"));
|
|
1124
1132
|
}
|
|
1125
1133
|
options.push(opt("Subagents", "Manage running agents"));
|
|
1126
|
-
options.push(opt("
|
|
1127
|
-
options.push(opt("Settings", "LSP and other configuration"));
|
|
1134
|
+
options.push(opt("Settings", "Task status, usage, and LSP"));
|
|
1128
1135
|
options.push(opt("Back", "Return to the prompt and keep working"));
|
|
1129
1136
|
|
|
1130
|
-
const
|
|
1137
|
+
const headerLines = [`/pp\n\nTask: ${task.type}\nPhase: ${phase}`];
|
|
1138
|
+
if (summary !== "/pp") headerLines.push(`\n${summary}`);
|
|
1139
|
+
const menuTitle = headerLines.join("");
|
|
1140
|
+
const choice = await selectOption(ctx, menuTitle, options);
|
|
1131
1141
|
if (!choice || choice === "Back") {
|
|
1132
1142
|
return "";
|
|
1133
1143
|
}
|
|
1134
1144
|
|
|
1135
|
-
if (choice === "Status") {
|
|
1136
|
-
showStatus(orchestrator, ctx);
|
|
1137
|
-
continue;
|
|
1138
|
-
}
|
|
1139
1145
|
if (choice === "Subagents") {
|
|
1140
1146
|
await showSubagentsMenu(ctx);
|
|
1141
1147
|
continue;
|
|
@@ -1179,17 +1185,95 @@ export async function showActiveTaskMenu(
|
|
|
1179
1185
|
return "";
|
|
1180
1186
|
}
|
|
1181
1187
|
|
|
1182
|
-
if (choice === "Review
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1188
|
+
if (choice === "Review") {
|
|
1189
|
+
const reviewOptions: OptionInput[] = [
|
|
1190
|
+
opt(autoLabel, "Run automated review with configured reviewers"),
|
|
1191
|
+
opt(deepLabel, "Run automated review with higher thinking level"),
|
|
1192
|
+
];
|
|
1193
|
+
if (hasPlannotator) {
|
|
1194
|
+
reviewOptions.push(opt("Review in Plannotator", phase === "plan" ? "Open plan review in browser" : "Open code diff review in browser"));
|
|
1195
|
+
}
|
|
1196
|
+
reviewOptions.push(opt("Review on my own", "Review manually, then continue"));
|
|
1197
|
+
reviewOptions.push(opt("Back", "Return to the previous menu"));
|
|
1198
|
+
|
|
1199
|
+
const reviewChoice = await selectOption(ctx, "Review", reviewOptions);
|
|
1200
|
+
if (!reviewChoice || reviewChoice === "Back") continue;
|
|
1201
|
+
|
|
1202
|
+
if (reviewChoice === "Review in Plannotator") {
|
|
1203
|
+
if (phase === "plan") {
|
|
1204
|
+
finalizeReviewCycle(task);
|
|
1205
|
+
const text = await enterReviewCycle(orchestrator, ctx, "plannotator");
|
|
1206
|
+
const curStep = orchestrator.active?.state.step;
|
|
1207
|
+
if (curStep === "await_reviewers") return "";
|
|
1208
|
+
const handled = handleReviewResult(ctx, text);
|
|
1209
|
+
if (handled.continueLoop) continue;
|
|
1210
|
+
return handled.text ?? text;
|
|
1211
|
+
}
|
|
1212
|
+
const diffChoice = await selectOption(ctx, "Review in Plannotator", [
|
|
1213
|
+
opt("All branch changes", "Committed changes vs base branch"),
|
|
1214
|
+
opt("Last commit", "Changes in the most recent commit"),
|
|
1215
|
+
opt("Recent commits", "Choose how many recent commits to review"),
|
|
1216
|
+
opt("Uncommitted changes", "Working directory changes"),
|
|
1217
|
+
opt("Back", "Return to the previous menu"),
|
|
1218
|
+
]);
|
|
1219
|
+
if (!diffChoice || diffChoice === "Back") continue;
|
|
1220
|
+
|
|
1221
|
+
let diffType: string | undefined;
|
|
1222
|
+
let defaultBranch: string | undefined;
|
|
1223
|
+
if (diffChoice === "All branch changes") {
|
|
1224
|
+
diffType = "branch";
|
|
1225
|
+
} else if (diffChoice === "Last commit") {
|
|
1226
|
+
diffType = "last-commit";
|
|
1227
|
+
} else if (diffChoice === "Recent commits") {
|
|
1228
|
+
let commits: Array<{ hash: string; message: string; age: string }> = [];
|
|
1229
|
+
try {
|
|
1230
|
+
const logResult = await orchestrator.pi.exec(
|
|
1231
|
+
"git", ["log", "--oneline", "--format=%h\t%s\t%cr", "-15"],
|
|
1232
|
+
{ cwd: orchestrator.cwd, timeout: 5000 },
|
|
1233
|
+
);
|
|
1234
|
+
if (logResult.code === 0 && logResult.stdout.trim()) {
|
|
1235
|
+
commits = logResult.stdout.trim().split("\n").map((line) => {
|
|
1236
|
+
const [hash, message, age] = line.split("\t");
|
|
1237
|
+
return { hash: hash || "", message: message || "", age: age || "" };
|
|
1238
|
+
}).filter((c) => c.hash);
|
|
1239
|
+
}
|
|
1240
|
+
} catch {}
|
|
1241
|
+
if (commits.length === 0) {
|
|
1242
|
+
ctx.ui.notify("No commits found.", "info");
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1245
|
+
const commitOptions: OptionInput[] = commits.map((c) => ({
|
|
1246
|
+
title: `${c.hash} ${c.message}`,
|
|
1247
|
+
description: c.age,
|
|
1248
|
+
}));
|
|
1249
|
+
commitOptions.push(opt("Back", "Return to the previous menu"));
|
|
1250
|
+
const picked = await selectOption(ctx, "Review changes since:", commitOptions);
|
|
1251
|
+
if (!picked || picked === "Back") continue;
|
|
1252
|
+
const pickedHash = picked.split(" ")[0];
|
|
1253
|
+
if (!pickedHash) continue;
|
|
1254
|
+
diffType = "branch";
|
|
1255
|
+
defaultBranch = pickedHash;
|
|
1256
|
+
} else {
|
|
1257
|
+
diffType = "uncommitted";
|
|
1258
|
+
}
|
|
1187
1259
|
|
|
1188
|
-
|
|
1260
|
+
const text = await openCodeReviewInPlannotator(orchestrator, diffType, defaultBranch);
|
|
1261
|
+
ctx.ui.notify(text, "info");
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
if (reviewChoice === "Review on my own") {
|
|
1266
|
+
if (phase === "plan") {
|
|
1267
|
+
setStep(orchestrator, "synthesize");
|
|
1268
|
+
} else {
|
|
1269
|
+
setStep(orchestrator, "llm_work");
|
|
1270
|
+
}
|
|
1271
|
+
return continueMessage;
|
|
1272
|
+
}
|
|
1189
1273
|
|
|
1190
|
-
|
|
1191
|
-
const kind =
|
|
1192
|
-
if (
|
|
1274
|
+
finalizeReviewCycle(task);
|
|
1275
|
+
const kind = reviewChoice === autoLabel ? "auto" as const : "auto-deep" as const;
|
|
1276
|
+
if (!hasEnabledReviewers(orchestrator, kind)) {
|
|
1193
1277
|
const label = phase === "brainstorm" ? "brainstorm" : phase === "plan" ? "plan" : "code";
|
|
1194
1278
|
ctx.ui.notify(`No ${label} reviewers enabled.`, "info");
|
|
1195
1279
|
continue;
|
|
@@ -1201,15 +1285,6 @@ export async function showActiveTaskMenu(
|
|
|
1201
1285
|
if (handled.continueLoop) continue;
|
|
1202
1286
|
return handled.text ?? text;
|
|
1203
1287
|
}
|
|
1204
|
-
|
|
1205
|
-
if (choice === "Review on my own") {
|
|
1206
|
-
if (phase === "plan") {
|
|
1207
|
-
setStep(orchestrator, "synthesize");
|
|
1208
|
-
} else {
|
|
1209
|
-
setStep(orchestrator, "llm_work");
|
|
1210
|
-
}
|
|
1211
|
-
return continueMessage;
|
|
1212
|
-
}
|
|
1213
1288
|
}
|
|
1214
1289
|
}
|
|
1215
1290
|
|
|
@@ -1217,6 +1292,6 @@ export async function showPpMenu(orchestrator: Orchestrator, ctx: any, mode: Men
|
|
|
1217
1292
|
if (!orchestrator.active) {
|
|
1218
1293
|
return showNoActiveMenu(orchestrator, ctx);
|
|
1219
1294
|
}
|
|
1220
|
-
const text = await showActiveTaskMenu(orchestrator, ctx, "
|
|
1295
|
+
const text = await showActiveTaskMenu(orchestrator, ctx, "/pp", mode);
|
|
1221
1296
|
return text || undefined;
|
|
1222
1297
|
}
|
|
@@ -12,13 +12,18 @@ export interface ModelUsage {
|
|
|
12
12
|
|
|
13
13
|
export interface UsageTracker {
|
|
14
14
|
recordTurn(modelId: string, provider: string, input: number, output: number, cacheRead: number, cacheWrite: number, cost: number): void;
|
|
15
|
-
recordSubagentCompletion(tokens: { input?: number; output?: number; total?: number }, cost?: number, meta?: { description?: string; modelId?: string; durationMs?: number; toolUses?: number }): void;
|
|
15
|
+
recordSubagentCompletion(tokens: { input?: number; output?: number; total?: number }, cost?: number, meta?: { description?: string; agentType?: string; modelId?: string; durationMs?: number; toolUses?: number }): void;
|
|
16
16
|
loadFromSummary(summary: Record<string, unknown>): void;
|
|
17
17
|
getTotalInputTokens(): number;
|
|
18
18
|
getTotalOutputTokens(): number;
|
|
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 };
|
|
@@ -29,6 +34,7 @@ export interface UsageTracker {
|
|
|
29
34
|
|
|
30
35
|
export interface SubagentUsage {
|
|
31
36
|
description: string;
|
|
37
|
+
agentType: string;
|
|
32
38
|
modelId: string;
|
|
33
39
|
inputTokens: number;
|
|
34
40
|
outputTokens: number;
|
|
@@ -124,7 +130,7 @@ export function createUsageTracker(): UsageTracker {
|
|
|
124
130
|
recordSubagentCompletion(
|
|
125
131
|
tokens: { input?: number; output?: number; cacheRead?: number; cacheWrite?: number; total?: number; cost?: number },
|
|
126
132
|
cost?: number,
|
|
127
|
-
meta?: { description?: string; modelId?: string; durationMs?: number; toolUses?: number },
|
|
133
|
+
meta?: { description?: string; agentType?: string; modelId?: string; durationMs?: number; toolUses?: number },
|
|
128
134
|
): void {
|
|
129
135
|
const safeInput = toFiniteNumber(tokens.input);
|
|
130
136
|
const safeOutput = toFiniteNumber(tokens.output);
|
|
@@ -141,6 +147,7 @@ export function createUsageTracker(): UsageTracker {
|
|
|
141
147
|
|
|
142
148
|
state.subagents.push({
|
|
143
149
|
description: meta?.description ?? "unknown",
|
|
150
|
+
agentType: meta?.agentType ?? "unknown",
|
|
144
151
|
modelId: meta?.modelId ?? "unknown",
|
|
145
152
|
inputTokens: effectiveInput,
|
|
146
153
|
outputTokens: safeOutput,
|
|
@@ -168,6 +175,7 @@ export function createUsageTracker(): UsageTracker {
|
|
|
168
175
|
for (const sa of summary.subagents as Record<string, unknown>[]) {
|
|
169
176
|
const entry: SubagentUsage = {
|
|
170
177
|
description: typeof sa.description === "string" ? sa.description : "unknown",
|
|
178
|
+
agentType: typeof sa.agentType === "string" ? sa.agentType : "unknown",
|
|
171
179
|
modelId: typeof sa.modelId === "string" ? sa.modelId : "unknown",
|
|
172
180
|
inputTokens: toFiniteNumber(sa.inputTokens),
|
|
173
181
|
outputTokens: toFiniteNumber(sa.outputTokens),
|
|
@@ -200,11 +208,11 @@ export function createUsageTracker(): UsageTracker {
|
|
|
200
208
|
},
|
|
201
209
|
|
|
202
210
|
getTotalInputTokens(): number {
|
|
203
|
-
return state.totalInputTokens;
|
|
211
|
+
return state.totalInputTokens + state.subagentInputTokens;
|
|
204
212
|
},
|
|
205
213
|
|
|
206
214
|
getTotalOutputTokens(): number {
|
|
207
|
-
return state.totalOutputTokens;
|
|
215
|
+
return state.totalOutputTokens + state.subagentOutputTokens;
|
|
208
216
|
},
|
|
209
217
|
|
|
210
218
|
getTotalCacheReadTokens(): number {
|
|
@@ -216,13 +224,35 @@ export function createUsageTracker(): UsageTracker {
|
|
|
216
224
|
},
|
|
217
225
|
|
|
218
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 {
|
|
219
247
|
return state.totalCost;
|
|
220
248
|
},
|
|
221
249
|
|
|
222
250
|
getCacheHitRate(): number {
|
|
223
|
-
const
|
|
251
|
+
const totalInput = state.totalInputTokens + state.subagentInputTokens;
|
|
252
|
+
const totalCacheRead = state.totalCacheReadTokens;
|
|
253
|
+
const denominator = totalCacheRead + totalInput;
|
|
224
254
|
if (denominator <= 0) return 0;
|
|
225
|
-
return
|
|
255
|
+
return totalCacheRead / denominator;
|
|
226
256
|
},
|
|
227
257
|
|
|
228
258
|
getPerModelUsage(): Record<string, ModelUsage> {
|