@ilya-lesikov/pi-pi 0.7.0 → 0.8.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/README.md +6 -5
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +25 -10
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.code-review.test.ts +172 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +50 -12
- package/3p/pi-plannotator/apps/pi-extension/server/project.test.ts +45 -0
- package/3p/pi-plannotator/apps/pi-extension/server/project.ts +7 -6
- package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +41 -25
- package/3p/pi-subagents/src/agent-manager.ts +34 -1
- package/3p/pi-subagents/src/agent-runner.ts +66 -33
- package/3p/pi-subagents/src/index.ts +3 -38
- package/3p/pi-subagents/src/types.ts +4 -0
- package/extensions/orchestrator/agents/advisor.ts +35 -0
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +7 -7
- package/extensions/orchestrator/agents/code-reviewer.ts +7 -7
- package/extensions/orchestrator/agents/constraints.test.ts +44 -0
- package/extensions/orchestrator/agents/constraints.ts +3 -0
- package/extensions/orchestrator/agents/deep-debugger.ts +35 -0
- package/extensions/orchestrator/agents/plan-reviewer.ts +7 -7
- package/extensions/orchestrator/agents/planner.ts +9 -8
- package/extensions/orchestrator/agents/prompts.test.ts +120 -0
- package/extensions/orchestrator/agents/reviewer.ts +48 -0
- package/extensions/orchestrator/agents/task.ts +6 -20
- package/extensions/orchestrator/agents/tool-routing.ts +23 -1
- package/extensions/orchestrator/command-handlers.ts +1 -1
- package/extensions/orchestrator/config.ts +5 -2
- package/extensions/orchestrator/context.test.ts +54 -0
- package/extensions/orchestrator/context.ts +65 -2
- package/extensions/orchestrator/event-handlers.test.ts +97 -1
- package/extensions/orchestrator/event-handlers.ts +176 -43
- package/extensions/orchestrator/flant-infra.test.ts +25 -0
- package/extensions/orchestrator/flant-infra.ts +91 -0
- package/extensions/orchestrator/index.ts +1 -1
- package/extensions/orchestrator/integration.test.ts +107 -12
- package/extensions/orchestrator/messages.test.ts +30 -0
- package/extensions/orchestrator/messages.ts +6 -0
- package/extensions/orchestrator/model-registry.test.ts +48 -1
- package/extensions/orchestrator/model-registry.ts +43 -1
- package/extensions/orchestrator/orchestrator.test.ts +113 -0
- package/extensions/orchestrator/orchestrator.ts +151 -2
- package/extensions/orchestrator/phases/brainstorm.ts +9 -20
- package/extensions/orchestrator/phases/implementation.test.ts +11 -0
- package/extensions/orchestrator/phases/implementation.ts +4 -6
- package/extensions/orchestrator/phases/planning.test.ts +16 -0
- package/extensions/orchestrator/phases/planning.ts +11 -4
- package/extensions/orchestrator/phases/review-task.ts +1 -4
- package/extensions/orchestrator/phases/review.test.ts +8 -0
- package/extensions/orchestrator/phases/review.ts +4 -3
- package/extensions/orchestrator/plannotator.ts +9 -6
- package/extensions/orchestrator/pp-menu.ts +168 -54
- package/extensions/orchestrator/pp-state-tools.test.ts +192 -0
- package/extensions/orchestrator/pp-state-tools.ts +249 -0
- package/extensions/orchestrator/rate-limit-fallback-flow.test.ts +128 -0
- package/extensions/orchestrator/rate-limit-fallback.test.ts +98 -0
- package/extensions/orchestrator/rate-limit-fallback.ts +243 -0
- package/extensions/orchestrator/test-helpers.ts +4 -1
- package/extensions/orchestrator/usage-tracker.ts +5 -1
- package/extensions/orchestrator/validate-artifacts.test.ts +20 -0
- package/extensions/orchestrator/validate-artifacts.ts +2 -2
- package/package.json +1 -1
|
@@ -24,7 +24,10 @@ import { registerAgentDefinitions, unregisterAgentDefinitions } from "./agents/r
|
|
|
24
24
|
import { createExploreAgent } from "./agents/explore.js";
|
|
25
25
|
import { createLibrarianAgent } from "./agents/librarian.js";
|
|
26
26
|
import { createTaskAgent } from "./agents/task.js";
|
|
27
|
-
import {
|
|
27
|
+
import { createAdvisorAgent } from "./agents/advisor.js";
|
|
28
|
+
import { createDeepDebuggerAgent } from "./agents/deep-debugger.js";
|
|
29
|
+
import { createReviewerAgent } from "./agents/reviewer.js";
|
|
30
|
+
import { resolveModel, getModelInfo, findLatestFamilyMatch, setSubscriptionFallbackActive } from "./model-registry.js";
|
|
28
31
|
import { buildRepoContext } from "./agents/repo-context.js";
|
|
29
32
|
import { getLogger, addTaskDestination, removeTaskDestination, setLogLevel } from "./log.js";
|
|
30
33
|
import { handleSpawnResult } from "./spawn-cleanup.js";
|
|
@@ -77,10 +80,36 @@ export class Orchestrator {
|
|
|
77
80
|
nudgeHalted = false;
|
|
78
81
|
pendingSubagentSpawns = 0;
|
|
79
82
|
errorRetryCount = 0;
|
|
83
|
+
// Halts the API-error auto-retry once errorRetryCount exceeds its cap, mirroring
|
|
84
|
+
// nudgeHalted. Without this, a benign intervening turn (e.g. the retried turn
|
|
85
|
+
// ends as a text-only "I'll wait") reset errorRetryCount to 0, so the 5-retry
|
|
86
|
+
// cap never accumulated and the "Previous request failed" nudge could fire
|
|
87
|
+
// unbounded (hundreds of times) against transient errors. Cleared only on
|
|
88
|
+
// genuine (non-[PI-PI]) user re-engagement, like nudgeHalted.
|
|
89
|
+
errorNudgeHalted = false;
|
|
80
90
|
commitReminderSent = false;
|
|
81
91
|
phaseStartTime = 0;
|
|
82
92
|
pendingRetryTimer: ReturnType<typeof setTimeout> | null = null;
|
|
93
|
+
// Unsubscribe for the direct ESC interrupt armed while pendingRetryTimer is
|
|
94
|
+
// live. pi-pi's own post-error retry is NOT covered by any SDK/interactive ESC
|
|
95
|
+
// binding (the turn already ended in error, the session is not streaming), so
|
|
96
|
+
// without this ESC would not cancel it.
|
|
97
|
+
pendingRetryEscUnsub: (() => void) | null = null;
|
|
83
98
|
activeTaskToken = 0;
|
|
99
|
+
// Subscription rate-limit fallback (Issue 5). subFallbackActive mirrors the
|
|
100
|
+
// model-registry override flag; subFallbackDialogPending guards against
|
|
101
|
+
// opening more than one switch dialogue at a time (across main + subagents);
|
|
102
|
+
// subFallbackModelId records the sub model that hit the limit (used by the
|
|
103
|
+
// switch-back probe); subSwitchBackTimer is the fixed-interval probe timer.
|
|
104
|
+
subFallbackActive = false;
|
|
105
|
+
subFallbackDialogPending = false;
|
|
106
|
+
// Set SYNCHRONOUSLY the moment a sub-429 is detected (before any async dialog),
|
|
107
|
+
// and cleared once the decision resolves. The autonomous planner/reviewer
|
|
108
|
+
// auto-retry consults this to avoid re-spawning a failed variant on the still-
|
|
109
|
+
// sub-routed model while the fallback decision is in flight.
|
|
110
|
+
subFallbackPendingDecision = false;
|
|
111
|
+
subFallbackModelId: string | null = null;
|
|
112
|
+
subSwitchBackTimer: ReturnType<typeof setTimeout> | null = null;
|
|
84
113
|
userGatePending = false;
|
|
85
114
|
lastCtx: any = null;
|
|
86
115
|
failedPlannerVariants: string[] = [];
|
|
@@ -123,6 +152,87 @@ export class Orchestrator {
|
|
|
123
152
|
};
|
|
124
153
|
}
|
|
125
154
|
|
|
155
|
+
// Arm a direct ESC interrupt for the post-error retry window. Idempotent: a
|
|
156
|
+
// single onTerminalInput handler stays registered until the retry is delivered,
|
|
157
|
+
// cancelled, or the task is reset. While pendingRetryTimer is live, ESC cancels
|
|
158
|
+
// the pending retry (no other binding covers this window).
|
|
159
|
+
armRetryEscInterrupt(ctx: any): void {
|
|
160
|
+
if (this.pendingRetryEscUnsub) return;
|
|
161
|
+
const onTerminalInput = ctx?.ui?.onTerminalInput;
|
|
162
|
+
if (typeof onTerminalInput !== "function") return;
|
|
163
|
+
const unsub = onTerminalInput.call(ctx.ui, (data: string) => {
|
|
164
|
+
if (!this.pendingRetryTimer) return undefined;
|
|
165
|
+
// Match a STANDALONE ESC only. Arrow/function/mouse sequences also start
|
|
166
|
+
// with 0x1b (e.g. "\x1b[A"), so `includes` would misfire on navigation
|
|
167
|
+
// keys and swallow them; a bare ESC is exactly the one-byte string.
|
|
168
|
+
if (data === "\x1b") {
|
|
169
|
+
this.cancelPendingRetry();
|
|
170
|
+
ctx?.ui?.notify?.("Retry cancelled.", "info");
|
|
171
|
+
return { consume: true };
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
});
|
|
175
|
+
this.pendingRetryEscUnsub = typeof unsub === "function" ? unsub : null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
disarmRetryEscInterrupt(): void {
|
|
179
|
+
if (this.pendingRetryEscUnsub) {
|
|
180
|
+
try {
|
|
181
|
+
this.pendingRetryEscUnsub();
|
|
182
|
+
} catch {
|
|
183
|
+
// ignore unsubscribe failures
|
|
184
|
+
}
|
|
185
|
+
this.pendingRetryEscUnsub = null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Cancel a pending post-error retry (timer + ESC interrupt) and reset the retry
|
|
190
|
+
// counter. Used by the ESC interrupt handler and by abort paths.
|
|
191
|
+
cancelPendingRetry(): void {
|
|
192
|
+
if (this.pendingRetryTimer) {
|
|
193
|
+
clearTimeout(this.pendingRetryTimer);
|
|
194
|
+
this.pendingRetryTimer = null;
|
|
195
|
+
}
|
|
196
|
+
this.disarmRetryEscInterrupt();
|
|
197
|
+
this.errorRetryCount = 0;
|
|
198
|
+
this.errorNudgeHalted = false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Deliver a queued message only once the main session is idle. Firing a
|
|
202
|
+
// followUp while the SDK still has an active run triggers an async, runtime-
|
|
203
|
+
// swallowed "Agent is already processing" rejection (surfaces as
|
|
204
|
+
// Extension "<runtime>" error), so we PRE-CHECK idle and DEFER (bounded poll)
|
|
205
|
+
// rather than dropping the nudge. Guarded by activeTaskToken; the poll reuses
|
|
206
|
+
// pendingRetryTimer so ESC/abort cancels it.
|
|
207
|
+
sendUserMessageWhenIdle(text: string, taskToken: number, attempt = 0): void {
|
|
208
|
+
const log = getLogger();
|
|
209
|
+
if (this.activeTaskToken !== taskToken || !this.active) {
|
|
210
|
+
this.disarmRetryEscInterrupt();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const idleFn = this.lastCtx?.isIdle;
|
|
214
|
+
const idle = typeof idleFn === "function" ? !!idleFn.call(this.lastCtx) : true;
|
|
215
|
+
if (idle) {
|
|
216
|
+
this.disarmRetryEscInterrupt();
|
|
217
|
+
this.safeSendUserMessage(text);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const MAX_ATTEMPTS = 120; // ~2min at 1s poll
|
|
221
|
+
if (attempt >= MAX_ATTEMPTS) {
|
|
222
|
+
log.warn({ s: "orchestrator", attempt }, "sendUserMessageWhenIdle gave up waiting for idle");
|
|
223
|
+
this.disarmRetryEscInterrupt();
|
|
224
|
+
this.lastCtx?.ui?.notify?.(
|
|
225
|
+
"pi-pi stopped waiting for the agent to go idle; auto-continuation was dropped. Send any message to resume.",
|
|
226
|
+
"warning",
|
|
227
|
+
);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
this.pendingRetryTimer = setTimeout(() => {
|
|
231
|
+
this.pendingRetryTimer = null;
|
|
232
|
+
this.sendUserMessageWhenIdle(text, taskToken, attempt + 1);
|
|
233
|
+
}, 1000);
|
|
234
|
+
}
|
|
235
|
+
|
|
126
236
|
safeSendUserMessage(text: string): void {
|
|
127
237
|
const log = getLogger();
|
|
128
238
|
const attempt = (retries: number) => {
|
|
@@ -539,6 +649,7 @@ export class Orchestrator {
|
|
|
539
649
|
this.agentLifecycle.clear();
|
|
540
650
|
this.pendingSubagentSpawns = 0;
|
|
541
651
|
this.errorRetryCount = 0;
|
|
652
|
+
this.errorNudgeHalted = false;
|
|
542
653
|
this.commitReminderSent = false;
|
|
543
654
|
this.consecutiveNudges = 0;
|
|
544
655
|
this.nudgeHalted = false;
|
|
@@ -552,10 +663,27 @@ export class Orchestrator {
|
|
|
552
663
|
clearTimeout(this.pendingRetryTimer);
|
|
553
664
|
this.pendingRetryTimer = null;
|
|
554
665
|
}
|
|
666
|
+
this.disarmRetryEscInterrupt();
|
|
555
667
|
if (this.staleAgentTimer) {
|
|
556
668
|
clearInterval(this.staleAgentTimer);
|
|
557
669
|
this.staleAgentTimer = null;
|
|
558
670
|
}
|
|
671
|
+
this.clearSubscriptionFallback();
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Reset the subscription rate-limit fallback: cancel the switch-back probe
|
|
675
|
+
// timer, clear the model-registry override, and reset guards. Called on task
|
|
676
|
+
// reset/cleanup so the sticky override never leaks across tasks.
|
|
677
|
+
clearSubscriptionFallback(): void {
|
|
678
|
+
if (this.subSwitchBackTimer) {
|
|
679
|
+
clearTimeout(this.subSwitchBackTimer);
|
|
680
|
+
this.subSwitchBackTimer = null;
|
|
681
|
+
}
|
|
682
|
+
this.subFallbackActive = false;
|
|
683
|
+
this.subFallbackDialogPending = false;
|
|
684
|
+
this.subFallbackPendingDecision = false;
|
|
685
|
+
this.subFallbackModelId = null;
|
|
686
|
+
setSubscriptionFallbackActive(false);
|
|
559
687
|
}
|
|
560
688
|
|
|
561
689
|
async cleanupActive(): Promise<void> {
|
|
@@ -578,7 +706,10 @@ export class Orchestrator {
|
|
|
578
706
|
const log = getLogger();
|
|
579
707
|
const explore = createExploreAgent(this.config);
|
|
580
708
|
const librarian = createLibrarianAgent(this.config);
|
|
581
|
-
const taskAgent = createTaskAgent(this.config
|
|
709
|
+
const taskAgent = createTaskAgent(this.config);
|
|
710
|
+
const advisor = createAdvisorAgent(this.config);
|
|
711
|
+
const deepDebugger = createDeepDebuggerAgent(this.config);
|
|
712
|
+
const reviewer = createReviewerAgent(this.config);
|
|
582
713
|
const phase = this.active?.state.phase;
|
|
583
714
|
const repos = this.active?.state.repos ?? [];
|
|
584
715
|
log.debug({ s: "agents", phase, repoCount: repos.length }, "registering agent definitions");
|
|
@@ -615,6 +746,24 @@ export class Orchestrator {
|
|
|
615
746
|
...taskAgent,
|
|
616
747
|
prompt: appendContext("task", taskAgent.prompt, getModelInfo(resolveModel(this.config.agents.subagents.simple.task.model))),
|
|
617
748
|
},
|
|
749
|
+
{
|
|
750
|
+
type: "advisor",
|
|
751
|
+
variant: null,
|
|
752
|
+
...advisor,
|
|
753
|
+
prompt: appendContext("advisor", advisor.prompt, getModelInfo(resolveModel(this.config.agents.subagents.simple.advisor.model))),
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
type: "deep-debugger",
|
|
757
|
+
variant: null,
|
|
758
|
+
...deepDebugger,
|
|
759
|
+
prompt: appendContext("deep-debugger", deepDebugger.prompt, getModelInfo(resolveModel(this.config.agents.subagents.simple["deep-debugger"].model))),
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
type: "reviewer",
|
|
763
|
+
variant: null,
|
|
764
|
+
...reviewer,
|
|
765
|
+
prompt: appendContext("reviewer", reviewer.prompt, getModelInfo(resolveModel(this.config.agents.subagents.simple.reviewer.model))),
|
|
766
|
+
},
|
|
618
767
|
]);
|
|
619
768
|
}
|
|
620
769
|
|
|
@@ -4,7 +4,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
4
4
|
import { resolvePreset, type PiPiConfig, type VariantConfig } from "../config.js";
|
|
5
5
|
import { registerAgentDefinitions, spawnViaRpc, waitForCompletion } from "../agents/registry.js";
|
|
6
6
|
import { createBrainstormReviewerAgent } from "../agents/brainstorm-reviewer.js";
|
|
7
|
-
import { getContextDirs } from "../context.js";
|
|
7
|
+
import { getContextDirs, getArtifactManifest } from "../context.js";
|
|
8
8
|
import type { RepoInfo } from "../repo-utils.js";
|
|
9
9
|
import type { TaskType } from "../state.js";
|
|
10
10
|
import type { PhaseSend } from "../transition-controller.js";
|
|
@@ -26,13 +26,8 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
26
26
|
"",
|
|
27
27
|
"# Your job:",
|
|
28
28
|
"1. Clarify the problem with the user if needed",
|
|
29
|
-
"2.
|
|
30
|
-
|
|
31
|
-
' - Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
|
|
32
|
-
' - Agent(subagent_type="Task", ...) — only when you need a subtask that writes files or runs complex multi-step commands.',
|
|
33
|
-
" Explore is fast and cheap. Use it liberally for codebase questions. Spawn multiple in parallel.",
|
|
34
|
-
"3. Use bash to run commands, check logs, reproduce issues",
|
|
35
|
-
"4. Use tools to trace the bug:",
|
|
29
|
+
"2. Delegate research to subagents where useful (see the delegation guidance in your system prompt), and use bash to run commands, check logs, reproduce issues",
|
|
30
|
+
"3. Use tools to trace the bug:",
|
|
36
31
|
" - cbm_search/cbm_search_code: find relevant code by concept or text",
|
|
37
32
|
" - lsp goToDefinition, findReferences, hover: precise navigation and type info",
|
|
38
33
|
" - lsp goToImplementation: check interface implementors",
|
|
@@ -60,6 +55,7 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
60
55
|
" <Unresolved items needing user input. Omit section if none.>",
|
|
61
56
|
"",
|
|
62
57
|
"These files are validated programmatically. Missing sections or unexpected sections will be rejected.",
|
|
58
|
+
"Use pp_write_state_file / pp_edit_state_file (NOT the generic write/edit) for .pp state files — they keep the output compact and validate structure.",
|
|
63
59
|
"",
|
|
64
60
|
"# Optional: focused analysis artifacts",
|
|
65
61
|
`You may also write additional analysis files to ${taskDir}/artifacts/<name>.md`,
|
|
@@ -84,11 +80,7 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
84
80
|
"",
|
|
85
81
|
"# How to work:",
|
|
86
82
|
"- Discuss the topic with the user. Ask clarifying questions. Propose approaches. Analyze tradeoffs.",
|
|
87
|
-
|
|
88
|
-
' Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
|
|
89
|
-
' Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
|
|
90
|
-
' Agent(subagent_type="Task", ...) — only when you need a subtask that writes files or runs complex multi-step commands.',
|
|
91
|
-
" Spawn multiple Explore agents in parallel for broad searches.",
|
|
83
|
+
"- Delegate research to subagents where useful (see the delegation guidance in your system prompt).",
|
|
92
84
|
"- Use tools directly for quick lookups (cbm_search, lsp, ast_search, grep, etc.)",
|
|
93
85
|
"- Present findings to the user and discuss them. Don't just dump raw results.",
|
|
94
86
|
"",
|
|
@@ -120,11 +112,7 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
120
112
|
"",
|
|
121
113
|
"# Steps:",
|
|
122
114
|
"1. Clarify requirements with the user if anything is ambiguous",
|
|
123
|
-
"2.
|
|
124
|
-
' - Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
|
|
125
|
-
' - Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
|
|
126
|
-
' - Agent(subagent_type="Task", ...) — only when you need a subtask that writes files or runs complex multi-step commands.',
|
|
127
|
-
" Spawn multiple Explore agents in parallel for broad searches.",
|
|
115
|
+
"2. Delegate research to subagents where useful (see the delegation guidance in your system prompt) — spawn multiple explores in parallel for broad searches",
|
|
128
116
|
"3. Use tools to understand code structure:",
|
|
129
117
|
" - cbm_search: natural-language search across all symbols",
|
|
130
118
|
" - cbm_search_code: graph-augmented grep (deduplicates into functions)",
|
|
@@ -155,6 +143,7 @@ export function brainstormSystemPrompt(taskType: TaskType, taskDescription: stri
|
|
|
155
143
|
" <Unresolved items needing user input. Omit section if none.>",
|
|
156
144
|
"",
|
|
157
145
|
"These files are validated programmatically. Missing sections or unexpected sections will be rejected.",
|
|
146
|
+
"Use pp_write_state_file / pp_edit_state_file (NOT the generic write/edit) for .pp state files — they keep the output compact and validate structure.",
|
|
158
147
|
"",
|
|
159
148
|
"# Optional: focused analysis artifacts",
|
|
160
149
|
`You may also write additional analysis files to ${taskDir}/artifacts/<name>.md`,
|
|
@@ -210,7 +199,7 @@ export async function spawnBrainstormReviewers(
|
|
|
210
199
|
const agent = createBrainstormReviewerAgent(
|
|
211
200
|
variant,
|
|
212
201
|
reviewerVariants,
|
|
213
|
-
{ userRequest, research, artifacts: artifacts.length > 0 ? artifacts : undefined },
|
|
202
|
+
{ userRequest, research, artifacts: artifacts.length > 0 ? artifacts : undefined, manifest: getArtifactManifest(taskDir) },
|
|
214
203
|
outputPath,
|
|
215
204
|
contextDirs,
|
|
216
205
|
"brainstorm",
|
|
@@ -261,7 +250,7 @@ export async function spawnBrainstormReviewers(
|
|
|
261
250
|
`${reviewOutputFiles.length} brainstorm reviewer(s) completed (round ${round}). Reviews in ${reviewsDir}:`,
|
|
262
251
|
...reviewOutputFiles.map((f) => ` - ${f}`),
|
|
263
252
|
"",
|
|
264
|
-
"Read all reviews and update USER_REQUEST.md
|
|
253
|
+
"Read all reviews and update USER_REQUEST.md, RESEARCH.md, and any artifacts/ files if needed.",
|
|
265
254
|
].join("\n"),
|
|
266
255
|
display: true,
|
|
267
256
|
},
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { implementationSystemPrompt } from "./implementation.js";
|
|
3
|
+
|
|
4
|
+
describe("implementationSystemPrompt self-complete directive", () => {
|
|
5
|
+
it("instructs the agent to call pp_phase_complete when the implement phase is complete", () => {
|
|
6
|
+
const prompt = implementationSystemPrompt("/tmp/task", "/tmp");
|
|
7
|
+
expect(prompt).toContain("pp_phase_complete");
|
|
8
|
+
expect(prompt).toContain("Do NOT instead ask the user to run /pp manually");
|
|
9
|
+
expect(prompt).not.toContain("do NOT wait for the user");
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -11,7 +11,7 @@ export function implementationSystemPrompt(taskDir: string, cwd: string): string
|
|
|
11
11
|
"# Instructions:",
|
|
12
12
|
"1. Review the synthesized plan in your context",
|
|
13
13
|
"2. Implement each item in order",
|
|
14
|
-
"3. Check off items in the plan (change - [ ] to - [x]) as you complete them",
|
|
14
|
+
"3. Check off items in the plan (change - [ ] to - [x]) as you complete them — use pp_edit_state_file (NOT the generic edit) to update the plan compactly",
|
|
15
15
|
"4. After editing files, run lsp diagnostics to check for errors; use lsp codeActions for auto-fixes",
|
|
16
16
|
"5. Before modifying code, understand it first:",
|
|
17
17
|
" - cbm_search/cbm_search_code: find relevant functions and their context",
|
|
@@ -21,11 +21,7 @@ export function implementationSystemPrompt(taskDir: string, cwd: string): string
|
|
|
21
21
|
" - cbm_trace: trace dependencies across the codebase",
|
|
22
22
|
" - ast_search: find structural patterns (e.g. error handling conventions)",
|
|
23
23
|
"6. After completing changes, run cbm_changes to verify blast radius",
|
|
24
|
-
"7.
|
|
25
|
-
' - Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
|
|
26
|
-
' - Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
|
|
27
|
-
' - Agent(subagent_type="Task", ...) — parallelizable, self-contained implementation subtasks.',
|
|
28
|
-
" Spawn multiple Explore agents in parallel for broad searches.",
|
|
24
|
+
"7. Delegate to subagents where useful (see the delegation guidance in your system prompt) — spawn multiple explores in parallel for broad searches, and use task for parallelizable self-contained implementation subtasks",
|
|
29
25
|
"",
|
|
30
26
|
"# Commits:",
|
|
31
27
|
"After completing a logical unit of work (a plan item, a bug fix, a test), call pp_commit",
|
|
@@ -34,5 +30,7 @@ export function implementationSystemPrompt(taskDir: string, cwd: string): string
|
|
|
34
30
|
"Don't batch all changes into one commit.",
|
|
35
31
|
"",
|
|
36
32
|
"Fix issues found by lsp diagnostics before moving on.",
|
|
33
|
+
"",
|
|
34
|
+
"When you judge the implement phase complete, call pp_phase_complete — the extension opens the advance gate for the user to review and confirm. Do NOT instead ask the user to run /pp manually.",
|
|
37
35
|
].join("\n");
|
|
38
36
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { planningSystemPrompt } from "./planning.js";
|
|
3
|
+
|
|
4
|
+
describe("planningSystemPrompt self-complete directive", () => {
|
|
5
|
+
it("guided synthesis instructs the agent to call pp_phase_complete when synthesis is complete", () => {
|
|
6
|
+
const prompt = planningSystemPrompt("/tmp/task", "guided");
|
|
7
|
+
expect(prompt).toContain("pp_phase_complete");
|
|
8
|
+
expect(prompt).toContain("Do NOT instead ask the user to run /pp manually");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("autonomous synthesis does not add the guided self-complete directive", () => {
|
|
12
|
+
const prompt = planningSystemPrompt("/tmp/task", "autonomous");
|
|
13
|
+
expect(prompt).not.toContain("pp_phase_complete");
|
|
14
|
+
expect(prompt).not.toContain("Do NOT instead ask the user to run /pp manually");
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -5,7 +5,7 @@ import { resolvePreset, type PiPiConfig, type VariantConfig } from "../config.js
|
|
|
5
5
|
import { registerAgentDefinitions, spawnViaRpc, waitForCompletion } from "../agents/registry.js";
|
|
6
6
|
import { createPlannerAgent } from "../agents/planner.js";
|
|
7
7
|
import { createPlanReviewerAgent } from "../agents/plan-reviewer.js";
|
|
8
|
-
import { getContextDirs, getLatestSynthesizedPlan } from "../context.js";
|
|
8
|
+
import { getContextDirs, getLatestSynthesizedPlan, getArtifactManifest } from "../context.js";
|
|
9
9
|
import type { RepoInfo } from "../repo-utils.js";
|
|
10
10
|
import { validatePlan } from "../validate-artifacts.js";
|
|
11
11
|
import type { TaskMode } from "../state.js";
|
|
@@ -21,6 +21,10 @@ export function planningSystemPrompt(taskDir: string, mode: TaskMode): string {
|
|
|
21
21
|
mode === "autonomous"
|
|
22
22
|
? " If planner outputs CONTRADICT each other on a locked decision, resolve it by favoring USER_REQUEST.md, RECORD the contradiction and your chosen resolution in the plan, and proceed — do NOT stall waiting for the user (there is none)."
|
|
23
23
|
: " If planner outputs CONTRADICT each other on a locked decision, surface the contradiction to the user and let them decide — do NOT silently invent a compromise.";
|
|
24
|
+
const synthesizeCompletionRule =
|
|
25
|
+
mode === "autonomous"
|
|
26
|
+
? ""
|
|
27
|
+
: "When you judge the plan synthesis complete, call pp_phase_complete — the extension opens the advance gate for the user to review and confirm. Do NOT instead ask the user to run /pp manually.";
|
|
24
28
|
return [
|
|
25
29
|
"[PI-PI — PLAN PHASE]",
|
|
26
30
|
"",
|
|
@@ -46,9 +50,12 @@ export function planningSystemPrompt(taskDir: string, mode: TaskMode): string {
|
|
|
46
50
|
"- ## Scope: 2-4 lines — what changes, what doesn't, critical constraints",
|
|
47
51
|
"- ## Checklist: each item is - [ ] <outcome> — Done when: <observable condition>",
|
|
48
52
|
" Each item = one independently verifiable outcome. No code snippets or file-by-file instructions.",
|
|
53
|
+
"- ## Pattern constraints: include this section whenever the task adds a type, function, parser, annotation, config key, enum, or any user-facing value. For each, name the CLOSEST EXISTING analog in the codebase (found by behavior, not filename) and the exact conventions the implementer MUST mirror: data shape (prefer one existing shape over inventing parallel/duplicated state), spelling/casing of user-facing values (match existing values — never invent a new casing), and parser/validation/error-handling shape. These are acceptance criteria, not suggestions. Omit the section only if the task adds none of the above.",
|
|
49
54
|
"- ## Blockers: unresolved issues blocking implementation (omit if none)",
|
|
55
|
+
"Write/update the synthesized plan with pp_write_state_file / pp_edit_state_file (NOT the generic write/edit) — they keep the output compact and validate structure.",
|
|
50
56
|
"- No other top-level sections allowed",
|
|
51
|
-
"- Describe outcomes, not code-level mechanics",
|
|
57
|
+
"- Describe outcomes, not code-level mechanics, EXCEPT in ## Pattern constraints where naming the concrete analog and conventions is required",
|
|
58
|
+
...(synthesizeCompletionRule ? ["", synthesizeCompletionRule] : []),
|
|
52
59
|
].join("\n");
|
|
53
60
|
}
|
|
54
61
|
|
|
@@ -84,7 +91,7 @@ export async function spawnPlanners(
|
|
|
84
91
|
|
|
85
92
|
for (const [variant] of enabledVariants) {
|
|
86
93
|
const outputPath = join(plansDir, `${timestamp}_${variant}.md`);
|
|
87
|
-
const agent = createPlannerAgent(variant, plannerVariants, { userRequest, research }, outputPath, contextDirs, "plan", repos);
|
|
94
|
+
const agent = createPlannerAgent(variant, plannerVariants, { userRequest, research, manifest: getArtifactManifest(taskDir) }, outputPath, contextDirs, "plan", repos);
|
|
88
95
|
|
|
89
96
|
registerAgentDefinitions(pi, [{ type: "planner", variant, ...agent }]);
|
|
90
97
|
|
|
@@ -211,7 +218,7 @@ export async function spawnPlanReviewers(
|
|
|
211
218
|
const agent = createPlanReviewerAgent(
|
|
212
219
|
variant,
|
|
213
220
|
reviewerVariants,
|
|
214
|
-
{ userRequest, research, synthesizedPlan },
|
|
221
|
+
{ userRequest, research, synthesizedPlan, manifest: getArtifactManifest(taskDir) },
|
|
215
222
|
outputPath,
|
|
216
223
|
contextDirs,
|
|
217
224
|
"plan",
|
|
@@ -12,10 +12,7 @@ export function reviewSystemPrompt(taskDir: string, cwd: string): string {
|
|
|
12
12
|
"- read, lsp, grep, find for understanding the code",
|
|
13
13
|
"- gh pr view for GitHub PR context if a PR URL is mentioned",
|
|
14
14
|
"",
|
|
15
|
-
"
|
|
16
|
-
'- Agent(subagent_type="Explore", ...) — codebase research. Prefer this for most lookups. Fast and cheap.',
|
|
17
|
-
'- Agent(subagent_type="Librarian", ...) — external docs, library APIs, web research.',
|
|
18
|
-
"Spawn multiple Explore agents in parallel for broad searches.",
|
|
15
|
+
"Delegate to subagents where useful (see the delegation guidance in your system prompt) — spawn multiple explores in parallel for broad searches.",
|
|
19
16
|
"",
|
|
20
17
|
"Write your findings:",
|
|
21
18
|
`- ${taskDir}/USER_REQUEST.md — update with a clear problem statement of issues found`,
|
|
@@ -23,6 +23,14 @@ describe("reviewSystemPrompt apply_feedback wording", () => {
|
|
|
23
23
|
expect(auto).not.toContain("pp_phase_complete");
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
it("brainstorm apply-feedback prompt permits artifact updates and steers to the compact tools", () => {
|
|
27
|
+
const prompt = reviewSystemPrompt("/tmp/task", 1, "brainstorm", "autonomous");
|
|
28
|
+
expect(prompt).toContain("artifacts/");
|
|
29
|
+
expect(prompt).toContain("pp_write_state_file");
|
|
30
|
+
// The no-new-sections rule for the two structured files is retained.
|
|
31
|
+
expect(prompt).toContain("Do NOT add, rename, or remove sections in USER_REQUEST.md or RESEARCH.md");
|
|
32
|
+
});
|
|
33
|
+
|
|
26
34
|
it("autonomous plan feedback folds into a new synthesized plan, not a separate fix-plan file", () => {
|
|
27
35
|
// Re-review and the transition read only the latest `*synthesized*` plan, so
|
|
28
36
|
// plan-phase feedback must land there.
|
|
@@ -4,7 +4,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
4
4
|
import { resolvePreset, type PiPiConfig, type VariantConfig } from "../config.js";
|
|
5
5
|
import { registerAgentDefinitions, spawnViaRpc, waitForCompletion } from "../agents/registry.js";
|
|
6
6
|
import { createCodeReviewerAgent } from "../agents/code-reviewer.js";
|
|
7
|
-
import { getContextDirs, getLatestSynthesizedPlan } from "../context.js";
|
|
7
|
+
import { getContextDirs, getLatestSynthesizedPlan, getArtifactManifest } from "../context.js";
|
|
8
8
|
import type { RepoInfo } from "../repo-utils.js";
|
|
9
9
|
import type { PhaseSend } from "../transition-controller.js";
|
|
10
10
|
|
|
@@ -38,13 +38,14 @@ export function reviewSystemPrompt(taskDir: string, pass: number, phase?: string
|
|
|
38
38
|
"# Your job:",
|
|
39
39
|
`1. Read ALL reviewer outputs from ${reviewsDir}/`,
|
|
40
40
|
"2. Identify valid gaps and inaccuracies that would block planning",
|
|
41
|
-
"3. If changes are needed: update
|
|
41
|
+
"3. If changes are needed: update the content within existing sections of USER_REQUEST.md / RESEARCH.md, and add/update artifacts/ files as needed",
|
|
42
42
|
"4. If reviewers found no actionable gaps (e.g. task already done, minor suggestions only): do NOT modify the files",
|
|
43
43
|
"5. Ignore suggestions that don't affect downstream planning quality",
|
|
44
44
|
"",
|
|
45
45
|
"USER_REQUEST.md MUST keep exactly: # User Request, ## Problem, ## Constraints",
|
|
46
46
|
"RESEARCH.md MUST keep exactly: ## Affected Code, ## Architecture Context, ## Constraints & Edge Cases, ## Open Questions (optional)",
|
|
47
47
|
"Any other sections will fail validation.",
|
|
48
|
+
"Use pp_write_state_file / pp_edit_state_file (NOT the generic write/edit) for these .pp state files — they keep the output compact and validate structure.",
|
|
48
49
|
].join("\n");
|
|
49
50
|
}
|
|
50
51
|
|
|
@@ -159,7 +160,7 @@ export async function spawnCodeReviewers(
|
|
|
159
160
|
const agent = createCodeReviewerAgent(
|
|
160
161
|
variant,
|
|
161
162
|
reviewerVariants,
|
|
162
|
-
{ userRequest, research, synthesizedPlan },
|
|
163
|
+
{ userRequest, research, synthesizedPlan, manifest: getArtifactManifest(taskDir) },
|
|
163
164
|
outputPath,
|
|
164
165
|
contextDirs,
|
|
165
166
|
reviewerPhase,
|
|
@@ -46,7 +46,8 @@ export function cancelPendingPlannotatorWait(orchestrator: Orchestrator): void {
|
|
|
46
46
|
export function waitForPlannotatorResult(
|
|
47
47
|
orchestrator: Orchestrator,
|
|
48
48
|
reviewId: string | null,
|
|
49
|
-
|
|
49
|
+
timeoutMs: number | null = PLANNOTATOR_RESULT_TIMEOUT_MS,
|
|
50
|
+
): Promise<{ approved: boolean; feedback?: string; error?: string }> {
|
|
50
51
|
cancelPendingPlannotatorWait(orchestrator);
|
|
51
52
|
const pi = orchestrator.pi;
|
|
52
53
|
return new Promise((resolve, reject) => {
|
|
@@ -54,7 +55,7 @@ export function waitForPlannotatorResult(
|
|
|
54
55
|
const unsub = pi.events.on("plannotator:review-result", (data: any) => {
|
|
55
56
|
if (reviewId && data?.reviewId && data.reviewId !== reviewId) return;
|
|
56
57
|
cleanup();
|
|
57
|
-
resolve({ approved: !!data?.approved, feedback: data?.feedback });
|
|
58
|
+
resolve({ approved: !!data?.approved, feedback: data?.feedback, error: data?.error });
|
|
58
59
|
});
|
|
59
60
|
orchestrator.plannotatorUnsub = unsub;
|
|
60
61
|
function cleanup() {
|
|
@@ -66,9 +67,11 @@ export function waitForPlannotatorResult(
|
|
|
66
67
|
orchestrator.plannotatorTimer = null;
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
if (timeoutMs !== null) {
|
|
71
|
+
orchestrator.plannotatorTimer = setTimeout(() => {
|
|
72
|
+
cleanup();
|
|
73
|
+
reject(new Error("Plannotator review timed out"));
|
|
74
|
+
}, timeoutMs);
|
|
75
|
+
}
|
|
73
76
|
});
|
|
74
77
|
}
|