@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.
- 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 +7 -5
- package/extensions/orchestrator/event-handlers.ts +35 -37
- package/extensions/orchestrator/orchestrator.ts +43 -43
- package/extensions/orchestrator/phases/brainstorm.ts +31 -24
- package/extensions/orchestrator/phases/implementation.ts +5 -3
- package/extensions/orchestrator/phases/planning.ts +3 -1
- package/extensions/orchestrator/phases/review-task.ts +9 -0
- package/extensions/orchestrator/pp-menu.ts +77 -55
- package/extensions/orchestrator/usage-tracker.ts +31 -4
- package/package.json +1 -1
|
@@ -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",
|
|
@@ -72,15 +72,15 @@ export async function transitionToNextPhase(
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
orchestrator.updateStatus(ctx);
|
|
75
|
+
orchestrator.phaseCompactionSummary = `Phase "${currentPhase}" completed. Now entering "${next}" phase.`;
|
|
75
76
|
|
|
76
77
|
if (next === "plan") {
|
|
77
78
|
orchestrator.active.state.step = "await_planners";
|
|
78
79
|
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (next === "plan") {
|
|
82
|
+
const onReady = next === "plan" ? () => {
|
|
83
|
+
if (!orchestrator.active) return;
|
|
84
84
|
orchestrator.pendingSubagentSpawns = Object.values(orchestrator.config.planners).filter((v) => v.enabled).length;
|
|
85
85
|
orchestrator.failedPlannerVariants = [];
|
|
86
86
|
spawnPlanners(orchestrator.pi, orchestrator.cwd, orchestrator.active.dir, orchestrator.active.taskId, orchestrator.config).then((result) => {
|
|
@@ -94,7 +94,9 @@ export async function transitionToNextPhase(
|
|
|
94
94
|
orchestrator.pendingSubagentSpawns = 0;
|
|
95
95
|
console.error(`[pi-pi] spawnPlanners failed: ${err.message}`);
|
|
96
96
|
});
|
|
97
|
-
}
|
|
97
|
+
} : undefined;
|
|
98
|
+
|
|
99
|
+
orchestrator.compactAndTransition(ctx, orchestrator.active.dir, orchestrator.active.state.phase, onReady);
|
|
98
100
|
|
|
99
101
|
return { ok: true };
|
|
100
102
|
}
|
|
@@ -109,7 +111,7 @@ export function registerCommandHandlers(orchestrator: Orchestrator): void {
|
|
|
109
111
|
const { showPpMenu } = await import("./pp-menu.js");
|
|
110
112
|
const text = await showPpMenu(orchestrator, ctx, "command");
|
|
111
113
|
if (text) {
|
|
112
|
-
|
|
114
|
+
orchestrator.safeSendUserMessage(`[PI-PI] ${text}`);
|
|
113
115
|
}
|
|
114
116
|
},
|
|
115
117
|
});
|
|
@@ -94,7 +94,7 @@ function tryCompleteReviewCycle(orchestrator: Orchestrator): void {
|
|
|
94
94
|
},
|
|
95
95
|
{ deliverAs: "followUp" },
|
|
96
96
|
);
|
|
97
|
-
|
|
97
|
+
orchestrator.safeSendUserMessage("[PI-PI] Review cycle is ready for apply_feedback. Read reviewer outputs and proceed.");
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
export async function enterReviewCycle(orchestrator: Orchestrator, ctx: any, kind: "auto" | "auto-deep" | "plannotator") {
|
|
@@ -204,13 +204,12 @@ export async function enterReviewCycle(orchestrator: Orchestrator, ctx: any, kin
|
|
|
204
204
|
export async function stopTask(orchestrator: Orchestrator): Promise<string> {
|
|
205
205
|
if (!orchestrator.active) return "No active task.";
|
|
206
206
|
orchestrator.abortAllSubagents();
|
|
207
|
-
orchestrator.active.state.phase = "done";
|
|
208
207
|
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
209
208
|
const desc = orchestrator.active.description;
|
|
210
209
|
await orchestrator.cleanupActive();
|
|
211
210
|
const taskStore = (globalThis as any)[Symbol.for("pi-tasks:store")];
|
|
212
211
|
taskStore?.clearAll?.();
|
|
213
|
-
return `Task "${desc}" stopped.`;
|
|
212
|
+
return `Task "${desc}" stopped. Use /pp → Resume to continue.`;
|
|
214
213
|
}
|
|
215
214
|
|
|
216
215
|
export function finalizeReviewCycle(task: ActiveTask): void {
|
|
@@ -311,8 +310,8 @@ function registerSpecifyReviewsTool(orchestrator: Orchestrator): void {
|
|
|
311
310
|
const { showActiveTaskMenu } = await import("./pp-menu.js");
|
|
312
311
|
const text = await showActiveTaskMenu(orchestrator, ctx, `Plannotator review complete.\n\n${summary}`, "tool");
|
|
313
312
|
if (orchestrator.phaseCompactionPending || orchestrator.taskDoneCompactionPending) {
|
|
314
|
-
|
|
315
|
-
return { content: [{ type: "text" as const, text: "
|
|
313
|
+
ctx.abort?.();
|
|
314
|
+
return { content: [{ type: "text" as const, text: "" }], details: {} };
|
|
316
315
|
}
|
|
317
316
|
if (!text) {
|
|
318
317
|
return { content: [{ type: "text" as const, text: "User dismissed the menu. Wait for the user's next message. When you resume work, update USER_REQUEST.md and RESEARCH.md with any new findings before calling pp_phase_complete." }], details: {} };
|
|
@@ -409,8 +408,8 @@ function registerPhaseCompleteTool(orchestrator: Orchestrator): void {
|
|
|
409
408
|
const { showActiveTaskMenu } = await import("./pp-menu.js");
|
|
410
409
|
const text = await showActiveTaskMenu(orchestrator, ctx, params.summary, "tool");
|
|
411
410
|
if (orchestrator.phaseCompactionPending || orchestrator.taskDoneCompactionPending) {
|
|
412
|
-
|
|
413
|
-
return { content: [{ type: "text" as const, text: "
|
|
411
|
+
ctx.abort?.();
|
|
412
|
+
return { content: [{ type: "text" as const, text: "" }], details: {} };
|
|
414
413
|
}
|
|
415
414
|
if (!text) {
|
|
416
415
|
return { content: [{ type: "text" as const, text: "User dismissed the menu. Wait for the user's next message. When you resume work, update USER_REQUEST.md and RESEARCH.md with any new findings before calling pp_phase_complete." }], details: {} };
|
|
@@ -545,7 +544,8 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
545
544
|
orchestrator.active.state.phase !== "plan" ||
|
|
546
545
|
orchestrator.active.state.step !== "await_planners" ||
|
|
547
546
|
orchestrator.spawnedAgentIds.size > 0 ||
|
|
548
|
-
orchestrator.pendingSubagentSpawns > 0
|
|
547
|
+
orchestrator.pendingSubagentSpawns > 0 ||
|
|
548
|
+
orchestrator.phaseCompactionPending
|
|
549
549
|
) return;
|
|
550
550
|
|
|
551
551
|
const plansDir = join(orchestrator.active.dir, "plans");
|
|
@@ -594,13 +594,13 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
594
594
|
console.error(`[pi-pi] retry spawnPlanners failed: ${err.message}`);
|
|
595
595
|
checkPlannerCompletion();
|
|
596
596
|
});
|
|
597
|
-
|
|
597
|
+
orchestrator.safeSendUserMessage(`[PI-PI] Retrying failed planners: ${variantsText}.`);
|
|
598
598
|
return;
|
|
599
599
|
}
|
|
600
600
|
}
|
|
601
601
|
|
|
602
602
|
if (choice === "Stop task") {
|
|
603
|
-
|
|
603
|
+
orchestrator.safeSendUserMessage(`[PI-PI] ${await stopTask(orchestrator)}`);
|
|
604
604
|
return;
|
|
605
605
|
}
|
|
606
606
|
|
|
@@ -632,7 +632,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
632
632
|
orchestrator.failedPlannerVariants = [];
|
|
633
633
|
orchestrator.active.state.step = "synthesize";
|
|
634
634
|
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
635
|
-
|
|
635
|
+
orchestrator.safeSendUserMessage("[PI-PI] All planners completed. Read their outputs and synthesize the plan.");
|
|
636
636
|
}
|
|
637
637
|
|
|
638
638
|
function checkReviewCycleCompletion(): void {
|
|
@@ -640,7 +640,8 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
640
640
|
!orchestrator.active?.state.reviewCycle ||
|
|
641
641
|
orchestrator.active.state.reviewCycle.step !== "await_reviewers" ||
|
|
642
642
|
orchestrator.spawnedAgentIds.size > 0 ||
|
|
643
|
-
orchestrator.pendingSubagentSpawns > 0
|
|
643
|
+
orchestrator.pendingSubagentSpawns > 0 ||
|
|
644
|
+
orchestrator.phaseCompactionPending
|
|
644
645
|
) return;
|
|
645
646
|
|
|
646
647
|
const failedReviewerVariants = [...orchestrator.failedReviewerVariants];
|
|
@@ -705,14 +706,14 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
705
706
|
console.error(`[pi-pi] retry spawn reviewers failed (${phase}): ${err.message}`);
|
|
706
707
|
checkReviewCycleCompletion();
|
|
707
708
|
});
|
|
708
|
-
|
|
709
|
+
orchestrator.safeSendUserMessage(`[PI-PI] Retrying failed reviewers: ${variantsText}.`);
|
|
709
710
|
return;
|
|
710
711
|
}
|
|
711
712
|
}
|
|
712
713
|
}
|
|
713
714
|
|
|
714
715
|
if (choice === "Stop task") {
|
|
715
|
-
|
|
716
|
+
orchestrator.safeSendUserMessage(`[PI-PI] ${await stopTask(orchestrator)}`);
|
|
716
717
|
return;
|
|
717
718
|
}
|
|
718
719
|
|
|
@@ -722,7 +723,7 @@ export function registerEventHandlers(orchestrator: Orchestrator): void {
|
|
|
722
723
|
orchestrator.active.state.reviewCycle = null;
|
|
723
724
|
orchestrator.active.state.step = "user_gate";
|
|
724
725
|
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
725
|
-
|
|
726
|
+
orchestrator.safeSendUserMessage("[PI-PI] Review cycle skipped. Use /pp to choose the next action.");
|
|
726
727
|
return;
|
|
727
728
|
}
|
|
728
729
|
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
+
phaseCompactionSummary = "";
|
|
64
65
|
taskDoneCompactionPending = false;
|
|
65
66
|
taskDoneCompactionSummary = "";
|
|
66
67
|
nudgeTimestamps: number[] = [];
|
|
@@ -69,6 +70,7 @@ export class Orchestrator {
|
|
|
69
70
|
pendingSubagentSpawns = 0;
|
|
70
71
|
errorRetryCount = 0;
|
|
71
72
|
commitReminderSent = false;
|
|
73
|
+
textStopReminderSent = false;
|
|
72
74
|
phaseStartTime = 0;
|
|
73
75
|
awaitPollTimer: ReturnType<typeof setInterval> | null = null;
|
|
74
76
|
pendingRetryTimer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -86,6 +88,25 @@ export class Orchestrator {
|
|
|
86
88
|
|
|
87
89
|
constructor(readonly pi: ExtensionAPI) {}
|
|
88
90
|
|
|
91
|
+
safeSendUserMessage(text: string): void {
|
|
92
|
+
const log = (event: string, extra?: Record<string, unknown>) => {
|
|
93
|
+
if (this.active) appendTaskLog(this.active.dir, "debug.jsonl", { timestamp: new Date().toISOString(), event, text: text.slice(0, 200), ...extra });
|
|
94
|
+
};
|
|
95
|
+
const attempt = (retries: number) => {
|
|
96
|
+
try {
|
|
97
|
+
this.pi.sendUserMessage(text);
|
|
98
|
+
log("safeSend_sent", { retries });
|
|
99
|
+
} catch (err: any) {
|
|
100
|
+
if (retries < 30) {
|
|
101
|
+
setTimeout(() => attempt(retries + 1), 1000);
|
|
102
|
+
} else {
|
|
103
|
+
log("safeSend_failed", { error: err?.message ?? String(err), retries });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
attempt(0);
|
|
108
|
+
}
|
|
109
|
+
|
|
89
110
|
truncateResult(result: string): string {
|
|
90
111
|
const trimmed = result.trim();
|
|
91
112
|
if (!trimmed) return "";
|
|
@@ -247,11 +268,10 @@ export class Orchestrator {
|
|
|
247
268
|
): Promise<void> {
|
|
248
269
|
if (this.active) {
|
|
249
270
|
ctx.ui.notify(
|
|
250
|
-
`
|
|
271
|
+
`Pausing previous task "${this.active.description}" (phase: ${this.active.state.phase})…`,
|
|
251
272
|
"info",
|
|
252
273
|
);
|
|
253
274
|
this.abortAllSubagents();
|
|
254
|
-
this.active.state.phase = "done";
|
|
255
275
|
saveTask(this.active.dir, this.active.state);
|
|
256
276
|
unregisterAgentDefinitions(this.pi);
|
|
257
277
|
await this.cleanupActive();
|
|
@@ -344,7 +364,7 @@ export class Orchestrator {
|
|
|
344
364
|
} else if (isWaitingForPlanners) {
|
|
345
365
|
ctx.ui.notify("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
|
|
346
366
|
} else {
|
|
347
|
-
this.
|
|
367
|
+
this.safeSendUserMessage(`[PI-PI] Entered ${this.active.state.phase} phase. Begin working.`);
|
|
348
368
|
}
|
|
349
369
|
|
|
350
370
|
if (this.active.state.phase === "plan" && this.active.state.step === "await_planners") {
|
|
@@ -383,11 +403,12 @@ export class Orchestrator {
|
|
|
383
403
|
this.pendingSubagentSpawns = 0;
|
|
384
404
|
this.errorRetryCount = 0;
|
|
385
405
|
this.commitReminderSent = false;
|
|
406
|
+
this.textStopReminderSent = false;
|
|
386
407
|
this.nudgeTimestamps = [];
|
|
387
408
|
this.cooldownHits = [];
|
|
388
409
|
this.nudgeHalted = false;
|
|
389
410
|
this.phaseCompactionPending = false;
|
|
390
|
-
this.
|
|
411
|
+
this.phaseCompactionSummary = "";
|
|
391
412
|
this.phaseStartTime = 0;
|
|
392
413
|
this.userGatePending = false;
|
|
393
414
|
this.reviewTransitionToken = -1;
|
|
@@ -458,53 +479,32 @@ export class Orchestrator {
|
|
|
458
479
|
}
|
|
459
480
|
}
|
|
460
481
|
|
|
461
|
-
|
|
462
|
-
if (!this.phaseCompactionPending && !this.taskDoneCompactionPending) return Promise.resolve();
|
|
463
|
-
return new Promise((resolve) => {
|
|
464
|
-
this.phaseCompactionResolve = resolve;
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
compactAndTransition(ctx: ExtensionContext, taskDir: string, phase: Phase): void {
|
|
482
|
+
compactAndTransition(ctx: ExtensionContext, taskDir: string, phase: Phase, onReady?: () => void): void {
|
|
469
483
|
this.phaseCompactionPending = true;
|
|
484
|
+
const finalize = () => {
|
|
485
|
+
this.phaseStartTime = Date.now();
|
|
486
|
+
if (this.active && (phase === "plan" || phase === "implement")) {
|
|
487
|
+
const modelConfig = this.config.mainModel.implement;
|
|
488
|
+
this.switchModel(ctx, modelConfig.model, modelConfig.thinking).catch(() => {});
|
|
489
|
+
}
|
|
490
|
+
this.injectContextAndArtifacts(taskDir, phase);
|
|
491
|
+
onReady?.();
|
|
492
|
+
if (this.active?.state.phase === "plan" && this.active.state.step === "await_planners") {
|
|
493
|
+
ctx.ui.notify("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
|
|
494
|
+
} else {
|
|
495
|
+
this.safeSendUserMessage(`[PI-PI] Entered ${phase} phase. Begin working.`);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
470
498
|
ctx.compact({
|
|
471
499
|
customInstructions: "Phase transition — discard all prior conversation. Produce a one-line summary: 'Previous phase completed.'",
|
|
472
500
|
onComplete: () => {
|
|
473
501
|
this.phaseCompactionPending = false;
|
|
474
|
-
|
|
475
|
-
this.phaseCompactionResolve();
|
|
476
|
-
this.phaseCompactionResolve = null;
|
|
477
|
-
}
|
|
478
|
-
this.phaseStartTime = Date.now();
|
|
479
|
-
if (this.active && (phase === "plan" || phase === "implement")) {
|
|
480
|
-
const modelConfig = this.config.mainModel.implement;
|
|
481
|
-
this.switchModel(ctx, modelConfig.model, modelConfig.thinking).catch(() => {});
|
|
482
|
-
}
|
|
483
|
-
this.injectContextAndArtifacts(taskDir, phase);
|
|
484
|
-
if (this.active?.state.phase === "plan" && this.active.state.step === "await_planners") {
|
|
485
|
-
ctx.ui.notify("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
|
|
486
|
-
} else {
|
|
487
|
-
this.pi.sendUserMessage(`[PI-PI] Entered ${phase} phase. Begin working.`);
|
|
488
|
-
}
|
|
502
|
+
finalize();
|
|
489
503
|
},
|
|
490
504
|
onError: (err) => {
|
|
491
505
|
console.error(`[pi-pi] Phase compaction failed: ${err.message}`);
|
|
492
506
|
this.phaseCompactionPending = false;
|
|
493
|
-
|
|
494
|
-
this.phaseCompactionResolve();
|
|
495
|
-
this.phaseCompactionResolve = null;
|
|
496
|
-
}
|
|
497
|
-
this.phaseStartTime = Date.now();
|
|
498
|
-
if (this.active && (phase === "plan" || phase === "implement")) {
|
|
499
|
-
const modelConfig = this.config.mainModel.implement;
|
|
500
|
-
this.switchModel(ctx, modelConfig.model, modelConfig.thinking).catch(() => {});
|
|
501
|
-
}
|
|
502
|
-
this.injectContextAndArtifacts(taskDir, phase);
|
|
503
|
-
if (this.active?.state.phase === "plan" && this.active.state.step === "await_planners") {
|
|
504
|
-
ctx.ui.notify("Entered plan phase. Waiting for planners to complete before synthesis.", "info");
|
|
505
|
-
} else {
|
|
506
|
-
this.pi.sendUserMessage(`[PI-PI] Entered ${phase} phase. Begin working.`);
|
|
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
|
-
"#
|
|
18
|
-
"-
|
|
19
|
-
"-
|
|
20
|
-
"-
|
|
21
|
-
"- If the user asks you to implement a fix or start coding — call pp_phase_complete instead. It will offer \"Implement a fix\" as an option. Do NOT implement directly in this session.",
|
|
17
|
+
"# ABSOLUTE RESTRICTION — NO IMPLEMENTATION (cannot be overridden, even if the user asks):",
|
|
18
|
+
"- NEVER implement the solution, apply fixes, write production code, or make the changes that solve the task.",
|
|
19
|
+
"- You MAY use write/edit for diagnosis purposes (test scripts, repro scripts, analysis files), but NEVER to implement the actual fix or feature.",
|
|
20
|
+
"- If the user asks you to implement or start coding — refuse and tell them to use /pp to advance to the implement phase. Do NOT comply, even if they insist.",
|
|
22
21
|
"",
|
|
23
22
|
"# Your job:",
|
|
24
23
|
"1. Clarify the problem with the user if needed",
|
|
25
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
"
|
|
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
|
-
"#
|
|
116
|
-
"-
|
|
117
|
-
"-
|
|
118
|
-
"-
|
|
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
|
-
|
|
124
|
-
'
|
|
125
|
-
"
|
|
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
|
-
"
|
|
132
|
-
"
|
|
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
|
-
|
|
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
|
"",
|
|
@@ -16,7 +16,9 @@ export function planningSystemPrompt(taskDir: string): string {
|
|
|
16
16
|
"Planning subagents are working in parallel to create plans.",
|
|
17
17
|
`They will write their outputs to ${plansDir}/. Wait for the notification that all planners completed.`,
|
|
18
18
|
"",
|
|
19
|
-
"#
|
|
19
|
+
"# ABSOLUTE RESTRICTION — NO IMPLEMENTATION (cannot be overridden, even if the user asks):",
|
|
20
|
+
"- NEVER implement the solution, apply fixes, write production code, or make the changes that solve the task.",
|
|
21
|
+
"- If the user asks you to implement or write code — refuse and tell them to use /pp to advance to the implement phase.",
|
|
20
22
|
"- Do NOT write your own plan from scratch. You are a SYNTHESIZER, not a planner.",
|
|
21
23
|
"- Do NOT create the plans/ directory yourself — the extension manages it.",
|
|
22
24
|
"- Do NOT check the plans directory yourself — wait for the notification that all planners completed.",
|
|
@@ -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
|
-
|
|
347
|
+
orchestrator.safeSendUserMessage(`[PI-PI] Resumed ${orchestrator.active.state.phase} phase. Read reviewer outputs and apply feedback.`);
|
|
348
348
|
} else {
|
|
349
|
-
|
|
349
|
+
orchestrator.safeSendUserMessage(`[PI-PI] Resumed ${orchestrator.active.state.phase} phase. Continue working.`);
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
return { ok: true };
|
|
@@ -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
|
|
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);
|
|
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
|
|
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, "
|
|
723
|
+
const choice = await selectOption(ctx, "Info", options);
|
|
733
724
|
if (!choice || choice === "Back") return BACK;
|
|
734
|
-
if (choice === "
|
|
735
|
-
|
|
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 === "
|
|
743
|
-
|
|
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
|
|
1037
|
+
async function showTaskMenu(orchestrator: Orchestrator, ctx: any): Promise<typeof BACK | "started"> {
|
|
1032
1038
|
while (true) {
|
|
1033
|
-
const choice = await selectOption(ctx, "
|
|
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: "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 === "
|
|
1076
|
-
await
|
|
1097
|
+
if (choice === "Info") {
|
|
1098
|
+
await showInfoMenu(orchestrator, ctx);
|
|
1077
1099
|
continue;
|
|
1078
1100
|
}
|
|
1079
1101
|
|
|
1080
|
-
await showSettingsMenu(orchestrator, ctx
|
|
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("
|
|
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("
|
|
1141
|
-
options.push(opt("Settings", "
|
|
1162
|
+
options.push(opt("Info", "Subagents, LSP, usage, and task status"));
|
|
1163
|
+
options.push(opt("Settings", "Flant AI and other configuration"));
|
|
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 === "
|
|
1153
|
-
await
|
|
1174
|
+
if (choice === "Info") {
|
|
1175
|
+
await showInfoMenu(orchestrator, ctx);
|
|
1154
1176
|
continue;
|
|
1155
1177
|
}
|
|
1156
1178
|
if (choice === "Settings") {
|
|
1157
|
-
await showSettingsMenu(orchestrator, ctx
|
|
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 === "
|
|
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, "
|
|
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
|
|
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
|
|
255
|
+
return totalCacheRead / denominator;
|
|
229
256
|
},
|
|
230
257
|
|
|
231
258
|
getPerModelUsage(): Record<string, ModelUsage> {
|